Commit eaf9a4ef authored by Diana Zubova's avatar Diana Zubova Committed by Kushal Pandya

Add notification about api error

Show alert on api failures for Storage purchase
parent 55d94517
<script>
import emptySvg from '@gitlab/svgs/dist/illustrations/security-dashboard-empty-state.svg';
import { GlEmptyState, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { GlEmptyState, GlIcon, GlAlert, GlTooltipDirective } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import OrderSummary from 'ee/subscriptions/buy_addons_shared/components/order_summary.vue';
import { ERROR_FETCHING_DATA_HEADER, ERROR_FETCHING_DATA_DESCRIPTION } from '~/ensure_data';
......@@ -18,6 +18,7 @@ export default {
Checkout,
GlEmptyState,
GlIcon,
GlAlert,
OrderSummary,
},
directives: {
......@@ -36,6 +37,7 @@ export default {
data() {
return {
hasError: false,
alertMessage: '',
};
},
computed: {
......@@ -88,6 +90,9 @@ export default {
selectedPlanPrice: price,
});
},
alertError(errorMessage) {
this.alertMessage = errorMessage;
},
},
apollo: {
plans: {
......@@ -130,8 +135,11 @@ export default {
class="row gl-flex-grow-1 gl-flex-direction-column gl-flex-nowrap gl-lg-flex-direction-row gl-xl-flex-direction-row gl-lg-flex-wrap gl-xl-flex-wrap"
>
<div
class="checkout-pane gl-px-3 gl-align-items-center gl-bg-gray-10 col-lg-7 gl-display-flex gl-flex-direction-column gl-flex-grow-1"
class="checkout-pane gl-px-3 gl-pt-5 gl-align-items-center gl-bg-gray-10 col-lg-7 gl-display-flex gl-flex-direction-column gl-flex-grow-1"
>
<gl-alert v-if="alertMessage" class="checkout-alert" variant="danger" :dismissible="false">
{{ alertMessage }}
</gl-alert>
<checkout :plan="plan">
<template #purchase-details>
<addon-purchase-details
......@@ -162,6 +170,7 @@ export default {
:plan="plan"
:title="config.title"
:purchase-has-expiration="config.hasExpiration"
@alertError="alertError"
>
<template #price-per-unit="{ price }">
{{ pricePerUnitLabel(price) }}
......
......@@ -4,7 +4,7 @@ import find from 'lodash/find';
import { logError } from '~/lib/logger';
import { TAX_RATE } from 'ee/subscriptions/new/constants';
import { CUSTOMERSDOT_CLIENT } from 'ee/subscriptions/buy_addons_shared/constants';
import { CUSTOMERSDOT_CLIENT, I18N_API_ERROR } from 'ee/subscriptions/buy_addons_shared/constants';
import formattingMixins from 'ee/subscriptions/new/formatting_mixins';
import { sprintf } from '~/locale';
......@@ -62,13 +62,16 @@ export default {
},
manual: true,
result({ data }) {
if (data.orderPreview) {
if (data.errors) {
this.hasError = true;
} else if (data.orderPreview) {
this.endDate = data.orderPreview.targetDate;
this.proratedAmount = data.orderPreview.amount;
}
},
error(error) {
this.hasError = true;
this.$emit('alertError', I18N_API_ERROR);
logError(error);
},
skip() {
......@@ -91,7 +94,7 @@ export default {
return this.plan.pricePerYear;
},
totalExVat() {
return this.isLoading
return this.hideAmount
? 0
: this.proratedAmount || this.subscription.quantity * this.selectedPlanPrice;
},
......@@ -99,7 +102,7 @@ export default {
return TAX_RATE * this.totalExVat;
},
totalAmount() {
return this.isLoading ? 0 : this.proratedAmount || this.totalExVat + this.vat;
return this.hideAmount ? 0 : this.proratedAmount || this.totalExVat + this.vat;
},
quantityPresent() {
return this.subscription.quantity > 0;
......@@ -116,6 +119,9 @@ export default {
isLoading() {
return this.$apollo.loading;
},
hideAmount() {
return this.isLoading || this.hasError;
},
},
taxRate: TAX_RATE,
};
......
......@@ -63,3 +63,7 @@ export const I18N_SUMMARY_TAX_NOTE = s__(
'Checkout|(may be %{linkStart}charged upon purchase%{linkEnd})',
);
export const I18N_SUMMARY_TOTAL = s__('Checkout|Total');
export const I18N_API_ERROR = s__(
'Checkout|An unknown error has occurred. Please try again by refreshing this page.',
);
......@@ -3,7 +3,7 @@
class="row gl-flex-grow-1 gl-flex-direction-column gl-flex-nowrap gl-lg-flex-direction-row gl-xl-flex-direction-row gl-lg-flex-wrap gl-xl-flex-wrap"
>
<div
class="checkout-pane gl-px-3 gl-align-items-center gl-bg-gray-10 col-lg-7 gl-display-flex gl-flex-direction-column gl-flex-grow-1"
class="checkout-pane gl-px-3 gl-pt-5 gl-align-items-center gl-bg-gray-10 col-lg-7 gl-display-flex gl-flex-direction-column gl-flex-grow-1"
>
<slot name="checkout"></slot>
</div>
......
......@@ -20,15 +20,22 @@ $subscriptions-full-width-lg: 541px;
}
}
.checkout-alert {
width: 100%;
max-width: $subscriptions-full-width-md;
@media(min-width: map-get($grid-breakpoints, lg)) {
max-width: $subscriptions-full-width-lg;
}
}
.checkout {
align-items: center;
flex-grow: 1;
margin-top: $gl-padding;
max-width: $subscriptions-full-width-md;
@media(min-width: map-get($grid-breakpoints, lg)) {
justify-content: inherit !important;
margin-top: $gl-padding-32;
max-width: none;
}
......
import { GlEmptyState } from '@gitlab/ui';
import { GlEmptyState, GlAlert } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import { pick } from 'lodash';
......@@ -13,6 +13,7 @@ import {
I18N_STORAGE_TITLE,
I18N_STORAGE_PRICE_PER_UNIT,
I18N_STORAGE_TOOLTIP_NOTE,
I18N_API_ERROR,
planTags,
STORAGE_PER_PACK,
} from 'ee/subscriptions/buy_addons_shared/constants';
......@@ -67,6 +68,7 @@ describe('Buy Storage App', () => {
const getStoragePlan = () => pick(mockStoragePlans[0], ['id', 'code', 'pricePerYear', 'name']);
const findCheckout = () => wrapper.findComponent(Checkout);
const findOrderSummary = () => wrapper.findComponent(OrderSummary);
const findAlert = () => wrapper.findComponent(GlAlert);
const findPriceLabel = () => wrapper.findByTestId('price-per-unit');
const findQuantityText = () => wrapper.findByTestId('addon-quantity-text');
const findRootElement = () => wrapper.findByTestId('buy-addons-shared');
......@@ -100,6 +102,20 @@ describe('Buy Storage App', () => {
title: I18N_STORAGE_TITLE,
});
});
describe('and an error occurred', () => {
beforeEach(() => {
findOrderSummary().vm.$emit('alertError', I18N_API_ERROR);
});
it('shows the alert', () => {
expect(findAlert().props()).toMatchObject({
dismissible: false,
variant: 'danger',
});
expect(findAlert().text()).toBe(I18N_API_ERROR);
});
});
});
describe('when data is not received', () => {
......
......@@ -15,7 +15,7 @@ import {
import createMockApollo, { createMockClient } from 'helpers/mock_apollo_helper';
import orderPreviewQuery from 'ee/subscriptions/graphql/queries/order_preview.customer.query.graphql';
import { CUSTOMERSDOT_CLIENT } from 'ee/subscriptions/buy_addons_shared/constants';
import { CUSTOMERSDOT_CLIENT, I18N_API_ERROR } from 'ee/subscriptions/buy_addons_shared/constants';
const localVue = createLocalVue();
localVue.use(VueApollo);
......@@ -123,7 +123,7 @@ describe('Order Summary', () => {
createComponent(apolloProvider, { purchaseHasExpiration: true });
});
it('renders amount from the state', () => {
it('renders default amount without proration from the state', () => {
expect(findAmount().text()).toBe('$60');
});
});
......@@ -139,8 +139,32 @@ describe('Order Summary', () => {
createComponent(apolloProvider, { purchaseHasExpiration: true });
});
it('renders amount from the state', () => {
expect(findAmount().text()).toBe('$60');
it('does not render amount', () => {
expect(findAmount().text()).toBe('-');
});
});
describe('calls api that returns an error', () => {
beforeEach(() => {
jest.spyOn(console, 'error').mockImplementation(() => {});
const orderPreviewQueryMock = jest.fn().mockRejectedValue(new Error('An error happened!'));
const apolloProvider = createMockApolloProvider(
{ subscription: { quantity: 1 } },
orderPreviewQueryMock,
);
createComponent(apolloProvider, { purchaseHasExpiration: true });
});
it('does not render amount', () => {
expect(findAmount().text()).toBe('-');
});
it('should emit `alertError` event', async () => {
jest.spyOn(wrapper.vm, '$emit');
await wrapper.vm.$nextTick();
expect(wrapper.vm.$emit).toHaveBeenCalledWith('alertError', I18N_API_ERROR);
});
});
......
......@@ -6906,6 +6906,9 @@ msgstr ""
msgid "Checkout|(x%{quantity})"
msgstr ""
msgid "Checkout|An unknown error has occurred. Please try again by refreshing this page."
msgstr ""
msgid "Checkout|Billing address"
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