import {OrganizationFixture} from 'sentry-fixture/organization';
import {PlanDetailsLookupFixture} from 'getsentry-test/fixtures/planDetailsLookup';
import {PlanMigrationFixture} from 'getsentry-test/fixtures/planMigration';
import {
Am3DsEnterpriseSubscriptionFixture,
SubscriptionFixture,
} from 'getsentry-test/fixtures/subscription';
import {render} from 'sentry-test/reactTestingLibrary';
import PendingChanges from 'admin/components/customers/pendingChanges';
import {PendingChangesFixture} from 'getsentry/__fixtures__/pendingChanges';
import {PlanFixture} from 'getsentry/__fixtures__/plan';
import {ANNUAL, RESERVED_BUDGET_QUOTA} from 'getsentry/constants';
import * as usePlanMigrations from 'getsentry/hooks/usePlanMigrations';
import {CohortId, OnDemandBudgetMode} from 'getsentry/types';
describe('PendingChanges', function () {
it('renders null pendingChanges)', function () {
const subscription = SubscriptionFixture({
organization: OrganizationFixture(),
});
const {container} = render();
expect(container).toBeEmptyDOMElement();
});
it('renders empty pendingChanges', function () {
const subscription = SubscriptionFixture({
organization: OrganizationFixture(),
pendingChanges: null,
});
const {container} = render();
expect(container).toBeEmptyDOMElement();
});
it('renders pending changes', function () {
const subscription = SubscriptionFixture({
organization: OrganizationFixture(),
customPrice: 0,
customPricePcss: 0,
pendingChanges: PendingChangesFixture({
planDetails: PlanFixture({
name: 'Team (Enterprise)',
contractInterval: 'annual',
billingInterval: 'annual',
}),
plan: 'am1_team_ent',
planName: 'Team (Enterprise)',
reservedEvents: 15000000,
reservedErrors: 15000000,
reservedTransactions: 20000000,
reservedAttachments: 25,
reserved: {errors: 15000000, transactions: 20000000, attachments: 25},
customPrice: 5000000,
customPriceErrors: 2000000,
customPriceTransactions: 2900000,
customPriceAttachments: 50000,
customPrices: {errors: 2000000, transactions: 2900000, attachments: 50000},
customPricePcss: 50000,
onDemandMaxSpend: 50000,
effectiveDate: '2022-03-16',
onDemandEffectiveDate: '2022-02-16',
}),
});
const {container} = render();
// expected copy for plan changes
expect(container).toHaveTextContent(
'This account has pending changes to the subscription'
);
expect(container).toHaveTextContent(
'The following changes will take effect on Mar 16, 2022'
);
expect(container).toHaveTextContent('Plan changes — Developer → Team (Enterprise)');
expect(container).toHaveTextContent('Contract period — monthly → annual');
expect(container).toHaveTextContent('Billing period — monthly → annual');
expect(container).toHaveTextContent('Reserved errors — 5,000 → 15,000,000 errors');
expect(container).toHaveTextContent(
'Reserved transactions — 10,000 → 20,000,000 transactions'
);
expect(container).toHaveTextContent('Reserved attachments — 1 GB → 25 GB');
expect(container).toHaveTextContent('Custom price (ACV) — $0.00 → $50,000.00');
expect(container).toHaveTextContent('Custom price for errors — $0.00 → $20,000.00');
expect(container).toHaveTextContent(
'Custom price for transactions — $0.00 → $29,000.00'
);
expect(container).toHaveTextContent('Custom price for attachments — $0.00 → $500.00');
expect(container).toHaveTextContent('Custom price for PCSS — $0.00 → $500.00');
// expected copy for on-demand changes
expect(container).toHaveTextContent(
'The following changes will take effect on Feb 16, 2022'
);
expect(container).toHaveTextContent('On-demand maximum — $0.00 → $500.00');
});
it('renders pending changes with all categories', function () {
const subscription = SubscriptionFixture({
organization: OrganizationFixture(),
customPrice: 0,
customPricePcss: 0,
pendingChanges: PendingChangesFixture({
planDetails: PlanFixture({
name: 'Team (Enterprise)',
contractInterval: 'annual',
billingInterval: 'annual',
}),
plan: 'am3_team_ent',
planName: 'Team (Enterprise)',
reservedEvents: 15000000,
reservedErrors: 15000000,
reservedTransactions: 0,
reservedAttachments: 25,
reserved: {errors: 15000000, spans: 20000000, attachments: 25},
customPrice: 5000000,
customPriceErrors: 2000000,
customPriceTransactions: 0,
customPriceAttachments: 50000,
customPrices: {errors: 2000000, spans: 200000, attachments: 50000},
customPricePcss: 50000,
onDemandMaxSpend: 50000,
effectiveDate: '2024-10-09',
onDemandEffectiveDate: '2024-02-20',
}),
});
const {container} = render();
// expected copy for plan changes
expect(container).toHaveTextContent(
'This account has pending changes to the subscription'
);
expect(container).toHaveTextContent(
'The following changes will take effect on Oct 9, 2024'
);
expect(container).toHaveTextContent('Plan changes — Developer → Team (Enterprise)');
expect(container).toHaveTextContent('Contract period — monthly → annual');
expect(container).toHaveTextContent('Billing period — monthly → annual');
expect(container).toHaveTextContent('Reserved errors — 5,000 → 15,000,000 errors');
expect(container).toHaveTextContent(
'Reserved transactions — 10,000 → 0 transactions'
);
expect(container).toHaveTextContent('Reserved spans — 0 → 20,000,000 spans');
expect(container).toHaveTextContent('Reserved attachments — 1 GB → 25 GB');
expect(container).toHaveTextContent('Custom price (ACV) — $0.00 → $50,000.00');
expect(container).toHaveTextContent('Custom price for errors — $0.00 → $20,000.00');
expect(container).toHaveTextContent('Custom price for spans — $0.00 → $2,000.00');
expect(container).toHaveTextContent('Custom price for attachments — $0.00 → $500.00');
expect(container).toHaveTextContent('Custom price for PCSS — $0.00 → $500.00');
// expected copy for on-demand changes
expect(container).toHaveTextContent(
'The following changes will take effect on Feb 20, 2024'
);
expect(container).toHaveTextContent('On-demand maximum — $0.00 → $500.00');
});
it('renders on-demand budgets', function () {
const subscription = SubscriptionFixture({
organization: OrganizationFixture(),
onDemandBudgets: {
enabled: true,
budgetMode: OnDemandBudgetMode.SHARED,
sharedMaxBudget: 10000,
onDemandSpendUsed: 0,
},
pendingChanges: PendingChangesFixture({
planDetails: PlanFixture({
name: 'Team (Enterprise)',
contractInterval: 'annual',
billingInterval: 'annual',
onDemandCategories: ['errors', 'transactions', 'attachments'],
}),
onDemandBudgets: {
enabled: true,
budgetMode: OnDemandBudgetMode.PER_CATEGORY,
errorsBudget: 300,
transactionsBudget: 200,
replaysBudget: 0,
attachmentsBudget: 100,
budgets: {errors: 300, transactions: 200, replays: 0, attachments: 100},
},
onDemandMaxSpend: 50000,
effectiveDate: '2022-03-16',
onDemandEffectiveDate: '2022-02-16',
}),
});
const {container} = render();
// expected copy for plan changes
expect(container).toHaveTextContent(
'This account has pending changes to the subscription'
);
expect(container).toHaveTextContent(
'The following changes will take effect on Mar 16, 2022'
);
// expected copy for on-demand changes
expect(container).toHaveTextContent(
'The following changes will take effect on Feb 16, 2022'
);
expect(container).toHaveTextContent(
'On-demand budget — shared on-demand of $100 → per-category on-demand (errors at $3, transactions at $2, and attachments at $1)'
);
});
it('renders pending changes for plan migration', function () {
const organization = OrganizationFixture();
const am2BusinessPlan = PlanDetailsLookupFixture('am2_business_auf');
const subscription = SubscriptionFixture({
planDetails: am2BusinessPlan,
plan: 'am2_business_auf',
contractInterval: ANNUAL,
organization,
onDemandPeriodEnd: '2018-10-24',
contractPeriodEnd: '2019-09-24',
pendingChanges: PendingChangesFixture({
planDetails: PlanFixture({
id: 'am3_business_auf',
name: 'Business',
contractInterval: 'annual',
billingInterval: 'annual',
onDemandCategories: [
'errors',
'attachments',
'spans',
'replays',
'monitorSeats',
],
}),
reservedEvents: 50_000,
reserved: {
errors: 50_000,
spans: 10_000_000,
replays: 50,
monitorSeats: 1,
attachments: 1,
},
effectiveDate: '2019-09-25',
onDemandEffectiveDate: '2018-10-25',
}),
});
const migrationDate = '2018-10-25';
const migration = PlanMigrationFixture({
cohortId: CohortId.TENTH,
effectiveAt: migrationDate,
});
jest
.spyOn(usePlanMigrations, 'usePlanMigrations')
.mockReturnValue({planMigrations: [migration], isLoading: false});
const {container} = render();
// expected copy for plan changes
expect(container).toHaveTextContent(
'This account has pending changes to the subscription'
);
expect(container).toHaveTextContent(
'The following changes will take effect on Oct 25, 2018'
);
expect(container).toHaveTextContent('Plan changes — Business → Business');
expect(container).toHaveTextContent(
'Reserved performance units — 100,000 → 0 transactions'
);
expect(container).toHaveTextContent('Reserved replays — 500 → 50 replays');
expect(container).toHaveTextContent('Reserved spans — 0 → 10,000,000 spans');
// no actual changes
expect(container).not.toHaveTextContent('Reserved errors — 50,000 → 50,000 errors');
expect(container).not.toHaveTextContent(
'Reserved attachments — 1 GB → 1 GB attachments'
);
expect(container).not.toHaveTextContent(
'Reserved cron monitors — 1 → 1 cron monitor'
);
});
it('renders reserved budgets with existing budgets', function () {
const subscription = Am3DsEnterpriseSubscriptionFixture({
organization: OrganizationFixture(),
pendingChanges: PendingChangesFixture({
planDetails: PlanDetailsLookupFixture('am3_business_ent_ds_auf'),
plan: 'am3_business_ent_ds_auf',
planName: 'Business',
reserved: {
spans: RESERVED_BUDGET_QUOTA,
spansIndexed: RESERVED_BUDGET_QUOTA,
},
reservedCpe: {
spans: 12.345678,
spansIndexed: 87.654321,
},
reservedBudgets: [
{
reservedBudget: 50_000_00,
categories: {spans: true, spansIndexed: true},
},
],
}),
});
const {container} = render();
expect(container).not.toHaveTextContent('Plan changes —');
expect(container).not.toHaveTextContent('Reserved spans —');
expect(container).not.toHaveTextContent('Reserved accepted spans —');
expect(container).not.toHaveTextContent('Reserved spansIndexed —');
expect(container).not.toHaveTextContent('Reserved stored spans —');
expect(container).toHaveTextContent(
'Reserved cost-per-event for accepted spans — $0.01000000 → $0.12345678'
);
expect(container).toHaveTextContent(
'Reserved cost-per-event for stored spans — $0.02000000 → $0.87654321'
);
expect(container).toHaveTextContent(
'Reserved budgets — $100,000.00 for accepted spans and stored spans → $50,000.00 for accepted spans and stored spans'
);
});
it('renders reserved budgets without existing budgets', function () {
const subscription = SubscriptionFixture({
organization: OrganizationFixture(),
plan: 'am3_business',
pendingChanges: PendingChangesFixture({
planDetails: PlanDetailsLookupFixture('am3_business_ent_ds_auf'),
plan: 'am3_business_ent_ds_auf',
planName: 'Business',
reserved: {
spans: RESERVED_BUDGET_QUOTA,
spansIndexed: RESERVED_BUDGET_QUOTA,
},
reservedCpe: {
spans: 12.345678,
spansIndexed: 87.654321,
},
reservedBudgets: [
{
reservedBudget: 50_000_00,
categories: {spans: true, spansIndexed: true},
},
],
}),
});
const {container} = render();
expect(container).toHaveTextContent('Plan changes — Business → Business');
expect(container).toHaveTextContent(
'Reserved accepted spans — 10,000,000 → reserved budget'
);
expect(container).toHaveTextContent('Reserved stored spans — 0 → reserved budget');
expect(container).not.toHaveTextContent('Reserved spans —');
expect(container).not.toHaveTextContent('Reserved spansIndexed —');
expect(container).toHaveTextContent(
'Reserved cost-per-event for accepted spans — None → $0.12345678'
);
expect(container).toHaveTextContent(
'Reserved cost-per-event for stored spans — None → $0.87654321'
);
expect(container).toHaveTextContent(
'Reserved budgets — None → $50,000.00 for accepted spans and stored spans'
);
});
it('renders reserved budgets to reserved volume', function () {
const subscription = Am3DsEnterpriseSubscriptionFixture({
organization: OrganizationFixture(),
pendingChanges: PendingChangesFixture({
planDetails: PlanDetailsLookupFixture('am3_business_ent_auf'),
plan: 'am3_business_ent_auf',
planName: 'Business',
reserved: {
spans: 10_000_000,
},
}),
});
const {container} = render();
expect(container).toHaveTextContent('Plan changes — Business → Business');
expect(container).toHaveTextContent(
'Reserved accepted spans — reserved budget → 10,000,000 spans'
);
expect(container).toHaveTextContent(
'Reserved stored spans — reserved budget → 0 spansIndexed'
);
expect(container).not.toHaveTextContent('Reserved spans —');
expect(container).not.toHaveTextContent('Reserved spansIndexed —');
expect(container).toHaveTextContent(
'Reserved cost-per-event for spans — $0.01000000 → None'
);
expect(container).toHaveTextContent(
'Reserved cost-per-event for spansIndexed — $0.02000000 → None'
);
expect(container).toHaveTextContent(
'Reserved budgets — $100,000.00 for accepted spans and stored spans → None'
);
});
it('does not render reserved budgets if there are no changes', function () {
const subscription = SubscriptionFixture({
organization: OrganizationFixture(),
});
const {container} = render();
expect(container).not.toHaveTextContent('Reserved budgets —');
});
});