diff options
Diffstat (limited to 'app/assets/javascripts/pipeline_editor')
16 files changed, 379 insertions, 63 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 9279273283e..b088678fee8 100644 --- a/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue +++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue @@ -21,7 +21,7 @@ export default { GlSprintf, }, props: { - defaultBranch: { + currentBranch: { type: String, required: false, default: '', @@ -40,23 +40,23 @@ export default { data() { return { message: this.defaultMessage, - branch: this.defaultBranch, openMergeRequest: false, + targetBranch: this.currentBranch, }; }, computed: { - isDefaultBranch() { - return this.branch === this.defaultBranch; + isCurrentBranchTarget() { + return this.targetBranch === this.currentBranch; }, submitDisabled() { - return !(this.message && this.branch); + return !(this.message && this.targetBranch); }, }, methods: { onSubmit() { this.$emit('submit', { message: this.message, - branch: this.branch, + targetBranch: this.targetBranch, openMergeRequest: this.openMergeRequest, }); }, @@ -100,12 +100,12 @@ export default { > <gl-form-input id="target-branch-field" - v-model="branch" + v-model="targetBranch" class="gl-font-monospace!" required /> <gl-form-checkbox - v-if="!isDefaultBranch" + v-if="!isCurrentBranchTarget" v-model="openMergeRequest" data-testid="new-mr-checkbox" class="gl-mt-3" diff --git a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue index b40c9a48903..14a4a9d5710 100644 --- a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue +++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue @@ -1,9 +1,16 @@ <script> import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility'; import { __, s__, sprintf } from '~/locale'; -import { COMMIT_FAILURE, COMMIT_SUCCESS } from '../../constants'; +import { + COMMIT_ACTION_CREATE, + COMMIT_ACTION_UPDATE, + 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 getCurrentBranch from '../../graphql/queries/client/current_branch.graphql'; +import getIsNewCiConfigFile from '../../graphql/queries/client/is_new_ci_config_file.graphql'; import CommitForm from './commit_form.vue'; @@ -21,7 +28,7 @@ export default { components: { CommitForm, }, - inject: ['projectFullPath', 'ciConfigPath', 'defaultBranch', 'newMergeRequestPath'], + inject: ['projectFullPath', 'ciConfigPath', 'newMergeRequestPath'], props: { ciFileContent: { type: String, @@ -31,15 +38,25 @@ export default { data() { return { commit: {}, + isNewCiConfigFile: false, isSaving: false, }; }, apollo: { + isNewCiConfigFile: { + query: getIsNewCiConfigFile, + }, commitSha: { query: getCommitSha, }, + currentBranch: { + query: getCurrentBranch, + }, }, computed: { + action() { + return this.isNewCiConfigFile ? COMMIT_ACTION_CREATE : COMMIT_ACTION_UPDATE; + }, defaultCommitMessage() { return sprintf(this.$options.i18n.defaultCommitMessage, { sourcePath: this.ciConfigPath }); }, @@ -49,13 +66,13 @@ export default { const url = mergeUrlParams( { [MR_SOURCE_BRANCH]: sourceBranch, - [MR_TARGET_BRANCH]: this.defaultBranch, + [MR_TARGET_BRANCH]: this.currentBranch, }, this.newMergeRequestPath, ); redirectTo(url); }, - async onCommitSubmit({ message, branch, openMergeRequest }) { + async onCommitSubmit({ message, targetBranch, openMergeRequest }) { this.isSaving = true; try { @@ -66,9 +83,10 @@ export default { } = await this.$apollo.mutate({ mutation: commitCIFile, variables: { + action: this.action, projectPath: this.projectFullPath, - branch, - startBranch: this.defaultBranch, + branch: targetBranch, + startBranch: this.currentBranch, message, filePath: this.ciConfigPath, content: this.ciFileContent, @@ -86,7 +104,7 @@ export default { if (errors?.length) { this.$emit('showError', { type: COMMIT_FAILURE, reasons: errors }); } else if (openMergeRequest) { - this.redirectToNewMergeRequest(branch); + this.redirectToNewMergeRequest(targetBranch); } else { this.$emit('commit', { type: COMMIT_SUCCESS }); } @@ -105,7 +123,7 @@ export default { <template> <commit-form - :default-branch="defaultBranch" + :current-branch="currentBranch" :default-message="defaultCommitMessage" :is-saving="isSaving" @cancel="onCommitCancel" 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 index 007faa4ed0d..f36b22f33c3 100644 --- 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 @@ -82,7 +82,7 @@ export default { </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" /> + <gl-icon :size="18" name="lock" use-deprecated-sizes 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"> 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 index ab41c0170e9..7a35e31e9ce 100644 --- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_header.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_header.vue @@ -1,13 +1,40 @@ <script> +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import PipelineStatus from './pipeline_status.vue'; import ValidationSegment from './validation_segment.vue'; +const baseClasses = ['gl-p-5', 'gl-bg-gray-10', 'gl-border-solid', 'gl-border-gray-100']; + +const pipelineStatusClasses = [ + ...baseClasses, + 'gl-border-1', + 'gl-border-b-0!', + 'gl-rounded-top-base', +]; + +const validationSegmentClasses = [...baseClasses, 'gl-border-1', 'gl-rounded-base']; + +const validationSegmentWithPipelineStatusClasses = [ + ...baseClasses, + 'gl-border-1', + 'gl-rounded-bottom-left-base', + 'gl-rounded-bottom-right-base', +]; + export default { - validationSegmentClasses: - 'gl-p-5 gl-bg-gray-10 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base', + pipelineStatusClasses, + validationSegmentClasses, + validationSegmentWithPipelineStatusClasses, components: { + PipelineStatus, ValidationSegment, }, + mixins: [glFeatureFlagsMixin()], props: { + ciFileContent: { + type: String, + required: true, + }, ciConfigData: { type: Object, required: true, @@ -17,13 +44,27 @@ export default { required: true, }, }, + computed: { + showPipelineStatus() { + return this.glFeatures.pipelineStatusForPipelineEditor; + }, + // make sure corners are rounded correctly depending on if + // pipeline status is rendered + validationStyling() { + return this.showPipelineStatus + ? this.$options.validationSegmentWithPipelineStatusClasses + : this.$options.validationSegmentClasses; + }, + }, }; </script> <template> <div class="gl-mb-5"> + <pipeline-status v-if="showPipelineStatus" :class="$options.pipelineStatusClasses" /> <validation-segment - :class="$options.validationSegmentClasses" + :class="validationStyling" :loading="isCiConfigDataLoading" + :ci-file-content="ciFileContent" :ci-config="ciConfigData" /> </div> diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue new file mode 100644 index 00000000000..b1ea464be99 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue @@ -0,0 +1,120 @@ +<script> +import { GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { s__ } from '~/locale'; +import getCommitSha from '~/pipeline_editor/graphql/queries/client/commit_sha.graphql'; +import getPipelineQuery from '~/pipeline_editor/graphql/queries/client/pipeline.graphql'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; + +const POLL_INTERVAL = 10000; +export const i18n = { + fetchError: s__('Pipeline|We are currently unable to fetch pipeline data'), + fetchLoading: s__('Pipeline|Checking pipeline status'), + pipelineInfo: s__( + `Pipeline|Pipeline %{idStart}#%{idEnd} %{statusStart}%{statusEnd} for %{commitStart}%{commitEnd}`, + ), +}; + +export default { + i18n, + components: { + CiIcon, + GlIcon, + GlLink, + GlLoadingIcon, + GlSprintf, + }, + inject: ['projectFullPath'], + apollo: { + commitSha: { + query: getCommitSha, + }, + pipeline: { + query: getPipelineQuery, + variables() { + return { + fullPath: this.projectFullPath, + sha: this.commitSha, + }; + }, + update: (data) => { + const { id, commitPath = '', shortSha = '', detailedStatus = {} } = + data.project?.pipeline || {}; + + return { + id, + commitPath, + shortSha, + detailedStatus, + }; + }, + error() { + this.hasError = true; + }, + pollInterval: POLL_INTERVAL, + }, + }, + data() { + return { + hasError: false, + }; + }, + computed: { + hasPipelineData() { + return Boolean(this.$apollo.queries.pipeline?.id); + }, + isQueryLoading() { + return this.$apollo.queries.pipeline.loading && !this.hasPipelineData; + }, + status() { + return this.pipeline.detailedStatus; + }, + pipelineId() { + return getIdFromGraphQLId(this.pipeline.id); + }, + }, +}; +</script> + +<template> + <div class="gl-white-space-nowrap gl-max-w-full"> + <template v-if="isQueryLoading"> + <gl-loading-icon class="gl-mr-auto gl-display-inline-block" size="sm" /> + <span data-testid="pipeline-loading-msg">{{ $options.i18n.fetchLoading }}</span> + </template> + <template v-else-if="hasError"> + <gl-icon class="gl-mr-auto" name="warning-solid" /> + <span data-testid="pipeline-error-msg">{{ $options.i18n.fetchError }}</span> + </template> + <template v-else> + <a :href="status.detailsPath" class="gl-mr-auto"> + <ci-icon :status="status" :size="18" /> + </a> + <span class="gl-font-weight-bold"> + <gl-sprintf :message="$options.i18n.pipelineInfo"> + <template #id="{ content }"> + <gl-link + :href="status.detailsPath" + class="pipeline-id gl-font-weight-normal pipeline-number" + target="_blank" + data-testid="pipeline-id" + > + {{ content }}{{ pipelineId }}</gl-link + > + </template> + <template #status>{{ status.text }}</template> + <template #commit> + <gl-link + :href="pipeline.commitPath" + class="commit-sha gl-font-weight-normal" + target="_blank" + data-testid="pipeline-commit" + > + {{ pipeline.shortSha }} + </gl-link> + </template> + </gl-sprintf> + </span> + </template> + </div> +</template> 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 94fb3a66fdd..541ab74b177 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,9 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; import { CI_CONFIG_STATUS_VALID } from '../../constants'; export const i18n = { + empty: __( + "We'll continuously validate your pipeline configuration. The validation results will appear here.", + ), learnMore: __('Learn more'), loading: s__('Pipelines|Validating GitLab CI configuration…'), invalid: s__('Pipelines|This GitLab CI configuration is invalid.'), @@ -26,6 +29,10 @@ export default { }, }, props: { + ciFileContent: { + type: String, + required: true, + }, ciConfig: { type: Object, required: false, @@ -38,17 +45,22 @@ export default { }, }, computed: { + isEmpty() { + return !this.ciFileContent; + }, isValid() { return this.ciConfig?.status === CI_CONFIG_STATUS_VALID; }, icon() { - if (this.isValid) { + if (this.isValid || this.isEmpty) { return 'check'; } return 'warning-solid'; }, message() { - if (this.isValid) { + if (this.isEmpty) { + return this.$options.i18n.empty; + } else if (this.isValid) { return this.$options.i18n.valid; } @@ -74,7 +86,7 @@ export default { <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"> + <span v-if="!isEmpty" class="gl-flex-shrink-0 gl-pl-2"> <gl-link data-testid="learnMoreLink" :href="ymlHelpPagePath"> {{ $options.i18n.learnMore }} </gl-link> diff --git a/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue b/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue new file mode 100644 index 00000000000..d4f04a0d055 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue @@ -0,0 +1,56 @@ +<script> +import { GlButton, GlSprintf } from '@gitlab/ui'; +import { __ } from '~/locale'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; + +export default { + components: { + GlButton, + GlSprintf, + }, + i18n: { + title: __('Optimize your workflow with CI/CD Pipelines'), + body: __( + 'Create a new %{codeStart}.gitlab-ci.yml%{codeEnd} file at the root of the repository to get started.', + ), + btnText: __('Create new CI/CD pipeline'), + }, + mixins: [glFeatureFlagsMixin()], + inject: { + emptyStateIllustrationPath: { + default: '', + }, + }, + computed: { + showCTAButton() { + return this.glFeatures.pipelineEditorEmptyStateAction; + }, + }, + methods: { + createEmptyConfigFile() { + this.$emit('createEmptyConfigFile'); + }, + }, +}; +</script> +<template> + <div class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-mt-11"> + <img :src="emptyStateIllustrationPath" /> + <h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1> + <p class="gl-mt-3"> + <gl-sprintf :message="$options.i18n.body"> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + <gl-button + v-if="showCTAButton" + variant="confirm" + class="gl-mt-3" + @click="createEmptyConfigFile" + > + {{ $options.i18n.btnText }} + </gl-button> + </div> +</template> diff --git a/app/assets/javascripts/pipeline_editor/constants.js b/app/assets/javascripts/pipeline_editor/constants.js index e676fdeae02..353deafe770 100644 --- a/app/assets/javascripts/pipeline_editor/constants.js +++ b/app/assets/javascripts/pipeline_editor/constants.js @@ -5,7 +5,6 @@ export const COMMIT_FAILURE = 'COMMIT_FAILURE'; export const COMMIT_SUCCESS = 'COMMIT_SUCCESS'; export const DEFAULT_FAILURE = 'DEFAULT_FAILURE'; -export const LOAD_FAILURE_NO_FILE = 'LOAD_FAILURE_NO_FILE'; export const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN'; export const CREATE_TAB = 'CREATE_TAB'; @@ -14,3 +13,6 @@ export const MERGED_TAB = 'MERGED_TAB'; export const VISUALIZE_TAB = 'VISUALIZE_TAB'; export const TABS_WITH_COMMIT_FORM = [CREATE_TAB, LINT_TAB, VISUALIZE_TAB]; + +export const COMMIT_ACTION_CREATE = 'CREATE'; +export const COMMIT_ACTION_UPDATE = 'UPDATE'; 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 2af0cd5f6d4..3b2daa45a18 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 @@ -1,4 +1,5 @@ mutation commitCIFile( + $action: CommitActionMode! $projectPath: ID! $branch: String! $startBranch: String @@ -14,7 +15,7 @@ mutation commitCIFile( startBranch: $startBranch message: $message actions: [ - { action: UPDATE, filePath: $filePath, lastCommitId: $lastCommitId, content: $content } + { action: $action, filePath: $filePath, lastCommitId: $lastCommitId, content: $content } ] } ) { diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.graphql new file mode 100644 index 00000000000..acd46013f5b --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.graphql @@ -0,0 +1,3 @@ +query getCurrentBranch { + currentBranch @client +} diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/is_new_ci_config_file.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/is_new_ci_config_file.graphql new file mode 100644 index 00000000000..8c2ca276f50 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/is_new_ci_config_file.graphql @@ -0,0 +1,3 @@ +query getIsNewCiConfigFile { + isNewCiConfigFile @client +} diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql new file mode 100644 index 00000000000..7cc7f92fb60 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql @@ -0,0 +1,17 @@ +query getPipeline($fullPath: ID!, $sha: String!) { + project(fullPath: $fullPath) @client { + pipeline(sha: $sha) { + commitPath + id + iid + shortSha + status + detailedStatus { + detailsPath + icon + group + text + } + } + } +} diff --git a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js index 81e75c32846..13f6200693b 100644 --- a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js +++ b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js @@ -11,6 +11,29 @@ export const resolvers = { }), }; }, + + /* eslint-disable @gitlab/require-i18n-strings */ + project() { + return { + __typename: 'Project', + pipeline: { + __typename: 'Pipeline', + commitPath: `/-/commit/aabbccdd`, + id: 'gid://gitlab/Ci::Pipeline/118', + iid: '28', + shortSha: 'aabbccdd', + status: 'SUCCESS', + detailedStatus: { + __typename: 'DetailedStatus', + detailsPath: '/root/sample-ci-project/-/pipelines/118"', + group: 'success', + icon: 'status_success', + text: 'passed', + }, + }, + }; + }, + /* eslint-enable @gitlab/require-i18n-strings */ }, Mutation: { lintCI: (_, { endpoint, content, dry_run }) => { diff --git a/app/assets/javascripts/pipeline_editor/index.js b/app/assets/javascripts/pipeline_editor/index.js index dc427f55b5f..b17ec2d5c25 100644 --- a/app/assets/javascripts/pipeline_editor/index.js +++ b/app/assets/javascripts/pipeline_editor/index.js @@ -22,9 +22,11 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { const { // Add to apollo cache as it can be updated by future queries commitSha, + initialBranchName, // Add to provide/inject API for static values ciConfigPath, defaultBranch, + emptyStateIllustrationPath, lintHelpPagePath, newMergeRequestPath, projectFullPath, @@ -41,6 +43,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { apolloProvider.clients.defaultClient.cache.writeData({ data: { + currentBranch: initialBranchName || defaultBranch, commitSha, }, }); @@ -51,6 +54,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { provide: { ciConfigPath, defaultBranch, + emptyStateIllustrationPath, lintHelpPagePath, newMergeRequestPath, projectFullPath, diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue index b4a818e2472..c1168979e9f 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue @@ -1,19 +1,16 @@ <script> import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; import httpStatusCodes from '~/lib/utils/http_status'; -import { __, s__, sprintf } from '~/locale'; +import { __, s__ } from '~/locale'; 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 PipelineEditorEmptyState from './components/ui/pipeline_editor_empty_state.vue'; +import { COMMIT_FAILURE, COMMIT_SUCCESS, DEFAULT_FAILURE, LOAD_FAILURE_UNKNOWN } from './constants'; import getBlobContent from './graphql/queries/blob_content.graphql'; import getCiConfigData from './graphql/queries/ci_config.graphql'; +import getCurrentBranch from './graphql/queries/client/current_branch.graphql'; +import getIsNewCiConfigFile from './graphql/queries/client/is_new_ci_config_file.graphql'; import PipelineEditorHome from './pipeline_editor_home.vue'; export default { @@ -21,15 +18,13 @@ export default { ConfirmUnsavedChangesDialog, GlAlert, GlLoadingIcon, + PipelineEditorEmptyState, PipelineEditorHome, }, inject: { ciConfigPath: { default: '', }, - defaultBranch: { - default: null, - }, projectFullPath: { default: '', }, @@ -40,6 +35,8 @@ export default { // Success and failure state failureType: null, failureReasons: [], + showStartScreen: false, + isNewCiConfigFile: false, initialCiFileContent: '', lastCommittedContent: '', currentCiFileContent: '', @@ -51,11 +48,18 @@ export default { apollo: { initialCiFileContent: { query: getBlobContent, + // If it's a brand new file, we don't want to fetch the content. + // Then when the user commits the first time, the query would run + // to get the initial file content, but we already have it in `lastCommitedContent` + // so we skip the loading altogether. + skip({ isNewCiConfigFile, lastCommittedContent }) { + return isNewCiConfigFile || lastCommittedContent; + }, variables() { return { projectPath: this.projectFullPath, path: this.ciConfigPath, - ref: this.defaultBranch, + ref: this.currentBranch, }; }, update(data) { @@ -94,6 +98,12 @@ export default { this.reportFailure(LOAD_FAILURE_UNKNOWN); }, }, + currentBranch: { + query: getCurrentBranch, + }, + isNewCiConfigFile: { + query: getIsNewCiConfigFile, + }, }, computed: { hasUnsavedChanges() { @@ -102,21 +112,11 @@ export default { isBlobContentLoading() { return this.$apollo.queries.initialCiFileContent.loading; }, - isBlobContentError() { - return this.failureType === LOAD_FAILURE_NO_FILE; - }, isCiConfigDataLoading() { return this.$apollo.queries.ciConfigData.loading; }, failure() { switch (this.failureType) { - case LOAD_FAILURE_NO_FILE: - return { - text: sprintf(this.$options.errorTexts[LOAD_FAILURE_NO_FILE], { - filePath: this.ciConfigPath, - }), - variant: 'danger', - }; case LOAD_FAILURE_UNKNOWN: return { text: this.$options.errorTexts[LOAD_FAILURE_UNKNOWN], @@ -154,9 +154,6 @@ export default { errorTexts: { [COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'), [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: { @@ -173,7 +170,7 @@ export default { response?.status === httpStatusCodes.NOT_FOUND || response?.status === httpStatusCodes.BAD_REQUEST ) { - this.reportFailure(LOAD_FAILURE_NO_FILE); + this.showStartScreen = true; } else { this.reportFailure(LOAD_FAILURE_UNKNOWN); } @@ -186,17 +183,25 @@ export default { this.showSuccessAlert = false; }, reportFailure(type, reasons = []) { + window.scrollTo({ top: 0, behavior: 'smooth' }); this.showFailureAlert = true; this.failureType = type; this.failureReasons = reasons; }, reportSuccess(type) { + window.scrollTo({ top: 0, behavior: 'smooth' }); this.showSuccessAlert = true; this.successType = type; }, resetContent() { this.currentCiFileContent = this.lastCommittedContent; }, + setNewEmptyCiConfigFile() { + this.$apollo + .getClient() + .writeQuery({ query: getIsNewCiConfigFile, data: { isNewCiConfigFile: true } }); + this.showStartScreen = false; + }, showErrorAlert({ type, reasons = [] }) { this.reportFailure(type, reasons); }, @@ -205,6 +210,12 @@ export default { }, updateOnCommit({ type }) { this.reportSuccess(type); + + if (this.isNewCiConfigFile) { + this.$apollo + .getClient() + .writeQuery({ query: getIsNewCiConfigFile, data: { isNewCiConfigFile: false } }); + } // 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; @@ -214,18 +225,22 @@ export default { </script> <template> - <div class="gl-mt-4"> - <gl-alert v-if="showSuccessAlert" :variant="success.variant" @dismiss="dismissSuccess"> - {{ success.text }} - </gl-alert> - <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> - </ul> - </gl-alert> + <div class="gl-mt-4 gl-relative"> <gl-loading-icon v-if="isBlobContentLoading" size="lg" class="gl-m-3" /> - <div v-else-if="!isBlobContentError" class="gl-mt-4"> + <pipeline-editor-empty-state + v-else-if="showStartScreen" + @createEmptyConfigFile="setNewEmptyCiConfigFile" + /> + <div v-else> + <gl-alert v-if="showSuccessAlert" :variant="success.variant" @dismiss="dismissSuccess"> + {{ success.text }} + </gl-alert> + <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> + </ul> + </gl-alert> <pipeline-editor-home :is-ci-config-data-loading="isCiConfigDataLoading" :ci-config-data="ciConfigData" @@ -235,7 +250,7 @@ export default { @showError="showErrorAlert" @updateCiConfig="updateCiConfig" /> + <confirm-unsaved-changes-dialog :has-unsaved-changes="hasUnsavedChanges" /> </div> - <confirm-unsaved-changes-dialog :has-unsaved-changes="hasUnsavedChanges" /> </div> </template> diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue index 8c9aad6ed87..ef46040153f 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue @@ -45,6 +45,7 @@ export default { <template> <div> <pipeline-editor-header + :ci-file-content="ciFileContent" :ci-config-data="ciConfigData" :is-ci-config-data-loading="isCiConfigDataLoading" /> |