Commit eecf15b9 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '330027-add-approve-button-to-pending-members' into 'master'

Add "Approve" button to pending members table

See merge request gitlab-org/gitlab!75756
parents 8830aa88 844577de
...@@ -56,3 +56,13 @@ export const fetchPendingGroupMembersList = (namespaceId, options = {}) => { ...@@ -56,3 +56,13 @@ export const fetchPendingGroupMembersList = (namespaceId, options = {}) => {
}, },
}); });
}; };
const APPROVE_PENDING_GROUP_MEMBER_PATH = '/api/:version/groups/:id/members/:user_id/approve';
export const approvePendingGroupMember = (namespaceId, userId) => {
const url = buildApiUrl(APPROVE_PENDING_GROUP_MEMBER_PATH)
.replace(':id', namespaceId)
.replace(':user_id', userId);
return axios.put(url);
};
<script> <script>
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { GlAvatarLabeled, GlAvatarLink, GlBadge, GlPagination, GlLoadingIcon } from '@gitlab/ui'; import {
GlAlert,
GlAvatarLabeled,
GlAvatarLink,
GlBadge,
GlButton,
GlPagination,
GlModal,
GlModalDirective,
GlLoadingIcon,
} from '@gitlab/ui';
import { sprintf } from '~/locale';
import { AVATAR_SIZE } from 'ee/seat_usage/constants'; import { AVATAR_SIZE } from 'ee/seat_usage/constants';
import { AWAITING_MEMBER_SIGNUP_TEXT } from 'ee/pending_members/constants'; import {
AWAITING_MEMBER_SIGNUP_TEXT,
LABEL_APPROVE,
LABEL_CONFIRM,
LABEL_CONFIRM_APPROVE,
} from 'ee/pending_members/constants';
export default { export default {
name: 'PendingMembersApp', name: 'PendingMembersApp',
components: { GlAvatarLabeled, GlAvatarLink, GlBadge, GlPagination, GlLoadingIcon }, components: {
GlAlert,
GlAvatarLabeled,
GlAvatarLink,
GlBadge,
GlButton,
GlPagination,
GlModal,
GlLoadingIcon,
},
directives: {
GlModalDirective,
},
computed: { computed: {
...mapState([ ...mapState([
'isLoading', 'isLoading',
'alertMessage',
'alertVariant',
'page', 'page',
'perPage', 'perPage',
'total', 'total',
...@@ -33,17 +63,24 @@ export default { ...@@ -33,17 +63,24 @@ export default {
created() { created() {
this.fetchPendingMembersList(); this.fetchPendingMembersList();
}, },
AWAITING_MEMBER_SIGNUP_TEXT,
methods: { methods: {
...mapActions(['fetchPendingMembersList', 'setCurrentPage']), ...mapActions(['fetchPendingMembersList', 'setCurrentPage', 'approveMember', 'dismissAlert']),
avatarLabel(member) { avatarLabel(member) {
if (member.invited) { if (member.invited) {
return member.email; return member.email;
} }
return member.name ?? ''; return member.name ?? '';
}, },
approveUserQuestion(member) {
return sprintf(LABEL_CONFIRM_APPROVE, {
user: member.name || member.email,
});
},
}, },
avatarSize: AVATAR_SIZE, avatarSize: AVATAR_SIZE,
AWAITING_MEMBER_SIGNUP_TEXT,
LABEL_APPROVE,
LABEL_CONFIRM,
}; };
</script> </script>
<template> <template>
...@@ -52,10 +89,14 @@ export default { ...@@ -52,10 +89,14 @@ export default {
<gl-loading-icon class="mt-5" size="lg" /> <gl-loading-icon class="mt-5" size="lg" />
</div> </div>
<template v-else> <template v-else>
<div>
<gl-alert v-if="alertMessage" :variant="alertVariant" @dismiss="dismissAlert">
{{ alertMessage }}
</gl-alert>
<div <div
v-for="item in tableItems" v-for="item in tableItems"
:key="item.id" :key="item.id"
class="gl-p-5 gl-border-0 gl-border-b-1! gl-border-gray-100 gl-border-solid" class="gl-p-5 gl-border-0 gl-border-b-1! gl-border-gray-100 gl-border-solid gl-display-flex gl-justify-content-space-between"
data-testid="pending-members-row" data-testid="pending-members-row"
> >
<gl-avatar-link target="blank" :href="item.web_url" :alt="item.name"> <gl-avatar-link target="blank" :href="item.web_url" :alt="item.name">
...@@ -71,6 +112,22 @@ export default { ...@@ -71,6 +112,22 @@ export default {
</template> </template>
</gl-avatar-labeled> </gl-avatar-labeled>
</gl-avatar-link> </gl-avatar-link>
<gl-button
v-gl-modal-directive="`approve-confirmation-modal-${item.id}`"
:loading="item.loading"
:disabled="item.approved"
>
{{ $options.LABEL_APPROVE }}
</gl-button>
<gl-modal
:modal-id="`approve-confirmation-modal-${item.id}`"
:title="$options.LABEL_CONFIRM"
no-fade
@primary="approveMember(item.id)"
>
<p>{{ approveUserQuestion(item) }}</p>
</gl-modal>
</div>
</div> </div>
</template> </template>
......
import { s__ } from '~/locale'; import { s__, __ } from '~/locale';
// Pending members HTTP headers // Pending members HTTP headers
export const HEADER_TOTAL_ENTRIES = 'x-total'; export const HEADER_TOTAL_ENTRIES = 'x-total';
...@@ -9,3 +9,9 @@ export const AWAITING_MEMBER_SIGNUP_TEXT = s__('Billing|Awaiting member signup') ...@@ -9,3 +9,9 @@ export const AWAITING_MEMBER_SIGNUP_TEXT = s__('Billing|Awaiting member signup')
export const PENDING_MEMBERS_LIST_ERROR = s__( export const PENDING_MEMBERS_LIST_ERROR = s__(
'Billing|An error occurred while loading pending members list', 'Billing|An error occurred while loading pending members list',
); );
export const LABEL_APPROVE = __('Approve');
export const LABEL_CONFIRM = __('Confirm approval');
export const LABEL_CONFIRM_APPROVE = __('Are you sure you want to approve %{user}?');
export const APPROVAL_SUCCESSFUL_MESSAGE = s__('Billing|%{user} was successfully approved');
export const APPROVAL_ERROR_MESSAGE = s__('Billing|An error occurred while approving %{user}');
import * as GroupsApi from 'ee/api/groups_api'; import * as GroupsApi from 'ee/api/groups_api';
import createFlash from '~/flash'; import {
import { PENDING_MEMBERS_LIST_ERROR } from 'ee/pending_members/constants'; APPROVAL_ERROR_MESSAGE,
APPROVAL_SUCCESSFUL_MESSAGE,
PENDING_MEMBERS_LIST_ERROR,
} from '../constants';
import * as types from './mutation_types'; import * as types from './mutation_types';
export const fetchPendingMembersList = ({ commit, state }) => { export const fetchPendingMembersList = ({ commit, state }) => {
...@@ -12,8 +15,9 @@ export const fetchPendingMembersList = ({ commit, state }) => { ...@@ -12,8 +15,9 @@ export const fetchPendingMembersList = ({ commit, state }) => {
.then(({ data, headers }) => commit(types.RECEIVE_PENDING_MEMBERS_SUCCESS, { data, headers })) .then(({ data, headers }) => commit(types.RECEIVE_PENDING_MEMBERS_SUCCESS, { data, headers }))
.catch(() => { .catch(() => {
commit(types.RECEIVE_PENDING_MEMBERS_ERROR); commit(types.RECEIVE_PENDING_MEMBERS_ERROR);
createFlash({ commit(types.SHOW_ALERT, {
message: PENDING_MEMBERS_LIST_ERROR, alertMessage: PENDING_MEMBERS_LIST_ERROR,
alertVariant: 'danger',
}); });
}); });
}; };
...@@ -23,3 +27,28 @@ export const setCurrentPage = ({ commit, dispatch }, page) => { ...@@ -23,3 +27,28 @@ export const setCurrentPage = ({ commit, dispatch }, page) => {
dispatch('fetchPendingMembersList'); dispatch('fetchPendingMembersList');
}; };
export const approveMember = ({ commit, state }, id) => {
commit(types.SET_MEMBER_AS_LOADING, id);
return GroupsApi.approvePendingGroupMember(state.namespaceId, id)
.then(() => {
commit(types.SET_MEMBER_AS_APPROVED, id);
commit(types.SHOW_ALERT, {
memberId: id,
alertMessage: APPROVAL_SUCCESSFUL_MESSAGE,
alertVariant: 'info',
});
})
.catch(() => {
commit(types.SET_MEMBER_ERROR, id);
commit(types.SHOW_ALERT, {
memberId: id,
alertMessage: APPROVAL_ERROR_MESSAGE,
alertVariant: 'danger',
});
});
};
export const dismissAlert = ({ commit }) => {
commit(types.DISMISS_ALERT);
};
...@@ -3,3 +3,9 @@ export const RECEIVE_PENDING_MEMBERS_SUCCESS = 'RECEIVE_PENDING_MEMBERS_SUCCESS' ...@@ -3,3 +3,9 @@ export const RECEIVE_PENDING_MEMBERS_SUCCESS = 'RECEIVE_PENDING_MEMBERS_SUCCESS'
export const RECEIVE_PENDING_MEMBERS_ERROR = 'RECEIVE_PENDING_MEMBERS_ERROR'; export const RECEIVE_PENDING_MEMBERS_ERROR = 'RECEIVE_PENDING_MEMBERS_ERROR';
export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE'; export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
export const SET_MEMBER_AS_LOADING = 'SET_MEMBER_AS_LOADING';
export const SET_MEMBER_AS_APPROVED = 'SET_MEMBER_AS_APPROVED';
export const SET_MEMBER_ERROR = 'SET_MEMBER_ERROR';
export const DISMISS_ALERT = 'DISMISS_ALERT';
export const SHOW_ALERT = 'SHOW_ALERT';
import { sprintf } from '~/locale';
import { HEADER_TOTAL_ENTRIES, HEADER_PAGE_NUMBER, HEADER_ITEMS_PER_PAGE } from '../constants'; import { HEADER_TOTAL_ENTRIES, HEADER_PAGE_NUMBER, HEADER_ITEMS_PER_PAGE } from '../constants';
import * as types from './mutation_types'; import * as types from './mutation_types';
...@@ -26,4 +27,51 @@ export default { ...@@ -26,4 +27,51 @@ export default {
[types.SET_CURRENT_PAGE](state, pageNumber) { [types.SET_CURRENT_PAGE](state, pageNumber) {
state.page = pageNumber; state.page = pageNumber;
}, },
[types.SET_MEMBER_AS_LOADING](state, id) {
state.members = state.members.map((member) => {
if (member.id === id) {
return { ...member, loading: true };
}
return member;
});
},
[types.SET_MEMBER_AS_APPROVED](state, id) {
state.members = state.members.map((member) => {
if (member.id === id) {
return { ...member, approved: true, loading: false };
}
return member;
});
},
[types.SET_MEMBER_ERROR](state, id) {
state.members = state.members.map((member) => {
if (member.id === id) {
return { ...member, loading: false };
}
return member;
});
},
[types.DISMISS_ALERT](state) {
state.alertMessage = '';
},
[types.SHOW_ALERT](state, payload) {
const { memberId, alertMessage, alertVariant } = payload;
if (memberId) {
const member = state.members.find((m) => m.id === memberId);
state.alertMessage = sprintf(alertMessage, {
user: member.name || member.email,
});
} else {
state.alertMessage = alertMessage;
}
state.alertVariant = alertVariant;
},
}; };
export default ({ namespaceId = null, namespaceName = null } = {}) => ({ export default ({ namespaceId = null, namespaceName = null } = {}) => ({
isLoading: false, isLoading: false,
hasError: false, hasError: false,
alertMessage: '',
alertVariant: '',
namespaceId, namespaceId,
namespaceName, namespaceName,
members: [], members: [],
......
...@@ -12,6 +12,7 @@ describe('GroupsApi', () => { ...@@ -12,6 +12,7 @@ describe('GroupsApi', () => {
relative_url_root: dummyUrlRoot, relative_url_root: dummyUrlRoot,
}; };
const namespaceId = 1000; const namespaceId = 1000;
const memberId = 2;
let originalGon; let originalGon;
let mock; let mock;
...@@ -45,7 +46,6 @@ describe('GroupsApi', () => { ...@@ -45,7 +46,6 @@ describe('GroupsApi', () => {
}); });
describe('fetchBillableGroupMemberMemberships', () => { describe('fetchBillableGroupMemberMemberships', () => {
const memberId = 2;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${namespaceId}/billable_members/${memberId}/memberships`; const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${namespaceId}/billable_members/${memberId}/memberships`;
it('fetches memberships for the member', async () => { it('fetches memberships for the member', async () => {
...@@ -60,7 +60,6 @@ describe('GroupsApi', () => { ...@@ -60,7 +60,6 @@ describe('GroupsApi', () => {
}); });
describe('removeBillableMemberFromGroup', () => { describe('removeBillableMemberFromGroup', () => {
const memberId = 2;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${namespaceId}/billable_members/${memberId}`; const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${namespaceId}/billable_members/${memberId}`;
it('removes a billable member from a group', async () => { it('removes a billable member from a group', async () => {
...@@ -90,4 +89,18 @@ describe('GroupsApi', () => { ...@@ -90,4 +89,18 @@ describe('GroupsApi', () => {
}); });
}); });
}); });
describe('approvePendingGroupMember', () => {
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${namespaceId}/members/${memberId}/approve`;
it('approves a pending member from a group', async () => {
jest.spyOn(axios, 'put');
mock.onPut(expectedUrl).replyOnce(httpStatus.OK, []);
const { data } = await GroupsApi.approvePendingGroupMember(namespaceId, memberId);
expect(data).toEqual([]);
expect(axios.put).toHaveBeenCalledWith(expectedUrl);
});
});
}); });
...@@ -2,15 +2,27 @@ ...@@ -2,15 +2,27 @@
exports[`PendingMembersApp renders pending members 1`] = ` exports[`PendingMembersApp renders pending members 1`] = `
Array [ Array [
"<div data-testid=\\"pending-members-row\\" class=\\"gl-p-5 gl-border-0 gl-border-b-1! gl-border-gray-100 gl-border-solid\\"> "<div data-testid=\\"pending-members-row\\" class=\\"gl-p-5 gl-border-0 gl-border-b-1! gl-border-gray-100 gl-border-solid gl-display-flex gl-justify-content-space-between\\">
<gl-avatar-link-stub target=\\"blank\\" href=\\"http://127.0.0.1:3000/334050-1\\" alt=\\"334050-1 334050-1\\"> <gl-avatar-link-stub target=\\"blank\\" href=\\"http://127.0.0.1:3000/334050-1\\" alt=\\"334050-1 334050-1\\">
<gl-avatar-labeled-stub label=\\"334050-1 334050-1\\" sublabel=\\"\\" labellink=\\"\\" sublabellink=\\"\\" src=\\"https://www.gravatar.com/avatar/9987bae8f71451bb2d422d0596367b25?s=80&amp;d=identicon\\" size=\\"32\\"></gl-avatar-labeled-stub> <gl-avatar-labeled-stub label=\\"334050-1 334050-1\\" sublabel=\\"\\" labellink=\\"\\" sublabellink=\\"\\" src=\\"https://www.gravatar.com/avatar/9987bae8f71451bb2d422d0596367b25?s=80&amp;d=identicon\\" size=\\"32\\"></gl-avatar-labeled-stub>
</gl-avatar-link-stub> </gl-avatar-link-stub>
<gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" role=\\"button\\" tabindex=\\"0\\">
Approve
</gl-button-stub>
<gl-modal-stub modalid=\\"approve-confirmation-modal-177\\" titletag=\\"h4\\" title=\\"Confirm approval\\" modalclass=\\"\\" size=\\"md\\" dismisslabel=\\"Close\\" no-fade=\\"\\">
<p>Are you sure you want to approve 334050-1 334050-1?</p>
</gl-modal-stub>
</div>", </div>",
"<div data-testid=\\"pending-members-row\\" class=\\"gl-p-5 gl-border-0 gl-border-b-1! gl-border-gray-100 gl-border-solid\\"> "<div data-testid=\\"pending-members-row\\" class=\\"gl-p-5 gl-border-0 gl-border-b-1! gl-border-gray-100 gl-border-solid gl-display-flex gl-justify-content-space-between\\">
<gl-avatar-link-stub target=\\"blank\\"> <gl-avatar-link-stub target=\\"blank\\">
<gl-avatar-labeled-stub label=\\"first-invite@gitlab.com\\" sublabel=\\"\\" labellink=\\"\\" sublabellink=\\"\\" src=\\"https://www.gravatar.com/avatar/8bad6be3d5070e7f7865d91a50f44f1f?s=80&amp;d=identicon\\" size=\\"32\\"></gl-avatar-labeled-stub> <gl-avatar-labeled-stub label=\\"first-invite@gitlab.com\\" sublabel=\\"\\" labellink=\\"\\" sublabellink=\\"\\" src=\\"https://www.gravatar.com/avatar/8bad6be3d5070e7f7865d91a50f44f1f?s=80&amp;d=identicon\\" size=\\"32\\"></gl-avatar-labeled-stub>
</gl-avatar-link-stub> </gl-avatar-link-stub>
<gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" role=\\"button\\" tabindex=\\"0\\">
Approve
</gl-button-stub>
<gl-modal-stub modalid=\\"approve-confirmation-modal-178\\" titletag=\\"h4\\" title=\\"Confirm approval\\" modalclass=\\"\\" size=\\"md\\" dismisslabel=\\"Close\\" no-fade=\\"\\">
<p>Are you sure you want to approve first-invite@gitlab.com?</p>
</gl-modal-stub>
</div>", </div>",
] ]
`; `;
...@@ -3,14 +3,16 @@ import State from 'ee/pending_members/store/state'; ...@@ -3,14 +3,16 @@ import State from 'ee/pending_members/store/state';
import * as GroupsApi from 'ee/api/groups_api'; import * as GroupsApi from 'ee/api/groups_api';
import * as actions from 'ee/pending_members/store/actions'; import * as actions from 'ee/pending_members/store/actions';
import * as types from 'ee/pending_members/store/mutation_types'; import * as types from 'ee/pending_members/store/mutation_types';
import {
PENDING_MEMBERS_LIST_ERROR,
APPROVAL_SUCCESSFUL_MESSAGE,
APPROVAL_ERROR_MESSAGE,
} from 'ee/pending_members/constants';
import { mockDataMembers } from 'ee_jest/pending_members/mock_data'; import { mockDataMembers } from 'ee_jest/pending_members/mock_data';
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
jest.mock('~/flash');
describe('Pending members actions', () => { describe('Pending members actions', () => {
let state; let state;
let mock; let mock;
...@@ -71,15 +73,97 @@ describe('Pending members actions', () => { ...@@ -71,15 +73,97 @@ describe('Pending members actions', () => {
}); });
it('dispatches the request and error action', async () => { it('dispatches the request and error action', async () => {
const mockShowAlertPayload = {
alertMessage: PENDING_MEMBERS_LIST_ERROR,
alertVariant: 'danger',
};
await testAction({ await testAction({
action: actions.fetchPendingMembersList, action: actions.fetchPendingMembersList,
state, state,
expectedMutations: [ expectedMutations: [
{ type: types.REQUEST_PENDING_MEMBERS }, { type: types.REQUEST_PENDING_MEMBERS },
{ type: types.RECEIVE_PENDING_MEMBERS_ERROR }, { type: types.RECEIVE_PENDING_MEMBERS_ERROR },
{ type: types.SHOW_ALERT, payload: mockShowAlertPayload },
],
});
});
});
});
describe('approveMember', () => {
const memberId = 2;
beforeEach(() => {
gon.api_version = 'v4';
state.namespaceId = 1;
});
it('passes correct arguments to API call', () => {
const spy = jest.spyOn(GroupsApi, 'approvePendingGroupMember');
testAction({
action: actions.approveMember,
payload: memberId,
state,
expectedMutations: expect.anything(),
expectedActions: expect.anything(),
});
expect(spy).toBeCalledWith(state.namespaceId, memberId);
});
describe('on success', () => {
beforeEach(() => {
mock
.onPut(`/api/v4/groups/1/members/${memberId}/approve`)
.replyOnce(httpStatusCodes.NO_CONTENT);
});
it('dispatches the request and success action', async () => {
const mockShowAlertPayload = {
memberId,
alertMessage: APPROVAL_SUCCESSFUL_MESSAGE,
alertVariant: 'info',
};
await testAction({
action: actions.approveMember,
payload: memberId,
state,
expectedMutations: [
{ type: types.SET_MEMBER_AS_LOADING, payload: memberId },
{ type: types.SET_MEMBER_AS_APPROVED, payload: memberId },
{ type: types.SHOW_ALERT, payload: mockShowAlertPayload },
],
});
});
});
describe('on error', () => {
beforeEach(() => {
mock
.onPut(`/api/v4/groups/1/members/${memberId}/approve`)
.replyOnce(httpStatusCodes.NOT_FOUND, {});
});
it('dispatches the request and error action', async () => {
const mockShowAlertPayload = {
memberId,
alertMessage: APPROVAL_ERROR_MESSAGE,
alertVariant: 'danger',
};
await testAction({
action: actions.approveMember,
payload: memberId,
state,
expectedMutations: [
{ type: types.SET_MEMBER_AS_LOADING, payload: memberId },
{ type: types.SET_MEMBER_ERROR, payload: memberId },
{ type: types.SHOW_ALERT, payload: mockShowAlertPayload },
], ],
}); });
expect(createFlash).toHaveBeenCalled();
}); });
}); });
}); });
......
...@@ -4,6 +4,8 @@ import mutations from 'ee/pending_members/store/mutations'; ...@@ -4,6 +4,8 @@ import mutations from 'ee/pending_members/store/mutations';
import createState from 'ee/pending_members/store/state'; import createState from 'ee/pending_members/store/state';
describe('Pending members mutations', () => { describe('Pending members mutations', () => {
const alertMessage = 'This is an alert';
const alertVariant = 'info';
let state; let state;
beforeEach(() => { beforeEach(() => {
...@@ -63,4 +65,74 @@ describe('Pending members mutations', () => { ...@@ -63,4 +65,74 @@ describe('Pending members mutations', () => {
expect(state.page).toBe(1); expect(state.page).toBe(1);
}); });
}); });
describe(types.DISMISS_ALERT, () => {
beforeEach(() => {
state.alertMessage = alertMessage;
});
it('cleans alertMessage state', () => {
mutations[types.DISMISS_ALERT](state);
expect(state.alertMessage).toBe('');
});
});
describe(types.SHOW_ALERT, () => {
beforeEach(() => {
state.alertMessage = '';
state.alertVariant = '';
});
it('sets alertMessage and alertVariant', () => {
mutations[types.SHOW_ALERT](state, { alertMessage, alertVariant });
expect(state.alertMessage).toBe(alertMessage);
expect(state.alertVariant).toBe(alertVariant);
});
});
describe('member specific mutations', () => {
const memberId = mockDataMembers.data[0].id;
beforeEach(() => {
state.members = mockDataMembers.data;
});
describe(types.SET_MEMBER_AS_LOADING, () => {
it('sets member loading state to true', () => {
mutations[types.SET_MEMBER_AS_LOADING](state, memberId);
const member = state.members.find((m) => m.id === memberId);
expect(member.loading).toBeTruthy();
});
});
describe(types.SET_MEMBER_AS_APPROVED, () => {
it('sets member loading state to false and approved state to true', () => {
mutations[types.SET_MEMBER_AS_APPROVED](state, memberId);
const member = state.members.find((m) => m.id === memberId);
expect(member.loading).toBeFalsy();
expect(member.approved).toBeTruthy();
});
});
describe(types.SHOW_ALERT, () => {
beforeEach(() => {
state.alertMessage = '';
state.alertVariant = '';
});
it('sets alertMessage and alertVariant', () => {
mutations[types.SHOW_ALERT](state, {
memberId,
alertMessage: `${alertMessage}%{user}`,
alertVariant,
});
const member = state.members.find((m) => m.id === memberId);
expect(state.alertMessage).toBe(`${alertMessage}${member.name}`);
expect(state.alertVariant).toBe(alertVariant);
});
});
});
}); });
...@@ -4620,6 +4620,9 @@ msgstr "" ...@@ -4620,6 +4620,9 @@ msgstr ""
msgid "Are you sure you want to %{action} %{name}?" msgid "Are you sure you want to %{action} %{name}?"
msgstr "" msgstr ""
msgid "Are you sure you want to approve %{user}?"
msgstr ""
msgid "Are you sure you want to attempt to merge?" msgid "Are you sure you want to attempt to merge?"
msgstr "" msgstr ""
...@@ -5535,9 +5538,15 @@ msgstr "" ...@@ -5535,9 +5538,15 @@ msgstr ""
msgid "Billings|Your account has been validated" msgid "Billings|Your account has been validated"
msgstr "" msgstr ""
msgid "Billing|%{user} was successfully approved"
msgstr ""
msgid "Billing|An email address is only visible for users with public emails." msgid "Billing|An email address is only visible for users with public emails."
msgstr "" msgstr ""
msgid "Billing|An error occurred while approving %{user}"
msgstr ""
msgid "Billing|An error occurred while getting a billable member details" msgid "Billing|An error occurred while getting a billable member details"
msgstr "" msgstr ""
...@@ -9070,6 +9079,9 @@ msgstr "" ...@@ -9070,6 +9079,9 @@ msgstr ""
msgid "Confirm" msgid "Confirm"
msgstr "" msgstr ""
msgid "Confirm approval"
msgstr ""
msgid "Confirm new password" msgid "Confirm new password"
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