diff options
Diffstat (limited to 'app/assets/javascripts/projects')
22 files changed, 578 insertions, 194 deletions
diff --git a/app/assets/javascripts/projects/commit/components/commit_options_dropdown.vue b/app/assets/javascripts/projects/commit/components/commit_options_dropdown.vue index a4edc988d67..7c00ce45b3a 100644 --- a/app/assets/javascripts/projects/commit/components/commit_options_dropdown.vue +++ b/app/assets/javascripts/projects/commit/components/commit_options_dropdown.vue @@ -1,14 +1,17 @@ <script> -import { GlDropdown, GlDropdownItem, GlDropdownDivider, GlDropdownSectionHeader } from '@gitlab/ui'; +import { GlDisclosureDropdownGroup, GlDisclosureDropdown } from '@gitlab/ui'; +import { s__, __ } from '~/locale'; import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '../constants'; import eventHub from '../event_hub'; export default { + i18n: { + gitlabTag: s__('CreateTag|Tag'), + }, + components: { - GlDropdown, - GlDropdownItem, - GlDropdownDivider, - GlDropdownSectionHeader, + GlDisclosureDropdown, + GlDisclosureDropdownGroup, }, inject: { newProjectTagPath: { @@ -43,66 +46,117 @@ export default { showDivider() { return this.canRevert || this.canCherryPick || this.canTag; }, + cherryPickItem() { + return { + text: s__('ChangeTypeAction|Cherry-pick'), + extraAttrs: { + 'data-testid': 'cherry-pick-link', + 'data-qa-selector': 'cherry_pick_button', + }, + action: () => this.showModal(OPEN_CHERRY_PICK_MODAL), + }; + }, + + revertLinkItem() { + return { + text: s__('ChangeTypeAction|Revert'), + extraAttrs: { + 'data-testid': 'revert-link', + 'data-qa-selector': 'revert_button', + }, + action: () => this.showModal(OPEN_REVERT_MODAL), + }; + }, + + tagLinkItem() { + return { + text: s__('CreateTag|Tag'), + href: this.newProjectTagPath, + extraAttrs: { + 'data-testid': 'tag-link', + }, + }; + }, + plainDiffItem() { + return { + text: s__('DownloadCommit|Plain Diff'), + href: this.plainDiffPath, + extraAttrs: { + download: '', + rel: 'nofollow', + 'data-testid': 'plain-diff-link', + 'data-qa-selector': 'plain_diff', + }, + }; + }, + patchesItem() { + return { + text: __('Patches'), + href: this.emailPatchesPath, + extraAttrs: { + download: '', + rel: 'nofollow', + 'data-testid': 'email-patches-link', + 'data-qa-selector': 'email_patches', + }, + }; + }, + + downloadsGroup() { + const items = []; + if (this.canEmailPatches) { + items.push(this.patchesItem); + } + items.push(this.plainDiffItem); + return { + name: __('Downloads'), + items, + }; + }, + + optionsGroup() { + const items = []; + if (this.canRevert) { + items.push(this.revertLinkItem); + } + if (this.canCherryPick) { + items.push(this.cherryPickItem); + } + if (this.canTag) { + items.push(this.tagLinkItem); + } + return { + items, + }; + }, }, + methods: { showModal(modalId) { eventHub.$emit(modalId); }, + closeDropdown() { + this.$refs.userDropdown.close(); + }, }, - openRevertModal: OPEN_REVERT_MODAL, - openCherryPickModal: OPEN_CHERRY_PICK_MODAL, }; </script> <template> - <gl-dropdown - :text="__('Options')" + <gl-disclosure-dropdown + ref="userDropdown" + :toggle-text="__('Options')" right data-testid="commit-options-dropdown" data-qa-selector="options_button" - class="gl-xs-w-full" + class="gl-xs-w-full gl-line-height-20" > - <gl-dropdown-item - v-if="canRevert" - data-testid="revert-link" - data-qa-selector="revert_button" - @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" - > - {{ __('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> + <gl-disclosure-dropdown-group :group="optionsGroup" @action="closeDropdown" /> + + <gl-disclosure-dropdown-group + :bordered="showDivider" + :group="downloadsGroup" + @action="closeDropdown" + /> + </gl-disclosure-dropdown> </template> diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue index 54d13ecc9c8..84e7edb48c1 100644 --- a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue +++ b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue @@ -7,10 +7,12 @@ import { toggleQueryPollingByVisibility, } from '~/pipelines/components/graph/utils'; import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils'; +import GraphqlPipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/graphql_pipeline_mini_graph.vue'; import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import getLinkedPipelinesQuery from '~/pipelines/graphql/queries/get_linked_pipelines.query.graphql'; +import getPipelineStagesQuery from '~/pipelines/graphql/queries/get_pipeline_stages.query.graphql'; import { formatStages } from '../utils'; -import getLinkedPipelinesQuery from '../graphql/queries/get_linked_pipelines.query.graphql'; -import getPipelineStagesQuery from '../graphql/queries/get_pipeline_stages.query.graphql'; import { COMMIT_BOX_POLL_INTERVAL } from '../constants'; export default { @@ -21,8 +23,10 @@ export default { }, components: { GlLoadingIcon, + GraphqlPipelineMiniGraph, PipelineMiniGraph, }, + mixins: [glFeatureFlagsMixin()], inject: { fullPath: { default: '', @@ -47,15 +51,15 @@ export default { }, query: getLinkedPipelinesQuery, pollInterval: COMMIT_BOX_POLL_INTERVAL, + skip() { + return !this.fullPath || !this.iid || this.isUsingPipelineMiniGraphQueries; + }, variables() { return { fullPath: this.fullPath, iid: this.iid, }; }, - skip() { - return !this.fullPath || !this.iid; - }, update({ project }) { return project?.pipeline; }, @@ -69,6 +73,9 @@ export default { }, query: getPipelineStagesQuery, pollInterval: COMMIT_BOX_POLL_INTERVAL, + skip() { + return this.isUsingPipelineMiniGraphQueries; + }, variables() { return { fullPath: this.fullPath, @@ -95,6 +102,9 @@ export default { const downstream = this.pipeline?.downstream?.nodes; return keepLatestDownstreamPipelines(downstream); }, + isUsingPipelineMiniGraphQueries() { + return this.glFeatures.ciGraphqlPipelineMiniGraph; + }, pipelinePath() { return this.pipeline?.path ?? ''; }, @@ -128,13 +138,22 @@ export default { <template> <div> <gl-loading-icon v-if="$apollo.queries.pipeline.loading" /> - <pipeline-mini-graph - v-else - data-testid="commit-box-pipeline-mini-graph" - :downstream-pipelines="downstreamPipelines" - :pipeline-path="pipelinePath" - :stages="formattedStages" - :upstream-pipeline="upstreamPipeline" - /> + <template v-else> + <graphql-pipeline-mini-graph + v-if="isUsingPipelineMiniGraphQueries" + data-testid="commit-box-pipeline-mini-graph" + :pipeline-etag="graphqlResourceEtag" + :full-path="fullPath" + :iid="iid" + /> + <pipeline-mini-graph + v-else + data-testid="commit-box-pipeline-mini-graph" + :downstream-pipelines="downstreamPipelines" + :pipeline-path="pipelinePath" + :stages="formattedStages" + :upstream-pipeline="upstreamPipeline" + /> + </template> </div> </template> diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_refs.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_refs.vue new file mode 100644 index 00000000000..25af4cc8082 --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/components/commit_refs.vue @@ -0,0 +1,134 @@ +<script> +import { createAlert } from '~/alert'; +import { joinPaths } from '~/lib/utils/url_utility'; +import commitReferencesQuery from '../graphql/queries/commit_references.query.graphql'; +import containingBranchesQuery from '../graphql/queries/commit_containing_branches.query.graphql'; +import containingTagsQuery from '../graphql/queries/commit_containing_tags.query.graphql'; +import { + BRANCHES, + TAGS, + FETCH_CONTAINING_REFS_EVENT, + FETCH_COMMIT_REFERENCES_ERROR, + BRANCHES_REF_TYPE, + TAGS_REF_TYPE, +} from '../constants'; +import RefsList from './refs_list.vue'; + +export default { + name: 'CommitRefs', + components: { + RefsList, + }, + inject: ['fullPath', 'commitSha'], + apollo: { + project: { + query: commitReferencesQuery, + variables() { + return this.queryVariables; + }, + update({ + project: { + commitReferences: { tippingTags, tippingBranches, containingBranches, containingTags }, + }, + }) { + this.tippingTags = tippingTags.names; + this.tippingBranches = tippingBranches.names; + this.hasContainingBranches = Boolean(containingBranches.names.length); + this.hasContainingTags = Boolean(containingTags.names.length); + }, + error() { + createAlert({ + message: this.$options.i18n.errorMessage, + captureError: true, + }); + }, + }, + }, + data() { + return { + containingTags: [], + containingBranches: [], + tippingTags: [], + tippingBranches: [], + hasContainingBranches: false, + hasContainingTags: false, + }; + }, + computed: { + hasBranches() { + return this.tippingBranches.length || this.hasContainingBranches; + }, + hasTags() { + return this.tippingTags.length || this.hasContainingTags; + }, + queryVariables() { + return { + fullPath: this.fullPath, + commitSha: this.commitSha, + }; + }, + commitsUrlPart() { + const urlPart = joinPaths(gon.relative_url_root || '', `/${this.fullPath}`, `/-/commits/`); + return urlPart; + }, + }, + methods: { + async fetchContainingRefs({ query, namespace }) { + try { + const { data } = await this.$apollo.query({ + query, + variables: this.queryVariables, + }); + this[namespace] = data.project.commitReferences[namespace].names; + return data.project.commitReferences[namespace].names; + } catch { + return createAlert({ + message: this.$options.i18n.errorMessage, + captureError: true, + }); + } + }, + fetchContainingBranches() { + this.fetchContainingRefs({ query: containingBranchesQuery, namespace: 'containingBranches' }); + }, + fetchContainingTags() { + this.fetchContainingRefs({ query: containingTagsQuery, namespace: 'containingTags' }); + }, + }, + i18n: { + branches: BRANCHES, + tags: TAGS, + errorMessage: FETCH_COMMIT_REFERENCES_ERROR, + }, + FETCH_CONTAINING_REFS_EVENT, + BRANCHES_REF_TYPE, + TAGS_REF_TYPE, +}; +</script> + +<template> + <div class="gl-ml-7"> + <refs-list + v-if="hasBranches" + :has-containing-refs="hasContainingBranches" + :is-loading="$apollo.queries.project.loading" + :tipping-refs="tippingBranches" + :containing-refs="containingBranches" + :namespace="$options.i18n.branches" + :url-part="commitsUrlPart" + :ref-type="$options.BRANCHES_REF_TYPE" + @[$options.FETCH_CONTAINING_REFS_EVENT]="fetchContainingBranches" + /> + <refs-list + v-if="hasTags" + :has-containing-refs="hasContainingTags" + :is-loading="$apollo.queries.project.loading" + :tipping-refs="tippingTags" + :containing-refs="containingTags" + :namespace="$options.i18n.tags" + :url-part="commitsUrlPart" + :ref-type="$options.TAGS_REF_TYPE" + @[$options.FETCH_CONTAINING_REFS_EVENT]="fetchContainingTags" + /> + </div> +</template> diff --git a/app/assets/javascripts/projects/commit_box/info/components/refs_list.vue b/app/assets/javascripts/projects/commit_box/info/components/refs_list.vue new file mode 100644 index 00000000000..8ceab9cb60b --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/components/refs_list.vue @@ -0,0 +1,112 @@ +<script> +import { GlCollapse, GlBadge, GlButton, GlIcon, GlSkeletonLoader } from '@gitlab/ui'; +import { CONTAINING_COMMIT, FETCH_CONTAINING_REFS_EVENT } from '../constants'; + +export default { + name: 'RefsList', + components: { + GlCollapse, + GlSkeletonLoader, + GlBadge, + GlButton, + GlIcon, + }, + props: { + urlPart: { + type: String, + required: true, + }, + refType: { + type: String, + required: true, + }, + containingRefs: { + type: Array, + required: false, + default: () => [], + }, + tippingRefs: { + type: Array, + required: false, + default: () => [], + }, + namespace: { + type: String, + required: true, + }, + hasContainingRefs: { + type: Boolean, + required: true, + }, + isLoading: { + type: Boolean, + required: true, + }, + }, + data() { + return { + isContainingRefsVisible: false, + }; + }, + computed: { + collapseIcon() { + return this.isContainingRefsVisible ? 'chevron-down' : 'chevron-right'; + }, + isLoadingRefs() { + return this.isLoading && !this.containingRefs.length; + }, + }, + methods: { + toggleCollapse() { + this.isContainingRefsVisible = !this.isContainingRefsVisible; + }, + showRefs() { + this.toggleCollapse(); + this.$emit(FETCH_CONTAINING_REFS_EVENT); + }, + getRefUrl(ref) { + return `${this.urlPart}${ref}?ref_type=${this.refType}`; + }, + }, + i18n: { + containingCommit: CONTAINING_COMMIT, + }, +}; +</script> + +<template> + <div class="gl-pt-4"> + <span data-testid="title" class="gl-mr-2">{{ namespace }}</span> + <gl-badge + v-for="ref in tippingRefs" + :key="ref" + :href="getRefUrl(ref)" + class="gl-mt-2 gl-mr-2" + size="sm" + >{{ ref }}</gl-badge + > + <gl-button + v-if="hasContainingRefs" + class="gl-mr-2 gl-font-sm!" + variant="link" + size="small" + @click="showRefs" + > + <gl-icon :name="collapseIcon" :size="14" /> + {{ namespace }} {{ $options.i18n.containingCommit }} + </gl-button> + <gl-collapse :visible="isContainingRefsVisible"> + <gl-skeleton-loader v-if="isLoadingRefs" :lines="1" /> + <template v-else> + <gl-badge + v-for="ref in containingRefs" + :key="ref" + :href="getRefUrl(ref)" + class="gl-mt-3 gl-mr-2" + size="sm" + >{{ ref }}</gl-badge + > + </template> + </gl-collapse> + </div> +</template> diff --git a/app/assets/javascripts/projects/commit_box/info/constants.js b/app/assets/javascripts/projects/commit_box/info/constants.js index be0bf715314..4b74fbe19e1 100644 --- a/app/assets/javascripts/projects/commit_box/info/constants.js +++ b/app/assets/javascripts/projects/commit_box/info/constants.js @@ -1,7 +1,23 @@ -import { __ } from '~/locale'; +import { __, s__ } from '~/locale'; export const COMMIT_BOX_POLL_INTERVAL = 10000; export const PIPELINE_STATUS_FETCH_ERROR = __( 'There was a problem fetching the latest pipeline status.', ); + +export const BRANCHES = s__('Commit|Branches'); + +export const TAGS = s__('Commit|Tags'); + +export const CONTAINING_COMMIT = s__('Commit|containing commit'); + +export const FETCH_CONTAINING_REFS_EVENT = 'fetch-containing-refs'; + +export const FETCH_COMMIT_REFERENCES_ERROR = s__( + 'Commit|There was an error fetching the commit references. Please try again later.', +); + +export const BRANCHES_REF_TYPE = 'heads'; + +export const TAGS_REF_TYPE = 'tags'; diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/commit_containing_branches.query.graphql b/app/assets/javascripts/projects/commit_box/info/graphql/queries/commit_containing_branches.query.graphql new file mode 100644 index 00000000000..ea74efdbc46 --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/graphql/queries/commit_containing_branches.query.graphql @@ -0,0 +1,10 @@ +query CommitContainingBranches($fullPath: ID!, $commitSha: String!) { + project(fullPath: $fullPath) { + id + commitReferences(commitSha: $commitSha) { + containingBranches(excludeTipped: true) { + names + } + } + } +} diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/commit_containing_tags.query.graphql b/app/assets/javascripts/projects/commit_box/info/graphql/queries/commit_containing_tags.query.graphql new file mode 100644 index 00000000000..d736dc3ab66 --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/graphql/queries/commit_containing_tags.query.graphql @@ -0,0 +1,10 @@ +query CommitContainingTags($fullPath: ID!, $commitSha: String!) { + project(fullPath: $fullPath) { + id + commitReferences(commitSha: $commitSha) { + containingTags(excludeTipped: true) { + names + } + } + } +} diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/commit_references.query.graphql b/app/assets/javascripts/projects/commit_box/info/graphql/queries/commit_references.query.graphql new file mode 100644 index 00000000000..71d911c2acc --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/graphql/queries/commit_references.query.graphql @@ -0,0 +1,19 @@ +query CommitReferences($fullPath: ID!, $commitSha: String!) { + project(fullPath: $fullPath) { + id + commitReferences(commitSha: $commitSha) { + containingBranches(excludeTipped: true, limit: 1) { + names + } + containingTags(excludeTipped: true, limit: 1) { + names + } + tippingBranches { + names + } + tippingTags { + names + } + } + } +} diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql deleted file mode 100644 index 9257cc7de7b..00000000000 --- a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql +++ /dev/null @@ -1,43 +0,0 @@ -query getLinkedPipelines($fullPath: ID!, $iid: ID!) { - project(fullPath: $fullPath) { - id - pipeline(iid: $iid) { - id - path - downstream { - nodes { - id - path - project { - id - name - } - detailedStatus { - id - group - icon - label - } - sourceJob { - id - retried - } - } - } - upstream { - id - path - project { - id - name - } - detailedStatus { - id - group - icon - label - } - } - } - } -} diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql deleted file mode 100644 index 69a29947b16..00000000000 --- a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql +++ /dev/null @@ -1,19 +0,0 @@ -query getPipelineStages($fullPath: ID!, $iid: ID!) { - project(fullPath: $fullPath) { - id - pipeline(iid: $iid) { - id - stages { - nodes { - id - name - detailedStatus { - id - icon - group - } - } - } - } - } -} diff --git a/app/assets/javascripts/projects/commit_box/info/index.js b/app/assets/javascripts/projects/commit_box/info/index.js index 7c4b76fd62f..8f09c8e1e11 100644 --- a/app/assets/javascripts/projects/commit_box/info/index.js +++ b/app/assets/javascripts/projects/commit_box/info/index.js @@ -1,12 +1,10 @@ import { fetchCommitMergeRequests } from '~/commit_merge_requests'; import { initCommitPipelineMiniGraph } from './init_commit_pipeline_mini_graph'; -import { loadBranches } from './load_branches'; import initCommitPipelineStatus from './init_commit_pipeline_status'; +import initCommitReferences from './init_commit_references'; export const initCommitBoxInfo = () => { // Display commit related branches - loadBranches(); - // Related merge requests to this commit fetchCommitMergeRequests(); @@ -14,4 +12,6 @@ export const initCommitBoxInfo = () => { initCommitPipelineMiniGraph(); initCommitPipelineStatus(); + + initCommitReferences(); }; diff --git a/app/assets/javascripts/projects/commit_box/info/init_commit_references.js b/app/assets/javascripts/projects/commit_box/info/init_commit_references.js new file mode 100644 index 00000000000..c8497187211 --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/init_commit_references.js @@ -0,0 +1,32 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import CommitBranches from './components/commit_refs.vue'; + +Vue.use(VueApollo); + +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), +}); + +export default (selector = 'js-commit-branches-and-tags') => { + const el = document.getElementById(selector); + + if (!el) { + return false; + } + + const { fullPath, commitSha } = el.dataset; + + return new Vue({ + el, + apolloProvider, + provide: { + fullPath, + commitSha, + }, + render(createElement) { + return createElement(CommitBranches); + }, + }); +}; diff --git a/app/assets/javascripts/projects/commit_box/info/load_branches.js b/app/assets/javascripts/projects/commit_box/info/load_branches.js deleted file mode 100644 index 8333e70b951..00000000000 --- a/app/assets/javascripts/projects/commit_box/info/load_branches.js +++ /dev/null @@ -1,24 +0,0 @@ -import axios from 'axios'; -import { sanitize } from '~/lib/dompurify'; -import { __ } from '~/locale'; -import { initDetailsButton } from './init_details_button'; - -export const loadBranches = (containerSelector = '.js-commit-box-info') => { - const containerEl = document.querySelector(containerSelector); - if (!containerEl) { - return; - } - - const { commitPath } = containerEl.dataset; - const branchesEl = containerEl.querySelector('.commit-info.branches'); - axios - .get(commitPath) - .then(({ data }) => { - branchesEl.innerHTML = sanitize(data); - - initDetailsButton(); - }) - .catch(() => { - branchesEl.textContent = __('Failed to load branches. Please try again.'); - }); -}; diff --git a/app/assets/javascripts/projects/compare/components/repo_dropdown.vue b/app/assets/javascripts/projects/compare/components/repo_dropdown.vue index c00e75db722..4c0b5d0b1f6 100644 --- a/app/assets/javascripts/projects/compare/components/repo_dropdown.vue +++ b/app/assets/javascripts/projects/compare/components/repo_dropdown.vue @@ -1,11 +1,9 @@ <script> -import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui'; +import { GlCollapsibleListbox } from '@gitlab/ui'; export default { components: { - GlDropdown, - GlDropdownItem, - GlSearchBoxByType, + GlCollapsibleListbox, }, props: { paramsName: { @@ -25,6 +23,7 @@ export default { data() { return { searchTerm: '', + selectedProjectId: this.selectedProject.id, }; }, computed: { @@ -32,49 +31,45 @@ export default { return this.projects === null; }, filteredRepos() { - const lowerCaseSearchTerm = this.searchTerm.toLowerCase(); + if (this.disableRepoDropdown) return []; - return this?.projects.filter(({ name }) => name.toLowerCase().includes(lowerCaseSearchTerm)); + const lowerCaseSearchTerm = this.searchTerm.toLowerCase(); + return this.projects + .filter(({ name }) => name.toLowerCase().includes(lowerCaseSearchTerm)) + .map((project) => ({ text: project.name, value: project.id })); }, inputName() { return `${this.paramsName}_project_id`; }, }, methods: { - onClick(project) { - this.emitTargetProject(project); - }, - emitTargetProject(project) { + emitTargetProject(projectId) { + if (this.disableRepoDropdown) return; + const project = this.projects.find(({ id }) => id === projectId); this.$emit('selectProject', { direction: this.paramsName, project }); }, + onSearch(searchTerm) { + this.searchTerm = searchTerm; + }, }, }; </script> <template> <div> - <input type="hidden" :name="inputName" :value="selectedProject.id" /> - <gl-dropdown - :text="selectedProject.name" + <input type="hidden" :name="inputName" :value="selectedProjectId" /> + <gl-collapsible-listbox + v-model="selectedProjectId" + :toggle-text="selectedProject.name" :header-text="s__(`CompareRevisions|Select target project`)" - class="gl-w-full gl-font-monospace" + class="gl-font-monospace" toggle-class="gl-min-w-0" :disabled="disableRepoDropdown" - > - <template #header> - <gl-search-box-by-type v-if="!disableRepoDropdown" v-model.trim="searchTerm" /> - </template> - <template v-if="!disableRepoDropdown"> - <gl-dropdown-item - v-for="repo in filteredRepos" - :key="repo.id" - is-check-item - :is-checked="selectedProject.id === repo.id" - @click="onClick(repo)" - > - {{ repo.name }} - </gl-dropdown-item> - </template> - </gl-dropdown> + :items="filteredRepos" + block + searchable + @select="emitTargetProject" + @search="onSearch" + /> </div> </template> diff --git a/app/assets/javascripts/projects/new/components/app.vue b/app/assets/javascripts/projects/new/components/app.vue index 2f58d4468be..6ca83b0b500 100644 --- a/app/assets/javascripts/projects/new/components/app.vue +++ b/app/assets/javascripts/projects/new/components/app.vue @@ -1,8 +1,8 @@ <script> -import createFromTemplateIllustration from '@gitlab/svgs/dist/illustrations/project-create-from-template-sm.svg'; -import blankProjectIllustration from '@gitlab/svgs/dist/illustrations/project-create-new-sm.svg'; -import importProjectIllustration from '@gitlab/svgs/dist/illustrations/project-import-sm.svg'; -import ciCdProjectIllustration from '@gitlab/svgs/dist/illustrations/project-run-CICD-pipelines-sm.svg'; +import createFromTemplateIllustration from '@gitlab/svgs/dist/illustrations/project-create-from-template-sm.svg?raw'; +import blankProjectIllustration from '@gitlab/svgs/dist/illustrations/project-create-new-sm.svg?raw'; +import importProjectIllustration from '@gitlab/svgs/dist/illustrations/project-import-sm.svg?raw'; +import ciCdProjectIllustration from '@gitlab/svgs/dist/illustrations/project-run-CICD-pipelines-sm.svg?raw'; import SafeHtml from '~/vue_shared/directives/safe_html'; import { s__ } from '~/locale'; import NewNamespacePage from '~/vue_shared/new_namespace/new_namespace_page.vue'; diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index 99ea02aaa4f..33320f59b0f 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -295,7 +295,7 @@ const bindEvents = () => { }); $newProjectForm.on('submit', () => { - $projectPath.val($projectPath.val().trim()); + $projectPath.value = $projectPath.value.trim(); }); const updateUrlPathWarningVisibility = async () => { diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/edit/branch_dropdown.vue b/app/assets/javascripts/projects/settings/branch_rules/components/edit/branch_dropdown.vue index 3dcacf9eb34..6494456d560 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/edit/branch_dropdown.vue +++ b/app/assets/javascripts/projects/settings/branch_rules/components/edit/branch_dropdown.vue @@ -55,7 +55,7 @@ export default { }, searchInputDelay: 250, wildcardsHelpPath: helpPagePath('user/project/protected_branches', { - anchor: 'configure-multiple-protected-branches-by-using-a-wildcard', + anchor: 'protect-multiple-branches-with-wildcard-rules', }), props: { projectPath: { diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js b/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js index b71c33d2b91..a45ed5c68af 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js @@ -49,7 +49,7 @@ export const BRANCH_PARAM_NAME = 'branch'; export const ALL_BRANCHES_WILDCARD = '*'; export const WILDCARDS_HELP_PATH = - 'user/project/protected_branches#configure-multiple-protected-branches-by-using-a-wildcard'; + 'user/project/protected_branches#protect-multiple-branches-with-wildcard-rules'; export const PROTECTED_BRANCHES_HELP_PATH = 'user/project/protected_branches'; diff --git a/app/assets/javascripts/projects/settings/components/access_dropdown.vue b/app/assets/javascripts/projects/settings/components/access_dropdown.vue index 08a1c586f69..a2e4827cbfa 100644 --- a/app/assets/javascripts/projects/settings/components/access_dropdown.vue +++ b/app/assets/javascripts/projects/settings/components/access_dropdown.vue @@ -63,6 +63,11 @@ export default { required: false, default: () => [], }, + items: { + type: Array, + required: false, + default: () => [], + }, }, data() { return { @@ -143,11 +148,37 @@ export default { query: debounce(function debouncedSearch() { return this.getData(); }, 500), + items(items) { + this.setDataForSave(items); + }, }, created() { this.getData({ initial: true }); }, methods: { + setDataForSave(items) { + this.selected = items.reduce( + (selected, item) => { + if (item.group_id) { + selected[LEVEL_TYPES.GROUP].push({ id: item.group_id, ...item }); + } else if (item.user_id) { + selected[LEVEL_TYPES.USER].push({ id: item.user_id, ...item }); + } else if (item.access_level) { + const level = this.accessLevelsData.find(({ id }) => item.access_level === id); + selected[LEVEL_TYPES.ROLE].push(level); + } else if (item.deploy_key_id) { + selected[LEVEL_TYPES.DEPLOY_KEY].push({ id: item.deploy_key_id, ...item }); + } + return selected; + }, + { + [LEVEL_TYPES.GROUP]: [], + [LEVEL_TYPES.USER]: [], + [LEVEL_TYPES.ROLE]: [], + [LEVEL_TYPES.DEPLOY_KEY]: [], + }, + ); + }, focusInput() { this.$refs.search.focusInput(); }, diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue index 79ece99e6ec..650b60cba4f 100644 --- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue +++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue @@ -23,6 +23,9 @@ export default { initialIsEnabled: { default: false, }, + isIssueTrackerEnabled: { + default: false, + }, endpoint: { default: '', }, @@ -163,6 +166,7 @@ export default { </gl-alert> <service-desk-setting :is-enabled="isEnabled" + :is-issue-tracker-enabled="isIssueTrackerEnabled" :incoming-email="incomingEmail" :custom-email="updatedCustomEmail" :custom-email-enabled="customEmailEnabled" diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue index 5a3930b5df4..38a2c12d137 100644 --- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue +++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue @@ -8,6 +8,7 @@ import { GlFormGroup, GlFormInput, GlLink, + GlAlert, } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; import { __ } from '~/locale'; @@ -17,6 +18,9 @@ import ServiceDeskTemplateDropdown from './service_desk_template_dropdown.vue'; export default { i18n: { toggleLabel: __('Activate Service Desk'), + issueTrackerEnableMessage: __( + 'To use Service Desk in this project, you must %{linkStart}activate the issue tracker%{linkEnd}.', + ), }, components: { ClipboardButton, @@ -28,6 +32,7 @@ export default { GlFormGroup, GlFormInputGroup, GlLink, + GlAlert, ServiceDeskTemplateDropdown, }, props: { @@ -35,6 +40,10 @@ export default { type: Boolean, required: true, }, + isIssueTrackerEnabled: { + type: Boolean, + required: true, + }, incomingEmail: { type: String, required: false, @@ -110,6 +119,11 @@ export default { anchor: 'use-a-custom-email-address', }); }, + issuesHelpPagePath() { + return helpPagePath('user/project/settings/index.md', { + anchor: 'configure-project-visibility-features-and-permissions', + }); + }, }, methods: { onCheckboxToggle(isChecked) { @@ -141,9 +155,24 @@ export default { <template> <div> + <gl-alert v-if="!isIssueTrackerEnabled" class="mb-3" variant="info" :dismissible="false"> + <gl-sprintf :message="$options.i18n.issueTrackerEnableMessage"> + <template #link="{ content }"> + <gl-link + class="gl-display-inline-block" + data-testid="issue-help-page" + :href="issuesHelpPagePath" + target="_blank" + > + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </gl-alert> <gl-toggle id="service-desk-checkbox" :value="isEnabled" + :disabled="!isIssueTrackerEnabled" class="d-inline-block align-middle mr-1" :label="$options.i18n.toggleLabel" label-position="hidden" @@ -194,6 +223,7 @@ export default { :label="__('Email address suffix')" :state="!projectKeyError" data-testid="suffix-form-group" + :disabled="!isIssueTrackerEnabled" > <gl-form-input v-if="hasProjectKeySupport" @@ -249,6 +279,7 @@ export default { :label="__('Template to append to all Service Desk issues')" :state="!projectKeyError" class="mt-3" + :disabled="!isIssueTrackerEnabled" > <service-desk-template-dropdown :selected-template="selectedTemplate" @@ -268,6 +299,7 @@ export default { id="service-desk-email-from-name" v-model.trim="outgoingName" data-testid="email-from-name" + :disabled="!isIssueTrackerEnabled" /> <template #description> @@ -280,7 +312,7 @@ export default { class="gl-mt-5" data-testid="save_service_desk_settings_button" data-qa-selector="save_service_desk_settings_button" - :disabled="isTemplateSaving" + :disabled="isTemplateSaving || !isIssueTrackerEnabled" @click="onSaveTemplate" > {{ __('Save changes') }} diff --git a/app/assets/javascripts/projects/settings_service_desk/index.js b/app/assets/javascripts/projects/settings_service_desk/index.js index 26435a5fac9..84229175c0b 100644 --- a/app/assets/javascripts/projects/settings_service_desk/index.js +++ b/app/assets/javascripts/projects/settings_service_desk/index.js @@ -13,6 +13,7 @@ export default () => { customEmail, customEmailEnabled, enabled, + issueTrackerEnabled, endpoint, incomingEmail, outgoingName, @@ -31,6 +32,7 @@ export default () => { endpoint, initialIncomingEmail: incomingEmail, initialIsEnabled: parseBoolean(enabled), + isIssueTrackerEnabled: parseBoolean(issueTrackerEnabled), outgoingName, projectKey, selectedTemplate, |