diff options
Diffstat (limited to 'app/assets/javascripts/pipeline_editor')
10 files changed, 85 insertions, 26 deletions
diff --git a/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue b/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue index 9f82d4a5395..ca78f194a82 100644 --- a/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue +++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue @@ -142,6 +142,7 @@ export default { class="js-no-auto-disable" category="primary" variant="confirm" + data-qa-selector="commit_changes_button" :disabled="submitDisabled" :loading="isSaving" > diff --git a/app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue b/app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue index 92fa411d5af..bfbf24c6b13 100644 --- a/app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue +++ b/app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue @@ -15,12 +15,10 @@ export default { onCiConfigUpdate(content) { this.$emit('updateCiConfig', content); }, - registerCiSchema() { + registerCiSchema({ detail: { instance } }) { if (this.glFeatures.schemaLinting) { - const editorInstance = this.$refs.editor.getEditor(); - - editorInstance.use({ definition: CiSchemaExtension }); - editorInstance.registerCiSchema(); + instance.use({ definition: CiSchemaExtension }); + instance.registerCiSchema(); } }, }, @@ -33,7 +31,7 @@ export default { ref="editor" :file-name="ciConfigPath" v-bind="$attrs" - @[$options.readyEvent]="registerCiSchema" + @[$options.readyEvent]="registerCiSchema($event)" @input="onCiConfigUpdate" v-on="$listeners" /> diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue index 16ad648afca..72b492a5877 100644 --- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue @@ -153,7 +153,9 @@ export default { <span class="gl-font-weight-bold"> <gl-sprintf :message="$options.i18n.pipelineInfo"> <template #id="{ content }"> - <span data-testid="pipeline-id"> {{ content }}{{ pipelineId }} </span> + <span data-testid="pipeline-id" data-qa-selector="pipeline_id_content"> + {{ content }}{{ pipelineId }} + </span> </template> <template #status>{{ status.text }}</template> <template #commit> diff --git a/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue index 833d784f940..23f1592cac1 100644 --- a/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue @@ -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: @@ -96,10 +111,13 @@ export default { <span v-else class="gl-display-inline-flex gl-white-space-nowrap gl-max-w-full"> <tooltip-on-truncate :title="message" class="gl-text-truncate"> - <gl-icon :name="icon" /> <span data-testid="validationMsg">{{ message }}</span> + <gl-icon :name="icon" /> + <span data-qa-selector="validation_message_content" 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="ymlHelpPagePath"> + <gl-link data-testid="learnMoreLink" :href="helpPath"> {{ $options.i18n.learnMore }} </gl-link> </span> diff --git a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue index 3f50a1225d8..c75b1d4bb11 100644 --- a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue +++ b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue @@ -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; }, @@ -142,6 +146,7 @@ export default { <template> <gl-tabs class="file-editor gl-mb-3" + data-qa-selector="file_editor_container" :query-param-name="$options.query.TAB_QUERY_PARAM" sync-active-tab-with-query-params > @@ -166,6 +171,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 +185,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 +199,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" diff --git a/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue b/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue index 7c032441a04..673599da085 100644 --- a/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue +++ b/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue @@ -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> diff --git a/app/assets/javascripts/pipeline_editor/constants.js b/app/assets/javascripts/pipeline_editor/constants.js index a2eaeeef286..bc79b0742e7 100644 --- a/app/assets/javascripts/pipeline_editor/constants.js +++ b/app/assets/javascripts/pipeline_editor/constants.js @@ -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, ]; diff --git a/app/assets/javascripts/pipeline_editor/index.js b/app/assets/javascripts/pipeline_editor/index.js index ee93e327b76..04f91cb3d1e 100644 --- a/app/assets/javascripts/pipeline_editor/index.js +++ b/app/assets/javascripts/pipeline_editor/index.js @@ -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, diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue index e397054f06a..90f48195c5e 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue @@ -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() { diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue index 8e8f31a4acc..96680080f0c 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue @@ -90,7 +90,7 @@ export default { </script> <template> - <div class="gl-pr-9 gl-transition-medium gl-w-full"> + <div class="gl-pr-10 gl-transition-medium gl-w-full"> <gl-modal v-if="showSwitchBranchModal" visible |