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] b1d4973144/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] <support@github.com>
Assisted-by: cursor ide for generalizing the `useMutationWithNotification`
hook.
This commit is contained in:
Gianluca Zuccarelli 2025-06-24 16:45:14 +01:00 committed by Klara Simickova
parent 77e0f5d6bf
commit e8d46dd716
32 changed files with 412 additions and 473 deletions

38
package-lock.json generated
View file

@ -15,7 +15,7 @@
"@patternfly/react-core": "6.1.0", "@patternfly/react-core": "6.1.0",
"@patternfly/react-table": "6.1.0", "@patternfly/react-table": "6.1.0",
"@redhat-cloud-services/frontend-components": "6.0.4", "@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", "@redhat-cloud-services/frontend-components-utilities": "6.1.0",
"@reduxjs/toolkit": "2.8.2", "@reduxjs/toolkit": "2.8.2",
"@scalprum/react-core": "0.9.5", "@scalprum/react-core": "0.9.5",
@ -4008,30 +4008,19 @@
} }
}, },
"node_modules/@redhat-cloud-services/frontend-components-notifications": { "node_modules/@redhat-cloud-services/frontend-components-notifications": {
"version": "5.0.4", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-5.0.4.tgz", "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-6.0.2.tgz",
"integrity": "sha512-2/D+tNwKyV8y41nFgzDPIo0wV4BmYKvY01GvtEccvZ0so09h6dDqhMb+1NzfVw1dso12obc3KAd/cQtqeHMuMQ==", "integrity": "sha512-twJAOyBpg/39rxzd21XzQTNwpfxhXx/m0Rmdv7EQhV3lyZ4YuOrEjabshEO9jhTgoANp6XvMbnOmsoYVfrPOwg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@redhat-cloud-services/frontend-components": "^6.0.0", "@redhat-cloud-services/frontend-components": "^6.0.0",
"@redhat-cloud-services/frontend-components-utilities": "^6.0.0", "@redhat-cloud-services/frontend-components-utilities": "^6.0.0"
"redux-promise-middleware": "6.1.3"
}, },
"peerDependencies": { "peerDependencies": {
"@patternfly/react-core": "^6.0.0", "@patternfly/react-core": "^6.0.0",
"@patternfly/react-icons": "^6.0.0", "@patternfly/react-icons": "^6.0.0",
"prop-types": "^15.6.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^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"
} }
}, },
"node_modules/@redhat-cloud-services/frontend-components-utilities": { "node_modules/@redhat-cloud-services/frontend-components-utilities": {
@ -22174,19 +22163,12 @@
} }
}, },
"@redhat-cloud-services/frontend-components-notifications": { "@redhat-cloud-services/frontend-components-notifications": {
"version": "5.0.4", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-5.0.4.tgz", "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-6.0.2.tgz",
"integrity": "sha512-2/D+tNwKyV8y41nFgzDPIo0wV4BmYKvY01GvtEccvZ0so09h6dDqhMb+1NzfVw1dso12obc3KAd/cQtqeHMuMQ==", "integrity": "sha512-twJAOyBpg/39rxzd21XzQTNwpfxhXx/m0Rmdv7EQhV3lyZ4YuOrEjabshEO9jhTgoANp6XvMbnOmsoYVfrPOwg==",
"requires": { "requires": {
"@redhat-cloud-services/frontend-components": "^6.0.0", "@redhat-cloud-services/frontend-components": "^6.0.0",
"@redhat-cloud-services/frontend-components-utilities": "^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": { "@redhat-cloud-services/frontend-components-utilities": {

View file

@ -13,7 +13,7 @@
"@patternfly/react-core": "6.1.0", "@patternfly/react-core": "6.1.0",
"@patternfly/react-table": "6.1.0", "@patternfly/react-table": "6.1.0",
"@redhat-cloud-services/frontend-components": "6.0.4", "@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", "@redhat-cloud-services/frontend-components-utilities": "6.1.0",
"@reduxjs/toolkit": "2.8.2", "@reduxjs/toolkit": "2.8.2",
"@scalprum/react-core": "0.9.5", "@scalprum/react-core": "0.9.5",

View file

@ -1,7 +1,7 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome'; 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 '@patternfly/patternfly/patternfly-addons.css';
import { Router } from './Router'; import { Router } from './Router';
@ -26,8 +26,9 @@ const App = () => {
return ( return (
<React.Fragment> <React.Fragment>
<NotificationsPortal /> <NotificationsProvider>
<Router /> <Router />
</NotificationsProvider>
</React.Fragment> </React.Fragment>
); );
}; };

View file

@ -5,7 +5,7 @@ import React from 'react';
import 'cockpit-dark-theme'; import 'cockpit-dark-theme';
import { Page, PageSection } from '@patternfly/react-core'; 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 { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { HashRouter } from 'react-router-dom'; import { HashRouter } from 'react-router-dom';
@ -31,10 +31,11 @@ const Application = () => {
return ( return (
<React.Fragment> <React.Fragment>
<NotificationsPortal /> <NotificationsProvider>
<HashRouter> <HashRouter>
<Router /> <Router />
</HashRouter> </HashRouter>
</NotificationsProvider>
</React.Fragment> </React.Fragment>
); );
}; };

View file

@ -10,15 +10,13 @@ import {
Spinner, Spinner,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { useDeleteBPWithNotification as useDeleteBlueprintMutation } from '../../Hooks';
import { import {
selectSelectedBlueprintId, selectSelectedBlueprintId,
setBlueprintId, setBlueprintId,
} from '../../store/BlueprintSlice'; } from '../../store/BlueprintSlice';
import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { useAppDispatch, useAppSelector } from '../../store/hooks';
import { import { BlueprintItem } from '../../store/imageBuilderApi';
BlueprintItem,
useDeleteBlueprintMutation,
} from '../../store/imageBuilderApi';
type blueprintProps = { type blueprintProps = {
blueprint: BlueprintItem; blueprint: BlueprintItem;
@ -28,7 +26,7 @@ const BlueprintCard = ({ blueprint }: blueprintProps) => {
const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId); const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [, { isLoading }] = useDeleteBlueprintMutation({ const { isLoading } = useDeleteBlueprintMutation({
fixedCacheKey: 'delete-blueprint', fixedCacheKey: 'delete-blueprint',
}); });

View file

@ -16,17 +16,14 @@ import {
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { MenuToggleElement } from '@patternfly/react-core/dist/esm/components/MenuToggle/MenuToggle'; import { MenuToggleElement } from '@patternfly/react-core/dist/esm/components/MenuToggle/MenuToggle';
import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; 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 { ChromeUser } from '@redhat-cloud-services/types';
import { skipToken } from '@reduxjs/toolkit/query'; import { skipToken } from '@reduxjs/toolkit/query';
import { AMPLITUDE_MODULE_NAME, targetOptions } from '../../constants'; import { AMPLITUDE_MODULE_NAME, targetOptions } from '../../constants';
import { import { useComposeBPWithNotification as useComposeBlueprintMutation } from '../../Hooks';
useGetBlueprintQuery, import { useGetBlueprintQuery } from '../../store/backendApi';
useComposeBlueprintMutation,
} from '../../store/backendApi';
import { selectSelectedBlueprintId } from '../../store/BlueprintSlice'; import { selectSelectedBlueprintId } from '../../store/BlueprintSlice';
import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { useAppSelector } from '../../store/hooks';
import { ImageTypes } from '../../store/imageBuilderApi'; import { ImageTypes } from '../../store/imageBuilderApi';
type BuildImagesButtonPropTypes = { type BuildImagesButtonPropTypes = {
@ -37,9 +34,8 @@ type BuildImagesButtonPropTypes = {
export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => { export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId); const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId);
const [deselectedTargets, setDeselectedTargets] = useState<ImageTypes[]>([]); const [deselectedTargets, setDeselectedTargets] = useState<ImageTypes[]>([]);
const [buildBlueprint, { isLoading: imageBuildLoading }] = const { trigger: buildBlueprint, isLoading: imageBuildLoading } =
useComposeBlueprintMutation(); useComposeBlueprintMutation();
const dispatch = useAppDispatch();
const { analytics, auth } = useChrome(); const { analytics, auth } = useChrome();
const [userData, setUserData] = useState<ChromeUser | void>(undefined); const [userData, setUserData] = useState<ChromeUser | void>(undefined);
@ -53,29 +49,19 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
const onBuildHandler = async () => { const onBuildHandler = async () => {
if (selectedBlueprintId) { if (selectedBlueprintId) {
try { await buildBlueprint({
await buildBlueprint({ id: selectedBlueprintId,
id: selectedBlueprintId, body: {
body: { image_types: blueprintImageType?.filter(
image_types: blueprintImageType?.filter( (target) => !deselectedTargets.includes(target)
(target) => !deselectedTargets.includes(target) ),
), },
}, });
}); analytics.track(`${AMPLITUDE_MODULE_NAME} - Image Requested`, {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Image Requested`, { module: AMPLITUDE_MODULE_NAME,
module: AMPLITUDE_MODULE_NAME, trigger: 'synchronize images',
trigger: 'synchronize images', account_id: userData?.identity.internal?.account_id || 'Not found',
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,
})
);
}
} }
}; };
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -180,7 +166,7 @@ export const BuildImagesButtonEmptyState = ({
children, children,
}: BuildImagesButtonEmptyStatePropTypes) => { }: BuildImagesButtonEmptyStatePropTypes) => {
const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId); const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId);
const [buildBlueprint, { isLoading: imageBuildLoading }] = const { trigger: buildBlueprint, isLoading: imageBuildLoading } =
useComposeBlueprintMutation(); useComposeBlueprintMutation();
const onBuildHandler = async () => { const onBuildHandler = async () => {
if (selectedBlueprintId) { if (selectedBlueprintId) {

View file

@ -10,11 +10,8 @@ import {
PAGINATION_LIMIT, PAGINATION_LIMIT,
PAGINATION_OFFSET, PAGINATION_OFFSET,
} from '../../constants'; } from '../../constants';
import { import { useDeleteBPWithNotification as useDeleteBlueprintMutation } from '../../Hooks';
backendApi, import { backendApi, useGetBlueprintsQuery } from '../../store/backendApi';
useDeleteBlueprintMutation,
useGetBlueprintsQuery,
} from '../../store/backendApi';
import { import {
selectBlueprintSearchInput, selectBlueprintSearchInput,
selectLimit, selectLimit,
@ -65,7 +62,7 @@ export const DeleteBlueprintModal: React.FunctionComponent<
)?.name, )?.name,
}), }),
}); });
const [deleteBlueprint] = useDeleteBlueprintMutation({ const { trigger: deleteBlueprint } = useDeleteBlueprintMutation({
fixedCacheKey: 'delete-blueprint', fixedCacheKey: 'delete-blueprint',
}); });
const handleDelete = async () => { const handleDelete = async () => {

View file

@ -16,7 +16,7 @@ import {
import { Modal, ModalVariant } from '@patternfly/react-core/deprecated'; import { Modal, ModalVariant } from '@patternfly/react-core/deprecated';
import { DropEvent } from '@patternfly/react-core/dist/esm/helpers'; import { DropEvent } from '@patternfly/react-core/dist/esm/helpers';
import { HelpIcon } from '@patternfly/react-icons'; 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 { useNavigate } from 'react-router-dom';
import { mapOnPremToHosted } from './helpers/onPremToHostedBlueprintMapper'; import { mapOnPremToHosted } from './helpers/onPremToHostedBlueprintMapper';
@ -26,7 +26,6 @@ import {
ApiRepositoryRequest, ApiRepositoryRequest,
useBulkImportRepositoriesMutation, useBulkImportRepositoriesMutation,
} from '../../store/contentSourcesApi'; } from '../../store/contentSourcesApi';
import { useAppDispatch } from '../../store/hooks';
import { import {
BlueprintExportResponse, BlueprintExportResponse,
BlueprintItem, BlueprintItem,
@ -64,7 +63,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
const [isRejected, setIsRejected] = React.useState(false); const [isRejected, setIsRejected] = React.useState(false);
const [isOnPrem, setIsOnPrem] = React.useState(false); const [isOnPrem, setIsOnPrem] = React.useState(false);
const [isCheckedImportRepos, setIsCheckedImportRepos] = React.useState(true); const [isCheckedImportRepos, setIsCheckedImportRepos] = React.useState(true);
const dispatch = useAppDispatch(); const addNotification = useAddNotification();
const [importRepositories] = useBulkImportRepositoriesMutation(); const [importRepositories] = useBulkImportRepositoriesMutation();
const handleFileInputChange = ( const handleFileInputChange = (
@ -103,33 +102,27 @@ export const ImportBlueprintModal: React.FunctionComponent<
importedRepositoryNames.push(repository.url); importedRepositoryNames.push(repository.url);
return; return;
} }
dispatch( addNotification({
addNotification({ variant: 'warning',
variant: 'warning', title: 'Failed to import custom repositories',
title: 'Failed to import custom repositories', description: JSON.stringify(repository.warnings),
description: JSON.stringify(repository.warnings), });
})
);
}); });
if (importedRepositoryNames.length !== 0) { if (importedRepositoryNames.length !== 0) {
dispatch( addNotification({
addNotification({ variant: 'info',
variant: 'info', title: 'Successfully imported custom repositories',
title: 'Successfully imported custom repositories', description: importedRepositoryNames.join(', '),
description: importedRepositoryNames.join(', '), });
})
);
} }
return newCustomRepos; return newCustomRepos;
} }
} catch { } catch {
dispatch( addNotification({
addNotification({ variant: 'danger',
variant: 'danger', title: 'Custom repositories import failed',
title: 'Custom repositories import failed', });
})
);
} }
} }
} }
@ -197,13 +190,11 @@ export const ImportBlueprintModal: React.FunctionComponent<
} }
} catch (error) { } catch (error) {
setIsInvalidFormat(true); setIsInvalidFormat(true);
dispatch( addNotification({
addNotification({ variant: 'warning',
variant: 'warning', title: 'File is not a valid blueprint',
title: 'File is not a valid blueprint', description: error?.data?.error?.message,
description: error?.data?.error?.message, });
})
);
} }
}; };
parseAndImport(); parseAndImport();

View file

@ -1,6 +1,6 @@
import React, { useEffect } from 'react'; 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 { useLocation, useNavigate } from 'react-router-dom';
import CreateImageWizard from './CreateImageWizard'; import CreateImageWizard from './CreateImageWizard';
@ -13,6 +13,7 @@ const ImportImageWizard = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const addNotification = useAddNotification();
const locationState = location.state as { blueprint?: wizardState }; const locationState = location.state as { blueprint?: wizardState };
const blueprint = locationState?.blueprint; const blueprint = locationState?.blueprint;
useEffect(() => { useEffect(() => {
@ -20,12 +21,10 @@ const ImportImageWizard = () => {
dispatch(loadWizardState(blueprint)); dispatch(loadWizardState(blueprint));
} else { } else {
navigate(resolveRelPath('')); navigate(resolveRelPath(''));
dispatch( addNotification({
addNotification({ variant: 'warning',
variant: 'warning', title: 'No blueprint was imported',
title: 'No blueprint was imported', });
})
);
} }
}, [blueprint, dispatch]); }, [blueprint, dispatch]);
return <CreateImageWizard />; return <CreateImageWizard />;

View file

@ -13,7 +13,7 @@ import {
TextInputGroup, TextInputGroup,
TextInputGroupMain, TextInputGroupMain,
} from '@patternfly/react-core'; } 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 ManageKeysButton from './ManageKeysButton';
import PopoverActivation from './PopoverActivation'; import PopoverActivation from './PopoverActivation';
@ -37,6 +37,7 @@ import { generateRandomId } from '../../../utilities/generateRandomId';
const ActivationKeysList = () => { const ActivationKeysList = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const addNotification = useAddNotification();
const activationKey = useAppSelector(selectActivationKey); const activationKey = useAppSelector(selectActivationKey);
const registrationType = useAppSelector(selectRegistrationType); const registrationType = useAppSelector(selectRegistrationType);
@ -138,13 +139,11 @@ const ActivationKeysList = () => {
); );
dispatch(changeActivationKey(defaultActivationKeyName)); dispatch(changeActivationKey(defaultActivationKeyName));
} catch (error) { } catch (error) {
dispatch( addNotification({
addNotification({ variant: 'danger',
variant: 'danger', title: 'Error creating activation key',
title: 'Error creating activation key', description: error?.data?.error?.message,
description: error?.data?.error?.message, });
})
);
} }
}; };

View file

@ -14,12 +14,15 @@ import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import { ChromeUser } from '@redhat-cloud-services/types'; import { ChromeUser } from '@redhat-cloud-services/types';
import { AMPLITUDE_MODULE_NAME } from '../../../../../constants'; 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 { setBlueprintId } from '../../../../../store/BlueprintSlice';
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks'; import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
import { import {
CreateBlueprintRequest, CreateBlueprintRequest,
useComposeBlueprintMutation, CreateBlueprintResponse,
} from '../../../../../store/imageBuilderApi'; } from '../../../../../store/imageBuilderApi';
import { selectPackages } from '../../../../../store/wizardSlice'; import { selectPackages } from '../../../../../store/wizardSlice';
import { createAnalytics } from '../../../../../Utilities/analytics'; import { createAnalytics } from '../../../../../Utilities/analytics';
@ -46,8 +49,8 @@ export const CreateSaveAndBuildBtn = ({
}, [auth]); }, [auth]);
const packages = useAppSelector(selectPackages); const packages = useAppSelector(selectPackages);
const [buildBlueprint] = useComposeBlueprintMutation(); const { trigger: buildBlueprint } = useComposeBlueprintMutation();
const [createBlueprint] = useCreateBlueprintMutation({ const { trigger: createBlueprint } = useCreateBlueprintMutation({
fixedCacheKey: 'createBlueprintKey', fixedCacheKey: 'createBlueprintKey',
}); });
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -70,13 +73,11 @@ export const CreateSaveAndBuildBtn = ({
), ),
}); });
} }
const blueprint = if (requestBody) {
requestBody && const blueprint = (await createBlueprint({
(await createBlueprint({
createBlueprintRequest: requestBody, createBlueprintRequest: requestBody,
}).unwrap()); // unwrap - access the success payload immediately after a mutation })) as CreateBlueprintResponse;
if (blueprint) {
buildBlueprint({ id: blueprint.id, body: {} }); buildBlueprint({ id: blueprint.id, body: {} });
dispatch(setBlueprintId(blueprint.id)); dispatch(setBlueprintId(blueprint.id));
} }
@ -107,7 +108,7 @@ export const CreateSaveButton = ({
}, [auth]); }, [auth]);
const packages = useAppSelector(selectPackages); const packages = useAppSelector(selectPackages);
const [createBlueprint, { isLoading }] = useCreateBlueprintMutation({ const { trigger: createBlueprint, isLoading } = useCreateBlueprintMutation({
fixedCacheKey: 'createBlueprintKey', fixedCacheKey: 'createBlueprintKey',
}); });
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -166,15 +167,11 @@ export const CreateSaveButton = ({
account_id: userData?.identity.internal?.account_id || 'Not found', account_id: userData?.identity.internal?.account_id || 'Not found',
}); });
} }
if (requestBody) {
const blueprint = const blueprint = (await createBlueprint({
requestBody &&
(await createBlueprint({
createBlueprintRequest: requestBody, createBlueprintRequest: requestBody,
}).unwrap()); })) as CreateBlueprintResponse;
dispatch(setBlueprintId(blueprint.id));
if (blueprint) {
dispatch(setBlueprintId(blueprint?.id));
} }
}; };

View file

@ -12,12 +12,12 @@ import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import { ChromeUser } from '@redhat-cloud-services/types'; import { ChromeUser } from '@redhat-cloud-services/types';
import { AMPLITUDE_MODULE_NAME } from '../../../../../constants'; import { AMPLITUDE_MODULE_NAME } from '../../../../../constants';
import { useUpdateBlueprintMutation } from '../../../../../store/backendApi';
import { useAppSelector } from '../../../../../store/hooks';
import { import {
CreateBlueprintRequest, useComposeBPWithNotification as useComposeBlueprintMutation,
useComposeBlueprintMutation, useUpdateBPWithNotification as useUpdateBlueprintMutation,
} from '../../../../../store/imageBuilderApi'; } from '../../../../../Hooks';
import { useAppSelector } from '../../../../../store/hooks';
import { CreateBlueprintRequest } from '../../../../../store/imageBuilderApi';
import { selectPackages } from '../../../../../store/wizardSlice'; import { selectPackages } from '../../../../../store/wizardSlice';
import { createAnalytics } from '../../../../../Utilities/analytics'; import { createAnalytics } from '../../../../../Utilities/analytics';
@ -43,10 +43,10 @@ export const EditSaveAndBuildBtn = ({
setUserData(data); setUserData(data);
})(); })();
}, [auth]); }, [auth]);
const [buildBlueprint] = useComposeBlueprintMutation(); const { trigger: buildBlueprint } = useComposeBlueprintMutation();
const packages = useAppSelector(selectPackages); const packages = useAppSelector(selectPackages);
const [updateBlueprint] = useUpdateBlueprintMutation({ const { trigger: updateBlueprint } = useUpdateBlueprintMutation({
fixedCacheKey: 'updateBlueprintKey', fixedCacheKey: 'updateBlueprintKey',
}); });
@ -104,7 +104,7 @@ export const EditSaveButton = ({
}, [auth]); }, [auth]);
const packages = useAppSelector(selectPackages); const packages = useAppSelector(selectPackages);
const [updateBlueprint, { isLoading }] = useUpdateBlueprintMutation({ const { trigger: updateBlueprint, isLoading } = useUpdateBlueprintMutation({
fixedCacheKey: 'updateBlueprintKey', fixedCacheKey: 'updateBlueprintKey',
}); });
const onSave = async () => { const onSave = async () => {

View file

@ -17,20 +17,20 @@ import { CreateSaveAndBuildBtn, CreateSaveButton } from './CreateDropdown';
import { EditSaveAndBuildBtn, EditSaveButton } from './EditDropdown'; import { EditSaveAndBuildBtn, EditSaveButton } from './EditDropdown';
import { import {
useCreateBlueprintMutation, useCreateBPWithNotification as useCreateBlueprintMutation,
useUpdateBlueprintMutation, useUpdateBPWithNotification as useUpdateBlueprintMutation,
} from '../../../../../store/backendApi'; } from '../../../../../Hooks';
import { resolveRelPath } from '../../../../../Utilities/path'; import { resolveRelPath } from '../../../../../Utilities/path';
import { mapRequestFromState } from '../../../utilities/requestMapper'; import { mapRequestFromState } from '../../../utilities/requestMapper';
import { useIsBlueprintValid } from '../../../utilities/useValidation'; import { useIsBlueprintValid } from '../../../utilities/useValidation';
const ReviewWizardFooter = () => { const ReviewWizardFooter = () => {
const { goToPrevStep, close } = useWizardContext(); const { goToPrevStep, close } = useWizardContext();
const [, { isSuccess: isCreateSuccess, reset: resetCreate }] = const { isSuccess: isCreateSuccess, reset: resetCreate } =
useCreateBlueprintMutation({ fixedCacheKey: 'createBlueprintKey' }); useCreateBlueprintMutation({ fixedCacheKey: 'createBlueprintKey' });
// initialize the server store with the data from RTK query // initialize the server store with the data from RTK query
const [, { isSuccess: isUpdateSuccess, reset: resetUpdate }] = const { isSuccess: isUpdateSuccess, reset: resetUpdate } =
useUpdateBlueprintMutation({ fixedCacheKey: 'updateBlueprintKey' }); useUpdateBlueprintMutation({ fixedCacheKey: 'updateBlueprintKey' });
const { auth } = useChrome(); const { auth } = useChrome();
const { composeId } = useParams(); const { composeId } = useParams();

View file

@ -13,6 +13,7 @@ import {
Title, Title,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { useFixupBPWithNotification as useFixupBlueprintMutation } from '../../Hooks';
import { import {
useGetBlueprintsQuery, useGetBlueprintsQuery,
useGetBlueprintQuery, useGetBlueprintQuery,
@ -28,7 +29,6 @@ import {
useGetBlueprintComposesQuery, useGetBlueprintComposesQuery,
Distributions, Distributions,
GetBlueprintComposesApiArg, GetBlueprintComposesApiArg,
useFixupBlueprintMutation,
} from '../../store/imageBuilderApi'; } from '../../store/imageBuilderApi';
import { BlueprintActionsMenu } from '../Blueprints/BlueprintActionsMenu'; import { BlueprintActionsMenu } from '../Blueprints/BlueprintActionsMenu';
import BlueprintDiffModal from '../Blueprints/BlueprintDiffModal'; import BlueprintDiffModal from '../Blueprints/BlueprintDiffModal';
@ -128,7 +128,7 @@ const ImagesTableToolbar: React.FC<imagesTableToolbarProps> = ({
{ skip: !selectedBlueprintId } { skip: !selectedBlueprintId }
); );
const [fixupBlueprint] = useFixupBlueprintMutation(); const { trigger: fixupBlueprint } = useFixupBlueprintMutation();
const hasErrors = const hasErrors =
blueprintDetails?.lint?.errors && blueprintDetails?.lint?.errors.length > 0; blueprintDetails?.lint?.errors && blueprintDetails?.lint?.errors.length > 0;
const [isLintExp, setIsLintExp] = React.useState(true); const [isLintExp, setIsLintExp] = React.useState(true);

View file

@ -24,7 +24,7 @@ import './LandingPage.scss';
import { NewAlert } from './NewAlert'; import { NewAlert } from './NewAlert';
import { MANAGING_WITH_DNF_URL, OSTREE_URL } from '../../constants'; 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 { resolveRelPath } from '../../Utilities/path';
import { useFlag } from '../../Utilities/useGetEnvironment'; import { useFlag } from '../../Utilities/useGetEnvironment';
import BlueprintsSidebar from '../Blueprints/BlueprintsSideBar'; import BlueprintsSidebar from '../Blueprints/BlueprintsSideBar';

View file

@ -29,9 +29,9 @@ import {
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { AWS_REGIONS } from '../../constants'; import { AWS_REGIONS } from '../../constants';
import { useCloneComposeWithNotification as useCloneComposeMutation } from '../../Hooks';
import { import {
ComposeStatus, ComposeStatus,
useCloneComposeMutation,
useGetComposeStatusQuery, useGetComposeStatusQuery,
} from '../../store/imageBuilderApi'; } from '../../store/imageBuilderApi';
import { resolveRelPath } from '../../Utilities/path'; 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({ const { data: composeStatus, isSuccess } = useGetComposeStatusQuery({
composeId, composeId,

View file

@ -3,19 +3,17 @@ import React from 'react';
import AsyncComponent from '@redhat-cloud-services/frontend-components/AsyncComponent'; import AsyncComponent from '@redhat-cloud-services/frontend-components/AsyncComponent';
import ErrorState from '@redhat-cloud-services/frontend-components/ErrorState'; import ErrorState from '@redhat-cloud-services/frontend-components/ErrorState';
import Unavailable from '@redhat-cloud-services/frontend-components/Unavailable'; import Unavailable from '@redhat-cloud-services/frontend-components/Unavailable';
import { useDispatch } from 'react-redux';
import { useNavigate, useLocation, useParams } from 'react-router-dom'; import { useNavigate, useLocation, useParams } from 'react-router-dom';
import { import {
getNotificationProp, useGetNotificationProp,
manageEdgeImagesUrlName, manageEdgeImagesUrlName,
} from '../../Utilities/edge'; } from '../../Hooks/Edge/useGetNotificationProp';
import { resolveRelPath } from '../../Utilities/path'; import { resolveRelPath } from '../../Utilities/path';
import { useFlag } from '../../Utilities/useGetEnvironment'; import { useFlag } from '../../Utilities/useGetEnvironment';
const ImageDetail = () => { const ImageDetail = () => {
const dispatch = useDispatch(); const notificationProp = useGetNotificationProp();
const notificationProp = getNotificationProp(dispatch);
// Feature flag for the federated modules // Feature flag for the federated modules
const edgeParityFlag = useFlag('edgeParity.image-list'); const edgeParityFlag = useFlag('edgeParity.image-list');
// Feature flag to access the 'local' images table list // Feature flag to access the 'local' images table list

View file

@ -3,20 +3,18 @@ import React from 'react';
import AsyncComponent from '@redhat-cloud-services/frontend-components/AsyncComponent'; import AsyncComponent from '@redhat-cloud-services/frontend-components/AsyncComponent';
import ErrorState from '@redhat-cloud-services/frontend-components/ErrorState'; import ErrorState from '@redhat-cloud-services/frontend-components/ErrorState';
import Unavailable from '@redhat-cloud-services/frontend-components/Unavailable'; import Unavailable from '@redhat-cloud-services/frontend-components/Unavailable';
import { useDispatch } from 'react-redux';
import { useNavigate, useLocation } from 'react-router-dom'; import { useNavigate, useLocation } from 'react-router-dom';
import { CREATING_IMAGES_WITH_IB_URL } from '../../constants'; import { CREATING_IMAGES_WITH_IB_URL } from '../../constants';
import { import {
getNotificationProp, useGetNotificationProp,
manageEdgeImagesUrlName, manageEdgeImagesUrlName,
} from '../../Utilities/edge'; } from '../../Hooks/Edge/useGetNotificationProp';
import { resolveRelPath } from '../../Utilities/path'; import { resolveRelPath } from '../../Utilities/path';
import { useFlag } from '../../Utilities/useGetEnvironment'; import { useFlag } from '../../Utilities/useGetEnvironment';
const ImagesTable = () => { const ImagesTable = () => {
const dispatch = useDispatch(); const notificationProp = useGetNotificationProp();
const notificationProp = getNotificationProp(dispatch);
// Feature flag for the federated modules // Feature flag for the federated modules
const edgeParityFlag = useFlag('edgeParity.image-list'); const edgeParityFlag = useFlag('edgeParity.image-list');
// Feature flag to access the 'local' images table list // Feature flag to access the 'local' images table list

View file

@ -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 };

View file

@ -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,
};
};

View file

@ -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,
};
};

View file

@ -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,
};
};

View file

@ -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,
};
};

View file

@ -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,
};
};

View file

@ -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<TArgs> = {
success: (args: TArgs) => string;
error?: (args: TArgs, error: unknown) => string;
};
export type HookOptions = {
fixedCacheKey?: string | string;
};
type MutationOptions<Arg> = {
options?: HookOptions | undefined;
messages: NotificationMessages<Arg>;
};
// 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<Result, Arg, BaseQueryFn>, State],
{ options, messages }: MutationOptions<Arg>
) {
const [trigger, state] = mutationHook(options);
const addNotification = useAddNotification();
const handler = async (args: Arg): Promise<Result> => {
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,
};
}

View file

@ -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,
};
};

6
src/Hooks/index.tsx Normal file
View file

@ -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';

View file

@ -4,7 +4,7 @@ import { Route, Routes } from 'react-router-dom';
import EdgeImageDetail from './Components/edge/ImageDetails'; import EdgeImageDetail from './Components/edge/ImageDetails';
import ShareImageModal from './Components/ShareImageModal/ShareImageModal'; import ShareImageModal from './Components/ShareImageModal/ShareImageModal';
import { manageEdgeImagesUrlName } from './Utilities/edge'; import { manageEdgeImagesUrlName } from './Hooks/Edge/useGetNotificationProp';
import { import {
useFlag, useFlag,
useFlagWithEphemDefault, useFlagWithEphemDefault,

View file

@ -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 };

View file

@ -1,5 +1,3 @@
import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux';
import { cockpitApi } from './cockpitApi'; import { cockpitApi } from './cockpitApi';
const enhancedApi = cockpitApi.enhanceEndpoints({ const enhancedApi = cockpitApi.enhanceEndpoints({
@ -17,98 +15,15 @@ const enhancedApi = cockpitApi.enhanceEndpoints({
}, },
createBlueprint: { createBlueprint: {
invalidatesTags: [{ type: 'Blueprints' }], 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: { updateBlueprint: {
invalidatesTags: [{ type: 'Blueprint' }, { type: 'Blueprints' }], 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: { deleteBlueprint: {
invalidatesTags: [{ type: 'Blueprints' }, { type: 'Composes' }], 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: { composeBlueprint: {
invalidatesTags: [{ type: 'Composes' }], 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: { getComposes: {
providesTags: [{ type: 'Composes' }], providesTags: [{ type: 'Composes' }],

View file

@ -1,4 +1,3 @@
import { notificationsReducer } from '@redhat-cloud-services/frontend-components-notifications/redux';
import { combineReducers, configureStore } from '@reduxjs/toolkit'; import { combineReducers, configureStore } from '@reduxjs/toolkit';
import promiseMiddleware from 'redux-promise-middleware'; import promiseMiddleware from 'redux-promise-middleware';
@ -27,7 +26,6 @@ export const serviceReducer = combineReducers({
[rhsmApi.reducerPath]: rhsmApi.reducer, [rhsmApi.reducerPath]: rhsmApi.reducer,
[provisioningApi.reducerPath]: provisioningApi.reducer, [provisioningApi.reducerPath]: provisioningApi.reducer,
[complianceApi.reducerPath]: complianceApi.reducer, [complianceApi.reducerPath]: complianceApi.reducer,
notifications: notificationsReducer,
wizard: wizardSlice, wizard: wizardSlice,
blueprints: blueprintsSlice.reducer, blueprints: blueprintsSlice.reducer,
}); });
@ -41,7 +39,6 @@ export const onPremReducer = combineReducers({
// TODO: add other endpoints so we can remove this. // TODO: add other endpoints so we can remove this.
// It's still needed to get things to work. // It's still needed to get things to work.
[imageBuilderApi.reducerPath]: imageBuilderApi.reducer, [imageBuilderApi.reducerPath]: imageBuilderApi.reducer,
notifications: notificationsReducer,
wizard: wizardSlice, wizard: wizardSlice,
blueprints: blueprintsSlice.reducer, blueprints: blueprintsSlice.reducer,
}); });
@ -64,7 +61,7 @@ startAppListening({
distribution: distribution, distribution: distribution,
})(state as serviceState); })(state as serviceState);
const allowedImageTypes = architecturesResponse?.data?.find( const allowedImageTypes = architecturesResponse.data?.find(
(elem) => elem.arch === architecture (elem) => elem.arch === architecture
)?.image_types; )?.image_types;

View file

@ -1,5 +1,3 @@
import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux';
import { imageBuilderApi } from '../imageBuilderApi'; import { imageBuilderApi } from '../imageBuilderApi';
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
@ -51,146 +49,45 @@ const enhancedApi = imageBuilderApi.enhanceEndpoints({
}, },
updateBlueprint: { updateBlueprint: {
onQueryStarted: async (_, { dispatch, queryFulfilled }) => { onQueryStarted: async (_, { dispatch, queryFulfilled }) => {
queryFulfilled queryFulfilled.then(() => {
.then(() => { dispatch(
dispatch( // @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints()
// @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints() imageBuilderApi.util.invalidateTags(['Blueprints', 'Blueprint'])
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
)}`,
})
);
});
}, },
}, },
createBlueprint: { createBlueprint: {
onQueryStarted: async (_, { dispatch, queryFulfilled }) => { onQueryStarted: async (_, { dispatch, queryFulfilled }) => {
queryFulfilled queryFulfilled.then(() => {
.then(() => { // @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints()
// @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints() dispatch(imageBuilderApi.util.invalidateTags(['Blueprints']));
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
)}`,
})
);
});
}, },
}, },
cloneCompose: { cloneCompose: {
onQueryStarted: async ( onQueryStarted: async ({ composeId }, { dispatch, queryFulfilled }) => {
{ composeId, cloneRequest }, queryFulfilled.then(() => {
{ dispatch, queryFulfilled } dispatch(
) => { imageBuilderApi.util.invalidateTags([
queryFulfilled // @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints()
.then(() => { { type: 'Clone', id: composeId },
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
)}`,
})
);
});
}, },
}, },
composeBlueprint: { composeBlueprint: {
invalidatesTags: [{ type: 'Compose' }, { type: 'BlueprintComposes' }], 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: { composeImage: {
onQueryStarted: async (_, { dispatch, queryFulfilled }) => { onQueryStarted: async (_, { dispatch, queryFulfilled }) => {
queryFulfilled queryFulfilled.then(() => {
.then(() => { dispatch(
dispatch( // @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints()
// @ts-expect-error Typescript is unaware of tag types being defined concurrently in enhanceEndpoints() imageBuilderApi.util.invalidateTags(['Blueprints', 'Compose'])
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
)}`,
})
);
});
}, },
}, },
deleteBlueprint: { deleteBlueprint: {
@ -199,53 +96,9 @@ const enhancedApi = imageBuilderApi.enhanceEndpoints({
{ type: 'BlueprintComposes' }, { type: 'BlueprintComposes' },
{ type: 'Compose' }, { 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: { fixupBlueprint: {
invalidatesTags: [{ type: 'Blueprint' }], 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
)}`,
})
);
});
},
}, },
}, },
}); });