diff options
author | Viktor Vorona <viktor.vorona@sonarsource.com> | 2024-11-25 11:10:30 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-11-26 20:02:50 +0000 |
commit | 76635fedaa16506415ebe436ad1dd5d93083ea7a (patch) | |
tree | 4b44faae0d3e279f7911678199ff8e96b438a59e /server | |
parent | 49432ece9dceba4ad844f642afda2f365fe26339 (diff) | |
download | sonarqube-76635fedaa16506415ebe436ad1dd5d93083ea7a.tar.gz sonarqube-76635fedaa16506415ebe436ad1dd5d93083ea7a.zip |
SONAR-23654 Design changes for Tour
Diffstat (limited to 'server')
-rw-r--r-- | server/sonar-web/public/images/mode-tour/step4.png | bin | 0 -> 30668 bytes | |||
-rw-r--r-- | server/sonar-web/src/main/js/app/components/ModeTour.tsx | 80 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/app/components/__tests__/ModeTour-test.tsx | 15 |
3 files changed, 56 insertions, 39 deletions
diff --git a/server/sonar-web/public/images/mode-tour/step4.png b/server/sonar-web/public/images/mode-tour/step4.png Binary files differnew file mode 100644 index 00000000000..80a606a5680 --- /dev/null +++ b/server/sonar-web/public/images/mode-tour/step4.png diff --git a/server/sonar-web/src/main/js/app/components/ModeTour.tsx b/server/sonar-web/src/main/js/app/components/ModeTour.tsx index 7c00716a6f2..8a9f745a1e7 100644 --- a/server/sonar-web/src/main/js/app/components/ModeTour.tsx +++ b/server/sonar-web/src/main/js/app/components/ModeTour.tsx @@ -34,6 +34,8 @@ import { NoticeType } from '../../types/users'; import { useAppState } from './app-state/withAppStateContext'; import { CurrentUserContext } from './current-user/CurrentUserContext'; +const MAX_STEPS = 4; + export default function ModeTour() { const { currentUser, updateDismissedNotices } = useContext(CurrentUserContext); const appState = useAppState(); @@ -42,34 +44,8 @@ export default function ModeTour() { const [step, setStep] = useState(1); const [runManually, setRunManually] = useState(false); - const steps: SpotlightTourStep[] = [ - { - target: '[data-guiding-id="mode-tour-1"]', - content: intl.formatMessage( - { id: 'mode_tour.step4.description' }, - { - mode: intl.formatMessage({ - id: `settings.mode.${isStandardMode ? 'standard' : 'mqr'}.name`, - }), - p1: (text) => <p>{text}</p>, - p: (text) => <p className="sw-mt-2">{text}</p>, - b: (text) => <b>{text}</b>, - }, - ), - title: intl.formatMessage({ id: 'mode_tour.step4.title' }), - placement: 'bottom', - }, - { - target: '[data-guiding-id="mode-tour-2"]', - title: intl.formatMessage({ id: 'mode_tour.step5.title' }), - content: null, - placement: 'left', - hideFooter: true, - }, - ]; - const nextStep = () => { - if ((step === 3 && !isAdmin) || step === 4) { + if (step === MAX_STEPS) { document.dispatchEvent(new CustomEvent(CustomEvents.OpenHelpMenu)); setTimeout(() => setStep(5)); } else { @@ -124,21 +100,51 @@ export default function ModeTour() { return null; } - const maxSteps = isAdmin ? 4 : 3; + const steps: SpotlightTourStep[] = [ + ...(isAdmin + ? [ + { + target: '[data-guiding-id="mode-tour-1"]', + content: intl.formatMessage( + { id: 'mode_tour.step4.description' }, + { + mode: intl.formatMessage({ + id: `settings.mode.${isStandardMode ? 'standard' : 'mqr'}.name`, + }), + p1: (text) => <p>{text}</p>, + p: (text) => <p className="sw-mt-2">{text}</p>, + b: (text) => <b>{text}</b>, + }, + ), + title: intl.formatMessage({ id: 'mode_tour.step4.title' }), + placement: 'bottom' as const, + }, + ] + : []), + { + target: '[data-guiding-id="mode-tour-2"]', + title: intl.formatMessage({ id: 'mode_tour.step5.title' }), + content: null, + placement: 'left', + hideFooter: true, + }, + ]; + + const maxModalSteps = isAdmin ? MAX_STEPS - 1 : MAX_STEPS; return ( <> <Modal size={ModalSize.Wide} - isOpen={step <= 3} + isOpen={step <= maxModalSteps} onOpenChange={(isOpen) => isOpen === false && dismissTour()} title={ - step < 4 && + step <= maxModalSteps && intl.formatMessage({ id: `mode_tour.step${step}.title` }, { version: appState.version }) } content={ <> - {step < 4 && ( + {step <= maxModalSteps && ( <> <Image alt={intl.formatMessage({ id: `mode_tour.step${step}.img_alt` })} @@ -148,6 +154,9 @@ export default function ModeTour() { {intl.formatMessage( { id: `mode_tour.step${step}.description` }, { + mode: intl.formatMessage({ + id: `settings.mode.${isStandardMode ? 'standard' : 'mqr'}.name`, + }), p1: (text) => <p>{text}</p>, p: (text) => <p className="sw-mt-4">{text}</p>, b: (text) => <b>{text}</b>, @@ -155,7 +164,7 @@ export default function ModeTour() { )} <div className="sw-mt-6"> <b> - {intl.formatMessage({ id: 'guiding.step_x_of_y' }, { 0: step, 1: maxSteps })} + {intl.formatMessage({ id: 'guiding.step_x_of_y' }, { 0: step, 1: MAX_STEPS })} </b> </div> </> @@ -183,13 +192,14 @@ export default function ModeTour() { <SpotlightTour callback={onToggle} steps={steps} - run={step > 3} + run={step > maxModalSteps} continuous + disableOverlay={step === 5} showProgress={step !== 5} - stepIndex={step - 4} + stepIndex={step - maxModalSteps - 1} nextLabel={intl.formatMessage({ id: 'next' })} stepXofYLabel={(x: number) => - intl.formatMessage({ id: 'guiding.step_x_of_y' }, { 0: x + 3, 1: maxSteps }) + intl.formatMessage({ id: 'guiding.step_x_of_y' }, { 0: x + maxModalSteps, 1: MAX_STEPS }) } /> </> diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ModeTour-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ModeTour-test.tsx index 5c09dd0bb49..88e5f674860 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/ModeTour-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/ModeTour-test.tsx @@ -35,6 +35,7 @@ const ui = { step1Dialog: byRole('dialog', { name: /mode_tour.step1.title/ }), step2Dialog: byRole('dialog', { name: /mode_tour.step2.title/ }), step3Dialog: byRole('dialog', { name: /mode_tour.step3.title/ }), + step4Dialog: byRole('dialog', { name: /mode_tour.step4.title/ }), next: byRole('button', { name: 'next' }), later: byRole('button', { name: 'later' }), skip: byRole('button', { name: 'skip' }), @@ -109,7 +110,7 @@ it('renders the tour for gateadmins', async () => { expect(ui.later.get()).toBeInTheDocument(); expect(ui.next.query()).not.toBeInTheDocument(); expect(ui.letsgo.get()).toBeInTheDocument(); - expect(ui.step1Dialog.get()).toHaveTextContent('guiding.step_x_of_y.1.3'); + expect(ui.step1Dialog.get()).toHaveTextContent('guiding.step_x_of_y.1.4'); await user.click(ui.letsgo.get()); expect(ui.step2Dialog.get()).toBeInTheDocument(); @@ -117,13 +118,19 @@ it('renders the tour for gateadmins', async () => { expect(ui.later.query()).not.toBeInTheDocument(); expect(ui.next.get()).toBeInTheDocument(); expect(ui.letsgo.query()).not.toBeInTheDocument(); - expect(ui.step2Dialog.get()).toHaveTextContent('guiding.step_x_of_y.2.3'); + expect(ui.step2Dialog.get()).toHaveTextContent('guiding.step_x_of_y.2.4'); await user.click(ui.next.get()); expect(ui.step3Dialog.get()).toBeInTheDocument(); expect(ui.step2Dialog.query()).not.toBeInTheDocument(); expect(ui.next.get()).toBeInTheDocument(); - expect(ui.step3Dialog.get()).toHaveTextContent('guiding.step_x_of_y.3.3'); + expect(ui.step3Dialog.get()).toHaveTextContent('guiding.step_x_of_y.3.4'); + await user.click(ui.next.get()); + + expect(ui.step4Dialog.get()).toBeInTheDocument(); + expect(ui.step3Dialog.query()).not.toBeInTheDocument(); + expect(ui.next.get()).toBeInTheDocument(); + expect(ui.step4Dialog.get()).toHaveTextContent('guiding.step_x_of_y.4.4'); await user.click(ui.next.get()); expect(ui.dialog.query()).not.toBeInTheDocument(); @@ -141,7 +148,7 @@ it('renders the tour for gateadmins', async () => { await user.click(ui.help.get()); await user.click(ui.tourTrigger.get()); expect(ui.step1Dialog.get()).toBeInTheDocument(); - expect(ui.step1Dialog.get()).toHaveTextContent('guiding.step_x_of_y.1.3'); + expect(ui.step1Dialog.get()).toHaveTextContent('guiding.step_x_of_y.1.4'); }); it('should not render the tour for regular users', async () => { |