diff --git a/packages/manager/apps/key-management-service/package.json b/packages/manager/apps/key-management-service/package.json index 1ea8542277d4..2f5003e0113c 100644 --- a/packages/manager/apps/key-management-service/package.json +++ b/packages/manager/apps/key-management-service/package.json @@ -53,9 +53,12 @@ "@playwright/test": "^1.34.3", "@tanstack/react-query-devtools": "^5.51.21", "@testing-library/jest-dom": "^6.4.6", - "@testing-library/react": "^14.1.2", + "@testing-library/react": "^16.0.0", + "@testing-library/user-event": "^14.5.2", "@vitejs/plugin-react": "^4.2.1", - "typescript": "^4.3.2", + "element-internals-polyfill": "^1.3.11", + "msw": "2.1.7", + "typescript": "^5.1.6", "vite": "^5.2.13", "vitest": "^1.2.0" }, diff --git a/packages/manager/apps/key-management-service/public/translations/key-management-service/serviceKeys/Messages_en_GB.json b/packages/manager/apps/key-management-service/public/translations/key-management-service/serviceKeys/Messages_en_GB.json index f81884cff988..404db8435e93 100644 --- a/packages/manager/apps/key-management-service/public/translations/key-management-service/serviceKeys/Messages_en_GB.json +++ b/packages/manager/apps/key-management-service/public/translations/key-management-service/serviceKeys/Messages_en_GB.json @@ -32,8 +32,8 @@ "key_management_service_service-keys_dashboard_field_operations_decrypt": "Decrypt", "key_management_service_service-keys_dashboard_field_operations_sign": "Signature", "key_management_service_service-keys_dashboard_field_operations_verify": "Verification", - "key_management_service_service-keys_dashboard_field_operations_wrapKey": "Encapsulation", - "key_management_service_service-keys_dashboard_field_operations_unwrapKey": "Decapsulation", + "key_management_service_service-keys_dashboard_field_operations_wrapKey": "Wrap", + "key_management_service_service-keys_dashboard_field_operations_unwrapKey": "Unwrap", "key_management_service_service-keys_update_name_title": "Rename", "key_management_service_service-keys_update_name_error_required": "The name must be between 1 and 32 characters long.", "key_management_service_service-keys_update_name_error_max": "The maximum number of characters has been exceeded. (32 characters maximum)", @@ -52,7 +52,7 @@ "key_management_service_service-keys_create_crypto_field_type_title": "Key type", "key_management_service_service-keys_create_crypto_field_type_subtitle": "Specify the type of algorithm used for the encryption key.", "key_management_service_service-keys_create_crypto_field_type_description_rsa": "Asymmetric key for signature and verification.", - "key_management_service_service-keys_create_crypto_field_type_description_oct": "Symmetric key for encryption/decryption and encapsulation.", + "key_management_service_service-keys_create_crypto_field_type_description_oct": "Symmetric key for encryption/decryption and wrapping.", "key_management_service_service-keys_create_crypto_field_type_description_ec": "Elliptical asymmetric key for signature and verification.", "key_management_service_service-keys_create_crypto_field_size_title": "Key size (algorithm)", "key_management_service_service-keys_create_crypto_field_size_subtitle": "Select the encryption key size.", diff --git a/packages/manager/apps/key-management-service/src/components/Listing/ListingCells.tsx b/packages/manager/apps/key-management-service/src/components/Listing/ListingCells.tsx index 30fbfa380778..8f6f41e26423 100644 --- a/packages/manager/apps/key-management-service/src/components/Listing/ListingCells.tsx +++ b/packages/manager/apps/key-management-service/src/components/Listing/ListingCells.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { ActionMenu, - DataGridClipboardCell, + Clipboard, DataGridTextCell, Links, } from '@ovh-ux/manager-react-components'; @@ -10,7 +10,7 @@ import { PageLocation, useOvhTracking, } from '@ovh-ux/manager-react-shell-client'; -import { useNavigate, useParams } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { ODS_ICON_NAME, ODS_SPINNER_SIZE } from '@ovhcloud/ods-components'; import { OsdsSpinner } from '@ovhcloud/ods-components/react'; @@ -24,9 +24,10 @@ import { useOkmsServiceKeyById } from '@/data/hooks/useOkmsServiceKeys'; import { useFormattedDate } from '@/hooks/useFormattedDate'; import { useKMSServiceInfos } from '@/data/hooks/useKMSServiceInfos'; import { OkmsServiceState } from '../layout-helpers/Dashboard/okmsServiceState/OkmsServiceState.component'; +import { OkmsContext } from '@/pages/dashboard'; export const DatagridCellId = (props: OKMS | OkmsAllServiceKeys) => { - return ; + return ; }; export const DatagridCellName = (props: OKMS) => { @@ -62,7 +63,7 @@ export const DatagridCellRegion = (props: OKMS) => { export const DatagridCellStatus = (props: OKMS) => { const { data: OkmsServiceInfos, isLoading, isError } = useKMSServiceInfos( - props, + props.id, ); if (isLoading) { return ; @@ -97,7 +98,7 @@ export const DatagridServiceKeyCellName = (props: OkmsAllServiceKeys) => { }; export const DatagridServiceKeyCellId = (props: OkmsAllServiceKeys) => { - return ; + return ; }; export const DatagridCellType = (props: OkmsAllServiceKeys) => { @@ -129,12 +130,12 @@ export const DatagridStatus = (props: OkmsAllServiceKeys) => { }; export const DatagridServiceKeyActionMenu = (props: OkmsAllServiceKeys) => { - const { okmsId } = useParams(); + const okms = useContext(OkmsContext); const { data: serviceKey, isPending } = useOkmsServiceKeyById({ - okmsId, + okmsId: okms.id, keyId: props.id, }); - const actionList = useServiceKeyActionsList(okmsId, serviceKey?.data, true); + const actionList = useServiceKeyActionsList(okms, serviceKey?.data, true); if (isPending) { return <>; diff --git a/packages/manager/apps/key-management-service/src/components/Modal/EditNameModal.tsx b/packages/manager/apps/key-management-service/src/components/Modal/EditNameModal.tsx deleted file mode 100644 index d3172e440f6a..000000000000 --- a/packages/manager/apps/key-management-service/src/components/Modal/EditNameModal.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { - OsdsButton, - OsdsInput, - OsdsText, -} from '@ovhcloud/ods-components/react'; -import { - ODS_INPUT_TYPE, - ODS_TEXT_LEVEL, - ODS_TEXT_SIZE, - OsdsInputCustomEvent, - OdsInputValueChangeEventDetail, - ODS_BUTTON_VARIANT, -} from '@ovhcloud/ods-components'; - -import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; -import React, { useState } from 'react'; - -import { useTranslation } from 'react-i18next'; -import { isValidOkmsName } from '@/utils'; -import Modal from './Modal'; -import { OKMS } from '@/types/okms.type'; - -interface DeleteModalProps { - okms: OKMS; - toggleModal: (showModal: boolean) => void; - onEditName: (okms: OKMS) => void; -} - -const EditNameModal = ({ okms, toggleModal, onEditName }: DeleteModalProps) => { - const { t } = useTranslation('key-management-service/dashboard'); - const [newName, setNewName] = useState(okms.iam?.displayName || ''); - const isValidName = isValidOkmsName(newName); - const isButtonValid = okms.iam?.displayName !== newName && isValidName; - - const onEdit = () => { - if (isButtonValid) { - onEditName({ - ...okms, - iam: { - ...okms.iam, - displayName: newName, - }, - } as OKMS); - toggleModal(false); - } - }; - return ( - toggleModal(false)} - > - - {t('key_management_service_dashboard_modal_title')} - - , - ) => setNewName(e.target.value as string)} - /> - { - toggleModal(false); - }} - > - {t('key_management_service_dashboard_modal_cta_cancel')} - - - {t('key_management_service_dashboard_modal_cta_edit')} - - - ); -}; - -export default EditNameModal; diff --git a/packages/manager/apps/key-management-service/src/components/layout-helpers/Dashboard/GeneralInformationsTiles/InformationsTile.tsx b/packages/manager/apps/key-management-service/src/components/layout-helpers/Dashboard/GeneralInformationsTiles/InformationsTile.tsx index 96bf74d1202a..820f75030242 100644 --- a/packages/manager/apps/key-management-service/src/components/layout-helpers/Dashboard/GeneralInformationsTiles/InformationsTile.tsx +++ b/packages/manager/apps/key-management-service/src/components/layout-helpers/Dashboard/GeneralInformationsTiles/InformationsTile.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { OsdsButton, OsdsIcon, OsdsLink } from '@ovhcloud/ods-components/react'; import { Clipboard } from '@ovh-ux/manager-react-components'; import { @@ -15,48 +15,41 @@ import { import { OdsHTMLAnchorElementTarget } from '@ovhcloud/ods-common-core'; import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; import { useTranslation } from 'react-i18next'; +import { Outlet, useNavigate } from 'react-router-dom'; import { OKMS } from '@/types/okms.type'; -import EditNameModal from '@/components/Modal/EditNameModal'; -import { useUpdateOkmsName } from '@/data/hooks/useUpdateOkmsName'; import { Tile } from '@/components/dashboard/tile/tile.component'; import { TileSeparator } from '@/components/dashboard/tile-separator/tileSeparator'; import { TileValue } from '@/components/dashboard/tile-value/tileValue.component'; import { TileItem } from '@/components/dashboard/tile-item/tileItem.component'; +import { KMSServiceInfos } from '@/types/okmsService.type'; type InformationTileProps = { okmsData?: OKMS; + okmsServiceInfos?: KMSServiceInfos; }; -const InformationsTile = ({ okmsData }: InformationTileProps) => { +const InformationsTile = ({ + okmsData, + okmsServiceInfos, +}: InformationTileProps) => { const { t } = useTranslation('key-management-service/dashboard'); const { trackClick } = useOvhTracking(); - const [editModalDisplayed, setEditModalDisplayed] = useState(false); - const { updateKmsName } = useUpdateOkmsName({}); + const navigate = useNavigate(); return ( - {editModalDisplayed && ( - - updateKmsName({ okms: okms.id, displayName: okms.iam.displayName }) - } - /> - )}
- + setEditModalDisplayed(true)} + onClick={() => navigate('update-name')} > setEditModalDisplayed(true)} name={ODS_ICON_NAME.PEN} size={ODS_ICON_SIZE.xs} color={ODS_THEME_COLOR_INTENT.primary} @@ -113,6 +106,7 @@ const InformationsTile = ({ okmsData }: InformationTileProps) => { {okmsData?.swaggerEndpoint} + ); }; diff --git a/packages/manager/apps/key-management-service/src/components/serviceKey/serviceKeyStateActions/ServiceKeyStateActions.component.tsx b/packages/manager/apps/key-management-service/src/components/serviceKey/serviceKeyStateActions/ServiceKeyStateActions.component.tsx index f12ca98cb561..8fd659428096 100644 --- a/packages/manager/apps/key-management-service/src/components/serviceKey/serviceKeyStateActions/ServiceKeyStateActions.component.tsx +++ b/packages/manager/apps/key-management-service/src/components/serviceKey/serviceKeyStateActions/ServiceKeyStateActions.component.tsx @@ -1,5 +1,6 @@ -import { OsdsIcon, OsdsButton } from '@ovhcloud/ods-components/react'; import React from 'react'; +import { OsdsIcon, OsdsButton } from '@ovhcloud/ods-components/react'; +import { ManagerButton } from '@ovh-ux/manager-react-components'; import { ODS_BUTTON_SIZE, ODS_BUTTON_TEXT_ALIGN, @@ -9,9 +10,10 @@ import { } from '@ovhcloud/ods-components'; import { OkmsAllServiceKeys } from '@/types/okmsServiceKey.type'; import useServiceKeyActionsList from '@/hooks/serviceKey/useServiceKeyActionsList'; +import { OKMS } from '@/types/okms.type'; type ServiceKeyStateActionsProps = { - okmsId: string; + okms: OKMS; okmsKey: OkmsAllServiceKeys; }; @@ -23,38 +25,62 @@ const ActionsIcons = [ ]; const ServiceKeyStateActions = ({ - okmsId, + okms, okmsKey, }: ServiceKeyStateActionsProps) => { - const actionList = useServiceKeyActionsList(okmsId, okmsKey); + const actionList = useServiceKeyActionsList(okms, okmsKey); const getActionIcon = (id: number) => { return ActionsIcons.find((actionIcon) => actionIcon.id === id)?.icon; }; return ( <> - {actionList.map((action) => ( - - {action.label} - - - - - ))} + {actionList.map((action) => { + return action.iamActions ? ( + + {action.label} + + + + + ) : ( + + {action.label} + + + + + ); + })} ); }; diff --git a/packages/manager/apps/key-management-service/src/data/api/okms.ts b/packages/manager/apps/key-management-service/src/data/api/okms.ts index caa5e0f830f9..f5f9865186f8 100644 --- a/packages/manager/apps/key-management-service/src/data/api/okms.ts +++ b/packages/manager/apps/key-management-service/src/data/api/okms.ts @@ -92,20 +92,6 @@ export const getListingIcebergV2 = async ({ } }; -/** - * Get okms listing with iceberg V2 - */ -export const getListingIceberg = async () => { - try { - const List = await fetchIcebergV2({ - route: '/okms/resource', - }); - return List.data as OKMS[]; - } catch (error) { - return null; - } -}; - export const getOkmsResourceQueryKey = (okmsId: string) => [ `get/okms/resource/${okmsId}`, ]; diff --git a/packages/manager/apps/key-management-service/src/data/api/okmsService.ts b/packages/manager/apps/key-management-service/src/data/api/okmsService.ts index 8f60cff29731..de066437a23e 100644 --- a/packages/manager/apps/key-management-service/src/data/api/okmsService.ts +++ b/packages/manager/apps/key-management-service/src/data/api/okmsService.ts @@ -16,24 +16,20 @@ export const updateOkmsName = async ({ displayName, }); -export type GetOkmsServiceIdParams = { - /** Filter on a specific service family */ - okms: string; -}; - -export const getOkmsServiceIdQueryKey = ({ - okms = '', -}: GetOkmsServiceIdParams) => [`get/services${okms}`]; +export const getOkmsServiceIdQueryKey = (okmsId: string) => [ + `get/okms/services`, + okmsId, +]; /** * allowedServices operations : List all services allowed in this kms */ -export const getOkmsServiceId = async ({ okms }: GetOkmsServiceIdParams) => { - const resourceName = okms ? `?resourceName=${okms}` : ''; +export const getOkmsServiceId = async (okmsId: string) => { + const resourceName = okmsId ? `?resourceName=${okmsId}` : ''; return apiClient.v6.get(`/services${resourceName}`); }; -export const getServiceInfos = async ({ okms }: GetOkmsServiceIdParams) => { - const serviceId = await getOkmsServiceId({ okms }); +export const getServiceInfos = async (okmsId: string) => { + const serviceId = await getOkmsServiceId(okmsId); return apiClient.v6.get(`/services/${serviceId.data[0]}`); }; diff --git a/packages/manager/apps/key-management-service/src/data/hooks/useKMSServiceInfos.ts b/packages/manager/apps/key-management-service/src/data/hooks/useKMSServiceInfos.ts index acc26a7515bf..c637da680dd6 100644 --- a/packages/manager/apps/key-management-service/src/data/hooks/useKMSServiceInfos.ts +++ b/packages/manager/apps/key-management-service/src/data/hooks/useKMSServiceInfos.ts @@ -1,16 +1,16 @@ import { useQuery } from '@tanstack/react-query'; -import { OKMS } from '@/types/okms.type'; import { ErrorResponse } from '@/types/api.type'; import { KMSServiceInfos } from '@/types/okmsService.type'; import { getServiceInfos } from '../api/okmsService'; -export const useKMSServiceInfos = (okms?: OKMS) => { +export const getKMSServiceInfosQueryKey = (okmsId: string) => [ + 'okms/service/infos', + okmsId, +]; +export const useKMSServiceInfos = (okmId?: string) => { return useQuery<{ data: KMSServiceInfos }, ErrorResponse>({ - queryKey: ['okms/service/infos', okms?.id], - queryFn: () => getServiceInfos({ okms: okms?.id }), + queryKey: getKMSServiceInfosQueryKey(okmId), + queryFn: () => getServiceInfos(okmId), retry: false, - ...{ - keepPreviousData: true, - }, }); }; diff --git a/packages/manager/apps/key-management-service/src/data/hooks/useOKMS.ts b/packages/manager/apps/key-management-service/src/data/hooks/useOKMS.ts index 2a326e9bf6de..6e254e0ed428 100644 --- a/packages/manager/apps/key-management-service/src/data/hooks/useOKMS.ts +++ b/packages/manager/apps/key-management-service/src/data/hooks/useOKMS.ts @@ -1,13 +1,12 @@ import { useQuery } from '@tanstack/react-query'; import apiClient from '@ovh-ux/manager-core-api'; +import { useResourcesIcebergV2 } from '@ovh-ux/manager-react-components'; -import { OKMS, OKMSOptions } from '@/types/okms.type'; +import { OKMS } from '@/types/okms.type'; import { ErrorResponse } from '@/types/api.type'; import { - getListingIceberg, getOkmsResourceQueryKey, getOkmsServicesResourceListQueryKey, - sortOKMS, } from '../api/okms'; export const getOKMSResource = async ( @@ -16,18 +15,6 @@ export const getOKMSResource = async ( return apiClient.v2.get(`okms/resource/${okmsId}`); }; -export const useAllOKMS = () => { - return useQuery({ - queryKey: getOkmsServicesResourceListQueryKey, - queryFn: () => getListingIceberg(), - retry: false, - ...{ - keepPreviousData: true, - }, - refetchInterval: 5000, - }); -}; - export const useOKMSById = (okmsId: string) => { return useQuery<{ data: OKMS }, ErrorResponse>({ queryKey: getOkmsResourceQueryKey(okmsId), @@ -36,21 +23,12 @@ export const useOKMSById = (okmsId: string) => { ...{ keepPreviousData: true, }, - refetchInterval: 5000, }); }; -export const useOKMS = ({ sorting }: OKMSOptions) => { - // retrieve All OKMS from API - const { - data: okms, - error: allOKMSError, - isLoading: allOKMSLoading, - } = useAllOKMS(); - - return { - isLoading: allOKMSLoading, - error: allOKMSError, - data: sortOKMS(okms || [], sorting), - }; -}; +export const useOKMSList = ({ pageSize }: { pageSize?: number }) => + useResourcesIcebergV2({ + route: '/okms/resource', + queryKey: getOkmsServicesResourceListQueryKey, + pageSize, + }); diff --git a/packages/manager/apps/key-management-service/src/data/hooks/useOkmsServiceKeys.ts b/packages/manager/apps/key-management-service/src/data/hooks/useOkmsServiceKeys.ts index b4f06145fce3..814af9dba6fd 100644 --- a/packages/manager/apps/key-management-service/src/data/hooks/useOkmsServiceKeys.ts +++ b/packages/manager/apps/key-management-service/src/data/hooks/useOkmsServiceKeys.ts @@ -1,5 +1,6 @@ import { useQuery } from '@tanstack/react-query'; +import { ApiError } from '@ovh-ux/manager-core-api'; import { ErrorResponse } from '@/types/api.type'; import { getOkmsServiceKeyResource, @@ -16,7 +17,7 @@ import { /* Service Key List */ export const useAllOkmsServiceKeys = (okmsId: string) => { - return useQuery({ + return useQuery<{ data: OkmsAllServiceKeys[] }, ApiError>({ queryKey: getOkmsServiceKeyResourceListQueryKey(okmsId), queryFn: () => getListingOkmsServiceKey(okmsId), retry: false, diff --git a/packages/manager/apps/key-management-service/src/data/hooks/useTerminateOKms.ts b/packages/manager/apps/key-management-service/src/data/hooks/useTerminateOKms.ts index d45109ddbc96..a862377f09d9 100644 --- a/packages/manager/apps/key-management-service/src/data/hooks/useTerminateOKms.ts +++ b/packages/manager/apps/key-management-service/src/data/hooks/useTerminateOKms.ts @@ -29,8 +29,8 @@ export const useTerminateOKms = ({ const { data: servicesId } = await queryClient.fetchQuery< ApiResponse >({ - queryKey: getOkmsServiceIdQueryKey({ okms: okmsId }), - queryFn: () => getOkmsServiceId({ okms: okmsId }), + queryKey: getOkmsServiceIdQueryKey(okmsId), + queryFn: () => getOkmsServiceId(okmsId), }); return terminateOKms({ serviceId: servicesId[0] }); }, diff --git a/packages/manager/apps/key-management-service/src/data/hooks/useUpdateOkmsName.ts b/packages/manager/apps/key-management-service/src/data/hooks/useUpdateOkmsName.ts index e153e4996f2c..8a7471075431 100644 --- a/packages/manager/apps/key-management-service/src/data/hooks/useUpdateOkmsName.ts +++ b/packages/manager/apps/key-management-service/src/data/hooks/useUpdateOkmsName.ts @@ -1,14 +1,24 @@ import { ApiError, ApiResponse } from '@ovh-ux/manager-core-api'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import React from 'react'; -import { getOkmsServicesResourceListQueryKey } from '../api/okms'; +import { useTranslation } from 'react-i18next'; +import { useNotifications } from '@ovh-ux/manager-react-components'; +import { + getOkmsResourceQueryKey, + getOkmsServicesResourceListQueryKey, +} from '../api/okms'; import { updateOkmsNameQueryKey, getOkmsServiceIdQueryKey, getOkmsServiceId, updateOkmsName, } from '../api/okmsService'; +import { getKMSServiceInfosQueryKey } from './useKMSServiceInfos'; +export type UpdateOkmsParams = { + okmsId: string; + onSuccess: () => void; + onError?: (result: ApiError) => void; +}; export type UpdateOkmsNameMutationParams = { /** Okms service id */ okms: string; @@ -20,14 +30,13 @@ export type UpdateOkmsNameMutationParams = { * Get the function to mutate a okms Services */ export const useUpdateOkmsName = ({ + okmsId, onSuccess, onError, -}: { - onSuccess?: () => void; - onError?: (result: ApiError) => void; -}) => { - const [isErrorVisible, setIsErrorVisible] = React.useState(false); +}: UpdateOkmsParams) => { const queryClient = useQueryClient(); + const { t } = useTranslation('key-management-service/serviceKeys'); + const { addError, addSuccess, clearNotifications } = useNotifications(); const { mutate: updateKmsName, @@ -39,19 +48,36 @@ export const useUpdateOkmsName = ({ const { data: servicesId } = await queryClient.fetchQuery< ApiResponse >({ - queryKey: getOkmsServiceIdQueryKey({ okms }), - queryFn: () => getOkmsServiceId({ okms }), + queryKey: getOkmsServiceIdQueryKey(okms), + queryFn: () => getOkmsServiceId(okms), }); return updateOkmsName({ serviceId: servicesId[0], displayName }); }, - onSuccess: () => { - queryClient.invalidateQueries({ + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: getOkmsServicesResourceListQueryKey, }); + await queryClient.invalidateQueries({ + queryKey: getKMSServiceInfosQueryKey(okmsId), + }); + await queryClient.invalidateQueries({ + queryKey: getOkmsResourceQueryKey(okmsId), + }); + clearNotifications(); + addSuccess( + t('key_management_service_service-keys_update_name_success'), + true, + ); onSuccess?.(); }, onError: (result: ApiError) => { - setIsErrorVisible(true); + clearNotifications(); + addError( + t('key_management_service_service-keys_update_error', { + error: result.message, + }), + true, + ); onError?.(result); }, }); @@ -59,8 +85,6 @@ export const useUpdateOkmsName = ({ return { updateKmsName, isPending, - isErrorVisible: updateNameError && isErrorVisible, error: updateNameError, - hideError: () => setIsErrorVisible(false), }; }; diff --git a/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyActions.spec.tsx b/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyActions.spec.tsx index 2664b2ab4b64..ad674f1fcf08 100644 --- a/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyActions.spec.tsx +++ b/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyActions.spec.tsx @@ -1,8 +1,13 @@ import { describe, expect, it, vi } from 'vitest'; import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; import { renderHook } from '@testing-library/react'; -import { OkmsKeyTypes, OkmsServiceKeyState } from '@/types/okmsServiceKey.type'; +import { + OkmsAllServiceKeys, + OkmsKeyTypes, + OkmsServiceKeyState, +} from '@/types/okmsServiceKey.type'; import useServiceKeyActionsList from './useServiceKeyActionsList'; +import { OKMS } from '@/types/okms.type'; vi.mock('react-i18next', () => ({ useTranslation: vi.fn(() => ({ t: vi.fn((key) => key) })), @@ -31,13 +36,25 @@ vi.mock('@/data/hooks/useUpdateOkmsServiceKey', () => ({ })); describe('useServiceKeyActionsList', () => { - const okmsId = 'testOkmsId'; - const commonKeyProps = { + const okms: OKMS = { + iam: { + displayName: 'kms-1', + id: '1b4e7c8e-d1b8-4b46-a584-52c8b4b0225c', + urn: `urn:v1:eu:resource:okms:1b4e7c8e-d1b8-4b46-a584-52c8b4b0225c`, + }, + id: '7f3a82ac-a8d8-4c2a-ab0c-f6e86ddf6a7c', + kmipEndpoint: 'eu-west-rbx.okms.ovh.net:1234', + region: 'EU_WEST_RBX', + restEndpoint: 'https://eu-west-rbx.okms.ovh.net', + swaggerEndpoint: '"https://swagger-eu-west-rbx.okms.ovh.net', + }; + + const commonKeyProps: Omit = { id: 'testKeyId', name: 'testKeyName', - keys: [] as any[], + keys: [], createdAt: '2023-01-01T00:00:00Z', - operations: [] as any[], + operations: [], }; const useCases = [ @@ -171,7 +188,7 @@ describe('useServiceKeyActionsList', () => { useCases.forEach(({ description, okmsKey, expectedActions }) => { it(description, () => { const { result } = renderHook(() => - useServiceKeyActionsList(okmsId, okmsKey), + useServiceKeyActionsList(okms, okmsKey), ); expect(result.current).toEqual( expect.arrayContaining( diff --git a/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyActionsList.tsx b/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyActionsList.tsx index 2f696ab258ee..03b7d9cd568e 100644 --- a/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyActionsList.tsx +++ b/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyActionsList.tsx @@ -13,9 +13,10 @@ import { OkmsKeyTypes, OkmsServiceKeyState, } from '@/types/okmsServiceKey.type'; +import { OKMS } from '@/types/okms.type'; const useServiceKeyActionsList = ( - okmsId: string, + okms: OKMS, okmsKey?: OkmsAllServiceKeys, isListMode?: boolean, ) => { @@ -26,10 +27,10 @@ const useServiceKeyActionsList = ( deleteKmsServiceKey, isPending: deleteIsPending, } = useDeleteOkmsServiceKey({ - okmsId, + okmsId: okms.id, keyId: okmsKey?.id, onSuccess: () => { - navigate(`/${okmsId}/${ROUTES_URLS.keys}`); + navigate(`/${okms.id}/${ROUTES_URLS.keys}`); }, onError: () => {}, }); @@ -38,7 +39,7 @@ const useServiceKeyActionsList = ( updateKmsServiceKey, isPending: updateIsPending, } = useUpdateOkmsServiceKey({ - okmsId, + okmsId: okms.id, keyId: okmsKey?.id, onSuccess: () => { addSuccess( @@ -72,9 +73,11 @@ const useServiceKeyActionsList = ( return isListMode ? navigate(`${ROUTES_URLS.serviceKeyDeactivate}/${okmsKey?.id}`) : navigate( - `/${okmsId}/${ROUTES_URLS.keys}/${okmsKey?.id}/${ROUTES_URLS.serviceKeyDeactivate}`, + `/${okms.id}/${ROUTES_URLS.keys}/${okmsKey?.id}/${ROUTES_URLS.serviceKeyDeactivate}`, ); }, + iamActions: ['okms:apiovh:resource/serviceKey/deactivate'], + urn: okms.iam.urn, }); } if ( @@ -87,6 +90,8 @@ const useServiceKeyActionsList = ( color: ODS_THEME_COLOR_INTENT.primary, disabled: updateIsPending, onClick: () => updateKmsServiceKey({ state: OkmsServiceKeyState.active }), + iamActions: ['okms:apiovh:resource/serviceKey/activate'], + urn: okms.iam.urn, }); } if ( @@ -100,6 +105,8 @@ const useServiceKeyActionsList = ( disabled: okmsKey?.state === OkmsServiceKeyState.active || deleteIsPending, onClick: () => deleteKmsServiceKey(), + iamActions: ['okms:apiovh:resource/serviceKey/delete'], + urn: okms.iam.urn, }); } return items; diff --git a/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyOperationsTranslations.spec.tsx b/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyOperationsTranslations.spec.tsx index c5a30fe9462c..13b98ec1e7d9 100644 --- a/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyOperationsTranslations.spec.tsx +++ b/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyOperationsTranslations.spec.tsx @@ -1,7 +1,16 @@ -import { describe, expect, it, test } from 'vitest'; +import { describe, expect, it, test, vi } from 'vitest'; import { OkmsServiceKeyOperations } from '@/types/okmsServiceKey.type'; import { useServiceKeyOperationsTranslations } from './useServiceKeyOperationsTranslations'; +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (translationKey: string) => translationKey, + i18n: { + changeLanguage: () => new Promise(() => {}), + }, + }), +})); + describe('get service key operations translation ', () => { const useCases: { operation: OkmsServiceKeyOperations; diff --git a/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyTypeTranslations.spec.tsx b/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyTypeTranslations.spec.tsx index 1c08795cd6b4..a916f9533012 100644 --- a/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyTypeTranslations.spec.tsx +++ b/packages/manager/apps/key-management-service/src/hooks/serviceKey/useServiceKeyTypeTranslations.spec.tsx @@ -1,7 +1,16 @@ -import { describe, expect, it, test } from 'vitest'; +import { describe, expect, it, test, vi } from 'vitest'; import { OkmsKeyTypes } from '@/types/okmsServiceKey.type'; import { useServiceKeyTypeTranslations } from './useServiceKeyTypeTranslations'; +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (translationKey: string) => translationKey, + i18n: { + changeLanguage: () => new Promise(() => {}), + }, + }), +})); + describe('get service key type translation ', () => { const useCases: { type: OkmsKeyTypes; diff --git a/packages/manager/apps/key-management-service/src/mocks/okms.mock.ts b/packages/manager/apps/key-management-service/src/mocks/okms.mock.ts new file mode 100644 index 000000000000..c7a1103b4b51 --- /dev/null +++ b/packages/manager/apps/key-management-service/src/mocks/okms.mock.ts @@ -0,0 +1,42 @@ +import { PathParams } from 'msw'; +import { Handler } from '../../../../../../playwright-helpers'; +import { OKMS } from '@/types/okms.type'; + +export const okmsList: OKMS[] = [ + { + iam: { + displayName: 'kms-1', + id: '1b4e7c8e-d1b8-4b46-a584-52c8b4b0225c', + urn: `urn:v1:eu:resource:okms:1b4e7c8e-d1b8-4b46-a584-52c8b4b0225c`, + }, + id: '7f3a82ac-a8d8-4c2a-ab0c-f6e86ddf6a7c', + kmipEndpoint: 'eu-west-rbx.okms.ovh.net:1234', + region: 'EU_WEST_RBX', + restEndpoint: 'https://eu-west-rbx.okms.ovh.net', + swaggerEndpoint: '"https://swagger-eu-west-rbx.okms.ovh.net', + }, +]; + +export type GetOkmsMocksParams = { + nbOkms?: number; +}; + +const findOkmsById = (params: PathParams) => + okmsList.find(({ id }) => id === params.id); + +export const getOkmsMocks = ({ + nbOkms = okmsList.length, +}: GetOkmsMocksParams): Handler[] => [ + { + url: '/okms/resource/:id', + response: (_: unknown, params: PathParams) => findOkmsById(params), + status: 200, + api: 'v2', + }, + { + url: '/okms/resource', + response: okmsList.slice(0, nbOkms), + status: 200, + api: 'v2', + }, +]; diff --git a/packages/manager/apps/key-management-service/src/mocks/services.mock.ts b/packages/manager/apps/key-management-service/src/mocks/services.mock.ts new file mode 100644 index 000000000000..19fe0b3e8db6 --- /dev/null +++ b/packages/manager/apps/key-management-service/src/mocks/services.mock.ts @@ -0,0 +1,36 @@ +import { KMSServiceInfos, OkmsState } from '@/types/okmsService.type'; +import { Handler } from '../../../../../../playwright-helpers'; + +const serviceList: KMSServiceInfos[] = [ + { + billing: { + lifecycle: { current: { creationDate: '2024-04-12T18:00:00.000Z' } }, + nextBillingDate: '2024-05-12T18:00:00.000Z', + }, + customer: { contacts: [{ customerCode: 'code', type: 'type' }] }, + resource: { + displayName: 'name', + name: 'name', + product: { name: 'name', description: 'descripton' }, + resellingProvider: 'test', + state: OkmsState.Active, + }, + }, +]; + +export const getServicesMocks = (): Handler[] => [ + { + url: '/services/:id', + response: serviceList[0], + status: 200, + method: 'get', + api: 'v6', + }, + { + url: '/services', + response: () => [1234567890], + status: 200, + method: 'get', + api: 'v6', + }, +]; diff --git a/packages/manager/apps/key-management-service/src/pages/credential/create/CreateAddIdentities.component.tsx b/packages/manager/apps/key-management-service/src/pages/credential/create/CreateAddIdentities.component.tsx index 820c776c97b0..4de995d7eb00 100644 --- a/packages/manager/apps/key-management-service/src/pages/credential/create/CreateAddIdentities.component.tsx +++ b/packages/manager/apps/key-management-service/src/pages/credential/create/CreateAddIdentities.component.tsx @@ -83,9 +83,9 @@ const CreateAddIdentities = ({ )}
- - - + + + )} diff --git a/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedBase.component.tsx b/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedBase.component.tsx index 762edddd6777..9b7041ca026d 100644 --- a/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedBase.component.tsx +++ b/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedBase.component.tsx @@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next'; type IdentitiesSelectedBaseProps = { title: string; + identityURNs: string[]; addCallback: () => void; addButtonLabel: string; deleteCallback: () => void; @@ -20,6 +21,7 @@ type IdentitiesSelectedBaseProps = { const IdentitiesSelectedBase = ({ title, + identityURNs, addCallback, addButtonLabel, deleteCallback, @@ -36,6 +38,7 @@ const IdentitiesSelectedBase = ({ variant={ODS_BUTTON_VARIANT.stroked} size={ODS_BUTTON_SIZE.sm} color={ODS_THEME_COLOR_INTENT.primary} + disabled={identityURNs.length > 25 || undefined} onClick={addCallback} > {addButtonLabel} diff --git a/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedGroups.component.tsx b/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedGroups.component.tsx index 0d6180275fae..a46f7054dce2 100644 --- a/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedGroups.component.tsx +++ b/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedGroups.component.tsx @@ -10,7 +10,13 @@ import IdentityGroupNameCell from './cell/group/IdentityGroupNameCell.component' import IdentityGroupDescriptionCell from './cell/group/IdentityGroupDescriptionCell.component'; import IdentityGroupDeleteActionCell from './cell/group/IdentityGroupDeleteActionCell'; -const IdentitiesSelectedGroups = () => { +type IdentitiesSelectedGroupsProps = { + identityURNs: string[]; +}; + +const IdentitiesSelectedGroups = ({ + identityURNs, +}: IdentitiesSelectedGroupsProps) => { const { t } = useTranslation('key-management-service/credential'); const navigate = useNavigate(); const { groupList, setGroupList } = useIdentityData(); @@ -49,6 +55,7 @@ const IdentitiesSelectedGroups = () => { deleteCallback={() => setGroupList([])} datagridColumns={columns} items={groupList} + identityURNs={identityURNs} > ); }; diff --git a/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedServiceAccounts.component.tsx b/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedServiceAccounts.component.tsx index ace116814196..95f49d40c3f1 100644 --- a/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedServiceAccounts.component.tsx +++ b/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedServiceAccounts.component.tsx @@ -6,11 +6,17 @@ import { ROUTES_URLS } from '@/routes/routes.constants'; import { useIdentityData } from '@/hooks/credential/useIdentityData'; import { IdentityOauthClient } from '@/types/identity.type'; import IdentitiesSelectedBase from './IdentitiesSelectedBase.component'; -import IdentityServiceAccountDescriptionCell from './cell/service-account/IdentityServiceAccountDescriptionCell.component'; +import IdentityServiceAccountIdentityCell from './cell/service-account/IdentityServiceAccountIdentityCell.component'; import IdentityServiceAccountNameCell from './cell/service-account/IdentityServiceAccountNameCell.component'; import IdentityServiceAccountDeleteActionCell from './cell/service-account/IdentityServiceAccountDeleteActionCell'; -const IdentitiesSelectedServiceAccounts = () => { +type IdentitiesSelectedServiceAccountsProps = { + identityURNs: string[]; +}; + +const IdentitiesSelectedServiceAccounts = ({ + identityURNs, +}: IdentitiesSelectedServiceAccountsProps) => { const { t } = useTranslation('key-management-service/credential'); const navigate = useNavigate(); const { serviceAccountList, setServiceAccountList } = useIdentityData(); @@ -23,10 +29,10 @@ const IdentitiesSelectedServiceAccounts = () => { isSortable: false, }, { - id: 'description', - cell: IdentityServiceAccountDescriptionCell, + id: 'identity', + cell: IdentityServiceAccountIdentityCell, label: t( - 'key_management_service_credential_user_list_column_description', + 'key_management_service_credential_create_identities_service-account_tile_identity_label', ), isSortable: false, }, @@ -51,6 +57,7 @@ const IdentitiesSelectedServiceAccounts = () => { deleteCallback={() => setServiceAccountList([])} datagridColumns={columns} items={serviceAccountList} + identityURNs={identityURNs} > ); }; diff --git a/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedUsersList.component.tsx b/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedUsersList.component.tsx index 79e1b2a89943..17e532a3ec9a 100644 --- a/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedUsersList.component.tsx +++ b/packages/manager/apps/key-management-service/src/pages/credential/create/identities/IdentitiesSelectedUsersList.component.tsx @@ -11,7 +11,12 @@ import IdentityUserGroupCell from './cell/user/IdentityUserGroupCell.component'; import IdentityUserStatusCell from './cell/user/IdentityUserStatusCell.component'; import IdentityUserDeleteActionCell from './cell/user/IdentityUserDeleteActionCell'; -const IdentitiesSelectedUsersList = () => { +type IdentitiesSelectedUsersListProps = { + identityURNs: string[]; +}; +const IdentitiesSelectedUsersList = ({ + identityURNs, +}: IdentitiesSelectedUsersListProps) => { const { t } = useTranslation('key-management-service/credential'); const navigate = useNavigate(); const { userList, setUserList } = useIdentityData(); @@ -59,6 +64,7 @@ const IdentitiesSelectedUsersList = () => { }} datagridColumns={columns} items={userList} + identityURNs={identityURNs} > ); }; diff --git a/packages/manager/apps/key-management-service/src/pages/credential/create/identities/cell/service-account/IdentityServiceAccountDescriptionCell.component.tsx b/packages/manager/apps/key-management-service/src/pages/credential/create/identities/cell/service-account/IdentityServiceAccountIdentityCell.component.tsx similarity index 53% rename from packages/manager/apps/key-management-service/src/pages/credential/create/identities/cell/service-account/IdentityServiceAccountDescriptionCell.component.tsx rename to packages/manager/apps/key-management-service/src/pages/credential/create/identities/cell/service-account/IdentityServiceAccountIdentityCell.component.tsx index 14ccda07496b..9a4bb3401142 100644 --- a/packages/manager/apps/key-management-service/src/pages/credential/create/identities/cell/service-account/IdentityServiceAccountDescriptionCell.component.tsx +++ b/packages/manager/apps/key-management-service/src/pages/credential/create/identities/cell/service-account/IdentityServiceAccountIdentityCell.component.tsx @@ -2,10 +2,10 @@ import React from 'react'; import { DataGridTextCell } from '@ovh-ux/manager-react-components'; import { IdentityOauthClient } from '@/types/identity.type'; -const IdentityServiceAccountDescriptionCell = ( +const IdentityServiceAccountIdentityCell = ( serviceAccount: IdentityOauthClient, ) => { - return {serviceAccount.description}; + return {serviceAccount.identity}; }; -export default IdentityServiceAccountDescriptionCell; +export default IdentityServiceAccountIdentityCell; diff --git a/packages/manager/apps/key-management-service/src/pages/dashboard/generalInformations/GeneralInformations.tsx b/packages/manager/apps/key-management-service/src/pages/dashboard/generalInformations/GeneralInformations.tsx index 5e545b4ea5a8..f1f37b1b86e7 100644 --- a/packages/manager/apps/key-management-service/src/pages/dashboard/generalInformations/GeneralInformations.tsx +++ b/packages/manager/apps/key-management-service/src/pages/dashboard/generalInformations/GeneralInformations.tsx @@ -14,7 +14,7 @@ function GeneralInformationsTab() { const { data: okmsService, isLoading: isOkmsServiceLoading, - } = useKMSServiceInfos(okms?.data); + } = useKMSServiceInfos(okms?.data.id); const navigate = useNavigate(); @@ -32,7 +32,10 @@ function GeneralInformationsTab() { return (
- + { + const { t: tDashboard } = useTranslation('key-management-service/dashboard'); + const { t: tCredential } = useTranslation( + 'key-management-service/credential', + ); + const { okmsId } = useParams(); + const { data: okmsServiceInfos, isLoading } = useKMSServiceInfos(okmsId); + const navigate = useNavigate(); + const { updateKmsName, isPending, error } = useUpdateOkmsName({ + okmsId, + onSuccess: () => { + navigate('..'); + }, + }); + + return ( + { + navigate('..'); + }} + updateDisplayName={(newDisplayName: string) => { + updateKmsName({ okms: okmsId, displayName: newDisplayName }); + }} + > + ); +}; + +export default OkmsNameUpdateModal; diff --git a/packages/manager/apps/key-management-service/src/pages/dashboard/index.tsx b/packages/manager/apps/key-management-service/src/pages/dashboard/index.tsx index 862b081ac235..18a66a1155d4 100644 --- a/packages/manager/apps/key-management-service/src/pages/dashboard/index.tsx +++ b/packages/manager/apps/key-management-service/src/pages/dashboard/index.tsx @@ -4,8 +4,8 @@ import { Outlet, useNavigate, useParams } from 'react-router-dom'; import { Notifications, BaseLayout, - ErrorBanner, HeadersProps, + ErrorBanner, } from '@ovh-ux/manager-react-components'; import { queryClient } from '@ovh-ux/manager-react-core-application'; import KmsGuidesHeader from '@/components/Guide/KmsGuidesHeader'; @@ -13,12 +13,13 @@ import Dashboard, { DashboardTabItemProps, } from '@/components/layout-helpers/Dashboard/Dashboard'; import Loading from '@/components/Loading/Loading'; -import { useOKMSById } from '@/data/hooks/useOKMS'; import Breadcrumb from '@/components/Breadcrumb/Breadcrumb'; import { ROUTES_URLS } from '@/routes/routes.constants'; import { BreadcrumbItem } from '@/hooks/breadcrumb/useBreadcrumb'; import { getOkmsResourceQueryKey } from '@/data/api/okms'; import { OKMS } from '@/types/okms.type'; +import { useKMSServiceInfos } from '@/data/hooks/useKMSServiceInfos'; +import { useOKMSById } from '@/data/hooks/useOKMS'; export const OkmsContext = createContext(null); @@ -32,14 +33,27 @@ export default function DashboardPage() { 'key-management-service/credential', ); const { okmsId } = useParams(); - const { data: okms, isLoading, error } = useOKMSById(okmsId); + const { + data: okms, + isLoading: isOkmsLoading, + isError: isOkmsError, + error: okmsError, + } = useOKMSById(okmsId); - if (isLoading) return ; + const { + data: okmsServiceInfos, + isLoading: isOkmsServiceInfosLoading, + isError: isOkmsServiceInfosError, + error: okmsServiceInfoError, + } = useKMSServiceInfos(okmsId); + const displayName = okmsServiceInfos?.data?.resource.displayName; - if (error) + if (isOkmsServiceInfosLoading || isOkmsLoading) return ; + + if (isOkmsServiceInfosError || isOkmsError) return ( navigate(ROUTES_URLS.listing)} onReloadPage={() => queryClient.refetchQueries({ @@ -67,7 +81,7 @@ export default function DashboardPage() { const breadcrumbItems: BreadcrumbItem[] = [ { id: okmsId, - label: okms.data.iam.displayName, + label: displayName, navigateTo: `/${okmsId}`, }, { @@ -80,10 +94,15 @@ export default function DashboardPage() { label: tCredentials('key_management_service_credential'), navigateTo: `/${okmsId}/${ROUTES_URLS.credentials}`, }, + { + id: ROUTES_URLS.okmsUpdateName, + label: tDashboard('key_management_service_update_name'), + navigateTo: `/${okmsId}/${ROUTES_URLS.okmsUpdateName}`, + }, ]; const headerProps: HeadersProps = { - title: okms.data.iam.displayName, + title: displayName, headerButton: , }; diff --git a/packages/manager/apps/key-management-service/src/pages/dashboard/serviceKeyList/ServiceKeyList.page.tsx b/packages/manager/apps/key-management-service/src/pages/dashboard/serviceKeyList/ServiceKeyList.page.tsx index 5a660992a85b..ff69268c48c6 100644 --- a/packages/manager/apps/key-management-service/src/pages/dashboard/serviceKeyList/ServiceKeyList.page.tsx +++ b/packages/manager/apps/key-management-service/src/pages/dashboard/serviceKeyList/ServiceKeyList.page.tsx @@ -1,30 +1,23 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { Outlet, useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; +import { queryClient } from '@ovh-ux/manager-react-core-application'; import { - ODS_SPINNER_SIZE, ODS_MESSAGE_TYPE, ODS_BUTTON_SIZE, ODS_ICON_NAME, ODS_ICON_SIZE, ODS_BUTTON_VARIANT, } from '@ovhcloud/ods-components'; -import { - OsdsText, - OsdsMessage, - OsdsSpinner, - OsdsButton, - OsdsIcon, -} from '@ovhcloud/ods-components/react'; +import { OsdsMessage, OsdsIcon } from '@ovhcloud/ods-components/react'; import { Datagrid, + Description, + ErrorBanner, + ManagerButton, useDatagridSearchParams, } from '@ovh-ux/manager-react-components'; -import { - ODS_THEME_COLOR_INTENT, - ODS_THEME_TYPOGRAPHY_LEVEL, - ODS_THEME_TYPOGRAPHY_SIZE, -} from '@ovhcloud/ods-common-theming'; +import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; import { DatagridCellType, DatagridCreationDate, @@ -35,6 +28,9 @@ import { } from '@/components/Listing/ListingCells'; import { useOkmsServiceKeys } from '@/data/hooks/useOkmsServiceKeys'; import { ROUTES_URLS } from '@/routes/routes.constants'; +import { OkmsContext } from '..'; +import Loading from '@/components/Loading/Loading'; +import { getOkmsServiceKeyResourceListQueryKey } from '@/data/api/okmsServiceKey'; export default function Keys() { const { t } = useTranslation('key-management-service/serviceKeys'); @@ -75,66 +71,70 @@ export default function Keys() { ]; const { sorting, setSorting } = useDatagridSearchParams(); + const okms = useContext(OkmsContext); const { okmsId } = useParams(); const { error, data: okmsServiceKey, isLoading } = useOkmsServiceKeys({ sorting, okmsId, }); + if (isLoading) return ; + + if (error) + return ( + navigate(ROUTES_URLS.listing)} + onReloadPage={() => + queryClient.refetchQueries({ + queryKey: getOkmsServiceKeyResourceListQueryKey(okmsId), + }) + } + /> + ); + return ( - <> -
- - {t('key_management_service_service-keys_headline')} - -
+
+ + {t('key_management_service_service-keys_headline')} + {error && ( {tError('manager_error_page_default')} )} +
+ { + navigate(ROUTES_URLS.createKmsServiceKey); + }} + urn={okms.iam.urn} + iamActions={['okms:apiovh:resource/serviceKey/create']} + > + + + - {isLoading && !error && ( -
- -
- )} - { - navigate(ROUTES_URLS.createKmsServiceKey); - }} - > - - - - - {t('key_management_service_service-keys_cta_create')} - - {!isLoading && !error && ( -
- -
- )} + {t('key_management_service_service-keys_cta_create')} +
+
+ - +
); } diff --git a/packages/manager/apps/key-management-service/src/pages/listing/KmsListing.page.spec.tsx b/packages/manager/apps/key-management-service/src/pages/listing/KmsListing.page.spec.tsx new file mode 100644 index 000000000000..861f4a46d699 --- /dev/null +++ b/packages/manager/apps/key-management-service/src/pages/listing/KmsListing.page.spec.tsx @@ -0,0 +1,102 @@ +import { act, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { renderTestApp } from '@/utils/tests/renderTestApp'; +import '@testing-library/jest-dom'; +import { labels } from '@/utils/tests/init.i18n'; +import { okmsList } from '@/mocks/okms.mock'; + +describe('KMS listing test suite', () => { + it('should redirect to the onboarding page when the kms list is empty', async () => { + await renderTestApp({ nbOkms: 0 }); + + expect(screen.getByText(labels.onboarding.title)).toBeVisible(); + + expect( + screen.queryByText(labels.listing.key_management_service_listing_title), + ).not.toBeInTheDocument(); + }); + + it('should display the kms listing page', async () => { + await renderTestApp(); + + expect( + screen.getByText(labels.listing.key_management_service_listing_title), + ).toBeVisible(); + + expect( + screen.queryByText(labels.onboarding.description), + ).not.toBeInTheDocument(); + }); + + it(`should navigate to the kms creation form on click on "${labels.listing.key_management_service_listing_add_kms_button}" button`, async () => { + await renderTestApp(); + + await waitFor( + () => + expect( + screen.getByText( + labels.listing.key_management_service_listing_add_kms_button, + ), + ).toBeEnabled(), + { + timeout: 30_000, + }, + ); + + await act(() => + userEvent.click( + screen.getByText( + labels.listing.key_management_service_listing_add_kms_button, + ), + ), + ); + + await waitFor( + () => + expect( + screen.getByText( + labels.create.key_management_service_create_subtitle, + ), + ).toBeVisible(), + { timeout: 30_000 }, + ); + }); + + it('should navigate to a kms dashboard on click on kms name', async () => { + await renderTestApp(); + + await act(() => + userEvent.click(screen.getByText(okmsList[0].iam.displayName)), + ); + + await waitFor( + () => + expect( + screen.getByText(labels.dashboard.billing_informations), + ).toBeVisible(), + { timeout: 30_000 }, + ); + }); + + it(`should navigate to the kms delete modal on click on "${labels.listing.key_management_service_listing_terminate}" list action button`, async () => { + await renderTestApp(); + + await act(() => + userEvent.click( + screen.getByText( + labels.listing.key_management_service_listing_terminate, + ), + ), + ); + + await waitFor( + () => + expect( + screen.getByText( + labels.terminate.key_management_service_terminate_cancel, + ), + ).toBeVisible(), + { timeout: 30_000 }, + ); + }); +}); diff --git a/packages/manager/apps/key-management-service/src/pages/listing/index.tsx b/packages/manager/apps/key-management-service/src/pages/listing/index.tsx index 2a33977bfaf3..1962ace06416 100644 --- a/packages/manager/apps/key-management-service/src/pages/listing/index.tsx +++ b/packages/manager/apps/key-management-service/src/pages/listing/index.tsx @@ -1,24 +1,19 @@ -import React, { Suspense, useEffect } from 'react'; +import React from 'react'; import { Outlet, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { - ODS_SPINNER_SIZE, ODS_MESSAGE_TYPE, ODS_BUTTON_SIZE, ODS_BUTTON_VARIANT, } from '@ovhcloud/ods-components'; -import { - OsdsButton, - OsdsMessage, - OsdsSpinner, -} from '@ovhcloud/ods-components/react'; +import { OsdsButton, OsdsMessage } from '@ovhcloud/ods-components/react'; import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; import { BaseLayout, Datagrid, HeadersProps, Notifications, - useDatagridSearchParams, + RedirectionGuard, useNotifications, } from '@ovh-ux/manager-react-components'; import { @@ -26,7 +21,7 @@ import { PageLocation, useOvhTracking, } from '@ovh-ux/manager-react-shell-client'; -import { useOKMS } from '@/data/hooks/useOKMS'; +import { useOKMSList } from '@/data/hooks/useOKMS'; import { ROUTES_URLS } from '@/routes/routes.constants'; import { DatagridActionMenu, @@ -36,7 +31,6 @@ import { DatagridCellStatus, } from '@/components/Listing/ListingCells'; import KmsGuidesHeader from '@/components/Guide/KmsGuidesHeader'; -import Loading from '@/components/Loading/Loading'; export default function Listing() { const { t } = useTranslation('key-management-service/listing'); @@ -74,24 +68,35 @@ export default function Listing() { }, ]; - const { sorting, setSorting } = useDatagridSearchParams(); - - const { error, data: okms, isLoading } = useOKMS({ - sorting, + const { + data, + flattenData, + fetchNextPage, + hasNextPage, + isError, + isLoading, + status, + } = useOKMSList({ + pageSize: 10, }); - useEffect(() => { - if (okms.length === 0 && !isLoading) { - navigate(ROUTES_URLS.onboarding); - } - }, [okms.length, isLoading]); - const headerProps: HeadersProps = { title: t('key_management_service_listing_title'), headerButton: , }; + return ( - }> + + {tError('manager_error_page_default')} + + } + > }>
- {error && ( - - {tError('manager_error_page_default')} - - )} - - {isLoading && !error && ( -
- -
- )} - {!isLoading && !error && ( + {flattenData && (
fetchNextPage()} contentAlignLeft />
)}
-
+ ); } diff --git a/packages/manager/apps/key-management-service/src/pages/serviceKey/ServiceKey.page.tsx b/packages/manager/apps/key-management-service/src/pages/serviceKey/ServiceKey.page.tsx index b07ec4555243..a99f0f2b22eb 100644 --- a/packages/manager/apps/key-management-service/src/pages/serviceKey/ServiceKey.page.tsx +++ b/packages/manager/apps/key-management-service/src/pages/serviceKey/ServiceKey.page.tsx @@ -3,13 +3,13 @@ import { useTranslation } from 'react-i18next'; import { useQueryClient } from '@tanstack/react-query'; import { BaseLayout, + Clipboard, ErrorBanner, + ManagerButton, Notifications, - Clipboard, } from '@ovh-ux/manager-react-components'; import { Outlet, useNavigate, useParams } from 'react-router-dom'; import { - OsdsButton, OsdsIcon, OsdsTabBar, OsdsTabBarItem, @@ -38,37 +38,49 @@ import { getOkmsServiceKeyResourceQueryKey } from '@/data/api/okmsServiceKey'; import { BreadcrumbItem } from '@/hooks/breadcrumb/useBreadcrumb'; import { useOKMSById } from '@/data/hooks/useOKMS'; import ServiceKeyStateActions from '@/components/serviceKey/serviceKeyStateActions/ServiceKeyStateActions.component'; +import { getOkmsResourceQueryKey } from '@/data/api/okms'; export default function Key() { const { okmsId, keyId } = useParams(); - const { data: okms } = useOKMSById(okmsId); - const { data: serviceKey, error, isLoading } = useOkmsServiceKeyById({ - okmsId, - keyId, - }); const { t } = useTranslation('key-management-service/serviceKeys'); const queryClient = useQueryClient(); - const navigate = useNavigate(); - const kms = okms?.data; - const kmsKey = serviceKey?.data; + const { + data: okms, + isLoading: isLoadingOkms, + error: okmsError, + } = useOKMSById(okmsId); - if (isLoading) return ; + const { + data: serviceKey, + error: serviceKeyError, + isLoading: isLoadingServiceKey, + } = useOkmsServiceKeyById({ + okmsId, + keyId, + }); - if (error) + if (isLoadingServiceKey || isLoadingOkms) return ; + + if (okmsError || serviceKeyError) return ( navigate(ROUTES_URLS.listing)} onReloadPage={() => queryClient.refetchQueries({ - queryKey: getOkmsServiceKeyResourceQueryKey({ okmsId, keyId }), + queryKey: okmsError + ? getOkmsResourceQueryKey(okmsId) + : getOkmsServiceKeyResourceQueryKey({ okmsId, keyId }), }) } /> ); + const kms = okms.data; + const kmsKey = serviceKey.data; + const breadcrumbItems: BreadcrumbItem[] = [ { id: okmsId, @@ -131,18 +143,20 @@ export default function Key() { >
- navigate(ROUTES_URLS.serviceKeyEditName)} + urn={kms.iam.urn} + iamActions={['okms:apiovh:resource/serviceKey/update']} > - +
@@ -161,7 +175,7 @@ export default function Key() { titleStatus={} >
- +
diff --git a/packages/manager/apps/key-management-service/src/routes/routes.constants.ts b/packages/manager/apps/key-management-service/src/routes/routes.constants.ts index c7c47288b056..0041c5ff8b1e 100644 --- a/packages/manager/apps/key-management-service/src/routes/routes.constants.ts +++ b/packages/manager/apps/key-management-service/src/routes/routes.constants.ts @@ -5,6 +5,7 @@ export const ROUTES_URLS = { terminateOkms: '/terminate', createKeyManagementService: 'create', okmsId: ':okmsId', + okmsUpdateName: 'update-name', keys: 'keys', keyId: ':keyId', createKmsServiceKey: 'create', diff --git a/packages/manager/apps/key-management-service/src/routes/routes.tsx b/packages/manager/apps/key-management-service/src/routes/routes.tsx index a37274924b0e..beee6cca23a4 100644 --- a/packages/manager/apps/key-management-service/src/routes/routes.tsx +++ b/packages/manager/apps/key-management-service/src/routes/routes.tsx @@ -84,6 +84,16 @@ export default [ pageType: PageType.dashboard, }, }, + children: [ + { + path: ROUTES_URLS.okmsUpdateName, + ...lazyRouteConfig(() => + import( + '@/pages/dashboard/generalInformations/updateName/OkmsNameUpdateModal' + ), + ), + }, + ], }, { path: ROUTES_URLS.keys, diff --git a/packages/manager/apps/key-management-service/src/setupTests.ts b/packages/manager/apps/key-management-service/src/setupTests.ts deleted file mode 100644 index 39d524c2e0e0..000000000000 --- a/packages/manager/apps/key-management-service/src/setupTests.ts +++ /dev/null @@ -1,11 +0,0 @@ -import '@testing-library/jest-dom'; -import { vi } from 'vitest'; - -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ - t: (translationKey: string) => translationKey, - i18n: { - changeLanguage: () => new Promise(() => {}), - }, - }), -})); diff --git a/packages/manager/apps/key-management-service/src/setupTests.tsx b/packages/manager/apps/key-management-service/src/setupTests.tsx new file mode 100644 index 000000000000..141b5063ffb0 --- /dev/null +++ b/packages/manager/apps/key-management-service/src/setupTests.tsx @@ -0,0 +1,39 @@ +import { odsSetup } from '@ovhcloud/ods-common-core'; +import { SetupServer, setupServer } from 'msw/node'; +import { toMswHandlers } from '../../../../../playwright-helpers'; +import { getAuthenticationMocks } from '../../../../../playwright-helpers/mocks/auth'; +import '@testing-library/jest-dom'; +import 'element-internals-polyfill'; + +odsSetup(); + +declare global { + // eslint-disable-next-line vars-on-top, no-var + var server: SetupServer; + // eslint-disable-next-line vars-on-top, no-var, @typescript-eslint/naming-convention + var __VERSION__: string; +} + +const server = setupServer( + ...toMswHandlers([ + ...getAuthenticationMocks({ isAuthMocked: true, region: 'EU' }), + ]), +); + +beforeAll(() => { + server.listen({ onUnhandledRequest: 'warn' }); + + delete global.server; + global.__VERSION__ = null; + global.server = server; +}); + +afterAll(() => { + server.close(); + + delete global.__VERSION__; +}); + +afterEach(() => { + server.resetHandlers(); +}); diff --git a/packages/manager/apps/key-management-service/src/utils/credential/credentialDownload.ts b/packages/manager/apps/key-management-service/src/utils/credential/credentialDownload.ts index d85895eb1717..821674362799 100644 --- a/packages/manager/apps/key-management-service/src/utils/credential/credentialDownload.ts +++ b/packages/manager/apps/key-management-service/src/utils/credential/credentialDownload.ts @@ -19,7 +19,7 @@ export const getDownloadCredentialParameters = (credential: OkmsCredential) => { credential.certificatePEM.replace(/\n/g, '\r\n'), )}` : undefined, - filename: `${credential.name}.pem`, + filename: `${credential.id}_certificate.pem`, isDisabled: isDownloadCredentialDisabled(credential.status), }; }; diff --git a/packages/manager/apps/key-management-service/src/utils/tests/TestApp.tsx b/packages/manager/apps/key-management-service/src/utils/tests/TestApp.tsx new file mode 100644 index 000000000000..c61879281812 --- /dev/null +++ b/packages/manager/apps/key-management-service/src/utils/tests/TestApp.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; +import { createMemoryRouter, RouterProvider } from 'react-router-dom'; +import routes from '@/routes/routes'; + +export function TestApp() { + const router = createMemoryRouter(routes, { + initialEntries: ['/'], + initialIndex: 0, + }); + + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, + }); + + return ( + + + + ); +} diff --git a/packages/manager/apps/key-management-service/src/utils/tests/init.i18n.ts b/packages/manager/apps/key-management-service/src/utils/tests/init.i18n.ts new file mode 100644 index 000000000000..d43dbd8afec9 --- /dev/null +++ b/packages/manager/apps/key-management-service/src/utils/tests/init.i18n.ts @@ -0,0 +1,75 @@ +import i18next, { i18n } from 'i18next'; +import common from '../../../public/translations/key-management-service/common/Messages_fr_FR.json'; +import create from '../../../public/translations/key-management-service/create/Messages_fr_FR.json'; +import dashboard from '../../../public/translations/key-management-service/dashboard/Messages_fr_FR.json'; +import error from '../../../public/translations/key-management-service/error/Messages_fr_FR.json'; +import guide from '../../../public/translations/key-management-service/guide/Messages_fr_FR.json'; +import listing from '../../../public/translations/key-management-service/listing/Messages_fr_FR.json'; +import onboarding from '../../../public/translations/key-management-service/onboarding/Messages_fr_FR.json'; +import serviceKeys from '../../../public/translations/key-management-service/serviceKeys/Messages_fr_FR.json'; +import terminate from '../../../public/translations/key-management-service/terminate/Messages_fr_FR.json'; + +export const defaultLocale = 'fr_FR'; +export const defaultAvailableLocales = [defaultLocale]; + +function addTranslations() { + i18next + .addResources(defaultLocale, 'key-management-service/common', common) + .addResources(defaultLocale, 'key-management-service/create', create) + .addResources(defaultLocale, 'key-management-service/dashboard', dashboard) + .addResources(defaultLocale, 'key-management-service/error', error) + .addResources(defaultLocale, 'key-management-service/guide', guide) + .addResources(defaultLocale, 'key-management-service/listing', listing) + .addResources(defaultLocale, 'key-management-service/terminate', terminate) + .addResources( + defaultLocale, + 'key-management-service/onboarding', + onboarding, + ) + .addResources( + defaultLocale, + 'key-management-service/serviceKeys', + serviceKeys, + ) + .use({ + type: 'postProcessor', + name: 'normalize', + process: (value: string) => + value ? value.replace(/&/g, '&') : value, + }); +} + +export const initTestI18n = () => + new Promise((resolve) => { + i18next.init({ + lng: defaultLocale, + defaultNS: 'key-management-service', + ns: [], + supportedLngs: defaultAvailableLocales, + postProcess: 'normalize', + interpolation: { + escapeValue: false, + }, + }); + + if (i18next.isInitialized) { + addTranslations(); + } else { + i18next.on('initialized', () => { + addTranslations(); + resolve(i18next); + }); + } + }); + +export const labels = { + common, + create, + dashboard, + error, + guide, + listing, + onboarding, + serviceKeys, + terminate, +}; diff --git a/packages/manager/apps/key-management-service/src/utils/tests/renderTestApp.tsx b/packages/manager/apps/key-management-service/src/utils/tests/renderTestApp.tsx new file mode 100644 index 000000000000..8849615ba54a --- /dev/null +++ b/packages/manager/apps/key-management-service/src/utils/tests/renderTestApp.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { + initShellContext, + ShellContext, + ShellContextType, +} from '@ovh-ux/manager-react-shell-client'; +import { i18n } from 'i18next'; +import { I18nextProvider } from 'react-i18next'; +import { render, waitFor, screen } from '@testing-library/react'; +import { TestApp } from './TestApp'; +import { initTestI18n } from './init.i18n'; +import { toMswHandlers } from '../../../../../../../playwright-helpers'; +import { getAuthenticationMocks } from '../../../../../../../playwright-helpers/mocks/auth'; +import { getOkmsMocks, GetOkmsMocksParams } from '@/mocks/okms.mock'; +import { getServicesMocks } from '@/mocks/services.mock'; + +let context: ShellContextType; +let i18nValue: i18n; + +export const renderTestApp = async (mockParams: GetOkmsMocksParams = {}) => { + global.server?.resetHandlers( + ...toMswHandlers([ + ...getAuthenticationMocks({ isAuthMocked: true }), + ...getOkmsMocks(mockParams), + ...getServicesMocks(), + ]), + ); + + if (!context) { + context = await initShellContext('key-management-service'); + } + + if (!i18nValue) { + i18nValue = await initTestI18n(); + } + + const result = render( + + + + + , + ); + + await waitFor( + () => + expect( + screen.getAllByText('Key Management Service', { exact: false }).length, + ).toBeGreaterThan(0), + { timeout: 30000 }, + ); + + return result; +}; diff --git a/packages/manager/apps/key-management-service/vitest.config.js b/packages/manager/apps/key-management-service/vitest.config.js index 156daa6627e5..51634113d665 100644 --- a/packages/manager/apps/key-management-service/vitest.config.js +++ b/packages/manager/apps/key-management-service/vitest.config.js @@ -8,7 +8,7 @@ export default defineConfig({ test: { globals: true, environment: 'jsdom', - setupFiles: './src/setupTests.ts', + setupFiles: './src/setupTests.tsx', coverage: { include: ['src'], exclude: [ @@ -25,6 +25,17 @@ export default defineConfig({ 'src/queryClient.ts', ], }, + testTimeout: 60_000, + fileParallelism: false, + maxWorkers: 1, + pollOptions: { + forks: { + singleFork: true, + }, + threads: { + singleThread: true, + }, + }, }, resolve: { alias: {