Skip to content

Commit

Permalink
feat(pci.private-registry): edit cidr
Browse files Browse the repository at this point in the history
ref: TAPC-2320
Signed-off-by: Pierre-Philippe <[email protected]>
  • Loading branch information
Pierre-Philippe committed Jan 13, 2025
1 parent 8985719 commit 7b6ced4
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"private_registry_noCIDR": "Actuellement votre Managed Private Registry est ouverte à toutes les connexions. \nPour limiter l'accès à votre Managed Private Registry, il est important de définir des adresses IP autorisées (Bloc CIDR). \nEn procédant ainsi, vous assurez que seules les personnes ou les systèmes que vous avez approuvés peuvent accéder à votre Registry, ce qui renforce la sécurité et facilite la gestion des connexions.",
"ip_restrictions_add_block": "Ajouter un bloc CIDR autorisé",
"ip_restrictions_delete_block": "Supprimer un bloc CIDR autorisé",
"ip_restrictions_edit_block": "Modifier un bloc CIDR autorisé",
"ip_restrictions_delete_multiple_block": "Supprimer plusieurs blocs CIDR autorisés",
"private_registry_bloc_cidr": "Bloc CIDR",
"private_registry_cidr_description": "Description",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,22 @@ export const updateIpRestriction = async (
action: TIPRestrictionsMethodEnum,
) => {
const entries = Object.entries(cidrToUpdate);

// This function is used to delete the object inside authorisation array if REPLACE action is used
const firstIpBlock = Object.values(cidrToUpdate)
.flat()
.find((item) => item);

const promises = entries.map(([authorization, values]) =>
processIpBlock({
projectId,
registryId,
authorization: authorization as FilterRestrictionsServer,
values,
action,
values: firstIpBlock && values.length === 0 ? [firstIpBlock] : values,
action:
firstIpBlock && values.length === 0
? TIPRestrictionsMethodEnum.DELETE
: action,
}),
);
return Promise.all(promises);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import { render, screen, fireEvent } from '@testing-library/react';

import { useFormContext } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { describe, it, expect, vi } from 'vitest';
import { describe, it, expect, vi, Mock } from 'vitest';
import ActionComponent from './Actions.component';
import { TIPRestrictionsData } from '@/types';
import * as useDataGridContext from '@/pages/CIDR/useDatagridContext';
import { ContextDatagridType } from '@/pages/CIDR/DatagridContext.provider';

vi.mock('react-hook-form', () => ({
useFormContext: vi.fn().mockReturnValue({ reset: vi.fn() }),
}));

vi.mock('@/pages/CIDR/useDatagridContext', async () => ({
__esModule: true,
default: () => ({
editActualRow: vi.fn(),
isDraft: false,
}),
}));
describe('ActionComponent', () => {
it('renders ActionMenu with the correct item', () => {
const cidr = ({
Expand All @@ -17,6 +32,33 @@ describe('ActionComponent', () => {
).toBeInTheDocument();
});

it('calls reset with the correct data when edit action is clicked', () => {
const mockReset = vi.fn();
const mockEditActualRow = vi.fn();

vi.mocked(useFormContext as Mock).mockReturnValue({
reset: mockReset,
});

mockEditActualRow.mockReturnValue({ ipBlock: '192.168.0.1/24' });
vi.spyOn(useDataGridContext, 'default').mockImplementation(
() =>
(({
editActualRow: mockEditActualRow,
isDraft: false,
} as unknown) as ContextDatagridType<TIPRestrictionsData[]>),
);
const cidr = {
ipBlock: '192.168.0.1/24',
} as TIPRestrictionsData;

render(<ActionComponent cidr={cidr} />);

fireEvent.click(screen.getByText('ip_restrictions_edit_block'));

expect(mockEditActualRow).toHaveBeenCalledWith('192.168.0.1/24');
expect(mockReset).toHaveBeenCalledWith({ ipBlock: '192.168.0.1/24' });
});
it('opens DeleteModal when the action item is clicked', () => {
const mockNavigate = vi.fn();
vi.mocked(useNavigate).mockReturnValue(mockNavigate);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
import { ActionMenu } from '@ovh-ux/manager-react-components';
import { useFormContext } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { TIPRestrictionsData } from '@/types';
import { categorizeByKey } from '@/helpers';
import useDataGridContext from '@/pages/CIDR/useDatagridContext';

export default function ActionComponent({
cidr,
}: {
cidr: TIPRestrictionsData;
}) {
const { t } = useTranslation(['ip-restrictions']);
const { reset } = useFormContext();
const { editActualRow, isDraft } = useDataGridContext();
const navigate = useNavigate();

const items = [
{
id: 0,
label: t('ip_restrictions_edit_block'),
disabled: isDraft,
onClick: () => {
const actualRow = editActualRow(cidr.ipBlock);
reset(actualRow);
},
},
{
id: 1,
label: t('ip_restrictions_delete_block'),
disabled: false,
onClick: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ const Authorization = () => {
</OsdsSelectOption>
<OsdsSelectOption
value={JSON.stringify([
FilterRestrictionsEnum.REGISTRY,
FilterRestrictionsEnum.MANAGEMENT,
FilterRestrictionsEnum.REGISTRY,
])}
>
{capitalizeAndJoin([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ vi.mock('react-router-dom', () => ({
useParams: vi.fn(),
}));

vi.mock('@/pages/CIDR/useDatagridContext', () => ({
__esModule: true,
default: () => ({
removeDraftRow: vi.fn(),
}),
}));

vi.mock('../../api/hooks/useIpRestrictions', async (importOriginal) => {
const actual = (await importOriginal()) as Record<string, unknown>;
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import useDataGridContext from '@/pages/CIDR/useDatagridContext';

const Buttons = () => {
const { projectId = '', registryId = '' } = useParams();

const { handleSubmit, formState, reset } = useFormContext();
const { resetRows } = useDataGridContext();
const { handleSubmit, formState, reset: resetForm } = useFormContext();
const { t } = useTranslation(['ip-restrictions', 'common']);

const { addSuccess, addError } = useNotifications();
Expand All @@ -34,7 +34,8 @@ const Buttons = () => {
[addError],
);
const onSuccess = useCallback(() => {
reset();
resetForm();
resetRows();
addSuccess(t('private_registry_cidr_submit_success'), true);
}, [addSuccess, t]);

Expand All @@ -45,7 +46,7 @@ const Buttons = () => {
onSuccess,
});

const { removeDraftRow } = useDataGridContext();
const { removeDraftRow, isUpdating } = useDataGridContext();

const onSubmit: SubmitHandler<FieldValues> = async (data) => {
const categorizeByKeyResult = categorizeByKey([data], 'authorization', [
Expand All @@ -57,7 +58,9 @@ const Buttons = () => {
FilterRestrictionsServer,
TIPRestrictionsData[]
>,
action: TIPRestrictionsMethodEnum.ADD,
action: isUpdating
? TIPRestrictionsMethodEnum.REPLACE
: TIPRestrictionsMethodEnum.ADD,
});
};

Expand All @@ -70,7 +73,10 @@ const Buttons = () => {
<button
className="button-datagrid-form cursor-pointer border-[--ods-color-blue-200] border-solid border pt-3 bg-white rounded"
data-testid="remove-draft-button"
onClick={removeDraftRow}
onClick={() => {
removeDraftRow();
resetForm();
}}
type={ODS_BUTTON_TYPE.reset}
>
<OsdsIcon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import {
Notifications,
useNotifications,
} from '@ovh-ux/manager-react-components';

import { useEffect } from 'react';
import { useMutationState } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';

import * as z from 'zod';
import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
import { useFormContext } from 'react-hook-form';
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import {
ODS_MESSAGE_TYPE,
ODS_SPINNER_SIZE,
Expand All @@ -25,13 +25,84 @@ import { useDatagridColumn } from '@/pages/CIDR/useDatagridColumn';
import Filters from '@/components/CIDR/Filters.component';
import { getRegistryQueyPrefixWithId } from '@/api/hooks/useIpRestrictions';
import useDataGridContext from '@/pages/CIDR/useDatagridContext';
import { FilterRestrictionsEnum } from '@/types';

const schemaAddCidr = (dataCIDR: string[], isUpdating: boolean) =>
z.object({
description: z.string().optional(),
ipBlock: z
.string()
.trim()
.transform((value) => {
try {
z.string()
.cidr()
.parse(value);
} catch (err) {
return `${value}/32`;
}
return value;
})
.superRefine((value, ctx) => {
try {
z.string()
.cidr()
.parse(value);
} catch (err) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'private_registry_cidr_validation_ipBlock',
});
}
if (!isUpdating) {
// verify duplication cidr
const existingIpBlocks = dataCIDR.map((item) => item);
if (existingIpBlocks.includes(value)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'private_registry_cidr_already_exist',
});
}
}
}),
authorization: z
.array(
z.enum([
FilterRestrictionsEnum.MANAGEMENT,
FilterRestrictionsEnum.REGISTRY,
]),
)
.default([])
.refine((auth) => auth.length > 0, {
message: 'private_registry_cidr_validation_authorization',
}),
});

export type ConfirmCIDRSchemaType = z.infer<ReturnType<typeof schemaAddCidr>>;

export default function CIDR() {
const { t } = useTranslation(['ip-restrictions', 'common']);
const { projectId = '', registryId = '' } = useParams();
const { formState, reset } = useFormContext();
const {
pagination,
setPagination,
rows,
totalRows,
isUpdating,
} = useDataGridContext();
const methods = useForm<ConfirmCIDRSchemaType>({
resolver: zodResolver(
schemaAddCidr(
rows.map((e) => e.ipBlock),
isUpdating,
),
),
mode: 'onBlur',
reValidateMode: 'onChange',
});

const columns = useDatagridColumn();
const { pagination, setPagination, rows, totalRows } = useDataGridContext();

const { clearNotifications } = useNotifications();

const variablesPending = useMutationState({
Expand All @@ -55,7 +126,7 @@ export default function CIDR() {
useUpdateIpRestrictionVariables.length - 1
].status === 'success'
) {
reset();
methods.reset();
}
}, [useUpdateIpRestrictionVariables]);

Expand All @@ -72,7 +143,7 @@ export default function CIDR() {
}

return (
<>
<FormProvider {...methods}>
{!rows.length && (
<OsdsMessage
color={ODS_THEME_COLOR_INTENT.info}
Expand All @@ -90,7 +161,7 @@ export default function CIDR() {
</OsdsText>
</OsdsMessage>
)}
{Object.values(formState.errors)?.map((err) => (
{Object.values(methods.formState.errors)?.map((err) => (
<OsdsMessage
color={ODS_THEME_COLOR_INTENT.error}
type={ODS_MESSAGE_TYPE.error}
Expand Down Expand Up @@ -123,6 +194,6 @@ export default function CIDR() {
</div>
</>
</div>
</>
</FormProvider>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ import {

import { OsdsInput } from '@ovhcloud/ods-components/react';
import { Controller, useFormContext } from 'react-hook-form';
import useDataGridContext from '@/pages/CIDR/useDatagridContext';

const IpBlock = () => {
const { formState, control } = useFormContext();
const { isUpdating } = useDataGridContext();

return (
<Controller
name="ipBlock"
control={control}
render={({ field: { onChange, value } }) => (
<OsdsInput
disabled={isUpdating || undefined}
placeholder="ex: 192.168.1.1/32"
color={ODS_TEXT_COLOR_INTENT.primary}
type={ODS_INPUT_TYPE.text}
Expand Down
Loading

0 comments on commit 7b6ced4

Please sign in to comment.