Commit 71344985 authored by David O'Regan's avatar David O'Regan

Merge branch '333064-add-pending-members-page-alert' into 'master'

Add alert to seats usage if there are any pending members

See merge request gitlab-org/gitlab!75431
parents bef5540c d80941dc
<script> <script>
import { import {
GlAlert,
GlAvatarLabeled, GlAvatarLabeled,
GlAvatarLink, GlAvatarLink,
GlBadge, GlBadge,
...@@ -14,6 +15,7 @@ import { ...@@ -14,6 +15,7 @@ import {
GlTooltipDirective, GlTooltipDirective,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { visitUrl } from '~/lib/utils/url_utility';
import { import {
FIELDS, FIELDS,
AVATAR_SIZE, AVATAR_SIZE,
...@@ -23,7 +25,7 @@ import { ...@@ -23,7 +25,7 @@ import {
CANNOT_REMOVE_BILLABLE_MEMBER_MODAL_CONTENT, CANNOT_REMOVE_BILLABLE_MEMBER_MODAL_CONTENT,
SORT_OPTIONS, SORT_OPTIONS,
} from 'ee/seat_usage/constants'; } from 'ee/seat_usage/constants';
import { s__, __ } from '~/locale'; import { s__, __, sprintf, n__ } from '~/locale';
import FilterSortContainerRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import FilterSortContainerRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import RemoveBillableMemberModal from './remove_billable_member_modal.vue'; import RemoveBillableMemberModal from './remove_billable_member_modal.vue';
import SubscriptionSeatDetails from './subscription_seat_details.vue'; import SubscriptionSeatDetails from './subscription_seat_details.vue';
...@@ -34,6 +36,7 @@ export default { ...@@ -34,6 +36,7 @@ export default {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
components: { components: {
GlAlert,
GlAvatarLabeled, GlAvatarLabeled,
GlAvatarLink, GlAvatarLink,
GlBadge, GlBadge,
...@@ -57,6 +60,8 @@ export default { ...@@ -57,6 +60,8 @@ export default {
'namespaceName', 'namespaceName',
'namespaceId', 'namespaceId',
'seatUsageExportPath', 'seatUsageExportPath',
'pendingMembersPagePath',
'pendingMembersCount',
'billableMemberToRemove', 'billableMemberToRemove',
'search', 'search',
'sort', 'sort',
...@@ -76,6 +81,21 @@ export default { ...@@ -76,6 +81,21 @@ export default {
} }
return s__('Billing|No users to display.'); return s__('Billing|No users to display.');
}, },
pendingMembersAlertMessage() {
return sprintf(
n__(
'You have %{pendingMembersCount} pending member that needs approval.',
'You have %{pendingMembersCount} pending members that need approval.',
this.pendingMembersCount,
),
{
pendingMembersCount: this.pendingMembersCount,
},
);
},
shouldShowPendingMembersAlert() {
return this.pendingMembersCount > 0 && this.pendingMembersPagePath;
},
}, },
created() { created() {
this.fetchBillableMembersList(); this.fetchBillableMembersList();
...@@ -115,12 +135,16 @@ export default { ...@@ -115,12 +135,16 @@ export default {
shouldShowDetails(item) { shouldShowDetails(item) {
return !this.isGroupInvite(item) && !this.isProjectInvite(item); return !this.isGroupInvite(item) && !this.isProjectInvite(item);
}, },
navigateToPendingMembersPage() {
visitUrl(this.pendingMembersPagePath);
},
}, },
i18n: { i18n: {
emailNotVisibleTooltipText: s__( emailNotVisibleTooltipText: s__(
'Billing|An email address is only visible for users with public emails.', 'Billing|An email address is only visible for users with public emails.',
), ),
filterUsersPlaceholder: __('Filter users'), filterUsersPlaceholder: __('Filter users'),
pendingMembersAlertButtonText: s__('Billing|View pending approvals'),
}, },
avatarSize: AVATAR_SIZE, avatarSize: AVATAR_SIZE,
fields: FIELDS, fields: FIELDS,
...@@ -134,6 +158,16 @@ export default { ...@@ -134,6 +158,16 @@ export default {
<template> <template>
<section> <section>
<gl-alert
v-if="shouldShowPendingMembersAlert"
variant="info"
:dismissible="false"
:primary-button-text="$options.i18n.pendingMembersAlertButtonText"
class="gl-my-3"
@primaryAction="navigateToPendingMembersPage"
>
{{ pendingMembersAlertMessage }}
</gl-alert>
<div <div
class="gl-bg-gray-10 gl-p-6 gl-md-display-flex gl-justify-content-space-between gl-align-items-center" class="gl-bg-gray-10 gl-p-6 gl-md-display-flex gl-justify-content-space-between gl-align-items-center"
> >
......
...@@ -12,12 +12,26 @@ export default (containerId = 'js-seat-usage-app') => { ...@@ -12,12 +12,26 @@ export default (containerId = 'js-seat-usage-app') => {
return false; return false;
} }
const { namespaceId, namespaceName, seatUsageExportPath } = el.dataset; const {
namespaceId,
namespaceName,
seatUsageExportPath,
pendingMembersPagePath,
pendingMembersCount,
} = el.dataset;
return new Vue({ return new Vue({
el, el,
apolloProvider: {}, apolloProvider: {},
store: new Vuex.Store(initialStore({ namespaceId, namespaceName, seatUsageExportPath })), store: new Vuex.Store(
initialStore({
namespaceId,
namespaceName,
seatUsageExportPath,
pendingMembersPagePath,
pendingMembersCount,
}),
),
render(createElement) { render(createElement) {
return createElement(SubscriptionSeats); return createElement(SubscriptionSeats);
}, },
......
export default ({ namespaceId = null, namespaceName = null, seatUsageExportPath = null } = {}) => ({ export default ({
namespaceId = null,
namespaceName = null,
seatUsageExportPath = null,
pendingMembersPagePath = null,
pendingMembersCount = 0,
} = {}) => ({
isLoading: false, isLoading: false,
hasError: false, hasError: false,
namespaceId, namespaceId,
namespaceName, namespaceName,
seatUsageExportPath, seatUsageExportPath,
pendingMembersPagePath,
pendingMembersCount,
members: [], members: [],
total: null, total: null,
page: null, page: null,
......
- page_title s_("UsageQuota|Usage") - page_title s_("UsageQuota|Usage")
- url_to_purchase_storage = buy_storage_path(@group) if purchase_storage_link_enabled?(@group) - url_to_purchase_storage = buy_storage_path(@group) if purchase_storage_link_enabled?(@group)
- pending_members_page_path = pending_members_group_usage_quotas_path(@group) if Feature.enabled?(:saas_user_caps, @group.root_ancestor)
- pending_members_count = Member.in_hierarchy(@group).with_state("awaiting").count
- if show_product_purchase_success_alert? - if show_product_purchase_success_alert?
= render 'product_purchase_success_alert', product_name: params[:purchased_product] = render 'product_purchase_success_alert', product_name: params[:purchased_product]
...@@ -24,7 +26,7 @@ ...@@ -24,7 +26,7 @@
= s_('UsageQuota|Storage') = s_('UsageQuota|Storage')
.tab-content .tab-content
.tab-pane#seats-quota-tab .tab-pane#seats-quota-tab
#js-seat-usage-app{ data: { namespace_id: @group.id, namespace_name: @group.name, seat_usage_export_path: group_seat_usage_path(@group, format: :csv) } } #js-seat-usage-app{ data: { namespace_id: @group.id, namespace_name: @group.name, seat_usage_export_path: group_seat_usage_path(@group, format: :csv), pending_members_page_path: pending_members_page_path, pending_members_count: pending_members_count } }
.tab-pane#pipelines-quota-tab .tab-pane#pipelines-quota-tab
= render "namespaces/pipelines_quota/list", = render "namespaces/pipelines_quota/list",
locals: { namespace: @group, projects: @projects } locals: { namespace: @group, projects: @projects }
......
import { import {
GlAlert,
GlPagination, GlPagination,
GlDropdown, GlDropdown,
GlTable, GlTable,
...@@ -273,4 +274,26 @@ describe('Subscription Seats', () => { ...@@ -273,4 +274,26 @@ describe('Subscription Seats', () => {
expect(actionSpies.setSearchQuery).toHaveBeenCalledWith(expect.any(Object), SEARCH_STRING); expect(actionSpies.setSearchQuery).toHaveBeenCalledWith(expect.any(Object), SEARCH_STRING);
}); });
}); });
describe('pending members alert', () => {
it.each`
pendingMembersPagePath | pendingMembersCount | shouldBeRendered
${undefined} | ${undefined} | ${false}
${undefined} | ${0} | ${false}
${'fake-path'} | ${0} | ${false}
${'fake-path'} | ${3} | ${true}
`(
'rendering alert is $shouldBeRendered when pendingMembersPagePath=$pendingMembersPagePath and pendingMembersCount=$pendingMembersCount',
({ pendingMembersPagePath, pendingMembersCount, shouldBeRendered }) => {
wrapper = createComponent({
initialState: {
pendingMembersCount,
pendingMembersPagePath,
},
});
expect(wrapper.findComponent(GlAlert).exists()).toBe(shouldBeRendered);
},
);
});
}); });
...@@ -5469,6 +5469,9 @@ msgstr "" ...@@ -5469,6 +5469,9 @@ msgstr ""
msgid "Billing|Users occupying seats in" msgid "Billing|Users occupying seats in"
msgstr "" msgstr ""
msgid "Billing|View pending approvals"
msgstr ""
msgid "Billing|You are about to remove user %{username} from your subscription. If you continue, the user will be removed from the %{namespace} group and all its subgroups and projects. This action can't be undone." msgid "Billing|You are about to remove user %{username} from your subscription. If you continue, the user will be removed from the %{namespace} group and all its subgroups and projects. This action can't be undone."
msgstr "" msgstr ""
...@@ -39877,6 +39880,11 @@ msgstr "" ...@@ -39877,6 +39880,11 @@ msgstr ""
msgid "You don’t have access to Value Stream Analytics for this group" msgid "You don’t have access to Value Stream Analytics for this group"
msgstr "" msgstr ""
msgid "You have %{pendingMembersCount} pending member that needs approval."
msgid_plural "You have %{pendingMembersCount} pending members that need approval."
msgstr[0] ""
msgstr[1] ""
msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}." msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}."
msgstr "" msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment