diff options
Diffstat (limited to 'app/assets/javascripts/pipeline_editor/components')
12 files changed, 330 insertions, 44 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 f1fe8cf10fd..905a5f2d271 100644 --- a/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue +++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue @@ -36,6 +36,11 @@ export default { required: false, default: false, }, + scrollToCommitForm: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -52,6 +57,13 @@ export default { return !(this.message && this.targetBranch); }, }, + watch: { + scrollToCommitForm(flag) { + if (flag) { + this.scrollIntoView(); + } + }, + }, methods: { onSubmit() { this.$emit('submit', { @@ -63,6 +75,10 @@ export default { onReset() { this.$emit('cancel'); }, + scrollIntoView() { + this.$el.scrollIntoView({ behavior: 'smooth' }); + this.$emit('scrolled-to-commit-form'); + }, }, i18n: { commitMessage: __('Commit message'), 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 0308cd9c565..14c11099756 100644 --- a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue +++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue @@ -10,9 +10,8 @@ import { import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql'; import updateCurrentBranchMutation from '../../graphql/mutations/update_current_branch.mutation.graphql'; import updateLastCommitBranchMutation from '../../graphql/mutations/update_last_commit_branch.mutation.graphql'; +import updatePipelineEtag from '../../graphql/mutations/update_pipeline_etag.mutation.graphql'; import getCurrentBranch from '../../graphql/queries/client/current_branch.graphql'; -import getIsNewCiConfigFile from '../../graphql/queries/client/is_new_ci_config_file.graphql'; -import getPipelineEtag from '../../graphql/queries/client/pipeline_etag.graphql'; import CommitForm from './commit_form.vue'; @@ -41,18 +40,24 @@ export default { required: false, default: '', }, + isNewCiConfigFile: { + type: Boolean, + required: false, + default: false, + }, + scrollToCommitForm: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { commit: {}, - isNewCiConfigFile: false, isSaving: false, }; }, apollo: { - isNewCiConfigFile: { - query: getIsNewCiConfigFile, - }, currentBranch: { query: getCurrentBranch, }, @@ -96,10 +101,10 @@ export default { content: this.ciFileContent, lastCommitId: this.commitSha, }, - update(store, { data }) { + update(_, { data }) { const pipelineEtag = data?.commitCreate?.commit?.commitPipelinePath; if (pipelineEtag) { - store.writeQuery({ query: getPipelineEtag, data: { pipelineEtag } }); + this.$apollo.mutate({ mutation: updatePipelineEtag, variables: pipelineEtag }); } }, }); @@ -146,6 +151,8 @@ export default { :current-branch="currentBranch" :default-message="defaultCommitMessage" :is-saving="isSaving" + :scroll-to-commit-form="scrollToCommitForm" + v-on="$listeners" @cancel="onCommitCancel" @submit="onCommitSubmit" /> diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue b/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue index ff1e0b6388f..d7594fb318a 100644 --- a/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue +++ b/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue @@ -2,6 +2,7 @@ import { GlButton, GlIcon } from '@gitlab/ui'; import { __ } from '~/locale'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; +import { experiment } from '~/experimentation/utils'; import { DRAWER_EXPANDED_KEY } from '../../constants'; import FirstPipelineCard from './cards/first_pipeline_card.vue'; import GettingStartedCard from './cards/getting_started_card.vue'; @@ -53,12 +54,23 @@ export default { }, methods: { setInitialExpandState() { + let isExpanded; + + experiment('pipeline_editor_walkthrough', { + control: () => { + isExpanded = true; + }, + candidate: () => { + isExpanded = false; + }, + }); + // We check in the local storage and if no value is defined, we want the default // to be true. We want to explicitly set it to true here so that the drawer // animates to open on load. const localValue = localStorage.getItem(this.$options.localDrawerKey); if (localValue === null) { - this.isExpanded = true; + this.isExpanded = isExpanded; } }, setTopPosition() { diff --git a/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue b/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue index 68065cc3c73..baf1d17b233 100644 --- a/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue +++ b/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue @@ -12,7 +12,7 @@ import { produce } from 'immer'; import { fetchPolicies } from '~/lib/graphql'; import { historyPushState } from '~/lib/utils/common_utils'; import { setUrlParams } from '~/lib/utils/url_utility'; -import { s__ } from '~/locale'; +import { __ } from '~/locale'; import { BRANCH_PAGINATION_LIMIT, BRANCH_SEARCH_DEBOUNCE, @@ -25,9 +25,9 @@ import getLastCommitBranchQuery from '~/pipeline_editor/graphql/queries/client/l export default { i18n: { - dropdownHeader: s__('Switch branch'), - title: s__('Branches'), - fetchError: s__('Unable to fetch branch list for this project.'), + dropdownHeader: __('Switch branch'), + title: __('Branches'), + fetchError: __('Unable to fetch branch list for this project.'), }, inputDebounce: BRANCH_SEARCH_DEBOUNCE, components: { @@ -43,14 +43,25 @@ export default { }, inject: ['projectFullPath', 'totalBranches'], props: { + hasUnsavedChanges: { + type: Boolean, + required: false, + default: false, + }, paginationLimit: { type: Number, required: false, default: BRANCH_PAGINATION_LIMIT, }, + shouldLoadNewBranch: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { + branchSelected: null, availableBranches: [], filteredBranches: [], isSearchingBranches: false, @@ -101,10 +112,17 @@ export default { isBranchesLoading() { return this.$apollo.queries.availableBranches.loading || this.isSearchingBranches; }, - showBranchSwitcher() { + enableBranchSwitcher() { return this.branches.length > 0 || this.searchTerm.length > 0; }, }, + watch: { + shouldLoadNewBranch(flag) { + if (flag) { + this.changeBranch(this.branchSelected); + } + }, + }, methods: { availableBranchesQueryVars(varsOverride = {}) { if (this.searchTerm.length > 0) { @@ -149,11 +167,7 @@ export default { }) .catch(this.showFetchError); }, - async selectBranch(newBranch) { - if (newBranch === this.currentBranch) { - return; - } - + async changeBranch(newBranch) { this.updateCurrentBranch(newBranch); const updatedPath = setUrlParams({ branch_name: newBranch }); historyPushState(updatedPath); @@ -164,6 +178,19 @@ export default { await this.$nextTick(); this.$emit('refetchContent'); }, + selectBranch(newBranch) { + if (newBranch !== this.currentBranch) { + // If there are unsaved changes, we want to show the user + // a modal to confirm what to do with these before changing + // branches. + if (this.hasUnsavedChanges) { + this.branchSelected = newBranch; + this.$emit('select-branch', newBranch); + } else { + this.changeBranch(newBranch); + } + } + }, async setSearchTerm(newSearchTerm) { this.pageCounter = 0; this.searchTerm = newSearchTerm.trim(); @@ -203,11 +230,11 @@ export default { <template> <gl-dropdown - v-if="showBranchSwitcher" v-gl-tooltip.hover :title="$options.i18n.dropdownHeader" :header-text="$options.i18n.dropdownHeader" :text="currentBranch" + :disabled="!enableBranchSwitcher" icon="branch" data-qa-selector="branch_selector_button" data-testid="branch-selector" diff --git a/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue b/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue index 551a0430fbf..83b074dd55c 100644 --- a/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue +++ b/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue @@ -5,10 +5,26 @@ export default { components: { BranchSwitcher, }, + props: { + hasUnsavedChanges: { + type: Boolean, + required: false, + default: false, + }, + shouldLoadNewBranch: { + type: Boolean, + required: false, + default: false, + }, + }, }; </script> <template> <div class="gl-mb-4"> - <branch-switcher v-on="$listeners" /> + <branch-switcher + :has-unsaved-changes="hasUnsavedChanges" + :should-load-new-branch="shouldLoadNewBranch" + v-on="$listeners" + /> </div> </template> 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 fcc31f087ff..ec6ee52b6b2 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 @@ -63,6 +63,7 @@ export default { v-if="showPipelineStatus" :commit-sha="commitSha" :class="$options.pipelineStatusClasses" + v-on="$listeners" /> <validation-segment :class="validationStyling" :ci-config="ciConfigData" /> </div> diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue index 75b1398a3c2..25a78aab933 100644 --- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue @@ -1,17 +1,52 @@ <script> +import { __ } from '~/locale'; import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; +import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql'; +import { PIPELINE_FAILURE } from '../../constants'; export default { + i18n: { + linkedPipelinesFetchError: __('Unable to fetch upstream and downstream pipelines.'), + }, components: { PipelineMiniGraph, + LinkedPipelinesMiniList: () => + import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'), }, + inject: ['projectFullPath'], props: { pipeline: { type: Object, required: true, }, }, + apollo: { + linkedPipelines: { + query: getLinkedPipelinesQuery, + variables() { + return { + fullPath: this.projectFullPath, + iid: this.pipeline.iid, + }; + }, + skip() { + return !this.pipeline.iid; + }, + update({ project }) { + return project?.pipeline; + }, + error() { + this.$emit('showError', { + type: PIPELINE_FAILURE, + reasons: [this.$options.i18n.linkedPipelinesFetchError], + }); + }, + }, + }, computed: { + downstreamPipelines() { + return this.linkedPipelines?.downstream?.nodes || []; + }, pipelinePath() { return this.pipeline.detailedStatus?.detailsPath || ''; }, @@ -38,12 +73,29 @@ export default { }; }); }, + showDownstreamPipelines() { + return this.downstreamPipelines.length > 0; + }, + upstreamPipeline() { + return this.linkedPipelines?.upstream; + }, }, }; </script> <template> <div v-if="pipelineStages.length > 0" class="stage-cell gl-mr-5"> + <linked-pipelines-mini-list + v-if="upstreamPipeline" + :triggered-by="[upstreamPipeline]" + data-testid="pipeline-editor-mini-graph-upstream" + /> <pipeline-mini-graph class="gl-display-inline" :stages="pipelineStages" /> + <linked-pipelines-mini-list + v-if="showDownstreamPipelines" + :triggered="downstreamPipelines" + :pipeline-path="pipelinePath" + data-testid="pipeline-editor-mini-graph-downstream" + /> </div> </template> 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 a1fa2147994..6fe1459c80c 100644 --- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue @@ -1,5 +1,5 @@ <script> -import { GlButton, GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui'; +import { GlButton, GlIcon, GlLink, GlLoadingIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { truncateSha } from '~/lib/utils/text_utility'; import { s__ } from '~/locale'; @@ -10,7 +10,6 @@ import { toggleQueryPollingByVisibility, } from '~/pipelines/components/graph/utils'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import PipelineEditorMiniGraph from './pipeline_editor_mini_graph.vue'; const POLL_INTERVAL = 10000; @@ -21,6 +20,10 @@ export const i18n = { `Pipeline|Pipeline %{idStart}#%{idEnd} %{statusStart}%{statusEnd} for %{commitStart}%{commitEnd}`, ), viewBtn: s__('Pipeline|View pipeline'), + viewCommit: s__('Pipeline|View commit'), + pipelineNotTriggeredMsg: s__( + 'Pipeline|No pipeline was triggered for the latest changes due to the current CI/CD configuration.', + ), }; export default { @@ -34,7 +37,9 @@ export default { GlSprintf, PipelineEditorMiniGraph, }, - mixins: [glFeatureFlagMixin()], + directives: { + GlTooltip: GlTooltipDirective, + }, inject: ['projectFullPath'], props: { commitSha: { @@ -59,12 +64,13 @@ export default { }; }, update(data) { - const { id, commitPath = '', detailedStatus = {}, stages, status } = + const { id, iid, commit = {}, detailedStatus = {}, stages, status } = data.project?.pipeline || {}; return { id, - commitPath, + iid, + commit, detailedStatus, stages, status, @@ -73,20 +79,36 @@ export default { result(res) { if (res.data?.project?.pipeline) { this.hasError = false; + } else { + this.hasError = true; + this.pipelineNotTriggered = true; } }, error() { this.hasError = true; + this.networkError = true; }, pollInterval: POLL_INTERVAL, }, }, data() { return { + networkError: false, + pipelineNotTriggered: false, hasError: false, }; }, computed: { + commitText() { + const shortSha = truncateSha(this.commitSha); + const commitTitle = this.pipeline.commit.title || ''; + + if (commitTitle.length > 0) { + return `${shortSha}: ${commitTitle}`; + } + + return shortSha; + }, hasPipelineData() { return Boolean(this.pipeline?.id); }, @@ -126,13 +148,19 @@ export default { </div> </template> <template v-else-if="hasError"> - <div> + <div v-if="networkError"> <gl-icon class="gl-mr-auto" name="warning-solid" /> <span data-testid="pipeline-error-msg">{{ $options.i18n.fetchError }}</span> </div> + <div v-else> + <gl-icon class="gl-mr-auto" name="information-o" /> + <span data-testid="pipeline-not-triggered-error-msg"> + {{ $options.i18n.pipelineNotTriggeredMsg }} + </span> + </div> </template> <template v-else> - <div> + <div class="gl-text-truncate gl-md-max-w-50p gl-mr-1"> <a :href="status.detailsPath" class="gl-mr-auto"> <ci-icon :status="status" :size="16" data-testid="pipeline-status-icon" /> </a> @@ -144,25 +172,21 @@ export default { <template #status>{{ status.text }}</template> <template #commit> <gl-link - :href="pipeline.commitPath" - class="commit-sha gl-font-weight-normal" - target="_blank" + v-gl-tooltip.hover + :href="pipeline.commit.webPath" + :title="$options.i18n.viewCommit" data-testid="pipeline-commit" > - {{ shortSha }} + {{ commitText }} </gl-link> </template> </gl-sprintf> </span> </div> <div class="gl-display-flex gl-flex-wrap"> - <pipeline-editor-mini-graph - v-if="glFeatures.pipelineEditorMiniGraph" - :pipeline="pipeline" - /> + <pipeline-editor-mini-graph :pipeline="pipeline" v-on="$listeners" /> <gl-button class="gl-mt-2 gl-md-mt-0" - target="_blank" category="secondary" variant="confirm" :href="status.detailsPath" 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 8bffd893473..611b78b3c5e 100644 --- a/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue @@ -75,7 +75,7 @@ export default { return this.$options.i18n.valid; default: // Only display first error as a reason - return this.ciConfig?.errors.length > 0 + return this.ciConfig?.errors?.length > 0 ? sprintf(this.$options.i18n.invalidWithReason, { reason }, false) : this.$options.i18n.invalid; } 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 f7c9f10ea46..0cd0d17d944 100644 --- a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue +++ b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue @@ -3,15 +3,18 @@ import { GlAlert, GlLoadingIcon, GlTabs } 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 { getParameterValues, setUrlParams, updateHistory } from '~/lib/utils/url_utility'; +import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue'; import { CREATE_TAB, EDITOR_APP_STATUS_EMPTY, - EDITOR_APP_STATUS_ERROR, EDITOR_APP_STATUS_INVALID, EDITOR_APP_STATUS_LOADING, EDITOR_APP_STATUS_VALID, LINT_TAB, MERGED_TAB, + TAB_QUERY_PARAM, + TABS_INDEX, VISUALIZE_TAB, } from '../constants'; import getAppStatus from '../graphql/queries/client/app_status.graphql'; @@ -20,6 +23,7 @@ import CiEditorHeader from './editor/ci_editor_header.vue'; import TextEditor from './editor/text_editor.vue'; import CiLint from './lint/ci_lint.vue'; import EditorTab from './ui/editor_tab.vue'; +import WalkthroughPopover from './walkthrough_popover.vue'; export default { i18n: { @@ -42,6 +46,9 @@ export default { errorTexts: { loadMergedYaml: s__('Pipelines|Could not load merged YAML content'), }, + query: { + TAB_QUERY_PARAM, + }, tabConstants: { CREATE_TAB, LINT_TAB, @@ -58,6 +65,8 @@ export default { GlTabs, PipelineGraph, TextEditor, + GitlabExperiment, + WalkthroughPopover, }, mixins: [glFeatureFlagsMixin()], props: { @@ -74,6 +83,10 @@ export default { required: false, default: '', }, + isNewCiConfigFile: { + type: Boolean, + required: true, + }, }, apollo: { appStatus: { @@ -81,9 +94,8 @@ export default { }, }, computed: { - hasAppError() { - // Not an invalid config and with `mergedYaml` data missing - return this.appStatus === EDITOR_APP_STATUS_ERROR; + isMergedYamlAvailable() { + return this.ciConfigData?.mergedYaml; }, isEmpty() { return this.appStatus === EDITOR_APP_STATUS_EMPTY; @@ -98,22 +110,51 @@ export default { return this.appStatus === EDITOR_APP_STATUS_LOADING; }, }, + created() { + const [tabQueryParam] = getParameterValues(TAB_QUERY_PARAM); + + if (tabQueryParam && TABS_INDEX[tabQueryParam]) { + this.setDefaultTab(tabQueryParam); + } + }, methods: { setCurrentTab(tabName) { this.$emit('set-current-tab', tabName); }, + setDefaultTab(tabName) { + // We associate tab name with the index so that we can use tab name + // in other part of the app and load the corresponding tab closer to the + // actual component using a hash that binds the name to the indexes. + // This also means that if we ever changed tab order, we would justs need to + // update `TABS_INDEX` hash instead of all the instances in the app + // where we used the individual indexes + const newUrl = setUrlParams({ [TAB_QUERY_PARAM]: TABS_INDEX[tabName] }); + + this.setCurrentTab(tabName); + updateHistory({ url: newUrl, title: document.title, replace: true }); + }, }, }; </script> <template> - <gl-tabs class="file-editor gl-mb-3"> + <gl-tabs + class="file-editor gl-mb-3" + :query-param-name="$options.query.TAB_QUERY_PARAM" + sync-active-tab-with-query-params + > <editor-tab class="gl-mb-3" + title-link-class="js-walkthrough-popover-target" :title="$options.i18n.tabEdit" lazy data-testid="editor-tab" @click="setCurrentTab($options.tabConstants.CREATE_TAB)" > + <gitlab-experiment name="pipeline_editor_walkthrough"> + <template #candidate> + <walkthrough-popover v-if="isNewCiConfigFile" v-on="$listeners" /> + </template> + </gitlab-experiment> <ci-editor-header /> <text-editor :commit-sha="commitSha" :value="ciFileContent" v-on="$listeners" /> </editor-tab> @@ -154,7 +195,7 @@ export default { @click="setCurrentTab($options.tabConstants.MERGED_TAB)" > <gl-loading-icon v-if="isLoading" size="lg" class="gl-m-3" /> - <gl-alert v-else-if="hasAppError" variant="danger" :dismissible="false"> + <gl-alert v-else-if="!isMergedYamlAvailable" variant="danger" :dismissible="false"> {{ $options.errorTexts.loadMergedYaml }} </gl-alert> <ci-config-merged-preview v-else :ci-config-data="ciConfigData" v-on="$listeners" /> diff --git a/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_messages.vue b/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_messages.vue index 091b202e10b..7206f19d060 100644 --- a/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_messages.vue +++ b/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_messages.vue @@ -8,6 +8,7 @@ import { DEFAULT_FAILURE, DEFAULT_SUCCESS, LOAD_FAILURE_UNKNOWN, + PIPELINE_FAILURE, } from '../../constants'; import CodeSnippetAlert from '../code_snippet_alert/code_snippet_alert.vue'; import { @@ -24,6 +25,7 @@ export default { [COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'), [DEFAULT_FAILURE]: __('Something went wrong on our end.'), [LOAD_FAILURE_UNKNOWN]: s__('Pipelines|The CI configuration was not loaded, please try again.'), + [PIPELINE_FAILURE]: s__('Pipelines|There was a problem with loading the pipeline data.'), }, successTexts: { [COMMIT_SUCCESS]: __('Your changes have been successfully committed.'), @@ -74,6 +76,11 @@ export default { text: this.$options.errorTexts[COMMIT_FAILURE], variant: 'danger', }; + case PIPELINE_FAILURE: + return { + text: this.$options.errorTexts[PIPELINE_FAILURE], + variant: 'danger', + }; default: return { text: this.$options.errorTexts[DEFAULT_FAILURE], diff --git a/app/assets/javascripts/pipeline_editor/components/walkthrough_popover.vue b/app/assets/javascripts/pipeline_editor/components/walkthrough_popover.vue new file mode 100644 index 00000000000..5742b11b841 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/walkthrough_popover.vue @@ -0,0 +1,83 @@ +<script> +import { GlButton, GlPopover, GlSprintf, GlOutsideDirective as Outside } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + directives: { Outside }, + i18n: { + title: s__('pipelineEditorWalkthrough|See how GitLab pipelines work'), + description: s__( + 'pipelineEditorWalkthrough|This %{codeStart}.gitlab-ci.yml%{codeEnd} file creates a simple test pipeline.', + ), + instruction: s__( + 'pipelineEditorWalkthrough|Use the %{boldStart}commit changes%{boldEnd} button at the bottom of the page to run the pipeline.', + ), + ctaText: s__("pipelineEditorWalkthrough|Let's do this!"), + }, + components: { + GlButton, + GlPopover, + GlSprintf, + }, + data() { + return { + show: true, + }; + }, + computed: { + targetElement() { + return document.querySelector('.js-walkthrough-popover-target'); + }, + }, + methods: { + close() { + this.show = false; + }, + handleClickCta() { + this.close(); + this.$emit('walkthrough-popover-cta-clicked'); + }, + }, +}; +</script> + +<template> + <gl-popover + :show.sync="show" + :title="$options.i18n.title" + :target="targetElement" + placement="right" + triggers="focus" + > + <div v-outside="close" class="gl-display-flex gl-flex-direction-column"> + <p> + <gl-sprintf :message="$options.i18n.description"> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + + <p> + <gl-sprintf :message="$options.i18n.instruction"> + <template #bold="{ content }"> + <strong> + {{ content }} + </strong> + </template> + </gl-sprintf> + </p> + + <gl-button + class="gl-align-self-end" + category="tertiary" + data-testid="ctaBtn" + variant="confirm" + @click="handleClickCta" + > + <gl-emoji data-name="rocket" /> + {{ this.$options.i18n.ctaText }} + </gl-button> + </div> + </gl-popover> +</template> |