diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-21 10:08:36 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-21 10:08:36 +0300 |
commit | 48aff82709769b098321c738f3444b9bdaa694c6 (patch) | |
tree | e00c7c43e2d9b603a5a6af576b1685e400410dee /app/assets/javascripts/projects | |
parent | 879f5329ee916a948223f8f43d77fba4da6cd028 (diff) |
Add latest changes from gitlab-org/gitlab@13-5-stable-eev13.5.0-rc42
Diffstat (limited to 'app/assets/javascripts/projects')
12 files changed, 226 insertions, 45 deletions
diff --git a/app/assets/javascripts/projects/commit_box/info/index.js b/app/assets/javascripts/projects/commit_box/info/index.js new file mode 100644 index 00000000000..352ac39f3c4 --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/index.js @@ -0,0 +1,18 @@ +import { loadBranches } from './load_branches'; +import { fetchCommitMergeRequests } from '~/commit_merge_requests'; +import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; + +export const initCommitBoxInfo = (containerSelector = '.js-commit-box-info') => { + const containerEl = document.querySelector(containerSelector); + + // Display commit related branches + loadBranches(containerEl); + + // Related merge requests to this commit + fetchCommitMergeRequests(); + + // Display pipeline info for this commit + new MiniPipelineGraph({ + container: '.js-commit-pipeline-graph', + }).bindEvents(); +}; diff --git a/app/assets/javascripts/projects/commit_box/info/load_branches.js b/app/assets/javascripts/projects/commit_box/info/load_branches.js new file mode 100644 index 00000000000..0efa1998507 --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/load_branches.js @@ -0,0 +1,20 @@ +import axios from 'axios'; +import { sanitize } from '~/lib/dompurify'; +import { __ } from '~/locale'; + +export const loadBranches = containerEl => { + if (!containerEl) { + return; + } + + const { commitPath } = containerEl.dataset; + const branchesEl = containerEl.querySelector('.commit-info.branches'); + axios + .get(commitPath) + .then(({ data }) => { + branchesEl.innerHTML = sanitize(data); + }) + .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 2204ec3cbe7..3bc772fe60a 100644 --- a/app/assets/javascripts/projects/commits/components/author_select.vue +++ b/app/assets/javascripts/projects/commits/components/author_select.vue @@ -119,7 +119,6 @@ export default { <gl-dropdown-divider /> <gl-search-box-by-type v-model.trim="authorInput" - class="gl-m-3" :placeholder="__('Search')" @input="searchAuthors" /> diff --git a/app/assets/javascripts/projects/commits/store/actions.js b/app/assets/javascripts/projects/commits/store/actions.js index 927501748a5..157e2409f7f 100644 --- a/app/assets/javascripts/projects/commits/store/actions.js +++ b/app/assets/javascripts/projects/commits/store/actions.js @@ -1,4 +1,4 @@ -import * as Sentry from '@sentry/browser'; +import * as Sentry from '~/sentry/wrapper'; import * as types from './mutation_types'; import axios from '~/lib/utils/axios_utils'; import { deprecatedCreateFlash as createFlash } from '~/flash'; diff --git a/app/assets/javascripts/projects/default_project_templates.js b/app/assets/javascripts/projects/default_project_templates.js index 2d321ead33e..a6019e9c01b 100644 --- a/app/assets/javascripts/projects/default_project_templates.js +++ b/app/assets/javascripts/projects/default_project_templates.js @@ -57,6 +57,10 @@ export default { text: s__('ProjectTemplates|Static Site Editor/Middleman'), icon: '.template-option .icon-sse_middleman', }, + gitpod_spring_petclinic: { + text: s__('ProjectTemplates|Gitpod/Spring Petclinic'), + icon: '.template-option .icon-gitpod_spring_petclinic', + }, nfhugo: { text: s__('ProjectTemplates|Netlify/Hugo'), icon: '.template-option .icon-nfhugo', diff --git a/app/assets/javascripts/projects/default_sample_data_templates.js b/app/assets/javascripts/projects/default_sample_data_templates.js new file mode 100644 index 00000000000..7c45e7ac62f --- /dev/null +++ b/app/assets/javascripts/projects/default_sample_data_templates.js @@ -0,0 +1,12 @@ +import { s__ } from '~/locale'; + +export default { + basic: { + text: s__('ProjectTemplates|Basic'), + icon: '.template-option .icon-basic', + }, + serenity_valley: { + text: s__('ProjectTemplates|Serenity Valley'), + icon: '.template-option .icon-serenity_valley', + }, +}; diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index 599aa52831b..d74a2d06786 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -1,5 +1,6 @@ import $ from 'jquery'; import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates'; +import DEFAULT_SAMPLE_DATA_TEMPLATES from '~/projects/default_sample_data_templates'; import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils'; import { convertToTitleCase, @@ -146,7 +147,8 @@ const bindEvents = () => { $selectedIcon.empty(); const value = $(this).val(); - const selectedTemplate = DEFAULT_PROJECT_TEMPLATES[value]; + const selectedTemplate = + DEFAULT_PROJECT_TEMPLATES[value] || DEFAULT_SAMPLE_DATA_TEMPLATES[value]; $selectedTemplateText.text(selectedTemplate.text); $(selectedTemplate.icon) .clone() diff --git a/app/assets/javascripts/projects/settings/access_dropdown.js b/app/assets/javascripts/projects/settings/access_dropdown.js index 5d51b7ea57b..3ca5bca4bf2 100644 --- a/app/assets/javascripts/projects/settings/access_dropdown.js +++ b/app/assets/javascripts/projects/settings/access_dropdown.js @@ -1,9 +1,9 @@ /* eslint-disable no-underscore-dangle, class-methods-use-this */ import { escape, find, countBy } from 'lodash'; import axios from '~/lib/utils/axios_utils'; -import { deprecatedCreateFlash as Flash } from '~/flash'; -import { n__, s__, __ } from '~/locale'; -import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVEL_NONE } from './constants'; +import createFlash from '~/flash'; +import { n__, s__, __, sprintf } from '~/locale'; +import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVELS, ACCESS_LEVEL_NONE } from './constants'; import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; export default class AccessDropdown { @@ -11,6 +11,7 @@ export default class AccessDropdown { const { $dropdown, accessLevel, accessLevelsData, hasLicense = true } = options; this.options = options; this.hasLicense = hasLicense; + this.deployKeysOnProtectedBranchesEnabled = gon.features.deployKeysOnProtectedBranches; this.groups = []; this.accessLevel = accessLevel; this.accessLevelsData = accessLevelsData.roles; @@ -18,6 +19,7 @@ export default class AccessDropdown { this.$wrap = this.$dropdown.closest(`.${this.accessLevel}-container`); this.usersPath = '/-/autocomplete/users.json'; this.groupsPath = '/-/autocomplete/project_groups.json'; + this.deployKeysPath = '/-/autocomplete/deploy_keys_with_owners.json'; this.defaultLabel = this.$dropdown.data('defaultLabel'); this.setSelectedItems([]); @@ -146,6 +148,8 @@ export default class AccessDropdown { obj.access_level = item.access_level; } else if (item.type === LEVEL_TYPES.USER) { obj.user_id = item.user_id; + } else if (item.type === LEVEL_TYPES.DEPLOY_KEY) { + obj.deploy_key_id = item.deploy_key_id; } else if (item.type === LEVEL_TYPES.GROUP) { obj.group_id = item.group_id; } @@ -177,6 +181,9 @@ export default class AccessDropdown { case LEVEL_TYPES.GROUP: comparator = LEVEL_ID_PROP.GROUP; break; + case LEVEL_TYPES.DEPLOY_KEY: + comparator = LEVEL_ID_PROP.DEPLOY_KEY; + break; case LEVEL_TYPES.USER: comparator = LEVEL_ID_PROP.USER; break; @@ -218,6 +225,11 @@ export default class AccessDropdown { group_id: selectedItem.id, type: LEVEL_TYPES.GROUP, }; + } else if (selectedItem.type === LEVEL_TYPES.DEPLOY_KEY) { + itemToAdd = { + deploy_key_id: selectedItem.id, + type: LEVEL_TYPES.DEPLOY_KEY, + }; } this.items.push(itemToAdd); @@ -233,11 +245,12 @@ export default class AccessDropdown { return true; } - if (item.type === LEVEL_TYPES.USER && item.user_id === itemToDelete.id) { - index = i; - } else if (item.type === LEVEL_TYPES.ROLE && item.access_level === itemToDelete.id) { - index = i; - } else if (item.type === LEVEL_TYPES.GROUP && item.group_id === itemToDelete.id) { + if ( + (item.type === LEVEL_TYPES.USER && item.user_id === itemToDelete.id) || + (item.type === LEVEL_TYPES.ROLE && item.access_level === itemToDelete.id) || + (item.type === LEVEL_TYPES.DEPLOY_KEY && item.deploy_key_id === itemToDelete.id) || + (item.type === LEVEL_TYPES.GROUP && item.group_id === itemToDelete.id) + ) { index = i; } @@ -289,6 +302,10 @@ export default class AccessDropdown { labelPieces.push(n__('1 user', '%d users', counts[LEVEL_TYPES.USER])); } + if (counts[LEVEL_TYPES.DEPLOY_KEY] > 0) { + labelPieces.push(n__('1 deploy key', '%d deploy keys', counts[LEVEL_TYPES.DEPLOY_KEY])); + } + if (counts[LEVEL_TYPES.GROUP] > 0) { labelPieces.push(n__('1 group', '%d groups', counts[LEVEL_TYPES.GROUP])); } @@ -299,20 +316,31 @@ export default class AccessDropdown { getData(query, callback) { if (this.hasLicense) { Promise.all([ + this.getDeployKeys(query), this.getUsers(query), this.groupsData ? Promise.resolve(this.groupsData) : this.getGroups(), ]) - .then(([usersResponse, groupsResponse]) => { + .then(([deployKeysResponse, usersResponse, groupsResponse]) => { this.groupsData = groupsResponse; - callback(this.consolidateData(usersResponse.data, groupsResponse.data)); + callback( + this.consolidateData(deployKeysResponse.data, usersResponse.data, groupsResponse.data), + ); }) - .catch(() => Flash(__('Failed to load groups & users.'))); + .catch(() => { + if (this.deployKeysOnProtectedBranchesEnabled) { + createFlash({ message: __('Failed to load groups, users and deploy keys.') }); + } else { + createFlash({ message: __('Failed to load groups & users.') }); + } + }); } else { - callback(this.consolidateData()); + this.getDeployKeys(query) + .then(deployKeysResponse => callback(this.consolidateData(deployKeysResponse.data))) + .catch(() => createFlash({ message: __('Failed to load deploy keys.') })); } } - consolidateData(usersResponse = [], groupsResponse = []) { + consolidateData(deployKeysResponse, usersResponse = [], groupsResponse = []) { let consolidatedData = []; // ID property is handled differently locally from the server @@ -328,6 +356,10 @@ export default class AccessDropdown { // For Users // In dropdown: `id` // For submit: `user_id` + // + // For Deploy Keys + // In dropdown: `id` + // For submit: `deploy_key_id` /* * Build roles @@ -410,6 +442,38 @@ export default class AccessDropdown { } } + if (this.deployKeysOnProtectedBranchesEnabled) { + const deployKeys = deployKeysResponse.map(response => { + const { + id, + fingerprint, + title, + owner: { avatar_url, name, username }, + } = response; + + const shortFingerprint = `(${fingerprint.substring(0, 14)}...)`; + + return { + id, + title: title.concat(' ', shortFingerprint), + avatar_url, + fullname: name, + username, + type: LEVEL_TYPES.DEPLOY_KEY, + }; + }); + + if (this.accessLevel === ACCESS_LEVELS.PUSH) { + if (deployKeys.length) { + consolidatedData = consolidatedData.concat( + [{ type: 'divider' }], + [{ type: 'header', content: s__('AccessDropdown|Deploy Keys') }], + deployKeys, + ); + } + } + } + return consolidatedData; } @@ -433,6 +497,22 @@ export default class AccessDropdown { }); } + getDeployKeys(query) { + if (this.deployKeysOnProtectedBranchesEnabled) { + return axios.get(this.buildUrl(gon.relative_url_root, this.deployKeysPath), { + params: { + search: query, + per_page: 20, + active: true, + project_id: gon.current_project_id, + push_code: true, + }, + }); + } + + return Promise.resolve({ data: [] }); + } + buildUrl(urlRoot, url) { let newUrl; if (urlRoot != null) { @@ -454,6 +534,9 @@ export default class AccessDropdown { case LEVEL_TYPES.ROLE: criteria = { access_level: item.id }; break; + case LEVEL_TYPES.DEPLOY_KEY: + criteria = { deploy_key_id: item.id }; + break; case LEVEL_TYPES.GROUP: criteria = { group_id: item.id }; break; @@ -470,6 +553,10 @@ export default class AccessDropdown { case LEVEL_TYPES.ROLE: groupRowEl = this.roleRowHtml(item, isActive); break; + case LEVEL_TYPES.DEPLOY_KEY: + groupRowEl = + this.accessLevel === ACCESS_LEVELS.PUSH ? this.deployKeyRowHtml(item, isActive) : ''; + break; case LEVEL_TYPES.GROUP: groupRowEl = this.groupRowHtml(item, isActive); break; @@ -495,6 +582,31 @@ export default class AccessDropdown { `; } + deployKeyRowHtml(key, isActive) { + const isActiveClass = isActive || ''; + + return ` + <li> + <a href="#" class="${isActiveClass}"> + <strong>${key.title}</strong> + <p> + ${sprintf( + __('Owned by %{image_tag}'), + { + image_tag: `<img src="${key.avatar_url}" class="avatar avatar-inline s26" width="30">`, + }, + false, + )} + <strong class="dropdown-menu-user-full-name gl-display-inline">${escape( + key.fullname, + )}</strong> + <span class="dropdown-menu-user-username gl-display-inline">${key.username}</span> + </p> + </a> + </li> + `; + } + groupRowHtml(group, isActive) { const isActiveClass = isActive || ''; const avatarEl = group.avatar_url diff --git a/app/assets/javascripts/projects/settings/constants.js b/app/assets/javascripts/projects/settings/constants.js index fadb1f4f178..f5591c43dc4 100644 --- a/app/assets/javascripts/projects/settings/constants.js +++ b/app/assets/javascripts/projects/settings/constants.js @@ -1,13 +1,20 @@ export const LEVEL_TYPES = { ROLE: 'role', USER: 'user', + DEPLOY_KEY: 'deploy_key', GROUP: 'group', }; export const LEVEL_ID_PROP = { ROLE: 'access_level', USER: 'user_id', + DEPLOY_KEY: 'deploy_key_id', GROUP: 'group_id', }; +export const ACCESS_LEVELS = { + MERGE: 'merge_access_levels', + PUSH: 'push_access_levels', +}; + export const ACCESS_LEVEL_NONE = 0; 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 81367f7d6b4..4bfed6d489d 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 @@ -1,6 +1,6 @@ <script> import { GlAlert } from '@gitlab/ui'; -import { __ } from '~/locale'; +import { __, sprintf } from '~/locale'; import ServiceDeskSetting from './service_desk_setting.vue'; import ServiceDeskService from '../services/service_desk_service'; import eventHub from '../event_hub'; @@ -122,11 +122,13 @@ export default { this.incomingEmail = data?.service_desk_address; this.showAlert(__('Changes were successfully made.'), 'success'); }) - .catch(() => + .catch(err => { this.showAlert( - __('An error occurred while saving the template. Please check if the template exists.'), - ), - ) + sprintf(__('An error occured while making the changes: %{error}'), { + error: err?.response?.data?.message, + }), + ); + }) .finally(() => { this.isTemplateSaving = false; }); 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 6a0810ad3a1..e18cfefc3ca 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 @@ -1,22 +1,19 @@ <script> -import { GlButton, GlFormSelect, GlToggle, GlLoadingIcon } from '@gitlab/ui'; +import { GlButton, GlFormSelect, GlToggle, GlLoadingIcon, GlSprintf } from '@gitlab/ui'; import { __ } from '~/locale'; -import tooltip from '~/vue_shared/directives/tooltip'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import eventHub from '../event_hub'; export default { name: 'ServiceDeskSetting', - directives: { - tooltip, - }, components: { ClipboardButton, GlButton, GlFormSelect, GlToggle, GlLoadingIcon, + GlSprintf, }, mixins: [glFeatureFlagsMixin()], props: { @@ -60,6 +57,7 @@ export default { selectedTemplate: this.initialSelectedTemplate, outgoingName: this.initialOutgoingName || __('GitLab Support Bot'), projectKey: this.initialProjectKey, + baseEmail: this.incomingEmail.replace(this.initialProjectKey, ''), }; }, computed: { @@ -108,7 +106,7 @@ export default { <input ref="service-desk-incoming-email" type="text" - class="form-control incoming-email h-auto" + class="form-control incoming-email" :placeholder="__('Incoming email')" :aria-label="__('Incoming email')" aria-describedby="incoming-email-describer" @@ -119,16 +117,37 @@ export default { <clipboard-button :title="__('Copy')" :text="incomingEmail" - css-class="btn qa-clipboard-button" + css-class="input-group-text qa-clipboard-button" /> </div> </div> + <span v-if="projectKey" class="form-text text-muted"> + <gl-sprintf :message="__('Emails sent to %{email} will still be supported')"> + <template #email> + <code>{{ baseEmail }}</code> + </template> + </gl-sprintf> + </span> </template> <template v-else> <gl-loading-icon :inline="true" /> <span class="sr-only">{{ __('Fetching incoming email') }}</span> </template> + <template v-if="hasProjectKeySupport"> + <label for="service-desk-project-suffix" class="mt-3"> + {{ __('Project name suffix') }} + </label> + <input id="service-desk-project-suffix" v-model.trim="projectKey" class="form-control" /> + <span class="form-text text-muted"> + {{ + __( + 'Project name suffix is a user-defined string which will be appended to the project path, and will form the Service Desk email address.', + ) + }} + </span> + </template> + <label for="service-desk-template-select" class="mt-3"> {{ __('Template to append to all Service Desk issues') }} </label> @@ -144,19 +163,6 @@ export default { <span class="form-text text-muted"> {{ __('Emails sent from Service Desk will have this name') }} </span> - <template v-if="hasProjectKeySupport"> - <label for="service-desk-project-suffix" class="mt-3"> - {{ __('Project name suffix') }} - </label> - <input id="service-desk-project-suffix" v-model.trim="projectKey" class="form-control" /> - <span class="form-text text-muted mb-3"> - {{ - __( - 'Project name suffix is a user-defined string which will be appended to the project path, and will form the Service Desk email address.', - ) - }} - </span> - </template> <div class="gl-display-flex gl-justify-content-end"> <gl-button variant="success" 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 e691f675e59..e582d5c3e47 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,16 +1,15 @@ <script> import Visibility from 'visibilityjs'; -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; import ciIcon from '~/vue_shared/components/ci_icon.vue'; import Poll from '~/lib/utils/poll'; import { deprecatedCreateFlash as Flash } from '~/flash'; import { __, s__, sprintf } from '~/locale'; -import tooltip from '~/vue_shared/directives/tooltip'; import CommitPipelineService from '../services/commit_pipeline_service'; export default { directives: { - tooltip, + GlTooltip: GlTooltipDirective, }, components: { ciIcon, @@ -97,7 +96,7 @@ export default { <gl-loading-icon v-if="isLoading" size="lg" label="Loading pipeline status" /> <a v-else :href="ciStatus.details_path"> <ci-icon - v-tooltip + v-gl-tooltip :title="statusTitle" :aria-label="statusTitle" :status="ciStatus" |