diff options
Diffstat (limited to 'app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue')
-rw-r--r-- | app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue | 269 |
1 files changed, 82 insertions, 187 deletions
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue index 21993e2120a..b4a818e2472 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue @@ -1,84 +1,55 @@ <script> -import { GlAlert, GlLoadingIcon, GlTabs, GlTab } from '@gitlab/ui'; -import { __, s__, sprintf } from '~/locale'; -import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; import httpStatusCodes from '~/lib/utils/http_status'; +import { __, s__, sprintf } from '~/locale'; -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 { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils'; +import ConfirmUnsavedChangesDialog from './components/ui/confirm_unsaved_changes_dialog.vue'; +import { + COMMIT_FAILURE, + COMMIT_SUCCESS, + DEFAULT_FAILURE, + LOAD_FAILURE_NO_FILE, + LOAD_FAILURE_UNKNOWN, +} from './constants'; import getBlobContent from './graphql/queries/blob_content.graphql'; import getCiConfigData from './graphql/queries/ci_config.graphql'; -import { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils'; - -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_UNKNOWN = 'LOAD_FAILURE_UNKNOWN'; +import PipelineEditorHome from './pipeline_editor_home.vue'; export default { components: { - CiLint, - CommitForm, - EditorTab, + ConfirmUnsavedChangesDialog, GlAlert, GlLoadingIcon, - GlTabs, - GlTab, - PipelineGraph, - TextEditor, - ValidationSegment, + PipelineEditorHome, }, - mixins: [glFeatureFlagsMixin()], - inject: ['projectFullPath'], - props: { - defaultBranch: { - type: String, - required: false, - default: null, + inject: { + ciConfigPath: { + default: '', }, - commitSha: { - type: String, - required: false, + defaultBranch: { default: null, }, - ciConfigPath: { - type: String, - required: true, - }, - newMergeRequestPath: { - type: String, - required: true, + projectFullPath: { + default: '', }, }, data() { return { ciConfigData: {}, - content: '', - contentModel: '', - lastCommitSha: this.commitSha, - isSaving: false, - // Success and failure state failureType: null, - showFailureAlert: false, failureReasons: [], - successType: null, + initialCiFileContent: '', + lastCommittedContent: '', + currentCiFileContent: '', + showFailureAlert: false, showSuccessAlert: false, + successType: null, }; }, apollo: { - content: { + initialCiFileContent: { query: getBlobContent, variables() { return { @@ -91,7 +62,10 @@ export default { return data?.blobContent?.rawData; }, result({ data }) { - this.contentModel = data?.blobContent?.rawData ?? ''; + const fileContent = data?.blobContent?.rawData ?? ''; + + this.lastCommittedContent = fileContent; + this.currentCiFileContent = fileContent; }, error(error) { this.handleBlobContentError(error); @@ -100,13 +74,13 @@ export default { ciConfigData: { query: getCiConfigData, // If content is not loaded, we can't lint the data - skip: ({ contentModel }) => { - return !contentModel; + skip: ({ currentCiFileContent }) => { + return !currentCiFileContent; }, variables() { return { projectPath: this.projectFullPath, - content: this.contentModel, + content: this.currentCiFileContent, }; }, update(data) { @@ -122,8 +96,11 @@ export default { }, }, computed: { + hasUnsavedChanges() { + return this.lastCommittedContent !== this.currentCiFileContent; + }, isBlobContentLoading() { - return this.$apollo.queries.content.loading; + return this.$apollo.queries.initialCiFileContent.loading; }, isBlobContentError() { return this.failureType === LOAD_FAILURE_NO_FILE; @@ -131,62 +108,60 @@ export default { isCiConfigDataLoading() { return this.$apollo.queries.ciConfigData.loading; }, - defaultCommitMessage() { - return sprintf(this.$options.i18n.defaultCommitMessage, { sourcePath: this.ciConfigPath }); - }, - success() { - switch (this.successType) { - case COMMIT_SUCCESS: - return { - text: this.$options.alertTexts[COMMIT_SUCCESS], - variant: 'info', - }; - default: - return null; - } - }, failure() { switch (this.failureType) { case LOAD_FAILURE_NO_FILE: return { - text: sprintf(this.$options.alertTexts[LOAD_FAILURE_NO_FILE], { + text: sprintf(this.$options.errorTexts[LOAD_FAILURE_NO_FILE], { filePath: this.ciConfigPath, }), variant: 'danger', }; case LOAD_FAILURE_UNKNOWN: return { - text: this.$options.alertTexts[LOAD_FAILURE_UNKNOWN], + text: this.$options.errorTexts[LOAD_FAILURE_UNKNOWN], variant: 'danger', }; case COMMIT_FAILURE: return { - text: this.$options.alertTexts[COMMIT_FAILURE], + text: this.$options.errorTexts[COMMIT_FAILURE], variant: 'danger', }; default: return { - text: this.$options.alertTexts[DEFAULT_FAILURE], + text: this.$options.errorTexts[DEFAULT_FAILURE], variant: 'danger', }; } }, + success() { + switch (this.successType) { + case COMMIT_SUCCESS: + return { + text: this.$options.successTexts[COMMIT_SUCCESS], + variant: 'info', + }; + default: + return null; + } + }, }, i18n: { - defaultCommitMessage: __('Update %{sourcePath} file'), tabEdit: s__('Pipelines|Write pipeline configuration'), tabGraph: s__('Pipelines|Visualize'), tabLint: s__('Pipelines|Lint'), }, - alertTexts: { + errorTexts: { [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_UNKNOWN]: s__('Pipelines|The CI configuration was not loaded, please try again.'), }, + successTexts: { + [COMMIT_SUCCESS]: __('Your changes have been successfully committed.'), + }, methods: { handleBlobContentError(error = {}) { const { networkError } = error; @@ -207,72 +182,32 @@ export default { dismissFailure() { this.showFailureAlert = false; }, + dismissSuccess() { + this.showSuccessAlert = false; + }, reportFailure(type, reasons = []) { this.showFailureAlert = true; this.failureType = type; this.failureReasons = reasons; }, - dismissSuccess() { - this.showSuccessAlert = false; - }, reportSuccess(type) { this.showSuccessAlert = true; this.successType = type; }, - - redirectToNewMergeRequest(sourceBranch) { - const url = mergeUrlParams( - { - [MR_SOURCE_BRANCH]: sourceBranch, - [MR_TARGET_BRANCH]: this.defaultBranch, - }, - this.newMergeRequestPath, - ); - redirectTo(url); + resetContent() { + this.currentCiFileContent = this.lastCommittedContent; }, - async onCommitSubmit(event) { - this.isSaving = true; - const { message, branch, openMergeRequest } = event; - - try { - const { - data: { - commitCreate: { errors, commit }, - }, - } = await this.$apollo.mutate({ - mutation: commitCiFileMutation, - variables: { - projectPath: this.projectFullPath, - branch, - startBranch: this.defaultBranch, - message, - filePath: this.ciConfigPath, - content: this.contentModel, - lastCommitId: this.lastCommitSha, - }, - }); - - if (errors?.length) { - this.reportFailure(COMMIT_FAILURE, errors); - return; - } - - if (openMergeRequest) { - this.redirectToNewMergeRequest(branch); - } else { - this.reportSuccess(COMMIT_SUCCESS); - - // Update latest commit - this.lastCommitSha = commit.sha; - } - } catch (error) { - this.reportFailure(COMMIT_FAILURE, [error?.message]); - } finally { - this.isSaving = false; - } + showErrorAlert({ type, reasons = [] }) { + this.reportFailure(type, reasons); }, - onCommitCancel() { - this.contentModel = this.content; + updateCiConfig(ciFileContent) { + this.currentCiFileContent = ciFileContent; + }, + updateOnCommit({ type }) { + this.reportSuccess(type); + // Keep track of the latest commited content to know + // if the user has made changes to the file that are unsaved. + this.lastCommittedContent = this.currentCiFileContent; }, }, }; @@ -280,20 +215,10 @@ export default { <template> <div class="gl-mt-4"> - <gl-alert - v-if="showSuccessAlert" - :variant="success.variant" - :dismissible="true" - @dismiss="dismissSuccess" - > + <gl-alert v-if="showSuccessAlert" :variant="success.variant" @dismiss="dismissSuccess"> {{ success.text }} </gl-alert> - <gl-alert - v-if="showFailureAlert" - :variant="failure.variant" - :dismissible="true" - @dismiss="dismissFailure" - > + <gl-alert v-if="showFailureAlert" :variant="failure.variant" @dismiss="dismissFailure"> {{ failure.text }} <ul v-if="failureReasons.length" class="gl-mb-0"> <li v-for="reason in failureReasons" :key="reason">{{ reason }}</li> @@ -301,46 +226,16 @@ export default { </gl-alert> <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" - data-testid="visualization-tab" - > - <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 - :default-branch="defaultBranch" - :default-message="defaultCommitMessage" - :is-saving="isSaving" - @cancel="onCommitCancel" - @submit="onCommitSubmit" + <pipeline-editor-home + :is-ci-config-data-loading="isCiConfigDataLoading" + :ci-config-data="ciConfigData" + :ci-file-content="currentCiFileContent" + @commit="updateOnCommit" + @resetContent="resetContent" + @showError="showErrorAlert" + @updateCiConfig="updateCiConfig" /> </div> + <confirm-unsaved-changes-dialog :has-unsaved-changes="hasUnsavedChanges" /> </div> </template> |