mirror of
https://github.com/grafana/grafana.git
synced 2024-11-22 08:56:43 -06:00
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
This commit is contained in:
parent
3e1f5559a6
commit
22d5efba25
@ -1,11 +1,12 @@
|
||||
import { css } from '@emotion/css';
|
||||
import history from 'history';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Prompt } from 'react-router-dom';
|
||||
import { Navigate } from 'react-router-dom-v5-compat';
|
||||
|
||||
import { Button, Modal } from '@grafana/ui';
|
||||
|
||||
import { Prompt } from './Prompt';
|
||||
|
||||
export interface Props {
|
||||
confirmRedirect?: boolean;
|
||||
onDiscard: () => void;
|
||||
|
57
public/app/core/components/FormPrompt/Prompt.test.tsx
Normal file
57
public/app/core/components/FormPrompt/Prompt.test.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { History, Location, createMemoryHistory } from 'history';
|
||||
import { render } from 'test/test-utils';
|
||||
|
||||
import { locationService } from '@grafana/runtime';
|
||||
|
||||
import { Prompt } from './Prompt';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
locationService: {
|
||||
getLocation: jest.fn(),
|
||||
getHistory: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('Prompt component with React Router', () => {
|
||||
let mockHistory: History & { block: jest.Mock };
|
||||
|
||||
beforeEach(() => {
|
||||
const historyInstance = createMemoryHistory({ initialEntries: ['/current'] });
|
||||
mockHistory = {
|
||||
...historyInstance,
|
||||
block: jest.fn(() => jest.fn()),
|
||||
};
|
||||
|
||||
(locationService.getLocation as jest.Mock).mockReturnValue({ pathname: '/current' } as Location);
|
||||
(locationService.getHistory as jest.Mock).mockReturnValue(mockHistory);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should call the block function when `when` is true', () => {
|
||||
const { unmount } = render(<Prompt when={true} message="Are you sure you want to leave?" />);
|
||||
|
||||
unmount();
|
||||
expect(mockHistory.block).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call the block function when `when` is false', () => {
|
||||
const { unmount } = render(<Prompt when={false} message="Are you sure you want to leave?" />);
|
||||
|
||||
unmount();
|
||||
expect(mockHistory.block).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use the message function if provided', async () => {
|
||||
const messageFn = jest.fn().mockReturnValue('Custom message');
|
||||
render(<Prompt when={true} message={messageFn} />);
|
||||
|
||||
const callback = mockHistory.block.mock.calls[0][0];
|
||||
callback({ pathname: '/new-path' } as Location);
|
||||
|
||||
expect(messageFn).toHaveBeenCalledWith(expect.objectContaining({ pathname: '/new-path' }));
|
||||
});
|
||||
});
|
27
public/app/core/components/FormPrompt/Prompt.tsx
Normal file
27
public/app/core/components/FormPrompt/Prompt.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import * as H from 'history';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { locationService } from '@grafana/runtime';
|
||||
|
||||
interface PromptProps {
|
||||
when?: boolean;
|
||||
message: string | ((location: H.Location) => string | boolean);
|
||||
}
|
||||
|
||||
export const Prompt = ({ message, when = true }: PromptProps) => {
|
||||
const history = locationService.getHistory();
|
||||
|
||||
useEffect(() => {
|
||||
if (!when) {
|
||||
return undefined;
|
||||
}
|
||||
//@ts-expect-error TODO Update the history package to fix types
|
||||
const unblock = history.block(message);
|
||||
|
||||
return () => {
|
||||
unblock();
|
||||
};
|
||||
}, [when, message, history]);
|
||||
|
||||
return null;
|
||||
};
|
@ -1,11 +1,11 @@
|
||||
import { css } from '@emotion/css';
|
||||
import * as H from 'history';
|
||||
import { memo, useContext, useEffect, useMemo } from 'react';
|
||||
import { Prompt } from 'react-router';
|
||||
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { Dashboard } from '@grafana/schema/dist/esm/index.gen';
|
||||
import { ModalsContext, Modal, Button, useStyles2 } from '@grafana/ui';
|
||||
import { Prompt } from 'app/core/components/FormPrompt/Prompt';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
|
||||
import { SaveLibraryVizPanelModal } from '../panel-edit/SaveLibraryVizPanelModal';
|
||||
|
@ -1,12 +1,12 @@
|
||||
import * as H from 'history';
|
||||
import { find } from 'lodash';
|
||||
import { memo, useContext, useEffect, useState } from 'react';
|
||||
import { Prompt } from 'react-router-dom';
|
||||
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import { ModalsContext } from '@grafana/ui';
|
||||
import { appEvents } from 'app/core/app_events';
|
||||
import { Prompt } from 'app/core/components/FormPrompt/Prompt';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { SaveLibraryPanelModal } from 'app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal';
|
||||
import { PanelModelWithLibraryPanel } from 'app/features/library-panels/types';
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Prompt } from 'react-router-dom';
|
||||
import { useBeforeUnload, useUnmount } from 'react-use';
|
||||
|
||||
import { GrafanaTheme2, colorManipulator } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { Button, Icon, Stack, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import { Prompt } from 'app/core/components/FormPrompt/Prompt';
|
||||
import { CORRELATION_EDITOR_POST_CONFIRM_ACTION, ExploreItemState, useDispatch, useSelector } from 'app/types';
|
||||
|
||||
import { CorrelationUnsavedChangesModal } from './CorrelationUnsavedChangesModal';
|
||||
@ -175,13 +175,13 @@ export const CorrelationEditorModeBar = ({ panes }: { panes: Array<[string, Expl
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Handle navigating outside of Explore */}
|
||||
{/* Handle navigating outside Explore */}
|
||||
<Prompt
|
||||
message={(location) => {
|
||||
if (
|
||||
location.pathname !== '/explore' &&
|
||||
(correlationDetails?.editorMode || false) &&
|
||||
(correlationDetails?.correlationDirty || false)
|
||||
correlationDetails?.editorMode &&
|
||||
correlationDetails?.correlationDirty
|
||||
) {
|
||||
return 'You have unsaved correlation data. Continue?';
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user