Skip to content
Snippets Groups Projects
Commit 5f11a67c authored by Michael Lunøe's avatar Michael Lunøe :palm_tree:
Browse files

Merge branch 'ag/417976-update-button-for-public-groups' into 'master'

Update button for Public Groups (Free Tier) in Usage Quotas page

See merge request !135924



Merged-by: default avatarMichael Lunøe <michael.lunoe@gmail.com>
Approved-by: default avatarTim Noah <tnoah@gitlab.com>
Approved-by: default avatarSharmad Nachnolkar <snachnolkar@gitlab.com>
Approved-by: default avatarMichael Lunøe <michael.lunoe@gmail.com>
Reviewed-by: default avatarMichael Lunøe <michael.lunoe@gmail.com>
Reviewed-by: default avatarAngelo Gulina <agulina@gitlab.com>
Reviewed-by: default avatarSharmad Nachnolkar <snachnolkar@gitlab.com>
Co-authored-by: default avatarAngelo Gulina <agulina@gitlab.com>
parents ed47ba01 63330a21
No related branches found
No related tags found
1 merge request!135924Update button for Public Groups (Free Tier) in Usage Quotas page
Pipeline #1068133054 passed
Pipeline: E2E Omnibus GitLab EE

#1068271935

    Pipeline: E2E GDK

    #1068137509

      Pipeline: [ipynbdiff gem] Ruby 3.0 pipeline

      #1068133739

        +20
        query getSubscriptionPlan($namespaceId: ID!) {
        subscription @client(namespaceId: $namespaceId) {
        plan @client {
        code
        }
        }
        }
        <script>
        import { GlLink, GlIcon, GlButton, GlModalDirective, GlSkeletonLoader } from '@gitlab/ui';
        import { s__ } from '~/locale';
        import * as Sentry from '~/sentry/sentry_browser_wrapper';
        import { getSubscriptionPermissionsData } from 'ee/fulfillment/shared_queries/subscription_actions_reason.customer.query.graphql';
        import getSubscriptionPlanQuery from 'ee/fulfillment/shared_queries/subscription_plan.query.graphql';
        import {
        addSeatsText,
        EXPLORE_PAID_PLANS_CLICKED,
        PLAN_CODE_FREE,
        seatsOwedHelpText,
        seatsOwedLink,
        seatsOwedText,
        ......@@ -13,7 +18,7 @@ import {
        import Tracking from '~/tracking';
        import { visitUrl } from '~/lib/utils/url_utility';
        import { LIMITED_ACCESS_KEYS } from 'ee/usage_quotas/components/constants';
        import LimitedAccessModal from '../../components/limited_access_modal.vue';
        import LimitedAccessModal from 'ee/usage_quotas/components/limited_access_modal.vue';
        export default {
        name: 'StatisticsSeatsCard',
        ......@@ -31,44 +36,31 @@ export default {
        seatsOwedText,
        seatsOwedHelpText,
        addSeatsText,
        explorePlansText: s__('Billing|Explore paid plans'),
        },
        mixins: [Tracking.mixin()],
        inject: ['explorePlansPath'],
        props: {
        /**
        * Number of seats used
        */
        seatsUsed: {
        type: Number,
        required: false,
        default: null,
        },
        /**
        * Number of seats owed
        */
        seatsOwed: {
        type: Number,
        required: false,
        default: null,
        },
        /**
        * Link for purchase seats button
        */
        purchaseButtonLink: {
        type: String,
        required: false,
        default: null,
        },
        /**
        * Text for the purchase seats button
        */
        purchaseButtonText: {
        type: String,
        required: false,
        default: null,
        },
        /**
        * Id of the attached namespace
        */
        namespaceId: {
        type: String,
        required: true,
        ......@@ -76,26 +68,21 @@ export default {
        },
        data() {
        return {
        plan: {},
        subscriptionPermissions: null,
        showLimitedAccessModal: false,
        };
        },
        apollo: {
        subscriptionPermissions: {
        query: getSubscriptionPermissionsData,
        client: 'customersDotClient',
        variables() {
        return {
        namespaceId: parseInt(this.namespaceId, 10),
        };
        },
        update: (data) => ({
        ...data.subscription,
        reason: data.userActionAccess?.limitedAccessReason,
        }),
        },
        },
        computed: {
        hasLimitedAccess() {
        return (
        gon.features?.limitedAccessModal &&
        LIMITED_ACCESS_KEYS.includes(this.subscriptionPermissions.reason)
        );
        },
        isFreePlan() {
        return this.plan.code === PLAN_CODE_FREE;
        },
        shouldRenderSeatsUsedBlock() {
        return this.seatsUsed !== null;
        },
        ......@@ -103,37 +90,81 @@ export default {
        return this.seatsOwed !== null;
        },
        canAddSeats() {
        if (this.isFreePlan) {
        return false;
        }
        return this.subscriptionPermissions?.canAddSeats ?? true;
        },
        parsedNamespaceId() {
        return parseInt(this.namespaceId, 10);
        },
        shouldShowModal() {
        return (
        !this.canAddSeats &&
        gon.features?.limitedAccessModal &&
        LIMITED_ACCESS_KEYS.includes(this.subscriptionPermissions.reason)
        );
        return !this.canAddSeats && this.hasLimitedAccess;
        },
        shouldShowAddSeatsButton() {
        return (
        !this.subscriptionPermissions?.communityPlan && this.purchaseButtonLink && !this.isLoading
        );
        if (this.isLoading || !this.purchaseButtonLink) {
        return false;
        }
        return this.canAddSeats || this.hasLimitedAccess;
        },
        shouldShowExplorePaidPlansButton() {
        if (this.isLoading) {
        return false;
        }
        return this.isFreePlan;
        },
        isLoading() {
        return this.$apollo.loading;
        },
        },
        methods: {
        trackClick() {
        this.track('click_button', { label: 'add_seats_saas', property: 'usage_quotas_page' });
        apollo: {
        subscriptionPermissions: {
        query: getSubscriptionPermissionsData,
        client: 'customersDotClient',
        variables() {
        return {
        namespaceId: this.parsedNamespaceId,
        };
        },
        update: (data) => ({
        ...data.subscription,
        reason: data.userActionAccess?.limitedAccessReason,
        }),
        error: (error) => {
        Sentry.captureException(error);
        },
        },
        plan: {
        query: getSubscriptionPlanQuery,
        variables() {
        return {
        namespaceId: this.parsedNamespaceId,
        };
        },
        update: (data) => {
        return data?.subscription?.plan || {};
        },
        error: (error) => {
        Sentry.captureException(error);
        },
        },
        },
        methods: {
        handleAddSeats() {
        if (this.shouldShowModal) {
        this.showLimitedAccessModal = true;
        return;
        }
        this.trackClick();
        this.trackAddSeats();
        visitUrl(this.purchaseButtonLink);
        },
        trackAddSeats() {
        this.track('click_button', { label: 'add_seats_saas', property: 'usage_quotas_page' });
        },
        trackExplorePlans() {
        this.track('click_button', { label: EXPLORE_PAID_PLANS_CLICKED });
        },
        },
        };
        </script>
        ......@@ -199,6 +230,18 @@ export default {
        >
        {{ $options.i18n.addSeatsText }}
        </gl-button>
        <gl-button
        v-if="shouldShowExplorePaidPlansButton"
        :href="explorePlansPath"
        category="primary"
        target="_blank"
        variant="confirm"
        class="gl-ml-3 gl-align-self-start"
        data-testid="explore-paid-plans"
        @click="trackExplorePlans"
        >
        {{ $options.i18n.explorePlansText }}
        </gl-button>
        <limited-access-modal
        v-if="shouldShowModal"
        v-model="showLimitedAccessModal"
        ......
        ......@@ -16,6 +16,7 @@ import {
        import { sprintf, n__ } from '~/locale';
        import StatisticsCard from 'ee/usage_quotas/components/statistics_card.vue';
        import StatisticsSeatsCard from 'ee/usage_quotas/seats/components/statistics_seats_card.vue';
        import { updateSubscriptionPlanApolloCache } from 'ee/usage_quotas/seats/graphql/utils';
        import SubscriptionUpgradeInfoCard from './subscription_upgrade_info_card.vue';
        import SubscriptionUserList from './subscription_user_list.vue';
        ......@@ -121,6 +122,14 @@ export default {
        },
        },
        created() {
        /* This will be removed with https://gitlab.com/groups/gitlab-org/-/epics/11942 */
        this.$store.subscribeAction({
        after: (action, state) => {
        if (action.type === 'receiveGitlabSubscriptionSuccess') {
        updateSubscriptionPlanApolloCache(this.$apolloProvider, { code: state.planCode });
        }
        },
        });
        this.fetchBillableMembersList();
        this.fetchGitlabSubscription();
        },
        ......
        import produce from 'immer';
        import getSubscriptionPlanQuery from 'ee/fulfillment/shared_queries/subscription_plan.query.graphql';
        const PLAN_TYPE = 'Plan';
        const SUBSCRIPTION_TYPE = 'Subscription';
        export const writeDataToApolloCache = (apolloProvider, { code = '' } = {}) => {
        apolloProvider.clients.defaultClient.cache.writeQuery({
        query: getSubscriptionPlanQuery,
        data: {
        subscription: {
        __typename: SUBSCRIPTION_TYPE,
        plan: {
        __typename: PLAN_TYPE,
        code,
        },
        },
        },
        });
        return apolloProvider;
        };
        export const updateSubscriptionPlanApolloCache = (apolloProvider, { code = '' } = {}) => {
        const sourceData = apolloProvider.clients.defaultClient.cache.readQuery({
        query: getSubscriptionPlanQuery,
        });
        if (!sourceData) {
        return;
        }
        const data = produce(sourceData, (draftState) => {
        draftState.subscription = {
        ...draftState.subscription,
        plan: {
        __typename: PLAN_TYPE,
        code,
        },
        };
        });
        apolloProvider.clients.defaultClient.cache.writeQuery({
        query: getSubscriptionPlanQuery,
        data,
        });
        };
        ......@@ -5,6 +5,7 @@ import VueApollo from 'vue-apollo';
        import { parseBoolean } from '~/lib/utils/common_utils';
        import SubscriptionSeats from 'ee/usage_quotas/seats/components/subscription_seats.vue';
        import apolloProvider from 'ee/usage_quotas/shared/provider';
        import { writeDataToApolloCache } from 'ee/usage_quotas/seats/graphql/utils';
        import initialStore from './store';
        Vue.use(Vuex);
        ......@@ -31,27 +32,30 @@ export default (containerId = 'js-seat-usage-app') => {
        enforcementFreeUserCapEnabled,
        } = el.dataset;
        const store = new Vuex.Store(
        initialStore({
        namespaceId,
        namespaceName,
        seatUsageExportPath,
        pendingMembersPagePath,
        pendingMembersCount,
        addSeatsHref,
        hasNoSubscription: parseBoolean(hasNoSubscription),
        maxFreeNamespaceSeats: parseInt(maxFreeNamespaceSeats, 10),
        explorePlansPath,
        enforcementFreeUserCapEnabled: parseBoolean(enforcementFreeUserCapEnabled),
        }),
        );
        return new Vue({
        el,
        name: 'SeatsUsageApp',
        apolloProvider,
        apolloProvider: writeDataToApolloCache(apolloProvider),
        provide: {
        explorePlansPath,
        fullPath,
        },
        store: new Vuex.Store(
        initialStore({
        namespaceId,
        namespaceName,
        seatUsageExportPath,
        pendingMembersPagePath,
        pendingMembersCount,
        addSeatsHref,
        hasNoSubscription: parseBoolean(hasNoSubscription),
        maxFreeNamespaceSeats: parseInt(maxFreeNamespaceSeats, 10),
        explorePlansPath,
        enforcementFreeUserCapEnabled: parseBoolean(enforcementFreeUserCapEnabled),
        }),
        ),
        store,
        render(createElement) {
        return createElement(SubscriptionSeats);
        },
        ......
        ......@@ -192,19 +192,6 @@
        expect(page).not_to have_selector('[data-testid="limited-access-modal-id"]')
        end
        context 'with community_plan: true' do
        before do
        stub_subscription_permissions_data(group.id, community_plan: true)
        visit group_seat_usage_path(group)
        wait_for_requests
        end
        it "does not show 'Add seats' button" do
        expect(page).not_to have_content('Add seats')
        end
        end
        context 'with limited_access_modal FF enabled' do
        before do
        stub_feature_flags(limited_access_modal: true)
        ......@@ -282,6 +269,25 @@
        end
        end
        context 'with a public group' do
        let_it_be(:group) { create(:group_with_plan, plan: :free_plan) }
        context 'when on a free plan' do
        before do
        allow_next_instance_of(GitlabSubscriptions::FetchSubscriptionPlansService) do |instance|
        allow(instance).to receive(:execute).and_return([{ 'code' => 'free', 'id' => 'free-plan-id' }])
        end
        visit group_seat_usage_path(group)
        wait_for_requests
        end
        it 'has correct `Explore paid plans` link' do
        expect(page).to have_link("Explore paid plans")
        end
        end
        end
        context 'with free user limit' do
        let(:awaiting_user_names) { awaiting_members.map { |m| m.user.name } }
        let(:active_user_names) { active_members.map { |m| m.user.name } }
        ......
        ......@@ -3,15 +3,19 @@ import Vue, { nextTick } from 'vue';
        import VueApollo from 'vue-apollo';
        import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
        import StatisticsSeatsCard from 'ee/usage_quotas/seats/components/statistics_seats_card.vue';
        import * as Sentry from '~/sentry/sentry_browser_wrapper';
        import Tracking from '~/tracking';
        import { visitUrl } from '~/lib/utils/url_utility';
        import LimitedAccessModal from 'ee/usage_quotas/components/limited_access_modal.vue';
        import waitForPromises from 'helpers/wait_for_promises';
        import { getSubscriptionPermissionsData } from 'ee/fulfillment/shared_queries/subscription_actions_reason.customer.query.graphql';
        import { createMockClient } from 'helpers/mock_apollo_helper';
        import getSubscriptionPlanQuery from 'ee/fulfillment/shared_queries/subscription_plan.query.graphql';
        import { PLAN_CODE_FREE } from 'ee/usage_quotas/seats/constants';
        Vue.use(VueApollo);
        jest.mock('~/sentry/sentry_browser_wrapper');
        jest.mock('~/lib/utils/url_utility', () => ({
        ...jest.requireActual('~/lib/utils/url_utility'),
        visitUrl: jest.fn().mockName('visitUrlMock'),
        ......@@ -19,6 +23,11 @@ jest.mock('~/lib/utils/url_utility', () => ({
        describe('StatisticsSeatsCard', () => {
        let wrapper;
        let subscriptionPermissionsQueryHandlerMock = jest
        .fn()
        .mockResolvedValue({ data: { subscription: null, userActionAccess: null } });
        const explorePlansPath = 'https://gitlab.com/explore-plans-path';
        const purchaseButtonLink = 'https://gitlab.com/purchase-more-seats';
        const defaultProps = {
        seatsUsed: 20,
        ......@@ -27,19 +36,9 @@ describe('StatisticsSeatsCard', () => {
        purchaseButtonLink,
        };
        const defaultApolloData = {
        subscription: { canAddSeats: true, canRenew: true, communityPlan: false },
        userActionAccess: { limitedAccessReason: 'INVALID_REASON' },
        };
        const createComponent = (options = {}) => {
        const { props = {}, apolloData = defaultApolloData } = options;
        const queryHandlerMock = jest.fn().mockResolvedValue({
        data: apolloData,
        });
        const createMockApolloProvider = ({ subscriptionPlanData }) => {
        const mockCustomersDotClient = createMockClient([
        [getSubscriptionPermissionsData, queryHandlerMock],
        [getSubscriptionPermissionsData, subscriptionPermissionsQueryHandlerMock],
        ]);
        const mockGitlabClient = createMockClient();
        const mockApollo = new VueApollo({
        ......@@ -47,9 +46,23 @@ describe('StatisticsSeatsCard', () => {
        clients: { customersDotClient: mockCustomersDotClient, gitlabClient: mockGitlabClient },
        });
        mockApollo.clients.defaultClient.cache.writeQuery({
        query: getSubscriptionPlanQuery,
        data: subscriptionPlanData,
        });
        return mockApollo;
        };
        const createComponent = (options = {}) => {
        const { props = {}, subscriptionPlanData = {} } = options;
        const apolloProvider = createMockApolloProvider({ subscriptionPlanData });
        wrapper = shallowMountExtended(StatisticsSeatsCard, {
        propsData: { ...defaultProps, ...props },
        apolloProvider: mockApollo,
        apolloProvider,
        provide: {
        explorePlansPath,
        },
        stubs: {
        LimitedAccessModal,
        },
        ......@@ -59,10 +72,17 @@ describe('StatisticsSeatsCard', () => {
        const findSeatsUsedBlock = () => wrapper.findByTestId('seats-used');
        const findSeatsOwedBlock = () => wrapper.findByTestId('seats-owed');
        const findPurchaseButton = () => wrapper.findByTestId('purchase-button');
        const findExplorePaidPlansButton = () => wrapper.findByTestId('explore-paid-plans');
        const findLimitedAccessModal = () => wrapper.findComponent(LimitedAccessModal);
        describe('when `isLoading` computed value is `true`', () => {
        beforeEach(() => {
        subscriptionPermissionsQueryHandlerMock = jest.fn().mockResolvedValue({
        data: {
        subscription: { canAddSeats: true, canRenew: true, communityPlan: false },
        userActionAccess: { limitedAccessReason: 'INVALID_REASON' },
        },
        });
        createComponent();
        });
        ......@@ -75,7 +95,6 @@ describe('StatisticsSeatsCard', () => {
        it('renders seats used block if seatsUsed is passed', async () => {
        createComponent();
        // wait for apollo to load
        await waitForPromises();
        const seatsUsedBlock = findSeatsUsedBlock();
        ......@@ -88,18 +107,31 @@ describe('StatisticsSeatsCard', () => {
        it('does not render seats used block if seatsUsed is not passed', async () => {
        createComponent({ props: { seatsUsed: null } });
        // wait for apollo to load
        await waitForPromises();
        expect(findSeatsUsedBlock().exists()).toBe(false);
        });
        });
        describe('when there are errors', () => {
        const mockError = new Error('Something went wrong!');
        beforeEach(async () => {
        subscriptionPermissionsQueryHandlerMock = jest.fn().mockRejectedValueOnce(mockError);
        createComponent();
        await waitForPromises();
        });
        it('captures the exception', () => {
        expect(Sentry.captureException).toHaveBeenCalledWith(mockError);
        });
        });
        describe('seats owed block', () => {
        it('renders seats owed block if seatsOwed is passed', async () => {
        createComponent();
        // wait for apollo to load
        await waitForPromises();
        const seatsOwedBlock = findSeatsOwedBlock();
        ......@@ -112,7 +144,6 @@ describe('StatisticsSeatsCard', () => {
        it('does not render seats owed block if seatsOwed is not passed', async () => {
        createComponent({ props: { seatsOwed: null } });
        // wait for apollo to load
        await waitForPromises();
        expect(findSeatsOwedBlock().exists()).toBe(false);
        ......@@ -123,7 +154,6 @@ describe('StatisticsSeatsCard', () => {
        it('renders purchase button if purchase link and purchase text is passed', async () => {
        createComponent();
        // wait for apollo to load
        await waitForPromises();
        const purchaseButton = findPurchaseButton();
        ......@@ -134,7 +164,6 @@ describe('StatisticsSeatsCard', () => {
        it('does not render purchase button if purchase link is not passed', async () => {
        createComponent({ props: { purchaseButtonLink: null } });
        // wait for apollo to load
        await waitForPromises();
        expect(findPurchaseButton().exists()).toBe(false);
        ......@@ -144,7 +173,6 @@ describe('StatisticsSeatsCard', () => {
        jest.spyOn(Tracking, 'event');
        createComponent();
        // wait for apollo to load
        await waitForPromises();
        findPurchaseButton().vm.$emit('click');
        ......@@ -158,7 +186,6 @@ describe('StatisticsSeatsCard', () => {
        it('redirects when clicked', async () => {
        createComponent();
        // wait for apollo to load
        await waitForPromises();
        findPurchaseButton().vm.$emit('click');
        ......@@ -166,16 +193,91 @@ describe('StatisticsSeatsCard', () => {
        expect(visitUrl).toHaveBeenCalledWith('https://gitlab.com/purchase-more-seats');
        });
        it('does not render purchase button if communityPlan is true', async () => {
        createComponent({
        apolloData: {
        subscription: { canAddSeats: false, canRenew: true, communityPlan: true },
        userActionAccess: { limitedAccessReason: 'INVALID_REASON' },
        },
        describe('when canAddSeats is not provided', () => {
        describe('with a Free Plan', () => {
        beforeEach(async () => {
        subscriptionPermissionsQueryHandlerMock = jest.fn().mockResolvedValue({});
        createComponent({
        subscriptionPlanData: { subscription: { plan: { code: PLAN_CODE_FREE } } },
        });
        await waitForPromises();
        });
        it('does not render the `Add more seats` button', () => {
        expect(findPurchaseButton().exists()).toBe(false);
        });
        it('does not render the modal', () => {
        expect(findLimitedAccessModal().exists()).toBe(false);
        });
        it('renders the `Explore paid plans` button', () => {
        expect(findExplorePaidPlansButton().exists()).toBe(true);
        });
        });
        await waitForPromises();
        expect(findPurchaseButton().exists()).toBe(false);
        describe('with no Free Plan', () => {
        beforeEach(async () => {
        createComponent();
        await waitForPromises();
        });
        it('renders the `Add more seats` button', () => {
        expect(findPurchaseButton().exists()).toBe(true);
        });
        it('does not render the modal', () => {
        expect(findLimitedAccessModal().exists()).toBe(false);
        });
        it('does not render the `Explore paid plans` button', () => {
        expect(findExplorePaidPlansButton().exists()).toBe(false);
        });
        });
        });
        describe('when canAddSeats is false', () => {
        beforeEach(() => {
        subscriptionPermissionsQueryHandlerMock = jest.fn().mockResolvedValue({
        data: {
        subscription: { canAddSeats: false, canRenew: true, communityPlan: false },
        userActionAccess: { limitedAccessReason: 'INVALID_REASON' },
        },
        });
        createComponent({
        subscriptionPlanData: { subscription: { plan: { code: PLAN_CODE_FREE } } },
        });
        return waitForPromises();
        });
        it('does not render the `Add more seats` button', () => {
        expect(findPurchaseButton().exists()).toBe(false);
        });
        it('does not render the modal', () => {
        expect(findLimitedAccessModal().exists()).toBe(false);
        });
        it('renders the `Explore paid plans` button', () => {
        expect(findExplorePaidPlansButton().exists()).toBe(true);
        });
        describe('when it is a community plan', () => {
        beforeEach(() => {
        subscriptionPermissionsQueryHandlerMock = jest.fn().mockResolvedValue({
        data: {
        subscription: { canAddSeats: false, canRenew: true, communityPlan: true },
        userActionAccess: { limitedAccessReason: 'INVALID_REASON' },
        },
        });
        createComponent();
        return waitForPromises();
        });
        it('does not show the `Explore paid plans` button', () => {
        expect(findExplorePaidPlansButton().exists()).toBe(false);
        });
        });
        });
        });
        ......@@ -189,6 +291,27 @@ describe('StatisticsSeatsCard', () => {
        gon.features = { limitedAccessModal: true };
        });
        describe('when canAddSeats=false and limitedAccessReason=INVALID_REASON', () => {
        beforeEach(async () => {
        subscriptionPermissionsQueryHandlerMock = jest.fn().mockResolvedValue({
        data: {
        subscription: { canAddSeats: false, canRenew: true, communityPlan: false },
        userActionAccess: { limitedAccessReason: 'INVALID_REASON' },
        },
        });
        createComponent();
        await waitForPromises();
        });
        it('does not render the `Add more seats` button', () => {
        expect(findPurchaseButton().exists()).toBe(false);
        });
        it('does not render the modal', () => {
        expect(findLimitedAccessModal().exists()).toBe(false);
        });
        });
        describe.each`
        canAddSeats | limitedAccessReason
        ${false} | ${'MANAGED_BY_RESELLER'}
        ......@@ -197,12 +320,13 @@ describe('StatisticsSeatsCard', () => {
        'when canAddSeats=$canAddSeats and limitedAccessReason=$limitedAccessReason',
        ({ canAddSeats, limitedAccessReason }) => {
        beforeEach(async () => {
        createComponent({
        apolloData: {
        subscriptionPermissionsQueryHandlerMock = jest.fn().mockResolvedValue({
        data: {
        subscription: { canAddSeats, canRenew: true, communityPlan: false },
        userActionAccess: { limitedAccessReason },
        },
        });
        createComponent();
        await waitForPromises();
        findPurchaseButton().vm.$emit('click');
        ......@@ -220,24 +344,28 @@ describe('StatisticsSeatsCard', () => {
        it('does not navigate to URL', () => {
        expect(visitUrl).not.toHaveBeenCalled();
        });
        it('does not show the `Explore paid plans` button', () => {
        expect(findExplorePaidPlansButton().exists()).toBe(false);
        });
        },
        );
        describe.each`
        canAddSeats | limitedAccessReason
        ${false} | ${'INVALID_REASON'}
        ${true} | ${'MANAGED_BY_RESELLER'}
        ${true} | ${'RAMP_SUBSCRIPTION'}
        `(
        'when canAddSeats=$canAddSeats and limitedAccessReason=$limitedAccessReason',
        ({ canAddSeats, limitedAccessReason }) => {
        beforeEach(async () => {
        createComponent({
        apolloData: {
        subscriptionPermissionsQueryHandlerMock = jest.fn().mockResolvedValue({
        data: {
        subscription: { canAddSeats, canRenew: true, communityPlan: false },
        userActionAccess: { limitedAccessReason },
        },
        });
        createComponent();
        await waitForPromises();
        findPurchaseButton().vm.$emit('click');
        ......@@ -251,6 +379,10 @@ describe('StatisticsSeatsCard', () => {
        it('navigates to URL', () => {
        expect(visitUrl).toHaveBeenCalledWith(purchaseButtonLink);
        });
        it('does not show the `Explore paid plans` button', () => {
        expect(findExplorePaidPlansButton().exists()).toBe(false);
        });
        },
        );
        });
        ......@@ -260,7 +392,6 @@ describe('StatisticsSeatsCard', () => {
        gon.features = { limitedAccessModal: false };
        createComponent();
        // wait for apollo to load
        await waitForPromises();
        findPurchaseButton().vm.$emit('click');
        ......
        0% Loading or .
        You are about to add 0 people to the discussion. Proceed with caution.
        Finish editing this message first!
        Please register or to comment