Skip to content

Commit

Permalink
Jotai migration isFeedbackModalOpen (#2860)
Browse files Browse the repository at this point in the history
* Jotai migration isFeedbackModalOpen

* Update Jotai migration isFeedbackModalOpen

* Use different atom method

* Add tests for Jotai migration isFeedbackModalOpen

* Remove toggleFeedbackModal function from reducers

* Remove dead code

---------

Co-authored-by: Adam Jetmar <ajetmar@redhat.com>
  • Loading branch information
JetyAdam and Adam Jetmar authored Jun 14, 2024
1 parent 1731eef commit 3af37c6
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 68 deletions.
141 changes: 141 additions & 0 deletions cypress/component/ChromeContext/feedbackModal.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, { Context, createContext, useContext, useState } from 'react';
import { createChromeContext } from '../../../src/chrome/create-chrome';
import { ChromeAPI } from '@redhat-cloud-services/types';
import { AnalyticsBrowser } from '@segment/analytics-next';
import { ChromeAuthContextValue } from '../../../src/auth/ChromeAuthContext';
import { getSharedScope, initSharedScope } from '@scalprum/core';
import { Store } from 'redux';
import Feedback from '../../../src/components/Feedback';
import { IntlProvider } from 'react-intl';
import InternalChromeContext from '../../../src/utils/internalChromeContext';
import { Provider as JotaiProvider } from 'jotai';
import chromeStore from '../../../src/state/chromeStore';

describe('Feedback Modal', () => {
let chromeContext: Context<ChromeAPI>;
let contextValue: ChromeAPI;
const NestedComponen = () => {
return (
<IntlProvider locale="en">
<InternalChromeContext.Provider value={{ getEnvironment: () => 'stage' } as any}>
<Feedback />
</InternalChromeContext.Provider>
</IntlProvider>
);
};
const InnerComponent = () => {
const { usePendoFeedback } = useContext(chromeContext);
usePendoFeedback();
return null;
};

const Wrapper = () => {
const [removeComponent, setRemoveComponent] = useState(false);
return (
<IntlProvider locale="en">
<InternalChromeContext.Provider value={{ getEnvironment: () => 'stage' } as any}>
<Feedback />
{!removeComponent ? <InnerComponent /> : null}
<button onClick={() => setRemoveComponent(true)}>Remove component from dom</button>
</InternalChromeContext.Provider>
</IntlProvider>
);
};

const CustomButton = () => {
const { toggleFeedbackModal } = useContext(chromeContext);

return (
<button style={{ padding: '5px 10px' }} onClick={() => toggleFeedbackModal(true)}>
Custom feedback button
</button>
);
};

const CustomComponent = () => {
return (
<IntlProvider locale="en">
<JotaiProvider store={chromeStore}>
<InternalChromeContext.Provider value={{ getEnvironment: () => 'stage' } as any}>
<CustomButton />
<Feedback />
</InternalChromeContext.Provider>
</JotaiProvider>
</IntlProvider>
);
};

beforeEach(() => {
initSharedScope();
const scope = getSharedScope();
scope['@chrome/visibilityFunctions'] = {
'*': {
loaded: 1,
get: () => {},
},
};
contextValue = createChromeContext({
analytics: {} as AnalyticsBrowser,
chromeAuth: {
getUser: () => Promise.resolve({}),
} as ChromeAuthContextValue,
helpTopics: {} as ChromeAPI['helpTopics'],
quickstartsAPI: {} as ChromeAPI['quickStarts'],
registerModule: () => {},
setPageMetadata: () => {},
store: {
dispatch: () => {},
} as unknown as Store,
useGlobalFilter: () => {},
});
chromeContext = createContext(contextValue);
});
it('should test opening and closing feedback modal', () => {
const Modal = () => {
return (
<chromeContext.Provider value={contextValue}>
<NestedComponen />
</chromeContext.Provider>
);
};
cy.mount(<Modal />);
cy.contains('Tell us about your experience').should('not.exist');
cy.contains('Feedback').click();
cy.contains('Tell us about your experience').should('exist');
cy.get('[aria-label="Close"]').click();
cy.contains('Tell us about your experience').should('not.exist');
});

it('should test pendoFeedback', () => {
const Context = () => {
return (
<chromeContext.Provider value={contextValue}>
<Wrapper />
</chromeContext.Provider>
);
};
cy.mount(<Context />);
cy.contains('Tell us about your experience').should('not.exist');
cy.contains('Feedback').click();
cy.contains('Tell us about your experience').should('not.exist');
cy.contains('Remove component from dom').click();
cy.contains('Feedback').click();
cy.contains('Tell us about your experience').should('exist');
});

it('should use custom feedback button to open feedback modal', () => {
const CustomModal = () => {
return (
<chromeContext.Provider value={contextValue}>
<CustomComponent />
</chromeContext.Provider>
);
};
cy.mount(<CustomModal />);
cy.contains('Tell us about your experience').should('not.exist');
cy.contains('Custom feedback button').click();
cy.contains('Tell us about your experience').should('exist');
cy.get('[aria-label="Close"]').click();
cy.contains('Tell us about your experience').should('not.exist');
});
});
8 changes: 5 additions & 3 deletions src/chrome/create-chrome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@ import {
removeGlobalFilter,
toggleDebuggerButton,
toggleDebuggerModal,
toggleFeedbackModal,
toggleGlobalFilter,
} from '../redux/actions';
import { ITLess, getEnv, getEnvDetails, isBeta, isProd, updateDocumentTitle } from '../utils/common';
import { createSupportCase } from '../utils/createCase';
import debugFunctions from '../utils/debugFunctions';
import { flatTags } from '../components/GlobalFilter/globalFilterApi';
import { PUBLIC_EVENTS } from '../utils/consts';
import { usePendoFeedback } from '../components/Feedback';
import { middlewareListener } from '../redux/redux-config';
import { clearAnsibleTrialFlag, isAnsibleTrialFlagActive, setAnsibleTrialFlag } from '../utils/isAnsibleTrialFlagActive';
import chromeHistory from '../utils/chromeHistory';
Expand All @@ -37,6 +35,8 @@ import qe from '../utils/iqeEnablement';
import { RegisterModulePayload } from '../state/atoms/chromeModuleAtom';
import requestPdf from '../pdf/requestPdf';
import chromeStore from '../state/chromeStore';
import { isFeedbackModalOpenAtom } from '../state/atoms/feedbackModalAtom';
import { usePendoFeedback } from '../components/Feedback';

export type CreateChromeContextConfig = {
useGlobalFilter: (callback: (selectedTags?: FlagTagsFilter) => any) => ReturnType<typeof callback>;
Expand Down Expand Up @@ -170,7 +170,9 @@ export const createChromeContext = ({
segment: {
setPageMetadata,
},
toggleFeedbackModal: (isOpen: boolean) => dispatch(toggleFeedbackModal(isOpen)),
toggleFeedbackModal: (isOpen: boolean) => {
chromeStore.set(isFeedbackModalOpenAtom, isOpen);
},
enableDebugging: () => dispatch(toggleDebuggerButton(true)),
toggleDebuggerModal: (isOpen: boolean) => dispatch(toggleDebuggerModal(isOpen)),
// FIXME: Update types once merged
Expand Down
22 changes: 13 additions & 9 deletions src/components/Feedback/FeedbackModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@ import ExternalLinkAltIcon from '@patternfly/react-icons/dist/dynamic/icons/exte
import OutlinedCommentsIcon from '@patternfly/react-icons/dist/dynamic/icons/outlined-comments-icon';
import { DeepRequired } from 'utility-types';
import { ChromeUser } from '@redhat-cloud-services/types';
import { useDispatch, useSelector } from 'react-redux';
import { useIntl } from 'react-intl';
import { useAtom, useAtomValue } from 'jotai';
import { isFeedbackModalOpenAtom, usePendoFeedbackAtom } from '../../state/atoms/feedbackModalAtom';

import feedbackIllo from '../../../static/images/feedback_illo.svg';
import FeedbackForm from './FeedbackForm';
import { toggleFeedbackModal } from '../../redux/actions';
import { ReduxState } from '../../redux/store';
import FeedbackSuccess from './FeedbackSuccess';
import messages from '../../locales/Messages';
import FeedbackError from './FeedbackError';
Expand All @@ -42,19 +41,18 @@ export type FeedbackPages =

const FeedbackModal = memo(() => {
const intl = useIntl();
const usePendoFeedback = useSelector<ReduxState, boolean | undefined>(({ chrome: { usePendoFeedback } }) => usePendoFeedback);
const isOpen = useSelector<ReduxState, boolean | undefined>(({ chrome: { isFeedbackModalOpen } }) => isFeedbackModalOpen);
const dispatch = useDispatch();
const [isModalOpen, setIsModalOpen] = useAtom(isFeedbackModalOpenAtom);
const usePendoFeedback = useAtomValue(usePendoFeedbackAtom);
const [modalPage, setModalPage] = useState<FeedbackPages>('feedbackHome');
const { getEnvironment } = useContext(InternalChromeContext);
const chromeAuth = useContext(ChromeAuthContext);
const { analytics } = useSegment();
const user = chromeAuth.user as DeepRequired<ChromeUser>;
const env = getEnvironment();
const isAvailable = env === 'prod' || env === 'stage';
const setIsModalOpen = (isOpen: boolean) => dispatch(toggleFeedbackModal(isOpen));
const handleCloseModal = () => {
setIsModalOpen(false), setModalPage('feedbackHome');
setIsModalOpen(false);
setModalPage('feedbackHome');
};

const ModalDescription = ({ modalPage }: { modalPage: FeedbackPages }) => {
Expand Down Expand Up @@ -208,7 +206,13 @@ const FeedbackModal = memo(() => {
<OutlinedCommentsIcon />
{intl.formatMessage(messages.feedback)}
</Button>
<Modal aria-label="Feedback modal" isOpen={isOpen} className="chr-c-feedback-modal" variant={ModalVariant.large} onClose={handleCloseModal}>
<Modal
aria-label="Feedback modal"
isOpen={isModalOpen}
className="chr-c-feedback-modal"
variant={ModalVariant.large}
onClose={handleCloseModal}
>
<Grid>
<GridItem span={8} rowSpan={12}>
<ModalDescription modalPage={modalPage} />
Expand Down
14 changes: 6 additions & 8 deletions src/components/Feedback/usePendoFeedback.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import { useEffect } from 'react';
import { spinUpStore } from '../../redux/redux-config';
import { setPendoFeedbackFlag } from '../../redux/actions';
import { useSetAtom } from 'jotai';
import { usePendoFeedbackAtom } from '../../state/atoms/feedbackModalAtom';

const usePendoFeedback = () => {
/**
* We have to use the "spinUpStore" instead of just calling useDispatch
* Otherwise we will end up using the "dispatch" instance from the application not chrome!
*/
const {
store: { dispatch },
} = spinUpStore();
const setPendoFeedback = useSetAtom(usePendoFeedbackAtom);

useEffect(() => {
dispatch(setPendoFeedbackFlag(true));
setPendoFeedback(true);
return () => {
dispatch(setPendoFeedbackFlag(false));
setPendoFeedback(false);
};
}, []);
}, [setPendoFeedback]);
};

export default usePendoFeedback;
2 changes: 0 additions & 2 deletions src/redux/action-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ export const GLOBAL_FILTER_REMOVE = '@@chrome/global-filter-remove';
export const LOAD_NAVIGATION_LANDING_PAGE = '@@chrome/load-navigation-landing-page';
export const LOAD_LEFT_NAVIGATION_SEGMENT = '@@chrome/load-navigation-segment';

export const SET_PENDO_FEEDBACK_FLAG = '@@chrome/set-pendo-feedback-flag';
export const TOGGLE_FEEDBACK_MODAL = '@@chrome/toggle-feedback-modal';
export const TOGGLE_DEBUGGER_MODAL = '@@chrome/toggle-debugger-modal';
export const TOGGLE_DEBUGGER_BUTTON = '@@chrome/toggle-debugger-button';
export const UPDATE_ACCESS_REQUESTS_NOTIFICATIONS = '@@chrome/update-access-requests-notifications';
Expand Down
10 changes: 0 additions & 10 deletions src/redux/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,6 @@ export const onToggle = () => ({
type: 'NAVIGATION_TOGGLE',
});

export const setPendoFeedbackFlag = (payload: boolean) => ({
type: actionTypes.SET_PENDO_FEEDBACK_FLAG,
payload,
});

export const toggleFeedbackModal = (payload: boolean) => ({
type: actionTypes.TOGGLE_FEEDBACK_MODAL,
payload,
});

export const toggleDebuggerModal = (payload: boolean) => ({
type: actionTypes.TOGGLE_DEBUGGER_MODAL,
payload,
Expand Down
28 changes: 0 additions & 28 deletions src/redux/chromeReducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,34 +80,6 @@ export function loadNavigationSegmentReducer(
return state;
}

export function setPendoFeedbackFlag(
state: ChromeState,
{
payload,
}: {
payload: boolean;
}
): ChromeState {
return {
...state,
usePendoFeedback: payload,
};
}

export function toggleFeedbackModal(
state: ChromeState,
{
payload,
}: {
payload: boolean;
}
): ChromeState {
return {
...state,
isFeedbackModalOpen: payload,
};
}

export function toggleDebuggerModal(
state: ChromeState,
{
Expand Down
6 changes: 0 additions & 6 deletions src/redux/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ import {
onPageAction,
onPageObjectId,
populateQuickstartsReducer,
setPendoFeedbackFlag,
toggleDebuggerButton,
toggleDebuggerModal,
toggleFeedbackModal,
} from './chromeReducers';
import {
globalFilterDefaultState,
Expand Down Expand Up @@ -52,10 +50,8 @@ import {
MARK_ACTIVE_PRODUCT,
MARK_REQUEST_NOTIFICATION_SEEN,
POPULATE_QUICKSTARTS_CATALOG,
SET_PENDO_FEEDBACK_FLAG,
TOGGLE_DEBUGGER_BUTTON,
TOGGLE_DEBUGGER_MODAL,
TOGGLE_FEEDBACK_MODAL,
UPDATE_ACCESS_REQUESTS_NOTIFICATIONS,
UPDATE_DOCUMENT_TITLE_REDUCER,
USER_LOGIN,
Expand All @@ -70,8 +66,6 @@ const reducers = {
[CHROME_PAGE_OBJECT]: onPageObjectId,
[LOAD_NAVIGATION_LANDING_PAGE]: loadNavigationLandingPageReducer,
[LOAD_LEFT_NAVIGATION_SEGMENT]: loadNavigationSegmentReducer,
[SET_PENDO_FEEDBACK_FLAG]: setPendoFeedbackFlag,
[TOGGLE_FEEDBACK_MODAL]: toggleFeedbackModal,
[TOGGLE_DEBUGGER_MODAL]: toggleDebuggerModal,
[TOGGLE_DEBUGGER_BUTTON]: toggleDebuggerButton,
[UPDATE_ACCESS_REQUESTS_NOTIFICATIONS]: accessRequestsNotificationsReducer,
Expand Down
2 changes: 0 additions & 2 deletions src/redux/store.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ export type ChromeState = {
pageAction?: string;
pageObjectId?: string;
navigation: InternalNavigation;
usePendoFeedback?: boolean;
isFeedbackModalOpen?: boolean;
isDebuggerModalOpen?: boolean;
isDebuggerEnabled?: boolean;
accessRequests: {
Expand Down
4 changes: 4 additions & 0 deletions src/state/atoms/feedbackModalAtom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { atom } from 'jotai';

export const isFeedbackModalOpenAtom = atom(false);
export const usePendoFeedbackAtom = atom(false);
2 changes: 2 additions & 0 deletions src/state/chromeStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { contextSwitcherOpenAtom } from './atoms/contextSwitcher';
import { isPreviewAtom } from './atoms/releaseAtom';
import { isBeta } from '../utils/common';
import { gatewayErrorAtom } from './atoms/gatewayErrorAtom';
import { isFeedbackModalOpenAtom } from './atoms/feedbackModalAtom';

const chromeStore = createStore();

Expand All @@ -12,6 +13,7 @@ chromeStore.set(contextSwitcherOpenAtom, false);
chromeStore.set(activeModuleAtom, undefined);
chromeStore.set(isPreviewAtom, isBeta());
chromeStore.set(gatewayErrorAtom, undefined);
chromeStore.set(isFeedbackModalOpenAtom, false);

// globally handle subscription to activeModuleAtom
chromeStore.sub(activeModuleAtom, () => {
Expand Down

0 comments on commit 3af37c6

Please sign in to comment.