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
46f810a3
Commit
46f810a3
authored
Oct 20, 2021
by
Frédéric Caplette
Committed by
Savas Vedova
Oct 20, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Style the CI Lint link in the pipeline page error as a link
parent
f0dbb59b
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
133 additions
and
38 deletions
+133
-38
app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
...ripts/pipeline_editor/components/pipeline_editor_tabs.vue
+30
-1
app/assets/javascripts/pipeline_editor/constants.js
app/assets/javascripts/pipeline_editor/constants.js
+7
-0
app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
...sets/javascripts/pipeline_editor/pipeline_editor_home.vue
+1
-1
app/views/projects/pipelines/show.html.haml
app/views/projects/pipelines/show.html.haml
+2
-2
spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
...d/pipeline_editor/components/pipeline_editor_tabs_spec.js
+57
-1
spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
+36
-32
spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
+0
-1
No files found.
app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
View file @
46f810a3
...
...
@@ -3,6 +3,7 @@ import { GlAlert, GlLoadingIcon, GlTabs } from '@gitlab/ui';
import
{
s__
}
from
'
~/locale
'
;
import
PipelineGraph
from
'
~/pipelines/components/pipeline_graph/pipeline_graph.vue
'
;
import
glFeatureFlagsMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
{
getParameterValues
,
setUrlParams
,
updateHistory
}
from
'
~/lib/utils/url_utility
'
;
import
{
CREATE_TAB
,
EDITOR_APP_STATUS_EMPTY
,
...
...
@@ -12,6 +13,8 @@ import {
EDITOR_APP_STATUS_VALID
,
LINT_TAB
,
MERGED_TAB
,
TAB_QUERY_PARAM
,
TABS_INDEX
,
VISUALIZE_TAB
,
}
from
'
../constants
'
;
import
getAppStatus
from
'
../graphql/queries/client/app_status.graphql
'
;
...
...
@@ -42,6 +45,9 @@ export default {
errorTexts
:
{
loadMergedYaml
:
s__
(
'
Pipelines|Could not load merged YAML content
'
),
},
query
:
{
TAB_QUERY_PARAM
,
},
tabConstants
:
{
CREATE_TAB
,
LINT_TAB
,
...
...
@@ -98,15 +104,38 @@ export default {
return
this
.
appStatus
===
EDITOR_APP_STATUS_LOADING
;
},
},
created
()
{
const
[
tabQueryParam
]
=
getParameterValues
(
TAB_QUERY_PARAM
);
if
(
tabQueryParam
&&
TABS_INDEX
[
tabQueryParam
])
{
this
.
setDefaultTab
(
tabQueryParam
);
}
},
methods
:
{
setCurrentTab
(
tabName
)
{
this
.
$emit
(
'
set-current-tab
'
,
tabName
);
},
setDefaultTab
(
tabName
)
{
// We associate tab name with the index so that we can use tab name
// in other part of the app and load the corresponding tab closer to the
// actual component using a hash that binds the name to the indexes.
// This also means that if we ever changed tab order, we would justs need to
// update `TABS_INDEX` hash instead of all the instances in the app
// where we used the individual indexes
const
newUrl
=
setUrlParams
({
[
TAB_QUERY_PARAM
]:
TABS_INDEX
[
tabName
]
});
this
.
setCurrentTab
(
tabName
);
updateHistory
({
url
:
newUrl
,
title
:
document
.
title
,
replace
:
true
});
},
},
};
</
script
>
<
template
>
<gl-tabs
class=
"file-editor gl-mb-3"
>
<gl-tabs
class=
"file-editor gl-mb-3"
:query-param-name=
"$options.query.TAB_QUERY_PARAM"
sync-active-tab-with-query-params
>
<editor-tab
class=
"gl-mb-3"
:title=
"$options.i18n.tabEdit"
...
...
app/assets/javascripts/pipeline_editor/constants.js
View file @
46f810a3
...
...
@@ -22,7 +22,14 @@ export const LINT_TAB = 'LINT_TAB';
export
const
MERGED_TAB
=
'
MERGED_TAB
'
;
export
const
VISUALIZE_TAB
=
'
VISUALIZE_TAB
'
;
export
const
TABS_INDEX
=
{
[
CREATE_TAB
]:
'
0
'
,
[
VISUALIZE_TAB
]:
'
1
'
,
[
LINT_TAB
]:
'
2
'
,
[
MERGED_TAB
]:
'
3
'
,
};
export
const
TABS_WITH_COMMIT_FORM
=
[
CREATE_TAB
,
LINT_TAB
,
VISUALIZE_TAB
];
export
const
TAB_QUERY_PARAM
=
'
tab
'
;
export
const
COMMIT_ACTION_CREATE
=
'
CREATE
'
;
export
const
COMMIT_ACTION_UPDATE
=
'
UPDATE
'
;
...
...
app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
View file @
46f810a3
...
...
@@ -4,7 +4,7 @@ import PipelineEditorDrawer from './components/drawer/pipeline_editor_drawer.vue
import
PipelineEditorFileNav
from
'
./components/file_nav/pipeline_editor_file_nav.vue
'
;
import
PipelineEditorHeader
from
'
./components/header/pipeline_editor_header.vue
'
;
import
PipelineEditorTabs
from
'
./components/pipeline_editor_tabs.vue
'
;
import
{
TABS_WITH_COMMIT_FORM
,
CREATE_TAB
}
from
'
./constants
'
;
import
{
CREATE_TAB
,
TABS_WITH_COMMIT_FORM
}
from
'
./constants
'
;
export
default
{
components
:
{
...
...
app/views/projects/pipelines/show.html.haml
View file @
46f810a3
...
...
@@ -22,8 +22,8 @@
%ul
-
@pipeline
.
yaml_errors
.
split
(
","
).
each
do
|
error
|
%li
=
error
-
lint_link_url
=
project_ci_
lint_path
(
@project
)
-
lint_link_start
=
'<a href="%{url}">'
.
html_safe
%
{
url:
lint_link_url
}
-
lint_link_url
=
project_ci_
pipeline_editor_path
(
@project
,
tab:
"LINT_TAB"
)
-
lint_link_start
=
'<a href="%{url}"
class="gl-text-blue-500!"
>'
.
html_safe
%
{
url:
lint_link_url
}
=
s_
(
'You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}'
).
html_safe
%
{
gitlab_ci_yml:
'.gitlab-ci.yml'
,
lint_link_start:
lint_link_start
,
lint_link_end:
'</a>'
.
html_safe
}
=
render
"projects/pipelines/with_tabs"
,
pipeline:
@pipeline
,
stages:
@stages
,
pipeline_has_errors:
pipeline_has_errors
...
...
spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
View file @
46f810a3
import
{
GlAlert
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
{
GlAlert
,
GlLoadingIcon
,
GlTabs
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
,
mount
}
from
'
@vue/test-utils
'
;
import
{
nextTick
}
from
'
vue
'
;
import
setWindowLocation
from
'
helpers/set_window_location_helper
'
;
import
CiConfigMergedPreview
from
'
~/pipeline_editor/components/editor/ci_config_merged_preview.vue
'
;
import
CiLint
from
'
~/pipeline_editor/components/lint/ci_lint.vue
'
;
import
PipelineEditorTabs
from
'
~/pipeline_editor/components/pipeline_editor_tabs.vue
'
;
import
EditorTab
from
'
~/pipeline_editor/components/ui/editor_tab.vue
'
;
import
{
CREATE_TAB
,
EDITOR_APP_STATUS_EMPTY
,
EDITOR_APP_STATUS_ERROR
,
EDITOR_APP_STATUS_LOADING
,
EDITOR_APP_STATUS_INVALID
,
EDITOR_APP_STATUS_VALID
,
MERGED_TAB
,
TAB_QUERY_PARAM
,
TABS_INDEX
,
}
from
'
~/pipeline_editor/constants
'
;
import
PipelineGraph
from
'
~/pipelines/components/pipeline_graph/pipeline_graph.vue
'
;
import
{
mockLintResponse
,
mockCiYml
}
from
'
../mock_data
'
;
...
...
@@ -53,6 +58,7 @@ describe('Pipeline editor tabs component', () => {
const
findAlert
=
()
=>
wrapper
.
findComponent
(
GlAlert
);
const
findCiLint
=
()
=>
wrapper
.
findComponent
(
CiLint
);
const
findGlTabs
=
()
=>
wrapper
.
findComponent
(
GlTabs
);
const
findLoadingIcon
=
()
=>
wrapper
.
findComponent
(
GlLoadingIcon
);
const
findPipelineGraph
=
()
=>
wrapper
.
findComponent
(
PipelineGraph
);
const
findTextEditor
=
()
=>
wrapper
.
findComponent
(
MockTextEditor
);
...
...
@@ -181,4 +187,54 @@ describe('Pipeline editor tabs component', () => {
},
);
});
describe
(
'
default tab based on url query param
'
,
()
=>
{
const
gitlabUrl
=
'
https://gitlab.test/ci/editor/
'
;
const
matchObject
=
{
hostname
:
'
gitlab.test
'
,
pathname
:
'
/ci/editor/
'
,
search
:
''
,
};
it
(
`is
${
CREATE_TAB
}
if the query param
${
TAB_QUERY_PARAM
}
is not present`
,
()
=>
{
setWindowLocation
(
gitlabUrl
);
createComponent
();
expect
(
window
.
location
).
toMatchObject
(
matchObject
);
});
it
(
`is
${
CREATE_TAB
}
tab if the query param
${
TAB_QUERY_PARAM
}
is invalid`
,
()
=>
{
const
queryValue
=
'
FOO
'
;
setWindowLocation
(
`
${
gitlabUrl
}
?
${
TAB_QUERY_PARAM
}
=
${
queryValue
}
`
);
createComponent
();
// If the query param remains unchanged, then we have ignored it.
expect
(
window
.
location
).
toMatchObject
({
...
matchObject
,
search
:
`?
${
TAB_QUERY_PARAM
}
=
${
queryValue
}
`
,
});
});
it
(
'
is the tab specified in query param and transform it into an index value
'
,
async
()
=>
{
setWindowLocation
(
`
${
gitlabUrl
}
?
${
TAB_QUERY_PARAM
}
=
${
MERGED_TAB
}
`
);
createComponent
();
// If the query param has changed to an index, it means we have synced the
// query with.
expect
(
window
.
location
).
toMatchObject
({
...
matchObject
,
search
:
`?
${
TAB_QUERY_PARAM
}
=
${
TABS_INDEX
[
MERGED_TAB
]}
`
,
});
});
});
describe
(
'
glTabs
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
();
});
it
(
'
passes the `sync-active-tab-with-query-params` prop
'
,
()
=>
{
expect
(
findGlTabs
().
props
(
'
syncActiveTabWithQueryParams
'
)).
toBe
(
true
);
});
});
});
spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
View file @
46f810a3
import
{
GlAlert
,
GlButton
,
GlLoadingIcon
,
GlTabs
}
from
'
@gitlab/ui
'
;
import
{
GlAlert
,
GlButton
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
setWindowLocation
from
'
helpers/set_window_location_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
CommitForm
from
'
~/pipeline_editor/components/commit/commit_form.vue
'
;
import
TextEditor
from
'
~/pipeline_editor/components/editor/text_editor.vue
'
;
import
PipelineEditorTabs
from
'
~/pipeline_editor/components/pipeline_editor_tabs.vue
'
;
import
PipelineEditorEmptyState
from
'
~/pipeline_editor/components/ui/pipeline_editor_empty_state.vue
'
;
...
...
@@ -35,10 +33,6 @@ import {
const
localVue
=
createLocalVue
();
localVue
.
use
(
VueApollo
);
const
MockSourceEditor
=
{
template
:
'
<div/>
'
,
};
const
mockProvide
=
{
ciConfigPath
:
mockCiConfigPath
,
defaultBranch
:
mockDefaultBranch
,
...
...
@@ -55,19 +49,15 @@ describe('Pipeline editor app component', () => {
let
mockLatestCommitShaQuery
;
let
mockPipelineQuery
;
const
createComponent
=
({
blobLoading
=
false
,
options
=
{},
provide
=
{}
}
=
{})
=>
{
const
createComponent
=
({
blobLoading
=
false
,
options
=
{},
provide
=
{},
stubs
=
{},
}
=
{})
=>
{
wrapper
=
shallowMount
(
PipelineEditorApp
,
{
provide
:
{
...
mockProvide
,
...
provide
},
stubs
:
{
GlTabs
,
GlButton
,
CommitForm
,
PipelineEditorHome
,
PipelineEditorTabs
,
PipelineEditorMessages
,
SourceEditor
:
MockSourceEditor
,
PipelineEditorEmptyState
,
},
stubs
,
data
()
{
return
{
commitSha
:
''
,
...
...
@@ -89,7 +79,7 @@ describe('Pipeline editor app component', () => {
});
};
const
createComponentWithApollo
=
async
({
props
=
{},
provide
=
{}
}
=
{})
=>
{
const
createComponentWithApollo
=
async
({
props
=
{},
provide
=
{}
,
stubs
=
{}
}
=
{})
=>
{
const
handlers
=
[
[
getBlobContent
,
mockBlobContentData
],
[
getCiConfigData
,
mockCiConfigData
],
...
...
@@ -111,7 +101,7 @@ describe('Pipeline editor app component', () => {
apolloProvider
:
mockApollo
,
};
createComponent
({
props
,
provide
,
options
});
createComponent
({
props
,
provide
,
stubs
,
options
});
return
waitForPromises
();
};
...
...
@@ -119,7 +109,6 @@ describe('Pipeline editor app component', () => {
const
findLoadingIcon
=
()
=>
wrapper
.
findComponent
(
GlLoadingIcon
);
const
findAlert
=
()
=>
wrapper
.
findComponent
(
GlAlert
);
const
findEditorHome
=
()
=>
wrapper
.
findComponent
(
PipelineEditorHome
);
const
findTextEditor
=
()
=>
wrapper
.
findComponent
(
TextEditor
);
const
findEmptyState
=
()
=>
wrapper
.
findComponent
(
PipelineEditorEmptyState
);
const
findEmptyStateButton
=
()
=>
wrapper
.
findComponent
(
PipelineEditorEmptyState
).
findComponent
(
GlButton
);
...
...
@@ -141,7 +130,7 @@ describe('Pipeline editor app component', () => {
createComponent
({
blobLoading
:
true
});
expect
(
findLoadingIcon
().
exists
()).
toBe
(
true
);
expect
(
find
TextEditor
().
exists
()).
toBe
(
false
);
expect
(
find
EditorHome
().
exists
()).
toBe
(
false
);
});
});
...
...
@@ -185,7 +174,11 @@ describe('Pipeline editor app component', () => {
describe
(
'
when no CI config file exists
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockBlobContentData
.
mockResolvedValue
(
mockBlobContentQueryResponseNoCiFile
);
await
createComponentWithApollo
();
await
createComponentWithApollo
({
stubs
:
{
PipelineEditorEmptyState
,
},
});
jest
.
spyOn
(
wrapper
.
vm
.
$apollo
.
queries
.
commitSha
,
'
startPolling
'
)
...
...
@@ -207,7 +200,11 @@ describe('Pipeline editor app component', () => {
const
loadUnknownFailureText
=
'
The CI configuration was not loaded, please try again.
'
;
mockBlobContentData
.
mockRejectedValueOnce
(
new
Error
(
'
My error!
'
));
await
createComponentWithApollo
();
await
createComponentWithApollo
({
stubs
:
{
PipelineEditorMessages
,
},
});
expect
(
findEmptyState
().
exists
()).
toBe
(
false
);
...
...
@@ -222,15 +219,20 @@ describe('Pipeline editor app component', () => {
mockBlobContentData
.
mockResolvedValue
(
mockBlobContentQueryResponseNoCiFile
);
mockLatestCommitShaQuery
.
mockResolvedValue
(
mockEmptyCommitShaResults
);
await
createComponentWithApollo
();
await
createComponentWithApollo
({
stubs
:
{
PipelineEditorHome
,
PipelineEditorEmptyState
,
},
});
expect
(
findEmptyState
().
exists
()).
toBe
(
true
);
expect
(
find
TextEditor
().
exists
()).
toBe
(
false
);
expect
(
find
EditorHome
().
exists
()).
toBe
(
false
);
await
findEmptyStateButton
().
vm
.
$emit
(
'
click
'
);
expect
(
findEmptyState
().
exists
()).
toBe
(
false
);
expect
(
find
TextEditor
().
exists
()).
toBe
(
true
);
expect
(
find
EditorHome
().
exists
()).
toBe
(
true
);
});
});
...
...
@@ -241,7 +243,7 @@ describe('Pipeline editor app component', () => {
describe
(
'
and the commit mutation succeeds
'
,
()
=>
{
beforeEach
(
async
()
=>
{
window
.
scrollTo
=
jest
.
fn
();
await
createComponentWithApollo
();
await
createComponentWithApollo
(
{
stubs
:
{
PipelineEditorMessages
}
}
);
findEditorHome
().
vm
.
$emit
(
'
commit
'
,
{
type
:
COMMIT_SUCCESS
});
});
...
...
@@ -295,7 +297,7 @@ describe('Pipeline editor app component', () => {
beforeEach
(
async
()
=>
{
window
.
scrollTo
=
jest
.
fn
();
await
createComponentWithApollo
();
await
createComponentWithApollo
(
{
stubs
:
{
PipelineEditorMessages
}
}
);
findEditorHome
().
vm
.
$emit
(
'
showError
'
,
{
type
:
COMMIT_FAILURE
,
...
...
@@ -319,7 +321,7 @@ describe('Pipeline editor app component', () => {
beforeEach
(
async
()
=>
{
window
.
scrollTo
=
jest
.
fn
();
await
createComponentWithApollo
();
await
createComponentWithApollo
(
{
stubs
:
{
PipelineEditorMessages
}
}
);
findEditorHome
().
vm
.
$emit
(
'
showError
'
,
{
type
:
COMMIT_FAILURE
,
...
...
@@ -386,7 +388,9 @@ describe('Pipeline editor app component', () => {
});
it
(
'
renders the given template
'
,
async
()
=>
{
await
createComponentWithApollo
();
await
createComponentWithApollo
({
stubs
:
{
PipelineEditorHome
,
PipelineEditorTabs
},
});
expect
(
mockGetTemplate
).
toHaveBeenCalledWith
({
projectPath
:
mockProjectFullPath
,
...
...
@@ -394,7 +398,7 @@ describe('Pipeline editor app component', () => {
});
expect
(
findEmptyState
().
exists
()).
toBe
(
false
);
expect
(
find
TextEditor
().
exists
()).
toBe
(
true
);
expect
(
find
EditorHome
().
exists
()).
toBe
(
true
);
});
});
});
spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
View file @
46f810a3
...
...
@@ -39,7 +39,6 @@ describe('Pipeline editor home wrapper', () => {
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
describe
(
'
renders
'
,
()
=>
{
...
...
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