diff --git a/webapp/channels/src/components/self_hosted_expansion_modal/expansion_card.scss b/webapp/channels/src/components/self_hosted_expansion_modal/expansion_card.scss
index 0ac7a31fd4..f9d7ca4d36 100644
--- a/webapp/channels/src/components/self_hosted_expansion_modal/expansion_card.scss
+++ b/webapp/channels/src/components/self_hosted_expansion_modal/expansion_card.scss
@@ -88,18 +88,18 @@
font-size: 12px;
}
- .totalCost {
+ .totalCostWarning {
width: 141px;
}
- .totalCost > span:first-child {
+ .totalCostWarning > span:first-child {
color: var(--sys-denim-center-channel-text);
font-family: 'Open Sans';
font-size: 14px;
font-weight: 700;
}
- .totalCost > span:last-child {
+ .totalCostWarning > span:last-child {
color: rgba(var(--sys-denim-center-channel-text-rgb), 0.72);
font-family: 'Open Sans';
font-size: 12px;
diff --git a/webapp/channels/src/components/self_hosted_expansion_modal/expansion_card.tsx b/webapp/channels/src/components/self_hosted_expansion_modal/expansion_card.tsx
index d79d6b66fc..6eadd2de6e 100644
--- a/webapp/channels/src/components/self_hosted_expansion_modal/expansion_card.tsx
+++ b/webapp/channels/src/components/self_hosted_expansion_modal/expansion_card.tsx
@@ -18,7 +18,6 @@ import {findSelfHostedProductBySku} from 'utils/hosted_customer';
import ExternalLink from 'components/external_link';
const MONTHS_IN_YEAR = 12;
-const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
const MAX_TRANSACTION_VALUE = 1_000_000 - 1;
interface Props {
@@ -35,14 +34,14 @@ export default function SelfHostedExpansionCard(props: Props) {
const endsAt = moment(parseInt(license.ExpiresAt, 10)).format('MMM. D, YYYY');
const [additionalSeats, setAdditionalSeats] = useState(props.initialSeats);
const [overMaxSeats, setOverMaxSeats] = useState(false);
- const licenseExpiry = parseInt(license.ExpiresAt, 10);
+ const licenseExpiry = new Date(parseInt(license.ExpiresAt, 10));
const invalidAdditionalSeats = additionalSeats === 0 || isNaN(additionalSeats);
const [products] = useGetSelfHostedProducts();
const currentProduct = findSelfHostedProductBySku(products, license.SkuShortName);
const getMonthsUntilExpiry = () => {
const now = new Date();
- return Math.ceil((licenseExpiry - now.getTime()) / MILLISECONDS_PER_DAY / 30);
+ return (licenseExpiry.getMonth() - now.getMonth()) + 12 * (licenseExpiry.getFullYear() - now.getFullYear());
};
const getMonthlyPrice = () => {
@@ -209,7 +208,7 @@ export default function SelfHostedExpansionCard(props: Props) {
{'$' + getCostPerUser().toFixed(2)}
-
+
-
+
{'$' + getTotal().toFixed(2)}
diff --git a/webapp/channels/src/components/self_hosted_expansion_modal/index.test.tsx b/webapp/channels/src/components/self_hosted_expansion_modal/index.test.tsx
index c44aae70a3..0097108ae8 100644
--- a/webapp/channels/src/components/self_hosted_expansion_modal/index.test.tsx
+++ b/webapp/channels/src/components/self_hosted_expansion_modal/index.test.tsx
@@ -3,7 +3,7 @@
import React from 'react';
-import {screen, fireEvent} from '@testing-library/react';
+import {screen, fireEvent, waitFor} from '@testing-library/react';
import {GlobalState} from 'types/store';
@@ -11,7 +11,7 @@ import {SelfHostedSignupForm, SelfHostedSignupProgress} from '@mattermost/types/
import {renderWithIntlAndStore} from 'tests/react_testing_utils';
import {TestHelper as TH} from 'utils/test_helper';
-import {SelfHostedProducts, ModalIdentifiers} from 'utils/constants';
+import {SelfHostedProducts, ModalIdentifiers, RecurringIntervals} from 'utils/constants';
import {DeepPartial} from '@mattermost/types/utilities';
@@ -76,6 +76,7 @@ const mockProfessionalProduct = TH.getProductMock({
name: 'Professional',
sku: SelfHostedProducts.PROFESSIONAL,
price_per_seat: 7.5,
+ recurring_interval: RecurringIntervals.MONTH
});
jest.mock('mattermost-redux/client', () => {
@@ -129,6 +130,11 @@ jest.mock('utils/hosted_customer', () => {
const productName = SelfHostedProducts.PROFESSIONAL;
+// Licensed expiry set as 3 months from the current date (rolls over to new years).
+const licenseExpiry = new Date();
+const monthsUntilLicenseExpiry = 3;
+licenseExpiry.setMonth(licenseExpiry.getMonth() + monthsUntilLicenseExpiry);
+
const initialState: DeepPartial = {
views: {
modals: {
@@ -143,11 +149,6 @@ const initialState: DeepPartial = {
storage: {},
},
entities: {
- admin: {
- analytics: {
- TOTAL_USERS: existingUsers,
- },
- },
teams: {
currentTeamId: '',
},
@@ -163,6 +164,7 @@ const initialState: DeepPartial = {
license: {
Sku: productName,
Users: '50',
+ ExpiresAt: licenseExpiry.getTime().toString()
},
},
cloud: {
@@ -224,6 +226,7 @@ interface PurchaseForm {
state: string;
zip: string;
seats: string;
+ agree: boolean;
}
const defaultSuccessForm: PurchaseForm = {
@@ -236,6 +239,7 @@ const defaultSuccessForm: PurchaseForm = {
state: 'MN',
zip: '55423',
seats: '10',
+ agree: true,
};
function fillForm(form: PurchaseForm) {
@@ -248,6 +252,9 @@ function fillForm(form: PurchaseForm) {
selectDropdownValue('selfHostedExpansionStateSelector', form.state);
changeByPlaceholder('Zip/Postal Code', form.zip);
changeByTestId('seatsInput', form.seats);
+ if (form.agree) {
+ fireEvent.click(screen.getByText('I have read and agree', {exact: false}));
+ }
expect(document.getElementsByClassName('SelfHostedExpansionRHSCard__AddSeatsWarning')[0] as HTMLElement).toBeEnabled();
@@ -263,7 +270,7 @@ function fillForm(form: PurchaseForm) {
return completeButton;
}
-describe('SelfHostedExpansionModal', () => {
+describe('SelfHostedExpansionModal Open', () => {
it('renders the form', () => {
renderWithIntlAndStore(
, initialState);
@@ -321,7 +328,45 @@ describe('SelfHostedExpansionModal', () => {
// });
});
-describe('SelfHostedExpansionModal :: canSubmit', () => {
+describe('SelfHostedExpansionModal RHS Card', () => {
+ it("New seats input should be pre-populated with the difference from the active users and licensed seats", () => {
+ renderWithIntlAndStore(
, initialState);
+
+ const expectedPrePopulatedSeats = (initialState.entities?.users?.filteredStats?.total_users_count || 1) - parseInt(initialState.entities?.general?.license?.Users || '0', 10);
+
+ const seatsField = screen.getByTestId('seatsInput').querySelector('input');
+ expect(seatsField).toBeInTheDocument();
+ expect(seatsField?.value).toBe(expectedPrePopulatedSeats.toString());
+ });
+
+ it("Cost per User should be represented as the current subscription price multiplied by the remaining months", () => {
+ renderWithIntlAndStore(
, initialState);
+
+ const expectedCostPerUser = monthsUntilLicenseExpiry * mockProfessionalProduct.price_per_seat;
+
+ const costPerUser = document.getElementsByClassName('costPerUser')[0];
+ expect(costPerUser).toBeInTheDocument();
+ expect(costPerUser.innerHTML).toContain('Cost per user
$' + mockProfessionalProduct.price_per_seat.toFixed(2) + ' x ' + monthsUntilLicenseExpiry + ' months');
+
+ const costAmount = document.getElementsByClassName('costAmount')[0];
+ expect(costAmount).toBeInTheDocument();
+ expect(costAmount.innerHTML).toContain('$' + expectedCostPerUser)
+ });
+
+ it("Total cost User should be represented as the current subscription price multiplied by the remaining months multiplied by the number of users", () => {
+ renderWithIntlAndStore(
, initialState);
+ const seatsInputValue = 100;
+ changeByTestId('seatsInput', seatsInputValue.toString());
+
+ const expectedTotalCost = monthsUntilLicenseExpiry * mockProfessionalProduct.price_per_seat * seatsInputValue;
+
+ const costAmount = document.getElementsByClassName('totalCostAmount')[0];
+ expect(costAmount).toBeInTheDocument();
+ expect(costAmount.innerHTML).toContain('$' + expectedTotalCost)
+ });
+});
+
+describe('SelfHostedExpansionModal Submit', () => {
function makeHappyPathState(): FormState {
return {
address: 'string',
@@ -366,7 +411,6 @@ describe('SelfHostedExpansionModal :: canSubmit', () => {
expect(canSubmit(state, SelfHostedSignupProgress.PAID)).toBe(true);
});
- // TODO: Needed?
it('if created subscription, can submit', () => {
const state = makeInitialState(1);
state.submitting = false;
diff --git a/webapp/channels/src/components/self_hosted_expansion_modal/index.tsx b/webapp/channels/src/components/self_hosted_expansion_modal/index.tsx
index da54d4ce65..845fafcfe2 100644
--- a/webapp/channels/src/components/self_hosted_expansion_modal/index.tsx
+++ b/webapp/channels/src/components/self_hosted_expansion_modal/index.tsx
@@ -409,6 +409,7 @@ export default function SelfHostedExpansionModal() {
/>
{
@@ -450,6 +451,7 @@ export default function SelfHostedExpansionModal() {
/>
{