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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-02-20 00:11:07 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-02-20 00:11:07 +0300
commit83d7a3922367d95a0875f9bdbdbf34a37f84e3fb (patch)
tree8b8bcfea60335e24f38d813e950624d30a4a94ff /app
parentfe389e7d345a391a8b6e6392dcf1bbd23546b323 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/api.js22
-rw-r--r--app/assets/javascripts/invite_members/components/group_select.vue103
-rw-r--r--app/assets/javascripts/invite_members/components/invite_group_trigger.vue34
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_modal.vue131
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_trigger.vue4
-rw-r--r--app/assets/javascripts/invite_members/components/members_token_select.vue4
-rw-r--r--app/assets/javascripts/invite_members/constants.js2
-rw-r--r--app/assets/javascripts/invite_members/init_invite_group_trigger.js20
-rw-r--r--app/assets/javascripts/jira_connect/components/app.vue6
-rw-r--r--app/assets/javascripts/jira_connect/index.js1
-rw-r--r--app/assets/javascripts/pages/groups/group_members/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/feature_flags_user_lists/new/index.js28
-rw-r--r--app/assets/javascripts/pages/projects/project_members/index.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue7
-rw-r--r--app/assets/javascripts/vue_shared/directives/tooltip.js35
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/lists.scss6
-rw-r--r--app/assets/stylesheets/framework/tooltips.scss6
-rw-r--r--app/assets/stylesheets/page_bundles/jira_connect.scss56
-rw-r--r--app/views/groups/group_members/index.html.haml5
-rw-r--r--app/views/jira_connect/subscriptions/index.html.haml83
-rw-r--r--app/views/layouts/jira_connect.html.haml3
-rw-r--r--app/views/projects/project_members/index.html.haml5
-rw-r--r--app/workers/all_queues.yml16
-rw-r--r--app/workers/releases/create_evidence_worker.rb1
25 files changed, 380 insertions, 203 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index c7e6b98a934..a9bb2909c39 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -24,6 +24,7 @@ const Api = {
projectPackagesPath: '/api/:version/projects/:id/packages',
projectPackagePath: '/api/:version/projects/:id/packages/:package_id',
groupProjectsPath: '/api/:version/groups/:id/projects.json',
+ groupSharePath: '/api/:version/groups/:id/share',
projectsPath: '/api/:version/projects.json',
projectPath: '/api/:version/projects/:id',
forkedProjectsPath: '/api/:version/projects/:id/forks',
@@ -39,6 +40,7 @@ const Api = {
projectRunnersPath: '/api/:version/projects/:id/runners',
projectProtectedBranchesPath: '/api/:version/projects/:id/protected_branches',
projectSearchPath: '/api/:version/projects/:id/search',
+ projectSharePath: '/api/:version/projects/:id/share',
projectMilestonesPath: '/api/:version/projects/:id/milestones',
projectIssuePath: '/api/:version/projects/:id/issues/:issue_iid',
mergeRequestsPath: '/api/:version/merge_requests',
@@ -365,6 +367,16 @@ const Api = {
});
},
+ projectShareWithGroup(id, options = {}) {
+ const url = Api.buildUrl(Api.projectSharePath).replace(':id', encodeURIComponent(id));
+
+ return axios.post(url, {
+ expires_at: options.expires_at,
+ group_access: options.group_access,
+ group_id: options.group_id,
+ });
+ },
+
projectMilestones(id, params = {}) {
const url = Api.buildUrl(Api.projectMilestonesPath).replace(':id', encodeURIComponent(id));
@@ -426,6 +438,16 @@ const Api = {
});
},
+ groupShareWithGroup(id, options = {}) {
+ const url = Api.buildUrl(Api.groupSharePath).replace(':id', encodeURIComponent(id));
+
+ return axios.post(url, {
+ expires_at: options.expires_at,
+ group_access: options.group_access,
+ group_id: options.group_id,
+ });
+ },
+
commit(id, sha, params = {}) {
const url = Api.buildUrl(this.commitPath)
.replace(':id', encodeURIComponent(id))
diff --git a/app/assets/javascripts/invite_members/components/group_select.vue b/app/assets/javascripts/invite_members/components/group_select.vue
new file mode 100644
index 00000000000..4a72e97db8c
--- /dev/null
+++ b/app/assets/javascripts/invite_members/components/group_select.vue
@@ -0,0 +1,103 @@
+<script>
+import { GlDropdown, GlDropdownItem, GlDropdownText, GlSearchBoxByType } from '@gitlab/ui';
+import { debounce } from 'lodash';
+import Api from '~/api';
+import { s__ } from '~/locale';
+import { SEARCH_DELAY } from '../constants';
+
+export default {
+ name: 'GroupSelect',
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownText,
+ GlSearchBoxByType,
+ },
+ model: {
+ prop: 'selectedGroup',
+ },
+ data() {
+ return {
+ isFetching: false,
+ groups: [],
+ selectedGroup: {},
+ searchTerm: '',
+ };
+ },
+ computed: {
+ selectedGroupName() {
+ return this.selectedGroup.name || this.$options.i18n.dropdownText;
+ },
+ isFetchResultEmpty() {
+ return this.groups.length === 0;
+ },
+ },
+ watch: {
+ searchTerm() {
+ this.retrieveGroups();
+ },
+ },
+ mounted() {
+ this.retrieveGroups();
+ },
+ methods: {
+ retrieveGroups: debounce(function debouncedRetrieveGroups() {
+ this.isFetching = true;
+ return Api.groups(this.searchTerm, this.$options.defaultFetchOptions)
+ .then((response) => {
+ this.groups = response.map((group) => ({
+ id: group.id,
+ name: group.full_name,
+ path: group.path,
+ }));
+ this.isFetching = false;
+ })
+ .catch(() => {
+ this.isFetching = false;
+ });
+ }, SEARCH_DELAY),
+ selectGroup(group) {
+ this.selectedGroup = group;
+
+ this.$emit('input', this.selectedGroup);
+ },
+ },
+ i18n: {
+ dropdownText: s__('GroupSelect|Select a group'),
+ searchPlaceholder: s__('GroupSelect|Search groups'),
+ emptySearchResult: s__('GroupSelect|No matching results'),
+ },
+ defaultFetchOptions: {
+ exclude_internal: true,
+ active: true,
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-dropdown
+ data-testid="group-select-dropdown"
+ :text="selectedGroupName"
+ block
+ menu-class="gl-w-full!"
+ >
+ <gl-search-box-by-type
+ v-model.trim="searchTerm"
+ :is-loading="isFetching"
+ :placeholder="$options.i18n.searchPlaceholder"
+ data-qa-selector="group_select_dropdown_search_field"
+ />
+ <gl-dropdown-item
+ v-for="group in groups"
+ :key="group.id"
+ :name="group.name"
+ @click="selectGroup(group)"
+ >
+ {{ group.name }}
+ </gl-dropdown-item>
+ <gl-dropdown-text v-if="isFetchResultEmpty && !isFetching" data-testid="empty-result-message">
+ <span class="gl-text-gray-500">{{ $options.i18n.emptySearchResult }}</span>
+ </gl-dropdown-text>
+ </gl-dropdown>
+ </div>
+</template>
diff --git a/app/assets/javascripts/invite_members/components/invite_group_trigger.vue b/app/assets/javascripts/invite_members/components/invite_group_trigger.vue
new file mode 100644
index 00000000000..c9de078319a
--- /dev/null
+++ b/app/assets/javascripts/invite_members/components/invite_group_trigger.vue
@@ -0,0 +1,34 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import eventHub from '../event_hub';
+
+export default {
+ components: {
+ GlButton,
+ },
+ props: {
+ displayText: {
+ type: String,
+ required: false,
+ default: s__('InviteMembers|Invite a group'),
+ },
+ classes: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ methods: {
+ openModal() {
+ eventHub.$emit('openModal', { inviteeType: 'group' });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-button :class="classes" data-qa-selector="invite_a_group_button" @click="openModal">
+ {{ displayText }}
+ </gl-button>
+</template>
diff --git a/app/assets/javascripts/invite_members/components/invite_members_modal.vue b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
index f5a65882fba..cd9c3b0b5d3 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
@@ -11,9 +11,10 @@ import {
} from '@gitlab/ui';
import { partition, isString } from 'lodash';
import Api from '~/api';
+import GroupSelect from '~/invite_members/components/group_select.vue';
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
-import { s__, __, sprintf } from '~/locale';
+import { s__, sprintf } from '~/locale';
import eventHub from '../event_hub';
export default {
@@ -28,6 +29,7 @@ export default {
GlButton,
GlFormInput,
MembersTokenSelect,
+ GroupSelect,
},
props: {
id: {
@@ -60,21 +62,21 @@ export default {
visible: true,
modalId: 'invite-members-modal',
selectedAccessLevel: this.defaultAccessLevel,
+ inviteeType: 'members',
newUsersToInvite: [],
selectedDate: undefined,
+ groupToBeSharedWith: {},
};
},
computed: {
- inviteToName() {
- return this.name.toUpperCase();
- },
- inviteToType() {
- return this.isProject ? __('project') : __('group');
+ isInviteGroup() {
+ return this.inviteeType === 'group';
},
introText() {
- return sprintf(s__("InviteMembersModal|You're inviting members to the %{name} %{type}"), {
- name: this.inviteToName,
- type: this.inviteToType,
+ const inviteTo = this.isProject ? 'toProject' : 'toGroup';
+
+ return sprintf(this.$options.labels[this.inviteeType][inviteTo].introText, {
+ name: this.name.toUpperCase(),
});
},
toastOptions() {
@@ -82,12 +84,12 @@ export default {
onComplete: () => {
this.selectedAccessLevel = this.defaultAccessLevel;
this.newUsersToInvite = [];
+ this.groupToBeSharedWith = {};
},
};
},
basePostData() {
return {
- access_level: this.selectedAccessLevel,
expires_at: this.selectedDate,
format: 'json',
};
@@ -97,9 +99,16 @@ export default {
(key) => this.accessLevels[key] === Number(this.selectedAccessLevel),
);
},
+ inviteDisabled() {
+ return (
+ this.newUsersToInvite.length === 0 && Object.keys(this.groupToBeSharedWith).length === 0
+ );
+ },
},
mounted() {
- eventHub.$on('openModal', this.openModal);
+ eventHub.$on('openModal', (options) => {
+ this.openModal(options);
+ });
},
methods: {
partitionNewUsersToInvite() {
@@ -113,26 +122,42 @@ export default {
usersToAddById.map((user) => user.id).join(','),
];
},
- openModal() {
+ openModal({ inviteeType }) {
+ this.inviteeType = inviteeType;
+
this.$root.$emit(BV_SHOW_MODAL, this.modalId);
},
closeModal() {
this.$root.$emit(BV_HIDE_MODAL, this.modalId);
},
sendInvite() {
- this.submitForm();
+ if (this.isInviteGroup) {
+ this.submitShareWithGroup();
+ } else {
+ this.submitInviteMembers();
+ }
this.closeModal();
},
cancelInvite() {
this.selectedAccessLevel = this.defaultAccessLevel;
this.selectedDate = undefined;
- this.newUsersToInvite = '';
+ this.newUsersToInvite = [];
+ this.groupToBeSharedWith = {};
this.closeModal();
},
changeSelectedItem(item) {
this.selectedAccessLevel = item;
},
- submitForm() {
+ submitShareWithGroup() {
+ const apiShareWithGroup = this.isProject
+ ? Api.projectShareWithGroup.bind(Api)
+ : Api.groupShareWithGroup.bind(Api);
+
+ apiShareWithGroup(this.id, this.shareWithGroupPostData(this.groupToBeSharedWith.id))
+ .then(this.showToastMessageSuccess)
+ .catch(this.showToastMessageError);
+ },
+ submitInviteMembers() {
const [usersToInviteByEmail, usersToAddById] = this.partitionNewUsersToInvite();
const promises = [];
@@ -155,10 +180,25 @@ export default {
Promise.all(promises).then(this.showToastMessageSuccess).catch(this.showToastMessageError);
},
inviteByEmailPostData(usersToInviteByEmail) {
- return { ...this.basePostData, email: usersToInviteByEmail };
+ return {
+ ...this.basePostData,
+ email: usersToInviteByEmail,
+ access_level: this.selectedAccessLevel,
+ };
},
addByUserIdPostData(usersToAddById) {
- return { ...this.basePostData, user_id: usersToAddById };
+ return {
+ ...this.basePostData,
+ user_id: usersToAddById,
+ access_level: this.selectedAccessLevel,
+ };
+ },
+ shareWithGroupPostData(groupToBeSharedWith) {
+ return {
+ ...this.basePostData,
+ group_id: groupToBeSharedWith,
+ group_access: this.selectedAccessLevel,
+ };
},
showToastMessageSuccess() {
this.$toast.show(this.$options.labels.toastMessageSuccessful, this.toastOptions);
@@ -170,9 +210,28 @@ export default {
},
},
labels: {
- modalTitle: s__('InviteMembersModal|Invite team members'),
- newUsersToInvite: s__('InviteMembersModal|GitLab member or Email address'),
- userPlaceholder: s__('InviteMembersModal|Search for members to invite'),
+ members: {
+ modalTitle: s__('InviteMembersModal|Invite team members'),
+ searchField: s__('InviteMembersModal|GitLab member or Email address'),
+ placeHolder: s__('InviteMembersModal|Search for members to invite'),
+ toGroup: {
+ introText: s__("InviteMembersModal|You're inviting members to the %{name} group"),
+ },
+ toProject: {
+ introText: s__("InviteMembersModal|You're inviting members to the %{name} project"),
+ },
+ },
+ group: {
+ modalTitle: s__('InviteMembersModal|Invite a group'),
+ searchField: s__('InviteMembersModal|Select a group to invite'),
+ placeHolder: s__('InviteMembersModal|Search for a group to invite'),
+ toGroup: {
+ introText: s__("InviteMembersModal|You're inviting a group to the %{name} group"),
+ },
+ toProject: {
+ introText: s__("InviteMembersModal|You're inviting a group to the %{name} project"),
+ },
+ },
accessLevel: s__('InviteMembersModal|Choose a role permission'),
accessExpireDate: s__('InviteMembersModal|Access expiration date (optional)'),
toastMessageSuccessful: s__('InviteMembersModal|Members were successfully added'),
@@ -189,27 +248,34 @@ export default {
<gl-modal
:modal-id="modalId"
size="sm"
- :title="$options.labels.modalTitle"
+ data-qa-selector="invite_members_modal_content"
+ :title="$options.labels[inviteeType].modalTitle"
:header-close-label="$options.labels.headerCloseLabel"
>
- <div class="gl-ml-5 gl-mr-5">
- <div>{{ introText }}</div>
+ <div>
+ <p ref="introText">{{ introText }}</p>
<label :id="$options.membersTokenSelectLabelId" class="gl-font-weight-bold gl-mt-5">{{
- $options.labels.newUsersToInvite
+ $options.labels[inviteeType].searchField
}}</label>
<div class="gl-mt-2">
<members-token-select
+ v-if="!isInviteGroup"
v-model="newUsersToInvite"
- :label="$options.labels.newUsersToInvite"
:aria-labelledby="$options.membersTokenSelectLabelId"
- :placeholder="$options.labels.userPlaceholder"
+ :placeholder="$options.labels[inviteeType].placeHolder"
/>
+ <group-select v-if="isInviteGroup" v-model="groupToBeSharedWith" />
</div>
- <label class="gl-font-weight-bold gl-mt-5">{{ $options.labels.accessLevel }}</label>
+ <label class="gl-font-weight-bold gl-mt-3">{{ $options.labels.accessLevel }}</label>
<div class="gl-mt-2 gl-w-half gl-xs-w-full">
- <gl-dropdown class="gl-shadow-none gl-w-full" v-bind="$attrs" :text="selectedRoleName">
+ <gl-dropdown
+ class="gl-shadow-none gl-w-full"
+ data-qa-selector="access_level_dropdown"
+ v-bind="$attrs"
+ :text="selectedRoleName"
+ >
<template v-for="(key, item) in accessLevels">
<gl-dropdown-item
:key="key"
@@ -223,7 +289,7 @@ export default {
</gl-dropdown>
</div>
- <div class="gl-mt-2">
+ <div class="gl-mt-2 gl-w-half gl-xs-w-full">
<gl-sprintf :message="$options.labels.readMoreText">
<template #link="{ content }">
<gl-link :href="helpLink" target="_blank">{{ content }}</gl-link>
@@ -231,7 +297,7 @@ export default {
</gl-sprintf>
</div>
- <label class="gl-font-weight-bold gl-mt-5" for="expires_at">{{
+ <label class="gl-font-weight-bold gl-mt-5 gl-display-block" for="expires_at">{{
$options.labels.accessExpireDate
}}</label>
<div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block">
@@ -253,15 +319,16 @@ export default {
</div>
<template #modal-footer>
- <div class="gl-display-flex gl-flex-direction-row gl-justify-content-end gl-flex-wrap gl-p-3">
+ <div class="gl-display-flex gl-flex-direction-row gl-justify-content-end gl-flex-wrap gl-m-0">
<gl-button ref="cancelButton" @click="cancelInvite">
{{ $options.labels.cancelButtonText }}
</gl-button>
<div class="gl-mr-3"></div>
<gl-button
ref="inviteButton"
- :disabled="!newUsersToInvite"
+ :disabled="inviteDisabled"
variant="success"
+ data-qa-selector="invite_button"
@click="sendInvite"
>{{ $options.labels.inviteButtonText }}</gl-button
>
diff --git a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
index eb97c458f88..f8cc74511d9 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
@@ -27,14 +27,14 @@ export default {
},
methods: {
openModal() {
- eventHub.$emit('openModal');
+ eventHub.$emit('openModal', { inviteeType: 'members' });
},
},
};
</script>
<template>
- <gl-link :class="classes" @click="openModal">
+ <gl-link :class="classes" data-qa-selector="invite_members_button" @click="openModal">
<div v-if="icon" class="nav-icon-container">
<gl-icon :size="16" :name="icon" />
</div>
diff --git a/app/assets/javascripts/invite_members/components/members_token_select.vue b/app/assets/javascripts/invite_members/components/members_token_select.vue
index 233a214013b..db6a7888786 100644
--- a/app/assets/javascripts/invite_members/components/members_token_select.vue
+++ b/app/assets/javascripts/invite_members/components/members_token_select.vue
@@ -3,7 +3,7 @@ import { GlTokenSelector, GlAvatar, GlAvatarLabeled, GlSprintf } from '@gitlab/u
import { debounce } from 'lodash';
import { __ } from '~/locale';
import { getUsers } from '~/rest_api';
-import { USER_SEARCH_DELAY } from '../constants';
+import { SEARCH_DELAY } from '../constants';
export default {
components: {
@@ -67,7 +67,7 @@ export default {
.catch(() => {
this.loading = false;
});
- }, USER_SEARCH_DELAY),
+ }, SEARCH_DELAY),
handleInput() {
this.$emit('input', this.selectedTokens);
},
diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js
index 1ff2125c292..2044dad896f 100644
--- a/app/assets/javascripts/invite_members/constants.js
+++ b/app/assets/javascripts/invite_members/constants.js
@@ -1 +1 @@
-export const USER_SEARCH_DELAY = 200;
+export const SEARCH_DELAY = 200;
diff --git a/app/assets/javascripts/invite_members/init_invite_group_trigger.js b/app/assets/javascripts/invite_members/init_invite_group_trigger.js
new file mode 100644
index 00000000000..c01bb1bae28
--- /dev/null
+++ b/app/assets/javascripts/invite_members/init_invite_group_trigger.js
@@ -0,0 +1,20 @@
+import Vue from 'vue';
+import InviteGroupTrigger from '~/invite_members/components/invite_group_trigger.vue';
+
+export default function initInviteGroupTrigger() {
+ const el = document.querySelector('.js-invite-group-trigger');
+
+ if (!el) {
+ return false;
+ }
+
+ return new Vue({
+ el,
+ render: (createElement) =>
+ createElement(InviteGroupTrigger, {
+ props: {
+ ...el.dataset,
+ },
+ }),
+ });
+}
diff --git a/app/assets/javascripts/jira_connect/components/app.vue b/app/assets/javascripts/jira_connect/components/app.vue
index a4ba86dc6a1..4290971623e 100644
--- a/app/assets/javascripts/jira_connect/components/app.vue
+++ b/app/assets/javascripts/jira_connect/components/app.vue
@@ -60,15 +60,15 @@ export default {
<template>
<div>
- <gl-alert v-if="errorMessage" class="gl-mb-6" variant="danger" :dismissible="false">
+ <gl-alert v-if="errorMessage" class="gl-mb-7" variant="danger" :dismissible="false">
{{ errorMessage }}
</gl-alert>
- <h2>{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
+ <h2 class="gl-text-center">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
<div
v-if="showNewUI"
- class="gl-display-flex gl-justify-content-space-between gl-my-7 gl-pb-4 gl-border-b-solid gl-border-b-1 gl-border-b-gray-200"
+ class="jira-connect-app-body gl-display-flex gl-justify-content-space-between gl-my-7 gl-pb-4 gl-border-b-solid gl-border-b-1 gl-border-b-gray-200"
>
<h5 class="gl-align-self-center gl-mb-0" data-testid="new-jira-connect-ui-heading">
{{ s__('Integrations|Linked namespaces') }}
diff --git a/app/assets/javascripts/jira_connect/index.js b/app/assets/javascripts/jira_connect/index.js
index 7191fce3c33..96029e2711b 100644
--- a/app/assets/javascripts/jira_connect/index.js
+++ b/app/assets/javascripts/jira_connect/index.js
@@ -77,6 +77,7 @@ export async function initJiraConnect() {
Vue.use(GlFeatureFlagsPlugin);
const { groupsPath, subscriptionsPath, usersPath } = el.dataset;
+ AP.sizeToParent();
return new Vue({
el,
diff --git a/app/assets/javascripts/pages/groups/group_members/index.js b/app/assets/javascripts/pages/groups/group_members/index.js
index 3496f699b06..63afc6fe94d 100644
--- a/app/assets/javascripts/pages/groups/group_members/index.js
+++ b/app/assets/javascripts/pages/groups/group_members/index.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import { groupMemberRequestFormatter } from '~/groups/members/utils';
import groupsSelect from '~/groups_select';
+import initInviteGroupTrigger from '~/invite_members/init_invite_group_trigger';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import { s__ } from '~/locale';
@@ -70,5 +71,6 @@ memberExpirationDate('.js-access-expiration-date-groups');
mountRemoveMemberModal();
initInviteMembersModal();
initInviteMembersTrigger();
+initInviteGroupTrigger();
new UsersSelect(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/feature_flags_user_lists/new/index.js b/app/assets/javascripts/pages/projects/feature_flags_user_lists/new/index.js
index 679f0af8efc..e855447d5ce 100644
--- a/app/assets/javascripts/pages/projects/feature_flags_user_lists/new/index.js
+++ b/app/assets/javascripts/pages/projects/feature_flags_user_lists/new/index.js
@@ -1,3 +1,5 @@
+/* eslint-disable no-new */
+
import Vue from 'vue';
import Vuex from 'vuex';
import NewUserList from '~/user_lists/components/new_user_list.vue';
@@ -5,18 +7,16 @@ import createStore from '~/user_lists/store/new';
Vue.use(Vuex);
-document.addEventListener('DOMContentLoaded', () => {
- const el = document.getElementById('js-new-user-list');
- const { userListsDocsPath, featureFlagsPath } = el.dataset;
- return new Vue({
- el,
- store: createStore(el.dataset),
- provide: {
- userListsDocsPath,
- featureFlagsPath,
- },
- render(h) {
- return h(NewUserList);
- },
- });
+const el = document.getElementById('js-new-user-list');
+const { userListsDocsPath, featureFlagsPath } = el.dataset;
+new Vue({
+ el,
+ store: createStore(el.dataset),
+ provide: {
+ userListsDocsPath,
+ featureFlagsPath,
+ },
+ render(h) {
+ return h(NewUserList);
+ },
});
diff --git a/app/assets/javascripts/pages/projects/project_members/index.js b/app/assets/javascripts/pages/projects/project_members/index.js
index ed11b07be4a..8d403b7688a 100644
--- a/app/assets/javascripts/pages/projects/project_members/index.js
+++ b/app/assets/javascripts/pages/projects/project_members/index.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import { deprecatedCreateFlash as flash } from '~/flash';
import groupsSelect from '~/groups_select';
+import initInviteGroupTrigger from '~/invite_members/init_invite_group_trigger';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import { __ } from '~/locale';
@@ -29,6 +30,7 @@ memberExpirationDate('.js-access-expiration-date-groups');
mountRemoveMemberModal();
initInviteMembersModal();
initInviteMembersTrigger();
+initInviteGroupTrigger();
new Members(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue
index 8aa6e29adf1..c5fdb5fc242 100644
--- a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue
+++ b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue
@@ -1,11 +1,11 @@
<script>
+import { GlTooltipDirective as GlTooltip } from '@gitlab/ui';
import { isFunction } from 'lodash';
import { hasHorizontalOverflow } from '~/lib/utils/dom_utils';
-import tooltip from '../directives/tooltip';
export default {
directives: {
- tooltip,
+ GlTooltip,
},
props: {
title: {
@@ -59,9 +59,8 @@ export default {
<template>
<span
v-if="showTooltip"
- v-tooltip
+ v-gl-tooltip="{ placement }"
:title="title"
- :data-placement="placement"
class="js-show-tooltip gl-min-w-0"
>
<slot></slot>
diff --git a/app/assets/javascripts/vue_shared/directives/tooltip.js b/app/assets/javascripts/vue_shared/directives/tooltip.js
deleted file mode 100644
index 0eb505bfce8..00000000000
--- a/app/assets/javascripts/vue_shared/directives/tooltip.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import $ from 'jquery';
-import '~/commons/bootstrap';
-import { parseBoolean } from '~/lib/utils/common_utils';
-
-export default {
- bind(el) {
- const glTooltipDelay = localStorage.getItem('gl-tooltip-delay');
- const delay = glTooltipDelay ? JSON.parse(glTooltipDelay) : 0;
-
- $(el).tooltip({
- trigger: 'hover',
- delay,
- // By default, sanitize is run even if there is no `html` or `template` present
- // so let's optimize to only run this when necessary.
- // https://github.com/twbs/bootstrap/blob/c5966de27395a407f9a3d20d0eb2ff8e8fb7b564/js/src/tooltip.js#L716
- sanitize: parseBoolean(el.dataset.html) || Boolean(el.dataset.template),
- });
- },
-
- componentUpdated(el) {
- $(el).tooltip('_fixTitle');
-
- // update visible tooltips
- const tooltipInstance = $(el).data('bs.tooltip');
- const tip = tooltipInstance.getTipElement();
- tooltipInstance.setElementContent(
- $(tip.querySelectorAll('.tooltip-inner')),
- tooltipInstance.getTitle(),
- );
- },
-
- unbind(el) {
- $(el).tooltip('dispose');
- },
-};
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 7931f4deea0..1fe94a796f5 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -42,7 +42,6 @@
@import 'framework/notes';
@import 'framework/tabs';
@import 'framework/timeline';
-@import 'framework/tooltips';
@import 'framework/toggle';
@import 'framework/typography';
@import 'framework/zen';
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index b0334da6943..b81e9828dec 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -283,12 +283,6 @@ ul.indent-list {
to { transform: rotate(360deg); }
}
-.namespace-title {
- .tooltip-inner {
- max-width: 350px;
- }
-}
-
.horizontal-list {
padding-left: 0;
list-style: none;
diff --git a/app/assets/stylesheets/framework/tooltips.scss b/app/assets/stylesheets/framework/tooltips.scss
deleted file mode 100644
index edc2fb532c8..00000000000
--- a/app/assets/stylesheets/framework/tooltips.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-.tooltip-inner {
- font-size: $gl-font-size-small;
- border-radius: $border-radius-default;
- line-height: $gl-line-height;
- font-weight: $gl-font-weight-normal;
-}
diff --git a/app/assets/stylesheets/page_bundles/jira_connect.scss b/app/assets/stylesheets/page_bundles/jira_connect.scss
index 25401a161da..eb2dd6e578e 100644
--- a/app/assets/stylesheets/page_bundles/jira_connect.scss
+++ b/app/assets/stylesheets/page_bundles/jira_connect.scss
@@ -20,36 +20,23 @@
@import '@gitlab/ui/src/components/base/tooltip/tooltip';
$atlaskit-border-color: #dfe1e6;
+$header-height: 40px;
-.ac-content {
- margin: 20px;
-
- .subscription-form {
- margin-bottom: 20px;
-
- .field-group-input {
- display: flex;
- padding-top: $gl-padding-4;
+.subscription-form {
+ .field-group-input {
+ display: flex;
+ padding-top: $gl-padding-4;
- .ak-button {
- align-items: center;
- height: auto;
- margin-left: $btn-margin-5;
- }
+ .ak-button {
+ align-items: center;
+ height: auto;
+ margin-left: $btn-margin-5;
}
}
}
-$header-height: 40px;
-
.jira-connect-header {
- border-bottom: 1px solid $gray-100;
- display: flex;
- align-items: center;
- justify-content: center;
min-height: $header-height;
- padding-left: 16px;
- padding-right: 16px;
position: fixed;
top: 0;
left: 0;
@@ -57,7 +44,6 @@ $header-height: 40px;
}
.jira-connect-user {
- font-size: $gl-font-size;
position: fixed;
top: 10px;
right: 20px;
@@ -65,20 +51,17 @@ $header-height: 40px;
.jira-connect-app {
margin-top: $header-height;
+ height: calc(100% - #{$header-height});
+ max-width: 1000px;
+}
+
+.jira-connect-app-body {
max-width: 600px;
- min-height: 95vh;
- padding-top: 48px;
- padding-left: 16px;
- padding-right: 16px;
margin-left: auto;
margin-right: auto;
- text-align: center;
-}
-
-.gl-mt-5 {
- margin-top: 16px;
}
+// for external_link buttons
svg {
fill: currentColor;
@@ -115,12 +98,3 @@ svg {
}
}
}
-
-.empty-subscriptions {
- color: $gray-900;
-}
-
-.browser-limitations-notice {
- font-size: $gl-font-size;
- margin-top: 32px;
-}
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index a5257ff20bc..de9859230d5 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -16,8 +16,9 @@
= html_escape(_('You can invite a new member to %{strong_start}%{group_name}%{strong_end}.')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- if can_invite_members_for_group?(@group)
.gl-w-half.gl-xs-w-full
- .gl-display-flex.gl-flex-wrap.gl-lg-justify-content-end.gl-mx-n2.gl-mb-3
- .js-invite-members-trigger.gl-px-2.gl-sm-w-auto.gl-w-full.gl-mb-4{ data: { classes: 'btn btn-success gl-button gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite members') } }
+ .gl-display-flex.gl-flex-wrap.gl-justify-content-end.gl-mb-3
+ .js-invite-group-trigger{ data: { classes: 'gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite a group') } }
+ .js-invite-members-trigger{ data: { classes: 'btn btn-success gl-button gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite members') } }
= render 'groups/invite_members_modal', group: @group
- if can_manage_members && !can_invite_members_for_group?(@group)
%hr.gl-mt-4
diff --git a/app/views/jira_connect/subscriptions/index.html.haml b/app/views/jira_connect/subscriptions/index.html.haml
index a549ed3540b..6a549227185 100644
--- a/app/views/jira_connect/subscriptions/index.html.haml
+++ b/app/views/jira_connect/subscriptions/index.html.haml
@@ -1,61 +1,62 @@
-%header.jira-connect-header
- = brand_header_logo
+%header.jira-connect-header.gl-display-flex.gl-align-items-center.gl-justify-content-center.gl-px-5.gl-border-b-solid.gl-border-b-gray-100.gl-border-b-1.gl-bg-white
+ = link_to brand_header_logo, Gitlab.config.gitlab.url, target: '_blank', rel: 'noopener noreferrer'
-.jira-connect-user
+.jira-connect-user.gl-font-base
- if current_user
- user_link = link_to(current_user.to_reference, jira_connect_users_path, target: '_blank', rel: 'noopener noreferrer', class: 'js-jira-connect-sign-in')
= _('Signed in to GitLab as %{user_link}').html_safe % { user_link: user_link }
- elsif @subscriptions.present?
= link_to _('Sign in to GitLab'), jira_connect_users_path, target: '_blank', rel: 'noopener noreferrer', class: 'js-jira-connect-sign-in'
-.jira-connect-app
+%main.jira-connect-app.gl-px-5.gl-pt-7.gl-mx-auto
- if current_user.blank? && @subscriptions.empty?
- %h2= s_('JiraService|GitLab for Jira Configuration')
- %p= s_('JiraService|Sign in to GitLab.com to get started.')
+ .jira-connect-app-body.gl-text-center
+ %h2= s_('JiraService|GitLab for Jira Configuration')
+ %p= s_('JiraService|Sign in to GitLab.com to get started.')
- .gl-mt-7
- - sign_in_button_class = new_jira_connect_ui? ? 'btn gl-button btn-confirm' : 'ak-button ak-button__appearance-primary'
- = external_link _('Sign in to GitLab'), jira_connect_users_path, class: "#{sign_in_button_class} js-jira-connect-sign-in"
+ .gl-mt-7
+ - sign_in_button_class = new_jira_connect_ui? ? 'btn gl-button btn-confirm' : 'ak-button ak-button__appearance-primary'
+ = external_link _('Sign in to GitLab'), jira_connect_users_path, class: "#{sign_in_button_class} js-jira-connect-sign-in"
- .gl-mt-7
- %p Note: this integration only works with accounts on GitLab.com (SaaS).
+ .gl-mt-7
+ %p= s_('Integrations|Note: this integration only works with accounts on GitLab.com (SaaS).')
- else
.js-jira-connect-app{ data: jira_connect_app_data(@subscriptions) }
- - unless new_jira_connect_ui?
- %form#add-subscription-form.subscription-form{ action: jira_connect_subscriptions_path }
- .ak-field-group
- %label
- GitLab namespace
+ .jira-connect-app-body
+ - unless new_jira_connect_ui?
+ %form#add-subscription-form.subscription-form.gl-mb-5{ action: jira_connect_subscriptions_path }
+ .ak-field-group
+ %label= _('GitLab namespace')
- .ak-field-group.field-group-input
- %input#namespace-input.ak-field-text{ type: 'text', required: true, placeholder: 'e.g. "MyCompany" or "MyCompany/GroupName"' }
- %button.ak-button.ak-button__appearance-primary{ type: 'submit' }
- Link namespace to Jira
+ .ak-field-group.field-group-input
+ %input#namespace-input.ak-field-text{ type: 'text', required: true, placeholder: 'e.g. "MyCompany" or "MyCompany/GroupName"' }
+ %button.ak-button.ak-button__appearance-primary{ type: 'submit' }
+ = s_('Integrations|Link namespace to Jira')
- - if @subscriptions.present?
- %table.subscriptions.gl-w-full
- %thead
- %tr
- %th Namespace
- %th Added
- %th
- %tbody
- - @subscriptions.each do |subscription|
+ - if @subscriptions.present?
+ %table.subscriptions.gl-w-full
+ %thead
%tr
- %td= subscription.namespace.full_path
- %td= subscription.created_at
- %td= link_to 'Remove', jira_connect_subscription_path(subscription), class: 'js-jira-connect-remove-subscription'
- - else
- %h4.empty-subscriptions
- No linked namespaces
- %p= s_('Integrations|Namespaces are your GitLab groups and subgroups that will be linked to this Jira instance.')
+ %th= _('Namespace')
+ %th= _('Added')
+ %th
+ %tbody
+ - @subscriptions.each do |subscription|
+ %tr
+ %td= subscription.namespace.full_path
+ %td= subscription.created_at
+ %td= link_to _('Remove'), jira_connect_subscription_path(subscription), class: 'js-jira-connect-remove-subscription'
+ - else
+ .gl-text-center
+ %h4= s_('Integrations|No linked namespaces')
+ %p= s_('Integrations|Namespaces are your GitLab groups and subgroups that will be linked to this Jira instance.')
- %p.browser-limitations-notice
- %strong Browser limitations:
- Adding a namespace currently works only in browsers that allow cross‑site cookies. Please make sure to use
- %a{ href: 'https://www.mozilla.org/en-US/firefox/', target: '_blank', rel: 'noopener noreferrer' } Firefox
- or enable cross‑site cookies in your browser when adding a namespace.
+ %p.jira-connect-app-body.gl-mt-7.gl-font-base.gl-text-center
+ %strong= s_('Integrations|Browser limitations')
+ - firefox_link_url = 'https://www.mozilla.org/en-US/firefox/'
+ - firefox_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: firefox_link_url }
+ = s_('Integrations|Adding a namespace currently works only in browsers that allow cross‑site cookies. Please make sure to use %{firefox_link_start}Firefox%{firefox_link_end} or enable cross‑site cookies in your browser when adding a namespace.').html_safe % { firefox_link_start: firefox_link_start, firefox_link_end: '</a>'.html_safe }
= link_to _('Learn more'), 'https://gitlab.com/gitlab-org/gitlab/-/issues/284211', target: '_blank', rel: 'noopener noreferrer'
= webpack_bundle_tag 'performance_bar' if performance_bar_enabled?
diff --git a/app/views/layouts/jira_connect.html.haml b/app/views/layouts/jira_connect.html.haml
index da45d84a83b..e9da940ca7a 100644
--- a/app/views/layouts/jira_connect.html.haml
+++ b/app/views/layouts/jira_connect.html.haml
@@ -12,5 +12,4 @@
= Gon::Base.render_data(nonce: content_security_policy_nonce)
= yield :head
%body
- .ac-content
- = yield
+ = yield
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index beb435d268a..509092b64c9 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -18,8 +18,9 @@
%p
= html_escape(_("Members can be added by project %{i_open}Maintainers%{i_close} or %{i_open}Owners%{i_close}")) % { i_open: '<i>'.html_safe, i_close: '</i>'.html_safe }
.col-md-12.col-lg-6
- .gl-display-flex.gl-flex-wrap.gl-lg-justify-content-end.gl-mx-n2.gl-mb-3
- .js-invite-members-trigger.gl-px-2.gl-sm-w-auto.gl-w-full.gl-mb-4{ data: { classes: 'btn btn-success gl-button gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite members') } }
+ .gl-display-flex.gl-flex-wrap.gl-justify-content-end
+ .js-invite-group-trigger{ data: { classes: 'gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite a group') } }
+ .js-invite-members-trigger{ data: { classes: 'btn btn-success gl-button gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite members') } }
= render 'projects/invite_members_modal', project: @project
- else
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 59c6b27e942..b8481382577 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -355,14 +355,6 @@
:weight: 1
:idempotent:
:tags: []
-- :name: cronjob:releases_create_evidence
- :feature_category: :release_evidence
- :has_external_dependencies:
- :urgency: :low
- :resource_boundary: :unknown
- :weight: 1
- :idempotent:
- :tags: []
- :name: cronjob:releases_manage_evidence
:feature_category: :release_evidence
:has_external_dependencies:
@@ -2100,6 +2092,14 @@
:weight: 2
:idempotent:
:tags: []
+- :name: releases_create_evidence
+ :feature_category: :release_evidence
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent:
+ :tags: []
- :name: remote_mirror_notification
:feature_category: :source_code_management
:has_external_dependencies:
diff --git a/app/workers/releases/create_evidence_worker.rb b/app/workers/releases/create_evidence_worker.rb
index db75fae1639..d22329216f9 100644
--- a/app/workers/releases/create_evidence_worker.rb
+++ b/app/workers/releases/create_evidence_worker.rb
@@ -3,7 +3,6 @@
module Releases
class CreateEvidenceWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
- include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :release_evidence