Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
e865188a
Commit
e865188a
authored
Sep 22, 2021
by
Jacques
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add text viewer feature flag
Added a FF for text viewer as part of the refactor
parent
314a36ab
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
72 additions
and
72 deletions
+72
-72
app/assets/javascripts/blob/components/blob_content.vue
app/assets/javascripts/blob/components/blob_content.vue
+6
-0
app/assets/javascripts/repository/components/blob_content_viewer.vue
...javascripts/repository/components/blob_content_viewer.vue
+27
-9
app/assets/javascripts/repository/components/blob_viewers/index.js
...s/javascripts/repository/components/blob_viewers/index.js
+3
-1
app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js
.../javascripts/vue_shared/components/blob_viewers/mixins.js
+5
-0
app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
...ipts/vue_shared/components/blob_viewers/simple_viewer.vue
+2
-13
app/controllers/projects/blob_controller.rb
app/controllers/projects/blob_controller.rb
+1
-0
app/controllers/projects_controller.rb
app/controllers/projects_controller.rb
+1
-0
config/feature_flags/development/refactor_text_viewer.yml
config/feature_flags/development/refactor_text_viewer.yml
+8
-0
spec/frontend/repository/components/blob_content_viewer_spec.js
...rontend/repository/components/blob_content_viewer_spec.js
+18
-11
spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js
.../vue_shared/components/blob_viewers/simple_viewer_spec.js
+1
-38
No files found.
app/assets/javascripts/blob/components/blob_content.vue
View file @
e865188a
...
@@ -41,6 +41,11 @@ export default {
...
@@ -41,6 +41,11 @@ export default {
type
:
Object
,
type
:
Object
,
required
:
true
,
required
:
true
,
},
},
hideLineNumbers
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
},
computed
:
{
computed
:
{
viewer
()
{
viewer
()
{
...
@@ -80,6 +85,7 @@ export default {
...
@@ -80,6 +85,7 @@ export default {
:is-raw-content=
"isRawContent"
:is-raw-content=
"isRawContent"
:file-name=
"blob.name"
:file-name=
"blob.name"
:type=
"activeViewer.fileType"
:type=
"activeViewer.fileType"
:hide-line-numbers=
"hideLineNumbers"
data-qa-selector=
"file_content"
data-qa-selector=
"file_content"
/>
/>
</
template
>
</
template
>
...
...
app/assets/javascripts/repository/components/blob_content_viewer.vue
View file @
e865188a
...
@@ -42,9 +42,6 @@ export default {
...
@@ -42,9 +42,6 @@ export default {
this
.
switchViewer
(
this
.
switchViewer
(
this
.
hasRichViewer
&&
!
window
.
location
.
hash
?
RICH_BLOB_VIEWER
:
SIMPLE_BLOB_VIEWER
,
this
.
hasRichViewer
&&
!
window
.
location
.
hash
?
RICH_BLOB_VIEWER
:
SIMPLE_BLOB_VIEWER
,
);
);
if
(
this
.
hasRichViewer
&&
!
this
.
blobViewer
)
{
this
.
loadLegacyViewer
();
}
},
},
error
()
{
error
()
{
this
.
displayError
();
this
.
displayError
();
...
@@ -69,6 +66,7 @@ export default {
...
@@ -69,6 +66,7 @@ export default {
data
()
{
data
()
{
return
{
return
{
legacyRichViewer
:
null
,
legacyRichViewer
:
null
,
legacySimpleViewer
:
null
,
isBinary
:
false
,
isBinary
:
false
,
isLoadingLegacyViewer
:
false
,
isLoadingLegacyViewer
:
false
,
activeViewerType
:
SIMPLE_BLOB_VIEWER
,
activeViewerType
:
SIMPLE_BLOB_VIEWER
,
...
@@ -115,7 +113,7 @@ export default {
...
@@ -115,7 +113,7 @@ export default {
return
isLoggedIn
();
return
isLoggedIn
();
},
},
isLoading
()
{
isLoading
()
{
return
this
.
$apollo
.
queries
.
project
.
loading
||
this
.
isLoadingLegacyViewer
;
return
this
.
$apollo
.
queries
.
project
.
loading
;
},
},
isBinaryFileType
()
{
isBinaryFileType
()
{
return
this
.
isBinary
||
this
.
blobInfo
.
simpleViewer
?.
fileType
!==
'
text
'
;
return
this
.
isBinary
||
this
.
blobInfo
.
simpleViewer
?.
fileType
!==
'
text
'
;
...
@@ -153,22 +151,41 @@ export default {
...
@@ -153,22 +151,41 @@ export default {
},
},
},
},
methods
:
{
methods
:
{
loadLegacyViewer
()
{
loadLegacyViewer
(
type
)
{
if
(
this
.
legacyViewerLoaded
(
type
))
{
return
;
}
this
.
isLoadingLegacyViewer
=
true
;
this
.
isLoadingLegacyViewer
=
true
;
axios
axios
.
get
(
`
${
this
.
blobInfo
.
webPath
}
?format=json&viewer=
rich
`
)
.
get
(
`
${
this
.
blobInfo
.
webPath
}
?format=json&viewer=
${
type
}
`
)
.
then
(({
data
:
{
html
,
binary
}
})
=>
{
.
then
(({
data
:
{
html
,
binary
}
})
=>
{
this
.
legacyRichViewer
=
html
;
if
(
type
===
'
simple
'
)
{
this
.
legacySimpleViewer
=
html
;
}
else
{
this
.
legacyRichViewer
=
html
;
}
this
.
isBinary
=
binary
;
this
.
isBinary
=
binary
;
this
.
isLoadingLegacyViewer
=
false
;
this
.
isLoadingLegacyViewer
=
false
;
})
})
.
catch
(()
=>
this
.
displayError
());
.
catch
(()
=>
this
.
displayError
());
},
},
legacyViewerLoaded
(
type
)
{
return
(
(
type
===
SIMPLE_BLOB_VIEWER
&&
this
.
legacySimpleViewer
)
||
(
type
===
RICH_BLOB_VIEWER
&&
this
.
legacyRichViewer
)
);
},
displayError
()
{
displayError
()
{
createFlash
({
message
:
__
(
'
An error occurred while loading the file. Please try again.
'
)
});
createFlash
({
message
:
__
(
'
An error occurred while loading the file. Please try again.
'
)
});
},
},
switchViewer
(
newViewer
)
{
switchViewer
(
newViewer
)
{
this
.
activeViewerType
=
newViewer
||
SIMPLE_BLOB_VIEWER
;
this
.
activeViewerType
=
newViewer
||
SIMPLE_BLOB_VIEWER
;
if
(
!
this
.
blobViewer
)
{
this
.
loadLegacyViewer
(
this
.
activeViewerType
);
}
},
},
},
},
};
};
...
@@ -210,10 +227,11 @@ export default {
...
@@ -210,10 +227,11 @@ export default {
v-if=
"!blobViewer"
v-if=
"!blobViewer"
:rich-viewer=
"legacyRichViewer"
:rich-viewer=
"legacyRichViewer"
:blob=
"blobInfo"
:blob=
"blobInfo"
:content=
"
blobInfo.rawTextBlob
"
:content=
"
legacySimpleViewer
"
:is-raw-content=
"true"
:is-raw-content=
"true"
:active-viewer=
"viewer"
:active-viewer=
"viewer"
:loading=
"false"
:hide-line-numbers=
"true"
:loading=
"isLoadingLegacyViewer"
/>
/>
<component
:is=
"blobViewer"
v-else
v-bind=
"viewerProps"
class=
"blob-viewer"
/>
<component
:is=
"blobViewer"
v-else
v-bind=
"viewerProps"
class=
"blob-viewer"
/>
</div>
</div>
...
...
app/assets/javascripts/repository/components/blob_viewers/index.js
View file @
e865188a
...
@@ -3,7 +3,9 @@ export const loadViewer = (type) => {
...
@@ -3,7 +3,9 @@ export const loadViewer = (type) => {
case
'
empty
'
:
case
'
empty
'
:
return
()
=>
import
(
/* webpackChunkName: 'blob_empty_viewer' */
'
./empty_viewer.vue
'
);
return
()
=>
import
(
/* webpackChunkName: 'blob_empty_viewer' */
'
./empty_viewer.vue
'
);
case
'
text
'
:
case
'
text
'
:
return
()
=>
import
(
/* webpackChunkName: 'blob_text_viewer' */
'
./text_viewer.vue
'
);
return
gon
.
features
.
refactorTextViewer
?
()
=>
import
(
/* webpackChunkName: 'blob_text_viewer' */
'
./text_viewer.vue
'
)
:
null
;
case
'
download
'
:
case
'
download
'
:
return
()
=>
import
(
/* webpackChunkName: 'blob_download_viewer' */
'
./download_viewer.vue
'
);
return
()
=>
import
(
/* webpackChunkName: 'blob_download_viewer' */
'
./download_viewer.vue
'
);
case
'
image
'
:
case
'
image
'
:
...
...
app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js
View file @
e865188a
...
@@ -27,6 +27,11 @@ export default {
...
@@ -27,6 +27,11 @@ export default {
required
:
false
,
required
:
false
,
default
:
''
,
default
:
''
,
},
},
hideLineNumbers
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
},
mounted
()
{
mounted
()
{
eventHub
.
$emit
(
SNIPPET_MEASURE_BLOBS_CONTENT
);
eventHub
.
$emit
(
SNIPPET_MEASURE_BLOBS_CONTENT
);
...
...
app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
View file @
e865188a
...
@@ -8,8 +8,6 @@ export default {
...
@@ -8,8 +8,6 @@ export default {
name
:
'
SimpleViewer
'
,
name
:
'
SimpleViewer
'
,
components
:
{
components
:
{
GlIcon
,
GlIcon
,
SourceEditor
:
()
=>
import
(
/* webpackChunkName: 'SourceEditor' */
'
~/vue_shared/components/source_editor.vue
'
),
},
},
mixins
:
[
ViewerMixin
,
glFeatureFlagsMixin
()],
mixins
:
[
ViewerMixin
,
glFeatureFlagsMixin
()],
inject
:
[
'
blobHash
'
],
inject
:
[
'
blobHash
'
],
...
@@ -22,9 +20,6 @@ export default {
...
@@ -22,9 +20,6 @@ export default {
lineNumbers
()
{
lineNumbers
()
{
return
this
.
content
.
split
(
'
\n
'
).
length
;
return
this
.
content
.
split
(
'
\n
'
).
length
;
},
},
refactorBlobViewerEnabled
()
{
return
this
.
glFeatures
.
refactorBlobViewer
;
},
},
},
mounted
()
{
mounted
()
{
const
{
hash
}
=
window
.
location
;
const
{
hash
}
=
window
.
location
;
...
@@ -52,14 +47,8 @@ export default {
...
@@ -52,14 +47,8 @@ export default {
</
script
>
</
script
>
<
template
>
<
template
>
<div>
<div>
<source-editor
<div
class=
"file-content code js-syntax-highlight"
:class=
"$options.userColorScheme"
>
v-if=
"isRawContent && refactorBlobViewerEnabled"
<div
v-if=
"!hideLineNumbers"
class=
"line-numbers"
>
:value=
"content"
:file-name=
"fileName"
:editor-options=
"
{ readOnly: true }"
/>
<div
v-else
class=
"file-content code js-syntax-highlight"
:class=
"$options.userColorScheme"
>
<div
class=
"line-numbers"
>
<a
<a
v-for=
"line in lineNumbers"
v-for=
"line in lineNumbers"
:id=
"`L$
{line}`"
:id=
"`L$
{line}`"
...
...
app/controllers/projects/blob_controller.rb
View file @
e865188a
...
@@ -43,6 +43,7 @@ class Projects::BlobController < Projects::ApplicationController
...
@@ -43,6 +43,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action
do
before_action
do
push_frontend_feature_flag
(
:refactor_blob_viewer
,
@project
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:refactor_blob_viewer
,
@project
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:refactor_text_viewer
,
@project
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:consolidated_edit_button
,
@project
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:consolidated_edit_button
,
@project
,
default_enabled: :yaml
)
push_licensed_feature
(
:file_locks
)
if
@project
.
licensed_feature_available?
(
:file_locks
)
push_licensed_feature
(
:file_locks
)
if
@project
.
licensed_feature_available?
(
:file_locks
)
end
end
...
...
app/controllers/projects_controller.rb
View file @
e865188a
...
@@ -33,6 +33,7 @@ class ProjectsController < Projects::ApplicationController
...
@@ -33,6 +33,7 @@ class ProjectsController < Projects::ApplicationController
before_action
do
before_action
do
push_frontend_feature_flag
(
:refactor_blob_viewer
,
@project
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:refactor_blob_viewer
,
@project
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:refactor_text_viewer
,
@project
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:increase_page_size_exponentially
,
@project
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:increase_page_size_exponentially
,
@project
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:paginated_tree_graphql_query
,
@project
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:paginated_tree_graphql_query
,
@project
,
default_enabled: :yaml
)
end
end
...
...
config/feature_flags/development/refactor_text_viewer.yml
0 → 100644
View file @
e865188a
---
name
:
refactor_text_viewer
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70909
rollout_issue_url
:
milestone
:
'
14.4'
type
:
development
group
:
'
group::source
code'
default_enabled
:
false
spec/frontend/repository/components/blob_content_viewer_spec.js
View file @
e865188a
...
@@ -159,8 +159,13 @@ describe('Blob content viewer component', () => {
...
@@ -159,8 +159,13 @@ describe('Blob content viewer component', () => {
const
findBlobContent
=
()
=>
wrapper
.
findComponent
(
BlobContent
);
const
findBlobContent
=
()
=>
wrapper
.
findComponent
(
BlobContent
);
const
findBlobButtonGroup
=
()
=>
wrapper
.
findComponent
(
BlobButtonGroup
);
const
findBlobButtonGroup
=
()
=>
wrapper
.
findComponent
(
BlobButtonGroup
);
beforeEach
(()
=>
{
gon
.
features
=
{
refactorTextViewer
:
true
};
});
afterEach
(()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
.
destroy
();
mockAxios
.
reset
();
});
});
it
(
'
renders a GlLoadingIcon component
'
,
()
=>
{
it
(
'
renders a GlLoadingIcon component
'
,
()
=>
{
...
@@ -183,7 +188,6 @@ describe('Blob content viewer component', () => {
...
@@ -183,7 +188,6 @@ describe('Blob content viewer component', () => {
it
(
'
renders a BlobContent component
'
,
()
=>
{
it
(
'
renders a BlobContent component
'
,
()
=>
{
expect
(
findBlobContent
().
props
(
'
loading
'
)).
toEqual
(
false
);
expect
(
findBlobContent
().
props
(
'
loading
'
)).
toEqual
(
false
);
expect
(
findBlobContent
().
props
(
'
content
'
)).
toEqual
(
'
raw content
'
);
expect
(
findBlobContent
().
props
(
'
isRawContent
'
)).
toBe
(
true
);
expect
(
findBlobContent
().
props
(
'
isRawContent
'
)).
toBe
(
true
);
expect
(
findBlobContent
().
props
(
'
activeViewer
'
)).
toEqual
({
expect
(
findBlobContent
().
props
(
'
activeViewer
'
)).
toEqual
({
fileType
:
'
text
'
,
fileType
:
'
text
'
,
...
@@ -192,6 +196,16 @@ describe('Blob content viewer component', () => {
...
@@ -192,6 +196,16 @@ describe('Blob content viewer component', () => {
renderError
:
null
,
renderError
:
null
,
});
});
});
});
describe
(
'
legacy viewers
'
,
()
=>
{
it
(
'
loads a legacy viewer when a viewer component is not available
'
,
async
()
=>
{
createComponentWithApollo
({
blobs
:
{
...
simpleMockData
,
fileType
:
'
unknown
'
}
});
await
waitForPromises
();
expect
(
mockAxios
.
history
.
get
).
toHaveLength
(
1
);
expect
(
mockAxios
.
history
.
get
[
0
].
url
).
toEqual
(
'
some_file.js?format=json&viewer=simple
'
);
});
});
});
});
describe
(
'
rich viewer
'
,
()
=>
{
describe
(
'
rich viewer
'
,
()
=>
{
...
@@ -210,7 +224,6 @@ describe('Blob content viewer component', () => {
...
@@ -210,7 +224,6 @@ describe('Blob content viewer component', () => {
it
(
'
renders a BlobContent component
'
,
()
=>
{
it
(
'
renders a BlobContent component
'
,
()
=>
{
expect
(
findBlobContent
().
props
(
'
loading
'
)).
toEqual
(
false
);
expect
(
findBlobContent
().
props
(
'
loading
'
)).
toEqual
(
false
);
expect
(
findBlobContent
().
props
(
'
content
'
)).
toEqual
(
'
raw content
'
);
expect
(
findBlobContent
().
props
(
'
isRawContent
'
)).
toBe
(
true
);
expect
(
findBlobContent
().
props
(
'
isRawContent
'
)).
toBe
(
true
);
expect
(
findBlobContent
().
props
(
'
activeViewer
'
)).
toEqual
({
expect
(
findBlobContent
().
props
(
'
activeViewer
'
)).
toEqual
({
fileType
:
'
markup
'
,
fileType
:
'
markup
'
,
...
@@ -241,18 +254,12 @@ describe('Blob content viewer component', () => {
...
@@ -241,18 +254,12 @@ describe('Blob content viewer component', () => {
});
});
describe
(
'
legacy viewers
'
,
()
=>
{
describe
(
'
legacy viewers
'
,
()
=>
{
it
(
'
does not load a legacy viewer when a rich viewer is not available
'
,
async
()
=>
{
it
(
'
loads a legacy viewer when a viewer component is not available
'
,
async
()
=>
{
createComponentWithApollo
({
blobs
:
simpleMockData
});
createComponentWithApollo
({
blobs
:
{
...
richMockData
,
fileType
:
'
unknown
'
}
});
await
waitForPromises
();
expect
(
mockAxios
.
history
.
get
).
toHaveLength
(
0
);
});
it
(
'
loads a legacy viewer when a rich viewer is available
'
,
async
()
=>
{
createComponentWithApollo
({
blobs
:
richMockData
});
await
waitForPromises
();
await
waitForPromises
();
expect
(
mockAxios
.
history
.
get
).
toHaveLength
(
1
);
expect
(
mockAxios
.
history
.
get
).
toHaveLength
(
1
);
expect
(
mockAxios
.
history
.
get
[
0
].
url
).
toEqual
(
'
some_file.js?format=json&viewer=rich
'
);
});
});
});
});
...
...
spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js
View file @
e865188a
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
{
HIGHLIGHT_CLASS_NAME
}
from
'
~/vue_shared/components/blob_viewers/constants
'
;
import
{
HIGHLIGHT_CLASS_NAME
}
from
'
~/vue_shared/components/blob_viewers/constants
'
;
import
SimpleViewer
from
'
~/vue_shared/components/blob_viewers/simple_viewer.vue
'
;
import
SimpleViewer
from
'
~/vue_shared/components/blob_viewers/simple_viewer.vue
'
;
import
SourceEditor
from
'
~/vue_shared/components/source_editor.vue
'
;
describe
(
'
Blob Simple Viewer component
'
,
()
=>
{
describe
(
'
Blob Simple Viewer component
'
,
()
=>
{
let
wrapper
;
let
wrapper
;
const
contentMock
=
`<span id="LC1">First</span>\n<span id="LC2">Second</span>\n<span id="LC3">Third</span>`
;
const
contentMock
=
`<span id="LC1">First</span>\n<span id="LC2">Second</span>\n<span id="LC3">Third</span>`
;
const
blobHash
=
'
foo-bar
'
;
const
blobHash
=
'
foo-bar
'
;
function
createComponent
(
function
createComponent
(
content
=
contentMock
,
isRawContent
=
false
)
{
content
=
contentMock
,
isRawContent
=
false
,
isRefactorFlagEnabled
=
false
,
)
{
wrapper
=
shallowMount
(
SimpleViewer
,
{
wrapper
=
shallowMount
(
SimpleViewer
,
{
provide
:
{
provide
:
{
blobHash
,
blobHash
,
glFeatures
:
{
refactorBlobViewer
:
isRefactorFlagEnabled
,
},
},
},
propsData
:
{
propsData
:
{
content
,
content
,
...
@@ -94,32 +85,4 @@ describe('Blob Simple Viewer component', () => {
...
@@ -94,32 +85,4 @@ describe('Blob Simple Viewer component', () => {
});
});
});
});
});
});
describe
(
'
Vue refactoring to use Source Editor
'
,
()
=>
{
const
findSourceEditor
=
()
=>
wrapper
.
find
(
SourceEditor
);
it
.
each
`
doesRender | condition | isRawContent | isRefactorFlagEnabled
${
'
Does not
'
}
|
${
'
rawContent is not specified
'
}
|
${
false
}
|
${
true
}
${
'
Does not
'
}
|
${
'
feature flag is disabled is not specified
'
}
|
${
true
}
|
${
false
}
${
'
Does not
'
}
|
${
'
both, the FF and rawContent are not specified
'
}
|
${
false
}
|
${
false
}
${
'
Does
'
}
|
${
'
both, the FF and rawContent are specified
'
}
|
${
true
}
|
${
true
}
`
(
'
$doesRender render Source Editor component in readonly mode when $condition
'
,
async
({
isRawContent
,
isRefactorFlagEnabled
}
=
{})
=>
{
createComponent
(
'
raw content
'
,
isRawContent
,
isRefactorFlagEnabled
);
await
waitForPromises
();
if
(
isRawContent
&&
isRefactorFlagEnabled
)
{
expect
(
findSourceEditor
().
exists
()).
toBe
(
true
);
expect
(
findSourceEditor
().
props
(
'
value
'
)).
toBe
(
'
raw content
'
);
expect
(
findSourceEditor
().
props
(
'
fileName
'
)).
toBe
(
'
test.js
'
);
expect
(
findSourceEditor
().
props
(
'
editorOptions
'
)).
toEqual
({
readOnly
:
true
});
}
else
{
expect
(
findSourceEditor
().
exists
()).
toBe
(
false
);
}
},
);
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment