From 09a62b3217c8d6a00c58c0aad09da88e6a9c3af4 Mon Sep 17 00:00:00 2001 From: Gianluca Zuccarelli Date: Fri, 5 Jun 2026 12:25:18 +0100 Subject: [PATCH 1/4] context: add PlatformContext, usePlatform hook, and PlatformProvider Provide the React context plumbing that entry points use to inject the correct platform hooks at runtime instead of relying on build-time environment variable switching. --- src/context/platform/index.ts | 15 +++++++ .../platform/tests/PlatformContext.test.tsx | 40 +++++++++++++++++++ src/context/platform/types.ts | 3 ++ 3 files changed, 58 insertions(+) create mode 100644 src/context/platform/index.ts create mode 100644 src/context/platform/tests/PlatformContext.test.tsx create mode 100644 src/context/platform/types.ts diff --git a/src/context/platform/index.ts b/src/context/platform/index.ts new file mode 100644 index 0000000000..a8466a90a3 --- /dev/null +++ b/src/context/platform/index.ts @@ -0,0 +1,15 @@ +import { createContext, useContext } from 'react'; + +import type { PlatformHooks } from './types'; + +const PlatformContext = createContext(null); + +export const usePlatform = (): PlatformHooks => { + const ctx = useContext(PlatformContext); + if (ctx === null) { + throw new Error('usePlatform must be used within a PlatformProvider'); + } + return ctx; +}; + +export const PlatformProvider = PlatformContext.Provider; diff --git a/src/context/platform/tests/PlatformContext.test.tsx b/src/context/platform/tests/PlatformContext.test.tsx new file mode 100644 index 0000000000..d83e7eb9ee --- /dev/null +++ b/src/context/platform/tests/PlatformContext.test.tsx @@ -0,0 +1,40 @@ +import React from 'react'; + +import { renderHook } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; + +import { PlatformProvider, usePlatform } from '@/context/platform'; +import type { PlatformHooks } from '@/context/platform/types'; + +describe('PlatformContext', () => { + it('throws when usePlatform is called outside PlatformProvider', () => { + const onError = (e: ErrorEvent) => e.preventDefault(); + window.addEventListener('error', onError); + + expect(() => { + renderHook(() => usePlatform()); + }).toThrow('usePlatform must be used within a PlatformProvider'); + + window.removeEventListener('error', onError); + }); + + it('provides the platform object when wrapped in PlatformProvider', () => { + const mockPlatform = { + queries: {}, + mutations: {}, + env: { + useFlag: () => false, + useGetEnvironment: () => ({ isBeta: () => false, isProd: () => true }), + }, + api: {}, + } as unknown as PlatformHooks; + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); + + const { result } = renderHook(() => usePlatform(), { wrapper }); + expect(result.current).toBe(mockPlatform); + expect(result.current.env.useFlag('test')).toBe(false); + }); +}); diff --git a/src/context/platform/types.ts b/src/context/platform/types.ts new file mode 100644 index 0000000000..fca8ca63f2 --- /dev/null +++ b/src/context/platform/types.ts @@ -0,0 +1,3 @@ +// Placeholder — replaced by the full PlatformHooks type in Task 2. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type PlatformHooks = Record; From 6dcc4013cc494e0666641e358ce81c02adb322f5 Mon Sep 17 00:00:00 2001 From: Gianluca Zuccarelli Date: Fri, 5 Jun 2026 13:25:58 +0100 Subject: [PATCH 2/4] context: add PlatformHooks type and platform implementations Define the PlatformHooks type contract with queries, mutations, env, and api sections, then wire up hostedPlatform and onPremPlatform as static objects satisfying the contract. Uses per-field type casts where on-prem base query types diverge from hosted. --- src/context/platform/hosted.ts | 77 ++++++++++++++++++++++++++++++++++ src/context/platform/onprem.ts | 74 ++++++++++++++++++++++++++++++++ src/context/platform/types.ts | 72 +++++++++++++++++++++++++++++-- 3 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 src/context/platform/hosted.ts create mode 100644 src/context/platform/onprem.ts diff --git a/src/context/platform/hosted.ts b/src/context/platform/hosted.ts new file mode 100644 index 0000000000..3bdfaa46d9 --- /dev/null +++ b/src/context/platform/hosted.ts @@ -0,0 +1,77 @@ +import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; +import { useFlag as useUnleashFlag } from '@unleash/proxy-client-react'; + +import { imageBuilderApi } from '@/store/api/backend/hosted/enhancedImageBuilderApi'; +import { + useComposeBlueprintMutation, + useCreateBlueprintMutation, + useDeleteBlueprintMutation, + useGetArchitecturesQuery, + useGetBlueprintComposesQuery, + useGetBlueprintQuery, + useGetBlueprintsQuery, + useGetComposesQuery, + useGetComposeStatusQuery, + useGetDistributionsQuery, + useGetOscapCustomizationsQuery, + useGetOscapProfilesQuery, + useLazyGetBlueprintsQuery, + useLazyGetOscapCustomizationsQuery, + useUpdateBlueprintMutation, +} from '@/store/api/backend/hosted/imageBuilderApi'; +import { + contentSourcesApi, + useListSnapshotsByDateMutation, + useSearchRpmMutation, +} from '@/store/api/contentSources/hosted'; + +import type { PlatformHooks } from './types'; + +// Hoisted stable function references to avoid per-call allocations +const isBetaTrue = () => true; +const isBetaFalse = () => false; + +const useHostedGetEnvironment = () => { + const { isBeta, isProd, getEnvironment } = useChrome(); + if (isBeta() || getEnvironment() === 'qa') { + return { isBeta: isBetaTrue, isProd }; + } + return { isBeta: isBetaFalse, isProd }; +}; + +export const hostedPlatform: PlatformHooks = { + queries: { + // PlatformHooks uses the on-prem type which accepts wider input. + // The hosted version is narrower, but runtime behavior is identical. + useGetArchitecturesQuery: + useGetArchitecturesQuery as unknown as PlatformHooks['queries']['useGetArchitecturesQuery'], + useGetDistributionsQuery: + useGetDistributionsQuery as unknown as PlatformHooks['queries']['useGetDistributionsQuery'], + useGetBlueprintQuery, + useGetBlueprintsQuery, + useLazyGetBlueprintsQuery, + useGetOscapProfilesQuery, + useGetOscapCustomizationsQuery, + useLazyGetOscapCustomizationsQuery, + useGetComposesQuery, + useGetBlueprintComposesQuery, + useGetComposeStatusQuery, + }, + mutations: { + useCreateBlueprintMutation, + useUpdateBlueprintMutation, + useDeleteBlueprintMutation, + useComposeBlueprintMutation, + useSearchRpmMutation, + useListSnapshotsByDateMutation, + }, + env: { + useFlag: useUnleashFlag, + useGetEnvironment: useHostedGetEnvironment, + }, + api: { + backendApi: imageBuilderApi, + contentSourcesApi, + useBackendPrefetch: imageBuilderApi.usePrefetch, + }, +}; diff --git a/src/context/platform/onprem.ts b/src/context/platform/onprem.ts new file mode 100644 index 0000000000..7732891e42 --- /dev/null +++ b/src/context/platform/onprem.ts @@ -0,0 +1,74 @@ +import { + useComposeBlueprintMutation, + useCreateBlueprintMutation, + useDeleteBlueprintMutation, + useGetArchitecturesQuery, + useGetBlueprintComposesQuery, + useGetBlueprintQuery, + useGetBlueprintsQuery, + useGetComposesQuery, + useGetComposeStatusQuery, + useGetDistributionsQuery, + useGetOscapCustomizationsQuery, + useGetOscapProfilesQuery, + useLazyGetBlueprintsQuery, + useLazyGetOscapCustomizationsQuery, + useUpdateBlueprintMutation, +} from '@/store/api/backend/onprem/composerApi'; +import { composerApi } from '@/store/api/backend/onprem/enhancedComposerApi'; +import { + contentSourcesApi, + useListSnapshotsByDateMutation, + useSearchRpmMutation, +} from '@/store/api/contentSources/onprem'; + +import type { PlatformHooks } from './types'; + +// Feature flag defaults for on-premises — Unleash is not available. +// All flags default to false (default-deny). Add cases here when a +// flag-gated feature needs to be enabled for on-prem users. +// NOTE: useGetEnvironment.ts has its own onPremFlag with a switch statement. +// That copy will be removed when Task 7 migrates useFlag to usePlatform().env. +export const onPremFlag = (_flag: string): boolean => { + return false; +}; + +// On-prem environment is static — hoist to avoid allocations per call +const onPremEnv = { isBeta: () => false, isProd: () => true }; + +// On-prem hooks use a different base query and reducer path than hosted, +// making them structurally incompatible with the hosted-anchored PlatformHooks +// type despite identical arg/response shapes. UseQuery is invariant in D +// so a single object-level cast is needed. +export const onPremPlatform = { + queries: { + useGetArchitecturesQuery, + useGetDistributionsQuery, + useGetBlueprintQuery, + useGetBlueprintsQuery, + useLazyGetBlueprintsQuery, + useGetOscapProfilesQuery, + useGetOscapCustomizationsQuery, + useLazyGetOscapCustomizationsQuery, + useGetComposesQuery, + useGetBlueprintComposesQuery, + useGetComposeStatusQuery, + }, + mutations: { + useCreateBlueprintMutation, + useUpdateBlueprintMutation, + useDeleteBlueprintMutation, + useComposeBlueprintMutation, + useSearchRpmMutation, + useListSnapshotsByDateMutation, + }, + env: { + useFlag: onPremFlag, + useGetEnvironment: () => onPremEnv, + }, + api: { + backendApi: composerApi, + contentSourcesApi, + useBackendPrefetch: composerApi.usePrefetch, + }, +} as unknown as PlatformHooks; diff --git a/src/context/platform/types.ts b/src/context/platform/types.ts index fca8ca63f2..fd490d01d1 100644 --- a/src/context/platform/types.ts +++ b/src/context/platform/types.ts @@ -1,3 +1,69 @@ -// Placeholder — replaced by the full PlatformHooks type in Task 2. -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type PlatformHooks = Record; +import type { imageBuilderApi } from '@/store/api/backend/hosted/enhancedImageBuilderApi'; +import type { + useComposeBlueprintMutation as useHostedComposeBlueprintMutation, + useCreateBlueprintMutation as useHostedCreateBlueprintMutation, + useDeleteBlueprintMutation as useHostedDeleteBlueprintMutation, + useGetBlueprintComposesQuery as useHostedGetBlueprintComposesQuery, + useGetBlueprintQuery as useHostedGetBlueprintQuery, + useGetBlueprintsQuery as useHostedGetBlueprintsQuery, + useGetComposesQuery as useHostedGetComposesQuery, + useGetComposeStatusQuery as useHostedGetComposeStatusQuery, + useGetOscapCustomizationsQuery as useHostedGetOscapCustomizationsQuery, + useGetOscapProfilesQuery as useHostedGetOscapProfilesQuery, + useLazyGetBlueprintsQuery as useHostedLazyGetBlueprintsQuery, + useLazyGetOscapCustomizationsQuery as useHostedLazyGetOscapCustomizationsQuery, + useUpdateBlueprintMutation as useHostedUpdateBlueprintMutation, +} from '@/store/api/backend/hosted/imageBuilderApi'; +import type { + useGetArchitecturesQuery as useComposerGetArchitecturesQuery, + useGetDistributionsQuery as useComposerGetDistributionsQuery, +} from '@/store/api/backend/onprem/composerApi'; +import type { composerApi } from '@/store/api/backend/onprem/enhancedComposerApi'; +import type { + contentSourcesApi, + useListSnapshotsByDateMutation as useHostedListSnapshotsByDateMutation, + useSearchRpmMutation as useHostedSearchRpmMutation, +} from '@/store/api/contentSources/hosted/contentSourcesApi'; + +// The useGetArchitecturesQuery and useGetDistributionsQuery types are +// widened to the composer (on-prem) versions because they accept wider +// input types. The response shapes are identical across platforms. +type UseGetArchitecturesQuery = typeof useComposerGetArchitecturesQuery; +type UseGetDistributionsQuery = typeof useComposerGetDistributionsQuery; + +type PlatformHooks = { + queries: { + useGetArchitecturesQuery: UseGetArchitecturesQuery; + useGetDistributionsQuery: UseGetDistributionsQuery; + useGetBlueprintQuery: typeof useHostedGetBlueprintQuery; + useGetBlueprintsQuery: typeof useHostedGetBlueprintsQuery; + useLazyGetBlueprintsQuery: typeof useHostedLazyGetBlueprintsQuery; + useGetOscapProfilesQuery: typeof useHostedGetOscapProfilesQuery; + useGetOscapCustomizationsQuery: typeof useHostedGetOscapCustomizationsQuery; + useLazyGetOscapCustomizationsQuery: typeof useHostedLazyGetOscapCustomizationsQuery; + useGetComposesQuery: typeof useHostedGetComposesQuery; + useGetBlueprintComposesQuery: typeof useHostedGetBlueprintComposesQuery; + useGetComposeStatusQuery: typeof useHostedGetComposeStatusQuery; + }; + mutations: { + useCreateBlueprintMutation: typeof useHostedCreateBlueprintMutation; + useUpdateBlueprintMutation: typeof useHostedUpdateBlueprintMutation; + useDeleteBlueprintMutation: typeof useHostedDeleteBlueprintMutation; + useComposeBlueprintMutation: typeof useHostedComposeBlueprintMutation; + useSearchRpmMutation: typeof useHostedSearchRpmMutation; + useListSnapshotsByDateMutation: typeof useHostedListSnapshotsByDateMutation; + }; + env: { + useFlag: (flag: string) => boolean; + useGetEnvironment: () => { isBeta: () => boolean; isProd: () => boolean }; + }; + api: { + backendApi: typeof imageBuilderApi | typeof composerApi; + contentSourcesApi: typeof contentSourcesApi; + useBackendPrefetch: + | typeof imageBuilderApi.usePrefetch + | typeof composerApi.usePrefetch; + }; +}; + +export type { PlatformHooks }; From 7efd354c5d79ac45651e5f3f88c074ffea58e892 Mon Sep 17 00:00:00 2001 From: Gianluca Zuccarelli Date: Fri, 5 Jun 2026 14:44:04 +0100 Subject: [PATCH 3/4] multi: wrap entry points with PlatformProvider AppEntry and AppCockpit now inject their respective platform objects into the React tree so all descendants can access the correct hooks via usePlatform() instead of build-time switching. --- src/AppCockpit.tsx | 14 +++++++++----- src/AppEntry.tsx | 6 +++++- src/test/renderUtils.jsx | 19 +++++++++++++------ 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/AppCockpit.tsx b/src/AppCockpit.tsx index 4c825be28c..be31af6c53 100644 --- a/src/AppCockpit.tsx +++ b/src/AppCockpit.tsx @@ -12,6 +12,8 @@ import { HashRouter } from 'react-router-dom'; import './AppCockpit.scss'; import { NotReady, RequireAdmin } from './Components/Cockpit'; +import { PlatformProvider } from './context/platform'; +import { onPremPlatform } from './context/platform/onprem'; import { Router } from './Router'; import { onPremStore as store } from './store'; import { useGetComposerSocketStatus } from './Utilities/useComposerStatus'; @@ -41,11 +43,13 @@ const Application = () => { }; const ImageBuilder = () => ( - - - - - + + + + + + + ); diff --git a/src/AppEntry.tsx b/src/AppEntry.tsx index 514c33828d..5303d3f36c 100644 --- a/src/AppEntry.tsx +++ b/src/AppEntry.tsx @@ -3,11 +3,15 @@ import React from 'react'; import { Provider } from 'react-redux'; import App from './App'; +import { PlatformProvider } from './context/platform'; +import { hostedPlatform } from './context/platform/hosted'; import { serviceStore as store } from './store'; const ImageBuilder = () => ( - + + + ); diff --git a/src/test/renderUtils.jsx b/src/test/renderUtils.jsx index 964b5f206e..b3e9d50e2e 100644 --- a/src/test/renderUtils.jsx +++ b/src/test/renderUtils.jsx @@ -6,6 +6,9 @@ import { Provider } from 'react-redux'; import { createMemoryRouter, RouterProvider } from 'react-router-dom'; import LandingPage from '../Components/LandingPage/LandingPage'; +import { PlatformProvider } from '../context/platform'; +import { hostedPlatform } from '../context/platform/hosted'; +import { onPremPlatform } from '../context/platform/onprem'; import { serviceMiddleware as middleware, onPremMiddleware as onPremMiddleware, @@ -52,14 +55,18 @@ export const renderCustomRoutesWithReduxRouter = async ( initialEntries: [resolveRelPath(route)], }); + const platform = process.env.IS_ON_PREMISE ? onPremPlatform : hostedPlatform; + render( - + + + , ); From 5dfc2a22279a0d0740931412fc355befcbe15b87 Mon Sep 17 00:00:00 2001 From: Gianluca Zuccarelli Date: Fri, 5 Jun 2026 15:32:48 +0100 Subject: [PATCH 4/4] multi: migrate all consumer imports to usePlatform() Move 35 component and hook files from importing platform-switched hooks directly from barrel files to destructuring them from usePlatform(), preparing for barrel file cleanup. --- .../Blueprints/BlueprintDiffModal.tsx | 5 ++++- .../Blueprints/BlueprintsPagination.tsx | 9 ++++---- .../Blueprints/BlueprintsSideBar.tsx | 16 ++++++++------ .../Blueprints/BuildImagesButton.tsx | 6 ++++- .../Blueprints/DeleteBlueprintModal.tsx | 11 +++++----- .../components/ImageSourceSelect.tsx | 9 ++++---- .../components/TargetEnvironment.tsx | 11 +++++----- .../tests/ImageSourceSelect.test.tsx | 15 +++++++++---- .../Kernel/components/KernelArguments.tsx | 5 ++++- .../CreateImageWizard/steps/Locale/index.tsx | 5 ++++- .../Oscap/components/ProfileSelector.tsx | 11 +++++++--- .../CreateImageWizard/steps/Oscap/index.tsx | 10 ++++----- .../Packages/components/PackageSearch.tsx | 10 +++++++-- .../Repositories/components/Repositories.tsx | 5 ++++- .../steps/Review/Footer/CreateDropdown.tsx | 4 ++-- .../steps/Review/Footer/EditDropdown.tsx | 4 ++-- .../Services/components/ServicesInputs.tsx | 5 ++++- .../CreateImageWizard/tests/helpers.tsx | 16 +++++++++----- .../utilities/useValidation.tsx | 9 ++++---- src/Components/ImagesTable/ImageDetails.tsx | 14 +++++++++++- src/Components/ImagesTable/ImagesTable.tsx | 18 +++++++++++---- .../ImagesTable/ImagesTableToolbar.tsx | 11 +++++++--- src/Components/ImagesTable/Instance.tsx | 8 ++++++- src/Components/ImagesTable/Status.tsx | 14 +++++++++++- src/Components/LandingPage/tests/helpers.tsx | 19 +++++++++++----- src/Components/Launch/AWSLaunchModal.tsx | 9 ++++---- src/Components/Launch/AzureLaunchModal.tsx | 9 ++++---- src/Components/Launch/GcpLaunchModal.tsx | 9 ++++---- src/Components/Launch/OciLaunchModal.tsx | 9 ++++---- .../sharedComponents/ImageBuilderHeader.tsx | 5 ++++- .../useComposeBPWithNotification.tsx | 5 ++++- .../useCreateBPWithNotification.tsx | 6 +++-- .../useDeleteBPWithNotification.tsx | 5 ++++- .../useUpdateBPWithNotification.tsx | 6 +++-- .../platform/tests/PlatformContext.test.tsx | 22 ++++++------------- src/context/platform/tests/mocks/fixtures.ts | 11 ++++++++++ src/context/platform/tests/mocks/index.ts | 1 + src/context/platform/types.ts | 7 ++---- src/test/testUtils/renderUtils.tsx | 11 +++++++++- 39 files changed, 245 insertions(+), 120 deletions(-) create mode 100644 src/context/platform/tests/mocks/fixtures.ts create mode 100644 src/context/platform/tests/mocks/index.ts diff --git a/src/Components/Blueprints/BlueprintDiffModal.tsx b/src/Components/Blueprints/BlueprintDiffModal.tsx index b37b4c7517..96d499c3d9 100644 --- a/src/Components/Blueprints/BlueprintDiffModal.tsx +++ b/src/Components/Blueprints/BlueprintDiffModal.tsx @@ -10,7 +10,7 @@ import { ModalVariant, } from '@patternfly/react-core'; -import { useGetBlueprintQuery } from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; import { selectSelectedBlueprintId } from '@/store/slices/blueprint'; import { BuildImagesButton } from './BuildImagesButton'; @@ -31,6 +31,9 @@ const BlueprintDiffModal = ({ isOpen, onClose, }: blueprintDiffProps) => { + const { + queries: { useGetBlueprintQuery }, + } = usePlatform(); const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId); const { data: baseBlueprint } = useGetBlueprintQuery( diff --git a/src/Components/Blueprints/BlueprintsPagination.tsx b/src/Components/Blueprints/BlueprintsPagination.tsx index c8061b8d8c..1b000f20f0 100644 --- a/src/Components/Blueprints/BlueprintsPagination.tsx +++ b/src/Components/Blueprints/BlueprintsPagination.tsx @@ -3,10 +3,8 @@ import React from 'react'; import { Pagination, PaginationVariant } from '@patternfly/react-core'; import { OnSetPage } from '@patternfly/react-core/dist/esm/components/Pagination/Pagination'; -import { - GetBlueprintsApiArg, - useGetBlueprintsQuery, -} from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; +import { GetBlueprintsApiArg } from '@/store/api/backend'; import { selectBlueprintSearchInput, selectLimit, @@ -18,6 +16,9 @@ import { import { useAppDispatch, useAppSelector } from '../../store/hooks'; const BlueprintsPagination = () => { + const { + queries: { useGetBlueprintsQuery }, + } = usePlatform(); const blueprintSearchInput = useAppSelector(selectBlueprintSearchInput); const blueprintsOffset = useAppSelector(selectOffset) || 0; const blueprintsLimit = useAppSelector(selectLimit) || 10; diff --git a/src/Components/Blueprints/BlueprintsSideBar.tsx b/src/Components/Blueprints/BlueprintsSideBar.tsx index 1600978d8e..95a07f7064 100644 --- a/src/Components/Blueprints/BlueprintsSideBar.tsx +++ b/src/Components/Blueprints/BlueprintsSideBar.tsx @@ -18,12 +18,8 @@ import { PlusCircleIcon, SearchIcon } from '@patternfly/react-icons'; import { SVGIconProps } from '@patternfly/react-icons/dist/esm/createIcon'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; -import { - BlueprintItem, - GetBlueprintsApiArg, - imageBuilderApi, - useGetBlueprintsQuery, -} from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; +import { BlueprintItem, GetBlueprintsApiArg } from '@/store/api/backend'; import { selectBlueprintSearchInput, selectLimit, @@ -57,6 +53,9 @@ type emptyBlueprintStateProps = { }; const BlueprintsSidebar = () => { + const { + queries: { useGetBlueprintsQuery }, + } = usePlatform(); const { analytics, auth } = useChrome(); const { userData } = useGetUser(auth); const isOnPremise = useAppSelector(selectIsOnPremise); @@ -181,6 +180,9 @@ const BlueprintsSidebar = () => { }; const BlueprintSearch = ({ blueprintsTotal }: blueprintSearchProps) => { + const { + api: { backendApi }, + } = usePlatform(); const blueprintSearchInput = useAppSelector(selectBlueprintSearchInput); const dispatch = useAppDispatch(); const [localSearchValue, setLocalSearchValue] = useState( @@ -190,7 +192,7 @@ const BlueprintSearch = ({ blueprintsTotal }: blueprintSearchProps) => { useEffect(() => { dispatch(setBlueprintsOffset(0)); - dispatch(imageBuilderApi.util.invalidateTags([{ type: 'Blueprints' }])); + dispatch(backendApi.util.invalidateTags([{ type: 'Blueprints' }])); dispatch( setBlueprintSearchInput( debouncedSearchValue.length > 0 ? debouncedSearchValue : undefined, diff --git a/src/Components/Blueprints/BuildImagesButton.tsx b/src/Components/Blueprints/BuildImagesButton.tsx index ed77ce48c7..e3833abb57 100644 --- a/src/Components/Blueprints/BuildImagesButton.tsx +++ b/src/Components/Blueprints/BuildImagesButton.tsx @@ -18,7 +18,8 @@ import { MenuToggleElement } from '@patternfly/react-core/dist/esm/components/Me import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; import { skipToken } from '@reduxjs/toolkit/query'; -import { ImageTypes, useGetBlueprintQuery } from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; +import { ImageTypes } from '@/store/api/backend'; import { selectSelectedBlueprintId } from '@/store/slices/blueprint'; import { selectIsOnPremise } from '@/store/slices/env'; @@ -35,6 +36,9 @@ type BuildImagesButtonPropTypes = { }; export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => { + const { + queries: { useGetBlueprintQuery }, + } = usePlatform(); const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId); const [deselectedTargets, setDeselectedTargets] = useState([]); const { trigger: buildBlueprint, isLoading: imageBuildLoading } = diff --git a/src/Components/Blueprints/DeleteBlueprintModal.tsx b/src/Components/Blueprints/DeleteBlueprintModal.tsx index 60ff59ee33..179cc9f221 100644 --- a/src/Components/Blueprints/DeleteBlueprintModal.tsx +++ b/src/Components/Blueprints/DeleteBlueprintModal.tsx @@ -10,11 +10,8 @@ import { } from '@patternfly/react-core'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; -import { - backendApi, - GetBlueprintsApiArg, - useGetBlueprintsQuery, -} from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; +import { GetBlueprintsApiArg } from '@/store/api/backend'; import { selectBlueprintSearchInput, selectLimit, @@ -43,6 +40,10 @@ interface DeleteBlueprintModalProps { export const DeleteBlueprintModal: React.FunctionComponent< DeleteBlueprintModalProps > = ({ setShowDeleteModal, isOpen }: DeleteBlueprintModalProps) => { + const { + queries: { useGetBlueprintsQuery }, + api: { backendApi }, + } = usePlatform(); const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId); const blueprintSearchInput = useAppSelector(selectBlueprintSearchInput); const blueprintsOffset = useAppSelector(selectOffset) || PAGINATION_OFFSET; diff --git a/src/Components/CreateImageWizard/steps/ImageOutput/components/ImageSourceSelect.tsx b/src/Components/CreateImageWizard/steps/ImageOutput/components/ImageSourceSelect.tsx index a4a7f95a62..30194c0864 100644 --- a/src/Components/CreateImageWizard/steps/ImageOutput/components/ImageSourceSelect.tsx +++ b/src/Components/CreateImageWizard/steps/ImageOutput/components/ImageSourceSelect.tsx @@ -19,10 +19,8 @@ import { import { SyncAltIcon } from '@patternfly/react-icons'; import { RHEL_10_IMAGE_MODE_IMAGE } from '@/constants'; -import { - BootcDistributionItem, - useGetDistributionsQuery, -} from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; +import { BootcDistributionItem } from '@/store/api/backend'; import { Distributions } from '@/store/api/backend/hosted'; import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { selectIsOnPremise } from '@/store/slices/env'; @@ -63,6 +61,9 @@ const InfoMessageContent = ({ source }: { source: string }) => { }; const ImageSourceSelect = () => { + const { + queries: { useGetDistributionsQuery }, + } = usePlatform(); const dispatch = useAppDispatch(); const isOnPremise = useAppSelector(selectIsOnPremise); const arch = useAppSelector(selectArchitecture); diff --git a/src/Components/CreateImageWizard/steps/ImageOutput/components/TargetEnvironment.tsx b/src/Components/CreateImageWizard/steps/ImageOutput/components/TargetEnvironment.tsx index ac57068963..e98a18f61b 100644 --- a/src/Components/CreateImageWizard/steps/ImageOutput/components/TargetEnvironment.tsx +++ b/src/Components/CreateImageWizard/steps/ImageOutput/components/TargetEnvironment.tsx @@ -11,14 +11,10 @@ import { Tooltip, } from '@patternfly/react-core'; +import { usePlatform } from '@/context/platform'; import { useTargetEnvironmentCategories } from '@/Hooks'; import { rhsmApi } from '@/store/api'; -import { - BootcDistributionItem, - ImageTypes, - useGetArchitecturesQuery, - useGetDistributionsQuery, -} from '@/store/api/backend'; +import { BootcDistributionItem, ImageTypes } from '@/store/api/backend'; import { useCustomizationRestrictions } from '@/store/api/distributions'; import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { @@ -67,6 +63,9 @@ const createLabelWithTooltip = ( }; const TargetEnvironment = () => { + const { + queries: { useGetArchitecturesQuery, useGetDistributionsQuery }, + } = usePlatform(); const arch = useAppSelector(selectArchitecture); const environments = useAppSelector(selectImageTypes); const distribution = useAppSelector(selectDistribution); diff --git a/src/Components/CreateImageWizard/steps/ImageOutput/tests/ImageSourceSelect.test.tsx b/src/Components/CreateImageWizard/steps/ImageOutput/tests/ImageSourceSelect.test.tsx index ffd9109a78..ec19770d5e 100644 --- a/src/Components/CreateImageWizard/steps/ImageOutput/tests/ImageSourceSelect.test.tsx +++ b/src/Components/CreateImageWizard/steps/ImageOutput/tests/ImageSourceSelect.test.tsx @@ -27,12 +27,19 @@ import ImageSourceSelect from '../components/ImageSourceSelect'; const mockRefetch = vi.fn(); const mockUseGetDistributionsQuery = vi.fn(); -vi.mock('@/store/api/backend', async (importOriginal) => { - const actual = await importOriginal(); +vi.mock('@/context/platform', async (importOriginal) => { + const actual = await importOriginal(); + const { mockPlatform } = await import('@/context/platform/tests/mocks'); return { ...actual, - useGetDistributionsQuery: (...args: unknown[]) => - mockUseGetDistributionsQuery(...args), + usePlatform: () => ({ + ...mockPlatform, + queries: { + ...mockPlatform.queries, + useGetDistributionsQuery: (...args: unknown[]) => + mockUseGetDistributionsQuery(...args), + }, + }), }; }); diff --git a/src/Components/CreateImageWizard/steps/Kernel/components/KernelArguments.tsx b/src/Components/CreateImageWizard/steps/Kernel/components/KernelArguments.tsx index 9bedf583dc..e1a1377d08 100644 --- a/src/Components/CreateImageWizard/steps/Kernel/components/KernelArguments.tsx +++ b/src/Components/CreateImageWizard/steps/Kernel/components/KernelArguments.tsx @@ -5,7 +5,7 @@ import { FormGroup, HelperText, HelperTextItem } from '@patternfly/react-core'; import LabelInput from '@/Components/CreateImageWizard/LabelInput'; import { useKernelValidation } from '@/Components/CreateImageWizard/utilities/useValidation'; import { isKernelArgumentValid } from '@/Components/CreateImageWizard/validators'; -import { useGetOscapCustomizationsQuery } from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; import { useAppSelector } from '@/store/hooks'; import { addKernelArg, @@ -16,6 +16,9 @@ import { } from '@/store/slices/wizard'; const KernelArguments = () => { + const { + queries: { useGetOscapCustomizationsQuery }, + } = usePlatform(); const kernelAppend = useAppSelector(selectKernel).append; const stepValidation = useKernelValidation(); diff --git a/src/Components/CreateImageWizard/steps/Locale/index.tsx b/src/Components/CreateImageWizard/steps/Locale/index.tsx index 2bd07bd6bb..425ac76ef2 100644 --- a/src/Components/CreateImageWizard/steps/Locale/index.tsx +++ b/src/Components/CreateImageWizard/steps/Locale/index.tsx @@ -3,7 +3,7 @@ import React, { useMemo } from 'react'; import { Content, Spinner, Title } from '@patternfly/react-core'; import { CustomizationLabels } from '@/Components/sharedComponents/CustomizationLabels'; -import { useGetArchitecturesQuery } from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; import { useSearchLanguagePacks } from '@/store/api/contentSources'; import { useAppSelector } from '@/store/hooks'; import { @@ -17,6 +17,9 @@ import KeyboardDropDown from './components/KeyboardDropDown'; import LanguagesDropDown from './components/LanguagesDropDown'; const LocaleStep = () => { + const { + queries: { useGetArchitecturesQuery }, + } = usePlatform(); const distribution = useAppSelector(selectDistribution); const arch = useAppSelector(selectArchitecture); const candidateLangpacks = useAppSelector(selectLocaleLangpackCandidates); diff --git a/src/Components/CreateImageWizard/steps/Oscap/components/ProfileSelector.tsx b/src/Components/CreateImageWizard/steps/Oscap/components/ProfileSelector.tsx index f7317a74d9..a664e7ecae 100644 --- a/src/Components/CreateImageWizard/steps/Oscap/components/ProfileSelector.tsx +++ b/src/Components/CreateImageWizard/steps/Oscap/components/ProfileSelector.tsx @@ -15,14 +15,12 @@ import { } from '@patternfly/react-core'; import { TimesIcon } from '@patternfly/react-icons'; +import { usePlatform } from '@/context/platform'; import { DistributionProfileItem, DistributionProfileResponse, OpenScap, OpenScapProfile, - useBackendPrefetch, - useGetOscapCustomizationsQuery, - useLazyGetOscapCustomizationsQuery, } from '@/store/api/backend'; import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { selectIsOnPremise } from '@/store/slices/env'; @@ -60,6 +58,13 @@ const ProfileSelector = ({ isSuccess, refetch, }: ProfileSelectorProps) => { + const { + queries: { + useGetOscapCustomizationsQuery, + useLazyGetOscapCustomizationsQuery, + }, + api: { useBackendPrefetch }, + } = usePlatform(); const isOnPremise = useAppSelector(selectIsOnPremise); const profileID = useAppSelector(selectComplianceProfileID); const release = removeBetaFromRelease(useAppSelector(selectDistribution)); diff --git a/src/Components/CreateImageWizard/steps/Oscap/index.tsx b/src/Components/CreateImageWizard/steps/Oscap/index.tsx index 35bcd524e9..4156c141fb 100644 --- a/src/Components/CreateImageWizard/steps/Oscap/index.tsx +++ b/src/Components/CreateImageWizard/steps/Oscap/index.tsx @@ -26,12 +26,8 @@ import { FIRST_BOOT_SERVICE, OSCAP_URL, } from '@/constants'; +import { usePlatform } from '@/context/platform'; import { useGetUser } from '@/Hooks'; -import { - useBackendPrefetch, - useGetOscapCustomizationsQuery, - useGetOscapProfilesQuery, -} from '@/store/api/backend'; import { useSecuritySummary } from '@/store/api/backend/hooks'; import { usePoliciesQuery } from '@/store/api/compliance'; import { useCustomizationRestrictions } from '@/store/api/distributions'; @@ -71,6 +67,10 @@ import { removeBetaFromRelease } from './removeBetaFromRelease'; import ExternalLinkButton from '../../utilities/ExternalLinkButton'; const OscapContent = () => { + const { + queries: { useGetOscapProfilesQuery, useGetOscapCustomizationsQuery }, + api: { useBackendPrefetch }, + } = usePlatform(); const dispatch = useAppDispatch(); const registrationType = useAppSelector(selectRegistrationType); const complianceType = useAppSelector(selectComplianceType); diff --git a/src/Components/CreateImageWizard/steps/Packages/components/PackageSearch.tsx b/src/Components/CreateImageWizard/steps/Packages/components/PackageSearch.tsx index 5baf48103b..9a97c611ad 100644 --- a/src/Components/CreateImageWizard/steps/Packages/components/PackageSearch.tsx +++ b/src/Components/CreateImageWizard/steps/Packages/components/PackageSearch.tsx @@ -30,6 +30,7 @@ import { ContentOrigin, EPEL_10_REPO_DEFINITION, } from '@/constants'; +import { usePlatform } from '@/context/platform'; import { Module, useGetArchitecturesQuery, @@ -42,7 +43,6 @@ import { useListRepositoriesQuery, useSearchPackageGroupMutation, useSearchRepositoryModuleStreamsMutation, - useSearchRpmMutation, } from '@/store/api/contentSources'; import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { selectIsOnPremise } from '@/store/slices/env'; @@ -100,6 +100,9 @@ const PackageSearch = ({ activeStream, setActiveStream, }: PackageSearchProps) => { + const { + mutations: { useSearchRpmMutation }, + } = usePlatform(); const dispatch = useAppDispatch(); const { analytics, isBeta } = useChrome(); @@ -254,13 +257,16 @@ const PackageSearch = ({ } if (debouncedSearchTerm.length > 1 && isSuccessDistroRepositories) { if (isOnPremise) { + // On-prem uses a different request shape with packages/architecture/distribution + // fields that don't exist in the hosted ApiContentUnitSearchRequest type. + // The on-prem implementation accepts these fields at runtime via PlatformContext. searchDistroRpms({ apiContentUnitSearchRequest: { packages: [debouncedSearchTerm], architecture: arch, distribution, }, - }); + } as Parameters[0]); } else { searchDistroRpms({ apiContentUnitSearchRequest: { diff --git a/src/Components/CreateImageWizard/steps/Repositories/components/Repositories.tsx b/src/Components/CreateImageWizard/steps/Repositories/components/Repositories.tsx index 421b03c033..c3cf0061c0 100644 --- a/src/Components/CreateImageWizard/steps/Repositories/components/Repositories.tsx +++ b/src/Components/CreateImageWizard/steps/Repositories/components/Repositories.tsx @@ -16,12 +16,12 @@ import { ExternalLinkAltIcon } from '@patternfly/react-icons'; import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import { CONTENT_URL, ContentOrigin } from '@/constants'; +import { usePlatform } from '@/context/platform'; import { ApiRepositoryResponseRead, useGetTemplateQuery, useListRepositoriesQuery, useListRepositoryParametersQuery, - useListSnapshotsByDateMutation, } from '@/store/api/contentSources'; import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { @@ -70,6 +70,9 @@ import { } from '../repositoriesUtilities'; const Repositories = () => { + const { + mutations: { useListSnapshotsByDateMutation }, + } = usePlatform(); const dispatch = useAppDispatch(); const arch = useAppSelector(selectArchitecture); diff --git a/src/Components/CreateImageWizard/steps/Review/Footer/CreateDropdown.tsx b/src/Components/CreateImageWizard/steps/Review/Footer/CreateDropdown.tsx index af852c65f7..9d7282e8b3 100644 --- a/src/Components/CreateImageWizard/steps/Review/Footer/CreateDropdown.tsx +++ b/src/Components/CreateImageWizard/steps/Review/Footer/CreateDropdown.tsx @@ -82,7 +82,7 @@ export const CreateSaveAndBuildBtn = ({ } if (requestBody) { const blueprint = (await createBlueprint({ - createBlueprintRequest: requestBody, + createBlueprintRequest: requestBody as CreateBlueprintRequest, })) as CreateBlueprintResponse; buildBlueprint({ id: blueprint.id, body: {} }); @@ -181,7 +181,7 @@ export const CreateSaveButton = ({ } if (requestBody) { const blueprint = (await createBlueprint({ - createBlueprintRequest: requestBody, + createBlueprintRequest: requestBody as CreateBlueprintRequest, })) 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 158464bde6..3cf86cd7bd 100644 --- a/src/Components/CreateImageWizard/steps/Review/Footer/EditDropdown.tsx +++ b/src/Components/CreateImageWizard/steps/Review/Footer/EditDropdown.tsx @@ -78,7 +78,7 @@ export const EditSaveAndBuildBtn = ({ if (requestBody) { await updateBlueprint({ id: blueprintId, - createBlueprintRequest: requestBody, + createBlueprintRequest: requestBody as CreateBlueprintRequest, }); } buildBlueprint({ id: blueprintId, body: {} }); @@ -127,7 +127,7 @@ export const EditSaveButton = ({ if (requestBody) { updateBlueprint({ id: blueprintId, - createBlueprintRequest: requestBody, + createBlueprintRequest: requestBody as CreateBlueprintRequest, }); } }; diff --git a/src/Components/CreateImageWizard/steps/Services/components/ServicesInputs.tsx b/src/Components/CreateImageWizard/steps/Services/components/ServicesInputs.tsx index 7d7b515d9a..fa446ec6bb 100644 --- a/src/Components/CreateImageWizard/steps/Services/components/ServicesInputs.tsx +++ b/src/Components/CreateImageWizard/steps/Services/components/ServicesInputs.tsx @@ -5,7 +5,7 @@ import { FormGroup, HelperText, HelperTextItem } from '@patternfly/react-core'; import LabelInput from '@/Components/CreateImageWizard/LabelInput'; import { useServicesValidation } from '@/Components/CreateImageWizard/utilities/useValidation'; import { isServiceValid } from '@/Components/CreateImageWizard/validators'; -import { useGetOscapCustomizationsQuery } from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; import { useAppSelector } from '@/store/hooks'; import { addDisabledService, @@ -20,6 +20,9 @@ import { } from '@/store/slices/wizard'; const ServicesInput = () => { + const { + queries: { useGetOscapCustomizationsQuery }, + } = usePlatform(); const disabledServices = useAppSelector(selectServices).disabled; const maskedServices = useAppSelector(selectServices).masked; const enabledServices = useAppSelector(selectServices).enabled; diff --git a/src/Components/CreateImageWizard/tests/helpers.tsx b/src/Components/CreateImageWizard/tests/helpers.tsx index 25a452f32d..b7378504f9 100644 --- a/src/Components/CreateImageWizard/tests/helpers.tsx +++ b/src/Components/CreateImageWizard/tests/helpers.tsx @@ -5,6 +5,8 @@ import { render, screen } from '@testing-library/react'; import { Provider } from 'react-redux'; import { createMemoryRouter, RouterProvider } from 'react-router-dom'; +import { PlatformProvider } from '@/context/platform'; +import { hostedPlatform } from '@/context/platform/hosted'; import { RootState, serviceMiddleware, serviceReducer } from '@/store'; import CreateImageWizard from '../CreateImageWizard'; @@ -36,12 +38,14 @@ export const renderWithQueryParams = async ( render( - + + + , ); diff --git a/src/Components/CreateImageWizard/utilities/useValidation.tsx b/src/Components/CreateImageWizard/utilities/useValidation.tsx index c3e24692b7..a9f357d8ca 100644 --- a/src/Components/CreateImageWizard/utilities/useValidation.tsx +++ b/src/Components/CreateImageWizard/utilities/useValidation.tsx @@ -3,10 +3,8 @@ import React, { useEffect, useState } from 'react'; import { CheckCircleIcon } from '@patternfly/react-icons'; import { jwtDecode } from 'jwt-decode'; -import { - BlueprintsResponse, - useLazyGetBlueprintsQuery, -} from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; +import { BlueprintsResponse } from '@/store/api/backend'; import { useShowActivationKeyQuery } from '@/store/api/rhsm'; import { selectIsOnPremise } from '@/store/slices/env'; import { @@ -1098,6 +1096,9 @@ const countCharacterTypes = (value: string) => { }; export function useDetailsValidation(): StepValidation { + const { + queries: { useLazyGetBlueprintsQuery }, + } = usePlatform(); const name = useAppSelector(selectBlueprintName); const description = useAppSelector(selectBlueprintDescription); const blueprintId = useAppSelector(selectBlueprintId); diff --git a/src/Components/ImagesTable/ImageDetails.tsx b/src/Components/ImagesTable/ImageDetails.tsx index 9a720fb3c4..a838218198 100644 --- a/src/Components/ImagesTable/ImageDetails.tsx +++ b/src/Components/ImagesTable/ImageDetails.tsx @@ -12,10 +12,10 @@ import { import { ExternalLinkAltIcon } from '@patternfly/react-icons'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; +import { usePlatform } from '@/context/platform'; import { ComposesResponseItem, GcpUploadRequestOptions, - useGetComposeStatusQuery, } from '@/store/api/backend'; import { selectIsOnPremise } from '@/store/slices/env'; @@ -45,6 +45,9 @@ type AwsDetailsPropTypes = { }; export const AwsDetails = ({ compose }: AwsDetailsPropTypes) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const options = compose.request.image_requests[0].upload_request.options; const { analytics, auth } = useChrome(); @@ -176,6 +179,9 @@ type AzureDetailsPropTypes = { }; export const AzureDetails = ({ compose }: AzureDetailsPropTypes) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const { data: composeStatus } = useGetComposeStatusQuery({ composeId: compose.id, }); @@ -256,6 +262,9 @@ type GcpDetailsPropTypes = { }; export const GcpDetails = ({ compose }: GcpDetailsPropTypes) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const { data: composeStatus } = useGetComposeStatusQuery({ composeId: compose.id, }); @@ -344,6 +353,9 @@ type OciDetailsPropTypes = { }; export const OciDetails = ({ compose }: OciDetailsPropTypes) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const { data: composeStatus } = useGetComposeStatusQuery({ composeId: compose.id, }); diff --git a/src/Components/ImagesTable/ImagesTable.tsx b/src/Components/ImagesTable/ImagesTable.tsx index 6c87300b24..b2788dd739 100644 --- a/src/Components/ImagesTable/ImagesTable.tsx +++ b/src/Components/ImagesTable/ImagesTable.tsx @@ -31,16 +31,13 @@ import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; import cockpit from 'cockpit'; import { useDispatch } from 'react-redux'; +import { usePlatform } from '@/context/platform'; import { BlueprintItem, ComposesResponseItem, GetBlueprintComposesApiArg, GetBlueprintsApiArg, LocalUploadStatus, - useGetBlueprintComposesQuery, - useGetBlueprintsQuery, - useGetComposesQuery, - useGetComposeStatusQuery, } from '@/store/api/backend'; import { selectBlueprintSearchInput, @@ -96,6 +93,13 @@ import { GcpLaunchModal } from '../Launch/GcpLaunchModal'; import { OciLaunchModal } from '../Launch/OciLaunchModal'; const ImagesTable = () => { + const { + queries: { + useGetBlueprintsQuery, + useGetBlueprintComposesQuery, + useGetComposesQuery, + }, + } = usePlatform(); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(10); @@ -322,6 +326,9 @@ type ImagesTableRowPropTypes = { }; const ImagesTableRow = ({ compose, rowIndex }: ImagesTableRowPropTypes) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const [pollingInterval, setPollingInterval] = useState( STATUS_POLLING_INTERVAL, ); @@ -580,6 +587,9 @@ const Row = ({ details, instance, }: RowPropTypes) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const { analytics, auth } = useChrome(); const { userData } = useGetUser(auth); const isOnPremise = useAppSelector(selectIsOnPremise); diff --git a/src/Components/ImagesTable/ImagesTableToolbar.tsx b/src/Components/ImagesTable/ImagesTableToolbar.tsx index 86e1e0e3a4..081cd2b613 100644 --- a/src/Components/ImagesTable/ImagesTableToolbar.tsx +++ b/src/Components/ImagesTable/ImagesTableToolbar.tsx @@ -13,13 +13,11 @@ import { ToolbarItem, } from '@patternfly/react-core'; +import { usePlatform } from '@/context/platform'; import { BlueprintItem, Distributions, GetBlueprintComposesApiArg, - useGetBlueprintComposesQuery, - useGetBlueprintQuery, - useGetBlueprintsQuery, } from '@/store/api/backend'; import { selectBlueprintSearchInput, @@ -58,6 +56,13 @@ const ImagesTableToolbar: React.FC = ({ setPage, onPerPageSelect, }: imagesTableToolbarProps) => { + const { + queries: { + useGetBlueprintQuery, + useGetBlueprintComposesQuery, + useGetBlueprintsQuery, + }, + } = usePlatform(); const isOnPremise = useAppSelector(selectIsOnPremise); const [showDeleteModal, setShowDeleteModal] = useState(false); const [showDiffModal, setShowDiffModal] = useState(false); diff --git a/src/Components/ImagesTable/Instance.tsx b/src/Components/ImagesTable/Instance.tsx index f1073cc89c..a012644512 100644 --- a/src/Components/ImagesTable/Instance.tsx +++ b/src/Components/ImagesTable/Instance.tsx @@ -6,11 +6,11 @@ import { Button, Skeleton } from '@patternfly/react-core'; import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; import cockpit from 'cockpit'; +import { usePlatform } from '@/context/platform'; import { ComposesResponseItem, ImageTypes, LocalUploadStatus, - useGetComposeStatusQuery, } from '@/store/api/backend'; import { selectIsOnPremise } from '@/store/slices/env'; @@ -32,6 +32,9 @@ export const AwsS3Instance = ({ compose, isExpired, }: AwsS3InstancePropTypes) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const { analytics } = useChrome(); const isOnPremise = useAppSelector(selectIsOnPremise); @@ -121,6 +124,9 @@ const VM_INSTALLABLE_IMAGE_TYPES: ImageTypes[] = [ ]; export const LocalInstance = ({ compose }: LocalInstancePropTypes) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const isMachinesAvailable = useCockpitMachinesAvailable(); const { data: composeStatus, isSuccess } = useGetComposeStatusQuery({ composeId: compose.id, diff --git a/src/Components/ImagesTable/Status.tsx b/src/Components/ImagesTable/Status.tsx index 3c2d75bf39..fdec39757d 100644 --- a/src/Components/ImagesTable/Status.tsx +++ b/src/Components/ImagesTable/Status.tsx @@ -24,12 +24,12 @@ import { PendingIcon, } from '@patternfly/react-icons'; +import { usePlatform } from '@/context/platform'; import { ComposerComposesResponseItem, ComposesResponseItem, ComposeStatus, ComposeStatusError, - useGetComposeStatusQuery, } from '@/store/api/backend'; import { @@ -42,6 +42,9 @@ type ComposeStatusPropTypes = { }; export const AwsDetailsStatus = ({ compose }: ComposeStatusPropTypes) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const { data, isSuccess } = useGetComposeStatusQuery({ composeId: compose.id, }); @@ -77,6 +80,9 @@ type CloudStatusPropTypes = { }; export const CloudStatus = ({ compose }: CloudStatusPropTypes) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const { data, isSuccess } = useGetComposeStatusQuery({ composeId: compose.id, }); @@ -145,6 +151,9 @@ export const ExpiringStatus = ({ isExpired, timeToExpiration, }: ExpiringStatusPropTypes) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const { data: composeStatus, isSuccess } = useGetComposeStatusQuery({ composeId: compose.id, }); @@ -237,6 +246,9 @@ type LocalStatusPropTypes = { }; export const LocalStatus = ({ compose }: LocalStatusPropTypes) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const { data: composeStatus, isSuccess } = useGetComposeStatusQuery({ composeId: compose.id, }); diff --git a/src/Components/LandingPage/tests/helpers.tsx b/src/Components/LandingPage/tests/helpers.tsx index 3302a5f269..6df6354742 100644 --- a/src/Components/LandingPage/tests/helpers.tsx +++ b/src/Components/LandingPage/tests/helpers.tsx @@ -4,6 +4,9 @@ import { render } from '@testing-library/react'; import { Provider } from 'react-redux'; import { createMemoryRouter, RouterProvider } from 'react-router-dom'; +import { PlatformProvider } from '@/context/platform'; +import { hostedPlatform } from '@/context/platform/hosted'; +import { onPremPlatform } from '@/context/platform/onprem'; import { createTestStore, type RenderWithReduxOptions } from '@/test/testUtils'; import LandingPage from '../LandingPage'; @@ -22,14 +25,18 @@ export const renderLandingPage = (options: RenderWithReduxOptions = {}) => { initialEntries: ['/'], }); + const isOnPremise = options.preloadedState?.env?.isOnPremise; + const platform = isOnPremise ? onPremPlatform : hostedPlatform; const view = render( - + + + , ); diff --git a/src/Components/Launch/AWSLaunchModal.tsx b/src/Components/Launch/AWSLaunchModal.tsx index 5038365b44..a70df493cb 100644 --- a/src/Components/Launch/AWSLaunchModal.tsx +++ b/src/Components/Launch/AWSLaunchModal.tsx @@ -15,10 +15,8 @@ import { } from '@patternfly/react-core'; import { ExternalLinkAltIcon } from '@patternfly/react-icons'; -import { - ComposesResponseItem, - useGetComposeStatusQuery, -} from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; +import { ComposesResponseItem } from '@/store/api/backend'; import { isAwsUploadRequestOptions } from '../../store/typeGuards'; @@ -27,6 +25,9 @@ type LaunchProps = { }; export const AWSLaunchModal = ({ compose }: LaunchProps) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const [isModalOpen, setIsModalOpen] = useState(false); const { data, isSuccess, isFetching } = useGetComposeStatusQuery({ composeId: compose.id, diff --git a/src/Components/Launch/AzureLaunchModal.tsx b/src/Components/Launch/AzureLaunchModal.tsx index 739c340871..bb6966f54b 100644 --- a/src/Components/Launch/AzureLaunchModal.tsx +++ b/src/Components/Launch/AzureLaunchModal.tsx @@ -15,10 +15,8 @@ import { } from '@patternfly/react-core'; import { ExternalLinkAltIcon } from '@patternfly/react-icons'; -import { - ComposesResponseItem, - useGetComposeStatusQuery, -} from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; +import { ComposesResponseItem } from '@/store/api/backend'; import { isAzureUploadStatus } from '../../store/typeGuards'; @@ -27,6 +25,9 @@ type LaunchProps = { }; export const AzureLaunchModal = ({ compose }: LaunchProps) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const [isModalOpen, setIsModalOpen] = useState(false); const { data, isSuccess, isFetching } = useGetComposeStatusQuery({ composeId: compose.id, diff --git a/src/Components/Launch/GcpLaunchModal.tsx b/src/Components/Launch/GcpLaunchModal.tsx index 3fc0aa8f84..a95bf0903f 100644 --- a/src/Components/Launch/GcpLaunchModal.tsx +++ b/src/Components/Launch/GcpLaunchModal.tsx @@ -18,10 +18,8 @@ import { } from '@patternfly/react-core'; import { ExternalLinkAltIcon } from '@patternfly/react-icons'; -import { - ComposesResponseItem, - useGetComposeStatusQuery, -} from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; +import { ComposesResponseItem } from '@/store/api/backend'; import { generateDefaultName } from './useGenerateDefaultName'; @@ -34,6 +32,9 @@ import { parseGcpSharedWith } from '../ImagesTable/ImageDetails'; type LaunchProps = { compose: ComposesResponseItem }; export const GcpLaunchModal = ({ compose }: LaunchProps) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const [isModalOpen, setIsModalOpen] = useState(false); const [customerProjectId, setCustomerProjectId] = useState(''); const { data, isSuccess, isFetching } = useGetComposeStatusQuery({ diff --git a/src/Components/Launch/OciLaunchModal.tsx b/src/Components/Launch/OciLaunchModal.tsx index d6a72e270d..3933da4230 100644 --- a/src/Components/Launch/OciLaunchModal.tsx +++ b/src/Components/Launch/OciLaunchModal.tsx @@ -18,10 +18,8 @@ import { import { ExternalLinkAltIcon } from '@patternfly/react-icons'; import { useNavigate } from 'react-router-dom'; -import { - ComposesResponseItem, - useGetComposeStatusQuery, -} from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; +import { ComposesResponseItem } from '@/store/api/backend'; import { selectPathResolver } from '@/store/slices/env'; import { useAppSelector } from '../../store/hooks'; @@ -33,6 +31,9 @@ type LaunchProps = { }; export const OciLaunchModal = ({ isExpired, compose }: LaunchProps) => { + const { + queries: { useGetComposeStatusQuery }, + } = usePlatform(); const [isModalOpen, setIsModalOpen] = useState(false); const { data, isSuccess, isFetching } = useGetComposeStatusQuery({ composeId: compose.id, diff --git a/src/Components/sharedComponents/ImageBuilderHeader.tsx b/src/Components/sharedComponents/ImageBuilderHeader.tsx index e301a29981..bb1e4322b1 100644 --- a/src/Components/sharedComponents/ImageBuilderHeader.tsx +++ b/src/Components/sharedComponents/ImageBuilderHeader.tsx @@ -8,7 +8,7 @@ import { PageHeaderTitle, } from '@redhat-cloud-services/frontend-components'; -import { useBackendPrefetch } from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; import { selectIsOnPremise } from '@/store/slices/env'; import { selectDistribution } from '@/store/slices/wizard'; import { openWizardModal } from '@/store/slices/wizardModal'; @@ -72,6 +72,9 @@ export const ImageBuilderHeader = ({ const dispatch = useAppDispatch(); const isOnPremise = useAppSelector(selectIsOnPremise); + const { + api: { useBackendPrefetch }, + } = usePlatform(); const distribution = useAppSelector(selectDistribution); const prefetchTargets = useBackendPrefetch('getArchitectures'); diff --git a/src/Hooks/MutationNotifications/useComposeBPWithNotification.tsx b/src/Hooks/MutationNotifications/useComposeBPWithNotification.tsx index 00f49ee123..a1cfed5da3 100644 --- a/src/Hooks/MutationNotifications/useComposeBPWithNotification.tsx +++ b/src/Hooks/MutationNotifications/useComposeBPWithNotification.tsx @@ -1,8 +1,11 @@ -import { useComposeBlueprintMutation } from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; import { useMutationWithNotification } from './useMutationWithNotification'; export const useComposeBPWithNotification = () => { + const { + mutations: { useComposeBlueprintMutation }, + } = usePlatform(); const { trigger: composeBlueprint, ...rest } = useMutationWithNotification( useComposeBlueprintMutation, { diff --git a/src/Hooks/MutationNotifications/useCreateBPWithNotification.tsx b/src/Hooks/MutationNotifications/useCreateBPWithNotification.tsx index de8c71b36d..9a637314af 100644 --- a/src/Hooks/MutationNotifications/useCreateBPWithNotification.tsx +++ b/src/Hooks/MutationNotifications/useCreateBPWithNotification.tsx @@ -1,4 +1,4 @@ -import { useCreateBlueprintMutation } from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; import { HookOptions, @@ -6,8 +6,10 @@ import { } from './useMutationWithNotification'; export const useCreateBPWithNotification = (options?: HookOptions) => { + const { + mutations: { useCreateBlueprintMutation }, + } = usePlatform(); const { trigger: createBlueprint, ...rest } = useMutationWithNotification( - // @ts-expect-error TODO: this will need to be revisited useCreateBlueprintMutation, { options, diff --git a/src/Hooks/MutationNotifications/useDeleteBPWithNotification.tsx b/src/Hooks/MutationNotifications/useDeleteBPWithNotification.tsx index 43dd18cd7a..48f755337a 100644 --- a/src/Hooks/MutationNotifications/useDeleteBPWithNotification.tsx +++ b/src/Hooks/MutationNotifications/useDeleteBPWithNotification.tsx @@ -1,4 +1,4 @@ -import { useDeleteBlueprintMutation } from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; import { HookOptions, @@ -6,6 +6,9 @@ import { } from './useMutationWithNotification'; export const useDeleteBPWithNotification = (options?: HookOptions) => { + const { + mutations: { useDeleteBlueprintMutation }, + } = usePlatform(); const { trigger: deleteBlueprint, ...rest } = useMutationWithNotification( useDeleteBlueprintMutation, { diff --git a/src/Hooks/MutationNotifications/useUpdateBPWithNotification.tsx b/src/Hooks/MutationNotifications/useUpdateBPWithNotification.tsx index b273e9bf99..fc901307f3 100644 --- a/src/Hooks/MutationNotifications/useUpdateBPWithNotification.tsx +++ b/src/Hooks/MutationNotifications/useUpdateBPWithNotification.tsx @@ -1,4 +1,4 @@ -import { useUpdateBlueprintMutation } from '@/store/api/backend'; +import { usePlatform } from '@/context/platform'; import { HookOptions, @@ -6,8 +6,10 @@ import { } from './useMutationWithNotification'; export const useUpdateBPWithNotification = (options?: HookOptions) => { + const { + mutations: { useUpdateBlueprintMutation }, + } = usePlatform(); const { trigger: updateBlueprint, ...rest } = useMutationWithNotification( - // @ts-expect-error TODO: this will need to be revisited useUpdateBlueprintMutation, { options, diff --git a/src/context/platform/tests/PlatformContext.test.tsx b/src/context/platform/tests/PlatformContext.test.tsx index d83e7eb9ee..2a7ce2bf7b 100644 --- a/src/context/platform/tests/PlatformContext.test.tsx +++ b/src/context/platform/tests/PlatformContext.test.tsx @@ -1,34 +1,26 @@ import React from 'react'; import { renderHook } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { PlatformProvider, usePlatform } from '@/context/platform'; -import type { PlatformHooks } from '@/context/platform/types'; + +import { mockPlatform } from './mocks'; describe('PlatformContext', () => { it('throws when usePlatform is called outside PlatformProvider', () => { - const onError = (e: ErrorEvent) => e.preventDefault(); - window.addEventListener('error', onError); + // React logs to console.error before the error propagates; + // suppress it so the global setup spy doesn't treat it as a failure. + const spy = vi.spyOn(console, 'error').mockImplementation(() => {}); expect(() => { renderHook(() => usePlatform()); }).toThrow('usePlatform must be used within a PlatformProvider'); - window.removeEventListener('error', onError); + spy.mockRestore(); }); it('provides the platform object when wrapped in PlatformProvider', () => { - const mockPlatform = { - queries: {}, - mutations: {}, - env: { - useFlag: () => false, - useGetEnvironment: () => ({ isBeta: () => false, isProd: () => true }), - }, - api: {}, - } as unknown as PlatformHooks; - const wrapper = ({ children }: { children: React.ReactNode }) => ( {children} ); diff --git a/src/context/platform/tests/mocks/fixtures.ts b/src/context/platform/tests/mocks/fixtures.ts new file mode 100644 index 0000000000..667656a0d0 --- /dev/null +++ b/src/context/platform/tests/mocks/fixtures.ts @@ -0,0 +1,11 @@ +import type { PlatformHooks } from '@/context/platform/types'; + +export const mockPlatform = { + queries: {}, + mutations: {}, + env: { + useFlag: () => false, + useGetEnvironment: () => ({ isBeta: () => false, isProd: () => true }), + }, + api: {}, +} as unknown as PlatformHooks; diff --git a/src/context/platform/tests/mocks/index.ts b/src/context/platform/tests/mocks/index.ts new file mode 100644 index 0000000000..995b5bc6e1 --- /dev/null +++ b/src/context/platform/tests/mocks/index.ts @@ -0,0 +1 @@ +export * from './fixtures'; diff --git a/src/context/platform/types.ts b/src/context/platform/types.ts index fd490d01d1..52e7c5f7be 100644 --- a/src/context/platform/types.ts +++ b/src/context/platform/types.ts @@ -18,7 +18,6 @@ import type { useGetArchitecturesQuery as useComposerGetArchitecturesQuery, useGetDistributionsQuery as useComposerGetDistributionsQuery, } from '@/store/api/backend/onprem/composerApi'; -import type { composerApi } from '@/store/api/backend/onprem/enhancedComposerApi'; import type { contentSourcesApi, useListSnapshotsByDateMutation as useHostedListSnapshotsByDateMutation, @@ -58,11 +57,9 @@ type PlatformHooks = { useGetEnvironment: () => { isBeta: () => boolean; isProd: () => boolean }; }; api: { - backendApi: typeof imageBuilderApi | typeof composerApi; + backendApi: typeof imageBuilderApi; contentSourcesApi: typeof contentSourcesApi; - useBackendPrefetch: - | typeof imageBuilderApi.usePrefetch - | typeof composerApi.usePrefetch; + useBackendPrefetch: typeof imageBuilderApi.usePrefetch; }; }; diff --git a/src/test/testUtils/renderUtils.tsx b/src/test/testUtils/renderUtils.tsx index 4882b933f9..633686f496 100644 --- a/src/test/testUtils/renderUtils.tsx +++ b/src/test/testUtils/renderUtils.tsx @@ -4,6 +4,9 @@ import { configureStore, EnhancedStore } from '@reduxjs/toolkit'; import { render, RenderResult } from '@testing-library/react'; import { Provider } from 'react-redux'; +import { PlatformProvider } from '@/context/platform'; +import { hostedPlatform } from '@/context/platform/hosted'; +import { onPremPlatform } from '@/context/platform/onprem'; import { onPremMiddleware, onPremReducer, @@ -29,7 +32,13 @@ export const renderWithRedux = ( options: RenderWithReduxOptions = {}, ): RenderWithReduxResult => { const store = createTestStore(wizardStateOverrides, options); - const view = render({component}); + const isOnPremise = options.preloadedState?.env?.isOnPremise; + const platform = isOnPremise ? onPremPlatform : hostedPlatform; + const view = render( + + {component} + , + ); return { ...view, store }; };