Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Speicher <rspeicher@gmail.com>2021-01-20 22:34:23 +0300
committerRobert Speicher <rspeicher@gmail.com>2021-01-20 22:34:23 +0300
commit6438df3a1e0fb944485cebf07976160184697d72 (patch)
tree00b09bfd170e77ae9391b1a2f5a93ef6839f2597 /app/assets/javascripts/pipeline_editor
parent42bcd54d971da7ef2854b896a7b34f4ef8601067 (diff)
Add latest changes from gitlab-org/gitlab@13-8-stable-eev13.8.0-rc42
Diffstat (limited to 'app/assets/javascripts/pipeline_editor')
-rw-r--r--app/assets/javascripts/pipeline_editor/components/info/validation_segment.vue84
-rw-r--r--app/assets/javascripts/pipeline_editor/components/lint/ci_lint.vue53
-rw-r--r--app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue18
-rw-r--r--app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results_value.vue40
-rw-r--r--app/assets/javascripts/pipeline_editor/components/text_editor.vue34
-rw-r--r--app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue68
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql2
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql2
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql6
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/resolvers.js4
-rw-r--r--app/assets/javascripts/pipeline_editor/index.js25
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue164
12 files changed, 410 insertions, 90 deletions
diff --git a/app/assets/javascripts/pipeline_editor/components/info/validation_segment.vue b/app/assets/javascripts/pipeline_editor/components/info/validation_segment.vue
new file mode 100644
index 00000000000..22f378c571a
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/components/info/validation_segment.vue
@@ -0,0 +1,84 @@
+<script>
+import { GlIcon, GlLink, GlLoadingIcon } from '@gitlab/ui';
+import { __, s__, sprintf } from '~/locale';
+import { CI_CONFIG_STATUS_VALID } from '../../constants';
+import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
+
+export const i18n = {
+ learnMore: __('Learn more'),
+ 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}.'),
+ valid: s__('Pipelines|This GitLab CI configuration is valid.'),
+};
+
+export default {
+ i18n,
+ components: {
+ GlIcon,
+ GlLink,
+ GlLoadingIcon,
+ TooltipOnTruncate,
+ },
+ inject: {
+ ymlHelpPagePath: {
+ default: '',
+ },
+ },
+ props: {
+ ciConfig: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ isValid() {
+ return this.ciConfig?.status === CI_CONFIG_STATUS_VALID;
+ },
+ icon() {
+ if (this.isValid) {
+ return 'check';
+ }
+ return 'warning-solid';
+ },
+ message() {
+ if (this.isValid) {
+ return this.$options.i18n.valid;
+ }
+
+ // Only display first error as a reason
+ const [reason] = this.ciConfig?.errors || [];
+ if (reason) {
+ return sprintf(this.$options.i18n.invalidWithReason, { reason }, false);
+ }
+ return this.$options.i18n.invalid;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <template v-if="loading">
+ <gl-loading-icon inline />
+ {{ $options.i18n.loading }}
+ </template>
+
+ <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>
+ </tooltip-on-truncate>
+ <span class="gl-flex-shrink-0 gl-pl-2">
+ <gl-link data-testid="learnMoreLink" :href="ymlHelpPagePath">
+ {{ $options.i18n.learnMore }}
+ </gl-link>
+ </span>
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipeline_editor/components/lint/ci_lint.vue b/app/assets/javascripts/pipeline_editor/components/lint/ci_lint.vue
new file mode 100644
index 00000000000..b27ab9a39d3
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/components/lint/ci_lint.vue
@@ -0,0 +1,53 @@
+<script>
+import { flatten } from 'lodash';
+import { CI_CONFIG_STATUS_VALID } from '../../constants';
+import CiLintResults from './ci_lint_results.vue';
+
+export default {
+ components: {
+ CiLintResults,
+ },
+ inject: {
+ lintHelpPagePath: {
+ default: '',
+ },
+ },
+ props: {
+ ciConfig: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ isValid() {
+ return this.ciConfig?.status === CI_CONFIG_STATUS_VALID;
+ },
+ stages() {
+ return this.ciConfig?.stages || [];
+ },
+ jobs() {
+ const groupedJobs = this.stages.reduce((acc, { groups, name: stageName }) => {
+ return acc.concat(
+ groups.map(({ jobs }) => {
+ return jobs.map((job) => ({
+ stage: stageName,
+ ...job,
+ }));
+ }),
+ );
+ }, []);
+
+ return flatten(groupedJobs);
+ },
+ },
+};
+</script>
+
+<template>
+ <ci-lint-results
+ :valid="isValid"
+ :jobs="jobs"
+ :errors="ciConfig.errors"
+ :lint-help-page-path="lintHelpPagePath"
+ />
+</template>
diff --git a/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue b/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue
index 0d1c214c5b1..58a96c3f725 100644
--- a/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue
+++ b/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue
@@ -10,11 +10,11 @@ const thBorderColor = 'gl-border-gray-100!';
export default {
correct: {
variant: 'success',
- text: __('syntax is correct.'),
+ text: __('Syntax is correct.'),
},
incorrect: {
variant: 'danger',
- text: __('syntax is incorrect.'),
+ text: __('Syntax is incorrect.'),
},
includesText: __(
'CI configuration validated, including all configuration added with the %{codeStart}includes%{codeEnd} keyword. %{link}',
@@ -48,19 +48,23 @@ export default {
},
jobs: {
type: Array,
- required: true,
+ required: false,
+ default: () => [],
},
errors: {
type: Array,
- required: true,
+ required: false,
+ default: () => [],
},
warnings: {
type: Array,
- required: true,
+ required: false,
+ default: () => [],
},
dryRun: {
type: Boolean,
- required: true,
+ required: false,
+ default: false,
},
lintHelpPagePath: {
type: String,
@@ -99,7 +103,7 @@ export default {
data-testid="ci-lint-status"
>{{ status.text }}
<gl-sprintf :message="$options.includesText">
- <template #code="{content}">
+ <template #code="{ content }">
<code>
{{ content }}
</code>
diff --git a/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results_value.vue b/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results_value.vue
index 4929c3206df..ef2be2a5fba 100644
--- a/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results_value.vue
+++ b/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results_value.vue
@@ -14,7 +14,7 @@ export default {
},
computed: {
tagList() {
- return this.item.tagList.join(', ');
+ return this.item.tags?.join(', ');
},
onlyPolicy() {
return this.item.only ? this.item.only.refs.join(', ') : this.item.only;
@@ -26,15 +26,15 @@ export default {
return {
beforeScript: {
show: !isEmpty(this.item.beforeScript),
- content: this.item.beforeScript.join('\n'),
+ content: this.item.beforeScript?.join('\n'),
},
script: {
show: !isEmpty(this.item.script),
- content: this.item.script.join('\n'),
+ content: this.item.script?.join('\n'),
},
afterScript: {
show: !isEmpty(this.item.afterScript),
- content: this.item.afterScript.join('\n'),
+ content: this.item.afterScript?.join('\n'),
},
};
},
@@ -43,35 +43,43 @@ export default {
</script>
<template>
- <div>
- <pre v-if="scripts.beforeScript.show" data-testid="ci-lint-before-script">{{
- scripts.beforeScript.content
- }}</pre>
- <pre v-if="scripts.script.show" data-testid="ci-lint-script">{{ scripts.script.content }}</pre>
- <pre v-if="scripts.afterScript.show" data-testid="ci-lint-after-script">{{
- scripts.afterScript.content
+ <div data-testid="ci-lint-value">
+ <pre
+ v-if="scripts.beforeScript.show"
+ class="gl-white-space-pre-wrap"
+ data-testid="ci-lint-before-script"
+ >{{ scripts.beforeScript.content }}</pre
+ >
+ <pre v-if="scripts.script.show" class="gl-white-space-pre-wrap" data-testid="ci-lint-script">{{
+ scripts.script.content
}}</pre>
+ <pre
+ v-if="scripts.afterScript.show"
+ class="gl-white-space-pre-wrap"
+ data-testid="ci-lint-after-script"
+ >{{ scripts.afterScript.content }}</pre
+ >
<ul class="gl-list-style-none gl-pl-0 gl-mb-0">
- <li>
+ <li v-if="tagList">
<b>{{ __('Tag list:') }}</b>
{{ tagList }}
</li>
<div v-if="!dryRun" data-testid="ci-lint-only-except">
- <li>
+ <li v-if="onlyPolicy">
<b>{{ __('Only policy:') }}</b>
{{ onlyPolicy }}
</li>
- <li>
+ <li v-if="exceptPolicy">
<b>{{ __('Except policy:') }}</b>
{{ exceptPolicy }}
</li>
</div>
- <li>
+ <li v-if="item.environment">
<b>{{ __('Environment:') }}</b>
{{ item.environment }}
</li>
- <li>
+ <li v-if="item.when">
<b>{{ __('When:') }}</b>
{{ item.when }}
<b v-if="item.allowFailure">{{ __('Allowed to fail') }}</b>
diff --git a/app/assets/javascripts/pipeline_editor/components/text_editor.vue b/app/assets/javascripts/pipeline_editor/components/text_editor.vue
index 22f2a32c9ac..b8d49d77ea9 100644
--- a/app/assets/javascripts/pipeline_editor/components/text_editor.vue
+++ b/app/assets/javascripts/pipeline_editor/components/text_editor.vue
@@ -1,14 +1,46 @@
<script>
import EditorLite from '~/vue_shared/components/editor_lite.vue';
+import { CiSchemaExtension } from '~/editor/extensions/editor_ci_schema_ext';
export default {
components: {
EditorLite,
},
+ inject: ['projectPath', 'projectNamespace'],
+ inheritAttrs: false,
+ props: {
+ ciConfigPath: {
+ type: String,
+ required: true,
+ },
+ commitSha: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ methods: {
+ onEditorReady() {
+ const editorInstance = this.$refs.editor.getEditor();
+
+ editorInstance.use(new CiSchemaExtension());
+ editorInstance.registerCiSchema({
+ projectPath: this.projectPath,
+ projectNamespace: this.projectNamespace,
+ ref: this.commitSha,
+ });
+ },
+ },
};
</script>
<template>
<div class="gl-border-solid gl-border-gray-100 gl-border-1">
- <editor-lite file-name="*.yml" v-bind="$attrs" v-on="$listeners" />
+ <editor-lite
+ ref="editor"
+ :file-name="ciConfigPath"
+ v-bind="$attrs"
+ @editor-ready="onEditorReady"
+ v-on="$listeners"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue b/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue
new file mode 100644
index 00000000000..b0acd3ca2ee
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue
@@ -0,0 +1,68 @@
+<script>
+import { GlTab } from '@gitlab/ui';
+
+/**
+ * Wrapper of <gl-tab> to optionally lazily render this tab's content
+ * when its shown **without dismounting after its hidden**.
+ *
+ * Usage:
+ *
+ * API is the same as <gl-tab>, for example:
+ *
+ * <gl-tabs>
+ * <editor-tab title="Tab 1" :lazy="true">
+ * lazily mounted content (gets mounted if this is first tab)
+ * </editor-tab>
+ * <editor-tab title="Tab 2" :lazy="true">
+ * lazily mounted content
+ * </editor-tab>
+ * <editor-tab title="Tab 3">
+ * eagerly mounted content
+ * </editor-tab>
+ * </gl-tabs>
+ *
+ * Once the tab is selected it is permanently set as "not-lazy"
+ * so it's contents are not dismounted.
+ *
+ * lazy is "false" by default, as in <gl-tab>.
+ */
+
+export default {
+ components: {
+ GlTab,
+ // Use a small renderless component to know when the tab content mounts because:
+ // - gl-tab always gets mounted, even if lazy is `true`. See:
+ // https://github.com/bootstrap-vue/bootstrap-vue/blob/dev/src/components/tabs/tab.js#L180
+ // - we cannot listen to events on <slot />
+ MountSpy: {
+ render: () => null,
+ },
+ },
+ inheritAttrs: false,
+ props: {
+ lazy: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ isLazy: this.lazy,
+ };
+ },
+ methods: {
+ onContentMounted() {
+ // When a child is first mounted make the entire tab
+ // permanently mounted by setting 'lazy' to false.
+ this.isLazy = false;
+ },
+ },
+};
+</script>
+<template>
+ <gl-tab :lazy="isLazy" v-bind="$attrs" v-on="$listeners">
+ <slot v-for="slot in Object.keys($slots)" :slot="slot" :name="slot"></slot>
+ <mount-spy @hook:mounted="onContentMounted" />
+ </gl-tab>
+</template>
diff --git a/app/assets/javascripts/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql b/app/assets/javascripts/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql
index 11bca42fd69..0c58749a8b2 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql
@@ -19,7 +19,7 @@ mutation commitCIFileMutation(
}
) {
commit {
- id
+ sha
}
errors
}
diff --git a/app/assets/javascripts/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql b/app/assets/javascripts/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql
index 496036f690f..5091d63111f 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql
@@ -15,7 +15,7 @@ mutation lintCI($endpoint: String, $content: String, $dry: Boolean) {
}
afterScript
stage
- tagList
+ tags
when
}
}
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql
index d65d9892260..dfddb29701d 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql
@@ -1,7 +1,7 @@
-#import "~/pipelines/graphql/queries/pipeline_stages_connection.fragment.graphql"
+#import "~/pipelines/graphql/fragments/pipeline_stages_connection.fragment.graphql"
-query getCiConfigData($content: String!) {
- ciConfig(content: $content) {
+query getCiConfigData($projectPath: ID!, $content: String!) {
+ ciConfig(projectPath: $projectPath, content: $content) {
errors
status
stages {
diff --git a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
index c1cdb5eb2ee..81e75c32846 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
+++ b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
@@ -18,7 +18,7 @@ export const resolvers = {
valid: data.valid,
errors: data.errors,
warnings: data.warnings,
- jobs: data.jobs.map(job => {
+ jobs: data.jobs.map((job) => {
const only = job.only ? { refs: job.only.refs, __typename: 'CiLintJobOnlyPolicy' } : null;
return {
@@ -27,7 +27,7 @@ export const resolvers = {
beforeScript: job.before_script,
script: job.script,
afterScript: job.after_script,
- tagList: job.tag_list,
+ tags: job.tag_list,
environment: job.environment,
when: job.when,
allowFailure: job.allow_failure,
diff --git a/app/assets/javascripts/pipeline_editor/index.js b/app/assets/javascripts/pipeline_editor/index.js
index 8268a907a29..583ba555080 100644
--- a/app/assets/javascripts/pipeline_editor/index.js
+++ b/app/assets/javascripts/pipeline_editor/index.js
@@ -14,7 +14,20 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
return null;
}
- const { ciConfigPath, commitId, defaultBranch, newMergeRequestPath, projectPath } = el?.dataset;
+ const {
+ // props
+ ciConfigPath,
+ commitSha,
+ defaultBranch,
+ newMergeRequestPath,
+
+ // `provide/inject` data
+ lintHelpPagePath,
+ projectFullPath,
+ projectPath,
+ projectNamespace,
+ ymlHelpPagePath,
+ } = el?.dataset;
Vue.use(VueApollo);
@@ -25,14 +38,20 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
return new Vue({
el,
apolloProvider,
+ provide: {
+ lintHelpPagePath,
+ projectFullPath,
+ projectPath,
+ projectNamespace,
+ ymlHelpPagePath,
+ },
render(h) {
return h(PipelineEditorApp, {
props: {
ciConfigPath,
- commitId,
+ commitSha,
defaultBranch,
newMergeRequestPath,
- projectPath,
},
});
},
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index 96dc782964b..21993e2120a 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -1,12 +1,16 @@
<script>
-import { GlAlert, GlLoadingIcon, GlTab, GlTabs } from '@gitlab/ui';
+import { GlAlert, GlLoadingIcon, GlTabs, GlTab } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
-import { mergeUrlParams, redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility';
+import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import httpStatusCodes from '~/lib/utils/http_status';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
+import CiLint from './components/lint/ci_lint.vue';
import CommitForm from './components/commit/commit_form.vue';
+import EditorTab from './components/ui/editor_tab.vue';
import TextEditor from './components/text_editor.vue';
+import ValidationSegment from './components/info/validation_segment.vue';
import commitCiFileMutation from './graphql/mutations/commit_ci_file.mutation.graphql';
import getBlobContent from './graphql/queries/blob_content.graphql';
@@ -17,33 +21,33 @@ const MR_SOURCE_BRANCH = 'merge_request[source_branch]';
const MR_TARGET_BRANCH = 'merge_request[target_branch]';
const COMMIT_FAILURE = 'COMMIT_FAILURE';
+const COMMIT_SUCCESS = 'COMMIT_SUCCESS';
const DEFAULT_FAILURE = 'DEFAULT_FAILURE';
const LOAD_FAILURE_NO_FILE = 'LOAD_FAILURE_NO_FILE';
-const LOAD_FAILURE_NO_REF = 'LOAD_FAILURE_NO_REF';
const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN';
export default {
components: {
+ CiLint,
CommitForm,
+ EditorTab,
GlAlert,
GlLoadingIcon,
- GlTab,
GlTabs,
+ GlTab,
PipelineGraph,
TextEditor,
+ ValidationSegment,
},
mixins: [glFeatureFlagsMixin()],
+ inject: ['projectFullPath'],
props: {
- projectPath: {
- type: String,
- required: true,
- },
defaultBranch: {
type: String,
required: false,
default: null,
},
- commitId: {
+ commitSha: {
type: String,
required: false,
default: null,
@@ -62,12 +66,15 @@ export default {
ciConfigData: {},
content: '',
contentModel: '',
- currentTabIndex: 0,
- editorIsReady: false,
- failureType: null,
- failureReasons: [],
+ lastCommitSha: this.commitSha,
isSaving: false,
+
+ // Success and failure state
+ failureType: null,
showFailureAlert: false,
+ failureReasons: [],
+ successType: null,
+ showSuccessAlert: false,
};
},
apollo: {
@@ -75,7 +82,7 @@ export default {
query: getBlobContent,
variables() {
return {
- projectPath: this.projectPath,
+ projectPath: this.projectFullPath,
path: this.ciConfigPath,
ref: this.defaultBranch,
};
@@ -98,15 +105,16 @@ export default {
},
variables() {
return {
+ projectPath: this.projectFullPath,
content: this.contentModel,
};
},
update(data) {
- const { ciConfigData } = data || {};
- const stageNodes = ciConfigData?.stages?.nodes || [];
+ const { ciConfig } = data || {};
+ const stageNodes = ciConfig?.stages?.nodes || [];
const stages = unwrapStagesWithNeeds(stageNodes);
- return { ...ciConfigData, stages };
+ return { ...ciConfig, stages };
},
error() {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
@@ -117,40 +125,48 @@ export default {
isBlobContentLoading() {
return this.$apollo.queries.content.loading;
},
- isVisualizationTabLoading() {
- return this.$apollo.queries.ciConfigData.loading;
+ isBlobContentError() {
+ return this.failureType === LOAD_FAILURE_NO_FILE;
},
- isVisualizeTabActive() {
- return this.currentTabIndex === 1;
+ isCiConfigDataLoading() {
+ return this.$apollo.queries.ciConfigData.loading;
},
defaultCommitMessage() {
return sprintf(this.$options.i18n.defaultCommitMessage, { sourcePath: this.ciConfigPath });
},
- failure() {
- switch (this.failureType) {
- case LOAD_FAILURE_NO_REF:
+ success() {
+ switch (this.successType) {
+ case COMMIT_SUCCESS:
return {
- text: this.$options.errorTexts[LOAD_FAILURE_NO_REF],
- variant: 'danger',
+ text: this.$options.alertTexts[COMMIT_SUCCESS],
+ variant: 'info',
};
+ default:
+ return null;
+ }
+ },
+ failure() {
+ switch (this.failureType) {
case LOAD_FAILURE_NO_FILE:
return {
- text: this.$options.errorTexts[LOAD_FAILURE_NO_FILE],
+ text: sprintf(this.$options.alertTexts[LOAD_FAILURE_NO_FILE], {
+ filePath: this.ciConfigPath,
+ }),
variant: 'danger',
};
case LOAD_FAILURE_UNKNOWN:
return {
- text: this.$options.errorTexts[LOAD_FAILURE_UNKNOWN],
+ text: this.$options.alertTexts[LOAD_FAILURE_UNKNOWN],
variant: 'danger',
};
case COMMIT_FAILURE:
return {
- text: this.$options.errorTexts[COMMIT_FAILURE],
+ text: this.$options.alertTexts[COMMIT_FAILURE],
variant: 'danger',
};
default:
return {
- text: this.$options.errorTexts[DEFAULT_FAILURE],
+ text: this.$options.alertTexts[DEFAULT_FAILURE],
variant: 'danger',
};
}
@@ -160,30 +176,34 @@ export default {
defaultCommitMessage: __('Update %{sourcePath} file'),
tabEdit: s__('Pipelines|Write pipeline configuration'),
tabGraph: s__('Pipelines|Visualize'),
+ tabLint: s__('Pipelines|Lint'),
},
- errorTexts: {
- [LOAD_FAILURE_NO_REF]: s__(
- 'Pipelines|Repository does not have a default branch, please set one.',
+ alertTexts: {
+ [COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'),
+ [COMMIT_SUCCESS]: __('Your changes have been successfully committed.'),
+ [DEFAULT_FAILURE]: __('Something went wrong on our end.'),
+ [LOAD_FAILURE_NO_FILE]: s__(
+ 'Pipelines|There is no %{filePath} file in this repository, please add one and visit the Pipeline Editor again.',
),
- [LOAD_FAILURE_NO_FILE]: s__('Pipelines|No CI file found in this repository, please add one.'),
[LOAD_FAILURE_UNKNOWN]: s__('Pipelines|The CI configuration was not loaded, please try again.'),
- [COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'),
},
methods: {
handleBlobContentError(error = {}) {
const { networkError } = error;
const { response } = networkError;
- if (response?.status === 404) {
- // 404 for missing CI file
+ // 404 for missing CI file
+ // 400 for blank projects with no repository
+ if (
+ response?.status === httpStatusCodes.NOT_FOUND ||
+ response?.status === httpStatusCodes.BAD_REQUEST
+ ) {
this.reportFailure(LOAD_FAILURE_NO_FILE);
- } else if (response?.status === 400) {
- // 400 for a missing ref when no default branch is set
- this.reportFailure(LOAD_FAILURE_NO_REF);
} else {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
}
},
+
dismissFailure() {
this.showFailureAlert = false;
},
@@ -192,6 +212,14 @@ export default {
this.failureType = type;
this.failureReasons = reasons;
},
+ dismissSuccess() {
+ this.showSuccessAlert = false;
+ },
+ reportSuccess(type) {
+ this.showSuccessAlert = true;
+ this.successType = type;
+ },
+
redirectToNewMergeRequest(sourceBranch) {
const url = mergeUrlParams(
{
@@ -209,18 +237,18 @@ export default {
try {
const {
data: {
- commitCreate: { errors },
+ commitCreate: { errors, commit },
},
} = await this.$apollo.mutate({
mutation: commitCiFileMutation,
variables: {
- projectPath: this.projectPath,
+ projectPath: this.projectFullPath,
branch,
startBranch: this.defaultBranch,
message,
filePath: this.ciConfigPath,
content: this.contentModel,
- lastCommitId: this.commitId,
+ lastCommitId: this.lastCommitSha,
},
});
@@ -232,8 +260,10 @@ export default {
if (openMergeRequest) {
this.redirectToNewMergeRequest(branch);
} else {
- // Refresh the page to ensure commit is updated
- refreshCurrentPage();
+ this.reportSuccess(COMMIT_SUCCESS);
+
+ // Update latest commit
+ this.lastCommitSha = commit.sha;
}
} catch (error) {
this.reportFailure(COMMIT_FAILURE, [error?.message]);
@@ -251,6 +281,14 @@ export default {
<template>
<div class="gl-mt-4">
<gl-alert
+ v-if="showSuccessAlert"
+ :variant="success.variant"
+ :dismissible="true"
+ @dismiss="dismissSuccess"
+ >
+ {{ success.text }}
+ </gl-alert>
+ <gl-alert
v-if="showFailureAlert"
:variant="failure.variant"
:dismissible="true"
@@ -261,25 +299,39 @@ export default {
<li v-for="reason in failureReasons" :key="reason">{{ reason }}</li>
</ul>
</gl-alert>
- <div class="gl-mt-4">
- <gl-loading-icon v-if="isBlobContentLoading" size="lg" class="gl-m-3" />
- <div v-else class="file-editor gl-mb-3">
- <gl-tabs v-model="currentTabIndex">
- <!-- editor should be mounted when its tab is visible, so the container has a size -->
- <gl-tab :title="$options.i18n.tabEdit" :lazy="!editorIsReady">
- <!-- editor should be mounted only once, when the tab is displayed -->
- <text-editor v-model="contentModel" @editor-ready="editorIsReady = true" />
- </gl-tab>
+ <gl-loading-icon v-if="isBlobContentLoading" size="lg" class="gl-m-3" />
+ <div v-else-if="!isBlobContentError" class="gl-mt-4">
+ <div class="file-editor gl-mb-3">
+ <div class="info-well gl-display-none gl-display-sm-block">
+ <validation-segment
+ class="well-segment"
+ :loading="isCiConfigDataLoading"
+ :ci-config="ciConfigData"
+ />
+ </div>
+ <gl-tabs>
+ <editor-tab :lazy="true" :title="$options.i18n.tabEdit">
+ <text-editor
+ v-model="contentModel"
+ :ci-config-path="ciConfigPath"
+ :commit-sha="lastCommitSha"
+ />
+ </editor-tab>
<gl-tab
v-if="glFeatures.ciConfigVisualizationTab"
+ :lazy="true"
:title="$options.i18n.tabGraph"
- :lazy="!isVisualizeTabActive"
data-testid="visualization-tab"
>
- <gl-loading-icon v-if="isVisualizationTabLoading" size="lg" class="gl-m-3" />
+ <gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" />
<pipeline-graph v-else :pipeline-data="ciConfigData" />
</gl-tab>
+
+ <editor-tab :title="$options.i18n.tabLint">
+ <gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" />
+ <ci-lint v-else :ci-config="ciConfigData" />
+ </editor-tab>
</gl-tabs>
</div>
<commit-form