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
89a9e6c5
Commit
89a9e6c5
authored
Dec 20, 2021
by
Frédéric Caplette
Committed by
Natalia Tepluhina
Dec 20, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Notify users about unavailable linter in the linter widget
parent
74f64ebd
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
228 additions
and
140 deletions
+228
-140
app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
.../pipeline_editor/components/header/validation_segment.vue
+16
-1
app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
...ripts/pipeline_editor/components/pipeline_editor_tabs.vue
+7
-0
app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue
.../javascripts/pipeline_editor/components/ui/editor_tab.vue
+14
-3
app/assets/javascripts/pipeline_editor/constants.js
app/assets/javascripts/pipeline_editor/constants.js
+2
-0
app/assets/javascripts/pipeline_editor/index.js
app/assets/javascripts/pipeline_editor/index.js
+2
-0
app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
...ssets/javascripts/pipeline_editor/pipeline_editor_app.vue
+30
-13
app/helpers/ci/pipeline_editor_helper.rb
app/helpers/ci/pipeline_editor_helper.rb
+1
-0
doc/ci/pipeline_editor/index.md
doc/ci/pipeline_editor/index.md
+17
-0
locale/gitlab.pot
locale/gitlab.pot
+6
-0
spec/frontend/pipeline_editor/components/header/validation_segment_spec.js
...eline_editor/components/header/validation_segment_spec.js
+32
-1
spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js
...frontend/pipeline_editor/components/ui/editor_tab_spec.js
+69
-20
spec/frontend/pipeline_editor/mock_data.js
spec/frontend/pipeline_editor/mock_data.js
+1
-0
spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
+29
-102
spec/helpers/ci/pipeline_editor_helper_spec.rb
spec/helpers/ci/pipeline_editor_helper_spec.rb
+2
-0
No files found.
app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
View file @
89a9e6c5
...
...
@@ -5,6 +5,7 @@ import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.qu
import
TooltipOnTruncate
from
'
~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue
'
;
import
{
EDITOR_APP_STATUS_EMPTY
,
EDITOR_APP_STATUS_LINT_UNAVAILABLE
,
EDITOR_APP_STATUS_LOADING
,
EDITOR_APP_STATUS_VALID
,
}
from
'
../../constants
'
;
...
...
@@ -17,6 +18,7 @@ export const i18n = {
loading
:
s__
(
'
Pipelines|Validating GitLab CI configuration…
'
),
invalid
:
s__
(
'
Pipelines|This GitLab CI configuration is invalid.
'
),
invalidWithReason
:
s__
(
'
Pipelines|This GitLab CI configuration is invalid: %{reason}.
'
),
unavailableValidation
:
s__
(
'
Pipelines|Configuration validation currently not available.
'
),
valid
:
s__
(
'
Pipelines|This GitLab CI configuration is valid.
'
),
};
...
...
@@ -29,6 +31,9 @@ export default {
TooltipOnTruncate
,
},
inject
:
{
lintUnavailableHelpPagePath
:
{
default
:
''
,
},
ymlHelpPagePath
:
{
default
:
''
,
},
...
...
@@ -49,9 +54,15 @@ export default {
},
},
computed
:
{
helpPath
()
{
return
this
.
isLintUnavailable
?
this
.
lintUnavailableHelpPagePath
:
this
.
ymlHelpPagePath
;
},
isEmpty
()
{
return
this
.
appStatus
===
EDITOR_APP_STATUS_EMPTY
;
},
isLintUnavailable
()
{
return
this
.
appStatus
===
EDITOR_APP_STATUS_LINT_UNAVAILABLE
;
},
isLoading
()
{
return
this
.
appStatus
===
EDITOR_APP_STATUS_LOADING
;
},
...
...
@@ -62,6 +73,8 @@ export default {
switch
(
this
.
appStatus
)
{
case
EDITOR_APP_STATUS_EMPTY
:
return
'
check
'
;
case
EDITOR_APP_STATUS_LINT_UNAVAILABLE
:
return
'
time-out
'
;
case
EDITOR_APP_STATUS_VALID
:
return
'
check
'
;
default
:
...
...
@@ -74,6 +87,8 @@ export default {
switch
(
this
.
appStatus
)
{
case
EDITOR_APP_STATUS_EMPTY
:
return
this
.
$options
.
i18n
.
empty
;
case
EDITOR_APP_STATUS_LINT_UNAVAILABLE
:
return
this
.
$options
.
i18n
.
unavailableValidation
;
case
EDITOR_APP_STATUS_VALID
:
return
this
.
$options
.
i18n
.
valid
;
default
:
...
...
@@ -99,7 +114,7 @@ export default {
<gl-icon
:name=
"icon"
/>
<span
data-testid=
"validationMsg"
>
{{ message }}
</span>
</tooltip-on-truncate>
<span
v-if=
"!isEmpty"
class=
"gl-flex-shrink-0 gl-pl-2"
>
<gl-link
data-testid=
"learnMoreLink"
:href=
"
ymlHelpPage
Path"
>
<gl-link
data-testid=
"learnMoreLink"
:href=
"
help
Path"
>
{{ $options.i18n.learnMore }}
</gl-link>
</span>
...
...
app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
View file @
89a9e6c5
...
...
@@ -11,6 +11,7 @@ import {
EDITOR_APP_STATUS_INVALID
,
EDITOR_APP_STATUS_LOADING
,
EDITOR_APP_STATUS_VALID
,
EDITOR_APP_STATUS_LINT_UNAVAILABLE
,
LINT_TAB
,
MERGED_TAB
,
TAB_QUERY_PARAM
,
...
...
@@ -106,6 +107,9 @@ export default {
isInvalid
()
{
return
this
.
appStatus
===
EDITOR_APP_STATUS_INVALID
;
},
isLintUnavailable
()
{
return
this
.
appStatus
===
EDITOR_APP_STATUS_LINT_UNAVAILABLE
;
},
isValid
()
{
return
this
.
appStatus
===
EDITOR_APP_STATUS_VALID
;
},
...
...
@@ -166,6 +170,7 @@ export default {
:empty-message=
"$options.i18n.empty.visualization"
:is-empty=
"isEmpty"
:is-invalid=
"isInvalid"
:is-unavailable=
"isLintUnavailable"
:keep-component-mounted=
"false"
:title=
"$options.i18n.tabGraph"
lazy
...
...
@@ -179,6 +184,7 @@ export default {
class=
"gl-mb-3"
:empty-message=
"$options.i18n.empty.lint"
:is-empty=
"isEmpty"
:is-unavailable=
"isLintUnavailable"
:title=
"$options.i18n.tabLint"
data-testid=
"lint-tab"
@
click=
"setCurrentTab($options.tabConstants.LINT_TAB)"
...
...
@@ -192,6 +198,7 @@ export default {
:keep-component-mounted=
"false"
:is-empty=
"isEmpty"
:is-invalid=
"isInvalid"
:is-unavailable=
"isLintUnavailable"
:title=
"$options.i18n.tabMergedYaml"
lazy
data-testid=
"merged-tab"
...
...
app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue
View file @
89a9e6c5
...
...
@@ -42,6 +42,9 @@ import { __, s__ } from '~/locale';
export
default
{
i18n
:
{
invalid
:
__
(
'
Your CI/CD configuration syntax is invalid. View Lint tab for more details.
'
),
unavailable
:
__
(
"
We're experiencing difficulties and this tab content is currently unavailable.
"
,
),
},
components
:
{
GlAlert
,
...
...
@@ -66,14 +69,14 @@ export default {
isEmpty
:
{
type
:
Boolean
,
required
:
false
,
default
:
null
,
default
:
false
,
},
isInvalid
:
{
type
:
Boolean
,
required
:
false
,
default
:
null
,
default
:
false
,
},
lazy
:
{
isUnavailable
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
...
...
@@ -83,6 +86,11 @@ export default {
required
:
false
,
default
:
true
,
},
lazy
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
data
()
{
return
{
...
...
@@ -109,6 +117,9 @@ export default {
<
template
>
<gl-tab
:lazy=
"isLazy"
v-bind=
"$attrs"
v-on=
"$listeners"
>
<gl-alert
v-if=
"isEmpty"
variant=
"tip"
>
{{
emptyMessage
}}
</gl-alert>
<gl-alert
v-else-if=
"isUnavailable"
variant=
"danger"
:dismissible=
"false"
>
{{
$options
.
i18n
.
unavailable
}}
</gl-alert
>
<gl-alert
v-else-if=
"isInvalid"
variant=
"danger"
>
{{
$options
.
i18n
.
invalid
}}
</gl-alert>
<template
v-else
>
<slot
v-for=
"slot in slots"
:name=
"slot"
></slot>
...
...
app/assets/javascripts/pipeline_editor/constants.js
View file @
89a9e6c5
...
...
@@ -6,12 +6,14 @@ export const CI_CONFIG_STATUS_VALID = 'VALID';
// represent the global state of the pipeline editor app.
export
const
EDITOR_APP_STATUS_EMPTY
=
'
EMPTY
'
;
export
const
EDITOR_APP_STATUS_INVALID
=
CI_CONFIG_STATUS_INVALID
;
export
const
EDITOR_APP_STATUS_LINT_UNAVAILABLE
=
'
LINT_DOWN
'
;
export
const
EDITOR_APP_STATUS_LOADING
=
'
LOADING
'
;
export
const
EDITOR_APP_STATUS_VALID
=
CI_CONFIG_STATUS_VALID
;
export
const
EDITOR_APP_VALID_STATUSES
=
[
EDITOR_APP_STATUS_EMPTY
,
EDITOR_APP_STATUS_INVALID
,
EDITOR_APP_STATUS_LINT_UNAVAILABLE
,
EDITOR_APP_STATUS_LOADING
,
EDITOR_APP_STATUS_VALID
,
];
...
...
app/assets/javascripts/pipeline_editor/index.js
View file @
89a9e6c5
...
...
@@ -37,6 +37,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
emptyStateIllustrationPath
,
helpPaths
,
lintHelpPagePath
,
lintUnavailableHelpPagePath
,
needsHelpPagePath
,
newMergeRequestPath
,
pipelinePagePath
,
...
...
@@ -124,6 +125,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
emptyStateIllustrationPath
,
helpPaths
,
lintHelpPagePath
,
lintUnavailableHelpPagePath
,
needsHelpPagePath
,
newMergeRequestPath
,
pipelinePagePath
,
...
...
app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
View file @
89a9e6c5
...
...
@@ -12,8 +12,9 @@ import PipelineEditorMessages from './components/ui/pipeline_editor_messages.vue
import
{
COMMIT_SHA_POLL_INTERVAL
,
EDITOR_APP_STATUS_EMPTY
,
EDITOR_APP_VALID_STATUSES
,
EDITOR_APP_STATUS_LOADING
,
EDITOR_APP_STATUS_LINT_UNAVAILABLE
,
EDITOR_APP_VALID_STATUSES
,
LOAD_FAILURE_UNKNOWN
,
STARTER_TEMPLATE_NAME
,
}
from
'
./constants
'
;
...
...
@@ -51,6 +52,7 @@ export default {
failureReasons
:
[],
initialCiFileContent
:
''
,
isFetchingCommitSha
:
false
,
isLintUnavailable
:
false
,
isNewCiConfigFile
:
false
,
lastCommittedContent
:
''
,
shouldSkipStartScreen
:
false
,
...
...
@@ -147,10 +149,19 @@ export default {
return
{
...
ciConfig
,
stages
};
},
result
({
data
})
{
this
.
setAppStatus
(
data
?.
ciConfig
?.
status
);
if
(
data
?.
ciConfig
?.
status
)
{
this
.
setAppStatus
(
data
.
ciConfig
.
status
);
if
(
this
.
isLintUnavailable
)
{
this
.
isLintUnavailable
=
false
;
}
}
},
error
(
err
)
{
this
.
reportFailure
(
LOAD_FAILURE_UNKNOWN
,
[
String
(
err
)]);
error
()
{
// We are not using `reportFailure` here because we don't
// need to bring attention to the linter being down. We let
// the user work on their file and if they look at their
// lint status, they will notice that the service is down
this
.
isLintUnavailable
=
true
;
},
watchLoading
(
isLoading
)
{
if
(
isLoading
)
{
...
...
@@ -247,6 +258,13 @@ export default {
this
.
setAppStatus
(
EDITOR_APP_STATUS_EMPTY
);
}
},
isLintUnavailable
(
flag
)
{
if
(
flag
)
{
// We cannot set this status directly in the `error`
// hook otherwise we get an infinite loop caused by apollo.
this
.
setAppStatus
(
EDITOR_APP_STATUS_LINT_UNAVAILABLE
);
}
},
},
mounted
()
{
this
.
loadTemplateFromURL
();
...
...
@@ -269,14 +287,10 @@ export default {
await
this
.
$apollo
.
queries
.
initialCiFileContent
.
refetch
();
},
reportFailure
(
type
,
reasons
=
[])
{
const
isCurrentFailure
=
this
.
failureType
===
type
&&
this
.
failureReasons
[
0
]
===
reasons
[
0
];
if
(
!
isCurrentFailure
)
{
this
.
showFailure
=
true
;
this
.
failureType
=
type
;
this
.
failureReasons
=
reasons
;
window
.
scrollTo
({
top
:
0
,
behavior
:
'
smooth
'
});
}
this
.
showFailure
=
true
;
this
.
failureType
=
type
;
this
.
failureReasons
=
reasons
;
window
.
scrollTo
({
top
:
0
,
behavior
:
'
smooth
'
});
},
reportSuccess
(
type
)
{
window
.
scrollTo
({
top
:
0
,
behavior
:
'
smooth
'
});
...
...
@@ -289,7 +303,10 @@ export default {
},
setAppStatus
(
appStatus
)
{
if
(
EDITOR_APP_VALID_STATUSES
.
includes
(
appStatus
))
{
this
.
$apollo
.
mutate
({
mutation
:
updateAppStatus
,
variables
:
{
appStatus
}
});
this
.
$apollo
.
mutate
({
mutation
:
updateAppStatus
,
variables
:
{
appStatus
},
});
}
},
setNewEmptyCiConfigFile
()
{
...
...
app/helpers/ci/pipeline_editor_helper.rb
View file @
89a9e6c5
...
...
@@ -20,6 +20,7 @@ module Ci
"empty-state-illustration-path"
=>
image_path
(
'illustrations/empty-state/empty-dag-md.svg'
),
"initial-branch-name"
=>
initial_branch
,
"lint-help-page-path"
=>
help_page_path
(
'ci/lint'
,
anchor:
'validate-basic-logic-and-syntax'
),
"lint-unavailable-help-page-path"
=>
help_page_path
(
'ci/pipeline_editor/index'
,
anchor:
'configuration-validation-currently-not-available'
),
"needs-help-page-path"
=>
help_page_path
(
'ci/yaml/index'
,
anchor:
'needs'
),
"new-merge-request-path"
=>
namespace_project_new_merge_request_path
,
"pipeline_etag"
=>
latest_commit
?
graphql_etag_pipeline_sha_path
(
commit_sha
)
:
''
,
...
...
doc/ci/pipeline_editor/index.md
View file @
89a9e6c5
...
...
@@ -97,3 +97,20 @@ If you enter a new branch name, the **Start a new merge request with these chang
checkbox appears. Select it to start a new merge request after you commit the changes.
![
The commit form with a new branch
](
img/pipeline_editor_commit_v13_8.png
)
## Troubleshooting
### `Configuration validation currently not available` message
This message is due to a problem with the syntax validation in the pipeline editor.
If GitLab is unable to communicate with the service that validates the syntax, the
information in these sections may not display properly:
-
The syntax status on the
**Edit**
tab (valid or invalid).
-
The
**Visualize**
tab.
-
The
**Lint**
tab.
-
The
**View merged YAML**
tab.
You can still work on your CI/CD configuration and commit the changes you made without
any issues. As soon as the service becomes available again, the syntax validation
should display immediately.
locale/gitlab.pot
View file @
89a9e6c5
...
...
@@ -25948,6 +25948,9 @@ msgstr ""
msgid "Pipelines|Clear runner caches"
msgstr ""
msgid "Pipelines|Configuration validation currently not available."
msgstr ""
msgid "Pipelines|Copy trigger token"
msgstr ""
...
...
@@ -39421,6 +39424,9 @@ msgstr ""
msgid "We'll use this to help surface the right features and information to you."
msgstr ""
msgid "We're experiencing difficulties and this tab content is currently unavailable."
msgstr ""
msgid "We've found no vulnerabilities"
msgstr ""
...
...
spec/frontend/pipeline_editor/components/header/validation_segment_spec.js
View file @
89a9e6c5
...
...
@@ -11,9 +11,15 @@ import {
EDITOR_APP_STATUS_EMPTY
,
EDITOR_APP_STATUS_INVALID
,
EDITOR_APP_STATUS_LOADING
,
EDITOR_APP_STATUS_LINT_UNAVAILABLE
,
EDITOR_APP_STATUS_VALID
,
}
from
'
~/pipeline_editor/constants
'
;
import
{
mockYmlHelpPagePath
,
mergeUnwrappedCiConfig
,
mockCiYml
}
from
'
../../mock_data
'
;
import
{
mergeUnwrappedCiConfig
,
mockCiYml
,
mockLintUnavailableHelpPagePath
,
mockYmlHelpPagePath
,
}
from
'
../../mock_data
'
;
describe
(
'
Validation segment component
'
,
()
=>
{
let
wrapper
;
...
...
@@ -23,6 +29,7 @@ describe('Validation segment component', () => {
shallowMount
(
ValidationSegment
,
{
provide
:
{
ymlHelpPagePath
:
mockYmlHelpPagePath
,
lintUnavailableHelpPagePath
:
mockLintUnavailableHelpPagePath
,
},
propsData
:
{
ciConfig
:
mergeUnwrappedCiConfig
(),
...
...
@@ -149,4 +156,28 @@ describe('Validation segment component', () => {
});
});
});
describe
(
'
when the lint service is unavailable
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
appStatus
:
EDITOR_APP_STATUS_LINT_UNAVAILABLE
,
props
:
{
ciConfig
:
{},
},
});
});
it
(
'
show a message that the service is unavailable
'
,
()
=>
{
expect
(
findValidationMsg
().
text
()).
toBe
(
i18n
.
unavailableValidation
);
});
it
(
'
shows the time-out icon
'
,
()
=>
{
expect
(
findIcon
().
props
(
'
name
'
)).
toBe
(
'
time-out
'
);
});
it
(
'
shows the learn more link
'
,
()
=>
{
expect
(
findLearnMoreLink
().
attributes
(
'
href
'
)).
toBe
(
mockLintUnavailableHelpPagePath
);
expect
(
findLearnMoreLink
().
text
()).
toBe
(
i18n
.
learnMore
);
});
});
});
spec/frontend/pipeline_editor/components/ui/editor_tab_spec.js
View file @
89a9e6c5
...
...
@@ -75,34 +75,83 @@ describe('~/pipeline_editor/components/ui/editor_tab.vue', () => {
expect
(
mockChildMounted
).
toHaveBeenCalledWith
(
mockContent1
);
});
describe
(
'
showing the tab content depending on `isEmpty` and `isInvalid`
'
,
()
=>
{
describe
(
'
alerts
'
,
()
=>
{
describe
(
'
unavailable state
'
,
()
=>
{
beforeEach
(()
=>
{
createWrapper
({
props
:
{
isUnavailable
:
true
}
});
});
it
(
'
shows the invalid alert when the status is invalid
'
,
()
=>
{
const
alert
=
findAlert
();
expect
(
alert
.
exists
()).
toBe
(
true
);
expect
(
alert
.
text
()).
toContain
(
wrapper
.
vm
.
$options
.
i18n
.
unavailable
);
});
});
describe
(
'
invalid state
'
,
()
=>
{
beforeEach
(()
=>
{
createWrapper
({
props
:
{
isInvalid
:
true
}
});
});
it
(
'
shows the invalid alert when the status is invalid
'
,
()
=>
{
const
alert
=
findAlert
();
expect
(
alert
.
exists
()).
toBe
(
true
);
expect
(
alert
.
text
()).
toBe
(
wrapper
.
vm
.
$options
.
i18n
.
invalid
);
});
});
describe
(
'
empty state
'
,
()
=>
{
const
text
=
'
my custom alert message
'
;
beforeEach
(()
=>
{
createWrapper
({
props
:
{
isEmpty
:
true
,
emptyMessage
:
text
},
});
});
it
(
'
displays an empty message
'
,
()
=>
{
createWrapper
({
props
:
{
isEmpty
:
true
},
});
const
alert
=
findAlert
();
expect
(
alert
.
exists
()).
toBe
(
true
);
expect
(
alert
.
text
()).
toBe
(
'
This tab will be usable when the CI/CD configuration file is populated with valid syntax.
'
,
);
});
it
(
'
can have a custom empty message
'
,
()
=>
{
const
alert
=
findAlert
();
expect
(
alert
.
exists
()).
toBe
(
true
);
expect
(
alert
.
text
()).
toBe
(
text
);
});
});
});
describe
(
'
showing the tab content depending on `isEmpty`, `isUnavailable` and `isInvalid`
'
,
()
=>
{
it
.
each
`
isEmpty | isInvalid | showSlotComponent | text
${
undefined
}
|
${
undefined
}
|
${
true
}
|
${
'
renders
'
}
${
false
}
|
${
false
}
|
${
true
}
|
${
'
renders
'
}
${
undefined
}
|
${
true
}
|
${
false
}
|
${
'
hides
'
}
${
true
}
|
${
false
}
|
${
false
}
|
${
'
hides
'
}
${
false
}
|
${
true
}
|
${
false
}
|
${
'
hides
'
}
isEmpty | isUnavailable | isInvalid | showSlotComponent | text
${
undefined
}
|
${
undefined
}
|
${
undefined
}
|
${
true
}
|
${
'
renders
'
}
${
false
}
|
${
false
}
|
${
false
}
|
${
true
}
|
${
'
renders
'
}
${
undefined
}
|
${
true
}
|
${
true
}
|
${
false
}
|
${
'
hides
'
}
${
true
}
|
${
false
}
|
${
false
}
|
${
false
}
|
${
'
hides
'
}
${
false
}
|
${
true
}
|
${
false
}
|
${
false
}
|
${
'
hides
'
}
${
false
}
|
${
false
}
|
${
true
}
|
${
false
}
|
${
'
hides
'
}
`
(
'
$text the slot component when isEmpty:$isEmpty and isInvalid:$isInvalid
'
,
({
isEmpty
,
isInvalid
,
showSlotComponent
})
=>
{
'
$text the slot component when isEmpty:$isEmpty
, isUnavailable:$isUnavailable
and isInvalid:$isInvalid
'
,
({
isEmpty
,
is
Unavailable
,
is
Invalid
,
showSlotComponent
})
=>
{
createWrapper
({
props
:
{
isEmpty
,
isInvalid
},
props
:
{
isEmpty
,
is
Unavailable
,
is
Invalid
},
});
expect
(
findSlotComponent
().
exists
()).
toBe
(
showSlotComponent
);
expect
(
findAlert
().
exists
()).
toBe
(
!
showSlotComponent
);
},
);
it
(
'
can have a custom empty message
'
,
()
=>
{
const
text
=
'
my custom alert message
'
;
createWrapper
({
props
:
{
isEmpty
:
true
,
emptyMessage
:
text
}
});
const
alert
=
findAlert
();
expect
(
alert
.
exists
()).
toBe
(
true
);
expect
(
alert
.
text
()).
toBe
(
text
);
});
});
describe
(
'
user interaction
'
,
()
=>
{
...
...
spec/frontend/pipeline_editor/mock_data.js
View file @
89a9e6c5
...
...
@@ -10,6 +10,7 @@ export const mockNewMergeRequestPath = '/-/merge_requests/new';
export
const
mockCommitSha
=
'
aabbccdd
'
;
export
const
mockCommitNextSha
=
'
eeffgghh
'
;
export
const
mockLintHelpPagePath
=
'
/-/lint-help
'
;
export
const
mockLintUnavailableHelpPagePath
=
'
/-/pipeline-editor/troubleshoot
'
;
export
const
mockYmlHelpPagePath
=
'
/-/yml-help
'
;
export
const
mockCommitMessage
=
'
My commit message
'
;
...
...
spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
View file @
89a9e6c5
...
...
@@ -5,10 +5,15 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import
setWindowLocation
from
'
helpers/set_window_location_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
{
resolvers
}
from
'
~/pipeline_editor/graphql/resolvers
'
;
import
PipelineEditorTabs
from
'
~/pipeline_editor/components/pipeline_editor_tabs.vue
'
;
import
PipelineEditorEmptyState
from
'
~/pipeline_editor/components/ui/pipeline_editor_empty_state.vue
'
;
import
PipelineEditorMessages
from
'
~/pipeline_editor/components/ui/pipeline_editor_messages.vue
'
;
import
{
COMMIT_SUCCESS
,
COMMIT_FAILURE
,
LOAD_FAILURE_UNKNOWN
}
from
'
~/pipeline_editor/constants
'
;
import
PipelineEditorHeader
from
'
~/pipeline_editor/components/header/pipeline_editor_header.vue
'
;
import
ValidationSegment
,
{
i18n
as
validationSegmenti18n
,
}
from
'
~/pipeline_editor/components/header/validation_segment.vue
'
;
import
{
COMMIT_SUCCESS
,
COMMIT_FAILURE
}
from
'
~/pipeline_editor/constants
'
;
import
getBlobContent
from
'
~/pipeline_editor/graphql/queries/blob_content.query.graphql
'
;
import
getCiConfigData
from
'
~/pipeline_editor/graphql/queries/ci_config.query.graphql
'
;
import
getTemplate
from
'
~/pipeline_editor/graphql/queries/get_starter_template.query.graphql
'
;
...
...
@@ -61,11 +66,6 @@ describe('Pipeline editor app component', () => {
wrapper
=
shallowMount
(
PipelineEditorApp
,
{
provide
:
{
...
mockProvide
,
...
provide
},
stubs
,
data
()
{
return
{
commitSha
:
''
,
};
},
mocks
:
{
$apollo
:
{
queries
:
{
...
...
@@ -90,17 +90,11 @@ describe('Pipeline editor app component', () => {
[
getLatestCommitShaQuery
,
mockLatestCommitShaQuery
],
[
getPipelineQuery
,
mockPipelineQuery
],
];
mockApollo
=
createMockApollo
(
handlers
);
mockApollo
=
createMockApollo
(
handlers
,
resolvers
);
const
options
=
{
localVue
,
data
()
{
return
{
currentBranch
:
mockDefaultBranch
,
lastCommitBranch
:
''
,
appStatus
:
''
,
};
},
mocks
:
{},
apolloProvider
:
mockApollo
,
};
...
...
@@ -116,6 +110,7 @@ describe('Pipeline editor app component', () => {
const
findEmptyState
=
()
=>
wrapper
.
findComponent
(
PipelineEditorEmptyState
);
const
findEmptyStateButton
=
()
=>
wrapper
.
findComponent
(
PipelineEditorEmptyState
).
findComponent
(
GlButton
);
const
findValidationSegment
=
()
=>
wrapper
.
findComponent
(
ValidationSegment
);
beforeEach
(()
=>
{
mockBlobContentData
=
jest
.
fn
();
...
...
@@ -240,6 +235,26 @@ describe('Pipeline editor app component', () => {
});
});
describe
(
'
when the lint query returns a 500 error
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockCiConfigData
.
mockRejectedValueOnce
(
new
Error
(
500
));
await
createComponentWithApollo
({
stubs
:
{
PipelineEditorHome
,
PipelineEditorHeader
,
ValidationSegment
},
});
});
it
(
'
shows that the lint service is down
'
,
()
=>
{
expect
(
findValidationSegment
().
text
()).
toContain
(
validationSegmenti18n
.
unavailableValidation
,
);
});
it
(
'
does not report an error or scroll to the top
'
,
()
=>
{
expect
(
findAlert
().
exists
()).
toBe
(
false
);
expect
(
window
.
scrollTo
).
not
.
toHaveBeenCalled
();
});
});
describe
(
'
when the user commits
'
,
()
=>
{
const
updateFailureMessage
=
'
The GitLab CI configuration could not be updated.
'
;
const
updateSuccessMessage
=
'
Your changes have been successfully committed.
'
;
...
...
@@ -411,94 +426,6 @@ describe('Pipeline editor app component', () => {
});
});
describe
(
'
when multiple errors occurs in a row
'
,
()
=>
{
const
updateFailureMessage
=
'
The GitLab CI configuration could not be updated.
'
;
const
unknownFailureMessage
=
'
The CI configuration was not loaded, please try again.
'
;
const
unknownReasons
=
[
'
Commit failed
'
];
const
alertErrorMessage
=
`
${
updateFailureMessage
}
${
unknownReasons
[
0
]}
`
;
const
emitError
=
(
type
=
COMMIT_FAILURE
,
reasons
=
unknownReasons
)
=>
findEditorHome
().
vm
.
$emit
(
'
showError
'
,
{
type
,
reasons
,
});
beforeEach
(
async
()
=>
{
mockBlobContentData
.
mockResolvedValue
(
mockBlobContentQueryResponse
);
mockCiConfigData
.
mockResolvedValue
(
mockCiConfigQueryResponse
);
mockLatestCommitShaQuery
.
mockResolvedValue
(
mockCommitShaResults
);
window
.
scrollTo
=
jest
.
fn
();
await
createComponentWithApollo
({
stubs
:
{
PipelineEditorMessages
}
});
await
emitError
();
});
it
(
'
shows an error message for the first error
'
,
()
=>
{
expect
(
findAlert
().
text
()).
toMatchInterpolatedText
(
alertErrorMessage
);
});
it
(
'
scrolls to the top of the page to bring attention to the error message
'
,
()
=>
{
expect
(
window
.
scrollTo
).
toHaveBeenCalledWith
({
top
:
0
,
behavior
:
'
smooth
'
});
expect
(
window
.
scrollTo
).
toHaveBeenCalledTimes
(
1
);
});
it
(
'
does not scroll to the top of the page if the same error occur multiple times in a row
'
,
async
()
=>
{
await
emitError
();
expect
(
window
.
scrollTo
).
toHaveBeenCalledTimes
(
1
);
expect
(
findAlert
().
text
()).
toMatchInterpolatedText
(
alertErrorMessage
);
});
it
(
'
scrolls to the top if the error is different
'
,
async
()
=>
{
await
emitError
(
LOAD_FAILURE_UNKNOWN
,
[]);
expect
(
findAlert
().
text
()).
toMatchInterpolatedText
(
unknownFailureMessage
);
expect
(
window
.
scrollTo
).
toHaveBeenCalledTimes
(
2
);
});
describe
(
'
when a user dismiss the alert
'
,
()
=>
{
beforeEach
(
async
()
=>
{
await
findAlert
().
vm
.
$emit
(
'
dismiss
'
);
});
it
(
'
shows an error if the type is the same, but the reason is different
'
,
async
()
=>
{
const
newReason
=
'
Something broke
'
;
await
emitError
(
COMMIT_FAILURE
,
[
newReason
]);
expect
(
window
.
scrollTo
).
toHaveBeenCalledTimes
(
2
);
expect
(
findAlert
().
text
()).
toMatchInterpolatedText
(
`
${
updateFailureMessage
}
${
newReason
}
`
);
});
it
(
'
does not show an error or scroll if a new error with the same type occurs
'
,
async
()
=>
{
await
emitError
();
expect
(
window
.
scrollTo
).
toHaveBeenCalledTimes
(
1
);
expect
(
findAlert
().
exists
()).
toBe
(
false
);
});
it
(
'
it shows an error and scroll when a new type is emitted
'
,
async
()
=>
{
await
emitError
(
LOAD_FAILURE_UNKNOWN
,
[]);
expect
(
window
.
scrollTo
).
toHaveBeenCalledTimes
(
2
);
expect
(
findAlert
().
text
()).
toMatchInterpolatedText
(
unknownFailureMessage
);
});
it
(
'
it shows an error and scroll if a previously shown type happen again
'
,
async
()
=>
{
await
emitError
(
LOAD_FAILURE_UNKNOWN
,
[]);
expect
(
window
.
scrollTo
).
toHaveBeenCalledTimes
(
2
);
expect
(
findAlert
().
text
()).
toMatchInterpolatedText
(
unknownFailureMessage
);
await
emitError
();
expect
(
window
.
scrollTo
).
toHaveBeenCalledTimes
(
3
);
expect
(
findAlert
().
text
()).
toMatchInterpolatedText
(
alertErrorMessage
);
});
});
});
describe
(
'
when add_new_config_file query param is present
'
,
()
=>
{
const
originalLocation
=
window
.
location
.
href
;
...
...
spec/helpers/ci/pipeline_editor_helper_spec.rb
View file @
89a9e6c5
...
...
@@ -46,6 +46,7 @@ RSpec.describe Ci::PipelineEditorHelper do
"empty-state-illustration-path"
=>
'foo'
,
"initial-branch-name"
=>
nil
,
"lint-help-page-path"
=>
help_page_path
(
'ci/lint'
,
anchor:
'validate-basic-logic-and-syntax'
),
"lint-unavailable-help-page-path"
=>
help_page_path
(
'ci/pipeline_editor/index'
,
anchor:
'configuration-validation-currently-not-available'
),
"needs-help-page-path"
=>
help_page_path
(
'ci/yaml/index'
,
anchor:
'needs'
),
"new-merge-request-path"
=>
'/mock/project/-/merge_requests/new'
,
"pipeline_etag"
=>
graphql_etag_pipeline_sha_path
(
project
.
commit
.
sha
),
...
...
@@ -72,6 +73,7 @@ RSpec.describe Ci::PipelineEditorHelper do
"empty-state-illustration-path"
=>
'foo'
,
"initial-branch-name"
=>
nil
,
"lint-help-page-path"
=>
help_page_path
(
'ci/lint'
,
anchor:
'validate-basic-logic-and-syntax'
),
"lint-unavailable-help-page-path"
=>
help_page_path
(
'ci/pipeline_editor/index'
,
anchor:
'configuration-validation-currently-not-available'
),
"needs-help-page-path"
=>
help_page_path
(
'ci/yaml/index'
,
anchor:
'needs'
),
"new-merge-request-path"
=>
'/mock/project/-/merge_requests/new'
,
"pipeline_etag"
=>
''
,
...
...
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