From 86768cfbce978912f0abf240843b758be441fdac Mon Sep 17 00:00:00 2001 From: Nicolas Pierre-charles Date: Wed, 27 Nov 2024 12:00:37 +0100 Subject: [PATCH 1/3] feat(common-api-module): create a common-api module ref: MANAGER-15700 Signed-off-by: Nicolas Pierre-charles --- .github/workflows/run-bdd-tests.yml | 5 +- package.json | 1 + .../__snapshots__/PlanStep.spec.tsx.snap | 8 +- packages/manager/core/test-utils/CHANGELOG.md | 0 packages/manager/core/test-utils/index.ts | 1 + packages/manager/core/test-utils/package.json | 47 ++++ .../core/test-utils/setup-file-msw-ods17.tsx | 32 +++ .../test-utils/src/auth/applications.mock.ts | 179 +++++++++++++++ .../core/test-utils/src/auth/auth.handler.ts | 30 +++ .../src/auth/configurations.mock.ts | 13 ++ .../manager/core/test-utils/src/auth/index.ts | 4 + .../core/test-utils/src/auth/user.mock.ts | 53 +++++ packages/manager/core/test-utils/src/index.ts | 4 + .../core/test-utils/src/test-setup/index.ts | 1 + .../test-utils/src/test-setup/test-i18n.ts | 47 ++++ .../core/test-utils/src/types/handler.type.ts | 23 ++ .../core/test-utils/src/types/index.ts | 1 + .../core/test-utils/src/utils/index.ts | 3 + .../manager/core/test-utils/src/utils/msw.ts | 46 ++++ .../core/test-utils/src/utils/sleep.ts | 9 + .../src/utils/ui-test-helpers-ods17.ts | 92 ++++++++ .../manager/core/test-utils/tsconfig.json | 25 +++ .../manager/modules/common-api/.gitignore | 1 + .../manager/modules/common-api/CHANGELOG.md | 0 .../manager/modules/common-api/package.json | 48 ++++ .../src/feature-availability/index.ts | 2 + .../mocks/feature-availability.mock.ts | 28 +++ .../useFeatureAvailability.spec.tsx | 75 +++++++ .../useFeatureAvailability.ts | 42 ++++ .../manager/modules/common-api/src/index.ts | 3 + .../common-api/src/services/api/get.ts | 24 ++ .../common-api/src/services/api/index.ts | 3 + .../common-api/src/services/api/post.ts | 11 + .../common-api/src/services/api/put.ts | 19 ++ .../common-api/src/services/hooks/index.ts | 3 + .../src/services/hooks/useDeleteService.ts | 47 ++++ .../src/services/hooks/useServiceDetails.ts | 38 ++++ .../services/hooks/useUpdateServiceName.ts | 51 +++++ .../modules/common-api/src/services/index.ts | 4 + .../common-api/src/services/mocks/index.ts | 2 + .../src/services/mocks/services.handler.ts | 68 ++++++ .../src/services/mocks/services.mock.ts | 96 ++++++++ .../common-api/src/services/services.type.ts | 148 ++++++++++++ .../src/tasks/hooks/useTask.spec.tsx | 210 ++++++++++++++++++ .../common-api/src/tasks/hooks/useTask.ts | 105 +++++++++ .../modules/common-api/src/tasks/index.ts | 1 + .../manager/modules/common-api/tsconfig.json | 25 +++ .../modules/common-api/vitest.config.js | 29 +++ yarn.lock | 202 ++++++++++++++--- 49 files changed, 1876 insertions(+), 33 deletions(-) create mode 100644 packages/manager/core/test-utils/CHANGELOG.md create mode 100644 packages/manager/core/test-utils/index.ts create mode 100644 packages/manager/core/test-utils/package.json create mode 100644 packages/manager/core/test-utils/setup-file-msw-ods17.tsx create mode 100644 packages/manager/core/test-utils/src/auth/applications.mock.ts create mode 100644 packages/manager/core/test-utils/src/auth/auth.handler.ts create mode 100644 packages/manager/core/test-utils/src/auth/configurations.mock.ts create mode 100644 packages/manager/core/test-utils/src/auth/index.ts create mode 100644 packages/manager/core/test-utils/src/auth/user.mock.ts create mode 100644 packages/manager/core/test-utils/src/index.ts create mode 100644 packages/manager/core/test-utils/src/test-setup/index.ts create mode 100644 packages/manager/core/test-utils/src/test-setup/test-i18n.ts create mode 100644 packages/manager/core/test-utils/src/types/handler.type.ts create mode 100644 packages/manager/core/test-utils/src/types/index.ts create mode 100644 packages/manager/core/test-utils/src/utils/index.ts create mode 100644 packages/manager/core/test-utils/src/utils/msw.ts create mode 100644 packages/manager/core/test-utils/src/utils/sleep.ts create mode 100644 packages/manager/core/test-utils/src/utils/ui-test-helpers-ods17.ts create mode 100644 packages/manager/core/test-utils/tsconfig.json create mode 100644 packages/manager/modules/common-api/.gitignore create mode 100644 packages/manager/modules/common-api/CHANGELOG.md create mode 100644 packages/manager/modules/common-api/package.json create mode 100644 packages/manager/modules/common-api/src/feature-availability/index.ts create mode 100644 packages/manager/modules/common-api/src/feature-availability/mocks/feature-availability.mock.ts create mode 100644 packages/manager/modules/common-api/src/feature-availability/useFeatureAvailability.spec.tsx create mode 100644 packages/manager/modules/common-api/src/feature-availability/useFeatureAvailability.ts create mode 100644 packages/manager/modules/common-api/src/index.ts create mode 100644 packages/manager/modules/common-api/src/services/api/get.ts create mode 100644 packages/manager/modules/common-api/src/services/api/index.ts create mode 100644 packages/manager/modules/common-api/src/services/api/post.ts create mode 100644 packages/manager/modules/common-api/src/services/api/put.ts create mode 100644 packages/manager/modules/common-api/src/services/hooks/index.ts create mode 100644 packages/manager/modules/common-api/src/services/hooks/useDeleteService.ts create mode 100644 packages/manager/modules/common-api/src/services/hooks/useServiceDetails.ts create mode 100644 packages/manager/modules/common-api/src/services/hooks/useUpdateServiceName.ts create mode 100644 packages/manager/modules/common-api/src/services/index.ts create mode 100644 packages/manager/modules/common-api/src/services/mocks/index.ts create mode 100644 packages/manager/modules/common-api/src/services/mocks/services.handler.ts create mode 100644 packages/manager/modules/common-api/src/services/mocks/services.mock.ts create mode 100644 packages/manager/modules/common-api/src/services/services.type.ts create mode 100644 packages/manager/modules/common-api/src/tasks/hooks/useTask.spec.tsx create mode 100644 packages/manager/modules/common-api/src/tasks/hooks/useTask.ts create mode 100644 packages/manager/modules/common-api/src/tasks/index.ts create mode 100644 packages/manager/modules/common-api/tsconfig.json create mode 100644 packages/manager/modules/common-api/vitest.config.js diff --git a/.github/workflows/run-bdd-tests.yml b/.github/workflows/run-bdd-tests.yml index d584f3a49387..d0b98efb2269 100644 --- a/.github/workflows/run-bdd-tests.yml +++ b/.github/workflows/run-bdd-tests.yml @@ -21,9 +21,10 @@ jobs: - name: Build BDD covered packages and dependencies run: | yarn exec turbo -- run build --filter="./packages/manager/core/*" --concurrency=5 - yarn exec turbo -- run build --filter="./packages/manager-react-components" + yarn exec turbo -- run build --filter="./packages/manager-react-components" yarn exec turbo -- run build --filter="./packages/manager/modules/order" - yarn exec turbo -- run build --filter="./packages/manager/modules/manager-pci-common" + yarn exec turbo -- run build --filter="./packages/manager/modules/common-api" --concurrency=5 + yarn exec turbo -- run build --filter="./packages/manager/modules/manager-pci-common" # This task is for running the jest tests outside microApps - name: Run tests Jest run: yarn test:jest diff --git a/package.json b/package.json index 63880180df72..79fb476ce05e 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "packages/manager/core/url-builder", "packages/manager/core/utils", "packages/manager/core/vite-config", + "packages/manager/core/test-utils", "packages/manager/modules/*", "packages/manager/tools/*", "packages/manager-react-components" diff --git a/packages/manager/apps/pci-private-registry/src/pages/create/steps/__snapshots__/PlanStep.spec.tsx.snap b/packages/manager/apps/pci-private-registry/src/pages/create/steps/__snapshots__/PlanStep.spec.tsx.snap index 7da6e0eb4af8..1282e7646d9d 100644 --- a/packages/manager/apps/pci-private-registry/src/pages/create/steps/__snapshots__/PlanStep.spec.tsx.snap +++ b/packages/manager/apps/pci-private-registry/src/pages/create/steps/__snapshots__/PlanStep.spec.tsx.snap @@ -27,9 +27,13 @@ exports[`PlanStep > should render 1`] = ` class="flex flex-col md:flex-row" >
- private_registry_create_choose_plan +
+ private_registry_create_choose_plan +
diff --git a/packages/manager/core/test-utils/CHANGELOG.md b/packages/manager/core/test-utils/CHANGELOG.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/manager/core/test-utils/index.ts b/packages/manager/core/test-utils/index.ts new file mode 100644 index 000000000000..8420b1093fdb --- /dev/null +++ b/packages/manager/core/test-utils/index.ts @@ -0,0 +1 @@ +export * from './src'; diff --git a/packages/manager/core/test-utils/package.json b/packages/manager/core/test-utils/package.json new file mode 100644 index 000000000000..03d3db9004f8 --- /dev/null +++ b/packages/manager/core/test-utils/package.json @@ -0,0 +1,47 @@ +{ + "name": "@ovh-ux/manager-core-test-utils", + "version": "0.1.0", + "private": true, + "description": "OVHcloud manager core test utils for vitest and jest", + "repository": { + "type": "git", + "url": "git+https://github.com/ovh/manager.git", + "directory": "packages/manager/core/test-utils" + }, + "license": "BSD-3-Clause", + "author": "OVH SAS", + "sideEffects": false, + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/types/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "dev": "tsc", + "start:watch": "tsc -w" + }, + "dependencies": { + "@ovh-ux/manager-core-api": "^0.9.0", + "@ovh-ux/manager-react-components": "^1.41.1", + "@ovh-ux/manager-react-shell-client": "^0.8.1", + "@ovhcloud/ods-common-core": "^17.2.2", + "@ovhcloud/ods-components": "^17.2.2", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.0.1", + "@testing-library/user-event": "^14.5.2", + "element-internals-polyfill": "^1.3.12", + "i18next": "^23.8.2" + }, + "devDependencies": { + "msw": "2.1.7", + "typescript": "^5.1.6", + "vite": "^5.2.13", + "vitest": "^2.1.4" + }, + "peerDependencies": { + "@tanstack/react-query": "5.x", + "react": "18.x" + } +} diff --git a/packages/manager/core/test-utils/setup-file-msw-ods17.tsx b/packages/manager/core/test-utils/setup-file-msw-ods17.tsx new file mode 100644 index 000000000000..95eb2ab2f4a9 --- /dev/null +++ b/packages/manager/core/test-utils/setup-file-msw-ods17.tsx @@ -0,0 +1,32 @@ +import { beforeAll, afterAll } from 'vitest'; +import { odsSetup } from '@ovhcloud/ods-common-core'; +import { setupServer } from 'msw/node'; +import { toMswHandlers } from './src/utils'; +import { getAuthenticationMocks } from './src/auth'; +import 'element-internals-polyfill'; + +odsSetup(); + +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/core/test-utils/src/auth/applications.mock.ts b/packages/manager/core/test-utils/src/auth/applications.mock.ts new file mode 100644 index 000000000000..f1bc44b2d316 --- /dev/null +++ b/packages/manager/core/test-utils/src/auth/applications.mock.ts @@ -0,0 +1,179 @@ +export const applications = { + cloud: { + universe: 'server', + url: 'https://www.ovh.com/manager/cloud/repsac/', + container: { isDefault: false }, + publicURL: 'https://www.ovh.com/manager/cloud/repsac/', + }, + dedicated: { + universe: 'server', + url: 'https://www.ovh.com/manager/dedicated/', + container: { isDefault: false, enabled: true, path: 'dedicated' }, + publicURL: 'https://www.ovh.com/manager/#/dedicated', + }, + hub: { + universe: 'hub', + url: 'https://www.ovh.com/manager/hub/', + container: { isDefault: true, enabled: true, path: 'hub' }, + publicURL: 'https://www.ovh.com/manager/#/hub', + }, + 'public-cloud': { + universe: 'public-cloud', + url: 'https://www.ovh.com/manager/public-cloud/', + container: { + isDefault: false, + enabled: true, + path: 'public-cloud', + }, + publicURL: 'https://www.ovh.com/manager/#/public-cloud', + }, + telecom: { + universe: 'telecom', + url: 'https://www.ovhtelecom.fr/manager/telecom/', + container: { isDefault: false, enabled: true, path: 'telecom' }, + publicURL: 'https://www.ovhtelecom.fr/manager/#/telecom', + }, + web: { + universe: 'web', + url: 'https://www.ovh.com/manager/web/', + container: { isDefault: false, enabled: true, path: 'web' }, + publicURL: 'https://www.ovh.com/manager/#/web', + }, + sunrise: { + url: 'https://www.ovh.com/manager/sunrise/', + container: { isDefault: false }, + publicURL: 'https://www.ovh.com/manager/sunrise/', + }, + billing: { + universe: 'server', + url: 'https://www.ovh.com/manager/dedicated/#/billing', + container: { isDefault: false }, + publicURL: 'https://www.ovh.com/manager/dedicated/#/billing', + }, + user: { + universe: 'server', + url: 'https://www.ovh.com/manager/dedicated/', + container: { isDefault: false }, + publicURL: 'https://www.ovh.com/manager/dedicated/', + }, + exchange: { + universe: 'web', + url: 'https://www.ovh.com/manager/web/', + container: { isDefault: false }, + publicURL: 'https://www.ovh.com/manager/web/', + }, + sharepoint: { + universe: 'web', + url: 'https://www.ovh.com/manager/web/', + container: { isDefault: false }, + publicURL: 'https://www.ovh.com/manager/web/', + }, + office: { + universe: 'web', + url: 'https://www.ovh.com/manager/web/', + container: { isDefault: false }, + publicURL: 'https://www.ovh.com/manager/web/', + }, + telephony: { + universe: 'telecom', + url: 'https://www.ovhtelecom.fr/manager/telecom/', + container: { isDefault: false }, + publicURL: 'https://www.ovhtelecom.fr/manager/telecom/', + }, + 'pack-xdsl': { + universe: 'telecom', + url: 'https://www.ovhtelecom.fr/manager/telecom/', + container: { isDefault: false }, + publicURL: 'https://www.ovhtelecom.fr/manager/telecom/', + }, + overthebox: { + universe: 'telecom', + url: 'https://www.ovhtelecom.fr/manager/telecom/', + container: { isDefault: false }, + publicURL: 'https://www.ovhtelecom.fr/manager/telecom/', + }, + freefax: { + universe: 'telecom', + url: 'https://www.ovhtelecom.fr/manager/telecom/', + container: { isDefault: false }, + publicURL: 'https://www.ovhtelecom.fr/manager/telecom/', + }, + sms: { + universe: 'telecom', + url: 'https://www.ovhtelecom.fr/manager/telecom/', + container: { isDefault: false }, + publicURL: 'https://www.ovhtelecom.fr/manager/telecom/', + }, + catalog: { + universe: 'hub', + url: 'https://www.ovh.com/manager/catalog', + container: { isDefault: false, enabled: true, path: 'catalog' }, + publicURL: 'https://www.ovh.com/manager/#/catalog', + }, + iam: { + universe: 'server', + url: 'https://www.ovh.com/manager/security', + container: { isDefault: false, enabled: true, path: 'iam' }, + publicURL: 'https://www.ovh.com/manager/#/iam', + }, + 'carbon-calculator': { + universe: 'server', + url: 'https://www.ovh.com/manager/carbon-calculator/', + container: { + isDefault: false, + enabled: true, + path: 'carbon-calculator', + }, + publicURL: 'https://www.ovh.com/manager/#/carbon-calculator', + }, + 'octavia-load-balancer': { + universe: 'public-cloud', + url: 'https://www.ovh.com/manager/octavia-load-balancer/', + container: { + isDefault: false, + enabled: true, + path: 'public-cloud', + hash: '/pci/projects/:projectId/octavia-load-balancer', + }, + publicURL: 'https://www.ovh.com/manager/#/public-cloud', + }, + 'pci-vouchers': { + universe: 'public-cloud', + url: 'https://www.ovh.com/manager/pci-vouchers/', + container: { + isDefault: false, + enabled: true, + path: 'public-cloud', + hash: '/pci/projects/:projectId/vouchers', + }, + publicURL: 'https://www.ovh.com/manager/#/public-cloud', + }, + restricted: { + universe: 'server', + url: 'https://www.ovh.com/manager/restricted/', + container: { isDefault: false, path: 'restricted' }, + publicURL: 'https://www.ovh.com/manager/restricted/', + }, + 'vrack-services': { + universe: 'server', + url: 'ttps://www.ovh.com/manager/vrack-services/', + container: { + isDefault: false, + path: 'dedicated', + hash: '/vrack-services', + }, + publicURL: 'ttps://www.ovh.com/manager/vrack-services/', + }, +}; + +export const applicationURLs = Object.entries(applications) + .map(([appName, app]) => ({ appName, url: app.publicURL })) + .reduce( + (result, { appName, url }) => ({ + ...result, + [appName]: url, + }), + {}, + ); + +export default applications; diff --git a/packages/manager/core/test-utils/src/auth/auth.handler.ts b/packages/manager/core/test-utils/src/auth/auth.handler.ts new file mode 100644 index 000000000000..0b2090513bdd --- /dev/null +++ b/packages/manager/core/test-utils/src/auth/auth.handler.ts @@ -0,0 +1,30 @@ +import applicationsResponse from './applications.mock'; +import { getConfigurationResponse } from './configurations.mock'; +import { Handler } from '../types'; + +export type GetAuthenticationMocks = { + isAuthMocked?: boolean; + region?: string; +}; + +export const getAuthenticationMocks = ({ + isAuthMocked, + region, +}: GetAuthenticationMocks): Handler[] => [ + { + url: '/applications', + response: () => applicationsResponse, + status: 200, + method: 'get', + api: 'aapi', + disabled: !isAuthMocked, + }, + { + url: '/configuration', + response: () => getConfigurationResponse({ region }), + status: 200, + method: 'get', + api: 'aapi', + disabled: !isAuthMocked, + }, +]; diff --git a/packages/manager/core/test-utils/src/auth/configurations.mock.ts b/packages/manager/core/test-utils/src/auth/configurations.mock.ts new file mode 100644 index 000000000000..75f4e466fd78 --- /dev/null +++ b/packages/manager/core/test-utils/src/auth/configurations.mock.ts @@ -0,0 +1,13 @@ +import applications, { applicationURLs } from './applications.mock'; +import user from './user.mock'; + +export const defaultRegion = 'EU'; + +export const getConfigurationResponse = ({ region = defaultRegion }): any => ({ + region, + user, + applicationURLs, + universe: '', + message: null, + applications, +}); diff --git a/packages/manager/core/test-utils/src/auth/index.ts b/packages/manager/core/test-utils/src/auth/index.ts new file mode 100644 index 000000000000..48eafc5ea60d --- /dev/null +++ b/packages/manager/core/test-utils/src/auth/index.ts @@ -0,0 +1,4 @@ +export * from './applications.mock'; +export * from './auth.handler'; +export * from './configurations.mock'; +export * from './user.mock'; diff --git a/packages/manager/core/test-utils/src/auth/user.mock.ts b/packages/manager/core/test-utils/src/auth/user.mock.ts new file mode 100644 index 000000000000..6940aa67f261 --- /dev/null +++ b/packages/manager/core/test-utils/src/auth/user.mock.ts @@ -0,0 +1,53 @@ +export const user = { + legalform: 'corporation', + italianSDI: '0000000', + currency: { code: 'EUR', symbol: '€' }, + companyNationalIdentificationNumber: '00000000000000', + zip: '00000', + phoneCountry: 'FR', + nationalIdentificationNumber: null, + organisation: 'Not OVH', + spareEmail: 'vitest@bot.ovh.net', + name: 'vitest', + vat: null, + phone: '+33.000000000', + phoneType: 'mobile', + email: 'vitest@interne.ovh.net', + area: 'IN-WB', + corporationType: 'La communauté', + firstname: 'vitest', + fax: null, + nichandle: 'vitest-ovh', + ovhCompany: 'ovh', + city: 'Somewhere', + customerCode: '0000-0000-00', + language: 'fr_FR', + state: 'complete', + address: '2 Rue de la Comté', + birthCity: '', + sex: 'male', + country: 'Terre du milieu', + ovhSubsidiary: 'FR', + birthDay: '1900-05-01', + kycValidated: true, + complementaryAddress: null, + purposeOfPurchase: null, + supportLevel: { level: 'standard' }, + certificates: [], + isTrusted: false, + auth: { + method: 'provider', + user: 'gandalf.ext@corp.ovh.com', + description: 'Gandalf le gris', + roles: ['UNPRIVILEGED'], + allowedRoutes: null, + account: 'lsgandalf-ovh', + identities: [ + 'urn:v1:eu:identity:user:lsgandalf-ovh/provider/gandalf.ext@corp.ovh.com', + 'urn:v1:eu:identity:group:lsgandalf-ovh/Domain Users', + ], + }, + enterprise: false, +}; + +export default user; diff --git a/packages/manager/core/test-utils/src/index.ts b/packages/manager/core/test-utils/src/index.ts new file mode 100644 index 000000000000..eee5cb7c1f99 --- /dev/null +++ b/packages/manager/core/test-utils/src/index.ts @@ -0,0 +1,4 @@ +export * from './auth'; +export * from './types/handler.type'; +export * from './utils'; +export * from './test-setup'; diff --git a/packages/manager/core/test-utils/src/test-setup/index.ts b/packages/manager/core/test-utils/src/test-setup/index.ts new file mode 100644 index 000000000000..2c51dab34244 --- /dev/null +++ b/packages/manager/core/test-utils/src/test-setup/index.ts @@ -0,0 +1 @@ +export * from './test-i18n'; diff --git a/packages/manager/core/test-utils/src/test-setup/test-i18n.ts b/packages/manager/core/test-utils/src/test-setup/test-i18n.ts new file mode 100644 index 000000000000..891e70d1b85c --- /dev/null +++ b/packages/manager/core/test-utils/src/test-setup/test-i18n.ts @@ -0,0 +1,47 @@ +import i18next, { i18n } from 'i18next'; + +export type TranslationObject = { + [translationKey: string]: Record; +}; + +export const defaultLocale = 'fr_FR'; +export const defaultAvailableLocales = [defaultLocale]; + +function addTranslations(translations: TranslationObject) { + Object.entries(translations).forEach(([key, values]) => + i18next.addResources(defaultLocale, key, values), + ); + i18next.use({ + type: 'postProcessor', + name: 'normalize', + process: (value: string) => (value ? value.replace(/&/g, '&') : value), + }); +} + +export const initTestI18n = ( + appName: string, + translations: TranslationObject, +) => + new Promise((resolve) => { + i18next.init({ + lng: defaultLocale, + defaultNS: appName, + ns: [], + supportedLngs: defaultAvailableLocales, + postProcess: 'normalize', + interpolation: { + escapeValue: false, + }, + }); + + if (i18next.isInitialized) { + addTranslations(translations); + } else { + i18next.on('initialized', () => { + addTranslations(translations); + resolve(i18next); + }); + } + + return translations; + }); diff --git a/packages/manager/core/test-utils/src/types/handler.type.ts b/packages/manager/core/test-utils/src/types/handler.type.ts new file mode 100644 index 000000000000..23eb16f62c8e --- /dev/null +++ b/packages/manager/core/test-utils/src/types/handler.type.ts @@ -0,0 +1,23 @@ +export type Handler = { + url: string; + response?: T; + headers?: HeadersInit; + statusText?: string; + type?: ResponseType; + responseText?: string; + delay?: number; + method?: + | 'get' + | 'post' + | 'put' + | 'delete' + | 'all' + | 'head' + | 'options' + | 'patch'; + status?: number; + api?: 'v2' | 'v6' | 'aapi' | 'ws'; + baseUrl?: string; + disabled?: boolean; + once?: boolean; +}; diff --git a/packages/manager/core/test-utils/src/types/index.ts b/packages/manager/core/test-utils/src/types/index.ts new file mode 100644 index 000000000000..88de2f6de6a4 --- /dev/null +++ b/packages/manager/core/test-utils/src/types/index.ts @@ -0,0 +1 @@ +export * from './handler.type'; diff --git a/packages/manager/core/test-utils/src/utils/index.ts b/packages/manager/core/test-utils/src/utils/index.ts new file mode 100644 index 000000000000..3a7ecf951fe4 --- /dev/null +++ b/packages/manager/core/test-utils/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './msw'; +export * from './sleep'; +export * from './ui-test-helpers-ods17'; diff --git a/packages/manager/core/test-utils/src/utils/msw.ts b/packages/manager/core/test-utils/src/utils/msw.ts new file mode 100644 index 000000000000..4cee674428f0 --- /dev/null +++ b/packages/manager/core/test-utils/src/utils/msw.ts @@ -0,0 +1,46 @@ +import { http, RequestHandler, HttpResponse, delay } from 'msw'; +import { apiClient } from '@ovh-ux/manager-core-api'; +import { Handler } from '../types'; + +export const toMswHandlers = (handlers: Handler[] = []): RequestHandler[] => + handlers + .filter(Boolean) + .filter(({ disabled }) => !disabled) + .map( + ({ + url, + method = 'get', + headers, + type, + statusText, + delay: delayTime = 1000, + status = 200, + response, + responseText, + api = 'v6', + baseUrl, + once, + }: Handler) => + http[method]( + `${baseUrl ?? apiClient[api].getUri()}${ + url.startsWith('/') ? '' : '/' + }${url}`, + async ({ request, params, cookies }) => { + await delay(delayTime); + if (responseText) { + return HttpResponse.text(responseText); + } + const json = + typeof response === 'function' + ? await response(request, params, cookies) + : response; + return HttpResponse.json(json, { + status, + headers, + type, + statusText, + }); + }, + { once }, + ), + ); diff --git a/packages/manager/core/test-utils/src/utils/sleep.ts b/packages/manager/core/test-utils/src/utils/sleep.ts new file mode 100644 index 000000000000..40f432987b8c --- /dev/null +++ b/packages/manager/core/test-utils/src/utils/sleep.ts @@ -0,0 +1,9 @@ +/** + * Wait for x miliseconds (30seconds by default) + */ +export const sleep = (timeout = 30_000) => + new Promise((resolve) => + setTimeout(() => { + resolve(); + }, timeout), + ); diff --git a/packages/manager/core/test-utils/src/utils/ui-test-helpers-ods17.ts b/packages/manager/core/test-utils/src/utils/ui-test-helpers-ods17.ts new file mode 100644 index 000000000000..76a497d2ba21 --- /dev/null +++ b/packages/manager/core/test-utils/src/utils/ui-test-helpers-ods17.ts @@ -0,0 +1,92 @@ +import { ODS_ICON_NAME } from '@ovhcloud/ods-components'; +import { screen, waitFor, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; + +export const waitForOptions = { + timeout: 30_000, +}; + +export const assertModalVisibility = async ({ + container, + isVisible, +}: { + container: HTMLElement; + isVisible: boolean; +}) => + waitFor(() => { + const modal = container.querySelector('osds-modal'); + return isVisible + ? expect(modal).toBeInTheDocument() + : expect(modal).not.toBeInTheDocument(); + }, waitForOptions); + +export const getButtonByLabel = async ({ + container, + label, + altLabel, + disabled, +}: { + container: HTMLElement; + label: string; + altLabel?: string; + disabled?: boolean; +}) => { + let button: HTMLElement; + await waitFor(() => { + const buttonList = container.querySelectorAll('osds-button'); + buttonList.forEach((btn) => { + if ([label, altLabel].includes(btn.textContent)) { + button = btn; + } + }); + return disabled + ? expect(button).toHaveAttribute('disabled') + : expect(button).not.toHaveAttribute('disabled'); + }, waitForOptions); + return button; +}; + +export const getButtonByIcon = async ({ + container, + iconName, + disabled, +}: { + container: HTMLElement; + iconName: ODS_ICON_NAME; + disabled?: boolean; +}) => { + let button: HTMLElement; + await waitFor(() => { + button = container.querySelector(`osds-icon[name="${iconName}"]`) + ?.parentElement; + return disabled + ? expect(button).toHaveAttribute('disabled') + : expect(button).not.toHaveAttribute('disabled'); + }, waitForOptions); + return button; +}; + +export const getButtonByTestId = async (testId: string, disabled?: boolean) => { + let button: HTMLElement; + await waitFor(() => { + button = screen.getByTestId(testId); + return disabled + ? expect(button).toHaveAttribute('disabled') + : expect(button).not.toHaveAttribute('disabled'); + }, waitForOptions); + return button; +}; + +export const changeInputValue = async ({ + inputLabel, + value, +}: { + inputLabel: string; + value: string; +}) => { + const input = screen.getByLabelText(inputLabel); + const event = new CustomEvent('odsValueChange', { + detail: { value }, + }); + return waitFor(() => fireEvent(input, event)); +}; diff --git a/packages/manager/core/test-utils/tsconfig.json b/packages/manager/core/test-utils/tsconfig.json new file mode 100644 index 000000000000..130eeca92488 --- /dev/null +++ b/packages/manager/core/test-utils/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "lib": ["dom", "es2021"], + "target": "es2021", + "types": ["vite/client", "node", "jest"], + "module": "ES2020", + "outDir": "dist", + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "moduleResolution": "Node", + "isolatedModules": true, + "declaration": true, + "declarationDir": "./dist/types", + "jsx": "react-jsx" + }, + "exclude": [ + "node_modules", + "dist", + "types", + "**/__tests__", + "src/**/*.spec.ts" + ] +} diff --git a/packages/manager/modules/common-api/.gitignore b/packages/manager/modules/common-api/.gitignore new file mode 100644 index 000000000000..4ebc8aea50e0 --- /dev/null +++ b/packages/manager/modules/common-api/.gitignore @@ -0,0 +1 @@ +coverage diff --git a/packages/manager/modules/common-api/CHANGELOG.md b/packages/manager/modules/common-api/CHANGELOG.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/manager/modules/common-api/package.json b/packages/manager/modules/common-api/package.json new file mode 100644 index 000000000000..9d5f1871a8e1 --- /dev/null +++ b/packages/manager/modules/common-api/package.json @@ -0,0 +1,48 @@ +{ + "name": "@ovh-ux/manager-module-common-api", + "version": "0.1.0", + "private": true, + "description": "OVHcloud manager module common API (order, services, me, ...).", + "repository": { + "type": "git", + "url": "git+https://github.com/ovh/manager.git", + "directory": "packages/manager/modules/common-api" + }, + "license": "BSD-3-Clause", + "author": "OVH SAS", + "sideEffects": false, + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/types/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "dev": "tsc", + "start:watch": "tsc -w", + "test": "vitest run", + "test:coverage": "vitest run --coverage" + }, + "dependencies": { + "@ovh-ux/manager-core-api": "^0.9.0", + "@ovh-ux/manager-module-order": "^0.8.0", + "@ovh-ux/manager-react-components": "^1.41.1", + "@ovh-ux/manager-react-shell-client": "^0.8.1" + }, + "devDependencies": { + "@ovh-ux/manager-core-test-utils": "^0.1.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.0.1", + "@vitejs/plugin-react": "^4.3.3", + "@vitest/coverage-v8": "^2.1.4", + "msw": "2.1.7", + "typescript": "^5.1.6", + "vite": "^5.2.13", + "vitest": "^2.1.4" + }, + "peerDependencies": { + "@tanstack/react-query": "5.x", + "react": "18.x" + } +} diff --git a/packages/manager/modules/common-api/src/feature-availability/index.ts b/packages/manager/modules/common-api/src/feature-availability/index.ts new file mode 100644 index 000000000000..f0c6a79a47c8 --- /dev/null +++ b/packages/manager/modules/common-api/src/feature-availability/index.ts @@ -0,0 +1,2 @@ +export * from './useFeatureAvailability'; +export * from './mocks/feature-availability.mock'; diff --git a/packages/manager/modules/common-api/src/feature-availability/mocks/feature-availability.mock.ts b/packages/manager/modules/common-api/src/feature-availability/mocks/feature-availability.mock.ts new file mode 100644 index 000000000000..72a5e026cb3f --- /dev/null +++ b/packages/manager/modules/common-api/src/feature-availability/mocks/feature-availability.mock.ts @@ -0,0 +1,28 @@ +import { Handler } from '@ovh-ux/manager-core-test-utils'; + +export type GetFeatureAvailabilityMocksParams = { + isFeatureAvailabilityServiceKo?: boolean; + featureAvailabilityResponse?: Record; +}; + +export const featureAvailabilityError = 'Feature availability service error'; + +export const getFeatureAvailabilityMocks = ({ + isFeatureAvailabilityServiceKo, + featureAvailabilityResponse, +}: GetFeatureAvailabilityMocksParams): Handler[] => [ + { + url: `/feature/${Object.keys(featureAvailabilityResponse).join( + ',', + )}/availability`, + response: () => + isFeatureAvailabilityServiceKo + ? { + message: featureAvailabilityError, + } + : featureAvailabilityResponse, + status: isFeatureAvailabilityServiceKo ? 500 : 200, + method: 'get', + api: 'aapi', + }, +]; diff --git a/packages/manager/modules/common-api/src/feature-availability/useFeatureAvailability.spec.tsx b/packages/manager/modules/common-api/src/feature-availability/useFeatureAvailability.spec.tsx new file mode 100644 index 000000000000..88a8a550fbf2 --- /dev/null +++ b/packages/manager/modules/common-api/src/feature-availability/useFeatureAvailability.spec.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { waitFor, screen, render } from '@testing-library/react'; +import { SetupServer, setupServer } from 'msw/node'; +import { toMswHandlers } from '@ovh-ux/manager-core-test-utils'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { useFeatureAvailability } from './useFeatureAvailability'; +import '@testing-library/jest-dom'; +import { + getFeatureAvailabilityMocks, + featureAvailabilityError, +} from './mocks/feature-availability.mock'; + +let server: SetupServer | null; + +const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, +}); + +const Example = () => { + const { data, isError, error, isSuccess } = useFeatureAvailability([ + 'feature1', + 'feature2', + ]); + return ( + <> + {isError &&
{error.response?.data.message}
} + {isSuccess && data.feature1 &&
feature1 available
} + {isSuccess && data.feature2 &&
feature2 available
} + + ); +}; + +const setupTest = (useCase: 'error' | 'ok') => { + server = setupServer( + ...toMswHandlers( + getFeatureAvailabilityMocks({ + isFeatureAvailabilityServiceKo: useCase === 'error', + featureAvailabilityResponse: { + feature1: true, + feature2: false, + }, + }), + ), + ); + server.listen({ onUnhandledRequest: 'warn' }); + return render( + + + , + ); +}; + +describe('useFeatureAvailability', () => { + afterEach(() => { + server?.close(); + server = null; + }); + + it('displays an error if the service is KO', async () => { + setupTest('error'); + await waitFor( + () => expect(screen.getByText(featureAvailabilityError)).toBeVisible(), + { timeout: 10000 }, + ); + }); + + it('display only the features that are available', async () => { + setupTest('ok'); + await waitFor( + () => expect(screen.getByText('feature1 available')).toBeVisible(), + { timeout: 10000 }, + ); + expect(screen.queryByText('feature2 available')).not.toBeInTheDocument(); + }); +}); diff --git a/packages/manager/modules/common-api/src/feature-availability/useFeatureAvailability.ts b/packages/manager/modules/common-api/src/feature-availability/useFeatureAvailability.ts new file mode 100644 index 000000000000..1dbeef11e6a9 --- /dev/null +++ b/packages/manager/modules/common-api/src/feature-availability/useFeatureAvailability.ts @@ -0,0 +1,42 @@ +import { apiClient, ApiError } from '@ovh-ux/manager-core-api'; +import { useQuery, UseQueryResult } from '@tanstack/react-query'; + +export type UseFeatureAvailabilityResult< + T = Record +> = UseQueryResult; + +export const fetchFeatureAvailabilityData = async ( + featureList: [...T], +) => { + const result = await apiClient.aapi.get( + `/feature/${featureList.join(',')}/availability`, + ); + + const features = {} as Record; + featureList.forEach((feature) => { + features[feature] = feature in result.data ? result.data[feature] : false; + }); + + return features; +}; + +export const getFeatureAvailabilityQueryKey = ( + featureList: [...T], +) => ['feature-availability', ...featureList]; + +/** + * @examples + * const featureList = ['billing', 'webooo', 'web:microsoft']; + * + * const { data, error, isLoading } = useFeatureAvailability(featureList); + * const isBillingAvailable = data?.billing; + * const isWebooooAvailable = data?.webooo; + * const isMicrosoftAvailable = data.['web:microsoft']; + */ +export const useFeatureAvailability = ( + featureList: [...T], +): UseFeatureAvailabilityResult> => + useQuery, ApiError>({ + queryKey: getFeatureAvailabilityQueryKey(featureList), + queryFn: () => fetchFeatureAvailabilityData(featureList), + }); diff --git a/packages/manager/modules/common-api/src/index.ts b/packages/manager/modules/common-api/src/index.ts new file mode 100644 index 000000000000..a851c65c07ab --- /dev/null +++ b/packages/manager/modules/common-api/src/index.ts @@ -0,0 +1,3 @@ +export * from './feature-availability'; +export * from './services'; +export * from './tasks'; diff --git a/packages/manager/modules/common-api/src/services/api/get.ts b/packages/manager/modules/common-api/src/services/api/get.ts new file mode 100644 index 000000000000..fa8ac3f244fb --- /dev/null +++ b/packages/manager/modules/common-api/src/services/api/get.ts @@ -0,0 +1,24 @@ +import { apiClient } from '@ovh-ux/manager-core-api'; +import { ServiceDetails } from '../services.type'; + +export type GetResourceServiceIdParams = { + /** Filter on a specific service family */ + resourceName: string; +}; + +export const getResourceServiceIdQueryKey = ({ + resourceName = '', +}: GetResourceServiceIdParams) => [`get/services${resourceName}`]; + +/** + * allowedServices operations : List all services allowed by resource + */ +export const getResourceServiceId = async ({ + resourceName, +}: GetResourceServiceIdParams) => + apiClient.v6.get( + `/services${resourceName ? `?resourceName=${resourceName}` : ''}`, + ); + +export const getServiceDetails = async (serviceId: number | string) => + apiClient.v6.get(`/services/${serviceId}`); diff --git a/packages/manager/modules/common-api/src/services/api/index.ts b/packages/manager/modules/common-api/src/services/api/index.ts new file mode 100644 index 000000000000..5255c7318f49 --- /dev/null +++ b/packages/manager/modules/common-api/src/services/api/index.ts @@ -0,0 +1,3 @@ +export * from './get'; +export * from './put'; +export * from './post'; diff --git a/packages/manager/modules/common-api/src/services/api/post.ts b/packages/manager/modules/common-api/src/services/api/post.ts new file mode 100644 index 000000000000..b86901ff3378 --- /dev/null +++ b/packages/manager/modules/common-api/src/services/api/post.ts @@ -0,0 +1,11 @@ +import { apiClient } from '@ovh-ux/manager-core-api'; + +export type DeleteServiceParams = { + serviceId: number; +}; + +/** + * Terminiate a service + */ +export const deleteService = async ({ serviceId }: DeleteServiceParams) => + apiClient.v6.post<{ message: string }>(`/services/${serviceId}/terminate`); diff --git a/packages/manager/modules/common-api/src/services/api/put.ts b/packages/manager/modules/common-api/src/services/api/put.ts new file mode 100644 index 000000000000..9df9b14b9497 --- /dev/null +++ b/packages/manager/modules/common-api/src/services/api/put.ts @@ -0,0 +1,19 @@ +import { apiClient } from '@ovh-ux/manager-core-api'; + +export type UpdateServiceNameParams = { + /** Service id */ + serviceId: number; + /** Service new display name */ + displayName: string; +}; + +/** + * Update a service's display name + */ +export const updateServiceName = async ({ + serviceId, + displayName, +}: UpdateServiceNameParams) => + apiClient.v6.put(`/services/${serviceId}`, { + displayName, + }); diff --git a/packages/manager/modules/common-api/src/services/hooks/index.ts b/packages/manager/modules/common-api/src/services/hooks/index.ts new file mode 100644 index 000000000000..056ede3d9246 --- /dev/null +++ b/packages/manager/modules/common-api/src/services/hooks/index.ts @@ -0,0 +1,3 @@ +export * from './useDeleteService'; +export * from './useUpdateServiceName'; +export * from './useServiceDetails'; diff --git a/packages/manager/modules/common-api/src/services/hooks/useDeleteService.ts b/packages/manager/modules/common-api/src/services/hooks/useDeleteService.ts new file mode 100644 index 000000000000..ad533f1ceed1 --- /dev/null +++ b/packages/manager/modules/common-api/src/services/hooks/useDeleteService.ts @@ -0,0 +1,47 @@ +import { ApiError, ApiResponse } from '@ovh-ux/manager-core-api'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { + getResourceServiceId, + getResourceServiceIdQueryKey, + deleteService, +} from '../api'; + +export type DeleteServiceMutationParams = { + resourceName: string; +}; + +export const deleteServiceMutationKey = ['delete-service']; + +export type UseDeleteServiceParams = { + onSuccess?: () => void; + onError?: (result: ApiError) => void; + mutationKey?: string[]; +}; + +export const useDeleteService = ({ + onSuccess, + onError, + mutationKey = deleteServiceMutationKey, +}: UseDeleteServiceParams) => { + const queryClient = useQueryClient(); + const { mutate: terminateService, ...mutation } = useMutation({ + mutationKey, + mutationFn: async ({ resourceName }: DeleteServiceMutationParams) => { + const { data } = await queryClient.fetchQuery< + ApiResponse, + ApiError + >({ + queryKey: getResourceServiceIdQueryKey({ resourceName }), + queryFn: () => getResourceServiceId({ resourceName }), + }); + return deleteService({ serviceId: data[0] }); + }, + onSuccess, + onError, + }); + + return { + terminateService, + ...mutation, + }; +}; diff --git a/packages/manager/modules/common-api/src/services/hooks/useServiceDetails.ts b/packages/manager/modules/common-api/src/services/hooks/useServiceDetails.ts new file mode 100644 index 000000000000..1ddca46cfa64 --- /dev/null +++ b/packages/manager/modules/common-api/src/services/hooks/useServiceDetails.ts @@ -0,0 +1,38 @@ +import { ApiError, ApiResponse } from '@ovh-ux/manager-core-api'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { + getResourceServiceId, + getResourceServiceIdQueryKey, + getServiceDetails, +} from '../api'; +import { ServiceDetails } from '../services.type'; + +export const getServiceDetailsQueryKey = (resourceName: string) => [ + 'service-details', + resourceName, +]; + +export type UseServiceDetailsParams = { + queryKey?: string[]; + resourceName: string; +}; + +export const useServiceDetails = ({ + queryKey, + resourceName, +}: UseServiceDetailsParams) => { + const queryClient = useQueryClient(); + return useQuery, ApiError>({ + queryKey: queryKey ?? getServiceDetailsQueryKey(resourceName), + queryFn: async () => { + const { data } = await queryClient.fetchQuery< + ApiResponse, + ApiError + >({ + queryKey: getResourceServiceIdQueryKey({ resourceName }), + queryFn: () => getResourceServiceId({ resourceName }), + }); + return getServiceDetails(data[0]); + }, + }); +}; diff --git a/packages/manager/modules/common-api/src/services/hooks/useUpdateServiceName.ts b/packages/manager/modules/common-api/src/services/hooks/useUpdateServiceName.ts new file mode 100644 index 000000000000..64a10712dde9 --- /dev/null +++ b/packages/manager/modules/common-api/src/services/hooks/useUpdateServiceName.ts @@ -0,0 +1,51 @@ +import { ApiError, ApiResponse } from '@ovh-ux/manager-core-api'; +import { useQueryClient, useMutation } from '@tanstack/react-query'; +import { + updateServiceName, + getResourceServiceId, + getResourceServiceIdQueryKey, +} from '../api'; + +export const updateServiceNameMutationKey = ['put/services/displayName']; + +export type UpdateServiceNameMutationParams = { + /** Resource name or id */ + resourceName: string; + /** Resource new display name */ + displayName?: string; +}; + +export type UseUpdateServiceDisplayNameParams = { + onSuccess?: () => void; + onError?: (result: ApiError) => void; + mutationKey?: string[]; +}; + +export const useUpdateServiceDisplayName = ({ + onSuccess, + onError, + mutationKey = updateServiceNameMutationKey, +}: UseUpdateServiceDisplayNameParams) => { + const queryClient = useQueryClient(); + const { mutate: updateDisplayName, ...mutation } = useMutation({ + mutationKey, + mutationFn: async ({ + resourceName, + displayName, + }: UpdateServiceNameMutationParams) => { + const { data: servicesId } = await queryClient.fetchQuery< + ApiResponse + >({ + queryKey: getResourceServiceIdQueryKey({ resourceName }), + queryFn: () => getResourceServiceId({ resourceName }), + }); + return updateServiceName({ serviceId: servicesId[0], displayName }); + }, + onSuccess, + onError, + }); + return { + updateDisplayName, + ...mutation, + }; +}; diff --git a/packages/manager/modules/common-api/src/services/index.ts b/packages/manager/modules/common-api/src/services/index.ts new file mode 100644 index 000000000000..904ba7a8685f --- /dev/null +++ b/packages/manager/modules/common-api/src/services/index.ts @@ -0,0 +1,4 @@ +export * from './api'; +export * from './hooks'; +export * from './mocks'; +export * from './services.type'; diff --git a/packages/manager/modules/common-api/src/services/mocks/index.ts b/packages/manager/modules/common-api/src/services/mocks/index.ts new file mode 100644 index 000000000000..09a58a2df3d4 --- /dev/null +++ b/packages/manager/modules/common-api/src/services/mocks/index.ts @@ -0,0 +1,2 @@ +export * from './services.handler'; +export * from './services.mock'; diff --git a/packages/manager/modules/common-api/src/services/mocks/services.handler.ts b/packages/manager/modules/common-api/src/services/mocks/services.handler.ts new file mode 100644 index 000000000000..0688d8d77f7d --- /dev/null +++ b/packages/manager/modules/common-api/src/services/mocks/services.handler.ts @@ -0,0 +1,68 @@ +import { Handler } from '@ovh-ux/manager-core-test-utils'; +import { ServiceDetails } from '../services.type'; +import { defaultServiceResponse, servicesMockErrors } from './services.mock'; + +export type GetServicesMocksParams = { + getServicesKo?: boolean; + getDetailsServicesKo?: boolean; + updateServicesKo?: boolean; + deleteServicesKo?: boolean; + serviceResponse?: ServiceDetails; +}; + +export const getServicesMocks = ({ + getServicesKo, + getDetailsServicesKo, + updateServicesKo, + deleteServicesKo, + serviceResponse = defaultServiceResponse, +}: GetServicesMocksParams): Handler[] => [ + { + url: '/services/:id/terminate', + response: () => + deleteServicesKo + ? { + message: servicesMockErrors.delete, + } + : null, + status: deleteServicesKo ? 500 : 200, + method: 'post', + api: 'v6', + }, + { + url: '/services/:id', + response: () => + updateServicesKo + ? { + message: servicesMockErrors.update, + } + : null, + status: updateServicesKo ? 500 : 200, + method: 'put', + api: 'v6', + }, + { + url: '/services/:id', + response: () => + getDetailsServicesKo + ? { + message: servicesMockErrors.getDetails, + } + : serviceResponse, + status: getDetailsServicesKo ? 500 : 200, + method: 'get', + api: 'v6', + }, + { + url: '/services', + response: () => + getServicesKo + ? { + message: servicesMockErrors.get, + } + : [1234567890], + status: getServicesKo ? 500 : 200, + method: 'get', + api: 'v6', + }, +]; diff --git a/packages/manager/modules/common-api/src/services/mocks/services.mock.ts b/packages/manager/modules/common-api/src/services/mocks/services.mock.ts new file mode 100644 index 000000000000..73ac7ce6657d --- /dev/null +++ b/packages/manager/modules/common-api/src/services/mocks/services.mock.ts @@ -0,0 +1,96 @@ +import { CurrencyCode } from '@ovh-ux/manager-react-components'; +import { ServiceDetails } from '../services.type'; + +export const servicesMockErrors = { + delete: 'Delete services error', + update: 'Update services error', + get: 'Get services error', + getDetails: 'Get services details error', +}; + +export const defaultServiceResponse: ServiceDetails = { + route: { + path: '/api/path/{id}', + url: '/api/path/id', + vars: [ + { + key: 'id', + value: 'id', + }, + ], + }, + billing: { + nextBillingDate: '2024-11-21T09:03:18Z', + expirationDate: '2024-11-21T09:03:18Z', + plan: { + code: 'code', + invoiceName: 'invoiceName', + }, + pricing: { + capacities: ['renew'], + description: 'Installation pricing', + interval: 1, + duration: 'P1M', + minimumQuantity: 1, + maximumQuantity: null, + minimumRepeat: 1, + maximumRepeat: null, + price: { currencyCode: CurrencyCode.EUR, text: '0.00 €', value: 0 }, + priceInUcents: 0, + pricingMode: 'default', + pricingType: 'rental', + engagementConfiguration: null, + }, + group: null, + lifecycle: { + current: { + pendingActions: [], + terminationDate: null, + creationDate: '2024-10-21T09:03:18Z', + state: 'active', + }, + capacities: { + actions: ['earlyRenewal', 'terminateAtExpirationDate'], + }, + }, + renew: { + current: { + mode: 'automatic', + nextDate: '2024-11-21T09:03:18Z', + period: 'P1M', + }, + capacities: { mode: ['automatic', 'manual'] }, + }, + engagement: null, + engagementRequest: null, + }, + resource: { + displayName: 'Test', + name: 'id-test', + state: 'active', + product: { + name: 'test', + description: 'description', + }, + resellingProvider: null, + }, + serviceId: 1234567890, + parentServiceId: null, + customer: { + contacts: [ + { + customerCode: 'adminCustomerCode', + type: 'administrator', + }, + { + customerCode: 'technicalCustomerCode', + type: 'technical', + }, + { + customerCode: 'billingCustomerCode', + type: 'billing', + }, + ], + }, + tags: [], +}; diff --git a/packages/manager/modules/common-api/src/services/services.type.ts b/packages/manager/modules/common-api/src/services/services.type.ts new file mode 100644 index 000000000000..e5261d2e253d --- /dev/null +++ b/packages/manager/modules/common-api/src/services/services.type.ts @@ -0,0 +1,148 @@ +import { CurrencyCode } from '@ovh-ux/manager-react-components'; + +export type EndRuleStrategy = + | 'CANCEL_SERVICE' + | 'REACTIVATE_ENGAGEMENT' + | 'STOP_ENGAGEMENT_FALLBACK_DEFAULT_PRICE' + | 'STOP_ENGAGEMENT_KEEP_PRICE'; + +export type LifecycleAction = + | 'earlyRenewal' + | 'terminate' + | 'terminateAtEngagementDate' + | 'terminateAtExpirationDate'; + +export type LifecycleState = + | 'active' + | 'error' + | 'rupture' + | 'terminated' + | 'toRenew' + | 'unpaid' + | 'unrenewed'; + +export type PricingCapacity = + | 'consumption' + | 'detach' + | 'downgrade' + | 'dynamic' + | 'installation' + | 'renew' + | 'upgrade'; + +export type EndAction = + | 'CANCEL_SERVICE' + | 'REACTIVATE_ENGAGEMENT' + | 'STOP_ENGAGEMENT_FALLBACK_DEFAULT_PRICE' + | 'STOP_ENGAGEMENT_KEEP_PRICE'; + +export type RenewMode = 'automatic' | 'manual'; + +export type EngagementType = 'periodic' | 'upfront'; + +export type PricingType = 'consumption' | 'purchase' | 'rental'; + +export type CustomerContact = { + customerCode: string; + type: 'administrator' | 'billing' | 'technical'; +}; + +export type ResourceStatus = + | 'active' + | 'deleted' + | 'suspended' + | 'toActivate' + | 'toDelete' + | 'toSuspend'; + +export type ServiceDetails = { + billing: { + engagement: { + endDate: string | null; + /** + * Describes the rule applied at the end of the Engagement + */ + endRule: { + possibleStrategies: EndRuleStrategy[]; + strategy: EndRuleStrategy; + } | null; + } | null; + engagementRequest: { + pricingMode: string; + requestDate: string; + }; + expirationDate: string; + group: { + id: number; + }; + lifecycle: { + capacities: { + actions: LifecycleAction[]; + }; + current: { + creationDate: string; + pendingActions: LifecycleAction[]; + state: LifecycleState; + terminationDate: string; + }; + }; + nextBillingDate: string; + plan: { + code: string; + invoiceName: string; + }; + pricing: { + capacities: PricingCapacity[]; + description: string; + duration: string; + engagementConfiguration: { + defaultEndAction: EndAction; + duration: string; + type: EngagementType; + }; + interval: number; + maximumQuantity: number | null; + maximumRepeat: number | null; + minimumQuantity: number; + minimumRepeat: number; + price: { + currencyCode: CurrencyCode; + priceInUcents?: number | null; + text: string; + value: number; + }; + priceInUcents?: number; + pricingMode: string; + pricingType: PricingType; + }; + renew: { + capacities: { mode: RenewMode[] }; + current: { + mode: RenewMode | null; + nextDate: string | null; + period: string; + }; + }; + }; + customer: { + contacts: CustomerContact[]; + }; + parentServiceId: number | null; + resource: { + displayName: string; + name: string; + product: { + description: string; + name: string; + }; + resellingProvider: 'ovh.ca' | 'ovh.eu'; + state: ResourceStatus; + }; + route: { + path: string; + url: string; + vars: { key: string; value: string }[]; + }; + serviceId: number; + tags: string[]; +}; diff --git a/packages/manager/modules/common-api/src/tasks/hooks/useTask.spec.tsx b/packages/manager/modules/common-api/src/tasks/hooks/useTask.spec.tsx new file mode 100644 index 000000000000..e93f135cb853 --- /dev/null +++ b/packages/manager/modules/common-api/src/tasks/hooks/useTask.spec.tsx @@ -0,0 +1,210 @@ +import React from 'react'; +import { vi } from 'vitest'; +import { waitFor, screen, render } from '@testing-library/react'; +import { SetupServer, setupServer } from 'msw/node'; +import { toMswHandlers } from '@ovh-ux/manager-core-test-utils'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { useTask, UseTaskParams } from './useTask'; +import '@testing-library/jest-dom'; + +const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, +}); + +let server: SetupServer; + +const taskId = '1234'; +const errorMessage = 'error'; +const successMessage = 'success'; +const pendingMessage = 'pending'; + +const Example = (props: Partial) => { + const { isError, isPending, isSuccess } = useTask({ + ...props, + resourceUrl: '/example', + refetchIntervalTime: 10, + }); + return ( + <> + {isError &&
{errorMessage}
} + {isSuccess &&
{successMessage}
} + {isPending &&
{pendingMessage}
} + + ); +}; + +const setupTest = ({ + apiVersion, + status, +}: { + apiVersion: 'v2' | 'v6'; + status: 'RUNNING' | 'DONE' | 'ERROR'; +}) => { + server = setupServer( + ...toMswHandlers([ + { + api: 'v2', + url: `/example/task/${taskId}`, + response: { + status: 'RUNNING', + }, + status: 200, + once: true, + }, + { + api: 'v2', + url: `/example/task/${taskId}`, + response: { + status, + }, + status: 200, + }, + { + api: 'v6', + url: `/example/task/${taskId}`, + response: + status === 'ERROR' + ? { + message: errorMessage, + } + : {}, + status: 200, + once: true, + }, + { + api: 'v6', + url: `/example/task/${taskId}`, + response: + status === 'ERROR' + ? { + message: errorMessage, + } + : {}, + status: (() => { + if (status === 'RUNNING') { + return 200; + } + return status === 'ERROR' ? 500 : 404; + })(), + }, + ]), + ); + + server.listen({ onUnhandledRequest: 'warn' }); + + const onSuccess = vi.fn(); + const onError = vi.fn(); + const onFinish = vi.fn(); + + const result = render( + + + , + ); + return { + ...result, + onSuccess, + onError, + onFinish, + }; +}; + +describe('useTask', () => { + afterEach(() => { + server?.close(); + }); + + describe('API v6 tasks', () => { + it('is pending while the task returns a 200 response', async () => { + const { onError, onFinish, onSuccess } = setupTest({ + apiVersion: 'v6', + status: 'RUNNING', + }); + await waitFor( + () => expect(screen.getByText(pendingMessage)).toBeVisible(), + { timeout: 5000 }, + ); + expect(onFinish).not.toHaveBeenCalled(); + expect(onError).not.toHaveBeenCalled(); + expect(onSuccess).not.toHaveBeenCalled(); + }); + + it('is successful if the task returns a 404', async () => { + const { onError, onFinish, onSuccess } = setupTest({ + apiVersion: 'v6', + status: 'DONE', + }); + await waitFor( + () => expect(screen.getByText(successMessage)).toBeVisible(), + { timeout: 5000 }, + ); + expect(onFinish).toHaveBeenCalledTimes(1); + expect(onError).not.toHaveBeenCalled(); + expect(onSuccess).toHaveBeenCalledTimes(1); + }); + + it('is in error if the task returns a 500', async () => { + const { onError, onFinish, onSuccess } = setupTest({ + apiVersion: 'v6', + status: 'ERROR', + }); + await waitFor( + () => expect(screen.getByText(errorMessage)).toBeVisible(), + { timeout: 5000 }, + ); + expect(onFinish).toHaveBeenCalledTimes(1); + expect(onError).toHaveBeenCalledTimes(1); + expect(onSuccess).not.toHaveBeenCalled(); + }); + }); + + describe('API v2 tasks', () => { + it('is pending while the task has a RUNNING status', async () => { + const { onError, onFinish, onSuccess } = setupTest({ + apiVersion: 'v2', + status: 'RUNNING', + }); + await waitFor( + () => expect(screen.getByText(pendingMessage)).toBeVisible(), + { timeout: 5000 }, + ); + expect(onFinish).not.toHaveBeenCalled(); + expect(onError).not.toHaveBeenCalled(); + expect(onSuccess).not.toHaveBeenCalled(); + }); + + it('is successful if the task has a DONE status', async () => { + const { onError, onFinish, onSuccess } = setupTest({ + apiVersion: 'v2', + status: 'DONE', + }); + await waitFor( + () => expect(screen.getByText(successMessage)).toBeVisible(), + { timeout: 5000 }, + ); + expect(onFinish).toHaveBeenCalledTimes(1); + expect(onError).not.toHaveBeenCalled(); + expect(onSuccess).toHaveBeenCalledTimes(1); + }); + + it('is in error if the task has an ERROR status', async () => { + const { onError, onFinish, onSuccess } = setupTest({ + apiVersion: 'v2', + status: 'ERROR', + }); + await waitFor( + () => expect(screen.getByText(errorMessage)).toBeVisible(), + { timeout: 5000 }, + ); + expect(onFinish).toHaveBeenCalledTimes(1); + expect(onError).toHaveBeenCalledTimes(1); + expect(onSuccess).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/manager/modules/common-api/src/tasks/hooks/useTask.ts b/packages/manager/modules/common-api/src/tasks/hooks/useTask.ts new file mode 100644 index 000000000000..e94585285e0b --- /dev/null +++ b/packages/manager/modules/common-api/src/tasks/hooks/useTask.ts @@ -0,0 +1,105 @@ +import React from 'react'; +import { ApiError, ApiResponse, apiClient } from '@ovh-ux/manager-core-api'; +import { useQuery } from '@tanstack/react-query'; + +export type UseTaskParams = { + resourceUrl: string; + apiVersion?: 'v2' | 'v6'; + taskId?: number | string; + queryKey?: string[]; + onSuccess?: () => void; + onError?: () => void; + onFinish?: () => void; + refetchIntervalTime?: number; +}; + +export const getDefaultQueryKey = (taskId: number | string) => [ + 'manage-task', + taskId, +]; + +export const useTask = ({ + resourceUrl, + apiVersion = 'v2', + taskId, + queryKey, + onSuccess, + onError, + onFinish, + refetchIntervalTime = 2000, +}: UseTaskParams) => { + const [isSuccess, setIsSuccess] = React.useState(false); + const [isPending, setIsPending] = React.useState(false); + const [isError, setIsError] = React.useState(false); + + const { error } = useQuery< + ApiResponse<{ status: 'DONE' | 'PENDING' | 'RUNNING' }>, + ApiError + >({ + staleTime: 0, + queryKey: queryKey || getDefaultQueryKey(taskId || resourceUrl), + queryFn: async () => { + const url = `/${resourceUrl + .split('/') + .filter(Boolean) + .concat(['task', taskId as string]) + .join('/')}`; + + try { + setIsPending(true); + const result = await apiClient[apiVersion].get(url); + if (apiVersion === 'v2') { + if (result.data?.status === 'DONE') { + setIsPending(false); + setIsSuccess(true); + setIsError(false); + onSuccess?.(); + onFinish?.(); + } + if (result.data?.status === 'ERROR') { + setIsPending(false); + setIsSuccess(false); + setIsError(true); + onError?.(); + onFinish?.(); + throw result; + } + } + return result; + } catch (err) { + if (apiVersion === 'v6') { + if (err?.response?.status === 404) { + setIsPending(false); + setIsSuccess(true); + setIsError(false); + onSuccess?.(); + } else { + setIsPending(false); + setIsError(true); + setIsSuccess(false); + onError?.(); + } + onFinish?.(); + } + throw err; + } + }, + enabled: !!taskId, + retry: false, + refetchInterval: (query) => { + if (apiVersion === 'v6') { + return query.state.status !== 'error' ? refetchIntervalTime : undefined; + } + return !['DONE', 'ERROR'].includes(query.state.data?.data?.status) + ? refetchIntervalTime + : undefined; + }, + }); + + return { + error, + isError, + isPending, + isSuccess, + }; +}; diff --git a/packages/manager/modules/common-api/src/tasks/index.ts b/packages/manager/modules/common-api/src/tasks/index.ts new file mode 100644 index 000000000000..f11f14389f28 --- /dev/null +++ b/packages/manager/modules/common-api/src/tasks/index.ts @@ -0,0 +1 @@ +export * from './hooks/useTask'; diff --git a/packages/manager/modules/common-api/tsconfig.json b/packages/manager/modules/common-api/tsconfig.json new file mode 100644 index 000000000000..130eeca92488 --- /dev/null +++ b/packages/manager/modules/common-api/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "lib": ["dom", "es2021"], + "target": "es2021", + "types": ["vite/client", "node", "jest"], + "module": "ES2020", + "outDir": "dist", + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "moduleResolution": "Node", + "isolatedModules": true, + "declaration": true, + "declarationDir": "./dist/types", + "jsx": "react-jsx" + }, + "exclude": [ + "node_modules", + "dist", + "types", + "**/__tests__", + "src/**/*.spec.ts" + ] +} diff --git a/packages/manager/modules/common-api/vitest.config.js b/packages/manager/modules/common-api/vitest.config.js new file mode 100644 index 000000000000..48d8a1344ec6 --- /dev/null +++ b/packages/manager/modules/common-api/vitest.config.js @@ -0,0 +1,29 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + test: { + globals: true, + environment: 'jsdom', + coverage: { + include: ['src'], + exclude: [], + }, + testTimeout: 60_000, + fileParallelism: false, + maxWorkers: 1, + pollOptions: { + forks: { + singleFork: true, + }, + threads: { + singleThread: true, + }, + }, + }, + resolve: { + mainFields: ['module'], + }, +}); diff --git a/yarn.lock b/yarn.lock index 973abdeb1c23..439f59cca37c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5922,38 +5922,18 @@ integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg== "@orchidjs/sifter@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@orchidjs/sifter/-/sifter-1.0.3.tgz#43f42519472282eb632d0a1589184f044d64129b" - integrity sha512-zCZbwKegHytfsPm8Amcfh7v/4vHqTAaOu6xFswBYcn8nznBOuseu6COB2ON7ez0tFV0mKL0nRNnCiZZA+lU9/g== + version "1.1.0" + resolved "https://registry.yarnpkg.com/@orchidjs/sifter/-/sifter-1.1.0.tgz#b36154ad0cda4898305d1ac44f318b41048a0438" + integrity sha512-mYwHCfr736cIWWdhhSZvDbf90AKt2xyrJspKFC3qyIJG1LtrJeJunYEqCGG4Aq2ijENbc4WkOjszcvNaIAS/pQ== dependencies: - "@orchidjs/unicode-variants" "^1.0.4" - -"@orchidjs/unicode-variants@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@orchidjs/unicode-variants/-/unicode-variants-1.0.4.tgz#6d2f812e3b19545bba2d81caffff1204de9a6a58" - integrity sha512-NvVBRnZNE+dugiXERFsET1JlKZfM5lJDEpSMilKW4bToYJ7pxf0Zne78xyXB2ny2c2aHfJ6WLnz1AaTNHAmQeQ== + "@orchidjs/unicode-variants" "^1.1.2" -"@ovh-ux/manager-react-components@^1.41.1": - version "1.41.1" - resolved "https://registry.yarnpkg.com/@ovh-ux/manager-react-components/-/manager-react-components-1.41.1.tgz#da0195c82fdf26e101f47c11d5b604c90547d810" - integrity sha512-aol1jycPupdDEFQ1IXibwvy5+YilUTvPEaASrePy3iNPozAQ/sXaweRJuUJoNqk77p/6AF2NGygV8R/MNOjcYg== - dependencies: - "@ovhcloud/ods-common-core" "17.2.2" - "@ovhcloud/ods-common-theming" "17.2.2" - "@ovhcloud/ods-components" "17.2.2" - "@ovhcloud/ods-theme-blue-jeans" "17.2.2" - "@tanstack/react-query" "^5.51.21" - "@tanstack/react-table" "^8.20.1" - clsx "^2.1.1" - lodash.isdate "^4.0.1" - lodash.isequal "^4.5.0" - react-i18next "^14.0.5" - react-use "^17.5.0" - sass "1.71.0" - tailwindcss "^3.4.4" - uuid "^9.0.1" +"@orchidjs/unicode-variants@^1.0.4", "@orchidjs/unicode-variants@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@orchidjs/unicode-variants/-/unicode-variants-1.1.2.tgz#1fd71791a67fdd1591ebe0dcaadd3964537a824e" + integrity sha512-5DobW1CHgnBROOEpFlEXytED5OosEWESFvg/VYmH0143oXcijYTprRYJTs+55HzGM4IqxiLFSuqEzu9mPNwVsA== -"@ovh-ux/manager-react-components@^1.41.2": +"@ovh-ux/manager-react-components@^1.41.1", "@ovh-ux/manager-react-components@^1.41.2": version "1.41.2" resolved "https://registry.yarnpkg.com/@ovh-ux/manager-react-components/-/manager-react-components-1.41.2.tgz#087cacbbff37c594201851f5f27d2a0c524545b5" integrity sha512-Yl7lWtGGqw3CUeVMaCMwkoTWomxlbCT2hD+Q2h8zgclnsyfEbTvQT6iFVpueiFwhTikCRm+oFsbRy3gL8XOOzw== @@ -8671,6 +8651,19 @@ lodash "^4.17.21" redent "^3.0.0" +"@testing-library/jest-dom@^6.6.3": + version "6.6.3" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz#26ba906cf928c0f8172e182c6fe214eb4f9f2bd2" + integrity sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA== + dependencies: + "@adobe/css-tools" "^4.4.0" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + lodash "^4.17.21" + redent "^3.0.0" + "@testing-library/react-hooks@^8.0.1": version "8.0.1" resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12" @@ -9744,6 +9737,17 @@ "@types/babel__core" "^7.20.5" react-refresh "^0.14.2" +"@vitejs/plugin-react@^4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz#28301ac6d7aaf20b73a418ee5c65b05519b4836c" + integrity sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA== + dependencies: + "@babel/core" "^7.25.2" + "@babel/plugin-transform-react-jsx-self" "^7.24.7" + "@babel/plugin-transform-react-jsx-source" "^7.24.7" + "@types/babel__core" "^7.20.5" + react-refresh "^0.14.2" + "@vitejs/plugin-vue@^5.0.4": version "5.1.0" resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.1.0.tgz#d29f2aad9127c73b578e7a463e76249e89256e0b" @@ -9841,6 +9845,33 @@ test-exclude "^7.0.1" tinyrainbow "^1.2.0" +"@vitest/coverage-v8@^2.1.4": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.1.5.tgz#74ef3bf6737f9897a54af22f820d90e85883ff83" + integrity sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw== + dependencies: + "@ampproject/remapping" "^2.3.0" + "@bcoe/v8-coverage" "^0.2.3" + debug "^4.3.7" + istanbul-lib-coverage "^3.2.2" + istanbul-lib-report "^3.0.1" + istanbul-lib-source-maps "^5.0.6" + istanbul-reports "^3.1.7" + magic-string "^0.30.12" + magicast "^0.3.5" + std-env "^3.8.0" + test-exclude "^7.0.1" + tinyrainbow "^1.2.0" + +"@vitest/expect@1.3.1": + version "1.3.1" + resolved "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz#d4c14b89c43a25fd400a6b941f51ba27fe0cb918" + integrity sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw== + dependencies: + "@vitest/spy" "1.3.1" + "@vitest/utils" "1.3.1" + chai "^4.3.10" + "@vitest/expect@1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.4.0.tgz#d64e17838a20007fecd252397f9b96a1ca81bfb0" @@ -9889,6 +9920,16 @@ chai "^5.1.2" tinyrainbow "^1.2.0" +"@vitest/expect@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.5.tgz#5a6afa6314cae7a61847927bb5bc038212ca7381" + integrity sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q== + dependencies: + "@vitest/spy" "2.1.5" + "@vitest/utils" "2.1.5" + chai "^5.1.2" + tinyrainbow "^1.2.0" + "@vitest/mocker@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.3.tgz#a3593b426551be5715fa108faf04f8a9ddb0a9cc" @@ -9907,6 +9948,15 @@ estree-walker "^3.0.3" magic-string "^0.30.12" +"@vitest/mocker@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.5.tgz#54ee50648bc0bb606dfc58e13edfacb8b9208324" + integrity sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ== + dependencies: + "@vitest/spy" "2.1.5" + estree-walker "^3.0.3" + magic-string "^0.30.12" + "@vitest/pretty-format@2.0.5", "@vitest/pretty-format@^2.0.5": version "2.0.5" resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.0.5.tgz#91d2e6d3a7235c742e1a6cc50e7786e2f2979b1e" @@ -9928,6 +9978,13 @@ dependencies: tinyrainbow "^1.2.0" +"@vitest/pretty-format@2.1.5", "@vitest/pretty-format@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.5.tgz#bc79b8826d4a63dc04f2a75d2944694039fa50aa" + integrity sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw== + dependencies: + tinyrainbow "^1.2.0" + "@vitest/runner@1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.4.0.tgz#907c2d17ad5975b70882c25ab7a13b73e5a28da9" @@ -9970,6 +10027,14 @@ "@vitest/utils" "2.1.4" pathe "^1.1.2" +"@vitest/runner@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.5.tgz#4d5e2ba2dfc0af74e4b0f9f3f8be020559b26ea9" + integrity sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g== + dependencies: + "@vitest/utils" "2.1.5" + pathe "^1.1.2" + "@vitest/snapshot@1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.4.0.tgz#2945b3fb53767a3f4f421919e93edfef2935b8bd" @@ -10015,6 +10080,22 @@ magic-string "^0.30.12" pathe "^1.1.2" +"@vitest/snapshot@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.5.tgz#a09a8712547452a84e08b3ec97b270d9cc156b4f" + integrity sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg== + dependencies: + "@vitest/pretty-format" "2.1.5" + magic-string "^0.30.12" + pathe "^1.1.2" + +"@vitest/spy@1.3.1": + version "1.3.1" + resolved "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz#814245d46d011b99edd1c7528f5725c64e85a88b" + integrity sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig== + dependencies: + tinyspy "^2.2.0" + "@vitest/spy@1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.4.0.tgz#cf953c93ae54885e801cbe6b408a547ae613f26c" @@ -10050,6 +10131,13 @@ dependencies: tinyspy "^3.0.2" +"@vitest/spy@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.5.tgz#f790d1394a5030644217ce73562e92465e83147e" + integrity sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw== + dependencies: + tinyspy "^3.0.2" + "@vitest/ui@^1.4.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@vitest/ui/-/ui-1.6.0.tgz#ffcc97ebcceca7fec840c29ab68632d0cd01db93" @@ -10111,6 +10199,15 @@ loupe "^3.1.2" tinyrainbow "^1.2.0" +"@vitest/utils@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.5.tgz#0e19ce677c870830a1573d33ee86b0d6109e9546" + integrity sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg== + dependencies: + "@vitest/pretty-format" "2.1.5" + loupe "^3.1.2" + tinyrainbow "^1.2.0" + "@volar/language-core@1.11.1", "@volar/language-core@~1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.11.1.tgz#ecdf12ea8dc35fb8549e517991abcbf449a5ad4f" @@ -14718,6 +14815,11 @@ es-module-lexer@^1.2.1: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.0.tgz#4878fee3789ad99e065f975fdd3c645529ff0236" integrity sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw== +es-module-lexer@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== + es-object-atoms@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" @@ -25989,6 +26091,11 @@ std-env@^3.3.3, std-env@^3.5.0, std-env@^3.7.0: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== +std-env@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5" + integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w== + stdin-discarder@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.1.0.tgz#22b3e400393a8e28ebf53f9958f3880622efde21" @@ -28211,6 +28318,17 @@ vite-node@2.1.4: pathe "^1.1.2" vite "^5.0.0" +vite-node@2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.5.tgz#cf28c637b2ebe65921f3118a165b7cf00a1cdf19" + integrity sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w== + dependencies: + cac "^6.7.14" + debug "^4.3.7" + es-module-lexer "^1.5.4" + pathe "^1.1.2" + vite "^5.0.0" + vite-plugin-dts@3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/vite-plugin-dts/-/vite-plugin-dts-3.5.1.tgz#58c225f7ecabff2ed76027e003e1ec8ca964a078" @@ -28457,6 +28575,32 @@ vitest@^2.1.3: vite-node "2.1.4" why-is-node-running "^2.3.0" +vitest@^2.1.4: + version "2.1.5" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.5.tgz#a93b7b84a84650130727baae441354e6df118148" + integrity sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A== + dependencies: + "@vitest/expect" "2.1.5" + "@vitest/mocker" "2.1.5" + "@vitest/pretty-format" "^2.1.5" + "@vitest/runner" "2.1.5" + "@vitest/snapshot" "2.1.5" + "@vitest/spy" "2.1.5" + "@vitest/utils" "2.1.5" + chai "^5.1.2" + debug "^4.3.7" + expect-type "^1.1.0" + magic-string "^0.30.12" + pathe "^1.1.2" + std-env "^3.8.0" + tinybench "^2.9.0" + tinyexec "^0.3.1" + tinypool "^1.0.1" + tinyrainbow "^1.2.0" + vite "^5.0.0" + vite-node "2.1.5" + why-is-node-running "^2.3.0" + void-elements@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" From e35a1e5580117e08be7b5bc7dfb4bc092be27856 Mon Sep 17 00:00:00 2001 From: Nicolas Pierre-charles Date: Fri, 29 Nov 2024 10:02:08 +0100 Subject: [PATCH 2/3] fix(*): dependency fixes ref: MANAGER-15700 Signed-off-by: Nicolas Pierre-charles --- packages/manager/core/test-utils/package.json | 25 +++++++------------ .../manager/modules/common-api/package.json | 7 +----- .../src/services/mocks/services.mock.ts | 3 +-- .../common-api/src/services/services.type.ts | 18 ++++++++++++- 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/packages/manager/core/test-utils/package.json b/packages/manager/core/test-utils/package.json index 03d3db9004f8..6ca338078928 100644 --- a/packages/manager/core/test-utils/package.json +++ b/packages/manager/core/test-utils/package.json @@ -11,37 +11,30 @@ "license": "BSD-3-Clause", "author": "OVH SAS", "sideEffects": false, - "main": "./dist/index.js", - "module": "./dist/index.js", - "types": "./dist/types/index.d.ts", - "files": [ - "dist" - ], + "main": "./index.ts", + "module": "./index.ts", + "types": "./index.ts", "scripts": { - "build": "tsc", + "build": "echo core-test-utils", "dev": "tsc", "start:watch": "tsc -w" }, - "dependencies": { - "@ovh-ux/manager-core-api": "^0.9.0", - "@ovh-ux/manager-react-components": "^1.41.1", - "@ovh-ux/manager-react-shell-client": "^0.8.1", - "@ovhcloud/ods-common-core": "^17.2.2", - "@ovhcloud/ods-components": "^17.2.2", + "devDependencies": { "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^14.5.2", "element-internals-polyfill": "^1.3.12", - "i18next": "^23.8.2" - }, - "devDependencies": { "msw": "2.1.7", "typescript": "^5.1.6", "vite": "^5.2.13", "vitest": "^2.1.4" }, "peerDependencies": { + "@ovh-ux/manager-core-api": "^0.9.0", + "@ovhcloud/ods-common-core": "17.x", + "@ovhcloud/ods-components": "17.x", "@tanstack/react-query": "5.x", + "i18next": "23.x", "react": "18.x" } } diff --git a/packages/manager/modules/common-api/package.json b/packages/manager/modules/common-api/package.json index 9d5f1871a8e1..8f266ebf8af2 100644 --- a/packages/manager/modules/common-api/package.json +++ b/packages/manager/modules/common-api/package.json @@ -24,12 +24,6 @@ "test": "vitest run", "test:coverage": "vitest run --coverage" }, - "dependencies": { - "@ovh-ux/manager-core-api": "^0.9.0", - "@ovh-ux/manager-module-order": "^0.8.0", - "@ovh-ux/manager-react-components": "^1.41.1", - "@ovh-ux/manager-react-shell-client": "^0.8.1" - }, "devDependencies": { "@ovh-ux/manager-core-test-utils": "^0.1.0", "@testing-library/jest-dom": "^6.6.3", @@ -42,6 +36,7 @@ "vitest": "^2.1.4" }, "peerDependencies": { + "@ovh-ux/manager-core-api": "^0.9.0", "@tanstack/react-query": "5.x", "react": "18.x" } diff --git a/packages/manager/modules/common-api/src/services/mocks/services.mock.ts b/packages/manager/modules/common-api/src/services/mocks/services.mock.ts index 73ac7ce6657d..81228a4f8045 100644 --- a/packages/manager/modules/common-api/src/services/mocks/services.mock.ts +++ b/packages/manager/modules/common-api/src/services/mocks/services.mock.ts @@ -1,5 +1,4 @@ -import { CurrencyCode } from '@ovh-ux/manager-react-components'; -import { ServiceDetails } from '../services.type'; +import { CurrencyCode, ServiceDetails } from '../services.type'; export const servicesMockErrors = { delete: 'Delete services error', diff --git a/packages/manager/modules/common-api/src/services/services.type.ts b/packages/manager/modules/common-api/src/services/services.type.ts index e5261d2e253d..77b67691ab10 100644 --- a/packages/manager/modules/common-api/src/services/services.type.ts +++ b/packages/manager/modules/common-api/src/services/services.type.ts @@ -1,4 +1,20 @@ -import { CurrencyCode } from '@ovh-ux/manager-react-components'; +export enum CurrencyCode { + AUD = 'AUD', + CAD = 'CAD', + CZK = 'CZK', + EUR = 'EUR', + GBP = 'GBP', + INR = 'INR', + MAD = 'MAD', + PLN = 'PLN', + SGD = 'SGD', + USD = 'USD', + TND = 'TND', + XOF = 'XOF', + LTL = 'LTL', + NA = 'N/A', + points = 'points', +} export type EndRuleStrategy = | 'CANCEL_SERVICE' From 837efb68f5403705432da3f97aae4121294d064c Mon Sep 17 00:00:00 2001 From: Nicolas Pierre-charles Date: Tue, 3 Dec 2024 09:50:53 +0100 Subject: [PATCH 3/3] fix(common-api): pr feedbacks ref: MANAGER-15700 Signed-off-by: Nicolas Pierre-charles --- .../mocks/feature-availability.mock.ts | 2 +- .../useFeatureAvailability.ts | 13 ++++++++- .../src/services/hooks/useDeleteService.ts | 24 ++++++++------- .../src/services/hooks/useServiceDetails.ts | 11 ++++++- .../services/hooks/useUpdateServiceName.ts | 29 +++++++++---------- .../common-api/src/services/services.type.ts | 10 +++---- .../common-api/src/tasks/hooks/useTask.ts | 2 +- 7 files changed, 56 insertions(+), 35 deletions(-) diff --git a/packages/manager/modules/common-api/src/feature-availability/mocks/feature-availability.mock.ts b/packages/manager/modules/common-api/src/feature-availability/mocks/feature-availability.mock.ts index 72a5e026cb3f..37a0f157070d 100644 --- a/packages/manager/modules/common-api/src/feature-availability/mocks/feature-availability.mock.ts +++ b/packages/manager/modules/common-api/src/feature-availability/mocks/feature-availability.mock.ts @@ -9,7 +9,7 @@ export const featureAvailabilityError = 'Feature availability service error'; export const getFeatureAvailabilityMocks = ({ isFeatureAvailabilityServiceKo, - featureAvailabilityResponse, + featureAvailabilityResponse = {}, }: GetFeatureAvailabilityMocksParams): Handler[] => [ { url: `/feature/${Object.keys(featureAvailabilityResponse).join( diff --git a/packages/manager/modules/common-api/src/feature-availability/useFeatureAvailability.ts b/packages/manager/modules/common-api/src/feature-availability/useFeatureAvailability.ts index 1dbeef11e6a9..9ca029500975 100644 --- a/packages/manager/modules/common-api/src/feature-availability/useFeatureAvailability.ts +++ b/packages/manager/modules/common-api/src/feature-availability/useFeatureAvailability.ts @@ -1,5 +1,9 @@ import { apiClient, ApiError } from '@ovh-ux/manager-core-api'; -import { useQuery, UseQueryResult } from '@tanstack/react-query'; +import { + DefinedInitialDataOptions, + useQuery, + UseQueryResult, +} from '@tanstack/react-query'; export type UseFeatureAvailabilityResult< T = Record @@ -35,8 +39,15 @@ export const getFeatureAvailabilityQueryKey = ( */ export const useFeatureAvailability = ( featureList: [...T], + options: Partial< + DefinedInitialDataOptions< + Record, + ApiError + > + > = {}, ): UseFeatureAvailabilityResult> => useQuery, ApiError>({ queryKey: getFeatureAvailabilityQueryKey(featureList), queryFn: () => fetchFeatureAvailabilityData(featureList), + ...options, }); diff --git a/packages/manager/modules/common-api/src/services/hooks/useDeleteService.ts b/packages/manager/modules/common-api/src/services/hooks/useDeleteService.ts index ad533f1ceed1..8ccdfeb4676f 100644 --- a/packages/manager/modules/common-api/src/services/hooks/useDeleteService.ts +++ b/packages/manager/modules/common-api/src/services/hooks/useDeleteService.ts @@ -1,5 +1,9 @@ import { ApiError, ApiResponse } from '@ovh-ux/manager-core-api'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { + MutationOptions, + useMutation, + useQueryClient, +} from '@tanstack/react-query'; import { getResourceServiceId, getResourceServiceIdQueryKey, @@ -12,21 +16,20 @@ export type DeleteServiceMutationParams = { export const deleteServiceMutationKey = ['delete-service']; -export type UseDeleteServiceParams = { - onSuccess?: () => void; - onError?: (result: ApiError) => void; - mutationKey?: string[]; -}; +export type UseDeleteServiceParams = MutationOptions< + ApiResponse<{ message: string }>, + ApiError, + DeleteServiceMutationParams +>; export const useDeleteService = ({ - onSuccess, - onError, mutationKey = deleteServiceMutationKey, + ...options }: UseDeleteServiceParams) => { const queryClient = useQueryClient(); const { mutate: terminateService, ...mutation } = useMutation({ mutationKey, - mutationFn: async ({ resourceName }: DeleteServiceMutationParams) => { + mutationFn: async ({ resourceName }) => { const { data } = await queryClient.fetchQuery< ApiResponse, ApiError @@ -36,8 +39,7 @@ export const useDeleteService = ({ }); return deleteService({ serviceId: data[0] }); }, - onSuccess, - onError, + ...options, }); return { diff --git a/packages/manager/modules/common-api/src/services/hooks/useServiceDetails.ts b/packages/manager/modules/common-api/src/services/hooks/useServiceDetails.ts index 1ddca46cfa64..85141cbb3cd6 100644 --- a/packages/manager/modules/common-api/src/services/hooks/useServiceDetails.ts +++ b/packages/manager/modules/common-api/src/services/hooks/useServiceDetails.ts @@ -1,5 +1,9 @@ import { ApiError, ApiResponse } from '@ovh-ux/manager-core-api'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { + DefinedInitialDataOptions, + useQuery, + useQueryClient, +} from '@tanstack/react-query'; import { getResourceServiceId, getResourceServiceIdQueryKey, @@ -15,11 +19,15 @@ export const getServiceDetailsQueryKey = (resourceName: string) => [ export type UseServiceDetailsParams = { queryKey?: string[]; resourceName: string; + options?: Partial< + DefinedInitialDataOptions, ApiError> + >; }; export const useServiceDetails = ({ queryKey, resourceName, + options = {}, }: UseServiceDetailsParams) => { const queryClient = useQueryClient(); return useQuery, ApiError>({ @@ -34,5 +42,6 @@ export const useServiceDetails = ({ }); return getServiceDetails(data[0]); }, + ...options, }); }; diff --git a/packages/manager/modules/common-api/src/services/hooks/useUpdateServiceName.ts b/packages/manager/modules/common-api/src/services/hooks/useUpdateServiceName.ts index 64a10712dde9..8c80d5b74e71 100644 --- a/packages/manager/modules/common-api/src/services/hooks/useUpdateServiceName.ts +++ b/packages/manager/modules/common-api/src/services/hooks/useUpdateServiceName.ts @@ -1,5 +1,9 @@ import { ApiError, ApiResponse } from '@ovh-ux/manager-core-api'; -import { useQueryClient, useMutation } from '@tanstack/react-query'; +import { + useQueryClient, + useMutation, + MutationOptions, +} from '@tanstack/react-query'; import { updateServiceName, getResourceServiceId, @@ -12,27 +16,23 @@ export type UpdateServiceNameMutationParams = { /** Resource name or id */ resourceName: string; /** Resource new display name */ - displayName?: string; + displayName: string; }; -export type UseUpdateServiceDisplayNameParams = { - onSuccess?: () => void; - onError?: (result: ApiError) => void; - mutationKey?: string[]; -}; +export type UseUpdateServiceDisplayNameParams = MutationOptions< + ApiResponse<{ message: string }>, + ApiError, + UpdateServiceNameMutationParams +>; export const useUpdateServiceDisplayName = ({ - onSuccess, - onError, mutationKey = updateServiceNameMutationKey, + ...options }: UseUpdateServiceDisplayNameParams) => { const queryClient = useQueryClient(); const { mutate: updateDisplayName, ...mutation } = useMutation({ mutationKey, - mutationFn: async ({ - resourceName, - displayName, - }: UpdateServiceNameMutationParams) => { + mutationFn: async ({ resourceName, displayName }) => { const { data: servicesId } = await queryClient.fetchQuery< ApiResponse >({ @@ -41,8 +41,7 @@ export const useUpdateServiceDisplayName = ({ }); return updateServiceName({ serviceId: servicesId[0], displayName }); }, - onSuccess, - onError, + ...options, }); return { updateDisplayName, diff --git a/packages/manager/modules/common-api/src/services/services.type.ts b/packages/manager/modules/common-api/src/services/services.type.ts index 77b67691ab10..caedfa2b06f7 100644 --- a/packages/manager/modules/common-api/src/services/services.type.ts +++ b/packages/manager/modules/common-api/src/services/services.type.ts @@ -86,11 +86,11 @@ export type ServiceDetails = { engagementRequest: { pricingMode: string; requestDate: string; - }; + } | null; expirationDate: string; group: { id: number; - }; + } | null; lifecycle: { capacities: { actions: LifecycleAction[]; @@ -99,7 +99,7 @@ export type ServiceDetails = { creationDate: string; pendingActions: LifecycleAction[]; state: LifecycleState; - terminationDate: string; + terminationDate: string | null; }; }; nextBillingDate: string; @@ -115,7 +115,7 @@ export type ServiceDetails = { defaultEndAction: EndAction; duration: string; type: EngagementType; - }; + } | null; interval: number; maximumQuantity: number | null; maximumRepeat: number | null; @@ -151,7 +151,7 @@ export type ServiceDetails = { description: string; name: string; }; - resellingProvider: 'ovh.ca' | 'ovh.eu'; + resellingProvider: 'ovh.ca' | 'ovh.eu' | null; state: ResourceStatus; }; route: { diff --git a/packages/manager/modules/common-api/src/tasks/hooks/useTask.ts b/packages/manager/modules/common-api/src/tasks/hooks/useTask.ts index e94585285e0b..e1ace3b6fd43 100644 --- a/packages/manager/modules/common-api/src/tasks/hooks/useTask.ts +++ b/packages/manager/modules/common-api/src/tasks/hooks/useTask.ts @@ -90,7 +90,7 @@ export const useTask = ({ if (apiVersion === 'v6') { return query.state.status !== 'error' ? refetchIntervalTime : undefined; } - return !['DONE', 'ERROR'].includes(query.state.data?.data?.status) + return !['DONE', 'ERROR'].includes(query.state.data?.data?.status || '') ? refetchIntervalTime : undefined; },