From e8d46dd716e7934abfb3212e2201c01f598639d7 Mon Sep 17 00:00:00 2001 From: Gianluca Zuccarelli Date: Tue, 24 Jun 2025 16:45:14 +0100 Subject: [PATCH] deps: migrate fec/notifications The frontend component library decoupled notifications from redux. Dispatching notifications via the notifications middleware was replaced by a new `useAddNotifications` hook. We mostly used the notifications middleware outside of React Components in our `enhancedImageBuilderApi` store for mutation events. I created a wrapper around the RTK hooks that uses the `useAddNotification` hook and created a directory for the new hooks. In other places, where we were using the notification dispatcher inside React components, I replaced the call with the new hook. [1] https://github.com/RedHatInsights/frontend-components/blob/b1d4973144ad16a96667945182b720ca8e1d8db7/packages/notifications/doc/migration.md bump @redhat-cloud-services/frontend-components-notifications --- updated-dependencies: - dependency-name: "@redhat-cloud-services/frontend-components-notifications" dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-major ... Co-authored-by: dependabot[bot] Assisted-by: cursor ide for generalizing the `useMutationWithNotification` hook. --- package-lock.json | 38 +--- package.json | 2 +- src/App.tsx | 7 +- src/AppCockpit.tsx | 11 +- src/Components/Blueprints/BlueprintCard.tsx | 8 +- .../Blueprints/BuildImagesButton.tsx | 50 ++--- .../Blueprints/DeleteBlueprintModal.tsx | 9 +- .../Blueprints/ImportBlueprintModal.tsx | 51 ++--- .../CreateImageWizard/ImportImageWizard.tsx | 13 +- .../components/ActivationKeysList.tsx | 15 +- .../steps/Review/Footer/CreateDropdown.tsx | 33 ++- .../steps/Review/Footer/EditDropdown.tsx | 16 +- .../steps/Review/Footer/Footer.tsx | 10 +- .../ImagesTable/ImagesTableToolbar.tsx | 4 +- src/Components/LandingPage/LandingPage.tsx | 2 +- .../ShareImageModal/RegionsSelect.tsx | 4 +- src/Components/edge/ImageDetails.tsx | 8 +- src/Components/edge/ImagesTable.tsx | 8 +- src/Hooks/Edge/useGetNotificationProp.ts | 34 +++ .../useCloneComposeWithNotification.tsx | 24 +++ .../useComposeBPWithNotification.tsx | 20 ++ .../useCreateBPWithNotification.tsx | 24 +++ .../useDeleteBPWithNotification.tsx | 24 +++ .../useFixupBPWithNotification.tsx | 24 +++ .../useMutationWithNotification.tsx | 88 ++++++++ .../useUpdateBPWithNotification.tsx | 23 ++ src/Hooks/index.tsx | 6 + src/Router.tsx | 2 +- src/Utilities/edge.ts | 40 ---- src/store/cockpit/enhancedCockpitApi.ts | 85 -------- src/store/index.ts | 5 +- src/store/service/enhancedImageBuilderApi.ts | 197 +++--------------- 32 files changed, 412 insertions(+), 473 deletions(-) create mode 100644 src/Hooks/Edge/useGetNotificationProp.ts create mode 100644 src/Hooks/MutationNotifications/useCloneComposeWithNotification.tsx create mode 100644 src/Hooks/MutationNotifications/useComposeBPWithNotification.tsx create mode 100644 src/Hooks/MutationNotifications/useCreateBPWithNotification.tsx create mode 100644 src/Hooks/MutationNotifications/useDeleteBPWithNotification.tsx create mode 100644 src/Hooks/MutationNotifications/useFixupBPWithNotification.tsx create mode 100644 src/Hooks/MutationNotifications/useMutationWithNotification.tsx create mode 100644 src/Hooks/MutationNotifications/useUpdateBPWithNotification.tsx create mode 100644 src/Hooks/index.tsx delete mode 100644 src/Utilities/edge.ts diff --git a/package-lock.json b/package-lock.json index 8bad5ba0..d1e2e95d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@patternfly/react-core": "6.1.0", "@patternfly/react-table": "6.1.0", "@redhat-cloud-services/frontend-components": "6.0.4", - "@redhat-cloud-services/frontend-components-notifications": "5.0.4", + "@redhat-cloud-services/frontend-components-notifications": "6.0.2", "@redhat-cloud-services/frontend-components-utilities": "6.1.0", "@reduxjs/toolkit": "2.8.2", "@scalprum/react-core": "0.9.5", @@ -4008,30 +4008,19 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-notifications": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-5.0.4.tgz", - "integrity": "sha512-2/D+tNwKyV8y41nFgzDPIo0wV4BmYKvY01GvtEccvZ0so09h6dDqhMb+1NzfVw1dso12obc3KAd/cQtqeHMuMQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-6.0.2.tgz", + "integrity": "sha512-twJAOyBpg/39rxzd21XzQTNwpfxhXx/m0Rmdv7EQhV3lyZ4YuOrEjabshEO9jhTgoANp6XvMbnOmsoYVfrPOwg==", "license": "Apache-2.0", "dependencies": { "@redhat-cloud-services/frontend-components": "^6.0.0", - "@redhat-cloud-services/frontend-components-utilities": "^6.0.0", - "redux-promise-middleware": "6.1.3" + "@redhat-cloud-services/frontend-components-utilities": "^6.0.0" }, "peerDependencies": { "@patternfly/react-core": "^6.0.0", "@patternfly/react-icons": "^6.0.0", - "prop-types": "^15.6.2", "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-redux": "^7.2.9 || ^8.0.0 || ^9.0.0", - "redux": ">=4.2.0" - } - }, - "node_modules/@redhat-cloud-services/frontend-components-notifications/node_modules/redux-promise-middleware": { - "version": "6.1.3", - "license": "MIT", - "peerDependencies": { - "redux": "^2.0.0 || ^3.0.0 || ^4.0.0" + "react-dom": "^18.2.0" } }, "node_modules/@redhat-cloud-services/frontend-components-utilities": { @@ -22174,19 +22163,12 @@ } }, "@redhat-cloud-services/frontend-components-notifications": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-5.0.4.tgz", - "integrity": "sha512-2/D+tNwKyV8y41nFgzDPIo0wV4BmYKvY01GvtEccvZ0so09h6dDqhMb+1NzfVw1dso12obc3KAd/cQtqeHMuMQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-6.0.2.tgz", + "integrity": "sha512-twJAOyBpg/39rxzd21XzQTNwpfxhXx/m0Rmdv7EQhV3lyZ4YuOrEjabshEO9jhTgoANp6XvMbnOmsoYVfrPOwg==", "requires": { "@redhat-cloud-services/frontend-components": "^6.0.0", - "@redhat-cloud-services/frontend-components-utilities": "^6.0.0", - "redux-promise-middleware": "6.1.3" - }, - "dependencies": { - "redux-promise-middleware": { - "version": "6.1.3", - "requires": {} - } + "@redhat-cloud-services/frontend-components-utilities": "^6.0.0" } }, "@redhat-cloud-services/frontend-components-utilities": { diff --git a/package.json b/package.json index 57d1b7bd..a4906057 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@patternfly/react-core": "6.1.0", "@patternfly/react-table": "6.1.0", "@redhat-cloud-services/frontend-components": "6.0.4", - "@redhat-cloud-services/frontend-components-notifications": "5.0.4", + "@redhat-cloud-services/frontend-components-notifications": "6.0.2", "@redhat-cloud-services/frontend-components-utilities": "6.1.0", "@reduxjs/toolkit": "2.8.2", "@scalprum/react-core": "0.9.5", diff --git a/src/App.tsx b/src/App.tsx index 5a02a3c8..f77a0c17 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome'; -import NotificationsPortal from '@redhat-cloud-services/frontend-components-notifications/NotificationPortal'; +import NotificationsProvider from '@redhat-cloud-services/frontend-components-notifications/NotificationsProvider'; import '@patternfly/patternfly/patternfly-addons.css'; import { Router } from './Router'; @@ -26,8 +26,9 @@ const App = () => { return ( - - + + + ); }; diff --git a/src/AppCockpit.tsx b/src/AppCockpit.tsx index 0d073197..c56b18fb 100644 --- a/src/AppCockpit.tsx +++ b/src/AppCockpit.tsx @@ -5,7 +5,7 @@ import React from 'react'; import 'cockpit-dark-theme'; import { Page, PageSection } from '@patternfly/react-core'; -import NotificationsPortal from '@redhat-cloud-services/frontend-components-notifications/NotificationPortal'; +import NotificationsProvider from '@redhat-cloud-services/frontend-components-notifications/NotificationsProvider'; import { createRoot } from 'react-dom/client'; import { Provider } from 'react-redux'; import { HashRouter } from 'react-router-dom'; @@ -31,10 +31,11 @@ const Application = () => { return ( - - - - + + + + + ); }; diff --git a/src/Components/Blueprints/BlueprintCard.tsx b/src/Components/Blueprints/BlueprintCard.tsx index 5a6361c0..cd52d416 100644 --- a/src/Components/Blueprints/BlueprintCard.tsx +++ b/src/Components/Blueprints/BlueprintCard.tsx @@ -10,15 +10,13 @@ import { Spinner, } from '@patternfly/react-core'; +import { useDeleteBPWithNotification as useDeleteBlueprintMutation } from '../../Hooks'; import { selectSelectedBlueprintId, setBlueprintId, } from '../../store/BlueprintSlice'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; -import { - BlueprintItem, - useDeleteBlueprintMutation, -} from '../../store/imageBuilderApi'; +import { BlueprintItem } from '../../store/imageBuilderApi'; type blueprintProps = { blueprint: BlueprintItem; @@ -28,7 +26,7 @@ const BlueprintCard = ({ blueprint }: blueprintProps) => { const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId); const dispatch = useAppDispatch(); - const [, { isLoading }] = useDeleteBlueprintMutation({ + const { isLoading } = useDeleteBlueprintMutation({ fixedCacheKey: 'delete-blueprint', }); diff --git a/src/Components/Blueprints/BuildImagesButton.tsx b/src/Components/Blueprints/BuildImagesButton.tsx index fee7a64a..4dc95952 100644 --- a/src/Components/Blueprints/BuildImagesButton.tsx +++ b/src/Components/Blueprints/BuildImagesButton.tsx @@ -16,17 +16,14 @@ import { } from '@patternfly/react-core'; import { MenuToggleElement } from '@patternfly/react-core/dist/esm/components/MenuToggle/MenuToggle'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; -import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux'; import { ChromeUser } from '@redhat-cloud-services/types'; import { skipToken } from '@reduxjs/toolkit/query'; import { AMPLITUDE_MODULE_NAME, targetOptions } from '../../constants'; -import { - useGetBlueprintQuery, - useComposeBlueprintMutation, -} from '../../store/backendApi'; +import { useComposeBPWithNotification as useComposeBlueprintMutation } from '../../Hooks'; +import { useGetBlueprintQuery } from '../../store/backendApi'; import { selectSelectedBlueprintId } from '../../store/BlueprintSlice'; -import { useAppDispatch, useAppSelector } from '../../store/hooks'; +import { useAppSelector } from '../../store/hooks'; import { ImageTypes } from '../../store/imageBuilderApi'; type BuildImagesButtonPropTypes = { @@ -37,9 +34,8 @@ type BuildImagesButtonPropTypes = { export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => { const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId); const [deselectedTargets, setDeselectedTargets] = useState([]); - const [buildBlueprint, { isLoading: imageBuildLoading }] = + const { trigger: buildBlueprint, isLoading: imageBuildLoading } = useComposeBlueprintMutation(); - const dispatch = useAppDispatch(); const { analytics, auth } = useChrome(); const [userData, setUserData] = useState(undefined); @@ -53,29 +49,19 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => { const onBuildHandler = async () => { if (selectedBlueprintId) { - try { - await buildBlueprint({ - id: selectedBlueprintId, - body: { - image_types: blueprintImageType?.filter( - (target) => !deselectedTargets.includes(target) - ), - }, - }); - analytics.track(`${AMPLITUDE_MODULE_NAME} - Image Requested`, { - module: AMPLITUDE_MODULE_NAME, - trigger: 'synchronize images', - account_id: userData?.identity.internal?.account_id || 'Not found', - }); - } catch (imageBuildError) { - dispatch( - addNotification({ - variant: 'warning', - title: 'No blueprint was build', - description: imageBuildError?.data?.error?.message, - }) - ); - } + await buildBlueprint({ + id: selectedBlueprintId, + body: { + image_types: blueprintImageType?.filter( + (target) => !deselectedTargets.includes(target) + ), + }, + }); + analytics.track(`${AMPLITUDE_MODULE_NAME} - Image Requested`, { + module: AMPLITUDE_MODULE_NAME, + trigger: 'synchronize images', + account_id: userData?.identity.internal?.account_id || 'Not found', + }); } }; const [isOpen, setIsOpen] = useState(false); @@ -180,7 +166,7 @@ export const BuildImagesButtonEmptyState = ({ children, }: BuildImagesButtonEmptyStatePropTypes) => { const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId); - const [buildBlueprint, { isLoading: imageBuildLoading }] = + const { trigger: buildBlueprint, isLoading: imageBuildLoading } = useComposeBlueprintMutation(); const onBuildHandler = async () => { if (selectedBlueprintId) { diff --git a/src/Components/Blueprints/DeleteBlueprintModal.tsx b/src/Components/Blueprints/DeleteBlueprintModal.tsx index 75908df6..71ab634d 100644 --- a/src/Components/Blueprints/DeleteBlueprintModal.tsx +++ b/src/Components/Blueprints/DeleteBlueprintModal.tsx @@ -10,11 +10,8 @@ import { PAGINATION_LIMIT, PAGINATION_OFFSET, } from '../../constants'; -import { - backendApi, - useDeleteBlueprintMutation, - useGetBlueprintsQuery, -} from '../../store/backendApi'; +import { useDeleteBPWithNotification as useDeleteBlueprintMutation } from '../../Hooks'; +import { backendApi, useGetBlueprintsQuery } from '../../store/backendApi'; import { selectBlueprintSearchInput, selectLimit, @@ -65,7 +62,7 @@ export const DeleteBlueprintModal: React.FunctionComponent< )?.name, }), }); - const [deleteBlueprint] = useDeleteBlueprintMutation({ + const { trigger: deleteBlueprint } = useDeleteBlueprintMutation({ fixedCacheKey: 'delete-blueprint', }); const handleDelete = async () => { diff --git a/src/Components/Blueprints/ImportBlueprintModal.tsx b/src/Components/Blueprints/ImportBlueprintModal.tsx index 61c78081..5a96a0b0 100644 --- a/src/Components/Blueprints/ImportBlueprintModal.tsx +++ b/src/Components/Blueprints/ImportBlueprintModal.tsx @@ -16,7 +16,7 @@ import { import { Modal, ModalVariant } from '@patternfly/react-core/deprecated'; import { DropEvent } from '@patternfly/react-core/dist/esm/helpers'; import { HelpIcon } from '@patternfly/react-icons'; -import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux'; +import { useAddNotification } from '@redhat-cloud-services/frontend-components-notifications/hooks'; import { useNavigate } from 'react-router-dom'; import { mapOnPremToHosted } from './helpers/onPremToHostedBlueprintMapper'; @@ -26,7 +26,6 @@ import { ApiRepositoryRequest, useBulkImportRepositoriesMutation, } from '../../store/contentSourcesApi'; -import { useAppDispatch } from '../../store/hooks'; import { BlueprintExportResponse, BlueprintItem, @@ -64,7 +63,7 @@ export const ImportBlueprintModal: React.FunctionComponent< const [isRejected, setIsRejected] = React.useState(false); const [isOnPrem, setIsOnPrem] = React.useState(false); const [isCheckedImportRepos, setIsCheckedImportRepos] = React.useState(true); - const dispatch = useAppDispatch(); + const addNotification = useAddNotification(); const [importRepositories] = useBulkImportRepositoriesMutation(); const handleFileInputChange = ( @@ -103,33 +102,27 @@ export const ImportBlueprintModal: React.FunctionComponent< importedRepositoryNames.push(repository.url); return; } - dispatch( - addNotification({ - variant: 'warning', - title: 'Failed to import custom repositories', - description: JSON.stringify(repository.warnings), - }) - ); + addNotification({ + variant: 'warning', + title: 'Failed to import custom repositories', + description: JSON.stringify(repository.warnings), + }); }); if (importedRepositoryNames.length !== 0) { - dispatch( - addNotification({ - variant: 'info', - title: 'Successfully imported custom repositories', - description: importedRepositoryNames.join(', '), - }) - ); + addNotification({ + variant: 'info', + title: 'Successfully imported custom repositories', + description: importedRepositoryNames.join(', '), + }); } return newCustomRepos; } } catch { - dispatch( - addNotification({ - variant: 'danger', - title: 'Custom repositories import failed', - }) - ); + addNotification({ + variant: 'danger', + title: 'Custom repositories import failed', + }); } } } @@ -197,13 +190,11 @@ export const ImportBlueprintModal: React.FunctionComponent< } } catch (error) { setIsInvalidFormat(true); - dispatch( - addNotification({ - variant: 'warning', - title: 'File is not a valid blueprint', - description: error?.data?.error?.message, - }) - ); + addNotification({ + variant: 'warning', + title: 'File is not a valid blueprint', + description: error?.data?.error?.message, + }); } }; parseAndImport(); diff --git a/src/Components/CreateImageWizard/ImportImageWizard.tsx b/src/Components/CreateImageWizard/ImportImageWizard.tsx index 67a38234..786bce18 100644 --- a/src/Components/CreateImageWizard/ImportImageWizard.tsx +++ b/src/Components/CreateImageWizard/ImportImageWizard.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; -import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux'; +import { useAddNotification } from '@redhat-cloud-services/frontend-components-notifications/hooks'; import { useLocation, useNavigate } from 'react-router-dom'; import CreateImageWizard from './CreateImageWizard'; @@ -13,6 +13,7 @@ const ImportImageWizard = () => { const dispatch = useAppDispatch(); const navigate = useNavigate(); const location = useLocation(); + const addNotification = useAddNotification(); const locationState = location.state as { blueprint?: wizardState }; const blueprint = locationState?.blueprint; useEffect(() => { @@ -20,12 +21,10 @@ const ImportImageWizard = () => { dispatch(loadWizardState(blueprint)); } else { navigate(resolveRelPath('')); - dispatch( - addNotification({ - variant: 'warning', - title: 'No blueprint was imported', - }) - ); + addNotification({ + variant: 'warning', + title: 'No blueprint was imported', + }); } }, [blueprint, dispatch]); return ; diff --git a/src/Components/CreateImageWizard/steps/Registration/components/ActivationKeysList.tsx b/src/Components/CreateImageWizard/steps/Registration/components/ActivationKeysList.tsx index 3a5779dc..4ef779b2 100644 --- a/src/Components/CreateImageWizard/steps/Registration/components/ActivationKeysList.tsx +++ b/src/Components/CreateImageWizard/steps/Registration/components/ActivationKeysList.tsx @@ -13,7 +13,7 @@ import { TextInputGroup, TextInputGroupMain, } from '@patternfly/react-core'; -import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux'; +import { useAddNotification } from '@redhat-cloud-services/frontend-components-notifications/hooks'; import ManageKeysButton from './ManageKeysButton'; import PopoverActivation from './PopoverActivation'; @@ -37,6 +37,7 @@ import { generateRandomId } from '../../../utilities/generateRandomId'; const ActivationKeysList = () => { const dispatch = useAppDispatch(); + const addNotification = useAddNotification(); const activationKey = useAppSelector(selectActivationKey); const registrationType = useAppSelector(selectRegistrationType); @@ -138,13 +139,11 @@ const ActivationKeysList = () => { ); dispatch(changeActivationKey(defaultActivationKeyName)); } catch (error) { - dispatch( - addNotification({ - variant: 'danger', - title: 'Error creating activation key', - description: error?.data?.error?.message, - }) - ); + addNotification({ + variant: 'danger', + title: 'Error creating activation key', + description: error?.data?.error?.message, + }); } }; diff --git a/src/Components/CreateImageWizard/steps/Review/Footer/CreateDropdown.tsx b/src/Components/CreateImageWizard/steps/Review/Footer/CreateDropdown.tsx index 3a313734..91bbb83d 100644 --- a/src/Components/CreateImageWizard/steps/Review/Footer/CreateDropdown.tsx +++ b/src/Components/CreateImageWizard/steps/Review/Footer/CreateDropdown.tsx @@ -14,12 +14,15 @@ import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; import { ChromeUser } from '@redhat-cloud-services/types'; import { AMPLITUDE_MODULE_NAME } from '../../../../../constants'; -import { useCreateBlueprintMutation } from '../../../../../store/backendApi'; +import { + useComposeBPWithNotification as useComposeBlueprintMutation, + useCreateBPWithNotification as useCreateBlueprintMutation, +} from '../../../../../Hooks'; import { setBlueprintId } from '../../../../../store/BlueprintSlice'; import { useAppDispatch, useAppSelector } from '../../../../../store/hooks'; import { CreateBlueprintRequest, - useComposeBlueprintMutation, + CreateBlueprintResponse, } from '../../../../../store/imageBuilderApi'; import { selectPackages } from '../../../../../store/wizardSlice'; import { createAnalytics } from '../../../../../Utilities/analytics'; @@ -46,8 +49,8 @@ export const CreateSaveAndBuildBtn = ({ }, [auth]); const packages = useAppSelector(selectPackages); - const [buildBlueprint] = useComposeBlueprintMutation(); - const [createBlueprint] = useCreateBlueprintMutation({ + const { trigger: buildBlueprint } = useComposeBlueprintMutation(); + const { trigger: createBlueprint } = useCreateBlueprintMutation({ fixedCacheKey: 'createBlueprintKey', }); const dispatch = useAppDispatch(); @@ -70,13 +73,11 @@ export const CreateSaveAndBuildBtn = ({ ), }); } - const blueprint = - requestBody && - (await createBlueprint({ + if (requestBody) { + const blueprint = (await createBlueprint({ createBlueprintRequest: requestBody, - }).unwrap()); // unwrap - access the success payload immediately after a mutation + })) as CreateBlueprintResponse; - if (blueprint) { buildBlueprint({ id: blueprint.id, body: {} }); dispatch(setBlueprintId(blueprint.id)); } @@ -107,7 +108,7 @@ export const CreateSaveButton = ({ }, [auth]); const packages = useAppSelector(selectPackages); - const [createBlueprint, { isLoading }] = useCreateBlueprintMutation({ + const { trigger: createBlueprint, isLoading } = useCreateBlueprintMutation({ fixedCacheKey: 'createBlueprintKey', }); const dispatch = useAppDispatch(); @@ -166,15 +167,11 @@ export const CreateSaveButton = ({ account_id: userData?.identity.internal?.account_id || 'Not found', }); } - - const blueprint = - requestBody && - (await createBlueprint({ + if (requestBody) { + const blueprint = (await createBlueprint({ createBlueprintRequest: requestBody, - }).unwrap()); - - if (blueprint) { - dispatch(setBlueprintId(blueprint?.id)); + })) as CreateBlueprintResponse; + dispatch(setBlueprintId(blueprint.id)); } }; diff --git a/src/Components/CreateImageWizard/steps/Review/Footer/EditDropdown.tsx b/src/Components/CreateImageWizard/steps/Review/Footer/EditDropdown.tsx index 2676d94c..6be18a8c 100644 --- a/src/Components/CreateImageWizard/steps/Review/Footer/EditDropdown.tsx +++ b/src/Components/CreateImageWizard/steps/Review/Footer/EditDropdown.tsx @@ -12,12 +12,12 @@ import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; import { ChromeUser } from '@redhat-cloud-services/types'; import { AMPLITUDE_MODULE_NAME } from '../../../../../constants'; -import { useUpdateBlueprintMutation } from '../../../../../store/backendApi'; -import { useAppSelector } from '../../../../../store/hooks'; import { - CreateBlueprintRequest, - useComposeBlueprintMutation, -} from '../../../../../store/imageBuilderApi'; + useComposeBPWithNotification as useComposeBlueprintMutation, + useUpdateBPWithNotification as useUpdateBlueprintMutation, +} from '../../../../../Hooks'; +import { useAppSelector } from '../../../../../store/hooks'; +import { CreateBlueprintRequest } from '../../../../../store/imageBuilderApi'; import { selectPackages } from '../../../../../store/wizardSlice'; import { createAnalytics } from '../../../../../Utilities/analytics'; @@ -43,10 +43,10 @@ export const EditSaveAndBuildBtn = ({ setUserData(data); })(); }, [auth]); - const [buildBlueprint] = useComposeBlueprintMutation(); + const { trigger: buildBlueprint } = useComposeBlueprintMutation(); const packages = useAppSelector(selectPackages); - const [updateBlueprint] = useUpdateBlueprintMutation({ + const { trigger: updateBlueprint } = useUpdateBlueprintMutation({ fixedCacheKey: 'updateBlueprintKey', }); @@ -104,7 +104,7 @@ export const EditSaveButton = ({ }, [auth]); const packages = useAppSelector(selectPackages); - const [updateBlueprint, { isLoading }] = useUpdateBlueprintMutation({ + const { trigger: updateBlueprint, isLoading } = useUpdateBlueprintMutation({ fixedCacheKey: 'updateBlueprintKey', }); const onSave = async () => { diff --git a/src/Components/CreateImageWizard/steps/Review/Footer/Footer.tsx b/src/Components/CreateImageWizard/steps/Review/Footer/Footer.tsx index a97a5847..cc2e8f35 100644 --- a/src/Components/CreateImageWizard/steps/Review/Footer/Footer.tsx +++ b/src/Components/CreateImageWizard/steps/Review/Footer/Footer.tsx @@ -17,20 +17,20 @@ import { CreateSaveAndBuildBtn, CreateSaveButton } from './CreateDropdown'; import { EditSaveAndBuildBtn, EditSaveButton } from './EditDropdown'; import { - useCreateBlueprintMutation, - useUpdateBlueprintMutation, -} from '../../../../../store/backendApi'; + useCreateBPWithNotification as useCreateBlueprintMutation, + useUpdateBPWithNotification as useUpdateBlueprintMutation, +} from '../../../../../Hooks'; import { resolveRelPath } from '../../../../../Utilities/path'; import { mapRequestFromState } from '../../../utilities/requestMapper'; import { useIsBlueprintValid } from '../../../utilities/useValidation'; const ReviewWizardFooter = () => { const { goToPrevStep, close } = useWizardContext(); - const [, { isSuccess: isCreateSuccess, reset: resetCreate }] = + const { isSuccess: isCreateSuccess, reset: resetCreate } = useCreateBlueprintMutation({ fixedCacheKey: 'createBlueprintKey' }); // initialize the server store with the data from RTK query - const [, { isSuccess: isUpdateSuccess, reset: resetUpdate }] = + const { isSuccess: isUpdateSuccess, reset: resetUpdate } = useUpdateBlueprintMutation({ fixedCacheKey: 'updateBlueprintKey' }); const { auth } = useChrome(); const { composeId } = useParams(); diff --git a/src/Components/ImagesTable/ImagesTableToolbar.tsx b/src/Components/ImagesTable/ImagesTableToolbar.tsx index 8c2cf8fc..017aab57 100644 --- a/src/Components/ImagesTable/ImagesTableToolbar.tsx +++ b/src/Components/ImagesTable/ImagesTableToolbar.tsx @@ -13,6 +13,7 @@ import { Title, } from '@patternfly/react-core'; +import { useFixupBPWithNotification as useFixupBlueprintMutation } from '../../Hooks'; import { useGetBlueprintsQuery, useGetBlueprintQuery, @@ -28,7 +29,6 @@ import { useGetBlueprintComposesQuery, Distributions, GetBlueprintComposesApiArg, - useFixupBlueprintMutation, } from '../../store/imageBuilderApi'; import { BlueprintActionsMenu } from '../Blueprints/BlueprintActionsMenu'; import BlueprintDiffModal from '../Blueprints/BlueprintDiffModal'; @@ -128,7 +128,7 @@ const ImagesTableToolbar: React.FC = ({ { skip: !selectedBlueprintId } ); - const [fixupBlueprint] = useFixupBlueprintMutation(); + const { trigger: fixupBlueprint } = useFixupBlueprintMutation(); const hasErrors = blueprintDetails?.lint?.errors && blueprintDetails?.lint?.errors.length > 0; const [isLintExp, setIsLintExp] = React.useState(true); diff --git a/src/Components/LandingPage/LandingPage.tsx b/src/Components/LandingPage/LandingPage.tsx index f112ea1d..80b010d8 100644 --- a/src/Components/LandingPage/LandingPage.tsx +++ b/src/Components/LandingPage/LandingPage.tsx @@ -24,7 +24,7 @@ import './LandingPage.scss'; import { NewAlert } from './NewAlert'; import { MANAGING_WITH_DNF_URL, OSTREE_URL } from '../../constants'; -import { manageEdgeImagesUrlName } from '../../Utilities/edge'; +import { manageEdgeImagesUrlName } from '../../Hooks/Edge/useGetNotificationProp'; import { resolveRelPath } from '../../Utilities/path'; import { useFlag } from '../../Utilities/useGetEnvironment'; import BlueprintsSidebar from '../Blueprints/BlueprintsSideBar'; diff --git a/src/Components/ShareImageModal/RegionsSelect.tsx b/src/Components/ShareImageModal/RegionsSelect.tsx index 217c532c..d1766d09 100644 --- a/src/Components/ShareImageModal/RegionsSelect.tsx +++ b/src/Components/ShareImageModal/RegionsSelect.tsx @@ -29,9 +29,9 @@ import { import { useNavigate } from 'react-router-dom'; import { AWS_REGIONS } from '../../constants'; +import { useCloneComposeWithNotification as useCloneComposeMutation } from '../../Hooks'; import { ComposeStatus, - useCloneComposeMutation, useGetComposeStatusQuery, } from '../../store/imageBuilderApi'; import { resolveRelPath } from '../../Utilities/path'; @@ -130,7 +130,7 @@ const RegionsSelect = ({ composeId, handleClose }: RegionsSelectPropTypes) => { } }; - const [cloneCompose] = useCloneComposeMutation(); + const { trigger: cloneCompose } = useCloneComposeMutation(); const { data: composeStatus, isSuccess } = useGetComposeStatusQuery({ composeId, diff --git a/src/Components/edge/ImageDetails.tsx b/src/Components/edge/ImageDetails.tsx index 51407738..a15d259e 100644 --- a/src/Components/edge/ImageDetails.tsx +++ b/src/Components/edge/ImageDetails.tsx @@ -3,19 +3,17 @@ import React from 'react'; import AsyncComponent from '@redhat-cloud-services/frontend-components/AsyncComponent'; import ErrorState from '@redhat-cloud-services/frontend-components/ErrorState'; import Unavailable from '@redhat-cloud-services/frontend-components/Unavailable'; -import { useDispatch } from 'react-redux'; import { useNavigate, useLocation, useParams } from 'react-router-dom'; import { - getNotificationProp, + useGetNotificationProp, manageEdgeImagesUrlName, -} from '../../Utilities/edge'; +} from '../../Hooks/Edge/useGetNotificationProp'; import { resolveRelPath } from '../../Utilities/path'; import { useFlag } from '../../Utilities/useGetEnvironment'; const ImageDetail = () => { - const dispatch = useDispatch(); - const notificationProp = getNotificationProp(dispatch); + const notificationProp = useGetNotificationProp(); // Feature flag for the federated modules const edgeParityFlag = useFlag('edgeParity.image-list'); // Feature flag to access the 'local' images table list diff --git a/src/Components/edge/ImagesTable.tsx b/src/Components/edge/ImagesTable.tsx index 27a3cbc2..7b7b486f 100644 --- a/src/Components/edge/ImagesTable.tsx +++ b/src/Components/edge/ImagesTable.tsx @@ -3,20 +3,18 @@ import React from 'react'; import AsyncComponent from '@redhat-cloud-services/frontend-components/AsyncComponent'; import ErrorState from '@redhat-cloud-services/frontend-components/ErrorState'; import Unavailable from '@redhat-cloud-services/frontend-components/Unavailable'; -import { useDispatch } from 'react-redux'; import { useNavigate, useLocation } from 'react-router-dom'; import { CREATING_IMAGES_WITH_IB_URL } from '../../constants'; import { - getNotificationProp, + useGetNotificationProp, manageEdgeImagesUrlName, -} from '../../Utilities/edge'; +} from '../../Hooks/Edge/useGetNotificationProp'; import { resolveRelPath } from '../../Utilities/path'; import { useFlag } from '../../Utilities/useGetEnvironment'; const ImagesTable = () => { - const dispatch = useDispatch(); - const notificationProp = getNotificationProp(dispatch); + const notificationProp = useGetNotificationProp(); // Feature flag for the federated modules const edgeParityFlag = useFlag('edgeParity.image-list'); // Feature flag to access the 'local' images table list diff --git a/src/Hooks/Edge/useGetNotificationProp.ts b/src/Hooks/Edge/useGetNotificationProp.ts new file mode 100644 index 00000000..d444a175 --- /dev/null +++ b/src/Hooks/Edge/useGetNotificationProp.ts @@ -0,0 +1,34 @@ +import { useAddNotification } from '@redhat-cloud-services/frontend-components-notifications/hooks'; + +const manageEdgeImagesUrlName = 'manage-edge-images'; + +const useGetNotificationProp = () => { + const addNotification = useAddNotification(); + return { + hasInfo: (hasInfoMessage: Notification) => { + addNotification({ + variant: 'info', + ...hasInfoMessage, + }); + }, + hasSuccess: (hasSuccessMessage: Notification) => { + addNotification({ + variant: 'success', + ...hasSuccessMessage, + }); + }, + /* eslint-disable @typescript-eslint/no-explicit-any */ + err: (errMessage: any, err: any) => { + addNotification({ + variant: 'danger', + ...errMessage, + // Add error message from API, if present + description: err?.Title + ? `${errMessage.description}: ${err.Title}` + : errMessage.description, + }); + }, + }; +}; + +export { useGetNotificationProp, manageEdgeImagesUrlName }; diff --git a/src/Hooks/MutationNotifications/useCloneComposeWithNotification.tsx b/src/Hooks/MutationNotifications/useCloneComposeWithNotification.tsx new file mode 100644 index 00000000..80178d37 --- /dev/null +++ b/src/Hooks/MutationNotifications/useCloneComposeWithNotification.tsx @@ -0,0 +1,24 @@ +import { useMutationWithNotification } from './useMutationWithNotification'; + +import { + CloneComposeApiArg, + useCloneComposeMutation, +} from '../../store/service/imageBuilderApi'; + +export const useCloneComposeWithNotification = () => { + const { trigger: cloneCompose, ...rest } = useMutationWithNotification( + useCloneComposeMutation, + { + messages: { + success: ({ cloneRequest }: CloneComposeApiArg) => + `Your image is being shared to ${cloneRequest.region} region`, + error: () => 'Your image could not be shared', + }, + } + ); + + return { + trigger: cloneCompose, + ...rest, + }; +}; diff --git a/src/Hooks/MutationNotifications/useComposeBPWithNotification.tsx b/src/Hooks/MutationNotifications/useComposeBPWithNotification.tsx new file mode 100644 index 00000000..61ef2b28 --- /dev/null +++ b/src/Hooks/MutationNotifications/useComposeBPWithNotification.tsx @@ -0,0 +1,20 @@ +import { useMutationWithNotification } from './useMutationWithNotification'; + +import { useComposeBlueprintMutation } from '../../store/backendApi'; + +export const useComposeBPWithNotification = () => { + const { trigger: composeBlueprint, ...rest } = useMutationWithNotification( + useComposeBlueprintMutation, + { + messages: { + success: () => 'Image is being built', + error: () => 'Image could not be built', + }, + } + ); + + return { + trigger: composeBlueprint, + ...rest, + }; +}; diff --git a/src/Hooks/MutationNotifications/useCreateBPWithNotification.tsx b/src/Hooks/MutationNotifications/useCreateBPWithNotification.tsx new file mode 100644 index 00000000..182679e4 --- /dev/null +++ b/src/Hooks/MutationNotifications/useCreateBPWithNotification.tsx @@ -0,0 +1,24 @@ +import { + HookOptions, + useMutationWithNotification, +} from './useMutationWithNotification'; + +import { useCreateBlueprintMutation } from '../../store/backendApi'; + +export const useCreateBPWithNotification = (options?: HookOptions) => { + const { trigger: createBlueprint, ...rest } = useMutationWithNotification( + useCreateBlueprintMutation, + { + options, + messages: { + success: () => 'Blueprint was created', + error: () => 'Blueprint could not be created', + }, + } + ); + + return { + trigger: createBlueprint, + ...rest, + }; +}; diff --git a/src/Hooks/MutationNotifications/useDeleteBPWithNotification.tsx b/src/Hooks/MutationNotifications/useDeleteBPWithNotification.tsx new file mode 100644 index 00000000..40a331fa --- /dev/null +++ b/src/Hooks/MutationNotifications/useDeleteBPWithNotification.tsx @@ -0,0 +1,24 @@ +import { + HookOptions, + useMutationWithNotification, +} from './useMutationWithNotification'; + +import { useDeleteBlueprintMutation } from '../../store/backendApi'; + +export const useDeleteBPWithNotification = (options?: HookOptions) => { + const { trigger: deleteBlueprint, ...rest } = useMutationWithNotification( + useDeleteBlueprintMutation, + { + options, + messages: { + success: () => 'Blueprint was deleted', + error: () => 'Blueprint could not be deleted', + }, + } + ); + + return { + trigger: deleteBlueprint, + ...rest, + }; +}; diff --git a/src/Hooks/MutationNotifications/useFixupBPWithNotification.tsx b/src/Hooks/MutationNotifications/useFixupBPWithNotification.tsx new file mode 100644 index 00000000..d2cb62be --- /dev/null +++ b/src/Hooks/MutationNotifications/useFixupBPWithNotification.tsx @@ -0,0 +1,24 @@ +import { + HookOptions, + useMutationWithNotification, +} from './useMutationWithNotification'; + +import { useFixupBlueprintMutation } from '../../store/imageBuilderApi'; + +export const useFixupBPWithNotification = (options?: HookOptions) => { + const { trigger: fixupBlueprint, ...rest } = useMutationWithNotification( + useFixupBlueprintMutation, + { + options, + messages: { + success: () => 'Blueprint was fixed', + error: () => 'Blueprint could not be fixed', + }, + } + ); + + return { + trigger: fixupBlueprint, + ...rest, + }; +}; diff --git a/src/Hooks/MutationNotifications/useMutationWithNotification.tsx b/src/Hooks/MutationNotifications/useMutationWithNotification.tsx new file mode 100644 index 00000000..8499c28a --- /dev/null +++ b/src/Hooks/MutationNotifications/useMutationWithNotification.tsx @@ -0,0 +1,88 @@ +import { useAddNotification } from '@redhat-cloud-services/frontend-components-notifications/hooks'; +import { + BaseQueryFn, + TypedMutationTrigger, +} from '@reduxjs/toolkit/dist/query/react'; + +import { errorMessage } from '../../store/service/enhancedImageBuilderApi'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const getErrorDescription = (err: any) => { + if (process.env.IS_ON_PREMISE) { + // If details are present, assume it's coming from composer + if (err.error?.body?.details) { + return `${err.error.message}: ${err.error.body.details}`; + } + + return JSON.stringify(err); + } + + if (err.error?.status) { + return `Status code ${err.error.status}: ${errorMessage(err)}`; + } + + return err as string; +}; + +type NotificationMessages = { + success: (args: TArgs) => string; + error?: (args: TArgs, error: unknown) => string; +}; + +export type HookOptions = { + fixedCacheKey?: string | string; +}; + +type MutationOptions = { + options?: HookOptions | undefined; + messages: NotificationMessages; +}; + +// cursor ide was used to make this hook more generic +// and re-usable. Specifically for extending the complicated +// types to pass in other mutation hooks with using `any` +export function useMutationWithNotification< + Arg, + Result, + State extends { + isLoading: boolean; + isSuccess: boolean; + isError: boolean; + error?: unknown; + reset: () => void; + } +>( + mutationHook: ( + options?: HookOptions + ) => readonly [TypedMutationTrigger, State], + { options, messages }: MutationOptions +) { + const [trigger, state] = mutationHook(options); + const addNotification = useAddNotification(); + + const handler = async (args: Arg): Promise => { + try { + const result = await trigger(args).unwrap(); + addNotification({ + variant: 'success', + title: messages.success(args), + }); + return result; + } catch (err) { + const description = getErrorDescription(err); + if (messages.error) { + addNotification({ + variant: 'danger', + title: messages.error(args, err), + description, + }); + } + return err; + } + }; + + return { + trigger: handler, + ...state, + }; +} diff --git a/src/Hooks/MutationNotifications/useUpdateBPWithNotification.tsx b/src/Hooks/MutationNotifications/useUpdateBPWithNotification.tsx new file mode 100644 index 00000000..9a0f9e21 --- /dev/null +++ b/src/Hooks/MutationNotifications/useUpdateBPWithNotification.tsx @@ -0,0 +1,23 @@ +import { + HookOptions, + useMutationWithNotification, +} from './useMutationWithNotification'; + +import { useUpdateBlueprintMutation } from '../../store/backendApi'; + +export const useUpdateBPWithNotification = (options?: HookOptions) => { + const { trigger: updateBlueprint, ...rest } = useMutationWithNotification( + useUpdateBlueprintMutation, + { + options, + messages: { + success: () => 'Blueprint was updated', + error: () => 'Blueprint could not be updated', + }, + } + ); + return { + trigger: updateBlueprint, + ...rest, + }; +}; diff --git a/src/Hooks/index.tsx b/src/Hooks/index.tsx new file mode 100644 index 00000000..df6abc94 --- /dev/null +++ b/src/Hooks/index.tsx @@ -0,0 +1,6 @@ +export { useCreateBPWithNotification } from './MutationNotifications/useCreateBPWithNotification'; +export { useUpdateBPWithNotification } from './MutationNotifications/useUpdateBPWithNotification'; +export { useDeleteBPWithNotification } from './MutationNotifications/useDeleteBPWithNotification'; +export { useFixupBPWithNotification } from './MutationNotifications/useFixupBPWithNotification'; +export { useComposeBPWithNotification } from './MutationNotifications/useComposeBPWithNotification'; +export { useCloneComposeWithNotification } from './MutationNotifications/useCloneComposeWithNotification'; diff --git a/src/Router.tsx b/src/Router.tsx index 7b2bd614..287c5858 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -4,7 +4,7 @@ import { Route, Routes } from 'react-router-dom'; import EdgeImageDetail from './Components/edge/ImageDetails'; import ShareImageModal from './Components/ShareImageModal/ShareImageModal'; -import { manageEdgeImagesUrlName } from './Utilities/edge'; +import { manageEdgeImagesUrlName } from './Hooks/Edge/useGetNotificationProp'; import { useFlag, useFlagWithEphemDefault, diff --git a/src/Utilities/edge.ts b/src/Utilities/edge.ts deleted file mode 100644 index 7bab7033..00000000 --- a/src/Utilities/edge.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux'; -import { Dispatch } from 'redux'; - -const manageEdgeImagesUrlName = 'manage-edge-images'; - -const getNotificationProp = (dispatch: Dispatch) => { - return { - hasInfo: (hasInfoMessage: Notification) => { - dispatch({ - ...addNotification({ - variant: 'info', - ...hasInfoMessage, - }), - }); - }, - hasSuccess: (hasSuccessMessage: Notification) => { - dispatch({ - ...addNotification({ - variant: 'success', - ...hasSuccessMessage, - }), - }); - }, - /* eslint-disable @typescript-eslint/no-explicit-any */ - err: (errMessage: any, err: any) => { - dispatch({ - ...addNotification({ - variant: 'danger', - ...errMessage, - // Add error message from API, if present - description: err?.Title - ? `${errMessage.description}: ${err.Title}` - : errMessage.description, - }), - }); - }, - }; -}; - -export { getNotificationProp, manageEdgeImagesUrlName }; diff --git a/src/store/cockpit/enhancedCockpitApi.ts b/src/store/cockpit/enhancedCockpitApi.ts index 2837e882..71958828 100644 --- a/src/store/cockpit/enhancedCockpitApi.ts +++ b/src/store/cockpit/enhancedCockpitApi.ts @@ -1,5 +1,3 @@ -import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux'; - import { cockpitApi } from './cockpitApi'; const enhancedApi = cockpitApi.enhanceEndpoints({ @@ -17,98 +15,15 @@ const enhancedApi = cockpitApi.enhanceEndpoints({ }, createBlueprint: { invalidatesTags: [{ type: 'Blueprints' }], - onQueryStarted: async (_, { dispatch, queryFulfilled }) => { - queryFulfilled - .then(() => { - dispatch( - addNotification({ - variant: 'success', - title: 'Blueprint was created', - }) - ); - }) - .catch((err) => { - dispatch( - addNotification({ - variant: 'danger', - title: 'Unable to create blueprint', - description: `Error: ${JSON.stringify(err)}`, - }) - ); - }); - }, }, updateBlueprint: { invalidatesTags: [{ type: 'Blueprint' }, { type: 'Blueprints' }], - onQueryStarted: async (_, { dispatch, queryFulfilled }) => { - queryFulfilled - .then(() => { - dispatch( - addNotification({ - variant: 'success', - title: 'Blueprint was created', - }) - ); - }) - .catch((err) => { - dispatch( - addNotification({ - variant: 'danger', - title: 'Unable to update blueprint', - description: `Error: ${JSON.stringify(err)}`, - }) - ); - }); - }, }, deleteBlueprint: { invalidatesTags: [{ type: 'Blueprints' }, { type: 'Composes' }], - onQueryStarted: async (_, { dispatch, queryFulfilled }) => { - queryFulfilled - .then(() => { - dispatch( - addNotification({ - variant: 'success', - title: 'Blueprint was deleted', - }) - ); - }) - .catch((err) => { - dispatch( - addNotification({ - variant: 'danger', - title: 'Blueprint could not be deleted', - description: `Error: ${JSON.stringify(err)}`, - }) - ); - }); - }, }, composeBlueprint: { invalidatesTags: [{ type: 'Composes' }], - onQueryStarted: async (_, { dispatch, queryFulfilled }) => { - queryFulfilled - .then(() => { - dispatch( - addNotification({ - variant: 'success', - title: 'Build was queued', - }) - ); - }) - .catch((err) => { - dispatch( - addNotification({ - variant: 'danger', - title: 'Unable to build blueprint', - // If details are present, assume it's coming from composer - description: err.error?.body?.details - ? `${err.error.message}: ${err.error.body.details}` - : `Error: ${JSON.stringify(err)}`, - }) - ); - }); - }, }, getComposes: { providesTags: [{ type: 'Composes' }], diff --git a/src/store/index.ts b/src/store/index.ts index 4cd4428f..17df1234 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,4 +1,3 @@ -import { notificationsReducer } from '@redhat-cloud-services/frontend-components-notifications/redux'; import { combineReducers, configureStore } from '@reduxjs/toolkit'; import promiseMiddleware from 'redux-promise-middleware'; @@ -27,7 +26,6 @@ export const serviceReducer = combineReducers({ [rhsmApi.reducerPath]: rhsmApi.reducer, [provisioningApi.reducerPath]: provisioningApi.reducer, [complianceApi.reducerPath]: complianceApi.reducer, - notifications: notificationsReducer, wizard: wizardSlice, blueprints: blueprintsSlice.reducer, }); @@ -41,7 +39,6 @@ export const onPremReducer = combineReducers({ // TODO: add other endpoints so we can remove this. // It's still needed to get things to work. [imageBuilderApi.reducerPath]: imageBuilderApi.reducer, - notifications: notificationsReducer, wizard: wizardSlice, blueprints: blueprintsSlice.reducer, }); @@ -64,7 +61,7 @@ startAppListening({ distribution: distribution, })(state as serviceState); - const allowedImageTypes = architecturesResponse?.data?.find( + const allowedImageTypes = architecturesResponse.data?.find( (elem) => elem.arch === architecture )?.image_types; diff --git a/src/store/service/enhancedImageBuilderApi.ts b/src/store/service/enhancedImageBuilderApi.ts index 18b20130..0d50ea94 100644 --- a/src/store/service/enhancedImageBuilderApi.ts +++ b/src/store/service/enhancedImageBuilderApi.ts @@ -1,5 +1,3 @@ -import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux'; - import { imageBuilderApi } from '../imageBuilderApi'; /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -51,146 +49,45 @@ const enhancedApi = imageBuilderApi.enhanceEndpoints({ }, updateBlueprint: { onQueryStarted: async (_, { dispatch, queryFulfilled }) => { - queryFulfilled - .then(() => { - dispatch( - // @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints() - imageBuilderApi.util.invalidateTags(['Blueprints', 'Blueprint']) - ); - dispatch( - addNotification({ - variant: 'success', - title: 'Changes saved to blueprint', - }) - ); - }) - .catch((err) => { - dispatch( - addNotification({ - variant: 'danger', - title: 'Blueprint could not be updated', - description: `Status code ${err.error.status}: ${errorMessage( - err - )}`, - }) - ); - }); + queryFulfilled.then(() => { + dispatch( + // @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints() + imageBuilderApi.util.invalidateTags(['Blueprints', 'Blueprint']) + ); + }); }, }, createBlueprint: { onQueryStarted: async (_, { dispatch, queryFulfilled }) => { - queryFulfilled - .then(() => { - // @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints() - dispatch(imageBuilderApi.util.invalidateTags(['Blueprints'])); - dispatch( - addNotification({ - variant: 'success', - title: 'Blueprint is being created', - }) - ); - }) - .catch((err) => { - dispatch( - addNotification({ - variant: 'danger', - title: 'Blueprint could not be created', - description: `Status code ${err.error.status}: ${errorMessage( - err - )}`, - }) - ); - }); + queryFulfilled.then(() => { + // @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints() + dispatch(imageBuilderApi.util.invalidateTags(['Blueprints'])); + }); }, }, cloneCompose: { - onQueryStarted: async ( - { composeId, cloneRequest }, - { dispatch, queryFulfilled } - ) => { - queryFulfilled - .then(() => { - dispatch( - imageBuilderApi.util.invalidateTags([ - // @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints() - { type: 'Clone', id: composeId }, - ]) - ); - - dispatch( - addNotification({ - variant: 'success', - title: - 'Your image is being shared to ' + - cloneRequest.region + - ' region', - }) - ); - }) - .catch((err) => { - dispatch( - addNotification({ - variant: 'danger', - title: 'Your image could not be shared', - description: `Status code ${err.error.status}: ${errorMessage( - err - )}`, - }) - ); - }); + onQueryStarted: async ({ composeId }, { dispatch, queryFulfilled }) => { + queryFulfilled.then(() => { + dispatch( + imageBuilderApi.util.invalidateTags([ + // @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints() + { type: 'Clone', id: composeId }, + ]) + ); + }); }, }, composeBlueprint: { invalidatesTags: [{ type: 'Compose' }, { type: 'BlueprintComposes' }], - onQueryStarted: async (_, { dispatch, queryFulfilled }) => { - queryFulfilled - .then(() => { - dispatch( - addNotification({ - variant: 'success', - title: 'Image is being built', - }) - ); - }) - .catch((err) => { - dispatch( - addNotification({ - variant: 'danger', - title: 'Image could not be built', - description: `Status code ${err.error.status}: ${errorMessage( - err - )}`, - }) - ); - }); - }, }, composeImage: { onQueryStarted: async (_, { dispatch, queryFulfilled }) => { - queryFulfilled - .then(() => { - dispatch( - // @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints() - imageBuilderApi.util.invalidateTags(['Blueprints', 'Compose']) - ); - dispatch( - addNotification({ - variant: 'success', - title: 'Your image is being created', - }) - ); - }) - .catch((err) => { - dispatch( - addNotification({ - variant: 'danger', - title: 'Your image could not be created', - description: `Status code ${err.error.status}: ${errorMessage( - err - )}`, - }) - ); - }); + queryFulfilled.then(() => { + dispatch( + // @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints() + imageBuilderApi.util.invalidateTags(['Blueprints', 'Compose']) + ); + }); }, }, deleteBlueprint: { @@ -199,53 +96,9 @@ const enhancedApi = imageBuilderApi.enhanceEndpoints({ { type: 'BlueprintComposes' }, { type: 'Compose' }, ], - onQueryStarted: async (_, { dispatch, queryFulfilled }) => { - queryFulfilled - .then(() => { - dispatch( - addNotification({ - variant: 'success', - title: 'Blueprint was deleted', - }) - ); - }) - .catch((err) => { - dispatch( - addNotification({ - variant: 'danger', - title: 'Blueprint could not be deleted', - description: `Status code ${err.error.status}: ${errorMessage( - err - )}`, - }) - ); - }); - }, }, fixupBlueprint: { invalidatesTags: [{ type: 'Blueprint' }], - onQueryStarted: async (_, { dispatch, queryFulfilled }) => { - queryFulfilled - .then(() => { - dispatch( - addNotification({ - variant: 'success', - title: 'Blueprint was fixed', - }) - ); - }) - .catch((err) => { - dispatch( - addNotification({ - variant: 'danger', - title: 'Blueprint could not be fixed', - description: `Status code ${err.error.status}: ${errorMessage( - err - )}`, - }) - ); - }); - }, }, }, });