Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/projects')
-rw-r--r--app/assets/javascripts/projects/commit_box/info/index.js18
-rw-r--r--app/assets/javascripts/projects/commit_box/info/load_branches.js20
-rw-r--r--app/assets/javascripts/projects/default_project_templates.js4
-rw-r--r--app/assets/javascripts/projects/settings/access_dropdown.js136
-rw-r--r--app/assets/javascripts/projects/settings/constants.js7
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue12
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue42
7 files changed, 206 insertions, 33 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/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/settings/access_dropdown.js b/app/assets/javascripts/projects/settings/access_dropdown.js
index 5d51b7ea57b..c189e617105 100644
--- a/app/assets/javascripts/projects/settings/access_dropdown.js
+++ b/app/assets/javascripts/projects/settings/access_dropdown.js
@@ -2,8 +2,8 @@
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 { 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) {
+ Flash(__('Failed to load groups, users and deploy keys.'));
+ } else {
+ Flash(__('Failed to load groups & users.'));
+ }
+ });
} else {
- callback(this.consolidateData());
+ this.getDeployKeys(query)
+ .then(deployKeysResponse => callback(this.consolidateData(deployKeysResponse.data)))
+ .catch(() => Flash(__('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..089cac9ee4c 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,5 +1,5 @@
<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';
@@ -17,6 +17,7 @@ export default {
GlFormSelect,
GlToggle,
GlLoadingIcon,
+ GlSprintf,
},
mixins: [glFeatureFlagsMixin()],
props: {
@@ -60,6 +61,7 @@ export default {
selectedTemplate: this.initialSelectedTemplate,
outgoingName: this.initialOutgoingName || __('GitLab Support Bot'),
projectKey: this.initialProjectKey,
+ baseEmail: this.incomingEmail.replace(this.initialProjectKey, ''),
};
},
computed: {
@@ -108,7 +110,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 +121,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 +167,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"