Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo } from 'react';
import { useCallback, useMemo } from 'react';

import { usePrefetchQuery, useQueries } from '@tanstack/react-query';

Expand All @@ -10,12 +10,15 @@ import {

import { useIsDistantBackupAvailable } from '@/api/hooks/feature';
import { TInstance } from '@/api/hooks/instance/selector/instances.selector';
import { getRegionPricing } from '@/api/hooks/order/selector/order.selector';
import { useRegionTranslation } from '@/api/hooks/region';
import { useMe } from '@/api/hooks/user';
import { isSnapshotConsumption } from '@/pages/new/utils/is-snapshot-consumption';
import { groupBy } from '@/utils';

export type ContinentRegion = Pick<TProductAvailabilityRegion, 'enabled' | 'name' | 'type'> & {
label: string;
price: number | null;
};

export const useInstanceSnapshotPricing = (projectId: string, instanceId: TInstance['id']) => {
Expand All @@ -32,33 +35,25 @@ export const useInstanceSnapshotPricing = (projectId: string, instanceId: TInsta
],
});

const snapshotPlan = useMemo(
() =>
snapshotAvailabilities?.plans.find(
({ code, regions }) =>
isSnapshotConsumption(code) && regions.find((r) => r.name === instanceId.region),
),
[snapshotAvailabilities, instanceId.region],
);
const currentRegion = useMemo(() => {
const currentPlan = snapshotAvailabilities?.plans.find(
({ code, regions }) =>
isSnapshotConsumption(code) && regions.find((r) => r.name === instanceId.region),
);

const catalogAddon = useMemo(
() => catalog?.addons.find(({ planCode }) => planCode === snapshotPlan.code),
[catalog, snapshotPlan],
);
return currentPlan?.regions.find((r) => r.name === instanceId.region);
}, [snapshotAvailabilities, instanceId]);

const currentRegion = useMemo(
() => snapshotPlan?.regions.find((r) => r.name === instanceId.region),
[snapshotPlan, instanceId],
);
const regionPriceCalculator = useCallback(getRegionPricing(snapshotAvailabilities, catalog), [
snapshotAvailabilities,
catalog,
]);

