Skip to content

Commit 4a02dae

Browse files
committed
feat(backup-agent): add tenant agents subscription
ref: #BKP-498 Signed-off-by: Vincent Bonmarchand <[email protected]>
1 parent 5904c6a commit 4a02dae

File tree

17 files changed

+458
-8
lines changed

17 files changed

+458
-8
lines changed

package.json

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,100 @@
55
"description": "OVH Control Panel also known as Manager",
66
"license": "BSD-3-Clause",
77
"workspaces": {
8-
"packages": []
8+
"packages": [
9+
"docs",
10+
"packages/components/*",
11+
"packages/manager-tools/*",
12+
"packages/manager-tools/manager-legacy-tools/*",
13+
"packages/manager-ui-kit",
14+
"packages/manager-wiki",
15+
"packages/manager/apps/account",
16+
"packages/manager/apps/billing",
17+
"packages/manager/apps/bmc-backup-agent-baremetal",
18+
"packages/manager/apps/carbon-calculator",
19+
"packages/manager/apps/carrier-sip",
20+
"packages/manager/apps/catalog",
21+
"packages/manager/apps/cda",
22+
"packages/manager/apps/cloud-connect",
23+
"packages/manager/apps/communication",
24+
"packages/manager/apps/container",
25+
"packages/manager/apps/dbaas-logs",
26+
"packages/manager/apps/dedicated",
27+
"packages/manager/apps/dedicated-servers",
28+
"packages/manager/apps/email-domain",
29+
"packages/manager/apps/email-pro",
30+
"packages/manager/apps/exchange",
31+
"packages/manager/apps/freefax",
32+
"packages/manager/apps/hpc-backup-agent-iaas",
33+
"packages/manager/apps/hpc-vmware-public-vcf-aas",
34+
"packages/manager/apps/hpc-vmware-vsphere",
35+
"packages/manager/apps/hub",
36+
"packages/manager/apps/hycu",
37+
"packages/manager/apps/iam",
38+
"packages/manager/apps/identity-access-management",
39+
"packages/manager/apps/iplb",
40+
"packages/manager/apps/metrics",
41+
"packages/manager/apps/nasha",
42+
"packages/manager/apps/netapp",
43+
"packages/manager/apps/nutanix",
44+
"packages/manager/apps/okms",
45+
"packages/manager/apps/order-tracking",
46+
"packages/manager/apps/overthebox",
47+
"packages/manager/apps/pci",
48+
"packages/manager/apps/pci-ai-endpoints",
49+
"packages/manager/apps/pci-ai-tools",
50+
"packages/manager/apps/pci-billing",
51+
"packages/manager/apps/pci-block-storage",
52+
"packages/manager/apps/pci-cold-archive",
53+
"packages/manager/apps/pci-databases-analytics",
54+
"packages/manager/apps/pci-dataplatform",
55+
"packages/manager/apps/pci-gateway",
56+
"packages/manager/apps/pci-instances",
57+
"packages/manager/apps/pci-kubernetes",
58+
"packages/manager/apps/pci-load-balancer",
59+
"packages/manager/apps/pci-object-storage",
60+
"packages/manager/apps/pci-private-network",
61+
"packages/manager/apps/pci-private-registry",
62+
"packages/manager/apps/pci-public-ip",
63+
"packages/manager/apps/pci-quota",
64+
"packages/manager/apps/pci-rancher",
65+
"packages/manager/apps/pci-savings-plan",
66+
"packages/manager/apps/pci-ssh-keys",
67+
"packages/manager/apps/pci-users",
68+
"packages/manager/apps/pci-volume-backup",
69+
"packages/manager/apps/pci-volume-snapshot",
70+
"packages/manager/apps/pci-vouchers",
71+
"packages/manager/apps/pci-workflow",
72+
"packages/manager/apps/procedures",
73+
"packages/manager/apps/public-cloud",
74+
"packages/manager/apps/restricted",
75+
"packages/manager/apps/sap-features-hub",
76+
"packages/manager/apps/sign-up",
77+
"packages/manager/apps/sms",
78+
"packages/manager/apps/support",
79+
"packages/manager/apps/telecom",
80+
"packages/manager/apps/telecom-dashboard",
81+
"packages/manager/apps/telecom-task",
82+
"packages/manager/apps/veeam-backup",
83+
"packages/manager/apps/veeam-enterprise",
84+
"packages/manager/apps/vps",
85+
"packages/manager/apps/vrack",
86+
"packages/manager/apps/vrack-services",
87+
"packages/manager/apps/web",
88+
"packages/manager/apps/web-domains",
89+
"packages/manager/apps/web-hosting",
90+
"packages/manager/apps/web-office",
91+
"packages/manager/apps/web-ongoing-operations",
92+
"packages/manager/apps/zimbra",
93+
"packages/manager/core/api",
94+
"packages/manager/core/application",
95+
"packages/manager/core/ovh-product-icons",
96+
"packages/manager/core/request-tagger",
97+
"packages/manager/core/sso",
98+
"packages/manager/core/url-builder",
99+
"packages/manager/core/utils",
100+
"packages/manager/modules/*"
101+
]
9102
},
10103
"scripts": {
11104
"build": "yarn pm:build:ci",
@@ -136,4 +229,4 @@
136229
"node": "^22",
137230
"yarn": ">=1.21.1"
138231
}
139-
}
232+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"installed_agents": "Agents installés",
3+
"connected_vaults": "Vaults connectés",
4+
"billing_more_info": "Plus d'information sur la facturation",
5+
"number_of_connected_vault": "{{connectedVaultCount}} connecté(s)",
6+
"number_of_installed_agents": "{{installedAgentsCount}} installé(s)"
7+
}

