From 6394f6c735100f0576b6bb646c7214c0b2915c86 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Tue, 13 Aug 2024 11:43:29 +0200 Subject: [PATCH] Allow additional support case configuration. --- .../ChromeContext/feedbackModal.cy.tsx | 47 +++++++++++-------- docs/api.md | 15 ++++++ src/@types/types.d.ts | 23 +++++---- src/chrome/create-chrome.ts | 2 +- src/components/Feedback/FeedbackModal.tsx | 4 +- src/components/Header/Tools.tsx | 4 +- src/hooks/useSupportCaseData.ts | 42 +++++++++++++++++ src/utils/createCase.ts | 14 ++++-- 8 files changed, 116 insertions(+), 35 deletions(-) create mode 100644 src/hooks/useSupportCaseData.ts diff --git a/cypress/component/ChromeContext/feedbackModal.cy.tsx b/cypress/component/ChromeContext/feedbackModal.cy.tsx index 1b7a1a2ab..a3d570c15 100644 --- a/cypress/component/ChromeContext/feedbackModal.cy.tsx +++ b/cypress/component/ChromeContext/feedbackModal.cy.tsx @@ -10,17 +10,20 @@ import { IntlProvider } from 'react-intl'; import InternalChromeContext from '../../../src/utils/internalChromeContext'; import { Provider as JotaiProvider } from 'jotai'; import chromeStore from '../../../src/state/chromeStore'; +import { BrowserRouter } from 'react-router-dom'; describe('Feedback Modal', () => { let chromeContext: Context; let contextValue: ChromeAPI; const NestedComponen = () => { return ( - - 'stage' } as any}> - - - + + + 'stage' } as any}> + + + + ); }; const InnerComponent = () => { @@ -32,13 +35,15 @@ describe('Feedback Modal', () => { const Wrapper = () => { const [removeComponent, setRemoveComponent] = useState(false); return ( - - 'stage' } as any}> - - {!removeComponent ? : null} - - - + + + 'stage' } as any}> + + {!removeComponent ? : null} + + + + ); }; @@ -54,14 +59,16 @@ describe('Feedback Modal', () => { const CustomComponent = () => { return ( - - - 'stage' } as any}> - - - - - + + + + 'stage' } as any}> + + + + + + ); }; diff --git a/docs/api.md b/docs/api.md index 04d759141..98ba920b9 100644 --- a/docs/api.md +++ b/docs/api.md @@ -186,6 +186,21 @@ chrome.createCase({ }) ``` +You can also configure the version and product of the support cases: + +```js +const chrome = useChrome() + +chrome.createCase({ + supportCaseData: { + product: 'Red Hat Insights', + version: 'Advisor', + }, + caseFields: {} + ... +}) +``` + ## Deprecated functions * `chrome.navigation` this is a legacy function and is no longer supported. Invoking it has no effect. diff --git a/src/@types/types.d.ts b/src/@types/types.d.ts index 388931e4f..86369e1af 100644 --- a/src/@types/types.d.ts +++ b/src/@types/types.d.ts @@ -138,15 +138,19 @@ export type RouteDefinition = { props?: any; }; -export type ModuleRoute = - | { - isFedramp?: boolean; - pathname: string; - exact?: boolean; - dynamic?: boolean; - props?: Record; - } - | string; +export type SupportCaseConfig = { + product: string; + version: string; +}; + +export type ModuleRoute = { + isFedramp?: boolean; + pathname: string; + exact?: boolean; + dynamic?: boolean; + props?: Record; + supportCaseData?: SupportCaseConfig; +}; export type RemoteModule = { module: string; @@ -157,6 +161,7 @@ export type ChromeModule = { manifestLocation: string; ssoUrl?: string; config?: { + supportCaseData?: SupportCaseConfig; ssoUrl?: string; fullProfile?: boolean; props?: Record; diff --git a/src/chrome/create-chrome.ts b/src/chrome/create-chrome.ts index 3b5d27385..7def356a0 100644 --- a/src/chrome/create-chrome.ts +++ b/src/chrome/create-chrome.ts @@ -139,7 +139,7 @@ export const createChromeContext = ({ return environment; }, getAvailableBundles: () => Object.entries(bundleMapping).map(([key, value]) => ({ id: key, title: value })), - createCase: (fields?: any) => chromeAuth.getUser().then((user) => createSupportCase(user!.identity, chromeAuth.token, fields)), + createCase: (options?: any) => chromeAuth.getUser().then((user) => createSupportCase(user!.identity, chromeAuth.token, options)), getUserPermissions: async (app = '', bypassCache?: boolean) => { const token = await chromeAuth.getToken(); return fetchPermissions(token, app, bypassCache); diff --git a/src/components/Feedback/FeedbackModal.tsx b/src/components/Feedback/FeedbackModal.tsx index 943c9276a..e72128706 100644 --- a/src/components/Feedback/FeedbackModal.tsx +++ b/src/components/Feedback/FeedbackModal.tsx @@ -27,6 +27,7 @@ import './Feedback.scss'; import ChromeAuthContext from '../../auth/ChromeAuthContext'; import { useSegment } from '../../analytics/useSegment'; import { isPreviewAtom } from '../../state/atoms/releaseAtom'; +import useSupportCaseData from '../../hooks/useSupportCaseData'; const FEEDBACK_OPEN_EVENT = 'chrome.feedback.open'; @@ -56,6 +57,7 @@ const FeedbackModal = memo(() => { setModalPage('feedbackHome'); }; const isPreview = useAtomValue(isPreviewAtom); + const supportCaseData = useSupportCaseData(); const ModalDescription = ({ modalPage }: { modalPage: FeedbackPages }) => { switch (modalPage) { @@ -79,7 +81,7 @@ const FeedbackModal = memo(() => { className="pf-v5-u-mb-lg" isSelectableRaised isCompact - onClick={() => createSupportCase(user.identity, chromeAuth.token, isPreview)} + onClick={() => createSupportCase(user.identity, chromeAuth.token, isPreview, { supportCaseData })} > diff --git a/src/components/Header/Tools.tsx b/src/components/Header/Tools.tsx index 03acac923..f5c4d4f9d 100644 --- a/src/components/Header/Tools.tsx +++ b/src/components/Header/Tools.tsx @@ -26,6 +26,7 @@ import ChromeAuthContext from '../../auth/ChromeAuthContext'; import { isPreviewAtom } from '../../state/atoms/releaseAtom'; import { notificationDrawerExpandedAtom, unreadNotificationsAtom } from '../../state/atoms/notificationDrawerAtom'; import PreviewAlert from './PreviewAlert'; +import useSupportCaseData from '../../hooks/useSupportCaseData'; const isITLessEnv = ITLess(); @@ -174,6 +175,7 @@ const Tools = () => { }); } }, [user]); + const supportCaseData = useSupportCaseData(); const supportOptionsUrl = () => { return isITLessEnv ? 'https://redhatgov.servicenowservices.com/css' : 'https://access.redhat.com/support'; @@ -188,7 +190,7 @@ const Tools = () => { }, { title: intl.formatMessage(messages.openSupportCase), - onClick: () => createSupportCase(user.identity, token, isPreview), + onClick: () => createSupportCase(user.identity, token, isPreview, { supportCaseData }), isDisabled: window.location.href.includes('/application-services') && !isRhosakEntitled, isHidden: isITLessEnv, }, diff --git a/src/hooks/useSupportCaseData.ts b/src/hooks/useSupportCaseData.ts new file mode 100644 index 000000000..562524363 --- /dev/null +++ b/src/hooks/useSupportCaseData.ts @@ -0,0 +1,42 @@ +import { useMemo } from 'react'; +import { useAtomValue } from 'jotai'; +import { matchPath, useLocation } from 'react-router-dom'; +import { chromeModulesAtom } from '../state/atoms/chromeModuleAtom'; +import { ModuleRoute, SupportCaseConfig } from '../@types/types'; +import { activeModuleAtom } from '../state/atoms/activeModuleAtom'; + +function findRouteSupportCaseData(routes: ModuleRoute[], currentPathname: string): SupportCaseConfig | undefined { + const sortedModules = routes.sort((a, b) => b.pathname.length - a.pathname.length); + const matchedRoute = sortedModules.find(({ pathname }) => { + return matchPath( + { + path: pathname, + end: false, + }, + currentPathname + ); + }); + + return matchedRoute?.supportCaseData; +} + +const useSupportCaseData = (): SupportCaseConfig | undefined => { + const scope = useAtomValue(activeModuleAtom); + const location = useLocation(); + const modules = useAtomValue(chromeModulesAtom); + const moduleConfig = useMemo(() => (scope ? modules[scope] : undefined), [modules, scope]); + const supportCaseData = useMemo(() => { + if (!moduleConfig?.modules) { + return undefined; + } + const routeCaseData = findRouteSupportCaseData( + moduleConfig.modules.flatMap(({ routes }) => routes), + location.pathname + ); + return routeCaseData ?? moduleConfig?.config?.supportCaseData; + }, [moduleConfig, location]); + + return supportCaseData; +}; + +export default useSupportCaseData; diff --git a/src/utils/createCase.ts b/src/utils/createCase.ts index 96bc41943..de53bf0f8 100644 --- a/src/utils/createCase.ts +++ b/src/utils/createCase.ts @@ -9,6 +9,7 @@ import { ChromeUser } from '@redhat-cloud-services/types'; import { getUrl } from '../hooks/useBundle'; import chromeStore from '../state/chromeStore'; import { activeModuleAtom } from '../state/atoms/activeModuleAtom'; +import { SupportCaseConfig } from '../@types/types'; // Lit of products that are bundles const BUNDLE_PRODUCTS = [ @@ -72,8 +73,9 @@ export async function createSupportCase( userInfo: ChromeUser['identity'], token: string, isPreview: boolean, - fields?: { - caseFields: Record; + options?: { + supportCaseData?: SupportCaseConfig | undefined; + caseFields?: Record; } ) { const currentProduct = registerProduct() || 'Other'; @@ -82,6 +84,7 @@ export async function createSupportCase( const { src_hash, app_name } = { src_hash: productData?.src_hash, app_name: productData?.app_name ?? getUrl('app') }; const portalUrl = `${getEnvDetails()?.portal}`; const caseUrl = `${portalUrl}${HYDRA_ENDPOINT}`; + const { supportCaseData, ...fields } = options ?? { caseFields: {} }; log('Creating a support case'); @@ -112,7 +115,12 @@ export async function createSupportCase( .then((response) => response.json()) .then((data) => { if (data) { - const query = URI(`?seSessionId=${data.session.id}&product=${data.sessionDetails.product}&version=${src_hash}`).normalize(); + // FIXME: Use the URLSearchParams API instead of URI.js + const query = URI( + `?seSessionId=${data.session.id}&product=${supportCaseData?.product ?? data.sessionDetails.product}&version=${ + supportCaseData?.version ?? src_hash + }` + ).normalize(); window.open(`${portalUrl}/support/cases/#/case/new/open-case/describe-issue${query.readable()}`); return createSupportSentry(data.session.id, fields); }