aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorViktor Vorona <viktor.vorona@sonarsource.com>2024-11-25 11:10:30 +0100
committersonartech <sonartech@sonarsource.com>2024-11-26 20:02:50 +0000
commit76635fedaa16506415ebe436ad1dd5d93083ea7a (patch)
tree4b44faae0d3e279f7911678199ff8e96b438a59e /server
parent49432ece9dceba4ad844f642afda2f365fe26339 (diff)
downloadsonarqube-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.pngbin0 -> 30668 bytes
-rw-r--r--server/sonar-web/src/main/js/app/components/ModeTour.tsx80
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/ModeTour-test.tsx15
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
new file mode 100644
index 00000000000..80a606a5680
--- /dev/null
+++ b/server/sonar-web/public/images/mode-tour/step4.png
Binary files differ
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 () => {