diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 19:05:49 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 19:05:49 +0300 |
commit | 43a25d93ebdabea52f99b05e15b06250cd8f07d7 (patch) | |
tree | dceebdc68925362117480a5d672bcff122fb625b /app/assets/javascripts/projects | |
parent | 20c84b99005abd1c82101dfeff264ac50d2df211 (diff) |
Add latest changes from gitlab-org/gitlab@16-0-stable-eev16.0.0-rc42
Diffstat (limited to 'app/assets/javascripts/projects')
40 files changed, 430 insertions, 161 deletions
diff --git a/app/assets/javascripts/projects/commit/components/branches_dropdown.vue b/app/assets/javascripts/projects/commit/components/branches_dropdown.vue index 0ed154c47dd..77e809e88ce 100644 --- a/app/assets/javascripts/projects/commit/components/branches_dropdown.vue +++ b/app/assets/javascripts/projects/commit/components/branches_dropdown.vue @@ -1,7 +1,7 @@ <script> import { GlCollapsibleListbox } from '@gitlab/ui'; import { mapActions, mapGetters, mapState } from 'vuex'; -import { debounce } from 'lodash'; +import { debounce, uniqBy } from 'lodash'; import { I18N_NO_RESULTS_MESSAGE, I18N_BRANCH_HEADER, @@ -19,11 +19,6 @@ export default { required: false, default: '', }, - blanked: { - type: Boolean, - required: false, - default: false, - }, }, i18n: { noResultsMessage: I18N_NO_RESULTS_MESSAGE, @@ -32,26 +27,26 @@ export default { }, data() { return { - searchTerm: this.blanked ? '' : this.value, + searchTerm: '', }; }, computed: { ...mapGetters(['joinedBranches']), - ...mapState(['isFetching']), + ...mapState(['isFetching', 'branch']), listboxItems() { - return this.joinedBranches.map((value) => ({ value, text: value })); - }, - }, - watch: { - // Parent component can set the branch value (e.g. when the user selects a different project) - // and we need to keep the search term in sync with the selected value - value(val) { - this.searchTerm = val; - this.fetchBranches(this.searchTerm); + const selectedItem = { value: this.branch, text: this.branch }; + const transformedList = this.joinedBranches.map((value) => ({ value, text: value })); + + if (this.searchTerm) { + return transformedList; + } + + // Add selected item to top of list if not searching + return uniqBy([selectedItem].concat(transformedList), 'value'); }, }, mounted() { - this.fetchBranches(this.searchTerm); + this.fetchBranches(); }, methods: { ...mapActions(['fetchBranches']), @@ -70,8 +65,10 @@ export default { </script> <template> <gl-collapsible-listbox + class="gl-max-w-full" :header-text="$options.i18n.branchHeaderTitle" :toggle-text="value" + toggle-class="gl-w-full" :items="listboxItems" searchable :search-placeholder="$options.i18n.branchSearchPlaceholder" 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 0fd31381ba6..a4edc988d67 100644 --- a/app/assets/javascripts/projects/commit/components/commit_options_dropdown.vue +++ b/app/assets/javascripts/projects/commit/components/commit_options_dropdown.vue @@ -93,7 +93,7 @@ export default { data-testid="email-patches-link" data-qa-selector="email_patches" > - {{ s__('DownloadCommit|Email Patches') }} + {{ __('Patches') }} </gl-dropdown-item> <gl-dropdown-item :href="plainDiffPath" diff --git a/app/assets/javascripts/projects/commit/components/form_modal.vue b/app/assets/javascripts/projects/commit/components/form_modal.vue index f78afef1c17..28bbf67c090 100644 --- a/app/assets/javascripts/projects/commit/components/form_modal.vue +++ b/app/assets/javascripts/projects/commit/components/form_modal.vue @@ -41,11 +41,6 @@ export default { required: false, default: false, }, - isRevert: { - type: Boolean, - required: false, - default: false, - }, primaryActionEventName: { type: String, required: false, @@ -57,16 +52,16 @@ export default { checked: true, actionPrimary: { text: this.i18n.actionPrimaryText, - attributes: [ - { variant: 'confirm' }, - { category: 'primary' }, - { 'data-testid': 'submit-commit' }, - { 'data-qa-selector': 'submit_commit_button' }, - ], + attributes: { + variant: 'confirm', + category: 'primary', + 'data-testid': 'submit-commit', + 'data-qa-selector': 'submit_commit_button', + }, }, actionCancel: { text: this.i18n.actionCancelText, - attributes: [{ 'data-testid': 'cancel-commit' }], + attributes: { 'data-testid': 'cancel-commit' }, }, }; }, @@ -85,7 +80,6 @@ export default { ]), }, mounted() { - this.setSelectedProject(this.targetProjectId); eventHub.$on(this.openModal, this.show); }, methods: { @@ -141,7 +135,7 @@ export default { :value="targetProjectId" /> - <projects-dropdown :value="targetProjectName" @selectProject="setSelectedProject" /> + <projects-dropdown :value="targetProjectName" @input="setSelectedProject" /> </gl-form-group> <gl-form-group @@ -151,7 +145,7 @@ export default { > <input id="start_branch" type="hidden" name="start_branch" :value="branch" /> - <branches-dropdown :value="branch" :blanked="isRevert" @input="setBranch" /> + <branches-dropdown :value="branch" @input="setBranch" /> </gl-form-group> <gl-form-checkbox diff --git a/app/assets/javascripts/projects/commit/components/projects_dropdown.vue b/app/assets/javascripts/projects/commit/components/projects_dropdown.vue index d43f5b99e2c..fe54b62e2c8 100644 --- a/app/assets/javascripts/projects/commit/components/projects_dropdown.vue +++ b/app/assets/javascripts/projects/commit/components/projects_dropdown.vue @@ -1,6 +1,7 @@ <script> import { GlCollapsibleListbox } from '@gitlab/ui'; import { mapGetters, mapState } from 'vuex'; +import { debounce, uniqBy } from 'lodash'; import { I18N_NO_RESULTS_MESSAGE, I18N_PROJECT_HEADER, @@ -26,7 +27,7 @@ export default { }, data() { return { - filterTerm: this.value, + filterTerm: '', }; }, computed: { @@ -39,7 +40,18 @@ export default { ); }, listboxItems() { - return this.filteredResults.map(({ id, name }) => ({ value: id, text: name })); + const selectedItem = { value: this.selectedProject.id, text: this.selectedProject.name }; + const transformedList = this.filteredResults.map(({ id, name }) => ({ + value: id, + text: name, + })); + + if (this.filterTerm) { + return transformedList; + } + + // Add selected item to top of list if not searching + return uniqBy([selectedItem].concat(transformedList), 'value'); }, selectedProject() { return this.sortedProjects.find((project) => project.id === this.targetProjectId) || {}; @@ -47,28 +59,26 @@ export default { }, methods: { selectProject(value) { - this.$emit('selectProject', value); - - // when we select a project, we want the dropdown to filter to the selected project - const project = this.listboxItems.find((x) => x.value === value); - this.filterTerm = project?.text || ''; - }, - filterTermChanged(value) { - this.filterTerm = value; + this.$emit('input', value); }, + debouncedSearch: debounce(function debouncedSearch(value) { + this.filterTerm = value.trim(); + }, 250), }, }; </script> <template> <gl-collapsible-listbox + class="gl-max-w-full" :header-text="$options.i18n.projectHeaderTitle" :items="listboxItems" searchable :search-placeholder="$options.i18n.projectSearchPlaceholder" :selected="selectedProject.id" :toggle-text="selectedProject.name" + toggle-class="gl-w-full" :no-results-text="$options.i18n.noResultsMessage" - @search="filterTermChanged" + @search="debouncedSearch" @select="selectProject" /> </template> diff --git a/app/assets/javascripts/projects/commit/init_revert_commit_modal.js b/app/assets/javascripts/projects/commit/init_revert_commit_modal.js index 41be71932e5..849b2f4858c 100644 --- a/app/assets/javascripts/projects/commit/init_revert_commit_modal.js +++ b/app/assets/javascripts/projects/commit/init_revert_commit_modal.js @@ -49,7 +49,6 @@ export default function initInviteMembersModal(primaryActionEventName) { i18n: { ...I18N_REVERT_MODAL, ...I18N_MODAL }, openModal: OPEN_REVERT_MODAL, modalId: REVERT_MODAL_ID, - isRevert: true, primaryActionEventName, }, }), diff --git a/app/assets/javascripts/projects/commit/store/actions.js b/app/assets/javascripts/projects/commit/store/actions.js index cfff93eac5a..501006a8be5 100644 --- a/app/assets/javascripts/projects/commit/store/actions.js +++ b/app/assets/javascripts/projects/commit/store/actions.js @@ -1,4 +1,4 @@ -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { PROJECT_BRANCHES_ERROR } from '../constants'; import * as types from './mutation_types'; 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 dafc4bc5abf..54d13ecc9c8 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 @@ -1,6 +1,6 @@ <script> import { GlLoadingIcon } from '@gitlab/ui'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { __ } from '~/locale'; import { getQueryHeaders, diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_status.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_status.vue index 62b1209131c..71f53613a3b 100644 --- a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_status.vue +++ b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_status.vue @@ -1,7 +1,7 @@ <script> import { GlLoadingIcon, GlLink } from '@gitlab/ui'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { getQueryHeaders, toggleQueryPollingByVisibility, diff --git a/app/assets/javascripts/projects/commit_box/info/index.js b/app/assets/javascripts/projects/commit_box/info/index.js index 7500c152b6a..7c4b76fd62f 100644 --- a/app/assets/javascripts/projects/commit_box/info/index.js +++ b/app/assets/javascripts/projects/commit_box/info/index.js @@ -1,6 +1,5 @@ import { fetchCommitMergeRequests } from '~/commit_merge_requests'; import { initCommitPipelineMiniGraph } from './init_commit_pipeline_mini_graph'; -import { initDetailsButton } from './init_details_button'; import { loadBranches } from './load_branches'; import initCommitPipelineStatus from './init_commit_pipeline_status'; @@ -14,7 +13,5 @@ export const initCommitBoxInfo = () => { // Display pipeline mini graph for this commit initCommitPipelineMiniGraph(); - initDetailsButton(); - initCommitPipelineStatus(); }; diff --git a/app/assets/javascripts/projects/commit_box/info/init_details_button.js b/app/assets/javascripts/projects/commit_box/info/init_details_button.js index bc2c16b9e83..520b20fcb86 100644 --- a/app/assets/javascripts/projects/commit_box/info/init_details_button.js +++ b/app/assets/javascripts/projects/commit_box/info/init_details_button.js @@ -1,7 +1,17 @@ export const initDetailsButton = () => { - document.querySelector('.commit-info').addEventListener('click', function expand(e) { - e.preventDefault(); - this.querySelector('.js-details-content').classList.remove('hide'); - this.querySelector('.js-details-expand').classList.add('gl-display-none'); + const expandButton = document.querySelector('.js-details-expand'); + + if (!expandButton) { + return; + } + + expandButton.addEventListener('click', (event) => { + const btn = event.currentTarget; + const contentEl = btn.parentElement.querySelector('.js-details-content'); + + if (contentEl) { + contentEl.classList.remove('hide'); + btn.classList.add('gl-display-none'); + } }); }; 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 d1136817cb3..8333e70b951 100644 --- a/app/assets/javascripts/projects/commit_box/info/load_branches.js +++ b/app/assets/javascripts/projects/commit_box/info/load_branches.js @@ -1,6 +1,7 @@ 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); @@ -14,6 +15,8 @@ export const loadBranches = (containerSelector = '.js-commit-box-info') => { .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/commits/components/author_select.vue b/app/assets/javascripts/projects/commits/components/author_select.vue index f85be67d4b3..2966214e051 100644 --- a/app/assets/javascripts/projects/commits/components/author_select.vue +++ b/app/assets/javascripts/projects/commits/components/author_select.vue @@ -9,7 +9,7 @@ import { } from '@gitlab/ui'; import { debounce } from 'lodash'; import { mapState, mapActions } from 'vuex'; -import { redirectTo, queryToObject } from '~/lib/utils/url_utility'; +import { redirectTo, queryToObject } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import { __ } from '~/locale'; const tooltipMessage = __('Searching by both author and message is currently not supported.'); @@ -89,10 +89,10 @@ export default { commitListElement.style.transition = 'opacity 200ms'; if (!user) { - return redirectTo(this.commitsPath); + return redirectTo(this.commitsPath); // eslint-disable-line import/no-deprecated } - return redirectTo(`${this.commitsPath}?author=${user}`); + return redirectTo(`${this.commitsPath}?author=${user}`); // eslint-disable-line import/no-deprecated }, searchAuthors() { this.fetchAuthors(this.authorInput); diff --git a/app/assets/javascripts/projects/commits/index.js b/app/assets/javascripts/projects/commits/index.js index f56884f605f..d37c1800718 100644 --- a/app/assets/javascripts/projects/commits/index.js +++ b/app/assets/javascripts/projects/commits/index.js @@ -35,6 +35,10 @@ export const initCommitsRefSwitcher = () => { const { projectId, ref, commitsPath, refType } = el.dataset; const commitsPathPrefix = commitsPath.match(COMMITS_PATH_REGEX)?.[0]; + const generateRefDestinationUrl = (selectedRef, selectedRefType) => { + const commitsPathSuffix = selectedRefType ? `?ref_type=${selectedRefType}` : ''; + return `${commitsPathPrefix}/${encodeURIComponent(selectedRef)}${commitsPathSuffix}`; + }; const useSymbolicRefNames = Boolean(refType); return new Vue({ el, @@ -42,21 +46,17 @@ export const initCommitsRefSwitcher = () => { return createElement(RefSelector, { props: { projectId, + queryParams: { sort: 'updated_desc' }, value: useSymbolicRefNames ? `refs/${refType}/${ref}` : ref, useSymbolicRefNames, - refType, }, on: { input(selected) { - if (useSymbolicRefNames) { - const matches = selected.match(/refs\/(heads|tags)\/(.+)/); - if (matches) { - visitUrl(`${commitsPathPrefix}/${matches[2]}?ref_type=${matches[1]}`); - } else { - visitUrl(`${commitsPathPrefix}/${selected}`); - } + const matches = selected.match(/refs\/(heads|tags)\/(.+)/); + if (useSymbolicRefNames && matches) { + visitUrl(generateRefDestinationUrl(matches[2], matches[1])); } else { - visitUrl(`${commitsPathPrefix}/${selected}`); + visitUrl(generateRefDestinationUrl(selected)); } }, }, diff --git a/app/assets/javascripts/projects/commits/store/actions.js b/app/assets/javascripts/projects/commits/store/actions.js index 9365066418b..5175f7f9151 100644 --- a/app/assets/javascripts/projects/commits/store/actions.js +++ b/app/assets/javascripts/projects/commits/store/actions.js @@ -1,5 +1,5 @@ import * as Sentry from '@sentry/browser'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { joinPaths } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/projects/compare/components/revision_dropdown.vue b/app/assets/javascripts/projects/compare/components/revision_dropdown.vue index 10531e950f9..8af1667e26b 100644 --- a/app/assets/javascripts/projects/compare/components/revision_dropdown.vue +++ b/app/assets/javascripts/projects/compare/components/revision_dropdown.vue @@ -1,7 +1,7 @@ <script> import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlDropdownSectionHeader } from '@gitlab/ui'; import { debounce } from 'lodash'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { s__ } from '~/locale'; @@ -149,6 +149,7 @@ export default { :key="branch" is-check-item :is-checked="selectedRevision === branch" + data-testid="branches-dropdown-item" @click="onClick(branch)" > {{ branch }} @@ -161,6 +162,7 @@ export default { :key="tag" is-check-item :is-checked="selectedRevision === tag" + data-testid="tags-dropdown-item" @click="onClick(tag)" > {{ 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 1e1677e947c..034bae3066d 100644 --- a/app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue +++ b/app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue @@ -1,6 +1,6 @@ <script> import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlDropdownSectionHeader } from '@gitlab/ui'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { s__ } from '~/locale'; @@ -132,6 +132,7 @@ export default { :key="`branch${index}`" is-check-item :is-checked="selectedRevision === branch" + data-testid="branches-dropdown-item" @click="onClick(branch)" > {{ branch }} @@ -144,6 +145,7 @@ export default { :key="`tag${index}`" is-check-item :is-checked="selectedRevision === tag" + data-testid="tags-dropdown-item" @click="onClick(tag)" > {{ tag }} diff --git a/app/assets/javascripts/projects/components/shared/delete_button.vue b/app/assets/javascripts/projects/components/shared/delete_button.vue index 64a16b462f5..06c0230c8e0 100644 --- a/app/assets/javascripts/projects/components/shared/delete_button.vue +++ b/app/assets/javascripts/projects/components/shared/delete_button.vue @@ -62,11 +62,11 @@ export default { return { primary: { text: __('Yes, delete project'), - attributes: [ - { variant: 'danger' }, - { disabled: this.confirmDisabled }, - { 'data-qa-selector': 'confirm_delete_button' }, - ], + attributes: { + variant: 'danger', + disabled: this.confirmDisabled, + 'data-qa-selector': 'confirm_delete_button', + }, }, cancel: { text: __('Cancel, keep project'), diff --git a/app/assets/javascripts/projects/default_project_templates.js b/app/assets/javascripts/projects/default_project_templates.js index a44855c14d5..5bbc881952f 100644 --- a/app/assets/javascripts/projects/default_project_templates.js +++ b/app/assets/javascripts/projects/default_project_templates.js @@ -53,10 +53,6 @@ export default { text: s__('ProjectTemplates|Pages/Plain HTML'), icon: '.template-option .icon-plainhtml', }, - gitbook: { - text: s__('ProjectTemplates|Pages/GitBook'), - icon: '.template-option .icon-gitbook', - }, hexo: { text: s__('ProjectTemplates|Pages/Hexo'), icon: '.template-option .icon-hexo', @@ -121,4 +117,8 @@ export default { text: s__('ProjectTemplates|TYPO3 Distribution'), icon: '.template-option .icon-typo3', }, + laravel: { + text: s__('ProjectTemplates|Laravel Framework'), + icon: '.template-option .icon-laravel', + }, }; diff --git a/app/assets/javascripts/projects/new/components/app.vue b/app/assets/javascripts/projects/new/components/app.vue index 3100029eb31..2f58d4468be 100644 --- a/app/assets/javascripts/projects/new/components/app.vue +++ b/app/assets/javascripts/projects/new/components/app.vue @@ -9,6 +9,7 @@ import NewNamespacePage from '~/vue_shared/new_namespace/new_namespace_page.vue' import NewProjectPushTipPopover from './new_project_push_tip_popover.vue'; const CI_CD_PANEL = 'cicd_for_external_repo'; +const IMPORT_PROJECT_PANEL = 'import_project'; const PANELS = [ { key: 'blank', @@ -32,7 +33,7 @@ const PANELS = [ }, { key: 'import', - name: 'import_project', + name: IMPORT_PROJECT_PANEL, selector: '#import-project-pane', title: s__('ProjectsNew|Import project'), description: s__( @@ -59,6 +60,24 @@ export default { SafeHtml, }, props: { + rootPath: { + type: String, + required: true, + }, + projectsUrl: { + type: String, + required: true, + }, + parentGroupUrl: { + type: String, + required: false, + default: null, + }, + parentGroupName: { + type: String, + required: false, + default: '', + }, hasErrors: { type: Boolean, required: false, @@ -74,11 +93,40 @@ export default { required: false, default: '', }, + canImportProjects: { + type: Boolean, + required: false, + default: true, + }, }, computed: { + initialBreadcrumbs() { + const breadcrumbs = this.parentGroupUrl + ? [{ text: this.parentGroupName, href: this.parentGroupUrl }] + : [ + { text: s__('Navigation|Your work'), href: this.rootPath }, + { text: s__('ProjectsNew|Projects'), href: this.projectsUrl }, + ]; + breadcrumbs.push({ text: s__('ProjectsNew|New project'), href: '#' }); + return breadcrumbs; + }, availablePanels() { - return this.isCiCdAvailable ? PANELS : PANELS.filter((p) => p.name !== CI_CD_PANEL); + if (this.isCiCdAvailable && this.canImportProjects) { + return PANELS; + } + + return PANELS.filter((panel) => { + if (!this.canImportProjects && panel.name === IMPORT_PROJECT_PANEL) { + return false; + } + + if (!this.isCiCdAvailable && panel.name === CI_CD_PANEL) { + return false; + } + + return true; + }); }, }, @@ -95,7 +143,7 @@ export default { <template> <new-namespace-page - :initial-breadcrumb="__('New project')" + :initial-breadcrumbs="initialBreadcrumbs" :panels="availablePanels" :jump-to-last-persisted-panel="hasErrors" :title="s__('ProjectsNew|Create new project')" diff --git a/app/assets/javascripts/projects/new/index.js b/app/assets/javascripts/projects/new/index.js index 910244c657b..a5a833dc73b 100644 --- a/app/assets/javascripts/projects/new/index.js +++ b/app/assets/javascripts/projects/new/index.js @@ -15,12 +15,22 @@ export function initNewProjectCreation() { newProjectGuidelines, hasErrors, isCiCdAvailable, + parentGroupUrl, + parentGroupName, + projectsUrl, + rootPath, + canImportProjects, } = el.dataset; const props = { hasErrors: parseBoolean(hasErrors), isCiCdAvailable: parseBoolean(isCiCdAvailable), newProjectGuidelines, + parentGroupUrl, + parentGroupName, + projectsUrl, + rootPath, + canImportProjects: parseBoolean(canImportProjects), }; const provide = { diff --git a/app/assets/javascripts/projects/project_find_file.js b/app/assets/javascripts/projects/project_find_file.js index 71329c4f461..a8b884a68a0 100644 --- a/app/assets/javascripts/projects/project_find_file.js +++ b/app/assets/javascripts/projects/project_find_file.js @@ -2,7 +2,7 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus'; import $ from 'jquery'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { sanitize } from '~/lib/dompurify'; import axios from '~/lib/utils/axios_utils'; import { spriteIcon } from '~/lib/utils/common_utils'; diff --git a/app/assets/javascripts/projects/settings/access_dropdown.js b/app/assets/javascripts/projects/settings/access_dropdown.js index dcf7415a444..d8675a851ea 100644 --- a/app/assets/javascripts/projects/settings/access_dropdown.js +++ b/app/assets/javascripts/projects/settings/access_dropdown.js @@ -1,7 +1,7 @@ /* eslint-disable no-underscore-dangle, class-methods-use-this */ import { escape, find, countBy } from 'lodash'; import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { n__, s__, __, sprintf } from '~/locale'; import { getUsers, getGroups, getDeployKeys } from './api/access_dropdown_api'; import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVELS, ACCESS_LEVEL_NONE } from './constants'; @@ -408,14 +408,16 @@ export default class AccessDropdown { // Has to be checked against server response // because the selected item can be in filter results - usersResponse.forEach((response) => { - // Add is it has not been added - if (map.indexOf(LEVEL_TYPES.USER + response.id) === -1) { - const user = { ...response }; - user.type = LEVEL_TYPES.USER; - users.push(user); - } - }); + if (gon.current_project_id) { + usersResponse.forEach((response) => { + // Add is it has not been added + if (map.indexOf(LEVEL_TYPES.USER + response.id) === -1) { + const user = { ...response }; + user.type = LEVEL_TYPES.USER; + users.push(user); + } + }); + } if (groups.length) { if (roles.length) { @@ -469,6 +471,14 @@ export default class AccessDropdown { } } + if (this.accessLevel === ACCESS_LEVELS.CREATE && deployKeys.length) { + consolidatedData = consolidatedData.concat( + [{ type: 'divider' }], + [{ type: 'header', content: s__('AccessDropdown|Deploy Keys') }], + deployKeys, + ); + } + return consolidatedData; } @@ -506,7 +516,10 @@ export default class AccessDropdown { break; case LEVEL_TYPES.DEPLOY_KEY: groupRowEl = - this.accessLevel === ACCESS_LEVELS.PUSH ? this.deployKeyRowHtml(item, isActive) : ''; + this.accessLevel === ACCESS_LEVELS.PUSH || this.accessLevel === ACCESS_LEVELS.CREATE + ? this.deployKeyRowHtml(item, isActive) + : ''; + break; case LEVEL_TYPES.GROUP: groupRowEl = this.groupRowHtml(item, isActive); 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 f2b1c749abc..3dcacf9eb34 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 @@ -7,7 +7,7 @@ import { GlSprintf, GlLink, } from '@gitlab/ui'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { s__, sprintf } from '~/locale'; import { helpPagePath } from '~/helpers/help_page_helper'; import branchesQuery from '../../queries/branches.query.graphql'; 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 a98c2439cde..b71c33d2b91 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 @@ -14,11 +14,6 @@ export const I18N = { wildcardsHelpText: s__( 'BranchRules|%{linkStart}Wildcards%{linkEnd} such as *-stable or production/ are supported', ), - forcePushTitle: s__('BranchRules|Force push'), - allowForcePushDescription: s__( - 'BranchRules|All users with push access are allowed to force push.', - ), - disallowForcePushDescription: s__('BranchRules|Force push is not allowed.'), approvalsTitle: s__('BranchRules|Approvals'), manageApprovalsLinkTitle: s__('BranchRules|Manage in merge request approvals'), approvalsDescription: s__( @@ -33,6 +28,19 @@ export const I18N = { allowedToPushHeader: s__('BranchRules|Allowed to push and merge (%{total})'), allowedToMergeHeader: s__('BranchRules|Allowed to merge (%{total})'), approvalsHeader: s__('BranchRules|Required approvals (%{total})'), + allowForcePushTitle: s__('BranchRules|Allows force push'), + doesNotAllowForcePushTitle: s__('BranchRules|Does not allow force push'), + forcePushDescription: s__('BranchRules|From users with push access.'), + requiresCodeOwnerApprovalTitle: s__('BranchRules|Requires approval from code owners'), + doesNotRequireCodeOwnerApprovalTitle: s__( + 'BranchRules|Does not require approval from code owners', + ), + requiresCodeOwnerApprovalDescription: s__( + 'BranchRules|Also rejects code pushes that change files listed in CODEOWNERS file.', + ), + doesNotRequireCodeOwnerApprovalDescription: s__( + 'BranchRules|Also accepts code pushes that change files listed in CODEOWNERS file.', + ), noData: s__('BranchRules|No data to display'), }; @@ -48,3 +56,9 @@ export const PROTECTED_BRANCHES_HELP_PATH = 'user/project/protected_branches'; export const APPROVALS_HELP_PATH = 'user/project/merge_requests/approvals/index.md'; export const STATUS_CHECKS_HELP_PATH = 'user/project/merge_requests/status_checks.md'; + +export const REQUIRED_ICON = 'check-circle-filled'; +export const NOT_REQUIRED_ICON = 'status-failed'; + +export const REQUIRED_ICON_CLASS = 'gl-fill-green-500'; +export const NOT_REQUIRED_ICON_CLASS = 'gl-text-red-500'; diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue index 740868e1d75..dbcb77b67f3 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue @@ -1,5 +1,5 @@ <script> -import { GlSprintf, GlLink, GlLoadingIcon } from '@gitlab/ui'; +import { GlSprintf, GlLink, GlLoadingIcon, GlIcon } from '@gitlab/ui'; import { sprintf, n__ } from '~/locale'; import { getParameterByName, mergeUrlParams } from '~/lib/utils/url_utility'; import { helpPagePath } from '~/helpers/help_page_helper'; @@ -12,6 +12,10 @@ import { BRANCH_PARAM_NAME, WILDCARDS_HELP_PATH, PROTECTED_BRANCHES_HELP_PATH, + REQUIRED_ICON, + NOT_REQUIRED_ICON, + REQUIRED_ICON_CLASS, + NOT_REQUIRED_ICON_CLASS, } from './constants'; const wildcardsHelpDocLink = helpPagePath(WILDCARDS_HELP_PATH); @@ -22,7 +26,7 @@ export default { i18n: I18N, wildcardsHelpDocLink, protectedBranchesHelpDocLink, - components: { Protection, GlSprintf, GlLink, GlLoadingIcon }, + components: { Protection, GlSprintf, GlLink, GlLoadingIcon, GlIcon }, inject: { projectPath: { default: '', @@ -33,6 +37,9 @@ export default { branchesPath: { default: '', }, + showStatusChecks: { default: false }, + showApprovers: { default: false }, + showCodeOwners: { default: false }, }, apollo: { project: { @@ -63,10 +70,28 @@ export default { }; }, computed: { - forcePushDescription() { - return this.branchProtection?.allowForcePush - ? this.$options.i18n.allowForcePushDescription - : this.$options.i18n.disallowForcePushDescription; + forcePushAttributes() { + const { allowForcePush } = this.branchProtection || {}; + const icon = allowForcePush ? REQUIRED_ICON : NOT_REQUIRED_ICON; + const iconClass = allowForcePush ? REQUIRED_ICON_CLASS : NOT_REQUIRED_ICON_CLASS; + const title = allowForcePush + ? this.$options.i18n.allowForcePushTitle + : this.$options.i18n.doesNotAllowForcePushTitle; + + return { icon, iconClass, title }; + }, + codeOwnersApprovalAttributes() { + const { codeOwnerApprovalRequired } = this.branchProtection || {}; + const icon = codeOwnerApprovalRequired ? REQUIRED_ICON : NOT_REQUIRED_ICON; + const iconClass = codeOwnerApprovalRequired ? REQUIRED_ICON_CLASS : NOT_REQUIRED_ICON_CLASS; + const title = codeOwnerApprovalRequired + ? this.$options.i18n.requiresCodeOwnerApprovalTitle + : this.$options.i18n.doesNotRequireCodeOwnerApprovalTitle; + const description = codeOwnerApprovalRequired + ? this.$options.i18n.requiresCodeOwnerApprovalDescription + : this.$options.i18n.doesNotRequireCodeOwnerApprovalDescription; + + return { icon, iconClass, title, description }; }, mergeAccessLevels() { const { mergeAccessLevels } = this.branchProtection || {}; @@ -98,7 +123,7 @@ export default { : this.$options.i18n.branchNameOrPattern; }, matchingBranchesLinkHref() { - return mergeUrlParams({ state: 'all', search: this.branch }, this.branchesPath); + return mergeUrlParams({ state: 'all', search: `^${this.branch}$` }, this.branchesPath); }, matchingBranchesLinkTitle() { const total = this.matchingBranchesCount; @@ -162,12 +187,9 @@ export default { :roles="pushAccessLevels.roles" :users="pushAccessLevels.users" :groups="pushAccessLevels.groups" + data-qa-selector="allowed_to_push_content" /> - <!-- Force push --> - <strong>{{ $options.i18n.forcePushTitle }}</strong> - <p>{{ forcePushDescription }}</p> - <!-- Allowed to merge --> <protection :header="allowedToMergeHeader" @@ -176,11 +198,40 @@ export default { :roles="mergeAccessLevels.roles" :users="mergeAccessLevels.users" :groups="mergeAccessLevels.groups" + data-qa-selector="allowed_to_merge_content" /> + <!-- Force push --> + <div class="gl-display-flex gl-align-items-center"> + <gl-icon + :size="14" + data-testid="force-push-icon" + :name="forcePushAttributes.icon" + :class="forcePushAttributes.iconClass" + /> + <strong class="gl-ml-2">{{ forcePushAttributes.title }}</strong> + </div> + + <div class="gl-text-gray-400 gl-mb-2">{{ $options.i18n.forcePushDescription }}</div> + <!-- EE start --> + <!-- Code Owners --> + <div v-if="showCodeOwners"> + <div class="gl-display-flex gl-align-items-center"> + <gl-icon + data-testid="code-owners-icon" + :size="14" + :name="codeOwnersApprovalAttributes.icon" + :class="codeOwnersApprovalAttributes.iconClass" + /> + <strong class="gl-ml-2">{{ codeOwnersApprovalAttributes.title }}</strong> + </div> + + <div class="gl-text-gray-400">{{ codeOwnersApprovalAttributes.description }}</div> + </div> + <!-- Approvals --> - <template v-if="approvalsHeader"> + <template v-if="showApprovers"> <h4 class="gl-mb-1 gl-mt-5">{{ $options.i18n.approvalsTitle }}</h4> <gl-sprintf :message="$options.i18n.approvalsDescription"> <template #link="{ content }"> @@ -200,7 +251,7 @@ export default { </template> <!-- Status checks --> - <template v-if="statusChecksHeader"> + <template v-if="showStatusChecks"> <h4 class="gl-mb-1 gl-mt-5">{{ $options.i18n.statusChecksTitle }}</h4> <gl-sprintf :message="$options.i18n.statusChecksDescription"> <template #link="{ content }"> diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue index 9bff2f5506c..3a5b3409596 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue @@ -105,7 +105,8 @@ export default { v-for="(item, index) in accessLevels" :key="index" data-testid="access-level" - class="gl-w-quarter" + data-qa-selector="access_level_content" + :data-qa-role="item.accessLevelDescription" > <span v-if="commaSeparateList && index > 0" data-testid="comma-separator">,</span> {{ item.accessLevelDescription }} diff --git a/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js b/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js index 081d6cec958..c429c352bfa 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js +++ b/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; +import { parseBoolean } from '~/lib/utils/common_utils'; import View from 'ee_else_ce/projects/settings/branch_rules/components/view/index.vue'; export default function mountBranchRules(el) { @@ -20,6 +21,9 @@ export default function mountBranchRules(el) { approvalRulesPath, statusChecksPath, branchesPath, + showStatusChecks, + showApprovers, + showCodeOwners, } = el.dataset; return new Vue({ @@ -31,6 +35,9 @@ export default function mountBranchRules(el) { approvalRulesPath, statusChecksPath, branchesPath, + showStatusChecks: parseBoolean(showStatusChecks), + showApprovers: parseBoolean(showApprovers), + showCodeOwners: parseBoolean(showCodeOwners), }, render(h) { return h(View); diff --git a/app/assets/javascripts/projects/settings/components/access_dropdown.vue b/app/assets/javascripts/projects/settings/components/access_dropdown.vue index cc47496971d..08a1c586f69 100644 --- a/app/assets/javascripts/projects/settings/components/access_dropdown.vue +++ b/app/assets/javascripts/projects/settings/components/access_dropdown.vue @@ -9,7 +9,7 @@ import { GlSprintf, } from '@gitlab/ui'; import { debounce, intersectionWith, groupBy, differenceBy, intersectionBy } from 'lodash'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { __, s__, n__ } from '~/locale'; import { getUsers, getGroups, getDeployKeys } from '../api/access_dropdown_api'; import { LEVEL_TYPES, ACCESS_LEVELS } from '../constants'; @@ -86,7 +86,10 @@ export default { return groupBy(this.preselectedItems, 'type'); }, showDeployKeys() { - return this.accessLevel === ACCESS_LEVELS.PUSH && this.deployKeys.length; + return ( + (this.accessLevel === ACCESS_LEVELS.PUSH || this.accessLevel === ACCESS_LEVELS.CREATE) && + this.deployKeys.length + ); }, toggleLabel() { const counts = Object.entries(this.selected).reduce((acc, [key, value]) => { diff --git a/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue b/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue index aa3235b1515..b8e7e9e15db 100644 --- a/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue +++ b/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue @@ -1,5 +1,5 @@ <script> -import { GlAlert, GlToggle, GlTooltip } from '@gitlab/ui'; +import { GlAlert, GlToggle } from '@gitlab/ui'; import axios from '~/lib/utils/axios_utils'; import { __, s__ } from '~/locale'; import { CC_VALIDATION_REQUIRED_ERROR } from '../constants'; @@ -16,7 +16,6 @@ export default { components: { GlAlert, GlToggle, - GlTooltip, CcValidationRequiredAlert: () => import('ee_component/billings/components/cc_validation_required_alert.vue'), }, @@ -94,10 +93,26 @@ export default { @dismiss="ccAlertDismissed = true" /> - <gl-alert v-if="genericError" class="gl-mb-3" variant="danger" :dismissible="false"> + <gl-alert + v-if="genericError" + data-testid="error-alert" + variant="danger" + :dismissible="false" + class="gl-mb-5" + > {{ errorMessage }} </gl-alert> + <gl-alert + v-if="isDisabledAndUnoverridable" + data-testid="unoverridable-alert" + variant="warning" + :dismissible="false" + class="gl-mb-5" + > + {{ s__('Runners|Shared runners are disabled in the group settings') }} + </gl-alert> + <gl-toggle ref="sharedRunnersToggle" :disabled="isDisabledAndUnoverridable" @@ -107,10 +122,6 @@ export default { data-testid="toggle-shared-runners" @change="toggleSharedRunners" /> - - <gl-tooltip v-if="isDisabledAndUnoverridable" :target="() => $refs.sharedRunnersToggle"> - {{ __('Shared runners are disabled on group level') }} - </gl-tooltip> </section> </div> </template> diff --git a/app/assets/javascripts/projects/settings/constants.js b/app/assets/javascripts/projects/settings/constants.js index 9cf1afd334f..595cbc9c991 100644 --- a/app/assets/javascripts/projects/settings/constants.js +++ b/app/assets/javascripts/projects/settings/constants.js @@ -17,6 +17,7 @@ export const LEVEL_ID_PROP = { export const ACCESS_LEVELS = { MERGE: 'merge_access_levels', PUSH: 'push_access_levels', + CREATE: 'create_access_levels', }; export const ACCESS_LEVEL_NONE = 0; diff --git a/app/assets/javascripts/projects/settings/mount_ref_switcher_badges.js b/app/assets/javascripts/projects/settings/mount_ref_switcher_badges.js new file mode 100644 index 00000000000..527678250fb --- /dev/null +++ b/app/assets/javascripts/projects/settings/mount_ref_switcher_badges.js @@ -0,0 +1,31 @@ +import Vue from 'vue'; +import RefSelector from '~/ref/components/ref_selector.vue'; +import { visitUrl } from '~/lib/utils/url_utility'; +import { generateRefDestinationPath } from './utils'; + +export default function initRefSwitcherBadges() { + const refSwitcherElements = document.getElementsByClassName('js-ref-switcher-badge'); + + if (refSwitcherElements.length === 0) return false; + + return Array.from(refSwitcherElements).forEach((element) => { + const { projectId, ref } = element.dataset; + + return new Vue({ + el: element, + render(createElement) { + return createElement(RefSelector, { + props: { + projectId, + value: ref, + }, + on: { + input(selectedRef) { + visitUrl(generateRefDestinationPath(selectedRef)); + }, + }, + }); + }, + }); + }); +} diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue index f3d392a0ec4..dcf5155644d 100644 --- a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue +++ b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue @@ -1,6 +1,6 @@ <script> import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import branchRulesQuery from 'ee_else_ce/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql'; import { expandSection } from '~/settings_panels'; import { scrollToElement } from '~/lib/utils/common_utils'; @@ -69,9 +69,14 @@ export default { <div v-if="!branchRules.length" data-testid="empty">{{ $options.i18n.emptyState }}</div> - <gl-button v-gl-modal="$options.modalId" class="gl-mt-5" category="secondary" variant="info">{{ - $options.i18n.addBranchRule - }}</gl-button> + <gl-button + v-gl-modal="$options.modalId" + class="gl-mt-5" + data-qa-selector="add_branch_rule_button" + category="secondary" + variant="info" + >{{ $options.i18n.addBranchRule }}</gl-button + > <gl-modal :ref="$options.modalId" diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue index fa96eee5f92..a5ff478a826 100644 --- a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue +++ b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue @@ -27,6 +27,9 @@ export default { branchRulesPath: { default: '', }, + showCodeOwners: { default: false }, + showStatusChecks: { default: false }, + showApprovers: { default: false }, }, props: { name: { @@ -70,7 +73,7 @@ export default { return this.approvalDetails.length; }, detailsPath() { - return `${this.branchRulesPath}?branch=${this.name}`; + return `${this.branchRulesPath}?branch=${encodeURIComponent(this.name)}`; }, statusChecksText() { return sprintf(this.$options.i18n.statusChecks, { @@ -112,13 +115,13 @@ export default { if (this.branchProtection?.allowForcePush) { approvalDetails.push(this.$options.i18n.allowForcePush); } - if (this.branchProtection?.codeOwnerApprovalRequired) { + if (this.showCodeOwners && this.branchProtection?.codeOwnerApprovalRequired) { approvalDetails.push(this.$options.i18n.codeOwnerApprovalRequired); } - if (this.statusChecksTotal) { + if (this.showStatusChecks && this.statusChecksTotal) { approvalDetails.push(this.statusChecksText); } - if (this.approvalRulesTotal) { + if (this.showApprovers && this.approvalRulesTotal) { approvalDetails.push(this.approvalRulesText); } if (this.mergeAccessLevels.total > 0) { @@ -150,7 +153,11 @@ export default { </script> <template> - <div class="gl-border-b gl-pt-5 gl-pb-5 gl-display-flex gl-justify-content-space-between"> + <div + class="gl-border-b gl-pt-5 gl-pb-5 gl-display-flex gl-justify-content-space-between" + data-qa-selector="branch_content" + :data-qa-branch-name="name" + > <div> <strong class="gl-font-monospace">{{ name }}</strong> @@ -166,7 +173,7 @@ export default { <li v-for="(detail, index) in approvalDetails" :key="index">{{ detail }}</li> </ul> </div> - <gl-button class="gl-align-self-start" :href="detailsPath"> + <gl-button class="gl-align-self-start" data-qa-selector="details_button" :href="detailsPath"> {{ $options.i18n.detailsButtonLabel }}</gl-button > </div> diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js b/app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js index 042be089e09..a8736c87e22 100644 --- a/app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js +++ b/app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; import BranchRulesApp from '~/projects/settings/repository/branch_rules/app.vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; Vue.use(VueApollo); @@ -12,7 +13,13 @@ const apolloProvider = new VueApollo({ export default function mountBranchRules(el) { if (!el) return null; - const { projectPath, branchRulesPath } = el.dataset; + const { + projectPath, + branchRulesPath, + showCodeOwners, + showStatusChecks, + showApprovers, + } = el.dataset; return new Vue({ el, @@ -20,6 +27,9 @@ export default function mountBranchRules(el) { provide: { projectPath, branchRulesPath, + showCodeOwners: parseBoolean(showCodeOwners), + showStatusChecks: parseBoolean(showStatusChecks), + showApprovers: parseBoolean(showApprovers), }, render(createElement) { return createElement(BranchRulesApp); diff --git a/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue b/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue index 3d553e71f71..47477d39b8a 100644 --- a/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue +++ b/app/assets/javascripts/projects/settings/topics/components/topics_token_selector.vue @@ -1,5 +1,6 @@ <script> -import { GlTokenSelector, GlAvatarLabeled } from '@gitlab/ui'; +import { GlTokenSelector, GlAvatarLabeled, GlFormGroup, GlLink, GlSprintf } from '@gitlab/ui'; +import { helpPagePath } from '~/helpers/help_page_helper'; import { s__ } from '~/locale'; import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants'; import searchProjectTopics from '~/graphql_shared/queries/project_topics_search.query.graphql'; @@ -8,8 +9,15 @@ export default { components: { GlTokenSelector, GlAvatarLabeled, + GlFormGroup, + GlLink, + GlSprintf, }, i18n: { + topicsTitle: s__('ProjectSettings|Topics'), + topicsHelpText: s__( + 'ProjectSettings|Topics are publicly visible even on private projects. Do not include sensitive information in topic names. %{linkStart}Learn more%{linkEnd}.', + ), placeholder: s__('ProjectSettings|Search for topic'), }, props: { @@ -51,6 +59,11 @@ export default { placeholderText() { return this.selectedTokens.length ? '' : this.$options.i18n.placeholder; }, + topicsHelpUrl() { + return helpPagePath('user/admin_area/index.html', { + anchor: 'administering-topics', + }); + }, }, methods: { handleEnter(event) { @@ -70,25 +83,34 @@ export default { }; </script> <template> - <gl-token-selector - ref="tokenSelector" - v-model="selectedTokens" - :dropdown-items="topics" - :loading="loading" - allow-user-defined-tokens - :placeholder="placeholderText" - @keydown.enter="handleEnter" - @text-input="filterTopics" - @input="onTokensUpdate" - > - <template #dropdown-item-content="{ dropdownItem }"> - <gl-avatar-labeled - :src="dropdownItem.avatarUrl" - :entity-name="dropdownItem.name" - :label="dropdownItem.title" - :size="32" - :shape="$options.AVATAR_SHAPE_OPTION_RECT" - /> + <gl-form-group id="project_topics" :label="$options.i18n.topicsTitle"> + <gl-token-selector + ref="tokenSelector" + v-model="selectedTokens" + :dropdown-items="topics" + :loading="loading" + allow-user-defined-tokens + :placeholder="placeholderText" + @keydown.enter="handleEnter" + @text-input="filterTopics" + @input="onTokensUpdate" + > + <template #dropdown-item-content="{ dropdownItem }"> + <gl-avatar-labeled + :src="dropdownItem.avatarUrl" + :entity-name="dropdownItem.name" + :label="dropdownItem.title" + :size="32" + :shape="$options.AVATAR_SHAPE_OPTION_RECT" + /> + </template> + </gl-token-selector> + <template #description> + <gl-sprintf :message="$options.i18n.topicsHelpText"> + <template #link="{ content }"> + <gl-link :href="topicsHelpUrl" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> </template> - </gl-token-selector> + </gl-form-group> </template> diff --git a/app/assets/javascripts/projects/settings/utils.js b/app/assets/javascripts/projects/settings/utils.js index ea4574119c0..9c19657bb39 100644 --- a/app/assets/javascripts/projects/settings/utils.js +++ b/app/assets/javascripts/projects/settings/utils.js @@ -1,3 +1,24 @@ +import { joinPaths } from '~/lib/utils/url_utility'; + +export const generateRefDestinationPath = (selectedRef) => { + const namespace = '-/settings/ci_cd'; + const { pathname } = window.location; + + if (!selectedRef || !pathname.includes(namespace)) { + return window.location.href; + } + + const [projectRootPath] = pathname.split(namespace); + + const destinationPath = joinPaths(projectRootPath, namespace); + + const newURL = new URL(window.location); + newURL.pathname = destinationPath; + newURL.searchParams.set('ref', selectedRef); + + return newURL.href; +}; + export const getAccessLevels = (accessLevels = {}) => { const total = accessLevels.edges?.length; const accessLevelTypes = { total, users: [], groups: [], roles: [] }; 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 b79b3fa4573..79ece99e6ec 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 @@ -8,7 +8,7 @@ import ServiceDeskSetting from './service_desk_setting.vue'; export default { customEmailHelpPath: helpPagePath('/user/project/service_desk.html', { - anchor: 'using-a-custom-email-address', + anchor: 'use-a-custom-email-address', }), components: { GlAlert, 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 85550e262e6..5a3930b5df4 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 @@ -102,12 +102,12 @@ export default { }, emailSuffixHelpUrl() { return helpPagePath('user/project/service_desk.html', { - anchor: 'configuring-a-custom-email-address-suffix', + anchor: 'configure-a-custom-email-address-suffix', }); }, customEmailAddressHelpUrl() { return helpPagePath('user/project/service_desk.html', { - anchor: 'using-a-custom-email-address', + anchor: 'use-a-custom-email-address', }); }, }, @@ -155,7 +155,7 @@ export default { <div v-if="isEnabled" class="row mt-3"> <div class="col-md-9 mb-0"> <gl-form-group - :label="__('Email address to use for Support Desk')" + :label="__('Email address to use for Service Desk')" label-for="incoming-email" data-testid="incoming-email-label" > diff --git a/app/assets/javascripts/projects/star.js b/app/assets/javascripts/projects/star.js index 55c3d68cd11..f294811dfff 100644 --- a/app/assets/javascripts/projects/star.js +++ b/app/assets/javascripts/projects/star.js @@ -1,4 +1,4 @@ -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { spriteIcon } from '~/lib/utils/common_utils'; import { __, s__ } from '~/locale'; diff --git a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue index 9f9b6424125..5b620aa2300 100644 --- a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue +++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue @@ -1,7 +1,7 @@ <script> import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; import Visibility from 'visibilityjs'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import Poll from '~/lib/utils/poll'; import { __, s__, sprintf } from '~/locale'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; |