packages/manager/modules/backup-agent/src/data/api/tenants/tenants.requests.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const getTenantDetails = async (tenantId: string) =>
2626
(await v2.get<Tenant>(getDetailsTenantRoute(tenantId))).data;
2727

2828
export const getVSPCTenantDetails = async (vspcTenantId: string) =>
29-
(await v2.get<Tenant>(getDetailsVspcTenantRoute(vspcTenantId))).data;
29+
(await v2.get<VSPCTenant>(getDetailsVspcTenantRoute(vspcTenantId))).data;
3030

3131

3232
export const getVSPCTenants = async (
@@ -36,3 +36,4 @@ export const getVSPCTenants = async (
3636

3737
export const deleteVSPCTenant = async (vspcTenantId: string): Promise<ApiResponse<string>> =>
3838
v2.delete(`${GET_VSPC_TENANTS_ROUTE}/${vspcTenantId}`);
39+

packages/manager/modules/backup-agent/src/data/hooks/tenants/useBackupTenantDetails.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useQuery, DefinedInitialDataOptions } from "@tanstack/react-query";
22
import { BACKUP_TENANTS_QUERY_KEY } from './useBackupTenants';
3-
import { Tenant } from '@/types/Tenant.type';
3+
import { Tenant } from '@/types/Tenant.type';
44
import { TENANTS_MOCKS } from '@/mocks/tenant/tenants.mock';
55
import {Resource} from "@/types/Resource.type";
66
import {
@@ -26,3 +26,4 @@ export const useBackupTenantDetails = (
2626
select: (data): Resource<WithRegion<Tenant>> => mapTenantResourceToTenantResourceWithRegion(data),
2727
...options
2828
})
29+

packages/manager/modules/backup-agent/src/data/hooks/tenants/useBackupTenants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { UseQueryResult, useQuery } from '@tanstack/react-query';
1+
import { useQuery } from '@tanstack/react-query';
22

33
import { TENANTS_MOCKS } from '@/mocks/tenant/tenants.mock';
44
import {Tenant} from '@/types/Tenant.type';
@@ -29,3 +29,5 @@ export const useBackupTenantsMocks = () =>
2929
}),
3030
select: (res): Resource<WithRegion<Tenant>>[] => res.data.map((tenantResource) => mapTenantResourceToTenantResourceWithRegion(tenantResource)),
3131
});
32+
33+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useQuery, DefinedInitialDataOptions } from "@tanstack/react-query";
2+
import {Resource} from "@/types/Resource.type";
3+
import {
4+
mapTenantResourceToTenantResourceWithRegion,
5+
} from "@/utils/mappers/mapTenantToTenantWithRegion";
6+
import {WithRegion} from "@/types/Utils.type";
7+
import { GET_VSPC_TENANTS_QUERY_KEY } from "./useVspcTenants";
8+
import { VSPCTenant } from "@/types/VspcTenant.type";
9+
import { VSPC_TENANTS_MOCKS } from "@/mocks/tenant/vspcTenants.mock";
10+
11+
export const BACKUP_VSPC_TENANT_DETAILS_QUERY_KEY = (vspcTenantID: string) => [...GET_VSPC_TENANTS_QUERY_KEY, vspcTenantID];
12+
13+
export const useBackupVSPCTenantDetails = (
14+
{
15+
tenantId,
16+
...options
17+
}: {
18+
tenantId: string;
19+
} & Partial<Omit<DefinedInitialDataOptions<Resource<VSPCTenant>, unknown, Resource<WithRegion<VSPCTenant>>>, "queryKey" | "queryFn">>,
20+
) => useQuery({
21+
queryFn: () => new Promise<Resource<VSPCTenant>>((resolve, reject) => {
22+
const result = VSPC_TENANTS_MOCKS.find(tenant => tenant.id === tenantId)
23+
result ? resolve(result) : reject(new Error('Tenant not found'))
24+
}),
25+
queryKey: BACKUP_VSPC_TENANT_DETAILS_QUERY_KEY(tenantId),
26+
select: (data): Resource<WithRegion<VSPCTenant>> => mapTenantResourceToTenantResourceWithRegion(data),
27+
...options
28+
})

