diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
commit | 4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch) | |
tree | 5423a1c7516cffe36384133ade12572cf709398d /app/assets/javascripts/pipeline_editor | |
parent | e570267f2f6b326480d284e0164a6464ba4081bc (diff) |
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'app/assets/javascripts/pipeline_editor')
22 files changed, 795 insertions, 171 deletions
diff --git a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue index 14a4a9d5710..567164cb0ee 100644 --- a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue +++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue @@ -11,6 +11,7 @@ import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphq 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 getPipelineEtag from '../../graphql/queries/client/pipeline_etag.graphql'; import CommitForm from './commit_form.vue'; @@ -94,10 +95,15 @@ export default { }, update(store, { data }) { const commitSha = data?.commitCreate?.commit?.sha; + const pipelineEtag = data?.commitCreate?.commit?.commitPipelinePath; if (commitSha) { store.writeQuery({ query: getCommitSha, data: { commitSha } }); } + + if (pipelineEtag) { + store.writeQuery({ query: getPipelineEtag, data: { pipelineEtag } }); + } }, }); diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/cards/first_pipeline_card.vue b/app/assets/javascripts/pipeline_editor/components/drawer/cards/first_pipeline_card.vue new file mode 100644 index 00000000000..22c1563350d --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/cards/first_pipeline_card.vue @@ -0,0 +1,67 @@ +<script> +import { GlCard, GlLink, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import PipelineVisualReference from '../ui/pipeline_visual_reference.vue'; + +export default { + i18n: { + title: s__('PipelineEditorTutorial|🚀 Run your first pipeline'), + firstParagraph: s__( + 'PipelineEditorTutorial|A typical GitLab pipeline consists of three stages: build, test and deploy. Each stage can have one or more jobs.', + ), + secondParagraph: s__( + 'PipelineEditorTutorial|In the example below, %{codeStart}build%{codeEnd} and %{codeStart}deploy%{codeEnd} each contain one job, and %{codeStart}test%{codeEnd} contains two jobs. Your scripts run in jobs like these.', + ), + thirdParagraph: s__( + 'PipelineEditorTutorial|You can use %{linkStart}CI/CD examples and templates%{linkEnd} to get your first %{codeStart}.gitlab-ci.yml%{codeEnd} configuration file started. Your first pipeline runs when you commit the changes.', + ), + note: s__( + 'PipelineEditorTutorial|If you’re using a self-managed GitLab instance, %{linkStart}make sure your instance has runners available.%{linkEnd}', + ), + }, + components: { + GlCard, + GlLink, + GlSprintf, + PipelineVisualReference, + }, + inject: ['ciExamplesHelpPagePath', 'runnerHelpPagePath'], +}; +</script> +<template> + <gl-card> + <template #default> + <h4 class="gl-font-lg gl-mt-0">{{ $options.i18n.title }}</h4> + <p class="gl-mb-3">{{ $options.i18n.firstParagraph }}</p> + <p class="gl-mb-3"> + <gl-sprintf :message="$options.i18n.secondParagraph"> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + <pipeline-visual-reference /> + <p class="gl-my-3"> + <gl-sprintf :message="$options.i18n.thirdParagraph"> + <template #link="{ content }"> + <gl-link :href="ciExamplesHelpPagePath" target="_blank"> + {{ content }} + </gl-link> + </template> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + <p class="gl-mb-0"> + <gl-sprintf :message="$options.i18n.note"> + <template #link="{ content }"> + <gl-link :href="runnerHelpPagePath" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </p> + </template> + </gl-card> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/cards/getting_started_card.vue b/app/assets/javascripts/pipeline_editor/components/drawer/cards/getting_started_card.vue new file mode 100644 index 00000000000..3da535f5f94 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/cards/getting_started_card.vue @@ -0,0 +1,35 @@ +<script> +import { GlCard, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + i18n: { + title: s__('PipelineEditorTutorial|Get started with GitLab CI/CD'), + firstParagraph: s__( + 'PipelineEditorTutorial|GitLab CI/CD can automatically build, test, and deploy your application.', + ), + secondParagraph: s__( + 'PipelineEditorTutorial|The pipeline stages and jobs are defined in a %{codeStart}.gitlab-ci.yml%{codeEnd} file. You can edit, visualize and validate the syntax in this file by using the Pipeline Editor.', + ), + }, + components: { + GlCard, + GlSprintf, + }, +}; +</script> +<template> + <gl-card> + <template #default> + <h4 class="gl-font-lg gl-mt-0">{{ $options.i18n.title }}</h4> + <p class="gl-mb-3">{{ $options.i18n.firstParagraph }}</p> + <p class="gl-mb-0"> + <gl-sprintf :message="$options.i18n.secondParagraph"> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + </template> + </gl-card> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue b/app/assets/javascripts/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue new file mode 100644 index 00000000000..f714f6411f1 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue @@ -0,0 +1,75 @@ +<script> +import { GlCard, GlLink, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + i18n: { + title: s__('PipelineEditorTutorial|⚙️ Pipeline configuration reference'), + firstParagraph: s__('PipelineEditorTutorial|Resources to help with your CI/CD configuration:'), + browseExamples: s__( + 'PipelineEditorTutorial|Browse %{linkStart}CI/CD examples and templates%{linkEnd}', + ), + viewSyntaxRef: s__( + 'PipelineEditorTutorial|View %{linkStart}.gitlab-ci.yml syntax reference%{linkEnd}', + ), + learnMore: s__( + 'PipelineEditorTutorial|Learn more about %{linkStart}GitLab CI/CD concepts%{linkEnd}', + ), + needs: s__( + 'PipelineEditorTutorial|Make your pipeline more efficient with the %{linkStart}Needs keyword%{linkEnd}', + ), + }, + components: { + GlCard, + GlLink, + GlSprintf, + }, + inject: ['ciExamplesHelpPagePath', 'ciHelpPagePath', 'needsHelpPagePath', 'ymlHelpPagePath'], +}; +</script> +<template> + <gl-card> + <template #default> + <h4 class="gl-font-lg gl-mt-0">{{ $options.i18n.title }}</h4> + <p class="gl-mb-3">{{ $options.i18n.firstParagraph }}</p> + <ul> + <li> + <gl-sprintf :message="$options.i18n.browseExamples"> + <template #link="{ content }"> + <gl-link :href="ciExamplesHelpPagePath" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </li> + <li> + <gl-sprintf :message="$options.i18n.viewSyntaxRef"> + <template #link="{ content }"> + <gl-link :href="ymlHelpPagePath" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </li> + <li> + <gl-sprintf :message="$options.i18n.learnMore"> + <template #link="{ content }"> + <gl-link :href="ciHelpPagePath" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </li> + <li> + <gl-sprintf :message="$options.i18n.needs"> + <template #link="{ content }"> + <gl-link :href="needsHelpPagePath" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </li> + </ul> + </template> + </gl-card> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/cards/visualize_and_lint_card.vue b/app/assets/javascripts/pipeline_editor/components/drawer/cards/visualize_and_lint_card.vue new file mode 100644 index 00000000000..512414f0246 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/cards/visualize_and_lint_card.vue @@ -0,0 +1,24 @@ +<script> +import { GlCard } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + i18n: { + title: s__('PipelineEditorTutorial|💡 Tip: Visualize and validate your pipeline'), + firstParagraph: s__( + 'PipelineEditorTutorial|Use the Visualize and Lint tabs in the Pipeline Editor to visualize your pipeline and check for any errors or warnings before committing your changes.', + ), + }, + components: { + GlCard, + }, +}; +</script> +<template> + <gl-card> + <template #default> + <h4 class="gl-font-lg gl-mt-0">{{ $options.i18n.title }}</h4> + <p class="gl-mb-0">{{ $options.i18n.firstParagraph }}</p> + </template> + </gl-card> +</template> 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 new file mode 100644 index 00000000000..ff1e0b6388f --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue @@ -0,0 +1,105 @@ +<script> +import { GlButton, GlIcon } from '@gitlab/ui'; +import { __ } from '~/locale'; +import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; +import { DRAWER_EXPANDED_KEY } from '../../constants'; +import FirstPipelineCard from './cards/first_pipeline_card.vue'; +import GettingStartedCard from './cards/getting_started_card.vue'; +import PipelineConfigReferenceCard from './cards/pipeline_config_reference_card.vue'; +import VisualizeAndLintCard from './cards/visualize_and_lint_card.vue'; + +export default { + width: { + expanded: '482px', + collapsed: '58px', + }, + i18n: { + toggleTxt: __('Collapse'), + }, + localDrawerKey: DRAWER_EXPANDED_KEY, + components: { + FirstPipelineCard, + GettingStartedCard, + GlButton, + GlIcon, + LocalStorageSync, + PipelineConfigReferenceCard, + VisualizeAndLintCard, + }, + data() { + return { + isExpanded: false, + topPosition: 0, + }; + }, + computed: { + buttonIconName() { + return this.isExpanded ? 'chevron-double-lg-right' : 'chevron-double-lg-left'; + }, + buttonClass() { + return this.isExpanded ? 'gl-justify-content-end!' : ''; + }, + rootStyle() { + const { expanded, collapsed } = this.$options.width; + const top = this.topPosition; + const style = { top: `${top}px` }; + + return this.isExpanded ? { ...style, width: expanded } : { ...style, width: collapsed }; + }, + }, + mounted() { + this.setTopPosition(); + this.setInitialExpandState(); + }, + methods: { + setInitialExpandState() { + // 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; + } + }, + setTopPosition() { + const navbarEl = document.querySelector('.js-navbar'); + + if (navbarEl) { + this.topPosition = navbarEl.getBoundingClientRect().bottom; + } + }, + toggleDrawer() { + this.isExpanded = !this.isExpanded; + }, + }, +}; +</script> +<template> + <local-storage-sync v-model="isExpanded" :storage-key="$options.localDrawerKey" as-json> + <aside + aria-live="polite" + class="gl-fixed gl-right-0 gl-bg-gray-10 gl-shadow-drawer gl-transition-property-width gl-transition-duration-medium gl-border-l-solid gl-border-1 gl-border-gray-100 gl-h-full gl-z-index-3 gl-overflow-y-auto" + :style="rootStyle" + > + <gl-button + category="tertiary" + class="gl-w-full gl-h-9 gl-rounded-0! gl-border-none! gl-border-b-solid! gl-border-1! gl-border-gray-100 gl-text-decoration-none! gl-outline-0! gl-display-flex" + :class="buttonClass" + :title="__('Toggle sidebar')" + @click="toggleDrawer" + > + <span v-if="isExpanded" class="gl-text-gray-500 gl-mr-3" data-testid="collapse-text"> + {{ __('Collapse') }} + </span> + <gl-icon data-testid="toggle-icon" :name="buttonIconName" /> + </gl-button> + <div v-if="isExpanded" class="gl-h-full gl-p-5" data-testid="drawer-content"> + <getting-started-card class="gl-mb-4" /> + <first-pipeline-card class="gl-mb-4" /> + <visualize-and-lint-card class="gl-mb-4" /> + <pipeline-config-reference-card /> + <div class="gl-h-13"></div> + </div> + </aside> + </local-storage-sync> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/ui/demo_job_pill.vue b/app/assets/javascripts/pipeline_editor/components/drawer/ui/demo_job_pill.vue new file mode 100644 index 00000000000..049504181c4 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/ui/demo_job_pill.vue @@ -0,0 +1,17 @@ +<script> +export default { + props: { + jobName: { + type: String, + required: true, + }, + }, +}; +</script> +<template> + <div + class="gl-w-13 gl-h-6 gl-font-sm gl-bg-white gl-inset-border-1-blue-500 gl-text-center gl-text-truncate gl-rounded-pill gl-px-4 gl-py-2 gl-relative gl-z-index-1 gl-transition-duration-slow gl-transition-timing-function-ease" + > + {{ jobName }} + </div> +</template> diff --git a/app/assets/javascripts/pipeline_editor/components/drawer/ui/pipeline_visual_reference.vue b/app/assets/javascripts/pipeline_editor/components/drawer/ui/pipeline_visual_reference.vue new file mode 100644 index 00000000000..1017237365b --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/drawer/ui/pipeline_visual_reference.vue @@ -0,0 +1,43 @@ +<script> +import { s__ } from '~/locale'; +import DemoJobPill from './demo_job_pill.vue'; + +export default { + i18n: { + stageNames: { + build: s__('StageName|Build'), + test: s__('StageName|Test'), + deploy: s__('StageName|Deploy'), + }, + jobNames: { + build: s__('JobName|build-job'), + test_1: s__('JobName|unit-test'), + test_2: s__('JobName|lint-test'), + deploy: s__('JobName|deploy-app'), + }, + }, + stageClasses: + 'gl-bg-blue-50 gl-display-flex gl-flex-direction-column gl-align-items-center gl-p-4 gl-rounded-base', + titleClasses: 'gl-text-blue-600 gl-mb-4', + components: { + DemoJobPill, + }, +}; +</script> +<template> + <div class="gl-display-flex gl-justify-content-center"> + <div :class="$options.stageClasses" class="gl-mr-5"> + <div :class="$options.titleClasses">{{ $options.i18n.stageNames.build }}</div> + <demo-job-pill :job-name="$options.i18n.jobNames.build" /> + </div> + <div :class="$options.stageClasses" class="gl-mr-5"> + <div :class="$options.titleClasses">{{ $options.i18n.stageNames.test }}</div> + <demo-job-pill class="gl-mb-3" :job-name="$options.i18n.jobNames.test_1" /> + <demo-job-pill :job-name="$options.i18n.jobNames.test_2" /> + </div> + <div :class="$options.stageClasses"> + <div :class="$options.titleClasses">{{ $options.i18n.stageNames.deploy }}</div> + <demo-job-pill :job-name="$options.i18n.jobNames.deploy" /> + </div> + </div> +</template> 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 b3eba0fcc19..1acf3a03e73 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 @@ -1,32 +1,77 @@ <script> -import { GlDropdown, GlDropdownItem, GlDropdownSectionHeader, GlIcon } from '@gitlab/ui'; +import { + GlDropdown, + GlDropdownItem, + GlDropdownSectionHeader, + GlInfiniteScroll, + GlLoadingIcon, + GlSearchBoxByType, +} from '@gitlab/ui'; +import { historyPushState } from '~/lib/utils/common_utils'; +import { setUrlParams } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; -import { DEFAULT_FAILURE } from '~/pipeline_editor/constants'; +import { + BRANCH_PAGINATION_LIMIT, + BRANCH_SEARCH_DEBOUNCE, + DEFAULT_FAILURE, +} from '~/pipeline_editor/constants'; import getAvailableBranches from '~/pipeline_editor/graphql/queries/available_branches.graphql'; import getCurrentBranch from '~/pipeline_editor/graphql/queries/client/current_branch.graphql'; export default { i18n: { + dropdownHeader: s__('Switch Branch'), title: s__('Branches'), fetchError: s__('Unable to fetch branch list for this project.'), }, + inputDebounce: BRANCH_SEARCH_DEBOUNCE, components: { GlDropdown, GlDropdownItem, GlDropdownSectionHeader, - GlIcon, + GlInfiniteScroll, + GlLoadingIcon, + GlSearchBoxByType, + }, + inject: ['projectFullPath', 'totalBranches'], + props: { + paginationLimit: { + type: Number, + required: false, + default: BRANCH_PAGINATION_LIMIT, + }, + }, + data() { + return { + branches: [], + page: { + limit: this.paginationLimit, + offset: 0, + searchTerm: '', + }, + }; }, - inject: ['projectFullPath'], apollo: { - branches: { + availableBranches: { query: getAvailableBranches, variables() { return { + limit: this.page.limit, + offset: this.page.offset, projectFullPath: this.projectFullPath, + searchPattern: this.searchPattern, }; }, update(data) { - return data.project?.repository?.branches || []; + return data.project?.repository?.branchNames || []; + }, + result({ data }) { + const newBranches = data.project?.repository?.branchNames || []; + + // check that we're not re-concatenating existing fetch results + if (!this.branches.includes(newBranches[0])) { + this.branches = this.branches.concat(newBranches); + } }, error() { this.$emit('showError', { @@ -40,26 +85,99 @@ export default { }, }, computed: { - hasBranchList() { - return this.branches?.length > 0; + isBranchesLoading() { + return this.$apollo.queries.availableBranches.loading; + }, + showBranchSwitcher() { + return this.branches.length > 0 || this.page.searchTerm.length > 0; + }, + searchPattern() { + if (this.page.searchTerm === '') { + return '*'; + } + + return `*${this.page.searchTerm}*`; + }, + }, + methods: { + // if there is no searchPattern, paginate by {paginationLimit} branches + fetchNextBranches() { + if ( + this.isBranchesLoading || + this.page.searchTerm.length > 0 || + this.branches.length === this.totalBranches + ) { + return; + } + + this.page = { + ...this.page, + limit: this.paginationLimit, + offset: this.page.offset + this.paginationLimit, + }; + }, + async selectBranch(newBranch) { + if (newBranch === this.currentBranch) { + return; + } + + await this.$apollo.getClient().writeQuery({ + query: getCurrentBranch, + data: { currentBranch: newBranch }, + }); + + const updatedPath = setUrlParams({ branch_name: newBranch }); + historyPushState(updatedPath); + + this.$emit('refetchContent'); + }, + setSearchTerm(newSearchTerm) { + this.branches = []; + this.page = { + limit: newSearchTerm.trim() === '' ? this.paginationLimit : this.totalBranches, + offset: 0, + searchTerm: newSearchTerm.trim(), + }; }, }, }; </script> <template> - <gl-dropdown v-if="hasBranchList" class="gl-ml-2" :text="currentBranch" icon="branch"> + <gl-dropdown + v-if="showBranchSwitcher" + class="gl-ml-2" + :header-text="$options.i18n.dropdownHeader" + :text="currentBranch" + icon="branch" + > + <gl-search-box-by-type :debounce="$options.inputDebounce" @input="setSearchTerm" /> <gl-dropdown-section-header> - {{ this.$options.i18n.title }} + {{ $options.i18n.title }} </gl-dropdown-section-header> - <gl-dropdown-item - v-for="branch in branches" - :key="branch.name" - :is-checked="currentBranch === branch.name" - :is-check-item="true" + + <gl-infinite-scroll + :fetched-items="branches.length" + :total-items="totalBranches" + :max-list-height="250" + @bottomReached="fetchNextBranches" > - <gl-icon name="check" class="gl-visibility-hidden" /> - {{ branch.name }} - </gl-dropdown-item> + <template #items> + <gl-dropdown-item + v-for="branch in branches" + :key="branch" + :is-checked="currentBranch === branch" + :is-check-item="true" + @click="selectBranch(branch)" + > + {{ branch }} + </gl-dropdown-item> + </template> + <template #default> + <gl-dropdown-item v-if="isBranchesLoading" key="loading"> + <gl-loading-icon size="md" /> + </gl-dropdown-item> + </template> + </gl-infinite-scroll> </gl-dropdown> </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 fefa784f060..24bca04e115 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,5 +1,4 @@ <script> -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import PipelineStatus from './pipeline_status.vue'; import ValidationSegment from './validation_segment.vue'; @@ -29,7 +28,6 @@ export default { PipelineStatus, ValidationSegment, }, - mixins: [glFeatureFlagsMixin()], props: { ciConfigData: { type: Object, @@ -42,7 +40,7 @@ export default { }, computed: { showPipelineStatus() { - return this.glFeatures.pipelineStatusForPipelineEditor && !this.isNewCiConfigFile; + return !this.isNewCiConfigFile; }, // make sure corners are rounded correctly depending on if // pipeline status is rendered 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 4a92e106da1..368a026bdaa 100644 --- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue @@ -5,7 +5,11 @@ import { truncateSha } from '~/lib/utils/text_utility'; 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 { toggleQueryPollingByVisibility } from '~/pipelines/components/graph/utils'; +import getPipelineEtag from '~/pipeline_editor/graphql/queries/client/pipeline_etag.graphql'; +import { + getQueryHeaders, + toggleQueryPollingByVisibility, +} from '~/pipelines/components/graph/utils'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; const POLL_INTERVAL = 10000; @@ -31,7 +35,13 @@ export default { commitSha: { query: getCommitSha, }, + pipelineEtag: { + query: getPipelineEtag, + }, pipeline: { + context() { + return getQueryHeaders(this.pipelineEtag); + }, query: getPipelineQuery, variables() { return { 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 5acb3355b23..4e2f26af51d 100644 --- a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue +++ b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue @@ -110,7 +110,6 @@ export default { <text-editor :value="ciFileContent" v-on="$listeners" /> </editor-tab> <editor-tab - v-if="glFeatures.ciConfigVisualizationTab" class="gl-mb-3" :empty-message="$options.i18n.empty.visualization" :is-empty="isEmpty" @@ -135,7 +134,6 @@ export default { <ci-lint v-else :is-valid="isValid" :ci-config="ciConfigData" /> </editor-tab> <editor-tab - v-if="glFeatures.ciConfigMergedTab" class="gl-mb-3" :empty-message="$options.i18n.empty.merge" :keep-component-mounted="false" 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 index d4f04a0d055..0ac4a40ff4a 100644 --- 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 @@ -1,12 +1,14 @@ <script> import { GlButton, GlSprintf } from '@gitlab/ui'; import { __ } from '~/locale'; +import PipelineEditorFileNav from '~/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { components: { GlButton, GlSprintf, + PipelineEditorFileNav, }, i18n: { title: __('Optimize your workflow with CI/CD Pipelines'), @@ -22,6 +24,9 @@ export default { }, }, computed: { + showFileNav() { + return this.glFeatures.pipelineEditorBranchSwitcher; + }, showCTAButton() { return this.glFeatures.pipelineEditorEmptyStateAction; }, @@ -34,23 +39,26 @@ export default { }; </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> + <pipeline-editor-file-nav v-if="showFileNav" v-on="$listeners" /> + <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> </div> </template> 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 new file mode 100644 index 00000000000..091b202e10b --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_messages.vue @@ -0,0 +1,155 @@ +<script> +import { GlAlert } from '@gitlab/ui'; +import { getParameterValues, removeParams } from '~/lib/utils/url_utility'; +import { __, s__ } from '~/locale'; +import { + COMMIT_FAILURE, + COMMIT_SUCCESS, + DEFAULT_FAILURE, + DEFAULT_SUCCESS, + LOAD_FAILURE_UNKNOWN, +} from '../../constants'; +import CodeSnippetAlert from '../code_snippet_alert/code_snippet_alert.vue'; +import { + CODE_SNIPPET_SOURCE_URL_PARAM, + CODE_SNIPPET_SOURCES, +} from '../code_snippet_alert/constants'; + +export default { + components: { + GlAlert, + CodeSnippetAlert, + }, + errorTexts: { + [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.'), + }, + successTexts: { + [COMMIT_SUCCESS]: __('Your changes have been successfully committed.'), + [DEFAULT_SUCCESS]: __('Your action succeeded.'), + }, + props: { + failureType: { + type: String, + required: false, + default: null, + }, + failureReasons: { + type: Array, + required: false, + default: () => [], + }, + showFailure: { + type: Boolean, + required: false, + default: false, + }, + showSuccess: { + type: Boolean, + required: false, + default: false, + }, + successType: { + type: String, + required: false, + default: null, + }, + }, + data() { + return { + codeSnippetCopiedFrom: '', + }; + }, + computed: { + failure() { + switch (this.failureType) { + case LOAD_FAILURE_UNKNOWN: + return { + text: this.$options.errorTexts[LOAD_FAILURE_UNKNOWN], + variant: 'danger', + }; + case COMMIT_FAILURE: + return { + text: this.$options.errorTexts[COMMIT_FAILURE], + variant: 'danger', + }; + default: + return { + text: this.$options.errorTexts[DEFAULT_FAILURE], + variant: 'danger', + }; + } + }, + success() { + switch (this.successType) { + case COMMIT_SUCCESS: + return { + text: this.$options.successTexts[COMMIT_SUCCESS], + variant: 'info', + }; + default: + return { + text: this.$options.successTexts[DEFAULT_SUCCESS], + variant: 'info', + }; + } + }, + }, + created() { + this.parseCodeSnippetSourceParam(); + }, + methods: { + dismissCodeSnippetAlert() { + this.codeSnippetCopiedFrom = ''; + }, + dismissFailure() { + this.$emit('hide-failure'); + }, + dismissSuccess() { + this.$emit('hide-success'); + }, + parseCodeSnippetSourceParam() { + const [codeSnippetCopiedFrom] = getParameterValues(CODE_SNIPPET_SOURCE_URL_PARAM); + if (codeSnippetCopiedFrom && CODE_SNIPPET_SOURCES.includes(codeSnippetCopiedFrom)) { + this.codeSnippetCopiedFrom = codeSnippetCopiedFrom; + window.history.replaceState( + {}, + document.title, + removeParams([CODE_SNIPPET_SOURCE_URL_PARAM]), + ); + } + }, + }, +}; +</script> + +<template> + <div> + <code-snippet-alert + v-if="codeSnippetCopiedFrom" + :source="codeSnippetCopiedFrom" + class="gl-mb-5" + @dismiss="dismissCodeSnippetAlert" + /> + <gl-alert + v-if="showSuccess" + :variant="success.variant" + class="gl-mb-5" + @dismiss="dismissSuccess" + > + {{ success.text }} + </gl-alert> + <gl-alert + v-if="showFailure" + :variant="failure.variant" + class="gl-mb-5" + @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> +</template> diff --git a/app/assets/javascripts/pipeline_editor/constants.js b/app/assets/javascripts/pipeline_editor/constants.js index 8d0ec6c3e2d..f0a24e0c061 100644 --- a/app/assets/javascripts/pipeline_editor/constants.js +++ b/app/assets/javascripts/pipeline_editor/constants.js @@ -14,6 +14,7 @@ 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 CREATE_TAB = 'CREATE_TAB'; @@ -25,3 +26,8 @@ export const TABS_WITH_COMMIT_FORM = [CREATE_TAB, LINT_TAB, VISUALIZE_TAB]; export const COMMIT_ACTION_CREATE = 'CREATE'; export const COMMIT_ACTION_UPDATE = 'UPDATE'; + +export const DRAWER_EXPANDED_KEY = 'pipeline_editor_drawer_expanded'; + +export const BRANCH_PAGINATION_LIMIT = 20; +export const BRANCH_SEARCH_DEBOUNCE = '500'; 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 3b2daa45a18..94e6facabfd 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 @@ -22,6 +22,7 @@ mutation commitCIFile( commit { sha } + commitPipelinePath errors } } diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/available_branches.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/available_branches.graphql index f162bb11d47..46e9b108b41 100644 --- a/app/assets/javascripts/pipeline_editor/graphql/queries/available_branches.graphql +++ b/app/assets/javascripts/pipeline_editor/graphql/queries/available_branches.graphql @@ -1,9 +1,12 @@ -query getAvailableBranches($projectFullPath: ID!) { - project(fullPath: $projectFullPath) @client { +query getAvailableBranches( + $limit: Int! + $offset: Int! + $projectFullPath: ID! + $searchPattern: String! +) { + project(fullPath: $projectFullPath) { repository { - branches { - name - } + branchNames(limit: $limit, offset: $offset, searchPattern: $searchPattern) } } } diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline_etag.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline_etag.graphql new file mode 100644 index 00000000000..b9946a9e233 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline_etag.graphql @@ -0,0 +1,3 @@ +query getPipelineEtag { + pipelineEtag @client +} diff --git a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js index caa2a65d424..81e75c32846 100644 --- a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js +++ b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js @@ -11,23 +11,6 @@ export const resolvers = { }), }; }, - /* eslint-disable @gitlab/require-i18n-strings */ - project() { - return { - __typename: 'Project', - repository: { - __typename: 'Repository', - branches: [ - { __typename: 'Branch', name: 'master' }, - { __typename: 'Branch', name: 'main' }, - { __typename: 'Branch', name: 'develop' }, - { __typename: 'Branch', name: 'production' }, - { __typename: 'Branch', name: 'test' }, - ], - }, - }; - }, - /* 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 8a1e26f9bff..66158bdba88 100644 --- a/app/assets/javascripts/pipeline_editor/index.js +++ b/app/assets/javascripts/pipeline_editor/index.js @@ -6,6 +6,7 @@ import { resetServiceWorkersPublicPath } from '../lib/utils/webpack'; import { CODE_SNIPPET_SOURCE_SETTINGS } from './components/code_snippet_alert/constants'; import getCommitSha from './graphql/queries/client/commit_sha.graphql'; import getCurrentBranch from './graphql/queries/client/current_branch.graphql'; +import getPipelineEtag from './graphql/queries/client/pipeline_etag.graphql'; import { resolvers } from './graphql/resolvers'; import typeDefs from './graphql/typedefs.graphql'; import PipelineEditorApp from './pipeline_editor_app.vue'; @@ -26,15 +27,23 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { // Add to apollo cache as it can be updated by future queries commitSha, initialBranchName, + pipelineEtag, // Add to provide/inject API for static values ciConfigPath, + ciExamplesHelpPagePath, + ciHelpPagePath, defaultBranch, emptyStateIllustrationPath, + helpPaths, lintHelpPagePath, + needsHelpPagePath, newMergeRequestPath, + pipelinePagePath, projectFullPath, projectPath, projectNamespace, + runnerHelpPagePath, + totalBranches, ymlHelpPagePath, } = el?.dataset; @@ -48,7 +57,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { Vue.use(VueApollo); const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(resolvers, { typeDefs }), + defaultClient: createDefaultClient(resolvers, { typeDefs, useGet: true }), }); const { cache } = apolloProvider.clients.defaultClient; @@ -66,20 +75,34 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { }, }); + cache.writeQuery({ + query: getPipelineEtag, + data: { + pipelineEtag, + }, + }); + return new Vue({ el, apolloProvider, provide: { ciConfigPath, + ciExamplesHelpPagePath, + ciHelpPagePath, + configurationPaths, defaultBranch, emptyStateIllustrationPath, + helpPaths, lintHelpPagePath, + needsHelpPagePath, newMergeRequestPath, + pipelinePagePath, projectFullPath, projectPath, projectNamespace, + runnerHelpPagePath, + totalBranches: parseInt(totalBranches, 10), ymlHelpPagePath, - configurationPaths, }, render(h) { return h(PipelineEditorApp); diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue index e0fb38004ec..79a2a51cebc 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue @@ -1,21 +1,15 @@ <script> -import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon } from '@gitlab/ui'; +import { fetchPolicies } from '~/lib/graphql'; import httpStatusCodes from '~/lib/utils/http_status'; -import { getParameterValues, removeParams } from '~/lib/utils/url_utility'; -import { __, s__ } from '~/locale'; +import { s__ } from '~/locale'; import { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils'; -import CodeSnippetAlert from './components/code_snippet_alert/code_snippet_alert.vue'; -import { - CODE_SNIPPET_SOURCE_URL_PARAM, - CODE_SNIPPET_SOURCES, -} from './components/code_snippet_alert/constants'; + import ConfirmUnsavedChangesDialog from './components/ui/confirm_unsaved_changes_dialog.vue'; import PipelineEditorEmptyState from './components/ui/pipeline_editor_empty_state.vue'; +import PipelineEditorMessages from './components/ui/pipeline_editor_messages.vue'; import { - COMMIT_FAILURE, - COMMIT_SUCCESS, - DEFAULT_FAILURE, EDITOR_APP_STATUS_EMPTY, EDITOR_APP_STATUS_ERROR, EDITOR_APP_STATUS_LOADING, @@ -31,11 +25,10 @@ import PipelineEditorHome from './pipeline_editor_home.vue'; export default { components: { ConfirmUnsavedChangesDialog, - GlAlert, GlLoadingIcon, PipelineEditorEmptyState, PipelineEditorHome, - CodeSnippetAlert, + PipelineEditorMessages, }, inject: { ciConfigPath: { @@ -50,20 +43,20 @@ export default { ciConfigData: {}, failureType: null, failureReasons: [], - showStartScreen: false, - isNewCiConfigFile: false, initialCiFileContent: '', + isNewCiConfigFile: false, lastCommittedContent: '', currentCiFileContent: '', - showFailureAlert: false, - showSuccessAlert: false, successType: null, - codeSnippetCopiedFrom: '', + showStartScreen: false, + showSuccess: false, + showFailure: false, }; }, apollo: { initialCiFileContent: { + fetchPolicy: fetchPolicies.NETWORK, 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 @@ -87,10 +80,21 @@ export default { this.lastCommittedContent = fileContent; this.currentCiFileContent = fileContent; + + // make sure to reset the start screen flag during a refetch + // e.g. when switching branches + if (fileContent.length) { + this.showStartScreen = false; + } }, error(error) { this.handleBlobContentError(error); }, + watchLoading(isLoading) { + if (isLoading) { + this.setAppStatus(EDITOR_APP_STATUS_LOADING); + } + }, }, ciConfigData: { query: getCiConfigData, @@ -145,50 +149,12 @@ export default { isEmpty() { return this.currentCiFileContent === ''; }, - failure() { - switch (this.failureType) { - case LOAD_FAILURE_UNKNOWN: - return { - text: this.$options.errorTexts[LOAD_FAILURE_UNKNOWN], - variant: 'danger', - }; - case COMMIT_FAILURE: - return { - text: this.$options.errorTexts[COMMIT_FAILURE], - variant: 'danger', - }; - default: - return { - text: this.$options.errorTexts[DEFAULT_FAILURE], - variant: 'danger', - }; - } - }, - success() { - switch (this.successType) { - case COMMIT_SUCCESS: - return { - text: this.$options.successTexts[COMMIT_SUCCESS], - variant: 'info', - }; - default: - return null; - } - }, }, i18n: { tabEdit: s__('Pipelines|Write pipeline configuration'), tabGraph: s__('Pipelines|Visualize'), tabLint: s__('Pipelines|Lint'), }, - errorTexts: { - [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.'), - }, - successTexts: { - [COMMIT_SUCCESS]: __('Your changes have been successfully committed.'), - }, watch: { isEmpty(flag) { if (flag) { @@ -196,9 +162,6 @@ export default { } }, }, - created() { - this.parseCodeSnippetSourceParam(); - }, methods: { handleBlobContentError(error = {}) { const { networkError } = error; @@ -216,24 +179,27 @@ export default { this.reportFailure(LOAD_FAILURE_UNKNOWN); } }, - - dismissFailure() { - this.showFailureAlert = false; + hideFailure() { + this.showFailure = false; + }, + hideSuccess() { + this.showSuccess = false; }, - dismissSuccess() { - this.showSuccessAlert = false; + async refetchContent() { + this.$apollo.queries.initialCiFileContent.skip = false; + await this.$apollo.queries.initialCiFileContent.refetch(); }, reportFailure(type, reasons = []) { this.setAppStatus(EDITOR_APP_STATUS_ERROR); window.scrollTo({ top: 0, behavior: 'smooth' }); - this.showFailureAlert = true; + this.showFailure = true; this.failureType = type; this.failureReasons = reasons; }, reportSuccess(type) { window.scrollTo({ top: 0, behavior: 'smooth' }); - this.showSuccessAlert = true; + this.showSuccess = true; this.successType = type; }, resetContent() { @@ -266,20 +232,6 @@ export default { // if the user has made changes to the file that are unsaved. this.lastCommittedContent = this.currentCiFileContent; }, - parseCodeSnippetSourceParam() { - const [codeSnippetCopiedFrom] = getParameterValues(CODE_SNIPPET_SOURCE_URL_PARAM); - if (codeSnippetCopiedFrom && CODE_SNIPPET_SOURCES.includes(codeSnippetCopiedFrom)) { - this.codeSnippetCopiedFrom = codeSnippetCopiedFrom; - window.history.replaceState( - {}, - document.title, - removeParams([CODE_SNIPPET_SOURCE_URL_PARAM]), - ); - } - }, - dismissCodeSnippetAlert() { - this.codeSnippetCopiedFrom = ''; - }, }, }; </script> @@ -290,33 +242,18 @@ export default { <pipeline-editor-empty-state v-else-if="showStartScreen" @createEmptyConfigFile="setNewEmptyCiConfigFile" + @refetchContent="refetchContent" /> <div v-else> - <code-snippet-alert - v-if="codeSnippetCopiedFrom" - :source="codeSnippetCopiedFrom" - class="gl-mb-5" - @dismiss="dismissCodeSnippetAlert" + <pipeline-editor-messages + :failure-type="failureType" + :failure-reasons="failureReasons" + :show-failure="showFailure" + :show-success="showSuccess" + :success-type="successType" + @hide-success="hideSuccess" + @hide-failure="hideFailure" /> - <gl-alert - v-if="showSuccessAlert" - :variant="success.variant" - class="gl-mb-5" - @dismiss="dismissSuccess" - > - {{ success.text }} - </gl-alert> - <gl-alert - v-if="showFailureAlert" - :variant="failure.variant" - class="gl-mb-5" - @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 :ci-config-data="ciConfigData" :ci-file-content="currentCiFileContent" @@ -324,6 +261,7 @@ export default { @commit="updateOnCommit" @resetContent="resetContent" @showError="showErrorAlert" + @refetchContent="refetchContent" @updateCiConfig="updateCiConfig" /> <confirm-unsaved-changes-dialog :has-unsaved-changes="hasUnsavedChanges" /> diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue index adba55f9f4b..dfe9c82b912 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue @@ -1,5 +1,7 @@ <script> +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; 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'; @@ -8,10 +10,12 @@ import { TABS_WITH_COMMIT_FORM, CREATE_TAB } from './constants'; export default { components: { CommitSection, + PipelineEditorDrawer, PipelineEditorFileNav, PipelineEditorHeader, PipelineEditorTabs, }, + mixins: [glFeatureFlagMixin()], props: { ciConfigData: { type: Object, @@ -35,6 +39,9 @@ export default { showCommitForm() { return TABS_WITH_COMMIT_FORM.includes(this.currentTab); }, + showPipelineDrawer() { + return this.glFeatures.pipelineEditorDrawer; + }, }, methods: { setCurrentTab(tabName) { @@ -45,7 +52,7 @@ export default { </script> <template> - <div> + <div class="gl-pr-9 gl-transition-medium gl-w-full"> <pipeline-editor-file-nav v-on="$listeners" /> <pipeline-editor-header :ci-config-data="ciConfigData" @@ -58,5 +65,6 @@ export default { @set-current-tab="setCurrentTab" /> <commit-section v-if="showCommitForm" :ci-file-content="ciFileContent" v-on="$listeners" /> + <pipeline-editor-drawer v-if="showPipelineDrawer" /> </div> </template> |