Commit 5bf2452f authored by Olena Horal-Koretska's avatar Olena Horal-Koretska

Merge branch 'dz/327891-parametrize-purchase-summary' into 'master'

Parametrize addon purchase details component

See merge request gitlab-org/gitlab!70686
parents d41c617b f3ed38c0
......@@ -3,7 +3,7 @@ import { GlIcon, GlCollapse, GlCollapseToggleDirective } from '@gitlab/ui';
import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import { TAX_RATE } from 'ee/subscriptions/new/constants';
import formattingMixins from 'ee/subscriptions/new/formatting_mixins';
import { sprintf, s__ } from '~/locale';
import { sprintf } from '~/locale';
import SummaryDetails from './order_summary/summary_details.vue';
export default {
......@@ -21,6 +21,14 @@ export default {
type: Object,
required: true,
},
title: {
type: String,
required: true,
},
purchaseHasExpiration: {
type: Boolean,
required: false,
},
},
apollo: {
state: {
......@@ -62,15 +70,12 @@ export default {
return this.selectedGroup.name;
},
titleWithName() {
return sprintf(this.$options.i18n.title, { name: this.namespaceName });
return sprintf(this.title, { name: this.namespaceName });
},
isVisible() {
return !this.$apollo.loading;
},
},
i18n: {
title: s__("Checkout|%{name}'s CI minutes"),
},
taxRate: TAX_RATE,
};
</script>
......@@ -79,18 +84,18 @@ export default {
v-if="isVisible"
class="order-summary gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-mt-2 mt-lg-5"
>
<div class="d-lg-none">
<div class="gl-lg-display-none">
<div v-gl-collapse-toggle.summary-details>
<h4 class="d-flex justify-content-between gl-font-lg">
<div class="d-flex">
<div class="gl-display-flex gl-justify-content-between gl-font-lg">
<div class="gl-display-flex">
<gl-icon v-if="isBottomSummaryVisible" name="chevron-down" />
<gl-icon v-else name="chevron-right" />
<div data-testid="title">{{ titleWithName }}</div>
<h4 data-testid="title">{{ titleWithName }}</h4>
</div>
<div class="gl-ml-3" data-testid="amount">
<p class="gl-ml-3" data-testid="amount">
{{ formatAmount(totalAmount, quantityPresent) }}
</div>
</h4>
</p>
</div>
</div>
<gl-collapse id="summary-details" v-model="isBottomSummaryVisible">
<summary-details
......@@ -102,15 +107,21 @@ export default {
:total-amount="totalAmount"
:quantity="subscription.quantity"
:tax-rate="$options.taxRate"
/>
:purchase-has-expiration="purchaseHasExpiration"
>
<template #price-per-unit="{ price }">
<slot name="price-per-unit" :price="price"></slot>
</template>
<template #tooltip>
<slot name="tooltip"></slot>
</template>
</summary-details>
</gl-collapse>
</div>
<div class="d-none d-lg-block">
<div class="append-bottom-20">
<h4>
{{ titleWithName }}
</h4>
</div>
<div class="gl-display-none gl-lg-display-block">
<h4 class="gl-mb-5">
{{ titleWithName }}
</h4>
<summary-details
:vat="vat"
:total-ex-vat="totalExVat"
......@@ -120,7 +131,15 @@ export default {
:total-amount="totalAmount"
:quantity="subscription.quantity"
:tax-rate="$options.taxRate"
/>
:purchase-has-expiration="purchaseHasExpiration"
>
<template #price-per-unit="{ price }">
<slot name="price-per-unit" :price="price"></slot>
</template>
<template #tooltip>
<slot name="tooltip"></slot>
</template>
</summary-details>
</div>
</div>
</template>
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import formattingMixins from 'ee/subscriptions/new/formatting_mixins';
import { s__ } from '~/locale';
import { formatNumber } from '~/locale';
import {
I18N_SUMMARY_DATES,
I18N_SUMMARY_QUANTITY,
I18N_SUMMARY_SUBTOTAL,
I18N_SUMMARY_TAX,
I18N_SUMMARY_TAX_NOTE,
I18N_SUMMARY_TOTAL,
} from '../../constants';
export default {
components: {
......@@ -42,6 +50,7 @@ export default {
purchaseHasExpiration: {
type: Boolean,
required: false,
default: false,
},
},
data() {
......@@ -62,75 +71,78 @@ export default {
taxLine() {
return `${this.$options.i18n.tax} ${this.$options.i18n.taxNote}`;
},
formattedPrice() {
return formatNumber(this.selectedPlanPrice);
},
},
i18n: {
quantity: s__('Checkout|(x%{quantity})'),
pricePerUnitPerYear: s__('Checkout|$%{selectedPlanPrice} per pack of 1,000 minutes'),
dates: s__('Checkout|%{startDate} - %{endDate}'),
subtotal: s__('Checkout|Subtotal'),
tax: s__('Checkout|Tax'),
taxNote: s__('Checkout|(may be %{linkStart}charged upon purchase%{linkEnd})'),
total: s__('Checkout|Total'),
quantity: I18N_SUMMARY_QUANTITY,
dates: I18N_SUMMARY_DATES,
subtotal: I18N_SUMMARY_SUBTOTAL,
tax: I18N_SUMMARY_TAX,
taxNote: I18N_SUMMARY_TAX_NOTE,
total: I18N_SUMMARY_TOTAL,
},
};
</script>
<template>
<div>
<div
class="gl-display-flex gl-justify-content-space-between gl-font-weight-bold gl-mt-3 gl-mb-3"
>
<div class="gl-display-flex gl-justify-content-space-between gl-font-weight-bold gl-my-3">
<div data-testid="selected-plan">
{{ selectedPlanText }}
<span v-if="quantity" data-testid="quantity">{{
sprintf($options.i18n.quantity, { quantity })
}}</span>
</div>
<div data-testid="amount">{{ formatAmount(totalExVat, hasPositiveQuantity) }}</div>
</div>
<div class="gl-text-gray-500" data-testid="price-per-unit">
{{
sprintf($options.i18n.pricePerUnitPerYear, {
selectedPlanPrice: selectedPlanPrice.toLocaleString(),
})
}}
<div>
{{ formatAmount(totalExVat, hasPositiveQuantity) }}
</div>
</div>
<div v-if="purchaseHasExpiration" class="gl-text-gray-500" data-testid="subscription-period">
{{
sprintf($options.i18n.dates, {
startDate: formatDate(startDate),
endDate: formatDate(endDate),
})
}}
<div class="gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-py-3">
<div class="gl-text-gray-500" data-testid="price-per-unit">
<slot name="price-per-unit" :price="formattedPrice"></slot>
</div>
<div v-if="purchaseHasExpiration" class="gl-text-gray-500" data-testid="subscription-period">
{{
sprintf($options.i18n.dates, {
startDate: formatDate(startDate),
endDate: formatDate(endDate),
})
}}
<slot name="tooltip"></slot>
</div>
</div>
<div>
<div class="border-bottom gl-mt-3 gl-mb-3"></div>
<div class="gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-py-3">
<div class="gl-display-flex gl-justify-content-space-between gl-text-gray-500">
<div>{{ $options.i18n.subtotal }}</div>
<div data-testid="total-ex-vat">{{ formatAmount(totalExVat, hasPositiveQuantity) }}</div>
<div data-testid="total-ex-vat">
{{ formatAmount(totalExVat, hasPositiveQuantity) }}
</div>
</div>
<div class="gl-display-flex gl-justify-content-space-between gl-text-gray-500">
<div>
<div data-testid="vat-info-line">
<gl-sprintf :message="taxLine">
<template #link="{ content }">
<gl-link
class="gl-text-decoration-underline gl-text-gray-500"
href="https://about.gitlab.com/handbook/tax/#indirect-taxes-management"
target="_blank"
data-testid="vat-help-link"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
</div>
<div data-testid="vat-info-line">
<gl-sprintf :message="taxLine">
<template #link="{ content }">
<gl-link
class="gl-text-decoration-underline gl-text-gray-500"
href="https://about.gitlab.com/handbook/tax/#indirect-taxes-management"
target="_blank"
data-testid="vat-help-link"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
</div>
<div data-testid="vat">{{ taxAmount }}</div>
</div>
</div>
<div class="gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-mt-3 gl-mb-3"></div>
<div class="gl-display-flex gl-justify-content-space-between gl-font-weight-bold gl-font-lg">
<div
class="gl-display-flex gl-justify-content-space-between gl-font-weight-bold gl-font-lg gl-mt-3"
>
<div>{{ $options.i18n.total }}</div>
<div data-testid="total-amount">{{ formatAmount(totalAmount, hasPositiveQuantity) }}</div>
<div data-testid="total-amount">
{{ formatAmount(totalAmount, hasPositiveQuantity) }}
</div>
</div>
</div>
</template>
......@@ -21,6 +21,10 @@ export const I18N_CI_MINUTES_SUMMARY_TOTAL = s__('Checkout|Total minutes: %{quan
export const I18N_CI_MINUTES_ALERT_TEXT = s__(
"Checkout|CI minute packs are only used after you've used your subscription's monthly quota. The additional minutes will roll over month to month and are valid for one year.",
);
export const I18N_CI_MINUTES_PRICE_PRE_UNIT = s__(
'Checkout|$%{selectedPlanPrice} per pack of 1,000 minutes',
);
export const I18N_CI_MINUTES_TITLE = s__("Checkout|%{name}'s CI minutes");
export const I18N_STORAGE_PRODUCT_LABEL = s__('Checkout|Storage packs');
export const I18N_STORAGE_PRODUCT_UNIT = s__('Checkout|GB');
......@@ -28,7 +32,23 @@ export const I18N_STORAGE_FORMULA_TOTAL = s__('Checkout|%{quantity} GB of storag
export const i18nStorageSummaryTitle = (quantity) =>
n__('Checkout|%{quantity} storage pack', 'Checkout|%{quantity} storage packs', quantity);
export const I18N_STORAGE_SUMMARY_TOTAL = s__('Checkout|Total storage: %{quantity} GB');
export const I18N_STORAGE_PRICE_PRE_UNIT = s__(
'Checkout|$%{selectedPlanPrice} per 10 GB storage per pack',
);
export const I18N_STORAGE_TITLE = s__("Checkout|%{name}'s storage subscription");
export const I18N_STORAGE_TOOLTIP_NOTE = s__(
'Checkout|Your storage subscription has the same term as your main subscription, and the price is prorated accordingly.',
);
export const I18N_DETAILS_STEP_TITLE = s__('Checkout|Purchase details');
export const I18N_DETAILS_NEXT_STEP_BUTTON_TEXT = s__('Checkout|Continue to billing');
export const I18N_DETAILS_FORMULA = s__('Checkout|x %{quantity} %{units} per pack =');
export const I18N_SUMMARY_QUANTITY = s__('Checkout|(x%{quantity})');
export const I18N_SUMMARY_DATES = s__('Checkout|%{startDate} - %{endDate}');
export const I18N_SUMMARY_SUBTOTAL = s__('Checkout|Subtotal');
export const I18N_SUMMARY_TAX = s__('Checkout|Tax');
export const I18N_SUMMARY_TAX_NOTE = s__(
'Checkout|(may be %{linkStart}charged upon purchase%{linkEnd})',
);
export const I18N_SUMMARY_TOTAL = s__('Checkout|Total');
......@@ -16,6 +16,8 @@ import {
i18nCIMinutesSummaryTitle,
I18N_CI_MINUTES_SUMMARY_TOTAL,
I18N_CI_MINUTES_ALERT_TEXT,
I18N_CI_MINUTES_PRICE_PRE_UNIT,
I18N_CI_MINUTES_TITLE,
planTags,
CUSTOMER_CLIENT,
CI_MINUTES_PER_PACK,
......@@ -42,6 +44,8 @@ export default {
summaryTitle: i18nCIMinutesSummaryTitle,
summaryTotal: I18N_CI_MINUTES_SUMMARY_TOTAL,
alertText: I18N_CI_MINUTES_ALERT_TEXT,
title: I18N_CI_MINUTES_TITLE,
pricePerUnit: I18N_CI_MINUTES_PRICE_PRE_UNIT,
},
CI_MINUTES_PER_PACK,
emptySvg,
......@@ -70,6 +74,11 @@ export default {
quantity: formatNumber(quantity * CI_MINUTES_PER_PACK),
});
},
pricePerUnitLabel(price) {
return sprintf(this.$options.i18n.pricePerUnit, {
selectedPlanPrice: price,
});
},
},
apollo: {
plans: {
......@@ -119,14 +128,18 @@ export default {
<strong data-testid="summary-label">
{{ summaryTitle(quantity) }}
</strong>
<p class="gl-mb-0" data-testid="summary-total">{{ summaryTotal(quantity) }}</p>
<div data-testid="summary-total">{{ summaryTotal(quantity) }}</div>
</template>
</addon-purchase-details>
</template>
</checkout>
</template>
<template #order-summary>
<order-summary :plan="plans[0]" />
<order-summary :plan="plans[0]" :title="$options.i18n.title">
<template #price-per-unit="{ price }">
{{ pricePerUnitLabel(price) }}
</template>
</order-summary>
</template>
</step-order-app>
</template>
<script>
import emptySvg from '@gitlab/svgs/dist/illustrations/security-dashboard-empty-state.svg';
import { GlEmptyState } from '@gitlab/ui';
import { GlEmptyState, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import StepOrderApp from 'ee/vue_shared/purchase_flow/components/step_order_app.vue';
import { ERROR_FETCHING_DATA_HEADER, ERROR_FETCHING_DATA_DESCRIPTION } from '~/ensure_data';
......@@ -15,6 +15,9 @@ import {
I18N_STORAGE_FORMULA_TOTAL,
i18nStorageSummaryTitle,
I18N_STORAGE_SUMMARY_TOTAL,
I18N_STORAGE_TITLE,
I18N_STORAGE_PRICE_PRE_UNIT,
I18N_STORAGE_TOOLTIP_NOTE,
planTags,
CUSTOMER_CLIENT,
STORAGE_PER_PACK,
......@@ -29,6 +32,10 @@ export default {
OrderSummary,
StepOrderApp,
AddonPurchaseDetails,
GlIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
i18n: {
ERROR_FETCHING_DATA_HEADER,
......@@ -39,6 +46,9 @@ export default {
formulaTotal: I18N_STORAGE_FORMULA_TOTAL,
summaryTitle: i18nStorageSummaryTitle,
summaryTotal: I18N_STORAGE_SUMMARY_TOTAL,
title: I18N_STORAGE_TITLE,
pricePerUnit: I18N_STORAGE_PRICE_PRE_UNIT,
tooltipNote: I18N_STORAGE_TOOLTIP_NOTE,
},
emptySvg,
STORAGE_PER_PACK,
......@@ -67,6 +77,11 @@ export default {
quantity: formatNumber(quantity * STORAGE_PER_PACK),
});
},
pricePerUnitLabel(price) {
return sprintf(this.$options.i18n.pricePerUnit, {
selectedPlanPrice: price,
});
},
},
apollo: {
plans: {
......@@ -121,7 +136,20 @@ export default {
</checkout>
</template>
<template #order-summary>
<order-summary :plan="plans[0]" />
<order-summary :plan="plans[0]" :title="$options.i18n.title" purchase-has-expiration>
<template #price-per-unit="{ price }">
{{ pricePerUnitLabel(price) }}
</template>
<template #tooltip>
<gl-icon
v-gl-tooltip.right
:title="$options.i18n.tooltipNote"
:aria-label="$options.i18n.tooltipNote"
role="tooltip"
name="question"
/>
</template>
</order-summary>
</template>
</step-order-app>
</template>
......@@ -43,10 +43,6 @@ describe('SummaryDetails', () => {
expect(wrapper.findByTestId('selected-plan').text()).toMatchInterpolatedText('Test (x1)');
});
it('renders the price per unit', () => {
expect(wrapper.findByTestId('price-per-unit').text()).toBe('$10 per pack of 1,000 minutes');
});
it('displays the total amount', () => {
expect(findTotalAmount().text()).toBe('$10');
});
......
......@@ -47,6 +47,7 @@ describe('Order Summary', () => {
apolloProvider,
propsData: {
plan: mockCiMinutesPlans[0],
title: "%{name}'s CI minutes",
},
});
};
......
......@@ -3,6 +3,8 @@ import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import Checkout from 'ee/subscriptions/buy_addons_shared/components/checkout.vue';
import AddonPurchaseDetails from 'ee/subscriptions/buy_addons_shared/components/checkout/addon_purchase_details.vue';
import OrderSummary from 'ee/subscriptions/buy_addons_shared/components/order_summary.vue';
import SummaryDetails from 'ee/subscriptions/buy_addons_shared/components/order_summary/summary_details.vue';
import App from 'ee/subscriptions/buy_minutes/components/app.vue';
import StepOrderApp from 'ee/vue_shared/purchase_flow/components/step_order_app.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
......@@ -24,6 +26,8 @@ describe('App', () => {
stubs: {
Checkout,
AddonPurchaseDetails,
OrderSummary,
SummaryDetails,
},
});
}
......@@ -31,6 +35,7 @@ describe('App', () => {
const findQuantityText = () => wrapper.findByTestId('addon-quantity-text');
const findSummaryLabel = () => wrapper.findByTestId('summary-label');
const findSummaryTotal = () => wrapper.findByTestId('summary-total');
const findPriceLabel = () => wrapper.findByTestId('price-per-unit');
afterEach(() => {
wrapper.destroy();
......@@ -101,16 +106,12 @@ describe('App', () => {
wrapper = createComponent(mockApollo);
await waitForPromises();
expect(findQuantityText().exists()).toBe(true);
expect(findQuantityText().text()).toMatchInterpolatedText(
'x 1,000 minutes per pack = 1,000 CI minutes',
);
expect(findSummaryLabel().exists()).toBe(true);
expect(findSummaryLabel().text()).toBe('1 CI minute pack');
expect(findSummaryTotal().exists()).toBe(true);
expect(findSummaryTotal().text()).toBe('Total minutes: 1,000');
expect(findPriceLabel().text()).toBe('$10 per pack of 1,000 minutes');
});
it('are shown correctly for 2 packs', async () => {
......@@ -118,15 +119,10 @@ describe('App', () => {
wrapper = createComponent(mockApollo);
await waitForPromises();
expect(findQuantityText().exists()).toBe(true);
expect(findQuantityText().text()).toMatchInterpolatedText(
'x 1,000 minutes per pack = 2,000 CI minutes',
);
expect(findSummaryLabel().exists()).toBe(true);
expect(findSummaryLabel().text()).toBe('2 CI minute packs');
expect(findSummaryTotal().exists()).toBe(true);
expect(findSummaryTotal().text()).toBe('Total minutes: 2,000');
});
});
......
......@@ -22,8 +22,8 @@ export const mockFullName = 'John Admin';
export const mockSetupForCompany = 'true';
export const mockDefaultCache = {
groupData: '[]',
namespaceId: 'My Namespace',
groupData: mockNamespaces,
namespaceId: 132,
redirectAfterSuccess: '/',
};
......
......@@ -6546,6 +6546,9 @@ msgstr ""
msgid "Checkout"
msgstr ""
msgid "Checkout|$%{selectedPlanPrice} per 10 GB storage per pack"
msgstr ""
msgid "Checkout|$%{selectedPlanPrice} per pack of 1,000 minutes"
msgstr ""
......@@ -6566,6 +6569,9 @@ msgstr ""
msgid "Checkout|%{name}'s GitLab subscription"
msgstr ""
msgid "Checkout|%{name}'s storage subscription"
msgstr ""
msgid "Checkout|%{quantity} GB of storage"
msgstr ""
......@@ -6730,6 +6736,9 @@ msgstr ""
msgid "Checkout|Your organization"
msgstr ""
msgid "Checkout|Your storage subscription has the same term as your main subscription, and the price is prorated accordingly."
msgstr ""
msgid "Checkout|Your subscription will be applied to this group"
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