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>2020-12-03 09:09:47 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-03 09:09:47 +0300
commit9214e550c07793a8deb6d5cd5bb136d0d010a7ca (patch)
treebf094d583e9f57e2816a6f272bcbff302e264efe /app
parente1e9056d03fec6d72771c7a4ba3fc1174b5ac009 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/filtered_search/recent_searches_storage_keys.js2
-rw-r--r--app/assets/javascripts/groups/members/components/app.vue6
-rw-r--r--app/assets/javascripts/jobs/components/unmet_prerequisites_block.vue27
-rw-r--r--app/assets/javascripts/members/components/filter_sort/filter_sort_container.vue18
-rw-r--r--app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue132
-rw-r--r--app/assets/javascripts/members/constants.js4
-rw-r--r--app/assets/javascripts/pages/groups/group_members/index.js6
-rw-r--r--app/controllers/groups/group_members_controller.rb4
-rw-r--r--app/views/groups/group_members/index.html.haml53
9 files changed, 212 insertions, 40 deletions
diff --git a/app/assets/javascripts/filtered_search/recent_searches_storage_keys.js b/app/assets/javascripts/filtered_search/recent_searches_storage_keys.js
index 7e9b809e9b2..54d49821d92 100644
--- a/app/assets/javascripts/filtered_search/recent_searches_storage_keys.js
+++ b/app/assets/javascripts/filtered_search/recent_searches_storage_keys.js
@@ -1,4 +1,6 @@
export default {
issues: 'issue-recent-searches',
merge_requests: 'merge-request-recent-searches',
+ group_members: 'group-members-recent-searches',
+ group_invited_members: 'group-invited-members-recent-searches',
};
diff --git a/app/assets/javascripts/groups/members/components/app.vue b/app/assets/javascripts/groups/members/components/app.vue
index 8f1bb6e8094..f6f3a955813 100644
--- a/app/assets/javascripts/groups/members/components/app.vue
+++ b/app/assets/javascripts/groups/members/components/app.vue
@@ -2,12 +2,15 @@
import { mapState, mapMutations } from 'vuex';
import { GlAlert } from '@gitlab/ui';
import MembersTable from '~/members/components/table/members_table.vue';
+import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue';
import { scrollToElement } from '~/lib/utils/common_utils';
import { HIDE_ERROR } from '~/members/store/mutation_types';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'GroupMembersApp',
- components: { MembersTable, GlAlert },
+ components: { MembersTable, FilterSortContainer, GlAlert },
+ mixins: [glFeatureFlagsMixin()],
computed: {
...mapState(['showError', 'errorMessage']),
},
@@ -33,6 +36,7 @@ export default {
<gl-alert v-if="showError" ref="errorAlert" variant="danger" @dismiss="hideError">{{
errorMessage
}}</gl-alert>
+ <filter-sort-container v-if="glFeatures.groupMembersFilteredSearch" />
<members-table />
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/unmet_prerequisites_block.vue b/app/assets/javascripts/jobs/components/unmet_prerequisites_block.vue
index 633561c879e..c9747ca9f02 100644
--- a/app/assets/javascripts/jobs/components/unmet_prerequisites_block.vue
+++ b/app/assets/javascripts/jobs/components/unmet_prerequisites_block.vue
@@ -1,11 +1,19 @@
<script>
-import { GlLink } from '@gitlab/ui';
+import { GlLink, GlAlert } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
/**
* Renders Unmet Prerequisites block for job's view.
*/
export default {
+ i18n: {
+ failMessage: s__(
+ 'Job|This job failed because the necessary resources were not successfully created.',
+ ),
+ moreInformation: __('More information'),
+ },
components: {
GlLink,
+ GlAlert,
},
props: {
helpPath: {
@@ -16,15 +24,10 @@ export default {
};
</script>
<template>
- <div class="bs-callout bs-callout-danger">
- <p class="js-failed-unmet-prerequisites gl-mb-0">
- {{
- s__(`Job|This job failed because the necessary resources were not successfully created.`)
- }}
-
- <gl-link :href="helpPath" class="js-help-path">
- <strong> {{ __('More information') }} </strong>
- </gl-link>
- </p>
- </div>
+ <gl-alert variant="danger" class="gl-mt-3" :dismissible="false">
+ {{ $options.i18n.failMessage }}
+ <gl-link :href="helpPath">
+ {{ $options.i18n.moreInformation }}
+ </gl-link>
+ </gl-alert>
</template>
diff --git a/app/assets/javascripts/members/components/filter_sort/filter_sort_container.vue b/app/assets/javascripts/members/components/filter_sort/filter_sort_container.vue
new file mode 100644
index 00000000000..f2acc3215cd
--- /dev/null
+++ b/app/assets/javascripts/members/components/filter_sort/filter_sort_container.vue
@@ -0,0 +1,18 @@
+<script>
+import { mapState } from 'vuex';
+import MembersFilteredSearchBar from './members_filtered_search_bar.vue';
+
+export default {
+ name: 'FilterSortContainer',
+ components: { MembersFilteredSearchBar },
+ computed: {
+ ...mapState(['filteredSearchBar']),
+ },
+};
+</script>
+
+<template>
+ <div v-if="filteredSearchBar.show" class="gl-bg-gray-10 gl-p-5">
+ <members-filtered-search-bar />
+ </div>
+</template>
diff --git a/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue b/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue
new file mode 100644
index 00000000000..c1df0b94234
--- /dev/null
+++ b/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue
@@ -0,0 +1,132 @@
+<script>
+import { mapState } from 'vuex';
+import { GlFilteredSearchToken } from '@gitlab/ui';
+import { setUrlParams, queryToObject } from '~/lib/utils/url_utility';
+import { getParameterByName } from '~/lib/utils/common_utils';
+import { s__ } from '~/locale';
+import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
+import { SEARCH_TOKEN_TYPE, SORT_PARAM } from '~/members/constants';
+
+export default {
+ name: 'MembersFilteredSearchBar',
+ components: { FilteredSearchBar },
+ availableTokens: [
+ {
+ type: 'two_factor',
+ icon: 'lock',
+ title: s__('Members|2FA'),
+ token: GlFilteredSearchToken,
+ unique: true,
+ operators: [{ value: '=', description: 'is' }],
+ options: [
+ { value: 'enabled', title: s__('Members|Enabled') },
+ { value: 'disabled', title: s__('Members|Disabled') },
+ ],
+ requiredPermissions: 'canManageMembers',
+ },
+ {
+ type: 'with_inherited_permissions',
+ icon: 'group',
+ title: s__('Members|Membership'),
+ token: GlFilteredSearchToken,
+ unique: true,
+ operators: [{ value: '=', description: 'is' }],
+ options: [
+ { value: 'exclude', title: s__('Members|Direct') },
+ { value: 'only', title: s__('Members|Inherited') },
+ ],
+ },
+ ],
+ data() {
+ return {
+ initialFilterValue: [],
+ };
+ },
+ computed: {
+ ...mapState(['sourceId', 'filteredSearchBar', 'canManageMembers']),
+ tokens() {
+ return this.$options.availableTokens.filter(token => {
+ if (
+ Object.prototype.hasOwnProperty.call(token, 'requiredPermissions') &&
+ !this[token.requiredPermissions]
+ ) {
+ return false;
+ }
+
+ return this.filteredSearchBar.tokens?.includes(token.type);
+ });
+ },
+ },
+ created() {
+ const query = queryToObject(window.location.search);
+
+ const tokens = this.tokens
+ .filter(token => query[token.type])
+ .map(token => ({
+ type: token.type,
+ value: {
+ data: query[token.type],
+ operator: '=',
+ },
+ }));
+
+ if (query[this.filteredSearchBar.searchParam]) {
+ tokens.push({
+ type: SEARCH_TOKEN_TYPE,
+ value: {
+ data: query[this.filteredSearchBar.searchParam],
+ },
+ });
+ }
+
+ this.initialFilterValue = tokens;
+ },
+ methods: {
+ handleFilter(tokens) {
+ const params = tokens.reduce((accumulator, token) => {
+ const { type, value } = token;
+
+ if (!type || !value) {
+ return accumulator;
+ }
+
+ if (type === SEARCH_TOKEN_TYPE) {
+ if (value.data !== '') {
+ return {
+ ...accumulator,
+ [this.filteredSearchBar.searchParam]: value.data,
+ };
+ }
+ } else {
+ return {
+ ...accumulator,
+ [type]: value.data,
+ };
+ }
+
+ return accumulator;
+ }, {});
+
+ const sortParam = getParameterByName(SORT_PARAM);
+
+ window.location.href = setUrlParams(
+ { ...params, ...(sortParam && { sort: sortParam }) },
+ window.location.href,
+ true,
+ );
+ },
+ },
+};
+</script>
+
+<template>
+ <filtered-search-bar
+ :namespace="sourceId.toString()"
+ :tokens="tokens"
+ :recent-searches-storage-key="filteredSearchBar.recentSearchesStorageKey"
+ :search-input-placeholder="filteredSearchBar.placeholder"
+ :initial-filter-value="initialFilterValue"
+ data-testid="members-filtered-search-bar"
+ @onFilter="handleFilter"
+ />
+</template>
diff --git a/app/assets/javascripts/members/constants.js b/app/assets/javascripts/members/constants.js
index 5885420a122..a23e9b942ef 100644
--- a/app/assets/javascripts/members/constants.js
+++ b/app/assets/javascripts/members/constants.js
@@ -69,3 +69,7 @@ export const DAYS_TO_EXPIRE_SOON = 7;
export const LEAVE_MODAL_ID = 'member-leave-modal';
export const REMOVE_GROUP_LINK_MODAL_ID = 'remove-group-link-modal-id';
+
+export const SEARCH_TOKEN_TYPE = 'filtered-search-term';
+
+export const SORT_PARAM = 'sort';
diff --git a/app/assets/javascripts/pages/groups/group_members/index.js b/app/assets/javascripts/pages/groups/group_members/index.js
index fbb960a7ceb..d3900b84fa7 100644
--- a/app/assets/javascripts/pages/groups/group_members/index.js
+++ b/app/assets/javascripts/pages/groups/group_members/index.js
@@ -6,7 +6,7 @@ import groupsSelect from '~/groups_select';
import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
import { initGroupMembersApp } from '~/groups/members';
import { memberRequestFormatter, groupLinkRequestFormatter } from '~/groups/members/utils';
-import { __ } from '~/locale';
+import { s__ } from '~/locale';
function mountRemoveMemberModal() {
const el = document.querySelector('.js-remove-member-modal');
@@ -33,7 +33,7 @@ initGroupMembersApp(document.querySelector('.js-group-members-list'), {
show: true,
tokens: ['two_factor', 'with_inherited_permissions'],
searchParam: 'search',
- placeholder: __('Members|Filter members'),
+ placeholder: s__('Members|Filter members'),
recentSearchesStorageKey: 'group_members',
},
});
@@ -52,7 +52,7 @@ initGroupMembersApp(document.querySelector('.js-group-invited-members-list'), {
show: true,
tokens: [],
searchParam: 'search_invited',
- placeholder: __('Members|Search invited'),
+ placeholder: s__('Members|Search invited'),
recentSearchesStorageKey: 'group_invited_members',
},
});
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 5df7ff0632a..8f836010d70 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -14,6 +14,10 @@ class Groups::GroupMembersController < Groups::ApplicationController
# Authorize
before_action :authorize_admin_group_member!, except: admin_not_required_endpoints
+ before_action do
+ push_frontend_feature_flag(:group_members_filtered_search, @group)
+ end
+
skip_before_action :check_two_factor_requirement, only: :leave
skip_cross_project_access_check :index, :create, :update, :destroy, :request_access,
:approve_access_request, :leave, :resend_invite,
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 2a87b42ef13..a08212f151c 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -4,6 +4,7 @@
- show_access_requests = can_manage_members && @requesters.exists?
- invited_active = params[:search_invited].present? || params[:invited_members_page].present?
- vue_members_list_enabled = Feature.enabled?(:vue_group_members_list, @group, default_enabled: true)
+- filtered_search_enabled = Feature.enabled?(:group_members_filtered_search, @group)
- current_user_is_group_owner = @group && @group.has_owner?(current_user)
- form_item_label_css_class = 'label-bold gl-mr-2 gl-mb-0 gl-py-2 align-self-md-center'
@@ -54,20 +55,21 @@
.tab-content
#tab-members.tab-pane{ class: ('active' unless invited_active) }
.card.card-without-border
- = render 'groups/group_members/tab_pane/header' do
- = render 'groups/group_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
+ - unless filtered_search_enabled
+ = render 'groups/group_members/tab_pane/header' do
+ = render 'groups/group_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 'groups/group_members/tab_pane/form_item' do
+ = label_tag '2fa', _('2FA'), class: form_item_label_css_class
+ = render 'shared/members/filter_2fa_dropdown'
= render 'groups/group_members/tab_pane/form_item' do
- = label_tag '2fa', _('2FA'), class: form_item_label_css_class
- = render 'shared/members/filter_2fa_dropdown'
- = render 'groups/group_members/tab_pane/form_item' do
- = label_tag :sort_by, _('Sort by'), class: form_item_label_css_class
- = render 'shared/members/sort_dropdown'
+ = label_tag :sort_by, _('Sort by'), class: form_item_label_css_class
+ = render 'shared/members/sort_dropdown'
- if vue_members_list_enabled
.js-group-members-list{ data: group_members_list_data_attributes(@group, @members) }
.loading
@@ -83,9 +85,10 @@
- if @group.shared_with_group_links.any?
#tab-groups.tab-pane
.card.card-without-border
- = render 'groups/group_members/tab_pane/header' do
- = render 'groups/group_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 }
+ - unless filtered_search_enabled
+ = render 'groups/group_members/tab_pane/header' do
+ = render 'groups/group_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 }
- if vue_members_list_enabled
.js-group-linked-list{ data: linked_groups_list_data_attributes(@group) }
.loading
@@ -97,11 +100,12 @@
- if show_invited_members
#tab-invited-members.tab-pane{ class: ('active' if invited_active) }
.card.card-without-border
- = render 'groups/group_members/tab_pane/header' do
- = render 'groups/group_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'
+ - unless filtered_search_enabled
+ = render 'groups/group_members/tab_pane/header' do
+ = render 'groups/group_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'
- if vue_members_list_enabled
.js-group-invited-members-list{ data: group_members_list_data_attributes(@group, @invited_members) }
.loading
@@ -117,9 +121,10 @@
- if show_access_requests
#tab-access-requests.tab-pane
.card.card-without-border
- = render 'groups/group_members/tab_pane/header' do
- = render 'groups/group_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 }
+ - unless filtered_search_enabled
+ = render 'groups/group_members/tab_pane/header' do
+ = render 'groups/group_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 }
- if vue_members_list_enabled
.js-group-access-requests-list{ data: group_members_list_data_attributes(@group, @requesters) }
.loading