packages/manager/modules/backup-agent/src/data/hooks/tenants/useVspcTenants.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
import { UseQueryResult, useQuery } from '@tanstack/react-query';
1+
import { DefinedInitialDataOptions, UseQueryResult, useQueries, useQuery } from '@tanstack/react-query';
22

33
import { VSPC_TENANTS_MOCKS } from '@/mocks/tenant/vspcTenants.mock';
44
import { VSPCTenant } from '@/types/VspcTenant.type';
55

6-
import { getVSPCTenants } from '../../api/tenants/tenants.requests';
6+
import { getVSPCTenantDetails, getVSPCTenants } from '../../api/tenants/tenants.requests';
77
import { BACKUP_TENANTS_QUERY_KEY } from './useBackupTenants';
88
import {Resource} from "@/types/Resource.type";
9+
import { useMemo } from 'react';
10+
import { AssociatedTenantVSPC } from '@/types/Tenant.type';
11+
import { countBackupAgents } from '@/utils/countBackupAgents';
912

1013
type TUseVSPCTenantsResult = UseQueryResult<Resource<VSPCTenant>[], Error>;
1114

@@ -29,3 +32,33 @@ export const useVSPCTenantsMocks = (): TUseVSPCTenantsResult =>
2932
});
3033
}),
3134
});
35+
36+
export const useInstalledBackupAgents = ( {
37+
vspcTenants,
38+
}: {
39+
vspcTenants: AssociatedTenantVSPC[];
40+
} & Partial<Omit<DefinedInitialDataOptions<Resource<VSPCTenant>, unknown>, "queryKey" | "queryFn">>,
41+
) => {
42+
43+
const vspcTenantIds = useMemo(() => {
44+
if (!vspcTenants) return [];
45+
return vspcTenants.map((vspc) => vspc.id);
46+
}, [vspcTenants]);
47+
48+
const vspcTenantQueries = useQueries({
49+
queries: vspcTenantIds.map((vspcTenantId) => ({
50+
queryKey: ['vspcTenantDetails', vspcTenantId],
51+
queryFn: () => getVSPCTenantDetails(vspcTenantId),
52+
enabled: !!vspcTenantId,
53+
})),
54+
});
55+
56+
const installedBackupAgents = useMemo(() => countBackupAgents(vspcTenantQueries), [vspcTenantQueries]);
57+
58+
const isLoading = vspcTenantQueries.some((q) => q.isLoading);
59+
60+
return {
61+
installedBackupAgents,
62+
isLoading,
63+
};
64+
};

packages/manager/modules/backup-agent/src/pages/services/dashboard/general-information/GeneralInformation.page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { BillingInformationsTileStandard } from "@ovh-ux/billing-informations"
33
import { GeneralInformationTenantTile } from "./_components/general-information-tenant-tile/GeneralInformationTenantTile.component";
44
import { useParams } from "react-router-dom";
55
import { useBackupTenantDetails } from "@/data/hooks/tenants/useBackupTenantDetails";
6+
import { SubscriptionTile } from "./_components/subscription-tile/SubscriptionTile.component";
67

78

