Skip to content

Commit 9ee55a1

Browse files
committed
feat(backup-agent): add edit configuration agent drawer
ref: #BKP-656 Signed-off-by: Thibault Barske <[email protected]>
1 parent 31340c8 commit 9ee55a1

File tree

17 files changed

+686
-53
lines changed

17 files changed

+686
-53
lines changed

packages/manager/modules/backup-agent/public/translations/services/agent/Messages_fr_FR.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
22
"add_configuration": "Ajouter une configuration",
3+
"edit_configuration": "Éditer un agent",
4+
"service_informations": "Informations sur le service",
35
"link_agent_to_a_server": "Lier un agent à un serveur existant",
46
"add_server": "Ajouter un serveur",
57
"select_server": "Sélectionner le serveur",
Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
1+
import { ApiResponse, v2 } from '@ovh-ux/manager-core-api';
2+
13
export const getBackupAgentsListRoute = (vspcTenantId: string) =>
24
`/backup/tenant/vspc/${vspcTenantId}/backupAgent`;
35

46
export const getBackupAgentsDetailsRoute = (vspcTenantId: string, backupAgentId: string) =>
5-
`${getBackupAgentsListRoute(vspcTenantId)}/${backupAgentId}`;
7+
`/backup/tenant/vspc/${vspcTenantId}/backupAgent/${backupAgentId}`;
8+
9+
export const getEditConfigurationBackupAgentsRoute = (
10+
vspcTenantId: string,
11+
backupAgentId: string,
12+
) => `/backup/tenant/vspc/${vspcTenantId}/backupAgent/${backupAgentId}`;
13+
14+
export type EditConfigurationBackupAgentsParams = {
15+
vspcTenantId: string;
16+
backupAgentId: string;
17+
ips: string[];
18+
displayName: string;
19+
policy: string;
20+
};
21+
22+
export const editConfigurationBackupAgents = async ({
23+
vspcTenantId,
24+
backupAgentId,
25+
...payload
26+
}: EditConfigurationBackupAgentsParams): Promise<ApiResponse<string>> =>
27+
v2.put(getEditConfigurationBackupAgentsRoute(vspcTenantId, backupAgentId), payload);
Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,43 @@
1-
import { DefinedInitialDataOptions, useQuery } from '@tanstack/react-query';
1+
import { queryOptions, useQuery } from '@tanstack/react-query';
22

33
import { BACKUP_TENANT_DETAILS_QUERY_KEY } from '@/data/hooks/tenants/useBackupTenantDetails';
44
import { mockAgents } from '@/mocks/agents/agents';
55
import { Agent } from '@/types/Agent.type';
66
import { Resource } from '@/types/Resource.type';
77

88
export const BACKUP_VSPC_TENANT_AGENT_DETAILS_QUERY_KEY = (
9-
vspcTenantID: string,
10-
backupId: string,
11-
) => [...BACKUP_TENANT_DETAILS_QUERY_KEY(vspcTenantID), backupId];
9+
vspcTenantId: string,
10+
agentId: string,
11+
) => [...BACKUP_TENANT_DETAILS_QUERY_KEY(vspcTenantId), agentId];
1212

