Skip to content

Commit 22d5efb

Browse files
authored
Routing: Replace Prompt component (#94675)
* Add custom Prompt component * Add test * Remove beforeunload handling * Updates * Use custom Prompt in CorrelationEditorModeBar.tsx * Simplify component * Update DashboardPrompt * Simplify Prompt * Update * Update DashboardPrompt.tsx * Update type * Update tests
1 parent 3e1f555 commit 22d5efb

File tree

6 files changed

+92
-7
lines changed

6 files changed

+92
-7
lines changed

public/app/core/components/FormPrompt/FormPrompt.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { css } from '@emotion/css';
22
import history from 'history';
33
import { useEffect, useState } from 'react';
4-
import { Prompt } from 'react-router-dom';
54
import { Navigate } from 'react-router-dom-v5-compat';
65

76
import { Button, Modal } from '@grafana/ui';
87

8+
import { Prompt } from './Prompt';
9+
910
export interface Props {
1011
confirmRedirect?: boolean;
1112
onDiscard: () => void;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { History, Location, createMemoryHistory } from 'history';
2+
import { render } from 'test/test-utils';
3+
4+
import { locationService } from '@grafana/runtime';
5+
6+
import { Prompt } from './Prompt';
7+
8+
jest.mock('@grafana/runtime', () => ({
9+
...jest.requireActual('@grafana/runtime'),
10+
locationService: {
11+
getLocation: jest.fn(),
12+
getHistory: jest.fn(),
13+
},
14+
}));
15+
16+
describe('Prompt component with React Router', () => {
17+
let mockHistory: History & { block: jest.Mock };
18+
19+
beforeEach(() => {
20+
const historyInstance = createMemoryHistory({ initialEntries: ['/current'] });
21+
mockHistory = {
22+
...historyInstance,
23+
block: jest.fn(() => jest.fn()),
24+
};
25+
26+
(locationService.getLocation as jest.Mock).mockReturnValue({ pathname: '/current' } as Location);
27+
(locationService.getHistory as jest.Mock).mockReturnValue(mockHistory);
28+
});
29+
30+
afterEach(() => {
31+
jest.clearAllMocks();
32+
});
33+
34+
it('should call the block function when `when` is true', () => {
35+
const { unmount } = render(<Prompt when={true} message="Are you sure you want to leave?" />);
36+
37+
unmount();
38+
expect(mockHistory.block).toHaveBeenCalled();
39+
});
40+
41+
it('should not call the block function when `when` is false', () => {
42+
const { unmount } = render(<Prompt when={false} message="Are you sure you want to leave?" />);
43+
44+
unmount();
45+
expect(mockHistory.block).not.toHaveBeenCalled();
46+
});
47+
48+
it('should use the message function if provided', async () => {
49+
const messageFn = jest.fn().mockReturnValue('Custom message');
50+
render(<Prompt when={true} message={messageFn} />);
51+
52+
const callback = mockHistory.block.mock.calls[0][0];
53+
callback({ pathname: '/new-path' } as Location);
54+
55+
expect(messageFn).toHaveBeenCalledWith(expect.objectContaining({ pathname: '/new-path' }));
56+
});
57+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as H from 'history';
2+
import { useEffect } from 'react';
3+
4+
import { locationService } from '@grafana/runtime';
5+
6+
interface PromptProps {
7+
when?: boolean;
8+
message: string | ((location: H.Location) => string | boolean);
9+
}
10+
11+
export const Prompt = ({ message, when = true }: PromptProps) => {
12+
const history = locationService.getHistory();
13+
14+
useEffect(() => {
15+
if (!when) {
16+
return undefined;
17+
}
18+
//@ts-expect-error TODO Update the history package to fix types
19+
const unblock = history.block(message);
20+
21+
return () => {
22+
unblock();
23+
};
24+
}, [when, message, history]);
25+
26+
return null;
27+
};

public/app/features/dashboard-scene/saving/DashboardPrompt.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { css } from '@emotion/css';
22
import * as H from 'history';
33
import { memo, useContext, useEffect, useMemo } from 'react';
4-
import { Prompt } from 'react-router';
54

65
import { locationService } from '@grafana/runtime';
76
import { Dashboard } from '@grafana/schema/dist/esm/index.gen';
87
import { ModalsContext, Modal, Button, useStyles2 } from '@grafana/ui';
8+
import { Prompt } from 'app/core/components/FormPrompt/Prompt';
99
import { contextSrv } from 'app/core/services/context_srv';
1010

1111
import { SaveLibraryVizPanelModal } from '../panel-edit/SaveLibraryVizPanelModal';

public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import * as H from 'history';
22
import { find } from 'lodash';
33
import { memo, useContext, useEffect, useState } from 'react';
4-
import { Prompt } from 'react-router-dom';
54

65
import { locationService } from '@grafana/runtime';
76
import { Dashboard } from '@grafana/schema';
87
import { ModalsContext } from '@grafana/ui';
98
import { appEvents } from 'app/core/app_events';
9+
import { Prompt } from 'app/core/components/FormPrompt/Prompt';
1010
import { contextSrv } from 'app/core/services/context_srv';
1111
import { SaveLibraryPanelModal } from 'app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal';
1212
import { PanelModelWithLibraryPanel } from 'app/features/library-panels/types';

public/app/features/explore/CorrelationEditorModeBar.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { css } from '@emotion/css';
22
import { useEffect, useState } from 'react';
3-
import { Prompt } from 'react-router-dom';
43
import { useBeforeUnload, useUnmount } from 'react-use';
54

65
import { GrafanaTheme2, colorManipulator } from '@grafana/data';
76
import { reportInteraction } from '@grafana/runtime';
87
import { Button, Icon, Stack, Tooltip, useStyles2 } from '@grafana/ui';
8+
import { Prompt } from 'app/core/components/FormPrompt/Prompt';
99
import { CORRELATION_EDITOR_POST_CONFIRM_ACTION, ExploreItemState, useDispatch, useSelector } from 'app/types';
1010

1111
import { CorrelationUnsavedChangesModal } from './CorrelationUnsavedChangesModal';
@@ -175,13 +175,13 @@ export const CorrelationEditorModeBar = ({ panes }: { panes: Array<[string, Expl
175175

176176
return (
177177
<>
178-
{/* Handle navigating outside of Explore */}
178+
{/* Handle navigating outside Explore */}
179179
<Prompt
180180
message={(location) => {
181181
if (
182182
location.pathname !== '/explore' &&
183-
(correlationDetails?.editorMode || false) &&
184-
(correlationDetails?.correlationDirty || false)
183+
correlationDetails?.editorMode &&
184+
correlationDetails?.correlationDirty
185185
) {
186186
return 'You have unsaved correlation data. Continue?';
187187
} else {

0 commit comments

Comments
 (0)