89
export default function GeneralInformationPage() {
@@ -12,6 +13,7 @@ export default function GeneralInformationPage() {
1213
return (
1314
<section className="max-w-6xl mx-auto px-12 grid md:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-8">
1415
<GeneralInformationTenantTile tenantId={tenantId!} />
16+
<SubscriptionTile tenantId={tenantResource?.id} />
1517
<BillingInformationsTileStandard resourceName={tenantResource?.id} />
1618
</section>
1719
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { vi } from "vitest"
2+
import { render, screen } from "@testing-library/react"
3+
import { NAMESPACES } from "@ovh-ux/manager-common-translations";
4+
import {mockVaults} from "@/mocks/vaults/vaults";
5+
6+
import { SubscriptionTile } from "../subscription-tile/SubscriptionTile.component"
7+
import {BACKUP_AGENT_NAMESPACES} from "@/lib";
8+
9+
const LABELS_VISIBLES = [`${NAMESPACES.BILLING}:subscription`, `${NAMESPACES.DASHBOARD}:consumption`, `${BACKUP_AGENT_NAMESPACES.VAULT_DASHBOARD}:type_billing`]
10+
11+
12+
const { useBackupVaultDetailsMock } = vi.hoisted(() => ({
13+
useBackupVaultDetailsMock: vi.fn()
14+
}))
15+
16+
vi.mock('@/data/hooks/vaults/getVaultDetails', () => ({
17+
useBackupVaultDetails: useBackupVaultDetailsMock
18+
}))
19+
20+
vi.mock("react-i18next", () => ({
21+
useTranslation: vi.fn().mockReturnValue({
22+
t: vi.fn().mockImplementation((key: string) => key),
23+
})
24+
}))
25+
26+
27+
describe('SubscriptionTile', () => {
28+
it("Should render SubscriptionTile component", async () => {
29+
useBackupVaultDetailsMock.mockReturnValue({ data: mockVaults[0]!, isLoading: false })
30+
const { container } = render(<SubscriptionTile tenantId={mockVaults[0]!.id} />)
31+
32+
await expect(container).toBeAccessible();
33+
34+
LABELS_VISIBLES.forEach(label => {
35+
expect(screen.getByText(label)).toBeVisible()
36+
})
37+
})
38+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Links, LinkType, ManagerTile } from '@ovh-ux/manager-react-components';
2+
import { useTranslation } from "react-i18next";
3+
import { NAMESPACES } from "@ovh-ux/manager-common-translations";
4+
import { OdsSkeleton } from "@ovhcloud/ods-components/react";
5+
import { BACKUP_AGENT_NAMESPACES } from "@/BackupAgent.translations";
6+
import { useBackupTenantDetails } from '@/data/hooks/tenants/useBackupTenantDetails';
7+
import { InstalledAgents } from './_components/InstalledAgents.component';
8+
import { ConnectedVaults } from './_components/ConnectedVaults.component';
9+
import { useHref } from 'react-router-dom';
10+
import { urls } from '@/routes/Routes.constants';
11+
12+
type SubscriptionTileProps = {
13+
tenantId?: string;
14+
};
15+
16+
export function SubscriptionTile({ tenantId }: Readonly<SubscriptionTileProps>) {
17+
const { t } = useTranslation([NAMESPACES.DASHBOARD, NAMESPACES.BILLING, BACKUP_AGENT_NAMESPACES.SERVICE_DASHBOARD]);
18+
const { data, isLoading } = useBackupTenantDetails({ tenantId: tenantId! })
19+
const billingHref = useHref(urls.listingBilling)
20+
return (
21+
<ManagerTile>
22+
<ManagerTile.Title>{t(`${NAMESPACES.BILLING}:subscription`)}</ManagerTile.Title>
23+
<ManagerTile.Divider />
24+
<ManagerTile.Item>
25+
<ManagerTile.Item.Label>{t(`${BACKUP_AGENT_NAMESPACES.SERVICE_DASHBOARD}:installed_agents`)}</ManagerTile.Item.Label>
26+
<ManagerTile.Item.Description>
27+
{isLoading ? <OdsSkeleton /> : <InstalledAgents vspcTenants={data?.currentState.vspcTenants} />}
28+
</ManagerTile.Item.Description>
29+
</ManagerTile.Item>
30+
<ManagerTile.Divider />
31+
<ManagerTile.Item>
32+
<ManagerTile.Item.Label>{t(`${BACKUP_AGENT_NAMESPACES.SERVICE_DASHBOARD}:connected_vaults`)}</ManagerTile.Item.Label>
33+
<ManagerTile.Item.Description>
34+
{isLoading ? <OdsSkeleton /> : <ConnectedVaults tenantDetails={data} />}
35+
</ManagerTile.Item.Description>
36+
</ManagerTile.Item>
37+
<ManagerTile.Divider />
38+
<ManagerTile.Item>
39+
<Links href={billingHref} label={t(`${BACKUP_AGENT_NAMESPACES.SERVICE_DASHBOARD}:billing_more_info`)} type={LinkType.external} />
40+
</ManagerTile.Item>
41+
</ManagerTile>
42+
);
43+
}

0 commit comments

Comments
 (0)