13-
export const useBackupVSPCTenantDetails = ({
13+
export const useBackupVSPCTenantAgentDetailsOptions = ({
1414
tenantId,
15-
backupId,
16-
...options
15+
agentId,
1716
}: {
18-
tenantId: string;
19-
backupId: string;
20-
} & Partial<
21-
Omit<DefinedInitialDataOptions<Resource<Agent>, unknown, Resource<Agent>>, 'queryKey' | 'queryFn'>
22-
>) =>
23-
useQuery({
17+
tenantId?: string;
18+
agentId?: string;
19+
}) =>
20+
queryOptions({
2421
queryFn: () =>
2522
new Promise<Resource<Agent>>((resolve, reject) => {
26-
const result = mockAgents.find((agent) => agent.id === tenantId);
27-
result ? resolve(result) : reject(new Error('Agent not found'));
23+
setTimeout(() => {
24+
const result = mockAgents.find((agent) => agent.id === agentId);
25+
result ? resolve(result) : reject(new Error('Agent not found'));
26+
}, 1000);
2827
}),
29-
queryKey: BACKUP_VSPC_TENANT_AGENT_DETAILS_QUERY_KEY(tenantId, backupId),
28+
queryKey: BACKUP_VSPC_TENANT_AGENT_DETAILS_QUERY_KEY(tenantId!, agentId!),
29+
enabled: !!tenantId && !!agentId,
30+
});
31+
32+
export const useBackupVSPCTenantAgentDetails = ({
33+
tenantId,
34+
agentId,
35+
...options
36+
}: {
37+
tenantId?: string;
38+
agentId?: string;
39+
}) =>
40+
useQuery({
41+
...useBackupVSPCTenantAgentDetailsOptions({ tenantId, agentId }),
3042
...options,
3143
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { UseMutationOptions, useMutation, useQueryClient } from '@tanstack/react-query';
2+
3+
import { ApiError, ApiResponse } from '@ovh-ux/manager-core-api';
4+
5+
import {
6+
EditConfigurationBackupAgentsParams,
7+
editConfigurationBackupAgents,
8+
} from '@/data/api/agents/agents.requests';
9+
10+
import { GET_VSPC_TENANTS_QUERY_KEY } from '../tenants/useVspcTenants';
11+
12+
type UseEditConfigurationVSPCTenantAgentParams = Partial<
13+
UseMutationOptions<ApiResponse<string>, ApiError, EditConfigurationBackupAgentsParams>
14+
>;
15+
export const useEditConfigurationVSPCTenantAgent = ({
16+
...options
17+
}: UseEditConfigurationVSPCTenantAgentParams) => {
18+
const queryClient = useQueryClient();
19+
20+
return useMutation({
21+
mutationFn: (payload: EditConfigurationBackupAgentsParams) =>
22+
editConfigurationBackupAgents(payload),
23+
...options,
24+
onSuccess: async (...params) => {
25+
await queryClient.invalidateQueries({
26+
queryKey: GET_VSPC_TENANTS_QUERY_KEY,
27+
});
28+
options.onSuccess?.(...params);
29+
},
30+
});
31+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useParams } from 'react-router-dom';
2+
3+
import { invariant } from '@/utils/invariant';
4+
5+
/**
6+
* A hook that ensures specific params exist in the URL.
7+
* Usage: const { id } = useRequiredParams('id');
8+
*/
9+
export function useRequiredParams<Key extends string>(...keys: Key[]): Record<Key, string> {
10+
const params = useParams();
11+
// Loop over the keys you requested and check them
12+
keys.forEach((key) => {
13+
invariant(params[key], `Missing required param "${key} in the URL"`);
14+
});
15+
// If we pass the checks, we force the type to match the keys provided
16+
return params as Record<Key, string>;
17+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const mockTenantBackupPolicies = ['windows', 'linux', 'macos'];
1+
export const mockTenantBackupPolicies = ['windows', 'linux', 'daily-backup-retention-30d', 'macos'];
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export const LABELS = {
22
BACKUP_AGENT: 'Backup Agent',
3+
BACKUP_POLICY: 'Backup Policy',
34
} as const;
Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,46 @@
11
import { useId } from 'react';
22

3+
import { useHref } from 'react-router-dom';
4+
35
import { useTranslation } from 'react-i18next';
46

5-
import { OdsButton, OdsPopover } from '@ovhcloud/ods-components/react';
7+
import { ODS_BUTTON_COLOR } from '@ovhcloud/ods-components';
68

79
import { NAMESPACES } from '@ovh-ux/manager-common-translations';
8-
import { DataGridTextCell } from '@ovh-ux/manager-react-components';
10+
import { ActionMenu, ActionMenuItem, DataGridTextCell } from '@ovh-ux/manager-react-components';
11+
12+
import { BACKUP_AGENT_NAMESPACES } from '@/BackupAgent.translations';
13+
import { urlParams, urls } from '@/routes/Routes.constants';
914

10-
import { BACKUP_AGENT_NAMESPACES } from '@/lib';
11-
import { Agent } from '@/types/Agent.type';
12-
import { Resource } from '@/types/Resource.type';
15+
export type AgentActionsCellProps = {
16+
tenantId: string;
17+
agentId: string;
18+
};
1319

14-
export const AgentActionsCell = (resourceAgent: Resource<Agent>) => {
20+
export const AgentActionsCell = ({ tenantId, agentId }: AgentActionsCellProps) => {
1521
const id = useId();
1622
const { t } = useTranslation([NAMESPACES.ACTIONS, BACKUP_AGENT_NAMESPACES.COMMON]);
23+
const configurationHref = useHref(
24+
urls.editAgentConfiguration.replace(urlParams.tenantId, tenantId).replace(':agentId', agentId),
25+
);
26+
27+
const actions: ActionMenuItem[] = [
28+
{
29+
id: 0,
30+
label: t(`${NAMESPACES.ACTIONS}:configure`),
31+
href: configurationHref,
32+
},
33+
{
34+
id: 1,
35+
label: t(`${NAMESPACES.ACTIONS}:delete`),
36+
href: '',
37+
color: ODS_BUTTON_COLOR.critical,
38+
},
39+
];
1740

1841
return (
1942
<DataGridTextCell>
20-
<OdsButton
21-
id={id}
22-
role="button"
23-
label=""
24-
icon="ellipsis-vertical"
25-
variant="ghost"
26-
aria-label={t(`${BACKUP_AGENT_NAMESPACES.COMMON}:actions`)}
27-
/>
28-
<OdsPopover triggerId={id} with-arrow="">
29-
<OdsButton
30-
label={t(`${NAMESPACES.ACTIONS}:configure`)}
31-
onClick={() => console.log(`Configure ${resourceAgent.id}`)}
32-
/>
33-
<OdsButton
34-
color="critical"
35-
label={t(`${NAMESPACES.ACTIONS}:delete`)}
36-
onClick={() => console.log(`Delete ${resourceAgent.id}`)}
37-
/>
38-
</OdsPopover>
43+
<ActionMenu id={id} items={actions} isCompact />
3944
</DataGridTextCell>
4045
);
4146
};
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,40 @@
11
import React from 'react';
22

3-
import { render } from '@testing-library/react';
4-
import { describe, expect, it, vi } from 'vitest';
3+
import { render, screen } from '@testing-library/react';
4+
import { describe, it, vi } from 'vitest';
55

66
import { mockAgents } from '@/mocks/agents/agents';
7+
import { TENANTS_MOCKS } from '@/mocks/tenant/tenants.mock';
78
import { AgentActionsCell } from '@/pages/services/dashboard/agent/_components';
89

9-
vi.mock('@ovh-ux/manager-react-components', () => ({
10-
DataGridTextCell: ({ children }: { children: React.ReactNode }) => (
11-
<div data-testid="cell">{children}</div>
12-
),
10+
vi.mock('react-router-dom', () => ({
11+
useHref: vi.fn().mockImplementation((url: string) => url),
1312
}));
1413

14+
// --- Mock translation ---
15+
vi.mock('@ovhcloud/ods-components/react', async () => {
16+
const actual = await vi.importActual('@ovhcloud/ods-components/react');
17+
return {
18+
...actual,
19+
OdsButton: vi
20+
.fn()
21+
.mockImplementation(({ label }: { children: React.ReactNode; label: string }) => (
22+
<button>{label}</button>
23+
)),
24+
};
25+
});
26+
27+
vi.mock('@ovh-ux/manager-react-components', async () => {
28+
const actual = await vi.importActual('@ovh-ux/manager-react-components');
29+
30+
return {
31+
...actual,
32+
DataGridTextCell: ({ children }: { children: React.ReactNode }) => (
33+
<div data-testid="cell">{children}</div>
34+
),
35+
};
36+
});
37+
1538
// --- Mock translation ---
1639
vi.mock('react-i18next', () => ({
1740
useTranslation: vi
@@ -20,9 +43,10 @@ vi.mock('react-i18next', () => ({
2043
}));
2144

2245
describe('AgentActionCell', () => {
23-
it('renders agent action', async () => {
24-
const { container } = render(<AgentActionsCell {...mockAgents[0]!} />);
46+
it('renders agent action', () => {
47+
render(<AgentActionsCell tenantId={mockAgents[0]!.id} agentId={TENANTS_MOCKS[0]!.id} />);
2548

26-
await expect(container).toBeAccessible();
49+
expect(screen.getByText('translated_configure')).toBeVisible();
50+
expect(screen.getByText('translated_delete')).toBeVisible();
2751
});
2852
});

packages/manager/modules/backup-agent/src/pages/services/dashboard/agent/_hooks/useAgentsListingColumns.hooks.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { useMemo } from 'react';
22

3+
import { useParams } from 'react-router-dom';
4+
35
import { useTranslation } from 'react-i18next';
46

57
import { NAMESPACES } from '@ovh-ux/manager-common-translations';
@@ -13,6 +15,7 @@ import { Resource } from '@/types/Resource.type';
1315
import { AgentActionsCell, AgentIpsCell, AgentNameCell, AgentPolicyCell } from '../_components';
1416

1517
export function useAgentsListingColumnsHooks() {
18+
const { tenantId } = useParams();
1619
const { t } = useTranslation([
1720
BACKUP_AGENT_NAMESPACES.COMMON,
1821
NAMESPACES.DASHBOARD,
@@ -59,7 +62,9 @@ export function useAgentsListingColumnsHooks() {
5962
},
6063
{
6164
id: 'action',
62-
cell: AgentActionsCell,
65+
cell: (agentResource: Resource<Agent>) => (
66+
<AgentActionsCell tenantId={tenantId!} agentId={agentResource.id} />
67+
),
6368
label: '',
6469
},
6570
],

0 commit comments

Comments
 (0)