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:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-11-18 16:16:36 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-11-18 16:16:36 +0300
commit311b0269b4eb9839fa63f80c8d7a58f32b8138a0 (patch)
tree07e7870bca8aed6d61fdcc810731c50d2c40af47 /app/assets/javascripts/pipeline_editor
parent27909cef6c4170ed9205afa7426b8d3de47cbb0c (diff)
Add latest changes from gitlab-org/gitlab@14-5-stable-eev14.5.0-rc42
Diffstat (limited to 'app/assets/javascripts/pipeline_editor')
-rw-r--r--app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue16
-rw-r--r--app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue23
-rw-r--r--app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue14
-rw-r--r--app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue49
-rw-r--r--app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue18
-rw-r--r--app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_header.vue1
-rw-r--r--app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue52
-rw-r--r--app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue56
-rw-r--r--app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue2
-rw-r--r--app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue53
-rw-r--r--app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_messages.vue7
-rw-r--r--app/assets/javascripts/pipeline_editor/components/walkthrough_popover.vue83
-rw-r--r--app/assets/javascripts/pipeline_editor/constants.js17
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/mutations/update_app_status.mutation.graphql3
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/mutations/update_pipeline_etag.mutation.graphql3
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/client/is_new_ci_config_file.graphql3
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql5
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/resolvers.js23
-rw-r--r--app/assets/javascripts/pipeline_editor/index.js11
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue51
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue70
21 files changed, 475 insertions, 85 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>
diff --git a/app/assets/javascripts/pipeline_editor/constants.js b/app/assets/javascripts/pipeline_editor/constants.js
index bb03fa126a5..a2eaeeef286 100644
--- a/app/assets/javascripts/pipeline_editor/constants.js
+++ b/app/assets/javascripts/pipeline_editor/constants.js
@@ -5,24 +5,37 @@ export const CI_CONFIG_STATUS_VALID = 'VALID';
// Values for EDITOR_APP_STATUS_* are frontend specifics and
// represent the global state of the pipeline editor app.
export const EDITOR_APP_STATUS_EMPTY = 'EMPTY';
-export const EDITOR_APP_STATUS_ERROR = 'ERROR';
export const EDITOR_APP_STATUS_INVALID = CI_CONFIG_STATUS_INVALID;
export const EDITOR_APP_STATUS_LOADING = 'LOADING';
export const EDITOR_APP_STATUS_VALID = CI_CONFIG_STATUS_VALID;
+export const EDITOR_APP_VALID_STATUSES = [
+ EDITOR_APP_STATUS_EMPTY,
+ EDITOR_APP_STATUS_INVALID,
+ EDITOR_APP_STATUS_LOADING,
+ EDITOR_APP_STATUS_VALID,
+];
+
export const COMMIT_FAILURE = 'COMMIT_FAILURE';
export const COMMIT_SUCCESS = 'COMMIT_SUCCESS';
export const DEFAULT_FAILURE = 'DEFAULT_FAILURE';
export const DEFAULT_SUCCESS = 'DEFAULT_SUCCESS';
export const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN';
+export const PIPELINE_FAILURE = 'PIPELINE_FAILURE';
export const CREATE_TAB = 'CREATE_TAB';
export const LINT_TAB = 'LINT_TAB';
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 TABS_INDEX = {
+ [CREATE_TAB]: '0',
+ [VISUALIZE_TAB]: '1',
+ [LINT_TAB]: '2',
+ [MERGED_TAB]: '3',
+};
+export const TAB_QUERY_PARAM = 'tab';
export const COMMIT_ACTION_CREATE = 'CREATE';
export const COMMIT_ACTION_UPDATE = 'UPDATE';
diff --git a/app/assets/javascripts/pipeline_editor/graphql/mutations/update_app_status.mutation.graphql b/app/assets/javascripts/pipeline_editor/graphql/mutations/update_app_status.mutation.graphql
new file mode 100644
index 00000000000..7487e328668
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/graphql/mutations/update_app_status.mutation.graphql
@@ -0,0 +1,3 @@
+mutation updateAppStatus($appStatus: String) {
+ updateAppStatus(appStatus: $appStatus) @client
+}
diff --git a/app/assets/javascripts/pipeline_editor/graphql/mutations/update_pipeline_etag.mutation.graphql b/app/assets/javascripts/pipeline_editor/graphql/mutations/update_pipeline_etag.mutation.graphql
new file mode 100644
index 00000000000..9025f00b343
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/graphql/mutations/update_pipeline_etag.mutation.graphql
@@ -0,0 +1,3 @@
+mutation updatePipelineEtag($pipelineEtag: String) {
+ updatePipelineEtag(pipelineEtag: $pipelineEtag) @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
deleted file mode 100644
index 8c2ca276f50..00000000000
--- a/app/assets/javascripts/pipeline_editor/graphql/queries/client/is_new_ci_config_file.graphql
+++ /dev/null
@@ -1,3 +0,0 @@
-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
index 0c3653a2880..34e98ae3eb3 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql
@@ -1,10 +1,13 @@
query getPipeline($fullPath: ID!, $sha: String!) {
project(fullPath: $fullPath) {
pipeline(sha: $sha) {
- commitPath
id
iid
status
+ commit {
+ title
+ webPath
+ }
detailedStatus {
detailsPath
icon
diff --git a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
index a34652b1495..e4965e00af3 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
+++ b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
@@ -1,7 +1,8 @@
-import produce from 'immer';
import axios from '~/lib/utils/axios_utils';
+import getAppStatus from './queries/client/app_status.graphql';
import getCurrentBranchQuery from './queries/client/current_branch.graphql';
import getLastCommitBranchQuery from './queries/client/last_commit_branch.query.graphql';
+import getPipelineEtag from './queries/client/pipeline_etag.graphql';
export const resolvers = {
Mutation: {
@@ -31,20 +32,28 @@ export const resolvers = {
__typename: 'CiLintContent',
}));
},
+ updateAppStatus: (_, { appStatus }, { cache }) => {
+ cache.writeQuery({
+ query: getAppStatus,
+ data: { appStatus },
+ });
+ },
updateCurrentBranch: (_, { currentBranch }, { cache }) => {
cache.writeQuery({
query: getCurrentBranchQuery,
- data: produce(cache.readQuery({ query: getCurrentBranchQuery }), (draftData) => {
- draftData.currentBranch = currentBranch;
- }),
+ data: { currentBranch },
});
},
updateLastCommitBranch: (_, { lastCommitBranch }, { cache }) => {
cache.writeQuery({
query: getLastCommitBranchQuery,
- data: produce(cache.readQuery({ query: getLastCommitBranchQuery }), (draftData) => {
- draftData.lastCommitBranch = lastCommitBranch;
- }),
+ data: { lastCommitBranch },
+ });
+ },
+ updatePipelineEtag: (_, { pipelineEtag }, { cache }) => {
+ cache.writeQuery({
+ query: getPipelineEtag,
+ data: { pipelineEtag },
});
},
},
diff --git a/app/assets/javascripts/pipeline_editor/index.js b/app/assets/javascripts/pipeline_editor/index.js
index 89b9091e6f9..4f7f2743aca 100644
--- a/app/assets/javascripts/pipeline_editor/index.js
+++ b/app/assets/javascripts/pipeline_editor/index.js
@@ -3,8 +3,10 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { resetServiceWorkersPublicPath } from '../lib/utils/webpack';
+import { EDITOR_APP_STATUS_LOADING } from './constants';
import { CODE_SNIPPET_SOURCE_SETTINGS } from './components/code_snippet_alert/constants';
import getCurrentBranch from './graphql/queries/client/current_branch.graphql';
+import getAppStatus from './graphql/queries/client/app_status.graphql';
import getLastCommitBranchQuery from './graphql/queries/client/last_commit_branch.query.graphql';
import getPipelineEtag from './graphql/queries/client/pipeline_etag.graphql';
import { resolvers } from './graphql/resolvers';
@@ -59,12 +61,18 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
defaultClient: createDefaultClient(resolvers, {
typeDefs,
useGet: true,
- assumeImmutableResults: true,
}),
});
const { cache } = apolloProvider.clients.defaultClient;
cache.writeQuery({
+ query: getAppStatus,
+ data: {
+ appStatus: EDITOR_APP_STATUS_LOADING,
+ },
+ });
+
+ cache.writeQuery({
query: getCurrentBranch,
data: {
currentBranch: initialBranchName || defaultBranch,
@@ -93,6 +101,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
ciExamplesHelpPagePath,
ciHelpPagePath,
configurationPaths,
+ dataMethod: 'graphql',
defaultBranch,
emptyStateIllustrationPath,
helpPaths,
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index e70417145ab..68db5d8078f 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -12,16 +12,16 @@ import PipelineEditorMessages from './components/ui/pipeline_editor_messages.vue
import {
COMMIT_SHA_POLL_INTERVAL,
EDITOR_APP_STATUS_EMPTY,
- EDITOR_APP_STATUS_ERROR,
+ EDITOR_APP_VALID_STATUSES,
EDITOR_APP_STATUS_LOADING,
LOAD_FAILURE_UNKNOWN,
STARTER_TEMPLATE_NAME,
} from './constants';
+import updateAppStatus from './graphql/mutations/update_app_status.mutation.graphql';
import getBlobContent from './graphql/queries/blob_content.graphql';
import getCiConfigData from './graphql/queries/ci_config.graphql';
import getAppStatus from './graphql/queries/client/app_status.graphql';
import getCurrentBranch from './graphql/queries/client/current_branch.graphql';
-import getIsNewCiConfigFile from './graphql/queries/client/is_new_ci_config_file.graphql';
import getTemplate from './graphql/queries/get_starter_template.query.graphql';
import getLatestCommitShaQuery from './graphql/queries/latest_commit_sha.query.graphql';
import PipelineEditorHome from './pipeline_editor_home.vue';
@@ -44,23 +44,23 @@ export default {
},
data() {
return {
- starterTemplateName: STARTER_TEMPLATE_NAME,
ciConfigData: {},
+ currentCiFileContent: '',
failureType: null,
failureReasons: [],
initialCiFileContent: '',
isFetchingCommitSha: false,
isNewCiConfigFile: false,
lastCommittedContent: '',
- currentCiFileContent: '',
- successType: null,
+ shouldSkipStartScreen: false,
+ showFailure: false,
showStartScreen: false,
showSuccess: false,
- showFailure: false,
starterTemplate: '',
+ starterTemplateName: STARTER_TEMPLATE_NAME,
+ successType: null,
};
},
-
apollo: {
initialCiFileContent: {
fetchPolicy: fetchPolicies.NETWORK_ONLY,
@@ -103,7 +103,11 @@ export default {
}
if (!hasCIFile) {
- this.showStartScreen = true;
+ if (this.shouldSkipStartScreen) {
+ this.setNewEmptyCiConfigFile();
+ } else {
+ this.showStartScreen = true;
+ }
} else if (fileContent.length) {
// If the file content is > 0, then we make sure to reset the
// start screen flag during a refetch
@@ -141,10 +145,10 @@ export default {
return { ...ciConfig, stages };
},
result({ data }) {
- this.setAppStatus(data?.ciConfig?.status || EDITOR_APP_STATUS_ERROR);
+ this.setAppStatus(data?.ciConfig?.status);
},
- error() {
- this.reportFailure(LOAD_FAILURE_UNKNOWN);
+ error(err) {
+ this.reportFailure(LOAD_FAILURE_UNKNOWN, [String(err)]);
},
watchLoading(isLoading) {
if (isLoading) {
@@ -179,9 +183,6 @@ export default {
currentBranch: {
query: getCurrentBranch,
},
- isNewCiConfigFile: {
- query: getIsNewCiConfigFile,
- },
starterTemplate: {
query: getTemplate,
variables() {
@@ -232,6 +233,7 @@ export default {
},
mounted() {
this.loadTemplateFromURL();
+ this.checkShouldSkipStartScreen();
},
methods: {
hideFailure() {
@@ -245,8 +247,6 @@ export default {
await this.$apollo.queries.initialCiFileContent.refetch();
},
reportFailure(type, reasons = []) {
- this.setAppStatus(EDITOR_APP_STATUS_ERROR);
-
window.scrollTo({ top: 0, behavior: 'smooth' });
this.showFailure = true;
this.failureType = type;
@@ -261,12 +261,12 @@ export default {
this.currentCiFileContent = this.lastCommittedContent;
},
setAppStatus(appStatus) {
- this.$apollo.getClient().writeQuery({ query: getAppStatus, data: { appStatus } });
+ if (EDITOR_APP_VALID_STATUSES.includes(appStatus)) {
+ this.$apollo.mutate({ mutation: updateAppStatus, variables: { appStatus } });
+ }
},
setNewEmptyCiConfigFile() {
- this.$apollo
- .getClient()
- .writeQuery({ query: getIsNewCiConfigFile, data: { isNewCiConfigFile: true } });
+ this.isNewCiConfigFile = true;
this.showStartScreen = false;
},
showErrorAlert({ type, reasons = [] }) {
@@ -283,9 +283,7 @@ export default {
this.reportSuccess(type);
if (this.isNewCiConfigFile) {
- this.$apollo
- .getClient()
- .writeQuery({ query: getIsNewCiConfigFile, data: { isNewCiConfigFile: false } });
+ this.isNewCiConfigFile = false;
}
// Keep track of the latest committed content to know
@@ -300,6 +298,10 @@ export default {
this.setNewEmptyCiConfigFile();
}
},
+ checkShouldSkipStartScreen() {
+ const params = queryToObject(window.location.search);
+ this.shouldSkipStartScreen = Boolean(params?.add_new_config_file);
+ },
},
};
</script>
@@ -325,8 +327,9 @@ export default {
<pipeline-editor-home
:ci-config-data="ciConfigData"
:ci-file-content="currentCiFileContent"
- :is-new-ci-config-file="isNewCiConfigFile"
:commit-sha="commitSha"
+ :has-unsaved-changes="hasUnsavedChanges"
+ :is-new-ci-config-file="isNewCiConfigFile"
@commit="updateOnCommit"
@resetContent="resetContent"
@showError="showErrorAlert"
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
index ba567023946..8e8f31a4acc 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
@@ -1,14 +1,31 @@
<script>
+import { GlModal } from '@gitlab/ui';
+import { __ } from '~/locale';
import CommitSection from './components/commit/commit_section.vue';
import PipelineEditorDrawer from './components/drawer/pipeline_editor_drawer.vue';
import PipelineEditorFileNav from './components/file_nav/pipeline_editor_file_nav.vue';
import PipelineEditorHeader from './components/header/pipeline_editor_header.vue';
import PipelineEditorTabs from './components/pipeline_editor_tabs.vue';
-import { TABS_WITH_COMMIT_FORM, CREATE_TAB } from './constants';
+import { CREATE_TAB } from './constants';
export default {
+ commitSectionRef: 'commitSectionRef',
+ modal: {
+ switchBranch: {
+ title: __('You have unsaved changes'),
+ body: __('Uncommitted changes will be lost if you change branches. Do you want to continue?'),
+ actionPrimary: {
+ text: __('Switch Branches'),
+ },
+ actionSecondary: {
+ text: __('Cancel'),
+ attributes: { variant: 'default' },
+ },
+ },
+ },
components: {
CommitSection,
+ GlModal,
PipelineEditorDrawer,
PipelineEditorFileNav,
PipelineEditorHeader,
@@ -28,6 +45,11 @@ export default {
required: false,
default: '',
},
+ hasUnsavedChanges: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
isNewCiConfigFile: {
type: Boolean,
required: true,
@@ -36,40 +58,82 @@ export default {
data() {
return {
currentTab: CREATE_TAB,
+ scrollToCommitForm: false,
+ shouldLoadNewBranch: false,
+ showSwitchBranchModal: false,
};
},
computed: {
showCommitForm() {
- return TABS_WITH_COMMIT_FORM.includes(this.currentTab);
+ return this.currentTab === CREATE_TAB;
},
},
methods: {
+ closeBranchModal() {
+ this.showSwitchBranchModal = false;
+ },
+ handleConfirmSwitchBranch() {
+ this.showSwitchBranchModal = true;
+ },
+ switchBranch() {
+ this.showSwitchBranchModal = false;
+ this.shouldLoadNewBranch = true;
+ },
setCurrentTab(tabName) {
this.currentTab = tabName;
},
+ setScrollToCommitForm(newValue = true) {
+ this.scrollToCommitForm = newValue;
+ },
},
};
</script>
<template>
<div class="gl-pr-9 gl-transition-medium gl-w-full">
- <pipeline-editor-file-nav v-on="$listeners" />
+ <gl-modal
+ v-if="showSwitchBranchModal"
+ visible
+ modal-id="switchBranchModal"
+ :title="$options.modal.switchBranch.title"
+ :action-primary="$options.modal.switchBranch.actionPrimary"
+ :action-secondary="$options.modal.switchBranch.actionSecondary"
+ @primary="switchBranch"
+ @secondary="closeBranchModal"
+ @cancel="closeBranchModal"
+ @hide="closeBranchModal"
+ >
+ {{ $options.modal.switchBranch.body }}
+ </gl-modal>
+ <pipeline-editor-file-nav
+ :has-unsaved-changes="hasUnsavedChanges"
+ :should-load-new-branch="shouldLoadNewBranch"
+ @select-branch="handleConfirmSwitchBranch"
+ v-on="$listeners"
+ />
<pipeline-editor-header
:ci-config-data="ciConfigData"
:commit-sha="commitSha"
:is-new-ci-config-file="isNewCiConfigFile"
+ v-on="$listeners"
/>
<pipeline-editor-tabs
:ci-config-data="ciConfigData"
:ci-file-content="ciFileContent"
:commit-sha="commitSha"
+ :is-new-ci-config-file="isNewCiConfigFile"
v-on="$listeners"
@set-current-tab="setCurrentTab"
+ @walkthrough-popover-cta-clicked="setScrollToCommitForm"
/>
<commit-section
v-if="showCommitForm"
+ :ref="$options.commitSectionRef"
:ci-file-content="ciFileContent"
:commit-sha="commitSha"
+ :is-new-ci-config-file="isNewCiConfigFile"
+ :scroll-to-commit-form="scrollToCommitForm"
+ @scrolled-to-commit-form="setScrollToCommitForm(false)"
v-on="$listeners"
/>
<pipeline-editor-drawer />