mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* feat: add a reactive extension registry Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com> * feat: add hooks to work with the reactive registry Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com> * feat: start using the reactive registry Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com> * feat: update the "command palette" extension point to use the hook * feat: update the "alerting" extension point to use the hooks Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com> * feat: update the "explore" extension point to use the hooks Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com> * feat: update the "datasources config" extension point to use the hooks Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com> * feat: update the "panel menu" extension point to use the hooks Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com> * feat: update the "pyroscope datasource" extension point to use the hooks Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com> * feat: update the "user profile page" extension point to use the hooks * chore: update betterer * fix: update the hooks to not re-render unnecessarily * chore: remove the old `createPluginExtensionRegistry` impementation * chore: add "TODO" for `PanelMenuBehaviour` extension point * feat: update the return value of the hooks to contain a `{ isLoading }` param * tests: add more tests for the usePluginExtensions() hook * fix: exclude the cloud-home-app from being non-awaited * refactor: use uuidv4() for random ID generation (for the registry object) * fix: linting issue * feat: use the hooks for the new alerting extension point * feat: use `useMemo()` for `AlertInstanceAction` extension point context --------- Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>
246 lines
8.5 KiB
TypeScript
246 lines
8.5 KiB
TypeScript
import { render, screen, waitFor } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import React, { ReactNode } from 'react';
|
|
import { Provider } from 'react-redux';
|
|
|
|
import { PluginExtensionPoints, PluginExtensionTypes } from '@grafana/data';
|
|
import { usePluginLinkExtensions } from '@grafana/runtime';
|
|
import { DataQuery } from '@grafana/schema';
|
|
import { contextSrv } from 'app/core/services/context_srv';
|
|
import { configureStore } from 'app/store/configureStore';
|
|
import { ExplorePanelData, ExploreState } from 'app/types';
|
|
|
|
import { createEmptyQueryResponse } from '../state/utils';
|
|
|
|
import { ToolbarExtensionPoint } from './ToolbarExtensionPoint';
|
|
|
|
jest.mock('@grafana/runtime', () => ({
|
|
...jest.requireActual('@grafana/runtime'),
|
|
usePluginLinkExtensions: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('app/core/services/context_srv');
|
|
|
|
const contextSrvMock = jest.mocked(contextSrv);
|
|
const usePluginLinkExtensionsMock = jest.mocked(usePluginLinkExtensions);
|
|
|
|
type storeOptions = {
|
|
targets: DataQuery[];
|
|
data: ExplorePanelData;
|
|
};
|
|
|
|
function renderWithExploreStore(
|
|
children: ReactNode,
|
|
options: storeOptions = { targets: [{ refId: 'A' }], data: createEmptyQueryResponse() }
|
|
) {
|
|
const { targets, data } = options;
|
|
const store = configureStore({
|
|
explore: {
|
|
panes: {
|
|
left: {
|
|
queries: targets,
|
|
queryResponse: data,
|
|
range: {
|
|
raw: { from: 'now-1h', to: 'now' },
|
|
},
|
|
},
|
|
},
|
|
} as unknown as ExploreState,
|
|
});
|
|
|
|
render(<Provider store={store}>{children}</Provider>, {});
|
|
}
|
|
|
|
describe('ToolbarExtensionPoint', () => {
|
|
describe('with extension points', () => {
|
|
beforeAll(() => {
|
|
usePluginLinkExtensionsMock.mockReturnValue({
|
|
extensions: [
|
|
{
|
|
pluginId: 'grafana',
|
|
id: '1',
|
|
type: PluginExtensionTypes.link,
|
|
title: 'Add to dashboard',
|
|
category: 'Dashboards',
|
|
description: 'Add the current query as a panel to a dashboard',
|
|
onClick: jest.fn(),
|
|
},
|
|
{
|
|
pluginId: 'grafana-ml-app',
|
|
id: '2',
|
|
type: PluginExtensionTypes.link,
|
|
title: 'ML: Forecast',
|
|
description: 'Add the query as a ML forecast',
|
|
path: '/a/grafana-ml-ap/forecast',
|
|
},
|
|
],
|
|
isLoading: false,
|
|
});
|
|
});
|
|
|
|
it('should render "Add" extension point menu button', () => {
|
|
renderWithExploreStore(<ToolbarExtensionPoint exploreId="left" timeZone="browser" />);
|
|
|
|
expect(screen.getByRole('button', { name: 'Add' })).toBeVisible();
|
|
});
|
|
|
|
it('should render menu with extensions when "Add" is clicked', async () => {
|
|
renderWithExploreStore(<ToolbarExtensionPoint exploreId="left" timeZone="browser" />);
|
|
|
|
await userEvent.click(screen.getByRole('button', { name: 'Add' }));
|
|
|
|
expect(screen.getByRole('group', { name: 'Dashboards' })).toBeVisible();
|
|
expect(screen.getByRole('menuitem', { name: 'Add to dashboard' })).toBeVisible();
|
|
expect(screen.getByRole('menuitem', { name: 'ML: Forecast' })).toBeVisible();
|
|
});
|
|
|
|
it('should call onClick from extension when menu item is clicked', async () => {
|
|
renderWithExploreStore(<ToolbarExtensionPoint exploreId="left" timeZone="browser" />);
|
|
|
|
await userEvent.click(screen.getByRole('button', { name: 'Add' }));
|
|
await userEvent.click(screen.getByRole('menuitem', { name: 'Add to dashboard' }));
|
|
|
|
const { extensions } = usePluginLinkExtensionsMock({
|
|
extensionPointId: PluginExtensionPoints.ExploreToolbarAction,
|
|
});
|
|
const [extension] = extensions;
|
|
|
|
expect(jest.mocked(extension.onClick)).toBeCalledTimes(1);
|
|
});
|
|
|
|
it('should render confirm navigation modal when extension with path is clicked', async () => {
|
|
renderWithExploreStore(<ToolbarExtensionPoint exploreId="left" timeZone="browser" />);
|
|
|
|
await userEvent.click(screen.getByRole('button', { name: 'Add' }));
|
|
await userEvent.click(screen.getByRole('menuitem', { name: 'ML: Forecast' }));
|
|
|
|
expect(screen.getByRole('button', { name: 'Open in new tab' })).toBeVisible();
|
|
expect(screen.getByRole('button', { name: 'Open' })).toBeVisible();
|
|
expect(screen.getByRole('button', { name: 'Cancel' })).toBeVisible();
|
|
});
|
|
|
|
it('should pass a correct constructed context when fetching extensions', async () => {
|
|
const targets = [{ refId: 'A' }];
|
|
const data = createEmptyQueryResponse();
|
|
|
|
renderWithExploreStore(<ToolbarExtensionPoint exploreId="left" timeZone="browser" />, {
|
|
targets,
|
|
data,
|
|
});
|
|
|
|
const [options] = usePluginLinkExtensionsMock.mock.calls[0];
|
|
const { context } = options;
|
|
|
|
expect(context).toEqual({
|
|
exploreId: 'left',
|
|
targets,
|
|
data: expect.objectContaining({
|
|
...data,
|
|
timeRange: expect.any(Object),
|
|
}),
|
|
timeZone: 'browser',
|
|
timeRange: { from: 'now-1h', to: 'now' },
|
|
shouldShowAddCorrelation: false,
|
|
});
|
|
});
|
|
|
|
it('should pass a context with correct timeZone when fetching extensions', async () => {
|
|
const targets = [{ refId: 'A' }];
|
|
const data = createEmptyQueryResponse();
|
|
|
|
renderWithExploreStore(<ToolbarExtensionPoint exploreId="left" timeZone="" />, {
|
|
targets,
|
|
data,
|
|
});
|
|
|
|
const [options] = usePluginLinkExtensionsMock.mock.calls[0];
|
|
const { context } = options;
|
|
|
|
expect(context).toHaveProperty('timeZone', 'browser');
|
|
});
|
|
|
|
it('should correct extension point id when fetching extensions', async () => {
|
|
renderWithExploreStore(<ToolbarExtensionPoint exploreId="left" timeZone="browser" />);
|
|
|
|
const [options] = usePluginLinkExtensionsMock.mock.calls[0];
|
|
const { extensionPointId } = options;
|
|
|
|
expect(extensionPointId).toBe(PluginExtensionPoints.ExploreToolbarAction);
|
|
});
|
|
});
|
|
|
|
describe('with extension points without categories', () => {
|
|
beforeAll(() => {
|
|
usePluginLinkExtensionsMock.mockReturnValue({
|
|
extensions: [
|
|
{
|
|
pluginId: 'grafana',
|
|
id: '1',
|
|
type: PluginExtensionTypes.link,
|
|
title: 'Dashboard',
|
|
description: 'Add the current query as a panel to a dashboard',
|
|
onClick: jest.fn(),
|
|
},
|
|
{
|
|
pluginId: 'grafana-ml-app',
|
|
id: '2',
|
|
type: PluginExtensionTypes.link,
|
|
title: 'ML: Forecast',
|
|
description: 'Add the query as a ML forecast',
|
|
path: '/a/grafana-ml-ap/forecast',
|
|
},
|
|
],
|
|
isLoading: false,
|
|
});
|
|
});
|
|
|
|
it('should render "Add" extension point menu button', () => {
|
|
renderWithExploreStore(<ToolbarExtensionPoint exploreId="left" timeZone="browser" />);
|
|
|
|
expect(screen.getByRole('button', { name: 'Add' })).toBeVisible();
|
|
});
|
|
|
|
it('should render menu with extensions when "Add" is clicked', async () => {
|
|
renderWithExploreStore(<ToolbarExtensionPoint exploreId="left" timeZone="browser" />);
|
|
|
|
await userEvent.click(screen.getByRole('button', { name: 'Add' }));
|
|
|
|
// Make sure we don't have anything related to categories rendered
|
|
expect(screen.queryAllByRole('group').length).toBe(0);
|
|
expect(screen.getByRole('menuitem', { name: 'Dashboard' })).toBeVisible();
|
|
expect(screen.getByRole('menuitem', { name: 'ML: Forecast' })).toBeVisible();
|
|
});
|
|
});
|
|
|
|
describe('without extension points', () => {
|
|
beforeAll(() => {
|
|
contextSrvMock.hasPermission.mockReturnValue(true);
|
|
usePluginLinkExtensionsMock.mockReturnValue({ extensions: [], isLoading: false });
|
|
});
|
|
|
|
it('should render "add to dashboard" action button if one pane is visible', async () => {
|
|
renderWithExploreStore(<ToolbarExtensionPoint exploreId="left" timeZone="browser" />);
|
|
|
|
await waitFor(() => {
|
|
const button = screen.getByRole('button', { name: /add to dashboard/i });
|
|
|
|
expect(button).toBeVisible();
|
|
expect(button).toBeEnabled();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('with insufficient permissions', () => {
|
|
beforeAll(() => {
|
|
contextSrvMock.hasPermission.mockReturnValue(false);
|
|
usePluginLinkExtensionsMock.mockReturnValue({ extensions: [], isLoading: false });
|
|
});
|
|
|
|
it('should not render "add to dashboard" action button', async () => {
|
|
renderWithExploreStore(<ToolbarExtensionPoint exploreId="left" timeZone="browser" />);
|
|
|
|
expect(screen.queryByRole('button', { name: /add to dashboard/i })).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|