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-01-08 18:10:26 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-01-08 18:10:26 +0300
commit6e734c809b18a0470d81c78e1ecd9b3f8278de89 (patch)
tree882251dce981323bc3c6e305cbc0f41aa4c39aae /app
parent9157fbe06fde892c647403f477ce31c888cbc822 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/api.js18
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue13
-rw-r--r--app/assets/javascripts/boards/graphql/board_destroy.mutation.graphql7
-rw-r--r--app/assets/javascripts/boards/index.js1
-rw-r--r--app/assets/javascripts/boards/mount_multiple_boards_switcher.js1
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js4
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue6
-rw-r--r--app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue19
-rw-r--r--app/assets/javascripts/import_entities/import_projects/store/getters.js12
-rw-r--r--app/assets/javascripts/import_entities/import_projects/utils.js6
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_modal.vue55
-rw-r--r--app/assets/javascripts/invite_members/components/members_token_select.vue29
-rw-r--r--app/assets/javascripts/invite_members/init_invite_members_modal.js2
-rw-r--r--app/helpers/projects/alert_management_helper.rb2
-rw-r--r--app/models/ci/build.rb8
-rw-r--r--app/models/project_services/alerts_service.rb2
-rw-r--r--app/models/user.rb1
-rw-r--r--app/views/admin/users/_admin_notes.html.haml2
-rw-r--r--app/views/groups/_invite_members_modal.html.haml2
-rw-r--r--app/views/groups/group_members/index.html.haml84
-rw-r--r--app/views/projects/_invite_members_modal.html.haml2
-rw-r--r--app/views/projects/jobs/index.html.haml5
22 files changed, 179 insertions, 102 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 569fbb9ab43..2f3a56ec046 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -13,6 +13,7 @@ const Api = {
groupMilestonesPath: '/api/:version/groups/:id/milestones',
subgroupsPath: '/api/:version/groups/:id/subgroups',
namespacesPath: '/api/:version/namespaces.json',
+ groupInvitationsPath: '/api/:version/groups/:id/invitations',
groupPackagesPath: '/api/:version/groups/:id/packages',
projectPackagesPath: '/api/:version/projects/:id/packages',
projectPackagePath: '/api/:version/projects/:id/packages/:package_id',
@@ -23,6 +24,7 @@ const Api = {
projectLabelsPath: '/:namespace_path/:project_path/-/labels',
projectFileSchemaPath: '/:namespace_path/:project_path/-/schema/:ref/:filename',
projectUsersPath: '/api/:version/projects/:id/users',
+ projectInvitationsPath: '/api/:version/projects/:id/invitations',
projectMembersPath: '/api/:version/projects/:id/members',
projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests',
projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
@@ -127,12 +129,18 @@ const Api = {
});
},
- inviteGroupMember(id, data) {
+ addGroupMembersByUserId(id, data) {
const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id));
return axios.post(url, data);
},
+ inviteGroupMembersByEmail(id, data) {
+ const url = Api.buildUrl(this.groupInvitationsPath).replace(':id', encodeURIComponent(id));
+
+ return axios.post(url, data);
+ },
+
groupMilestones(id, options) {
const url = Api.buildUrl(this.groupMilestonesPath).replace(':id', encodeURIComponent(id));
@@ -217,12 +225,18 @@ const Api = {
.then(({ data }) => data);
},
- inviteProjectMembers(id, data) {
+ addProjectMembersByUserId(id, data) {
const url = Api.buildUrl(this.projectMembersPath).replace(':id', encodeURIComponent(id));
return axios.post(url, data);
},
+ inviteProjectMembersByEmail(id, data) {
+ const url = Api.buildUrl(this.projectInvitationsPath).replace(':id', encodeURIComponent(id));
+
+ return axios.post(url, data);
+ },
+
// Return single project
project(projectPath) {
const url = Api.buildUrl(Api.projectPath).replace(':id', encodeURIComponent(projectPath));
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 63248a5ad48..50782781538 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -10,6 +10,7 @@ import { fullLabelId, fullBoardId } from '../boards_util';
import BoardConfigurationOptions from './board_configuration_options.vue';
import updateBoardMutation from '../graphql/board_update.mutation.graphql';
import createBoardMutation from '../graphql/board_create.mutation.graphql';
+import destroyBoardMutation from '../graphql/board_destroy.mutation.graphql';
const boardDefaults = {
id: false,
@@ -95,6 +96,9 @@ export default {
fullPath: {
default: '',
},
+ rootPath: {
+ default: '',
+ },
},
data() {
return {
@@ -221,8 +225,13 @@ export default {
this.isLoading = true;
if (this.isDeleteForm) {
try {
- await boardsStore.deleteBoard(this.currentBoard);
- visitUrl(boardsStore.rootPath);
+ await this.$apollo.mutate({
+ mutation: destroyBoardMutation,
+ variables: {
+ id: fullBoardId(this.board.id),
+ },
+ });
+ visitUrl(this.rootPath);
} catch {
Flash(this.$options.i18n.deleteErrorMessage);
} finally {
diff --git a/app/assets/javascripts/boards/graphql/board_destroy.mutation.graphql b/app/assets/javascripts/boards/graphql/board_destroy.mutation.graphql
new file mode 100644
index 00000000000..d4b928749de
--- /dev/null
+++ b/app/assets/javascripts/boards/graphql/board_destroy.mutation.graphql
@@ -0,0 +1,7 @@
+mutation destroyBoard($id: BoardID!) {
+ destroyBoard(input: { id: $id }) {
+ board {
+ id
+ }
+ }
+}
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 18d2c75b7e1..e978eedee7f 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -336,5 +336,6 @@ export default () => {
mountMultipleBoardsSwitcher({
fullPath: $boardApp.dataset.fullPath,
+ rootPath: $boardApp.dataset.boardsEndpoint,
});
};
diff --git a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
index 71463010898..17a12e84a37 100644
--- a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
+++ b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
@@ -37,6 +37,7 @@ export default (params = {}) => {
},
provide: {
fullPath: params.fullPath,
+ rootPath: params.rootPath,
},
render(createElement) {
return createElement(BoardsSelector, {
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index 3bc077be552..af00c035a91 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -753,10 +753,6 @@ const boardsStore = {
return axios.get(this.state.endpoints.recentBoardsEndpoint);
},
- deleteBoard({ id }) {
- return axios.delete(this.generateBoardsPath(id));
- },
-
setCurrentBoard(board) {
this.state.currentBoard = board;
},
diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue
index a548354f257..acaa7ae72d1 100644
--- a/app/assets/javascripts/diffs/components/commit_item.vue
+++ b/app/assets/javascripts/diffs/components/commit_item.vue
@@ -6,7 +6,7 @@ import { GlButtonGroup, GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui'
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
@@ -39,7 +39,7 @@ import { setUrlParams } from '../../lib/utils/url_utility';
export default {
components: {
UserAvatarLink,
- ClipboardButton,
+ ModalCopyButton,
TimeAgoTooltip,
CommitPipelineStatus,
GlButtonGroup,
@@ -142,7 +142,7 @@ export default {
data-testid="commit-sha-short-id"
v-text="commit.short_id"
/>
- <clipboard-button
+ <modal-copy-button
:text="commit.id"
:title="__('Copy commit SHA')"
class="input-group-text"
diff --git a/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue b/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue
index 2b6b8b765a2..192d6e056cd 100644
--- a/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue
+++ b/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue
@@ -35,6 +35,7 @@ export default {
...mapGetters([
'isLoading',
'isImportingAnyRepo',
+ 'importingRepoCount',
'hasImportableRepos',
'hasIncompatibleRepos',
'importAllCount',
@@ -60,13 +61,17 @@ export default {
},
importAllButtonText() {
- return this.hasIncompatibleRepos
- ? n__(
- 'Import %d compatible repository',
- 'Import %d compatible repositories',
- this.importAllCount,
- )
- : n__('Import %d repository', 'Import %d repositories', this.importAllCount);
+ if (this.isImportingAnyRepo) {
+ return n__('Importing %d repository', 'Importing %d repositories', this.importingRepoCount);
+ }
+
+ if (this.hasIncompatibleRepos)
+ return n__(
+ 'Import %d compatible repository',
+ 'Import %d compatible repositories',
+ this.importAllCount,
+ );
+ return n__('Import %d repository', 'Import %d repositories', this.importAllCount);
},
emptyStateText() {
diff --git a/app/assets/javascripts/import_entities/import_projects/store/getters.js b/app/assets/javascripts/import_entities/import_projects/store/getters.js
index 8903133ea12..ef01a67ec94 100644
--- a/app/assets/javascripts/import_entities/import_projects/store/getters.js
+++ b/app/assets/javascripts/import_entities/import_projects/store/getters.js
@@ -1,14 +1,10 @@
-import { STATUSES } from '../../constants';
-import { isProjectImportable, isIncompatible } from '../utils';
+import { isProjectImportable, isIncompatible, isImporting } from '../utils';
export const isLoading = (state) => state.isLoadingRepos || state.isLoadingNamespaces;
-export const isImportingAnyRepo = (state) =>
- state.repositories.some((repo) =>
- [STATUSES.SCHEDULING, STATUSES.SCHEDULED, STATUSES.STARTED].includes(
- repo.importedProject?.importStatus,
- ),
- );
+export const importingRepoCount = (state) => state.repositories.filter(isImporting).length;
+
+export const isImportingAnyRepo = (state) => state.repositories.some(isImporting);
export const hasIncompatibleRepos = (state) => state.repositories.some(isIncompatible);
diff --git a/app/assets/javascripts/import_entities/import_projects/utils.js b/app/assets/javascripts/import_entities/import_projects/utils.js
index 0610117e09b..38bd529321a 100644
--- a/app/assets/javascripts/import_entities/import_projects/utils.js
+++ b/app/assets/javascripts/import_entities/import_projects/utils.js
@@ -11,3 +11,9 @@ export function getImportStatus(project) {
export function isProjectImportable(project) {
return !isIncompatible(project) && getImportStatus(project) === STATUSES.NONE;
}
+
+export function isImporting(repo) {
+ return [STATUSES.SCHEDULING, STATUSES.SCHEDULED, STATUSES.STARTED].includes(
+ repo.importedProject?.importStatus,
+ );
+}
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 dbb0ed11e1b..a92289ca8c1 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
@@ -9,6 +9,7 @@ import {
GlButton,
GlFormInput,
} from '@gitlab/ui';
+import { partition, isString } from 'lodash';
import eventHub from '../event_hub';
import { s__, __, sprintf } from '~/locale';
import Api from '~/api';
@@ -58,7 +59,7 @@ export default {
visible: true,
modalId: 'invite-members-modal',
selectedAccessLevel: this.defaultAccessLevel,
- newUsersToInvite: '',
+ newUsersToInvite: [],
selectedDate: undefined,
};
},
@@ -79,13 +80,12 @@ export default {
return {
onComplete: () => {
this.selectedAccessLevel = this.defaultAccessLevel;
- this.newUsersToInvite = '';
+ this.newUsersToInvite = [];
},
};
},
- postData() {
+ basePostData() {
return {
- user_id: this.newUsersToInvite,
access_level: this.selectedAccessLevel,
expires_at: this.selectedDate,
format: 'json',
@@ -101,6 +101,17 @@ export default {
eventHub.$on('openModal', this.openModal);
},
methods: {
+ partitionNewUsersToInvite() {
+ const [usersToInviteByEmail, usersToAddById] = partition(
+ this.newUsersToInvite,
+ (user) => isString(user.id) && user.id.includes('user-defined-token'),
+ );
+
+ return [
+ usersToInviteByEmail.map((user) => user.name).join(','),
+ usersToAddById.map((user) => user.id).join(','),
+ ];
+ },
openModal() {
this.$root.$emit('bv::show::modal', this.modalId);
},
@@ -108,7 +119,7 @@ export default {
this.$root.$emit('bv::hide::modal', this.modalId);
},
sendInvite() {
- this.submitForm(this.postData);
+ this.submitForm();
this.closeModal();
},
cancelInvite() {
@@ -120,15 +131,33 @@ export default {
changeSelectedItem(item) {
this.selectedAccessLevel = item;
},
- submitForm(formData) {
- if (this.isProject) {
- return Api.inviteProjectMembers(this.id, formData)
- .then(this.showToastMessageSuccess)
- .catch(this.showToastMessageError);
+ submitForm() {
+ const [usersToInviteByEmail, usersToAddById] = this.partitionNewUsersToInvite();
+ const promises = [];
+
+ if (usersToInviteByEmail !== '') {
+ const apiInviteByEmail = this.isProject
+ ? Api.inviteProjectMembersByEmail.bind(Api)
+ : Api.inviteGroupMembersByEmail.bind(Api);
+
+ promises.push(apiInviteByEmail(this.id, this.inviteByEmailPostData(usersToInviteByEmail)));
}
- return Api.inviteGroupMember(this.id, formData)
- .then(this.showToastMessageSuccess)
- .catch(this.showToastMessageError);
+
+ if (usersToAddById !== '') {
+ const apiAddByUserId = this.isProject
+ ? Api.addProjectMembersByUserId.bind(Api)
+ : Api.addGroupMembersByUserId.bind(Api);
+
+ promises.push(apiAddByUserId(this.id, this.addByUserIdPostData(usersToAddById)));
+ }
+
+ Promise.all(promises).then(this.showToastMessageSuccess).catch(this.showToastMessageError);
+ },
+ inviteByEmailPostData(usersToInviteByEmail) {
+ return { ...this.basePostData, email: usersToInviteByEmail };
+ },
+ addByUserIdPostData(usersToAddById) {
+ return { ...this.basePostData, user_id: usersToAddById };
},
showToastMessageSuccess() {
this.$toast.show(this.$options.labels.toastMessageSuccessful, this.toastOptions);
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 d839c089f2e..b54812dfd96 100644
--- a/app/assets/javascripts/invite_members/components/members_token_select.vue
+++ b/app/assets/javascripts/invite_members/components/members_token_select.vue
@@ -1,6 +1,7 @@
<script>
import { debounce } from 'lodash';
-import { GlTokenSelector, GlAvatar, GlAvatarLabeled } from '@gitlab/ui';
+import { GlTokenSelector, GlAvatar, GlAvatarLabeled, GlSprintf } from '@gitlab/ui';
+import { __ } from '~/locale';
import { USER_SEARCH_DELAY } from '../constants';
import Api from '~/api';
@@ -9,6 +10,7 @@ export default {
GlTokenSelector,
GlAvatar,
GlAvatarLabeled,
+ GlSprintf,
},
props: {
placeholder: {
@@ -32,12 +34,10 @@ export default {
};
},
computed: {
- newUsersToInvite() {
- return this.selectedTokens
- .map((obj) => {
- return obj.id;
- })
- .join(',');
+ emailIsValid() {
+ const regex = /.+@/;
+
+ return this.query.match(regex) !== null;
},
placeholderText() {
if (this.selectedTokens.length === 0) {
@@ -69,7 +69,7 @@ export default {
});
}, USER_SEARCH_DELAY),
handleInput() {
- this.$emit('input', this.newUsersToInvite);
+ this.$emit('input', this.selectedTokens);
},
handleBlur() {
this.hideDropdownWithNoItems = false;
@@ -86,6 +86,9 @@ export default {
},
},
queryOptions: { exclude_internal: true, active: true },
+ i18n: {
+ inviteTextMessage: __('Invite "%{email}" by email'),
+ },
};
</script>
@@ -94,7 +97,7 @@ export default {
v-model="selectedTokens"
:dropdown-items="users"
:loading="loading"
- :allow-user-defined-tokens="false"
+ :allow-user-defined-tokens="emailIsValid"
:hide-dropdown-with-no-items="hideDropdownWithNoItems"
:placeholder="placeholderText"
:aria-labelledby="ariaLabelledby"
@@ -116,5 +119,13 @@ export default {
:sub-label="dropdownItem.username"
/>
</template>
+
+ <template #user-defined-token-content="{ inputText: email }">
+ <gl-sprintf :message="$options.i18n.inviteTextMessage">
+ <template #email>
+ <span>{{ email }}</span>
+ </template>
+ </gl-sprintf>
+ </template>
</gl-token-selector>
</template>
diff --git a/app/assets/javascripts/invite_members/init_invite_members_modal.js b/app/assets/javascripts/invite_members/init_invite_members_modal.js
index 4c67c310e9e..74c374018de 100644
--- a/app/assets/javascripts/invite_members/init_invite_members_modal.js
+++ b/app/assets/javascripts/invite_members/init_invite_members_modal.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import { GlToast } from '@gitlab/ui';
+import { parseBoolean } from '~/lib/utils/common_utils';
import InviteMembersModal from '~/invite_members/components/invite_members_modal.vue';
Vue.use(GlToast);
@@ -17,6 +18,7 @@ export default function initInviteMembersModal() {
createElement(InviteMembersModal, {
props: {
...el.dataset,
+ isProject: parseBoolean(el.dataset.isProject),
accessLevels: JSON.parse(el.dataset.accessLevels),
},
}),
diff --git a/app/helpers/projects/alert_management_helper.rb b/app/helpers/projects/alert_management_helper.rb
index 997551d9659..58f1abb2818 100644
--- a/app/helpers/projects/alert_management_helper.rb
+++ b/app/helpers/projects/alert_management_helper.rb
@@ -34,5 +34,3 @@ module Projects::AlertManagementHelper
)
end
end
-
-Projects::AlertManagementHelper.prepend_if_ee('EE::Projects::AlertManagementHelper')
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 8b9055ae289..efe5789e49a 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -730,7 +730,13 @@ module Ci
end
def any_runners_online?
- project.any_runners? { |runner| runner.active? && runner.online? && runner.can_pick?(self) }
+ project.any_runners? do |runner|
+ if Feature.enabled?(:ci_build_stuck_badge_performance_experiment, project, type: :development, default_enabled: false)
+ runner.active? && runner.online?
+ else
+ runner.active? && runner.online? && runner.can_pick?(self)
+ end
+ end
end
def stuck?
diff --git a/app/models/project_services/alerts_service.rb b/app/models/project_services/alerts_service.rb
index 5b7d149ace1..58d507971ca 100644
--- a/app/models/project_services/alerts_service.rb
+++ b/app/models/project_services/alerts_service.rb
@@ -88,5 +88,3 @@ class AlertsService < Service
.execute
end
end
-
-AlertsService.prepend_if_ee('EE::AlertsService')
diff --git a/app/models/user.rb b/app/models/user.rb
index 02092d70d20..c6be3d6a839 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1358,6 +1358,7 @@ class User < ApplicationRecord
def hook_attrs
{
+ id: id,
name: name,
username: username,
avatar_url: avatar_url(only_path: false),
diff --git a/app/views/admin/users/_admin_notes.html.haml b/app/views/admin/users/_admin_notes.html.haml
index 5d91ba1a1ca..4da70a504f7 100644
--- a/app/views/admin/users/_admin_notes.html.haml
+++ b/app/views/admin/users/_admin_notes.html.haml
@@ -1,7 +1,7 @@
%fieldset
%legend= _('Admin notes')
.form-group.row
- .col-sm-2.col-form-label.text-right
+ .col-sm-2.col-form-label
= f.label :note, s_('AdminNote|Note')
.col-sm-10
= f.text_area :note, class: 'form-control'
diff --git a/app/views/groups/_invite_members_modal.html.haml b/app/views/groups/_invite_members_modal.html.haml
index 3aae81cef8d..bd53f73230e 100644
--- a/app/views/groups/_invite_members_modal.html.haml
+++ b/app/views/groups/_invite_members_modal.html.haml
@@ -1,7 +1,7 @@
- if invite_members_allowed?(group)
.js-invite-members-modal{ data: { id: group.id,
name: group.name,
- is_project: false,
+ is_project: 'false',
access_levels: GroupMember.access_level_roles.to_json,
default_access_level: Gitlab::Access::GUEST,
help_link: help_page_url('user/permissions') } }
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index f13c1f29041..9a7cfc0a573 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -51,56 +51,52 @@
%span.badge.badge-pill= @requesters.count
.tab-content
#tab-members.tab-pane{ class: ('active' unless invited_active) }
- .card.card-without-border
+ - unless filtered_search_enabled
+ = render 'shared/members/tab_pane/header' do
+ = render 'shared/members/tab_pane/title' do
+ = html_escape(_('Members with access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
+ = form_tag group_group_members_path(@group), method: :get, class: 'user-search-form gl-display-flex gl-md-align-items-center gl-flex-wrap gl-flex-direction-column gl-md-flex-direction-row gl-mx-n3 gl-my-n3', data: { testid: 'user-search-form' } do
+ .gl-px-3.gl-py-2
+ .search-control-wrap.gl-relative
+ = render 'shared/members/search_field'
+ - if can_manage_members
+ = render 'shared/members/tab_pane/form_item' do
+ = label_tag '2fa', _('2FA'), class: form_item_label_css_class
+ = render 'shared/members/filter_2fa_dropdown'
+ = render 'shared/members/tab_pane/form_item' do
+ = label_tag :sort_by, _('Sort by'), class: form_item_label_css_class
+ = render 'shared/members/sort_dropdown'
+ .js-group-members-list{ data: group_members_list_data_attributes(@group, @members) }
+ .loading
+ .spinner.spinner-md
+ = paginate @members, theme: 'gitlab', params: { invited_members_page: nil, search_invited: nil }
+ - if @group.shared_with_group_links.any?
+ #tab-groups.tab-pane
- unless filtered_search_enabled
= render 'shared/members/tab_pane/header' do
= render 'shared/members/tab_pane/title' do
- = html_escape(_('Members with access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- = form_tag group_group_members_path(@group), method: :get, class: 'user-search-form gl-display-flex gl-md-align-items-center gl-flex-wrap gl-flex-direction-column gl-md-flex-direction-row gl-mx-n3 gl-my-n3', data: { testid: 'user-search-form' } do
- .gl-px-3.gl-py-2
- .search-control-wrap.gl-relative
- = render 'shared/members/search_field'
- - if can_manage_members
- = render 'shared/members/tab_pane/form_item' do
- = label_tag '2fa', _('2FA'), class: form_item_label_css_class
- = render 'shared/members/filter_2fa_dropdown'
- = render 'shared/members/tab_pane/form_item' do
- = label_tag :sort_by, _('Sort by'), class: form_item_label_css_class
- = render 'shared/members/sort_dropdown'
- .js-group-members-list{ data: group_members_list_data_attributes(@group, @members) }
+ = html_escape(_('Groups with access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
+ .js-group-linked-list{ data: linked_groups_list_data_attributes(@group) }
.loading
.spinner.spinner-md
- = paginate @members, theme: 'gitlab', params: { invited_members_page: nil, search_invited: nil }
- - if @group.shared_with_group_links.any?
- #tab-groups.tab-pane
- .card.card-without-border
- - unless filtered_search_enabled
- = render 'shared/members/tab_pane/header' do
- = render 'shared/members/tab_pane/title' do
- = html_escape(_('Groups with access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- .js-group-linked-list{ data: linked_groups_list_data_attributes(@group) }
- .loading
- .spinner.spinner-md
- if show_invited_members
#tab-invited-members.tab-pane{ class: ('active' if invited_active) }
- .card.card-without-border
- - unless filtered_search_enabled
- = render 'shared/members/tab_pane/header' do
- = render 'shared/members/tab_pane/title' do
- = html_escape(_('Members invited to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- = form_tag group_group_members_path(@group), method: :get, class: 'user-search-form', data: { testid: 'user-search-form' } do
- = render 'shared/members/search_field', name: 'search_invited'
- .js-group-invited-members-list{ data: group_members_list_data_attributes(@group, @invited_members) }
- .loading
- .spinner.spinner-md
- = paginate @invited_members, param_name: 'invited_members_page', theme: 'gitlab', params: { page: nil }
+ - unless filtered_search_enabled
+ = render 'shared/members/tab_pane/header' do
+ = render 'shared/members/tab_pane/title' do
+ = html_escape(_('Members invited to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
+ = form_tag group_group_members_path(@group), method: :get, class: 'user-search-form', data: { testid: 'user-search-form' } do
+ = render 'shared/members/search_field', name: 'search_invited'
+ .js-group-invited-members-list{ data: group_members_list_data_attributes(@group, @invited_members) }
+ .loading
+ .spinner.spinner-md
+ = paginate @invited_members, param_name: 'invited_members_page', theme: 'gitlab', params: { page: nil }
- if show_access_requests
#tab-access-requests.tab-pane
- .card.card-without-border
- - unless filtered_search_enabled
- = render 'shared/members/tab_pane/header' do
- = render 'shared/members/tab_pane/title' do
- = html_escape(_('Users requesting access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- .js-group-access-requests-list{ data: group_members_list_data_attributes(@group, @requesters) }
- .loading
- .spinner.spinner-md
+ - unless filtered_search_enabled
+ = render 'shared/members/tab_pane/header' do
+ = render 'shared/members/tab_pane/title' do
+ = html_escape(_('Users requesting access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
+ .js-group-access-requests-list{ data: group_members_list_data_attributes(@group, @requesters) }
+ .loading
+ .spinner.spinner-md
diff --git a/app/views/projects/_invite_members_modal.html.haml b/app/views/projects/_invite_members_modal.html.haml
index ad95f39bbfa..e8f61336882 100644
--- a/app/views/projects/_invite_members_modal.html.haml
+++ b/app/views/projects/_invite_members_modal.html.haml
@@ -1,7 +1,7 @@
- if invite_members_allowed?(project.group)
.js-invite-members-modal{ data: { id: project.id,
name: project.name,
- is_project: true,
+ is_project: 'true',
access_levels: GroupMember.access_level_roles.to_json,
default_access_level: Gitlab::Access::GUEST,
help_link: help_page_url('user/permissions') } }
diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml
index cd062fcf675..e14473708af 100644
--- a/app/views/projects/jobs/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -8,10 +8,11 @@
.nav-controls
- if can?(current_user, :update_build, @project)
- if !@repository.gitlab_ci_yml && !experiment_enabled?(:jobs_empty_state)
- = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn gl-button btn-info js-empty-state-button'
+ = link_to s_('Pipelines|Get started with Pipelines'), help_page_path('ci/quick_start/README'), class: 'btn gl-button btn-info js-empty-state-button'
= link_to project_ci_lint_path(@project), class: 'btn gl-button btn-default' do
- %span CI lint
+ %span
+ = _('CI Lint')
.content-list.builds-content-list
= render "table", builds: @builds, project: @project