diff options
Diffstat (limited to 'app/assets/javascripts/projects')
25 files changed, 363 insertions, 152 deletions
diff --git a/app/assets/javascripts/projects/commit/components/commit_comments_button.vue b/app/assets/javascripts/projects/commit/components/commit_comments_button.vue new file mode 100644 index 00000000000..67b5e1e512c --- /dev/null +++ b/app/assets/javascripts/projects/commit/components/commit_comments_button.vue @@ -0,0 +1,42 @@ +<script> +import { GlButton, GlTooltipDirective } from '@gitlab/ui'; +import { n__ } from '~/locale'; + +export default { + directives: { + GlTooltip: GlTooltipDirective, + }, + components: { + GlButton, + }, + props: { + commentsCount: { + type: Number, + required: true, + }, + }, + computed: { + tooltipText() { + return n__('%d comment on this commit', '%d comments on this commit', this.commentsCount); + }, + showCommentButton() { + return this.commentsCount > 0; + }, + }, +}; +</script> + +<template> + <span + v-if="showCommentButton" + v-gl-tooltip + class="gl-display-none gl-sm-display-inline-block" + tabindex="0" + :title="tooltipText" + data-testid="comment-button-wrapper" + > + <gl-button icon="comment" class="gl-mr-3" disabled> + {{ commentsCount }} + </gl-button> + </span> +</template> diff --git a/app/assets/javascripts/projects/commit/components/commit_options_dropdown.vue b/app/assets/javascripts/projects/commit/components/commit_options_dropdown.vue new file mode 100644 index 00000000000..d96d1035ed0 --- /dev/null +++ b/app/assets/javascripts/projects/commit/components/commit_options_dropdown.vue @@ -0,0 +1,107 @@ +<script> +import { GlDropdown, GlDropdownItem, GlDropdownDivider, GlDropdownSectionHeader } from '@gitlab/ui'; +import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '../constants'; +import eventHub from '../event_hub'; + +export default { + components: { + GlDropdown, + GlDropdownItem, + GlDropdownDivider, + GlDropdownSectionHeader, + }, + inject: { + newProjectTagPath: { + default: '', + }, + emailPatchesPath: { + default: '', + }, + plainDiffPath: { + default: '', + }, + }, + props: { + canRevert: { + type: Boolean, + required: true, + }, + canCherryPick: { + type: Boolean, + required: true, + }, + canTag: { + type: Boolean, + required: true, + }, + canEmailPatches: { + type: Boolean, + required: true, + }, + }, + computed: { + showDivider() { + return this.canRevert || this.canCherryPick || this.canTag; + }, + }, + methods: { + showModal(modalId) { + eventHub.$emit(modalId); + }, + }, + openRevertModal: OPEN_REVERT_MODAL, + openCherryPickModal: OPEN_CHERRY_PICK_MODAL, +}; +</script> + +<template> + <gl-dropdown + :text="__('Options')" + right + data-testid="commit-options-dropdown" + data-qa-selector="options_button" + class="gl-xs-w-full" + > + <gl-dropdown-item + v-if="canRevert" + data-testid="revert-link" + @click="showModal($options.openRevertModal)" + > + {{ s__('ChangeTypeAction|Revert') }} + </gl-dropdown-item> + <gl-dropdown-item + v-if="canCherryPick" + data-testid="cherry-pick-link" + data-qa-selector="cherry_pick_button" + @click="showModal($options.openCherryPickModal)" + > + {{ s__('ChangeTypeAction|Cherry-pick') }} + </gl-dropdown-item> + <gl-dropdown-item v-if="canTag" :href="newProjectTagPath" data-testid="tag-link"> + {{ s__('CreateTag|Tag') }} + </gl-dropdown-item> + <gl-dropdown-divider v-if="showDivider" /> + <gl-dropdown-section-header> + {{ __('Download') }} + </gl-dropdown-section-header> + <gl-dropdown-item + v-if="canEmailPatches" + :href="emailPatchesPath" + download + rel="nofollow" + data-testid="email-patches-link" + data-qa-selector="email_patches" + > + {{ s__('DownloadCommit|Email Patches') }} + </gl-dropdown-item> + <gl-dropdown-item + :href="plainDiffPath" + download + rel="nofollow" + data-testid="plain-diff-link" + data-qa-selector="plain_diff" + > + {{ s__('DownloadCommit|Plain Diff') }} + </gl-dropdown-item> + </gl-dropdown> +</template> diff --git a/app/assets/javascripts/projects/commit/components/form_modal.vue b/app/assets/javascripts/projects/commit/components/form_modal.vue index 30968d29cde..6eefa5f55e4 100644 --- a/app/assets/javascripts/projects/commit/components/form_modal.vue +++ b/app/assets/javascripts/projects/commit/components/form_modal.vue @@ -37,6 +37,11 @@ export default { type: String, required: true, }, + isCherryPick: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -47,6 +52,7 @@ export default { { variant: 'success' }, { category: 'primary' }, { 'data-testid': 'submit-commit' }, + { 'data-qa-selector': 'submit_commit_button' }, ], }, actionCancel: { @@ -110,7 +116,7 @@ export default { <input type="hidden" name="authenticity_token" :value="$options.csrf.token" /> <gl-form-group - v-if="glFeatures.pickIntoProject" + v-if="glFeatures.pickIntoProject && isCherryPick" :label="i18n.projectLabel" label-for="start_project" data-testid="dropdown-group" diff --git a/app/assets/javascripts/projects/commit/components/form_trigger.vue b/app/assets/javascripts/projects/commit/components/form_trigger.vue deleted file mode 100644 index 3561b5c2473..00000000000 --- a/app/assets/javascripts/projects/commit/components/form_trigger.vue +++ /dev/null @@ -1,35 +0,0 @@ -<script> -import { GlLink } from '@gitlab/ui'; -import eventHub from '../event_hub'; - -export default { - components: { - GlLink, - }, - inject: { - displayText: { - default: '', - }, - testId: { - default: '', - }, - }, - props: { - openModal: { - type: String, - required: true, - }, - }, - methods: { - showModal() { - eventHub.$emit(this.openModal); - }, - }, -}; -</script> - -<template> - <gl-link data-is-link="true" :data-testid="testId" @click="showModal"> - {{ displayText }} - </gl-link> -</template> diff --git a/app/assets/javascripts/projects/commit/constants.js b/app/assets/javascripts/projects/commit/constants.js index d6bb4e9483f..d553bca360e 100644 --- a/app/assets/javascripts/projects/commit/constants.js +++ b/app/assets/javascripts/projects/commit/constants.js @@ -2,10 +2,8 @@ import { s__, __ } from '~/locale'; export const OPEN_REVERT_MODAL = 'openRevertModal'; export const REVERT_MODAL_ID = 'revert-commit-modal'; -export const REVERT_LINK_TEST_ID = 'revert-commit-link'; export const OPEN_CHERRY_PICK_MODAL = 'openCherryPickModal'; export const CHERRY_PICK_MODAL_ID = 'cherry-pick-commit-modal'; -export const CHERRY_PICK_LINK_TEST_ID = 'cherry-pick-commit-link'; export const I18N_MODAL = { startMergeRequest: s__('ChangeTypeAction|Start a %{newMergeRequest} with these changes'), diff --git a/app/assets/javascripts/projects/commit/index.js b/app/assets/javascripts/projects/commit/index.js index b5fdfc25236..d8d30c4332c 100644 --- a/app/assets/javascripts/projects/commit/index.js +++ b/app/assets/javascripts/projects/commit/index.js @@ -1,11 +1,11 @@ import initCherryPickCommitModal from './init_cherry_pick_commit_modal'; -import initCherryPickCommitTrigger from './init_cherry_pick_commit_trigger'; +import initCommitCommentsButton from './init_commit_comments_button'; +import initCommitOptionsDropdown from './init_commit_options_dropdown'; import initRevertCommitModal from './init_revert_commit_modal'; -import initRevertCommitTrigger from './init_revert_commit_trigger'; export default () => { initRevertCommitModal(); - initRevertCommitTrigger(); initCherryPickCommitModal(); - initCherryPickCommitTrigger(); + initCommitCommentsButton(); + initCommitOptionsDropdown(); }; diff --git a/app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js b/app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js index ad31ad14b2a..47ee8237fea 100644 --- a/app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js +++ b/app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js @@ -51,6 +51,7 @@ export default function initInviteMembersModal() { i18n: { ...I18N_CHERRY_PICK_MODAL, ...I18N_MODAL }, openModal: OPEN_CHERRY_PICK_MODAL, modalId: CHERRY_PICK_MODAL_ID, + isCherryPick: true, }, }), }); diff --git a/app/assets/javascripts/projects/commit/init_cherry_pick_commit_trigger.js b/app/assets/javascripts/projects/commit/init_cherry_pick_commit_trigger.js deleted file mode 100644 index 942451dc96a..00000000000 --- a/app/assets/javascripts/projects/commit/init_cherry_pick_commit_trigger.js +++ /dev/null @@ -1,20 +0,0 @@ -import Vue from 'vue'; -import CommitFormTrigger from './components/form_trigger.vue'; -import { OPEN_CHERRY_PICK_MODAL, CHERRY_PICK_LINK_TEST_ID } from './constants'; - -export default function initInviteMembersTrigger() { - const el = document.querySelector('.js-cherry-pick-commit-trigger'); - - if (!el) { - return false; - } - - const { displayText } = el.dataset; - - return new Vue({ - el, - provide: { displayText, testId: CHERRY_PICK_LINK_TEST_ID }, - render: (createElement) => - createElement(CommitFormTrigger, { props: { openModal: OPEN_CHERRY_PICK_MODAL } }), - }); -} diff --git a/app/assets/javascripts/projects/commit/init_commit_comments_button.js b/app/assets/javascripts/projects/commit/init_commit_comments_button.js new file mode 100644 index 00000000000..d70f7cb65f3 --- /dev/null +++ b/app/assets/javascripts/projects/commit/init_commit_comments_button.js @@ -0,0 +1,18 @@ +import Vue from 'vue'; +import CommitCommentsButton from './components/commit_comments_button.vue'; + +export default function initCommitCommentsButton() { + const el = document.querySelector('#js-commit-comments-button'); + + if (!el) { + return false; + } + + const { commentsCount } = el.dataset; + + return new Vue({ + el, + render: (createElement) => + createElement(CommitCommentsButton, { props: { commentsCount: Number(commentsCount) } }), + }); +} diff --git a/app/assets/javascripts/projects/commit/init_commit_options_dropdown.js b/app/assets/javascripts/projects/commit/init_commit_options_dropdown.js new file mode 100644 index 00000000000..339918e7661 --- /dev/null +++ b/app/assets/javascripts/projects/commit/init_commit_options_dropdown.js @@ -0,0 +1,35 @@ +import Vue from 'vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import CommitOptionsDropdown from './components/commit_options_dropdown.vue'; + +export default function initCommitOptionsDropdown() { + const el = document.querySelector('#js-commit-options-dropdown'); + + if (!el) { + return false; + } + + const { + newProjectTagPath, + emailPatchesPath, + plainDiffPath, + canRevert, + canCherryPick, + canTag, + canEmailPatches, + } = el.dataset; + + return new Vue({ + el, + provide: { newProjectTagPath, emailPatchesPath, plainDiffPath }, + render: (createElement) => + createElement(CommitOptionsDropdown, { + props: { + canRevert: parseBoolean(canRevert), + canCherryPick: parseBoolean(canCherryPick), + canTag: parseBoolean(canTag), + canEmailPatches: parseBoolean(canEmailPatches), + }, + }), + }); +} diff --git a/app/assets/javascripts/projects/commit/init_revert_commit_trigger.js b/app/assets/javascripts/projects/commit/init_revert_commit_trigger.js deleted file mode 100644 index dc5168524ca..00000000000 --- a/app/assets/javascripts/projects/commit/init_revert_commit_trigger.js +++ /dev/null @@ -1,20 +0,0 @@ -import Vue from 'vue'; -import CommitFormTrigger from './components/form_trigger.vue'; -import { OPEN_REVERT_MODAL, REVERT_LINK_TEST_ID } from './constants'; - -export default function initInviteMembersTrigger() { - const el = document.querySelector('.js-revert-commit-trigger'); - - if (!el) { - return false; - } - - const { displayText } = el.dataset; - - return new Vue({ - el, - provide: { displayText, testId: REVERT_LINK_TEST_ID }, - render: (createElement) => - createElement(CommitFormTrigger, { props: { openModal: OPEN_REVERT_MODAL } }), - }); -} diff --git a/app/assets/javascripts/projects/commit/store/actions.js b/app/assets/javascripts/projects/commit/store/actions.js index c72704303ca..2b25082eced 100644 --- a/app/assets/javascripts/projects/commit/store/actions.js +++ b/app/assets/javascripts/projects/commit/store/actions.js @@ -22,8 +22,8 @@ export const fetchBranches = ({ commit, dispatch, state }, query) => { .get(state.branchesEndpoint, { params: { search: query }, }) - .then(({ data }) => { - commit(types.RECEIVE_BRANCHES_SUCCESS, data.Branches || []); + .then(({ data = [] }) => { + commit(types.RECEIVE_BRANCHES_SUCCESS, data.Branches?.length ? data.Branches : data); }) .catch(() => { createFlash({ message: PROJECT_BRANCHES_ERROR }); diff --git a/app/assets/javascripts/projects/commit_box/info/index.js b/app/assets/javascripts/projects/commit_box/info/index.js index 17c63ecf66b..69fe2d30489 100644 --- a/app/assets/javascripts/projects/commit_box/info/index.js +++ b/app/assets/javascripts/projects/commit_box/info/index.js @@ -1,27 +1,17 @@ import { fetchCommitMergeRequests } from '~/commit_merge_requests'; -import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; import { initCommitPipelineMiniGraph } from './init_commit_pipeline_mini_graph'; import { initDetailsButton } from './init_details_button'; import { loadBranches } from './load_branches'; -export const initCommitBoxInfo = (containerSelector = '.js-commit-box-info') => { - const containerEl = document.querySelector(containerSelector); - +export const initCommitBoxInfo = () => { // Display commit related branches - loadBranches(containerEl); + loadBranches(); // Related merge requests to this commit fetchCommitMergeRequests(); // Display pipeline mini graph for this commit - // Feature flag ci_commit_pipeline_mini_graph_vue - if (gon.features.ciCommitPipelineMiniGraphVue) { - initCommitPipelineMiniGraph(); - } else { - new MiniPipelineGraph({ - container: '.js-commit-pipeline-graph', - }).bindEvents(); - } + initCommitPipelineMiniGraph(); initDetailsButton(); }; diff --git a/app/assets/javascripts/projects/commit_box/info/load_branches.js b/app/assets/javascripts/projects/commit_box/info/load_branches.js index 8a0b2c30abe..d1136817cb3 100644 --- a/app/assets/javascripts/projects/commit_box/info/load_branches.js +++ b/app/assets/javascripts/projects/commit_box/info/load_branches.js @@ -2,7 +2,8 @@ import axios from 'axios'; import { sanitize } from '~/lib/dompurify'; import { __ } from '~/locale'; -export const loadBranches = (containerEl) => { +export const loadBranches = (containerSelector = '.js-commit-box-info') => { + const containerEl = document.querySelector(containerSelector); if (!containerEl) { return; } diff --git a/app/assets/javascripts/projects/compare/components/app_legacy.vue b/app/assets/javascripts/projects/compare/components/app_legacy.vue index c0ff58ee074..d3f09f7d69f 100644 --- a/app/assets/javascripts/projects/compare/components/app_legacy.vue +++ b/app/assets/javascripts/projects/compare/components/app_legacy.vue @@ -37,10 +37,22 @@ export default { required: true, }, }, + data() { + return { + from: this.paramsFrom, + to: this.paramsTo, + }; + }, methods: { onSubmit() { this.$refs.form.submit(); }, + onSwapRevision() { + [this.from, this.to] = [this.to, this.from]; // swaps 'from' and 'to' + }, + onSelectRevision({ direction, revision }) { + this[direction] = revision; // direction is either 'from' or 'to' + }, }, }; </script> @@ -57,19 +69,30 @@ export default { :refs-project-path="refsProjectPath" revision-text="Source" params-name="to" - :params-branch="paramsTo" + :params-branch="to" + data-testid="sourceRevisionDropdown" + @selectRevision="onSelectRevision" /> <div class="compare-ellipsis gl-display-inline" data-testid="ellipsis">...</div> <revision-dropdown :refs-project-path="refsProjectPath" revision-text="Target" params-name="from" - :params-branch="paramsFrom" + :params-branch="from" + data-testid="targetRevisionDropdown" + @selectRevision="onSelectRevision" /> <gl-button category="primary" variant="success" class="gl-ml-3" @click="onSubmit"> {{ s__('CompareRevisions|Compare') }} </gl-button> <gl-button + data-testid="swapRevisionsButton" + class="btn btn-default gl-button gl-ml-3" + @click="onSwapRevision" + > + {{ s__('CompareRevisions|Swap revisions') }} + </gl-button> + <gl-button v-if="projectMergeRequestPath" :href="projectMergeRequestPath" data-testid="projectMrButton" diff --git a/app/assets/javascripts/projects/compare/components/repo_dropdown.vue b/app/assets/javascripts/projects/compare/components/repo_dropdown.vue index 822dfc09d81..cb9d8b64b33 100644 --- a/app/assets/javascripts/projects/compare/components/repo_dropdown.vue +++ b/app/assets/javascripts/projects/compare/components/repo_dropdown.vue @@ -46,14 +46,7 @@ export default { this.emitTargetProject(repo.name); }, setDefaultRepo() { - if (this.isSourceRevision) { - this.selectedRepo = this.projectTo; - return; - } - - const [defaultTargetProject] = this.projectsFrom; - this.emitTargetProject(defaultTargetProject.name); - this.selectedRepo = defaultTargetProject; + this.selectedRepo = this.projectTo; }, emitTargetProject(name) { if (!this.isSourceRevision) { diff --git a/app/assets/javascripts/projects/compare/components/revision_dropdown.vue b/app/assets/javascripts/projects/compare/components/revision_dropdown.vue index a175af2f32e..d0b69344c12 100644 --- a/app/assets/javascripts/projects/compare/components/revision_dropdown.vue +++ b/app/assets/javascripts/projects/compare/components/revision_dropdown.vue @@ -1,10 +1,12 @@ <script> import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlDropdownSectionHeader } from '@gitlab/ui'; +import { debounce } from 'lodash'; import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { s__ } from '~/locale'; -const emptyDropdownText = s__('CompareRevisions|Select branch/tag'); +const EMPTY_DROPDOWN_TEXT = s__('CompareRevisions|Select branch/tag'); +const SEARCH_DEBOUNCE_MS = 300; export default { components: { @@ -38,19 +40,11 @@ export default { }; }, computed: { - filteredBranches() { - return this.branches.filter((branch) => - branch.toLowerCase().includes(this.searchTerm.toLowerCase()), - ); + hasBranches() { + return Boolean(this.branches?.length); }, - hasFilteredBranches() { - return this.filteredBranches.length; - }, - filteredTags() { - return this.tags.filter((tag) => tag.toLowerCase().includes(this.searchTerm.toLowerCase())); - }, - hasFilteredTags() { - return this.filteredTags.length; + hasTags() { + return Boolean(this.tags?.length); }, }, watch: { @@ -59,13 +53,34 @@ export default { this.fetchBranchesAndTags(true); } }, + searchTerm: debounce(function debounceSearch() { + this.searchBranchesAndTags(); + }, SEARCH_DEBOUNCE_MS), }, mounted() { this.fetchBranchesAndTags(); }, methods: { + searchBranchesAndTags() { + return axios + .get(this.refsProjectPath, { + params: { + search: this.searchTerm, + }, + }) + .then(({ data }) => { + this.branches = data.Branches || []; + this.tags = data.Tags || []; + }) + .catch(() => { + createFlash({ + message: s__( + 'CompareRevisions|There was an error while searching the branch/tag list. Please try again.', + ), + }); + }); + }, fetchBranchesAndTags(reset = false) { - const endpoint = this.refsProjectPath; this.loading = true; if (reset) { @@ -73,7 +88,7 @@ export default { } return axios - .get(endpoint) + .get(this.refsProjectPath) .then(({ data }) => { this.branches = data.Branches || []; this.tags = data.Tags || []; @@ -90,7 +105,7 @@ export default { }); }, getDefaultBranch() { - return this.paramsBranch || emptyDropdownText; + return this.paramsBranch || EMPTY_DROPDOWN_TEXT; }, onClick(revision) { this.selectedRevision = revision; @@ -119,24 +134,24 @@ export default { @keyup.enter="onSearchEnter" /> </template> - <gl-dropdown-section-header v-if="hasFilteredBranches"> + <gl-dropdown-section-header v-if="hasBranches"> {{ s__('CompareRevisions|Branches') }} </gl-dropdown-section-header> <gl-dropdown-item - v-for="(branch, index) in filteredBranches" - :key="`branch${index}`" + v-for="branch in branches" + :key="branch" is-check-item :is-checked="selectedRevision === branch" @click="onClick(branch)" > {{ branch }} </gl-dropdown-item> - <gl-dropdown-section-header v-if="hasFilteredTags"> + <gl-dropdown-section-header v-if="hasTags"> {{ s__('CompareRevisions|Tags') }} </gl-dropdown-section-header> <gl-dropdown-item - v-for="(tag, index) in filteredTags" - :key="`tag${index}`" + v-for="tag in tags" + :key="tag" is-check-item :is-checked="selectedRevision === tag" @click="onClick(tag)" diff --git a/app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue b/app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue index 13d80b5ae0b..f57a8942a77 100644 --- a/app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue +++ b/app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue @@ -55,6 +55,11 @@ export default { return this.filteredTags.length; }, }, + watch: { + paramsBranch(newBranch) { + this.setSelectedRevision(newBranch); + }, + }, mounted() { this.fetchBranchesAndTags(); }, @@ -83,10 +88,14 @@ export default { return this.paramsBranch || s__('CompareRevisions|Select branch/tag'); }, onClick(revision) { - this.selectedRevision = revision; + this.setSelectedRevision(revision); }, onSearchEnter() { - this.selectedRevision = this.searchTerm; + this.setSelectedRevision(this.searchTerm); + }, + setSelectedRevision(revision) { + this.selectedRevision = revision || s__('CompareRevisions|Select branch/tag'); + this.$emit('selectRevision', { direction: this.paramsName, revision }); }, }, }; diff --git a/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue b/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue index ef61fba88fe..1060b37067e 100644 --- a/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue +++ b/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue @@ -1,8 +1,9 @@ <script> /* eslint-disable vue/no-v-html */ import { GlBreadcrumb, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import { experiment } from '~/experimentation/utils'; import { __, s__ } from '~/locale'; - +import { NEW_REPO_EXPERIMENT } from '../constants'; import blankProjectIllustration from '../illustrations/blank-project.svg'; import ciCdProjectIllustration from '../illustrations/ci-cd-project.svg'; import createFromTemplateIllustration from '../illustrations/create-from-template.svg'; @@ -13,8 +14,10 @@ import WelcomePage from './welcome.vue'; const BLANK_PANEL = 'blank_project'; const CI_CD_PANEL = 'cicd_for_external_repo'; const LAST_ACTIVE_TAB_KEY = 'new_project_last_active_tab'; + const PANELS = [ { + key: 'blank', name: BLANK_PANEL, selector: '#blank-project-pane', title: s__('ProjectsNew|Create blank project'), @@ -24,6 +27,7 @@ const PANELS = [ illustration: blankProjectIllustration, }, { + key: 'template', name: 'create_from_template', selector: '#create-from-template-pane', title: s__('ProjectsNew|Create from template'), @@ -33,6 +37,7 @@ const PANELS = [ illustration: createFromTemplateIllustration, }, { + key: 'import', name: 'import_project', selector: '#import-project-pane', title: s__('ProjectsNew|Import project'), @@ -42,6 +47,7 @@ const PANELS = [ illustration: importProjectIllustration, }, { + key: 'ci', name: CI_CD_PANEL, selector: '#ci-cd-project-pane', title: s__('ProjectsNew|Run CI/CD for external repository'), @@ -85,16 +91,34 @@ export default { }, computed: { + decoratedPanels() { + const PANEL_TITLES = experiment(NEW_REPO_EXPERIMENT, { + use: () => ({ + blank: s__('ProjectsNew|Create blank project'), + import: s__('ProjectsNew|Import project'), + }), + try: () => ({ + blank: s__('ProjectsNew|Create blank project/repository'), + import: s__('ProjectsNew|Import project/repository'), + }), + }); + + return PANELS.map(({ key, title, ...el }) => ({ + ...el, + title: PANEL_TITLES[key] !== undefined ? PANEL_TITLES[key] : title, + })); + }, + availablePanels() { if (this.isCiCdAvailable) { - return PANELS; + return this.decoratedPanels; } - return PANELS.filter((p) => p.name !== CI_CD_PANEL); + return this.decoratedPanels.filter((p) => p.name !== CI_CD_PANEL); }, activePanel() { - return PANELS.find((p) => p.name === this.activeTab); + return this.decoratedPanels.find((p) => p.name === this.activeTab); }, breadcrumbs() { diff --git a/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue b/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue index ed82a635b1f..d342ce4c9c2 100644 --- a/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue +++ b/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue @@ -1,9 +1,10 @@ <script> /* eslint-disable vue/no-v-html */ import Tracking from '~/tracking'; +import { NEW_REPO_EXPERIMENT } from '../constants'; import NewProjectPushTipPopover from './new_project_push_tip_popover.vue'; -const trackingMixin = Tracking.mixin(gon.tracking_data); +const trackingMixin = Tracking.mixin({ ...gon.tracking_data, experiment: NEW_REPO_EXPERIMENT }); export default { components: { diff --git a/app/assets/javascripts/projects/experiment_new_project_creation/constants.js b/app/assets/javascripts/projects/experiment_new_project_creation/constants.js new file mode 100644 index 00000000000..402ca887cf1 --- /dev/null +++ b/app/assets/javascripts/projects/experiment_new_project_creation/constants.js @@ -0,0 +1 @@ +export const NEW_REPO_EXPERIMENT = 'new_repo'; diff --git a/app/assets/javascripts/projects/pipelines/charts/components/app.vue b/app/assets/javascripts/projects/pipelines/charts/components/app.vue index 4a8e1424fa8..8d005373508 100644 --- a/app/assets/javascripts/projects/pipelines/charts/components/app.vue +++ b/app/assets/javascripts/projects/pipelines/charts/components/app.vue @@ -3,8 +3,6 @@ import { GlTabs, GlTab } from '@gitlab/ui'; import { mergeUrlParams, updateHistory, getParameterValues } from '~/lib/utils/url_utility'; import PipelineCharts from './pipeline_charts.vue'; -const charts = ['pipelines', 'deployments']; - export default { components: { GlTabs, @@ -12,9 +10,11 @@ export default { PipelineCharts, DeploymentFrequencyCharts: () => import('ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue'), + LeadTimeCharts: () => + import('ee_component/projects/pipelines/charts/components/lead_time_charts.vue'), }, inject: { - shouldRenderDeploymentFrequencyCharts: { + shouldRenderDoraCharts: { type: Boolean, default: false, }, @@ -24,20 +24,31 @@ export default { selectedTab: 0, }; }, + computed: { + charts() { + const chartsToShow = ['pipelines']; + + if (this.shouldRenderDoraCharts) { + chartsToShow.push('deployments', 'lead-time'); + } + + return chartsToShow; + }, + }, created() { this.selectTab(); window.addEventListener('popstate', this.selectTab); }, methods: { selectTab() { - const [chart] = getParameterValues('chart') || charts; - const tab = charts.indexOf(chart); + const [chart] = getParameterValues('chart') || this.charts; + const tab = this.charts.indexOf(chart); this.selectedTab = tab >= 0 ? tab : 0; }, onTabChange(index) { if (index !== this.selectedTab) { this.selectedTab = index; - const path = mergeUrlParams({ chart: charts[index] }, window.location.pathname); + const path = mergeUrlParams({ chart: this.charts[index] }, window.location.pathname); updateHistory({ url: path, title: window.title }); } }, @@ -46,13 +57,18 @@ export default { </script> <template> <div> - <gl-tabs v-if="shouldRenderDeploymentFrequencyCharts" :value="selectedTab" @input="onTabChange"> + <gl-tabs v-if="charts.length > 1" :value="selectedTab" @input="onTabChange"> <gl-tab :title="__('Pipelines')"> <pipeline-charts /> </gl-tab> - <gl-tab :title="__('Deployments')"> - <deployment-frequency-charts /> - </gl-tab> + <template v-if="shouldRenderDoraCharts"> + <gl-tab :title="__('Deployments')"> + <deployment-frequency-charts /> + </gl-tab> + <gl-tab :title="__('Lead Time')"> + <lead-time-charts /> + </gl-tab> + </template> </gl-tabs> <pipeline-charts v-else /> </div> diff --git a/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue b/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue index 3590e2c4632..ad3e6713e45 100644 --- a/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue +++ b/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue @@ -30,12 +30,16 @@ export default { <resizable-chart-container> <gl-area-chart slot-scope="{ width }" + v-bind="$attrs" :width="width" :height="$options.chartContainerHeight" :data="chartData" :include-legend-avg-max="false" :option="areaChartOptions" - /> + > + <slot slot="tooltip-title" name="tooltip-title"></slot> + <slot slot="tooltip-content" name="tooltip-content"></slot> + </gl-area-chart> </resizable-chart-container> </div> </template> diff --git a/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_charts.vue b/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_charts.vue index 43b36da8b2c..f4fd57e4cdc 100644 --- a/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_charts.vue +++ b/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_charts.vue @@ -41,10 +41,14 @@ export default { <gl-segmented-control v-model="selectedChart" :options="chartRanges" class="gl-mb-4" /> <ci-cd-analytics-area-chart v-if="chart" + v-bind="$attrs" :chart-data="chart.data" :area-chart-options="chartOptions" > {{ dateRange }} + + <slot slot="tooltip-title" name="tooltip-title"></slot> + <slot slot="tooltip-content" name="tooltip-content"></slot> </ci-cd-analytics-area-chart> </div> </template> diff --git a/app/assets/javascripts/projects/pipelines/charts/index.js b/app/assets/javascripts/projects/pipelines/charts/index.js index 7e746423b6a..5f5ee44c204 100644 --- a/app/assets/javascripts/projects/pipelines/charts/index.js +++ b/app/assets/javascripts/projects/pipelines/charts/index.js @@ -13,9 +13,7 @@ const apolloProvider = new VueApollo({ const mountPipelineChartsApp = (el) => { const { projectPath } = el.dataset; - const shouldRenderDeploymentFrequencyCharts = parseBoolean( - el.dataset.shouldRenderDeploymentFrequencyCharts, - ); + const shouldRenderDoraCharts = parseBoolean(el.dataset.shouldRenderDoraCharts); return new Vue({ el, @@ -26,7 +24,7 @@ const mountPipelineChartsApp = (el) => { apolloProvider, provide: { projectPath, - shouldRenderDeploymentFrequencyCharts, + shouldRenderDoraCharts, }, render: (createElement) => createElement(ProjectPipelinesCharts, {}), }); |