diff options
Diffstat (limited to 'app/assets/javascripts/pipeline_editor/components')
-rw-r--r-- | app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue | 114 | ||||
-rw-r--r-- | app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue | 100 | ||||
-rw-r--r-- | app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue (renamed from app/assets/javascripts/pipeline_editor/components/text_editor.vue) | 30 | ||||
-rw-r--r-- | app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_header.vue | 30 | ||||
-rw-r--r-- | app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue (renamed from app/assets/javascripts/pipeline_editor/components/info/validation_segment.vue) | 2 | ||||
-rw-r--r-- | app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue | 6 | ||||
-rw-r--r-- | app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results_param.vue | 2 | ||||
-rw-r--r-- | app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue | 121 | ||||
-rw-r--r-- | app/assets/javascripts/pipeline_editor/components/ui/confirm_unsaved_changes_dialog.vue | 26 |
9 files changed, 414 insertions, 17 deletions
diff --git a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue new file mode 100644 index 00000000000..b40c9a48903 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue @@ -0,0 +1,114 @@ +<script> +import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility'; +import { __, s__, sprintf } from '~/locale'; +import { COMMIT_FAILURE, COMMIT_SUCCESS } from '../../constants'; +import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql'; +import getCommitSha from '../../graphql/queries/client/commit_sha.graphql'; + +import CommitForm from './commit_form.vue'; + +const MR_SOURCE_BRANCH = 'merge_request[source_branch]'; +const MR_TARGET_BRANCH = 'merge_request[target_branch]'; + +export default { + alertTexts: { + [COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'), + [COMMIT_SUCCESS]: __('Your changes have been successfully committed.'), + }, + i18n: { + defaultCommitMessage: __('Update %{sourcePath} file'), + }, + components: { + CommitForm, + }, + inject: ['projectFullPath', 'ciConfigPath', 'defaultBranch', 'newMergeRequestPath'], + props: { + ciFileContent: { + type: String, + required: true, + }, + }, + data() { + return { + commit: {}, + isSaving: false, + }; + }, + apollo: { + commitSha: { + query: getCommitSha, + }, + }, + computed: { + defaultCommitMessage() { + return sprintf(this.$options.i18n.defaultCommitMessage, { sourcePath: this.ciConfigPath }); + }, + }, + methods: { + redirectToNewMergeRequest(sourceBranch) { + const url = mergeUrlParams( + { + [MR_SOURCE_BRANCH]: sourceBranch, + [MR_TARGET_BRANCH]: this.defaultBranch, + }, + this.newMergeRequestPath, + ); + redirectTo(url); + }, + async onCommitSubmit({ message, branch, openMergeRequest }) { + this.isSaving = true; + + try { + const { + data: { + commitCreate: { errors }, + }, + } = await this.$apollo.mutate({ + mutation: commitCIFile, + variables: { + projectPath: this.projectFullPath, + branch, + startBranch: this.defaultBranch, + message, + filePath: this.ciConfigPath, + content: this.ciFileContent, + lastCommitId: this.commitSha, + }, + update(store, { data }) { + const commitSha = data?.commitCreate?.commit?.sha; + + if (commitSha) { + store.writeQuery({ query: getCommitSha, data: { commitSha } }); + } + }, + }); + + if (errors?.length) { + this.$emit('showError', { type: COMMIT_FAILURE, reasons: errors }); + } else if (openMergeRequest) { + this.redirectToNewMergeRequest(branch); + } else { + this.$emit('commit', { type: COMMIT_SUCCESS }); + } + } catch (error) { + this.$emit('showError', { type: COMMIT_FAILURE, reasons: [error?.message] }); + } finally { + this.isSaving = false; + } + }, + onCommitCancel() { + this.$emit('resetContent'); + }, + }, +}; +</script> + +<template> + <commit-form + :default-branch="defaultBranch" + :default-message="defaultCommitMessage" + :is-saving="isSaving" + @cancel="onCommitCancel" + @submit="onCommitSubmit" + /> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue b/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue new file mode 100644 index 00000000000..007faa4ed0d --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue @@ -0,0 +1,100 @@ +<script> +import { GlAlert, GlIcon } from '@gitlab/ui'; +import { uniqueId } from 'lodash'; +import { __, s__ } from '~/locale'; +import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants'; +import { DEFAULT, INVALID_CI_CONFIG } from '~/pipelines/constants'; +import EditorLite from '~/vue_shared/components/editor_lite.vue'; + +export default { + i18n: { + viewOnlyMessage: s__('Pipelines|Merged YAML is view only'), + }, + errorTexts: { + [INVALID_CI_CONFIG]: __('Your CI configuration file is invalid.'), + [DEFAULT]: __('An unknown error occurred.'), + }, + components: { + EditorLite, + GlAlert, + GlIcon, + }, + inject: ['ciConfigPath'], + props: { + ciConfigData: { + type: Object, + required: true, + }, + }, + data() { + return { + failureType: null, + }; + }, + computed: { + failure() { + switch (this.failureType) { + case INVALID_CI_CONFIG: + return this.$options.errorTexts[INVALID_CI_CONFIG]; + default: + return this.$options.errorTexts[DEFAULT]; + } + }, + fileGlobalId() { + return `${this.ciConfigPath}-${uniqueId()}`; + }, + hasError() { + return this.failureType; + }, + isInvalidConfiguration() { + return this.ciConfigData.status === CI_CONFIG_STATUS_INVALID; + }, + mergedYaml() { + return this.ciConfigData.mergedYaml; + }, + }, + watch: { + ciConfigData: { + immediate: true, + handler() { + if (this.isInvalidConfiguration) { + this.reportFailure(INVALID_CI_CONFIG); + } else if (this.hasError) { + this.resetFailure(); + } + }, + }, + }, + methods: { + reportFailure(errorType) { + this.failureType = errorType; + }, + resetFailure() { + this.failureType = null; + }, + }, +}; +</script> +<template> + <div> + <gl-alert v-if="hasError" variant="danger" :dismissible="false"> + {{ failure }} + </gl-alert> + <div v-else> + <div class="gl-display-flex gl-align-items-center"> + <gl-icon :size="18" name="lock" class="gl-text-gray-500 gl-mr-3" /> + {{ $options.i18n.viewOnlyMessage }} + </div> + <div class="gl-mt-3 gl-border-solid gl-border-gray-100 gl-border-1"> + <editor-lite + ref="editor" + :value="mergedYaml" + :file-name="ciConfigPath" + :file-global-id="fileGlobalId" + :editor-options="{ readOnly: true }" + v-on="$listeners" + /> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/text_editor.vue b/app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue index b8d49d77ea9..872da88d3e6 100644 --- a/app/assets/javascripts/pipeline_editor/components/text_editor.vue +++ b/app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue @@ -1,26 +1,30 @@ <script> -import EditorLite from '~/vue_shared/components/editor_lite.vue'; +import { EDITOR_READY_EVENT } from '~/editor/constants'; import { CiSchemaExtension } from '~/editor/extensions/editor_ci_schema_ext'; +import EditorLite from '~/vue_shared/components/editor_lite.vue'; +import getCommitSha from '../../graphql/queries/client/commit_sha.graphql'; export default { components: { EditorLite, }, - inject: ['projectPath', 'projectNamespace'], + inject: ['ciConfigPath', 'projectPath', 'projectNamespace'], inheritAttrs: false, - props: { - ciConfigPath: { - type: String, - required: true, - }, + data() { + return { + commitSha: '', + }; + }, + apollo: { commitSha: { - type: String, - required: false, - default: null, + query: getCommitSha, }, }, methods: { - onEditorReady() { + onCiConfigUpdate(content) { + this.$emit('updateCiConfig', content); + }, + registerCiSchema() { const editorInstance = this.$refs.editor.getEditor(); editorInstance.use(new CiSchemaExtension()); @@ -31,6 +35,7 @@ export default { }); }, }, + readyEvent: EDITOR_READY_EVENT, }; </script> <template> @@ -39,7 +44,8 @@ export default { ref="editor" :file-name="ciConfigPath" v-bind="$attrs" - @editor-ready="onEditorReady" + @[$options.readyEvent]="registerCiSchema" + @input="onCiConfigUpdate" v-on="$listeners" /> </div> diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_header.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_header.vue new file mode 100644 index 00000000000..ab41c0170e9 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_header.vue @@ -0,0 +1,30 @@ +<script> +import ValidationSegment from './validation_segment.vue'; + +export default { + validationSegmentClasses: + 'gl-p-5 gl-bg-gray-10 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base', + components: { + ValidationSegment, + }, + props: { + ciConfigData: { + type: Object, + required: true, + }, + isCiConfigDataLoading: { + type: Boolean, + required: true, + }, + }, +}; +</script> +<template> + <div class="gl-mb-5"> + <validation-segment + :class="$options.validationSegmentClasses" + :loading="isCiConfigDataLoading" + :ci-config="ciConfigData" + /> + </div> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/info/validation_segment.vue b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue index 22f378c571a..94fb3a66fdd 100644 --- a/app/assets/javascripts/pipeline_editor/components/info/validation_segment.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue @@ -1,8 +1,8 @@ <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'; +import { CI_CONFIG_STATUS_VALID } from '../../constants'; export const i18n = { learnMore: __('Learn more'), 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 58a96c3f725..5d9697c9427 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 @@ -1,9 +1,9 @@ <script> import { GlAlert, GlLink, GlSprintf, GlTable } from '@gitlab/ui'; -import CiLintWarnings from './ci_lint_warnings.vue'; -import CiLintResultsValue from './ci_lint_results_value.vue'; -import CiLintResultsParam from './ci_lint_results_param.vue'; import { __ } from '~/locale'; +import CiLintResultsParam from './ci_lint_results_param.vue'; +import CiLintResultsValue from './ci_lint_results_value.vue'; +import CiLintWarnings from './ci_lint_warnings.vue'; const thBorderColor = 'gl-border-gray-100!'; diff --git a/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results_param.vue b/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results_param.vue index 23808bcb292..49225a7cac7 100644 --- a/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results_param.vue +++ b/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results_param.vue @@ -1,6 +1,6 @@ <script> -import { __ } from '~/locale'; import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; +import { __ } from '~/locale'; export default { props: { diff --git a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue new file mode 100644 index 00000000000..3bdcf383bee --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue @@ -0,0 +1,121 @@ +<script> +import { GlAlert, GlLoadingIcon, GlTabs, GlTab } 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 { + CI_CONFIG_STATUS_INVALID, + CREATE_TAB, + LINT_TAB, + MERGED_TAB, + VISUALIZE_TAB, +} from '../constants'; +import CiConfigMergedPreview from './editor/ci_config_merged_preview.vue'; +import TextEditor from './editor/text_editor.vue'; +import CiLint from './lint/ci_lint.vue'; +import EditorTab from './ui/editor_tab.vue'; + +export default { + i18n: { + tabEdit: s__('Pipelines|Write pipeline configuration'), + tabGraph: s__('Pipelines|Visualize'), + tabLint: s__('Pipelines|Lint'), + tabMergedYaml: s__('Pipelines|View merged YAML'), + }, + errorTexts: { + loadMergedYaml: s__('Pipelines|Could not load merged YAML content'), + }, + tabConstants: { + CREATE_TAB, + LINT_TAB, + MERGED_TAB, + VISUALIZE_TAB, + }, + components: { + CiConfigMergedPreview, + CiLint, + EditorTab, + GlAlert, + GlLoadingIcon, + GlTab, + GlTabs, + PipelineGraph, + TextEditor, + }, + mixins: [glFeatureFlagsMixin()], + props: { + ciConfigData: { + type: Object, + required: true, + }, + ciFileContent: { + type: String, + required: true, + }, + isCiConfigDataLoading: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + hasMergedYamlLoadError() { + return ( + !this.ciConfigData?.mergedYaml && this.ciConfigData.status !== CI_CONFIG_STATUS_INVALID + ); + }, + }, + methods: { + setCurrentTab(tabName) { + this.$emit('set-current-tab', tabName); + }, + }, +}; +</script> +<template> + <gl-tabs class="file-editor gl-mb-3"> + <editor-tab + class="gl-mb-3" + :title="$options.i18n.tabEdit" + lazy + data-testid="editor-tab" + @click="setCurrentTab($options.tabConstants.CREATE_TAB)" + > + <text-editor :value="ciFileContent" v-on="$listeners" /> + </editor-tab> + <gl-tab + v-if="glFeatures.ciConfigVisualizationTab" + class="gl-mb-3" + :title="$options.i18n.tabGraph" + lazy + data-testid="visualization-tab" + @click="setCurrentTab($options.tabConstants.VISUALIZE_TAB)" + > + <gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" /> + <pipeline-graph v-else :pipeline-data="ciConfigData" /> + </gl-tab> + <editor-tab + class="gl-mb-3" + :title="$options.i18n.tabLint" + data-testid="lint-tab" + @click="setCurrentTab($options.tabConstants.LINT_TAB)" + > + <gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" /> + <ci-lint v-else :ci-config="ciConfigData" /> + </editor-tab> + <gl-tab + v-if="glFeatures.ciConfigMergedTab" + class="gl-mb-3" + :title="$options.i18n.tabMergedYaml" + lazy + data-testid="merged-tab" + @click="setCurrentTab($options.tabConstants.MERGED_TAB)" + > + <gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" /> + <gl-alert v-else-if="hasMergedYamlLoadError" variant="danger" :dismissible="false"> + {{ $options.errorTexts.loadMergedYaml }} + </gl-alert> + <ci-config-merged-preview v-else :ci-config-data="ciConfigData" v-on="$listeners" /> + </gl-tab> + </gl-tabs> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/ui/confirm_unsaved_changes_dialog.vue b/app/assets/javascripts/pipeline_editor/components/ui/confirm_unsaved_changes_dialog.vue new file mode 100644 index 00000000000..bc076fbe349 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/ui/confirm_unsaved_changes_dialog.vue @@ -0,0 +1,26 @@ +<script> +export default { + props: { + hasUnsavedChanges: { + type: Boolean, + required: true, + }, + }, + created() { + window.addEventListener('beforeunload', this.confirmChanges); + }, + destroyed() { + window.removeEventListener('beforeunload', this.confirmChanges); + }, + methods: { + confirmChanges(e = {}) { + if (this.hasUnsavedChanges) { + e.preventDefault(); + // eslint-disable-next-line no-param-reassign + e.returnValue = ''; // Chrome requires returnValue to be set + } + }, + }, + render: () => null, +}; +</script> |