Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jotai migration isFeedbackModalOpen #2860

Merged
merged 8 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
JetyAdam marked this conversation as resolved.
Show resolved Hide resolved
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
Loading