Commit 5f651fd8 authored by Peter Hegman's avatar Peter Hegman

Merge branch '350788-use-statistics-seats-card' into 'master'

Use StatisticsCard in usage_quotas/seats

See merge request gitlab-org/gitlab!82399
parents 1f9ba955 d7e1c5e0
......@@ -16,10 +16,8 @@ const Template = (_, { argTypes }) => ({
});
export const Default = Template.bind({});
/* eslint-disable @gitlab/require-i18n-strings */
Default.args = {
seatsUsed: 160,
seatsOwed: 10,
purchaseButtonLink: 'purchase.com/test',
purchaseButtonText: 'Add seats',
};
<script>
import { GlLink, GlIcon, GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
import { __, s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
export default {
......@@ -11,6 +11,7 @@ export default {
seatsUsedHelpText: __('Learn more about max seats used'),
seatsOwedText: __('Seats owed'),
seatsOwedHelpText: __('Learn more about seats owed'),
addSeatsText: s__('Billing|Add seats'),
},
helpLinks: {
seatsOwedLink: helpPagePath('subscriptions/gitlab_com/index', { anchor: 'seats-owed' }),
......@@ -73,7 +74,9 @@ export default {
class="gl-font-size-h-display gl-font-weight-bold gl-mb-3"
data-testid="seats-used-block"
>
{{ seatsUsed }}
<span class="gl-relative gl-top-1">
{{ seatsUsed }}
</span>
<span class="gl-font-lg">
{{ $options.i18n.seatsUsedText }}
</span>
......@@ -90,7 +93,9 @@ export default {
class="gl-font-size-h-display gl-font-weight-bold gl-mb-0"
data-testid="seats-owed-block"
>
{{ seatsOwed }}
<span class="gl-relative gl-top-1">
{{ seatsOwed }}
</span>
<span class="gl-font-lg">
{{ $options.i18n.seatsOwedText }}
</span>
......@@ -104,14 +109,15 @@ export default {
</p>
</div>
<gl-button
v-if="purchaseButtonLink && purchaseButtonText"
v-if="purchaseButtonLink"
:href="purchaseButtonLink"
category="primary"
target="_blank"
variant="confirm"
class="gl-ml-3 gl-align-self-start"
data-testid="purchase-button"
>
{{ purchaseButtonText }}
{{ $options.i18n.addSeatsText }}
</gl-button>
</div>
</template>
......@@ -13,6 +13,7 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex';
import { helpPagePath } from '~/helpers/help_page_helper';
import { visitUrl } from '~/lib/utils/url_utility';
import {
FIELDS,
......@@ -25,6 +26,8 @@ import {
} from 'ee/usage_quotas/seats/constants';
import { s__, __, sprintf, n__ } from '~/locale';
import FilterSortContainerRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import StatisticsCard from 'ee/usage_quotas/components/statistics_card.vue';
import StatisticsSeatsCard from 'ee/usage_quotas/components/statistics_seats_card.vue';
import RemoveBillableMemberModal from './remove_billable_member_modal.vue';
import SubscriptionSeatDetails from './subscription_seat_details.vue';
......@@ -46,6 +49,8 @@ export default {
RemoveBillableMemberModal,
SubscriptionSeatDetails,
FilterSortContainerRoot,
StatisticsCard,
StatisticsSeatsCard,
},
computed: {
...mapState([
......@@ -61,6 +66,12 @@ export default {
'billableMemberToRemove',
'search',
'sort',
'seatsInSubscription',
'seatsInUse',
'maxSeatsUsed',
'seatsOwed',
'addSeatsHref',
'hasNoSubscription',
]),
...mapGetters(['tableItems']),
currentPage: {
......@@ -92,13 +103,24 @@ export default {
shouldShowPendingMembersAlert() {
return this.pendingMembersCount > 0 && this.pendingMembersPagePath;
},
seatsInUsePercentage() {
return Math.round((this.seatsInUse * 100) / this.seatsInSubscription);
},
totalSeatsInSubscription() {
return this.hasNoSubscription ? '-' : String(this.seatsInSubscription);
},
totalSeatsInUse() {
return this.hasNoSubscription ? String(this.total) : String(this.seatsInUse);
},
},
created() {
this.fetchBillableMembersList();
this.fetchGitlabSubscription();
},
methods: {
...mapActions([
'fetchBillableMembersList',
'fetchGitlabSubscription',
'resetBillableMembers',
'setBillableMemberToRemove',
'setSearchQuery',
......@@ -141,6 +163,10 @@ export default {
),
filterUsersPlaceholder: __('Filter users'),
pendingMembersAlertButtonText: s__('Billing|View pending approvals'),
seatsInUseText: s__('Billings|Seats in use / Seats in subscription'),
seatsInUseLink: helpPagePath('subscription/gitlab_com/index', {
anchor: 'how-seat-usage-is-determined',
}),
},
avatarSize: AVATAR_SIZE,
fields: FIELDS,
......@@ -164,35 +190,43 @@ export default {
>
{{ pendingMembersAlertMessage }}
</gl-alert>
<div
class="gl-bg-gray-10 gl-p-6 gl-md-display-flex gl-justify-content-space-between gl-align-items-center"
>
<div data-testid="heading-info">
<h4
data-testid="heading-info-text"
class="gl-font-base gl-display-inline-block gl-font-weight-normal"
>
{{ s__('Billing|Users occupying seats in') }}
<span class="gl-font-weight-bold">{{ namespaceName }} {{ s__('Billing|Group') }}</span>
</h4>
<gl-badge>{{ total }}</gl-badge>
</div>
<div class="gl-bg-gray-10 gl-display-flex gl-sm-flex-direction-column gl-p-5">
<statistics-card
:help-link="$options.i18n.seatsInUseLink"
:description="$options.i18n.seatsInUseText"
:percentage="seatsInUsePercentage"
:usage-value="totalSeatsInUse"
:total-value="totalSeatsInSubscription"
class="gl-w-full gl-md-w-half gl-md-mr-5"
/>
<gl-button v-if="seatUsageExportPath" data-testid="export-button" :href="seatUsageExportPath">
{{ s__('Billing|Export list') }}
</gl-button>
<statistics-seats-card
:seats-used="maxSeatsUsed"
:seats-owed="seatsOwed"
:purchase-button-link="addSeatsHref"
class="gl-w-full gl-md-w-half gl-md-mt-0 gl-mt-5"
/>
</div>
<div class="gl-bg-gray-10 gl-p-3">
<div class="gl-bg-gray-10 gl-p-5 gl-display-flex">
<filter-sort-container-root
:namespace="namespaceId"
:tokens="[]"
:search-input-placeholder="$options.i18n.filterUsersPlaceholder"
:sort-options="$options.sortOptions"
initial-sort-by="last_activity_on_desc"
class="gl-flex-grow-1"
@onFilter="applyFilter"
@onSort="setSortOption"
/>
<gl-button
v-if="seatUsageExportPath"
data-testid="export-button"
:href="seatUsageExportPath"
class="gl-ml-3"
>
{{ s__('Billing|Export list') }}
</gl-button>
</div>
<gl-table
......
import Vue from 'vue';
import Vuex from 'vuex';
import { parseBoolean } from '~/lib/utils/common_utils';
import SubscriptionSeats from './components/subscription_seats.vue';
import initialStore from './store';
......@@ -18,6 +19,8 @@ export default (containerId = 'js-seat-usage-app') => {
seatUsageExportPath,
pendingMembersPagePath,
pendingMembersCount,
addSeatsHref,
hasNoSubscription,
} = el.dataset;
return new Vue({
......@@ -31,6 +34,8 @@ export default (containerId = 'js-seat-usage-app') => {
seatUsageExportPath,
pendingMembersPagePath,
pendingMembersCount,
addSeatsHref,
hasNoSubscription: parseBoolean(hasNoSubscription),
}),
),
render(createElement) {
......
import * as GroupsApi from 'ee/api/groups_api';
import createFlash, { FLASH_TYPES } from '~/flash';
import Api from 'ee/api';
import { createAlert, VARIANT_SUCCESS } from '~/flash';
import { s__ } from '~/locale';
import * as types from './mutation_types';
......@@ -13,16 +14,34 @@ export const fetchBillableMembersList = ({ commit, dispatch, state }) => {
.catch(() => dispatch('receiveBillableMembersListError'));
};
export const fetchGitlabSubscription = ({ commit, dispatch, state }) => {
commit(types.REQUEST_GITLAB_SUBSCRIPTION);
return Api.userSubscription(state.namespaceId)
.then(({ data }) => dispatch('receiveGitlabSubscriptionSuccess', data))
.catch(() => dispatch('receiveGitlabSubscriptionError'));
};
export const receiveBillableMembersListSuccess = ({ commit }, response) =>
commit(types.RECEIVE_BILLABLE_MEMBERS_SUCCESS, response);
export const receiveBillableMembersListError = ({ commit }) => {
createFlash({
message: s__('Billing|An error occurred while loading billable members list'),
createAlert({
message: s__('Billing|An error occurred while loading billable members list.'),
});
commit(types.RECEIVE_BILLABLE_MEMBERS_ERROR);
};
export const receiveGitlabSubscriptionSuccess = ({ commit }, response) =>
commit(types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS, response);
export const receiveGitlabSubscriptionError = ({ commit }) => {
createAlert({
message: s__('Billing|An error occurred while loading GitLab subscription details.'),
});
commit(types.RECEIVE_GITLAB_SUBSCRIPTION_ERROR);
};
export const resetBillableMembers = ({ commit }) => {
commit(types.RESET_BILLABLE_MEMBERS);
};
......@@ -40,17 +59,17 @@ export const removeBillableMember = ({ dispatch, state }) => {
export const removeBillableMemberSuccess = ({ dispatch, commit }) => {
dispatch('fetchBillableMembersList');
createFlash({
createAlert({
message: s__('Billing|User was successfully removed'),
type: FLASH_TYPES.SUCCESS,
variant: VARIANT_SUCCESS,
});
commit(types.REMOVE_BILLABLE_MEMBER_SUCCESS);
};
export const removeBillableMemberError = ({ commit }) => {
createFlash({
message: s__('Billing|An error occurred while removing a billable member'),
createAlert({
message: s__('Billing|An error occurred while removing a billable member.'),
});
commit(types.REMOVE_BILLABLE_MEMBER_ERROR);
};
......@@ -77,8 +96,8 @@ export const fetchBillableMemberDetails = ({ dispatch, commit, state }, memberId
export const fetchBillableMemberDetailsError = ({ commit }, memberId) => {
commit(types.FETCH_BILLABLE_MEMBER_DETAILS_ERROR, memberId);
createFlash({
message: s__('Billing|An error occurred while getting a billable member details'),
createAlert({
message: s__('Billing|An error occurred while getting a billable member details.'),
});
};
......
......@@ -2,6 +2,10 @@ export const REQUEST_BILLABLE_MEMBERS = 'REQUEST_BILLABLE_MEMBERS';
export const RECEIVE_BILLABLE_MEMBERS_SUCCESS = 'RECEIVE_BILLABLE_MEMBERS_SUCCESS';
export const RECEIVE_BILLABLE_MEMBERS_ERROR = 'RECEIVE_BILLABLE_MEMBERS_ERROR';
export const REQUEST_GITLAB_SUBSCRIPTION = 'REQUEST_GITLAB_SUBSCRIPTION';
export const RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS = 'RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS';
export const RECEIVE_GITLAB_SUBSCRIPTION_ERROR = 'RECEIVE_GITLAB_SUBSCRIPTION_ERROR';
export const SET_SEARCH_QUERY = 'SET_SEARCH_QUERY';
export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
export const SET_SORT_OPTION = 'SET_SORT_OPTION';
......
......@@ -12,6 +12,11 @@ export default {
state.hasError = false;
},
[types.REQUEST_GITLAB_SUBSCRIPTION](state) {
state.isLoading = true;
state.hasError = false;
},
[types.RECEIVE_BILLABLE_MEMBERS_SUCCESS](state, payload) {
const { data, headers } = payload;
state.members = data;
......@@ -23,11 +28,27 @@ export default {
state.isLoading = false;
},
[types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, payload) {
const { usage } = payload;
state.seatsInSubscription = usage?.seats_in_subscription ?? 0;
state.seatsInUse = usage?.seats_in_use ?? 0;
state.maxSeatsUsed = usage?.max_seats_used ?? 0;
state.seatsOwed = usage?.seats_owed ?? 0;
state.isLoading = false;
},
[types.RECEIVE_BILLABLE_MEMBERS_ERROR](state) {
state.isLoading = false;
state.hasError = true;
},
[types.RECEIVE_GITLAB_SUBSCRIPTION_ERROR](state) {
state.isLoading = false;
state.hasError = true;
},
[types.SET_SEARCH_QUERY](state, searchString) {
state.search = searchString ?? null;
},
......
......@@ -4,6 +4,8 @@ export default ({
seatUsageExportPath = null,
pendingMembersPagePath = null,
pendingMembersCount = 0,
addSeatsHref = '',
hasNoSubscription = null,
} = {}) => ({
isLoading: false,
hasError: false,
......@@ -20,4 +22,10 @@ export default ({
userDetails: {},
search: null,
sort: 'last_activity_on_desc',
seatsInSubscription: null,
seatsInUse: null,
maxSeatsUsed: null,
seatsOwed: null,
hasNoSubscription,
addSeatsHref,
});
......@@ -35,7 +35,7 @@
= s_('UsageQuota|Storage')
.tab-content
.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), pending_members_page_path: pending_members_page_path, pending_members_count: pending_members_count } }
#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, add_seats_href: add_seats_url(@group), has_no_subscription: @group.has_free_or_no_subscription?.to_s } }
.tab-pane#pipelines-quota-tab
#js-ci-minutes-usage-group{ data: { namespace_id: @group.id } }
= render "namespaces/pipelines_quota/list",
......
......@@ -5,12 +5,10 @@ import StatisticsSeatsCard from 'ee/usage_quotas/components/statistics_seats_car
describe('StatisticsSeatsCard', () => {
let wrapper;
const purchaseButtonLink = 'https://gitlab.com/purchase-more-seats';
const purchaseButtonText = 'Add seats';
const defaultProps = {
seatsUsed: 20,
seatsOwed: 5,
purchaseButtonLink,
purchaseButtonText,
};
const createComponent = (props = {}) => {
......@@ -71,7 +69,7 @@ describe('StatisticsSeatsCard', () => {
expect(purchaseButton.exists()).toBe(true);
expect(purchaseButton.attributes('href')).toBe(purchaseButtonLink);
expect(purchaseButton.text()).toBe(purchaseButtonText);
expect(purchaseButton.attributes('target')).toBe('_blank');
});
it('does not render purchase button if purchase link is not passed', () => {
......@@ -79,11 +77,5 @@ describe('StatisticsSeatsCard', () => {
expect(findPurchaseButton().exists()).toBe(false);
});
it('does not render purchase button if purchase text is not passed', () => {
createComponent({ purchaseButtonText: null });
expect(findPurchaseButton().exists()).toBe(false);
});
});
});
......@@ -11,6 +11,8 @@ import {
import { mount, shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import StatisticsCard from 'ee/usage_quotas/components/statistics_card.vue';
import StatisticsSeatsCard from 'ee/usage_quotas/components/statistics_seats_card.vue';
import SubscriptionSeats from 'ee/usage_quotas/seats/components/subscription_seats.vue';
import { CANNOT_REMOVE_BILLABLE_MEMBER_MODAL_CONTENT } from 'ee/usage_quotas/seats/constants';
import { mockDataSeats, mockTableItems } from 'ee_jest/usage_quotas/seats/mock_data';
......@@ -21,6 +23,7 @@ Vue.use(Vuex);
const actionSpies = {
fetchBillableMembersList: jest.fn(),
fetchGitlabSubscription: jest.fn(),
resetBillableMembers: jest.fn(),
setBillableMemberToRemove: jest.fn(),
setSearchQuery: jest.fn(),
......@@ -70,10 +73,6 @@ describe('Subscription Seats', () => {
const findTable = () => wrapper.findComponent(GlTable);
const findPageHeading = () => wrapper.find('[data-testid="heading-info"]');
const findPageHeadingText = () => findPageHeading().find('[data-testid="heading-info-text"]');
const findPageHeadingBadge = () => findPageHeading().findComponent(GlBadge);
const findExportButton = () => wrapper.findByTestId('export-button');
const findSearchBox = () => wrapper.findComponent(FilterSortContainerRoot);
......@@ -81,6 +80,8 @@ describe('Subscription Seats', () => {
const findAllRemoveUserItems = () => wrapper.findAllByTestId('remove-user');
const findErrorModal = () => wrapper.findComponent(GlModal);
const findStatisticsCard = () => wrapper.findComponent(StatisticsCard);
const findStatisticsSeatsCard = () => wrapper.findComponent(StatisticsSeatsCard);
const serializeUser = (rowWrapper) => {
const avatarLink = rowWrapper.findComponent(GlAvatarLink);
......@@ -142,13 +143,6 @@ describe('Subscription Seats', () => {
wrapper.destroy();
});
describe('heading text', () => {
it('contains the group name and total seats number', () => {
expect(findPageHeadingText().text()).toMatch(providedFields.namespaceName);
expect(findPageHeadingBadge().text()).toMatch('300');
});
});
describe('export button', () => {
it('has the correct href', () => {
expect(findExportButton().attributes().href).toBe(providedFields.seatUsageExportPath);
......@@ -241,6 +235,52 @@ describe('Subscription Seats', () => {
});
});
describe('statistics cards', () => {
beforeEach(() => {
wrapper = createComponent({
initialState: {
seatsInSubscription: 3,
seatsInUse: 2,
maxSeatsUsed: 3,
seatsOwed: 1,
},
});
});
it('calls the correct action on create', () => {
expect(actionSpies.fetchGitlabSubscription).toHaveBeenCalled();
});
it('renders <statistics-card> with the necessary props', () => {
const statisticsCard = findStatisticsCard();
expect(statisticsCard.exists()).toBe(true);
expect(statisticsCard.props()).toEqual(
expect.objectContaining({
description: 'Seats in use / Seats in subscription',
helpLink: '/help/subscription/gitlab_com/index#how-seat-usage-is-determined',
percentage: 67,
totalUnit: null,
totalValue: '3',
usageUnit: null,
usageValue: '2',
}),
);
});
it('renders <statistics-seats-card> with the necessary props', () => {
const statisticsSeatsCard = findStatisticsSeatsCard();
expect(statisticsSeatsCard.exists()).toBe(true);
expect(statisticsSeatsCard.props()).toEqual(
expect.objectContaining({
seatsOwed: 1,
seatsUsed: 3,
}),
);
});
});
describe('is loading', () => {
beforeEach(() => {
wrapper = createComponent({ initialState: { isLoading: true } });
......
......@@ -123,3 +123,24 @@ export const mockTableItems = [
},
},
];
export const mockUserSubscription = {
plan: {
code: null,
name: null,
trial: false,
auto_renew: null,
upgradable: false,
},
usage: {
seats_in_subscription: 10,
seats_in_use: 5,
max_seats_used: 2,
seats_owed: 3,
},
billing: {
subscription_start_date: '2022-03-08',
subscription_end_date: null,
trial_ends_on: null,
},
};
import MockAdapter from 'axios-mock-adapter';
import * as GroupsApi from 'ee/api/groups_api';
import Api from 'ee/api';
import * as actions from 'ee/usage_quotas/seats/store/actions';
import * as types from 'ee/usage_quotas/seats/store/mutation_types';
import State from 'ee/usage_quotas/seats/store/state';
import { mockDataSeats, mockMemberDetails } from 'ee_jest/usage_quotas/seats/mock_data';
import {
mockDataSeats,
mockMemberDetails,
mockUserSubscription,
} from 'ee_jest/usage_quotas/seats/mock_data';
import testAction from 'helpers/vuex_action_helper';
import createFlash, { FLASH_TYPES } from '~/flash';
import { createAlert, VARIANT_SUCCESS } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
......@@ -105,7 +110,91 @@ describe('seats actions', () => {
expectedMutations: [{ type: types.RECEIVE_BILLABLE_MEMBERS_ERROR }],
});
expect(createFlash).toHaveBeenCalled();
expect(createAlert).toHaveBeenCalled();
});
});
describe('fetchGitlabSubscription', () => {
beforeEach(() => {
gon.api_version = 'v4';
state.namespaceId = 1;
});
it('passes correct arguments to Api call', () => {
const spy = jest.spyOn(Api, 'userSubscription');
testAction({
action: actions.fetchGitlabSubscription,
state,
expectedMutations: expect.anything(),
expectedActions: expect.anything(),
});
expect(spy).toBeCalledWith(state.namespaceId);
});
describe('on success', () => {
beforeEach(() => {
mock
.onGet('/api/v4/namespaces/1/gitlab_subscription')
.replyOnce(httpStatusCodes.OK, mockUserSubscription);
});
it('should dispatch the request and success actions', () => {
testAction({
action: actions.fetchGitlabSubscription,
state,
expectedActions: [
{
type: 'receiveGitlabSubscriptionSuccess',
payload: mockUserSubscription,
},
],
expectedMutations: [{ type: types.REQUEST_GITLAB_SUBSCRIPTION }],
});
});
});
describe('on error', () => {
beforeEach(() => {
mock
.onGet('/api/v4/namespaces/1/gitlab_subscription')
.replyOnce(httpStatusCodes.NOT_FOUND, {});
});
it('should dispatch the request and error actions', () => {
testAction({
action: actions.fetchGitlabSubscription,
state,
expectedActions: [{ type: 'receiveGitlabSubscriptionError' }],
expectedMutations: [{ type: types.REQUEST_GITLAB_SUBSCRIPTION }],
});
});
});
});
describe('receiveGitlabSubscriptionSuccess', () => {
it('should commit the success mutation', () => {
testAction({
action: actions.receiveGitlabSubscriptionSuccess,
payload: mockDataSeats,
state,
expectedMutations: [
{ type: types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS, payload: mockDataSeats },
],
});
});
});
describe('receiveGitlabSubscriptionError', () => {
it('should commit the error mutation', async () => {
await testAction({
action: actions.receiveGitlabSubscriptionError,
state,
expectedMutations: [{ type: types.RECEIVE_GITLAB_SUBSCRIPTION_ERROR }],
});
expect(createAlert).toHaveBeenCalled();
});
});
......@@ -187,9 +276,9 @@ describe('seats actions', () => {
expectedMutations: [{ type: types.REMOVE_BILLABLE_MEMBER_SUCCESS }],
});
expect(createFlash).toHaveBeenCalledWith({
expect(createAlert).toHaveBeenCalledWith({
message: 'User was successfully removed',
type: FLASH_TYPES.SUCCESS,
variant: VARIANT_SUCCESS,
});
});
});
......@@ -202,8 +291,8 @@ describe('seats actions', () => {
expectedMutations: [{ type: types.REMOVE_BILLABLE_MEMBER_ERROR }],
});
expect(createFlash).toHaveBeenCalledWith({
message: 'An error occurred while removing a billable member',
expect(createAlert).toHaveBeenCalledWith({
message: 'An error occurred while removing a billable member.',
});
});
});
......@@ -304,15 +393,15 @@ describe('seats actions', () => {
});
});
it('calls createFlash', async () => {
it('calls createAlert', async () => {
await testAction({
action: actions.fetchBillableMemberDetailsError,
state,
expectedMutations: [{ type: types.FETCH_BILLABLE_MEMBER_DETAILS_ERROR }],
});
expect(createFlash).toHaveBeenCalledWith({
message: 'An error occurred while getting a billable member details',
expect(createAlert).toHaveBeenCalledWith({
message: 'An error occurred while getting a billable member details.',
});
});
});
......
import * as types from 'ee/usage_quotas/seats/store/mutation_types';
import mutations from 'ee/usage_quotas/seats/store/mutations';
import createState from 'ee/usage_quotas/seats/store/state';
import { mockDataSeats, mockMemberDetails } from 'ee_jest/usage_quotas/seats/mock_data';
import {
mockDataSeats,
mockMemberDetails,
mockUserSubscription,
} from 'ee_jest/usage_quotas/seats/mock_data';
describe('EE seats module mutations', () => {
let state;
......@@ -16,11 +20,11 @@ describe('EE seats module mutations', () => {
});
it('sets isLoading to true', () => {
expect(state.isLoading).toBeTruthy();
expect(state.isLoading).toBe(true);
});
it('sets hasError to false', () => {
expect(state.hasError).toBeFalsy();
expect(state.hasError).toBe(false);
});
});
......@@ -38,7 +42,7 @@ describe('EE seats module mutations', () => {
});
it('sets isLoading to false', () => {
expect(state.isLoading).toBeFalsy();
expect(state.isLoading).toBe(false);
});
});
......@@ -48,11 +52,75 @@ describe('EE seats module mutations', () => {
});
it('sets isLoading to false', () => {
expect(state.isLoading).toBeFalsy();
expect(state.isLoading).toBe(false);
});
it('sets hasError to true', () => {
expect(state.hasError).toBeTruthy();
expect(state.hasError).toBe(true);
});
});
describe(types.REQUEST_GITLAB_SUBSCRIPTION, () => {
beforeEach(() => {
mutations[types.REQUEST_GITLAB_SUBSCRIPTION](state);
});
it('sets isLoading to true', () => {
expect(state.isLoading).toBe(true);
});
it('sets hasError to false', () => {
expect(state.hasError).toBe(false);
});
});
describe(types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS, () => {
describe('when subscription data is passed', () => {
beforeEach(() => {
mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, mockUserSubscription);
});
it('sets state as expected', () => {
expect(state.seatsInSubscription).toBe(mockUserSubscription.usage.seats_in_subscription);
expect(state.seatsInUse).toBe(mockUserSubscription.usage.seats_in_use);
expect(state.maxSeatsUsed).toBe(mockUserSubscription.usage.max_seats_used);
expect(state.seatsOwed).toBe(mockUserSubscription.usage.seats_owed);
});
it('sets isLoading to false', () => {
expect(state.isLoading).toBe(false);
});
});
describe('when subscription data is not passed', () => {
beforeEach(() => {
mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_SUCCESS](state, {});
});
it('sets state as expected', () => {
expect(state.seatsInSubscription).toBe(0);
expect(state.seatsInUse).toBe(0);
expect(state.maxSeatsUsed).toBe(0);
expect(state.seatsOwed).toBe(0);
});
it('sets isLoading to false', () => {
expect(state.isLoading).toBe(false);
});
});
});
describe(types.RECEIVE_GITLAB_SUBSCRIPTION_ERROR, () => {
beforeEach(() => {
mutations[types.RECEIVE_GITLAB_SUBSCRIPTION_ERROR](state);
});
it('sets isLoading to false', () => {
expect(state.isLoading).toBe(false);
});
it('sets hasError to true', () => {
expect(state.hasError).toBe(true);
});
});
......@@ -85,11 +153,11 @@ describe('EE seats module mutations', () => {
expect(state.page).toBeNull();
expect(state.perPage).toBeNull();
expect(state.isLoading).toBeFalsy();
expect(state.isLoading).toBe(false);
});
it('sets isLoading to false', () => {
expect(state.isLoading).toBeFalsy();
expect(state.isLoading).toBe(false);
});
});
......
......@@ -5779,6 +5779,9 @@ msgstr ""
msgid "Billings|Reactivate trial"
msgstr ""
msgid "Billings|Seats in use / Seats in subscription"
msgstr ""
msgid "Billings|Shared runners cannot be enabled until a valid credit card is on file."
msgstr ""
......@@ -5806,22 +5809,28 @@ msgstr ""
msgid "Billing|%{user} was successfully approved"
msgstr ""
msgid "Billing|Add seats"
msgstr ""
msgid "Billing|An email address is only visible for users with public emails."
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 ""
msgid "Billing|An error occurred while loading GitLab subscription details."
msgstr ""
msgid "Billing|An error occurred while loading billable members list"
msgid "Billing|An error occurred while loading billable members list."
msgstr ""
msgid "Billing|An error occurred while loading pending members list"
msgstr ""
msgid "Billing|An error occurred while removing a billable member"
msgid "Billing|An error occurred while removing a billable member."
msgstr ""
msgid "Billing|Awaiting member signup"
......@@ -5839,9 +5848,6 @@ msgstr ""
msgid "Billing|Export list"
msgstr ""
msgid "Billing|Group"
msgstr ""
msgid "Billing|Group invite"
msgstr ""
......@@ -5869,9 +5875,6 @@ msgstr ""
msgid "Billing|User was successfully removed"
msgstr ""
msgid "Billing|Users occupying seats in"
msgstr ""
msgid "Billing|View pending approvals"
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