return {
isPending: !snapshotAvailabilities || !catalog,
pricing: useMemo(
() =>
catalogAddon?.pricings.find(
({ intervalUnit }) => intervalUnit === 'none' || intervalUnit === 'hour',
) ?? null,
[catalogAddon],
() => regionPriceCalculator(instanceId.region),
[instanceId, regionPriceCalculator],
),
distantContinents: useMemo(() => {
if (
Expand All @@ -70,7 +65,7 @@ export const useInstanceSnapshotPricing = (projectId: string, instanceId: TInsta
)
return new Map<string, ContinentRegion[]>();

return Map.groupBy(
return groupBy(
snapshotAvailabilities.plans
.filter(({ code }) => isSnapshotConsumption(code))
.flatMap((p) => p.regions)
Expand All @@ -83,6 +78,7 @@ export const useInstanceSnapshotPricing = (projectId: string, instanceId: TInsta
.map((r) => ({
...r,
label: translateMicroRegion(r.name) || r.name,
price: regionPriceCalculator(r.name)?.price,
})),
(r) => translateContinent(r.name) || 'Internal',
);
Expand All @@ -93,6 +89,7 @@ export const useInstanceSnapshotPricing = (projectId: string, instanceId: TInsta
translateMicroRegion,
translateContinent,
isDistantBackupAvailable,
regionPriceCalculator,
]),
};
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { describe } from 'vitest';

import { TCatalog, TProductAvailability } from '@ovh-ux/manager-pci-common';

import { getRegionPricing } from '@/api/hooks/order/selector/order.selector';

describe('order selector', () => {
describe('getRegionPricing', () => {
it('should get the princing for the snapshot.consumption plan corresponding to the region', () => {
const region = 'currentRegion';

const snapshotAvailabilities = {
plans: [
{
code: 'snapshot.consumption.3AZ',
regions: [{ name: region }],
},
{
code: 'snapshot.consumption',
regions: [{ name: 'otherRegion' }],
},
{
code: 'volume.snapshot.consumption',
regions: [{ name: region }],
},
],
} as TProductAvailability;

const catalog = {
addons: [
{
planCode: 'snapshot.consumption.3AZ',
pricings: [
{ intervalUnit: 'month', price: 10500 },
{ intervalUnit: 'hour', price: 2500 },
],
},
{
planCode: 'snapshot.consumption',
pricings: [{ intervalUnit: 'month', price: 20000 }],
},
],
} as TCatalog;

const result = getRegionPricing(snapshotAvailabilities, catalog)(region);

expect(result).toEqual({ intervalUnit: 'hour', price: 2500 });
});

it('should return null if no plan correspond to the region', () => {
const region = 'currentRegion';

const snapshotAvailabilities = {
plans: [
{
code: 'snapshot.consumption',
regions: [{ name: 'otherRegion' }],
},
],
} as TProductAvailability;

const catalog = {
addons: [
{
planCode: 'snapshot.consumption.3AZ',
pricings: [
{ intervalUnit: 'month', price: 10500 },
{ intervalUnit: 'hour', price: 2500 },
],
},
{
planCode: 'snapshot.consumption',
pricings: [{ intervalUnit: 'month', price: 20000 }],
},
],
} as TCatalog;

const result = getRegionPricing(snapshotAvailabilities, catalog)(region);

expect(result).toBeNull();
});

it('should return null if no pricing correspond to plan', () => {
const region = 'currentRegion';

const snapshotAvailabilities = {
plans: [
{
code: 'snapshot.consumption.3AZ',
regions: [{ name: region }],
},
{
code: 'snapshot.consumption',
regions: [{ name: 'otherRegion' }],
},
{
code: 'volume.snapshot.consumption',
regions: [{ name: region }],
},
],
} as TProductAvailability;

const catalog = {
addons: [
{
planCode: 'snapshot.consumption',
pricings: [{ intervalUnit: 'month', price: 20000 }],
},
],
} as TCatalog;

const result = getRegionPricing(snapshotAvailabilities, catalog)(region);

expect(result).toBeNull();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { TCatalog, TProductAvailability } from '@ovh-ux/manager-pci-common';

import { isSnapshotConsumption } from '@/pages/new/utils/is-snapshot-consumption';

export const getRegionPricing =
(snapshotAvailabilities: TProductAvailability, catalog: TCatalog) => (region: string) => {
const regionPlan =
snapshotAvailabilities?.plans.find(
(plan) => isSnapshotConsumption(plan.code) && plan.regions.some((r) => r.name === region),
) || null;

if (!catalog || !regionPlan) return null;

return (
catalog.addons
.find((addon) => addon.planCode === regionPlan.code)
?.pricings.find(({ intervalUnit }) => intervalUnit === 'none' || intervalUnit === 'hour') ??
null
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from '@ovh-ux/manager-react-components';

import { usePrefetchInstances } from '@/api/hooks/instance/useInstances';
import { usePrefetchSnapshotPricing } from '@/api/hooks/order';
import { usePrefetchSnapshotPricing } from '@/api/hooks/order/order';
import { useAddWorkflow } from '@/api/hooks/workflows';
import { useSafeParam } from '@/hooks/useSafeParam';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { vi } from 'vitest';
import { useCatalogPrice, useMe } from '@ovh-ux/manager-react-components';

import { buildInstanceId } from '@/api/hooks/instance/selector/instances.selector';
import { useInstanceSnapshotPricing } from '@/api/hooks/order';
import { useInstanceSnapshotPricing } from '@/api/hooks/order/order';

import { WorkflowName } from './WorkflowName.component';

vi.mock('@ovh-ux/manager-react-components');
vi.mock('@/api/hooks/order');
vi.mock('@/api/hooks/order/order');

describe('WorkflowName', () => {
beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
import { convertHourlyPriceToMonthly, useCatalogPrice } from '@ovh-ux/manager-react-components';

import { TInstance } from '@/api/hooks/instance/selector/instances.selector';
import { useInstanceSnapshotPricing } from '@/api/hooks/order';
import { useInstanceSnapshotPricing } from '@/api/hooks/order/order';
import { StepState } from '@/pages/new/hooks/useStep';

interface WorkflowNameProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ import { describe, it, vi } from 'vitest';

import { renderWithMockedWrappers } from '@/__tests__/renderWithMockedWrappers';
import { buildInstanceId } from '@/api/hooks/instance/selector/instances.selector';
import { ContinentRegion, useInstanceSnapshotPricing } from '@/api/hooks/order';
import { ContinentRegion, useInstanceSnapshotPricing } from '@/api/hooks/order/order';
import { StepState } from '@/pages/new/hooks/useStep';

import { WorkflowScheduling } from './WorkflowScheduling.component';

vi.mock('@/api/hooks/order');
vi.mock('@/api/hooks/order/order');
vi.mock('@ovh-ux/manager-react-components', () => ({
convertHourlyPriceToMonthly: vi.fn(),
useCatalogPrice: vi.fn().mockReturnValue({
getFormattedCatalogPrice: (input: number) => `${input}`,
}),
}));

describe('WorkflowScheduling Component', () => {
const mockOnSubmit = vi.fn();
Expand Down Expand Up @@ -90,14 +96,24 @@ describe('WorkflowScheduling Component', () => {
it('can select distant region when distantContinents is provided', async () => {
vi.mocked(useInstanceSnapshotPricing).mockReturnValue({
distantContinents: new Map<string, ContinentRegion[]>([
['Europe', [{ label: 'Region 1', name: 'region1', enabled: true } as ContinentRegion]],
[
'Europe',
[
{
label: 'Region 1',
name: 'region1',
enabled: true,
price: 1500,
} as ContinentRegion,
],
],
]),
pricing: null,
isPending: false,
});

const user = userEvent.setup();
const { getByLabelText, getByRole } = renderWithMockedWrappers(
const { getByLabelText, getByRole, findByText } = renderWithMockedWrappers(
<WorkflowScheduling
step={stepUnlocked as StepState}
onSubmit={mockOnSubmit}
Expand All @@ -106,22 +122,27 @@ describe('WorkflowScheduling Component', () => {
);

const distantToggle = getByLabelText(/pci_workflow_create_distant_label/);
expect(distantToggle).toBeInTheDocument();
expect(distantToggle).toBeVisible();

await act(async () => {
await user.click(distantToggle);
});

// ODS 19 combobox labels are broken so we get by role hidden instead
const distantRegion = getByRole('combobox');
expect(distantRegion).toBeInTheDocument();
expect(distantRegion).toBeVisible();

await act(async () => {
await user.click(distantRegion);
});

// We only test if the option is here because ODS a11y doesn't work and doesn't trigger a value change
const region1Option = getByRole('option', { name: 'Region 1', hidden: true });
expect(region1Option).toBeInTheDocument();
const region1Option = getByRole('option', { name: 'Region 1' });
expect(region1Option).toBeVisible();

// It would have been better to use user.click on the element but it doesn't work
act(() => region1Option.click());

expect(await findByText('pci_workflow_create_price_monthly')).toBeVisible();
});
});
Loading
Loading