Plugin extensions: Make sure core features use new hooks (#92723)

* use new extensions APIs across grafana core

* setup setPluginLinksHook

* fix tests

* fix mock

* fix more broken tests

* use plugin components hook

* remove unused func

* fix tests

* remove unused import
This commit is contained in:
Erik Sundell 2024-09-13 09:23:18 +02:00 committed by GitHub
parent 01a4e6b9af
commit 8c702d4a6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 115 additions and 111 deletions

View File

@ -16,7 +16,7 @@ import { AppChrome } from './AppChrome';
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'), ...jest.requireActual('@grafana/runtime'),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), usePluginLinks: jest.fn().mockReturnValue({ links: [] }),
})); }));
const searchData: DataFrame = { const searchData: DataFrame = {

View File

@ -3,7 +3,7 @@ import { byTestId, byText } from 'testing-library-selector';
import { DataSourceApi } from '@grafana/data'; import { DataSourceApi } from '@grafana/data';
import { PromOptions, PrometheusDatasource } from '@grafana/prometheus'; import { PromOptions, PrometheusDatasource } from '@grafana/prometheus';
import { setDataSourceSrv, setPluginExtensionsHook } from '@grafana/runtime'; import { setDataSourceSrv, setPluginLinksHook } from '@grafana/runtime';
import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons'; import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
@ -191,8 +191,8 @@ describe('PanelAlertTabContent', () => {
AccessControlAction.AlertingRuleExternalWrite, AccessControlAction.AlertingRuleExternalWrite,
]); ]);
setPluginExtensionsHook(() => ({ setPluginLinksHook(() => ({
extensions: [], links: [],
isLoading: false, isLoading: false,
})); }));

View File

@ -13,7 +13,7 @@ import {
locationService, locationService,
setAppEvents, setAppEvents,
setDataSourceSrv, setDataSourceSrv,
usePluginLinkExtensions, usePluginLinks,
} from '@grafana/runtime'; } from '@grafana/runtime';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons'; import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons';
@ -52,7 +52,7 @@ import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'), ...jest.requireActual('@grafana/runtime'),
getPluginLinkExtensions: jest.fn(), getPluginLinkExtensions: jest.fn(),
usePluginLinkExtensions: jest.fn(), usePluginLinks: jest.fn(),
useReturnToPrevious: jest.fn(), useReturnToPrevious: jest.fn(),
})); }));
jest.mock('./api/buildInfo'); jest.mock('./api/buildInfo');
@ -72,7 +72,7 @@ setupPluginsExtensionsHook();
const mocks = { const mocks = {
getAllDataSourcesMock: jest.mocked(config.getAllDataSources), getAllDataSourcesMock: jest.mocked(config.getAllDataSources),
getPluginLinkExtensionsMock: jest.mocked(getPluginLinkExtensions), getPluginLinkExtensionsMock: jest.mocked(getPluginLinkExtensions),
usePluginLinkExtensionsMock: jest.mocked(usePluginLinkExtensions), usePluginLinksMock: jest.mocked(usePluginLinks),
rulesInSameGroupHaveInvalidForMock: jest.mocked(actions.rulesInSameGroupHaveInvalidFor), rulesInSameGroupHaveInvalidForMock: jest.mocked(actions.rulesInSameGroupHaveInvalidFor),
api: { api: {
@ -168,8 +168,8 @@ describe('RuleList', () => {
AccessControlAction.AlertingRuleExternalWrite, AccessControlAction.AlertingRuleExternalWrite,
]); ]);
mocks.rulesInSameGroupHaveInvalidForMock.mockReturnValue([]); mocks.rulesInSameGroupHaveInvalidForMock.mockReturnValue([]);
mocks.usePluginLinkExtensionsMock.mockReturnValue({ mocks.usePluginLinksMock.mockReturnValue({
extensions: [ links: [
{ {
pluginId: 'grafana-ml-app', pluginId: 'grafana-ml-app',
id: '1', id: '1',

View File

@ -1,7 +1,7 @@
import { ReactElement, useMemo, useState } from 'react'; import { ReactElement, useMemo, useState } from 'react';
import { PluginExtensionLink, PluginExtensionPoints } from '@grafana/data'; import { PluginExtensionLink, PluginExtensionPoints } from '@grafana/data';
import { usePluginLinkExtensions } from '@grafana/runtime'; import { usePluginLinks } from '@grafana/runtime';
import { Dropdown, IconButton } from '@grafana/ui'; import { Dropdown, IconButton } from '@grafana/ui';
import { ConfirmNavigationModal } from 'app/features/explore/extensions/ConfirmNavigationModal'; import { ConfirmNavigationModal } from 'app/features/explore/extensions/ConfirmNavigationModal';
import { Alert, CombinedRule } from 'app/types/unified-alerting'; import { Alert, CombinedRule } from 'app/types/unified-alerting';
@ -21,13 +21,13 @@ export const AlertInstanceExtensionPoint = ({
}: AlertInstanceExtensionPointProps): ReactElement | null => { }: AlertInstanceExtensionPointProps): ReactElement | null => {
const [selectedExtension, setSelectedExtension] = useState<PluginExtensionLink | undefined>(); const [selectedExtension, setSelectedExtension] = useState<PluginExtensionLink | undefined>();
const context = useMemo(() => ({ instance, rule }), [instance, rule]); const context = useMemo(() => ({ instance, rule }), [instance, rule]);
const { extensions } = usePluginLinkExtensions({ context, extensionPointId, limitPerPlugin: 3 }); const { links } = usePluginLinks({ context, extensionPointId, limitPerPlugin: 3 });
if (extensions.length === 0) { if (links.length === 0) {
return null; return null;
} }
const menu = <AlertExtensionPointMenu extensions={extensions} onSelect={setSelectedExtension} />; const menu = <AlertExtensionPointMenu extensions={links} onSelect={setSelectedExtension} />;
return ( return (
<> <>
<Dropdown placement="bottom-start" overlay={menu}> <Dropdown placement="bottom-start" overlay={menu}>

View File

@ -2,7 +2,7 @@ import { within } from '@testing-library/react';
import { render, waitFor, screen, userEvent } from 'test/test-utils'; import { render, waitFor, screen, userEvent } from 'test/test-utils';
import { byText, byRole } from 'testing-library-selector'; import { byText, byRole } from 'testing-library-selector';
import { setPluginExtensionsHook } from '@grafana/runtime'; import { setPluginLinksHook } from '@grafana/runtime';
import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { setupMswServer } from 'app/features/alerting/unified/mockApi';
import { setFolderAccessControl } from 'app/features/alerting/unified/mocks/server/configure'; import { setFolderAccessControl } from 'app/features/alerting/unified/mocks/server/configure';
import { AlertManagerDataSourceJsonData } from 'app/plugins/datasource/alertmanager/types'; import { AlertManagerDataSourceJsonData } from 'app/plugins/datasource/alertmanager/types';
@ -66,8 +66,8 @@ const ELEMENTS = {
setupMswServer(); setupMswServer();
setupDataSources(mockDataSource({ type: DataSourceType.Prometheus, name: 'mimir-1' })); setupDataSources(mockDataSource({ type: DataSourceType.Prometheus, name: 'mimir-1' }));
setPluginExtensionsHook(() => ({ setPluginLinksHook(() => ({
extensions: [ links: [
mockPluginLinkExtension({ pluginId: 'grafana-slo-app', title: 'SLO dashboard', path: '/a/grafana-slo-app' }), mockPluginLinkExtension({ pluginId: 'grafana-slo-app', title: 'SLO dashboard', path: '/a/grafana-slo-app' }),
mockPluginLinkExtension({ mockPluginLinkExtension({
pluginId: 'grafana-asserts-app', pluginId: 'grafana-asserts-app',

View File

@ -404,8 +404,8 @@ const helpStyles = (theme: GrafanaTheme2) => ({
}); });
function usePluginsFilterStatus() { function usePluginsFilterStatus() {
const { extensions } = useAlertingHomePageExtensions(); const { components } = useAlertingHomePageExtensions();
return { pluginsFilterEnabled: extensions.length > 0 }; return { pluginsFilterEnabled: components.length > 0 };
} }
export default RulesFilter; export default RulesFilter;

View File

@ -2,7 +2,7 @@ import { produce } from 'immer';
import { render, screen, userEvent } from 'test/test-utils'; import { render, screen, userEvent } from 'test/test-utils';
import { byLabelText, byRole } from 'testing-library-selector'; import { byLabelText, byRole } from 'testing-library-selector';
import { config, setPluginExtensionsHook } from '@grafana/runtime'; import { config, setPluginLinksHook } from '@grafana/runtime';
import { contextSrv } from 'app/core/services/context_srv'; import { contextSrv } from 'app/core/services/context_srv';
import { RuleActionsButtons } from 'app/features/alerting/unified/components/rules/RuleActionsButtons'; import { RuleActionsButtons } from 'app/features/alerting/unified/components/rules/RuleActionsButtons';
import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { setupMswServer } from 'app/features/alerting/unified/mockApi';
@ -53,8 +53,8 @@ const getMenuContents = async () => {
return [...allMenuItems, ...allLinkItems]; return [...allMenuItems, ...allLinkItems];
}; };
setPluginExtensionsHook(() => ({ setPluginLinksHook(() => ({
extensions: [], links: [],
isLoading: false, isLoading: false,
})); }));

View File

@ -2,7 +2,7 @@ import { render } from 'test/test-utils';
import { byRole } from 'testing-library-selector'; import { byRole } from 'testing-library-selector';
import { PluginExtensionTypes } from '@grafana/data'; import { PluginExtensionTypes } from '@grafana/data';
import { usePluginLinkExtensions } from '@grafana/runtime'; import { usePluginLinks } from '@grafana/runtime';
import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { setupMswServer } from 'app/features/alerting/unified/mockApi';
import { useIsRuleEditable } from '../../hooks/useIsRuleEditable'; import { useIsRuleEditable } from '../../hooks/useIsRuleEditable';
@ -12,14 +12,14 @@ import { RuleDetails } from './RuleDetails';
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'), ...jest.requireActual('@grafana/runtime'),
usePluginLinkExtensions: jest.fn(), usePluginLinks: jest.fn(),
useReturnToPrevious: jest.fn(), useReturnToPrevious: jest.fn(),
})); }));
jest.mock('../../hooks/useIsRuleEditable'); jest.mock('../../hooks/useIsRuleEditable');
const mocks = { const mocks = {
usePluginLinkExtensionsMock: jest.mocked(usePluginLinkExtensions), usePluginLinksMock: jest.mocked(usePluginLinks),
useIsRuleEditable: jest.mocked(useIsRuleEditable), useIsRuleEditable: jest.mocked(useIsRuleEditable),
}; };
@ -37,8 +37,8 @@ beforeAll(() => {
}); });
beforeEach(() => { beforeEach(() => {
mocks.usePluginLinkExtensionsMock.mockReturnValue({ mocks.usePluginLinksMock.mockReturnValue({
extensions: [ links: [
{ {
pluginId: 'grafana-ml-app', pluginId: 'grafana-ml-app',
id: '1', id: '1',

View File

@ -4,7 +4,7 @@ import { times } from 'lodash';
import { byLabelText, byRole, byTestId } from 'testing-library-selector'; import { byLabelText, byRole, byTestId } from 'testing-library-selector';
import { PluginExtensionTypes } from '@grafana/data'; import { PluginExtensionTypes } from '@grafana/data';
import { usePluginLinkExtensions } from '@grafana/runtime'; import { usePluginLinks } from '@grafana/runtime';
import { CombinedRuleNamespace } from '../../../../../types/unified-alerting'; import { CombinedRuleNamespace } from '../../../../../types/unified-alerting';
import { GrafanaAlertState, PromAlertingRuleState } from '../../../../../types/unified-alerting-dto'; import { GrafanaAlertState, PromAlertingRuleState } from '../../../../../types/unified-alerting-dto';
@ -16,11 +16,11 @@ import { RuleDetailsMatchingInstances } from './RuleDetailsMatchingInstances';
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'), ...jest.requireActual('@grafana/runtime'),
getPluginLinkExtensions: jest.fn(), getPluginLinkExtensions: jest.fn(),
usePluginLinkExtensions: jest.fn(), usePluginLinks: jest.fn(),
})); }));
const mocks = { const mocks = {
usePluginLinkExtensionsMock: jest.mocked(usePluginLinkExtensions), usePluginLinksMock: jest.mocked(usePluginLinks),
}; };
const ui = { const ui = {
@ -43,8 +43,8 @@ const ui = {
describe('RuleDetailsMatchingInstances', () => { describe('RuleDetailsMatchingInstances', () => {
beforeEach(() => { beforeEach(() => {
mocks.usePluginLinkExtensionsMock.mockReturnValue({ mocks.usePluginLinksMock.mockReturnValue({
extensions: [ links: [
{ {
pluginId: 'grafana-ml-app', pluginId: 'grafana-ml-app',
id: '1', id: '1',

View File

@ -2,7 +2,7 @@ import { waitFor } from '@testing-library/react';
import { render } from 'test/test-utils'; import { render } from 'test/test-utils';
import { byRole } from 'testing-library-selector'; import { byRole } from 'testing-library-selector';
import { setPluginExtensionsHook } from '@grafana/runtime'; import { setPluginLinksHook } from '@grafana/runtime';
import { contextSrv } from 'app/core/services/context_srv'; import { contextSrv } from 'app/core/services/context_srv';
import { AccessControlAction } from 'app/types'; import { AccessControlAction } from 'app/types';
import { CombinedRuleNamespace } from 'app/types/unified-alerting'; import { CombinedRuleNamespace } from 'app/types/unified-alerting';
@ -20,8 +20,8 @@ const ui = {
cloudRulesHeading: byRole('heading', { name: 'Data source-managed' }), cloudRulesHeading: byRole('heading', { name: 'Data source-managed' }),
}; };
setPluginExtensionsHook(() => ({ setPluginLinksHook(() => ({
extensions: [], links: [],
isLoading: false, isLoading: false,
})); }));

View File

@ -1,6 +1,6 @@
import { render, screen } from 'test/test-utils'; import { render, screen } from 'test/test-utils';
import { setPluginExtensionsHook } from '@grafana/runtime'; import { setPluginLinksHook } from '@grafana/runtime';
import { RuleListStateView } from 'app/features/alerting/unified/components/rules/RuleListStateView'; import { RuleListStateView } from 'app/features/alerting/unified/components/rules/RuleListStateView';
import { import {
mockCombinedRule, mockCombinedRule,
@ -10,8 +10,8 @@ import {
} from 'app/features/alerting/unified/mocks'; } from 'app/features/alerting/unified/mocks';
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
setPluginExtensionsHook(() => ({ setPluginLinksHook(() => ({
extensions: [], links: [],
isLoading: false, isLoading: false,
})); }));

View File

@ -1,7 +1,7 @@
import { render, userEvent, screen } from 'test/test-utils'; import { render, userEvent, screen } from 'test/test-utils';
import { byRole } from 'testing-library-selector'; import { byRole } from 'testing-library-selector';
import { setPluginExtensionsHook } from '@grafana/runtime'; import { setPluginLinksHook } from '@grafana/runtime';
import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { setupMswServer } from 'app/features/alerting/unified/mockApi';
import { AlertRuleAction, useAlertRuleAbility } from '../../hooks/useAbilities'; import { AlertRuleAction, useAlertRuleAbility } from '../../hooks/useAbilities';
@ -15,8 +15,8 @@ const mocks = {
useAlertRuleAbility: jest.mocked(useAlertRuleAbility), useAlertRuleAbility: jest.mocked(useAlertRuleAbility),
}; };
setPluginExtensionsHook(() => ({ setPluginLinksHook(() => ({
extensions: [], links: [],
isLoading: false, isLoading: false,
})); }));

View File

@ -9,9 +9,9 @@ import { useAlertingHomePageExtensions } from '../plugins/useAlertingHomePageExt
export function PluginIntegrations() { export function PluginIntegrations() {
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const { extensions } = useAlertingHomePageExtensions(); const { components } = useAlertingHomePageExtensions();
if (extensions.length === 0) { if (components.length === 0) {
return null; return null;
} }
@ -21,9 +21,9 @@ export function PluginIntegrations() {
Speed up your alerts creation now by using one of our tailored apps Speed up your alerts creation now by using one of our tailored apps
</Text> </Text>
<Stack gap={2} wrap="wrap" direction="row"> <Stack gap={2} wrap="wrap" direction="row">
{extensions.map((extension) => ( {components.map((Component, i) => (
<div key={extension.id} className={styles.box}> <div key={i} className={styles.box}>
<extension.component /> <Component />
</div> </div>
))} ))}
</Stack> </Stack>

View File

@ -1,8 +1,8 @@
import { PluginExtensionPoints } from '@grafana/data'; import { PluginExtensionPoints } from '@grafana/data';
import { usePluginComponentExtensions } from '@grafana/runtime'; import { usePluginComponents } from '@grafana/runtime';
export function useAlertingHomePageExtensions() { export function useAlertingHomePageExtensions() {
return usePluginComponentExtensions({ return usePluginComponents({
extensionPointId: PluginExtensionPoints.AlertingHomePage, extensionPointId: PluginExtensionPoints.AlertingHomePage,
limitPerPlugin: 1, limitPerPlugin: 1,
}); });

View File

@ -1,7 +1,7 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { PluginExtensionPoints } from '@grafana/data'; import { PluginExtensionPoints } from '@grafana/data';
import { usePluginLinkExtensions } from '@grafana/runtime'; import { usePluginLinks } from '@grafana/runtime';
import { CombinedRule } from 'app/types/unified-alerting'; import { CombinedRule } from 'app/types/unified-alerting';
import { PromRuleType } from 'app/types/unified-alerting-dto'; import { PromRuleType } from 'app/types/unified-alerting-dto';
@ -23,7 +23,7 @@ export interface RecordingRuleExtensionContext extends BaseRuleExtensionContext
export function useRulePluginLinkExtension(rule: CombinedRule) { export function useRulePluginLinkExtension(rule: CombinedRule) {
const ruleExtensionPoint = useRuleExtensionPoint(rule); const ruleExtensionPoint = useRuleExtensionPoint(rule);
const { extensions } = usePluginLinkExtensions(ruleExtensionPoint); const { links } = usePluginLinks(ruleExtensionPoint);
const ruleOrigin = getRulePluginOrigin(rule); const ruleOrigin = getRulePluginOrigin(rule);
const ruleType = rule.promRule?.type; const ruleType = rule.promRule?.type;
@ -33,7 +33,7 @@ export function useRulePluginLinkExtension(rule: CombinedRule) {
const { pluginId } = ruleOrigin; const { pluginId } = ruleOrigin;
return extensions.filter((extension) => extension.pluginId === pluginId); return links.filter((link) => link.pluginId === pluginId);
} }
export interface PluginRuleExtensionParam { export interface PluginRuleExtensionParam {

View File

@ -1,5 +1,5 @@
import { PluginMeta, PluginType } from '@grafana/data'; import { PluginMeta, PluginType } from '@grafana/data';
import { setPluginExtensionsHook } from '@grafana/runtime'; import { setPluginComponentsHook, setPluginExtensionsHook } from '@grafana/runtime';
import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges'; import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges';
import { mockPluginLinkExtension } from '../mocks'; import { mockPluginLinkExtension } from '../mocks';
@ -15,6 +15,10 @@ export function setupPluginsExtensionsHook() {
), ),
isLoading: false, isLoading: false,
})); }));
setPluginComponentsHook(() => ({
components: [],
isLoading: false,
}));
} }
export const plugins: PluginMeta[] = [ export const plugins: PluginMeta[] = [

View File

@ -1,7 +1,7 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { PluginExtensionCommandPaletteContext, PluginExtensionPoints } from '@grafana/data'; import { PluginExtensionCommandPaletteContext, PluginExtensionPoints } from '@grafana/data';
import { usePluginLinkExtensions } from '@grafana/runtime'; import { usePluginLinks } from '@grafana/runtime';
import { CommandPaletteAction } from '../types'; import { CommandPaletteAction } from '../types';
import { EXTENSIONS_PRIORITY } from '../values'; import { EXTENSIONS_PRIORITY } from '../values';
@ -10,20 +10,20 @@ import { EXTENSIONS_PRIORITY } from '../values';
const context: PluginExtensionCommandPaletteContext = {}; const context: PluginExtensionCommandPaletteContext = {};
export default function useExtensionActions(): CommandPaletteAction[] { export default function useExtensionActions(): CommandPaletteAction[] {
const { extensions } = usePluginLinkExtensions({ const { links } = usePluginLinks({
extensionPointId: PluginExtensionPoints.CommandPalette, extensionPointId: PluginExtensionPoints.CommandPalette,
context, context,
limitPerPlugin: 3, limitPerPlugin: 3,
}); });
return useMemo(() => { return useMemo(() => {
return extensions.map((extension) => ({ return links.map((link) => ({
section: extension.category ?? 'Extensions', section: link.category ?? 'Extensions',
priority: EXTENSIONS_PRIORITY, priority: EXTENSIONS_PRIORITY,
id: extension.id, id: link.id,
name: extension.title, name: link.title,
target: extension.path, target: link.path,
perform: () => extension.onClick && extension.onClick(), perform: () => link.onClick && link.onClick(),
})); }));
}, [extensions]); }, [links]);
} }

View File

@ -5,7 +5,7 @@ import { byTestId } from 'testing-library-selector';
import { DataSourceApi } from '@grafana/data'; import { DataSourceApi } from '@grafana/data';
import { PromOptions, PrometheusDatasource } from '@grafana/prometheus'; import { PromOptions, PrometheusDatasource } from '@grafana/prometheus';
import { locationService, setDataSourceSrv, setPluginExtensionsHook } from '@grafana/runtime'; import { locationService, setDataSourceSrv, setPluginLinksHook } from '@grafana/runtime';
import { backendSrv } from 'app/core/services/backend_srv'; import { backendSrv } from 'app/core/services/backend_srv';
import * as ruler from 'app/features/alerting/unified/api/ruler'; import * as ruler from 'app/features/alerting/unified/api/ruler';
import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons'; import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons';
@ -50,8 +50,8 @@ jest.spyOn(ruleActionButtons, 'matchesWidth').mockReturnValue(false);
jest.spyOn(ruler, 'rulerUrlBuilder'); jest.spyOn(ruler, 'rulerUrlBuilder');
jest.spyOn(alertingAbilities, 'useAlertRuleAbility'); jest.spyOn(alertingAbilities, 'useAlertRuleAbility');
setPluginExtensionsHook(() => ({ setPluginLinksHook(() => ({
extensions: [], links: [],
isLoading: false, isLoading: false,
})); }));

View File

@ -63,7 +63,7 @@ jest.mock('app/core/core', () => ({
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'), ...jest.requireActual('@grafana/runtime'),
getPluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), getPluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), usePluginLinks: jest.fn().mockReturnValue({ links: [] }),
})); }));
function getTestDashboard(overrides?: Partial<Dashboard>, metaOverrides?: Partial<DashboardMeta>): DashboardModel { function getTestDashboard(overrides?: Partial<Dashboard>, metaOverrides?: Partial<DashboardMeta>): DashboardModel {

View File

@ -7,7 +7,7 @@ import {
PluginExtensionPoints, PluginExtensionPoints,
getTimeZone, getTimeZone,
} from '@grafana/data'; } from '@grafana/data';
import { usePluginLinkExtensions } from '@grafana/runtime'; import { usePluginLinks } from '@grafana/runtime';
import { getPanelStateForModel } from 'app/features/panel/state/selectors'; import { getPanelStateForModel } from 'app/features/panel/state/selectors';
import { useSelector } from 'app/types'; import { useSelector } from 'app/types';
@ -29,15 +29,15 @@ export function PanelHeaderMenuProvider({ panel, dashboard, loadingState, childr
const [items, setItems] = useState<PanelMenuItem[]>([]); const [items, setItems] = useState<PanelMenuItem[]>([]);
const angularComponent = useSelector((state) => getPanelStateForModel(state, panel)?.angularComponent); const angularComponent = useSelector((state) => getPanelStateForModel(state, panel)?.angularComponent);
const context = useMemo(() => createExtensionContext(panel, dashboard), [panel, dashboard]); const context = useMemo(() => createExtensionContext(panel, dashboard), [panel, dashboard]);
const { extensions } = usePluginLinkExtensions({ const { links } = usePluginLinks({
extensionPointId: PluginExtensionPoints.DashboardPanelMenu, extensionPointId: PluginExtensionPoints.DashboardPanelMenu,
context, context,
limitPerPlugin: 3, limitPerPlugin: 3,
}); });
useEffect(() => { useEffect(() => {
setItems(getPanelMenu(dashboard, panel, extensions, angularComponent)); setItems(getPanelMenu(dashboard, panel, links, angularComponent));
}, [dashboard, panel, angularComponent, loadingState, setItems, extensions]); }, [dashboard, panel, angularComponent, loadingState, setItems, links]);
return children({ items }); return children({ items });
} }

View File

@ -1,7 +1,7 @@
import { Store } from 'redux'; import { Store } from 'redux';
import { PanelMenuItem, PluginExtensionLink, PluginExtensionTypes } from '@grafana/data'; import { PanelMenuItem, PluginExtensionLink, PluginExtensionTypes } from '@grafana/data';
import { AngularComponent, usePluginLinkExtensions } from '@grafana/runtime'; import { AngularComponent, usePluginLinks } from '@grafana/runtime';
import config from 'app/core/config'; import config from 'app/core/config';
import { grantUserPermissions } from 'app/features/alerting/unified/mocks'; import { grantUserPermissions } from 'app/features/alerting/unified/mocks';
import * as actions from 'app/features/explore/state/main'; import * as actions from 'app/features/explore/state/main';
@ -22,16 +22,16 @@ jest.mock('app/core/services/context_srv', () => ({
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'), ...jest.requireActual('@grafana/runtime'),
setPluginExtensionsHook: jest.fn(), setPluginLinksHook: jest.fn(),
usePluginLinkExtensions: jest.fn(), usePluginLinks: jest.fn(),
})); }));
const usePluginLinkExtensionsMock = jest.mocked(usePluginLinkExtensions); const usePluginLinksMock = jest.mocked(usePluginLinks);
describe('getPanelMenu()', () => { describe('getPanelMenu()', () => {
beforeEach(() => { beforeEach(() => {
usePluginLinkExtensionsMock.mockRestore(); usePluginLinksMock.mockRestore();
usePluginLinkExtensionsMock.mockReturnValue({ extensions: [], isLoading: false }); usePluginLinksMock.mockReturnValue({ links: [], isLoading: false });
grantUserPermissions([AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleUpdate]); grantUserPermissions([AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleUpdate]);
config.unifiedAlertingEnabled = false; config.unifiedAlertingEnabled = false;
}); });

View File

@ -12,7 +12,7 @@ import {
store, store,
} from '@grafana/data'; } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { usePluginLinkExtensions } from '@grafana/runtime'; import { usePluginLinks } from '@grafana/runtime';
import { configureStore } from 'app/store/configureStore'; import { configureStore } from 'app/store/configureStore';
import { ContentOutlineContextProvider } from './ContentOutline/ContentOutlineContext'; import { ContentOutlineContextProvider } from './ContentOutline/ContentOutlineContext';
@ -124,7 +124,7 @@ jest.mock('app/core/core', () => ({
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'), ...jest.requireActual('@grafana/runtime'),
usePluginLinkExtensions: jest.fn(() => ({ extensions: [] })), usePluginLinks: jest.fn(() => ({ links: [] })),
})); }));
// for the AutoSizer component to have a width // for the AutoSizer component to have a width
@ -138,7 +138,7 @@ jest.mock('react-virtualized-auto-sizer', () => {
}); });
}); });
const usePluginLinkExtensionsMock = jest.mocked(usePluginLinkExtensions); const usePluginLinksMock = jest.mocked(usePluginLinks);
const setup = (overrideProps?: Partial<Props>) => { const setup = (overrideProps?: Partial<Props>) => {
const store = configureStore({ const store = configureStore({
@ -180,8 +180,8 @@ describe('Explore', () => {
}); });
it('should render toolbar extension point if extensions is available', async () => { it('should render toolbar extension point if extensions is available', async () => {
usePluginLinkExtensionsMock.mockReturnValueOnce({ usePluginLinksMock.mockReturnValueOnce({
extensions: [ links: [
{ {
id: '1', id: '1',
pluginId: 'grafana', pluginId: 'grafana',

View File

@ -4,7 +4,7 @@ import { ReactNode } from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { PluginExtensionPoints, PluginExtensionTypes } from '@grafana/data'; import { PluginExtensionPoints, PluginExtensionTypes } from '@grafana/data';
import { usePluginLinkExtensions } from '@grafana/runtime'; import { usePluginLinks } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema'; import { DataQuery } from '@grafana/schema';
import { contextSrv } from 'app/core/services/context_srv'; import { contextSrv } from 'app/core/services/context_srv';
import { configureStore } from 'app/store/configureStore'; import { configureStore } from 'app/store/configureStore';
@ -16,13 +16,13 @@ import { ToolbarExtensionPoint } from './ToolbarExtensionPoint';
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'), ...jest.requireActual('@grafana/runtime'),
usePluginLinkExtensions: jest.fn(), usePluginLinks: jest.fn(),
})); }));
jest.mock('app/core/services/context_srv'); jest.mock('app/core/services/context_srv');
const contextSrvMock = jest.mocked(contextSrv); const contextSrvMock = jest.mocked(contextSrv);
const usePluginLinkExtensionsMock = jest.mocked(usePluginLinkExtensions); const usePluginLinksMock = jest.mocked(usePluginLinks);
type storeOptions = { type storeOptions = {
targets: DataQuery[]; targets: DataQuery[];
@ -54,8 +54,8 @@ function renderWithExploreStore(
describe('ToolbarExtensionPoint', () => { describe('ToolbarExtensionPoint', () => {
describe('with extension points', () => { describe('with extension points', () => {
beforeAll(() => { beforeAll(() => {
usePluginLinkExtensionsMock.mockReturnValue({ usePluginLinksMock.mockReturnValue({
extensions: [ links: [
{ {
pluginId: 'grafana', pluginId: 'grafana',
id: '1', id: '1',
@ -100,10 +100,10 @@ describe('ToolbarExtensionPoint', () => {
await userEvent.click(screen.getByRole('button', { name: 'Add' })); await userEvent.click(screen.getByRole('button', { name: 'Add' }));
await userEvent.click(screen.getByRole('menuitem', { name: 'Add to dashboard' })); await userEvent.click(screen.getByRole('menuitem', { name: 'Add to dashboard' }));
const { extensions } = usePluginLinkExtensionsMock({ const { links } = usePluginLinksMock({
extensionPointId: PluginExtensionPoints.ExploreToolbarAction, extensionPointId: PluginExtensionPoints.ExploreToolbarAction,
}); });
const [extension] = extensions; const [extension] = links;
expect(jest.mocked(extension.onClick)).toBeCalledTimes(1); expect(jest.mocked(extension.onClick)).toBeCalledTimes(1);
}); });
@ -128,7 +128,7 @@ describe('ToolbarExtensionPoint', () => {
data, data,
}); });
const [options] = usePluginLinkExtensionsMock.mock.calls[0]; const [options] = usePluginLinksMock.mock.calls[0];
const { context } = options; const { context } = options;
expect(context).toEqual({ expect(context).toEqual({
@ -153,7 +153,7 @@ describe('ToolbarExtensionPoint', () => {
data, data,
}); });
const [options] = usePluginLinkExtensionsMock.mock.calls[0]; const [options] = usePluginLinksMock.mock.calls[0];
const { context } = options; const { context } = options;
expect(context).toHaveProperty('timeZone', 'browser'); expect(context).toHaveProperty('timeZone', 'browser');
@ -162,7 +162,7 @@ describe('ToolbarExtensionPoint', () => {
it('should correct extension point id when fetching extensions', async () => { it('should correct extension point id when fetching extensions', async () => {
renderWithExploreStore(<ToolbarExtensionPoint exploreId="left" timeZone="browser" />); renderWithExploreStore(<ToolbarExtensionPoint exploreId="left" timeZone="browser" />);
const [options] = usePluginLinkExtensionsMock.mock.calls[0]; const [options] = usePluginLinksMock.mock.calls[0];
const { extensionPointId } = options; const { extensionPointId } = options;
expect(extensionPointId).toBe(PluginExtensionPoints.ExploreToolbarAction); expect(extensionPointId).toBe(PluginExtensionPoints.ExploreToolbarAction);
@ -171,8 +171,8 @@ describe('ToolbarExtensionPoint', () => {
describe('with extension points without categories', () => { describe('with extension points without categories', () => {
beforeAll(() => { beforeAll(() => {
usePluginLinkExtensionsMock.mockReturnValue({ usePluginLinksMock.mockReturnValue({
extensions: [ links: [
{ {
pluginId: 'grafana', pluginId: 'grafana',
id: '1', id: '1',
@ -215,7 +215,7 @@ describe('ToolbarExtensionPoint', () => {
describe('without extension points', () => { describe('without extension points', () => {
beforeAll(() => { beforeAll(() => {
contextSrvMock.hasPermission.mockReturnValue(true); contextSrvMock.hasPermission.mockReturnValue(true);
usePluginLinkExtensionsMock.mockReturnValue({ extensions: [], isLoading: false }); usePluginLinksMock.mockReturnValue({ links: [], isLoading: false });
}); });
it('should render "add to dashboard" action button if one pane is visible', async () => { it('should render "add to dashboard" action button if one pane is visible', async () => {
@ -233,7 +233,7 @@ describe('ToolbarExtensionPoint', () => {
describe('with insufficient permissions', () => { describe('with insufficient permissions', () => {
beforeAll(() => { beforeAll(() => {
contextSrvMock.hasPermission.mockReturnValue(false); contextSrvMock.hasPermission.mockReturnValue(false);
usePluginLinkExtensionsMock.mockReturnValue({ extensions: [], isLoading: false }); usePluginLinksMock.mockReturnValue({ links: [], isLoading: false });
}); });
it('should not render "add to dashboard" action button', async () => { it('should not render "add to dashboard" action button', async () => {

View File

@ -1,7 +1,7 @@
import { lazy, ReactElement, Suspense, useMemo, useState } from 'react'; import { lazy, ReactElement, Suspense, useMemo, useState } from 'react';
import { type PluginExtensionLink, PluginExtensionPoints, RawTimeRange, getTimeZone } from '@grafana/data'; import { type PluginExtensionLink, PluginExtensionPoints, RawTimeRange, getTimeZone } from '@grafana/data';
import { config, usePluginLinkExtensions } from '@grafana/runtime'; import { config, usePluginLinks } from '@grafana/runtime';
import { DataQuery, TimeZone } from '@grafana/schema'; import { DataQuery, TimeZone } from '@grafana/schema';
import { Dropdown, ToolbarButton } from '@grafana/ui'; import { Dropdown, ToolbarButton } from '@grafana/ui';
import { contextSrv } from 'app/core/services/context_srv'; import { contextSrv } from 'app/core/services/context_srv';
@ -26,7 +26,7 @@ export function ToolbarExtensionPoint(props: Props): ReactElement | null {
const [selectedExtension, setSelectedExtension] = useState<PluginExtensionLink | undefined>(); const [selectedExtension, setSelectedExtension] = useState<PluginExtensionLink | undefined>();
const [isOpen, setIsOpen] = useState<boolean>(false); const [isOpen, setIsOpen] = useState<boolean>(false);
const context = useExtensionPointContext(props); const context = useExtensionPointContext(props);
const { extensions } = usePluginLinkExtensions({ const { links } = usePluginLinks({
extensionPointId: PluginExtensionPoints.ExploreToolbarAction, extensionPointId: PluginExtensionPoints.ExploreToolbarAction,
context: context, context: context,
limitPerPlugin: 3, limitPerPlugin: 3,
@ -36,7 +36,7 @@ export function ToolbarExtensionPoint(props: Props): ReactElement | null {
// If we only have the explore core extension point registered we show the old way of // If we only have the explore core extension point registered we show the old way of
// adding a query to a dashboard. // adding a query to a dashboard.
if (extensions.length <= 1) { if (links.length <= 1) {
const canAddPanelToDashboard = const canAddPanelToDashboard =
contextSrv.hasPermission(AccessControlAction.DashboardsCreate) || contextSrv.hasPermission(AccessControlAction.DashboardsCreate) ||
contextSrv.hasPermission(AccessControlAction.DashboardsWrite); contextSrv.hasPermission(AccessControlAction.DashboardsWrite);
@ -52,7 +52,7 @@ export function ToolbarExtensionPoint(props: Props): ReactElement | null {
); );
} }
const menu = <ToolbarExtensionPointMenu extensions={extensions} onSelect={setSelectedExtension} />; const menu = <ToolbarExtensionPointMenu extensions={links} onSelect={setSelectedExtension} />;
return ( return (
<> <>

View File

@ -21,12 +21,12 @@ import {
locationService, locationService,
HistoryWrapper, HistoryWrapper,
LocationService, LocationService,
setPluginExtensionsHook,
setBackendSrv, setBackendSrv,
getBackendSrv, getBackendSrv,
getDataSourceSrv, getDataSourceSrv,
getEchoSrv, getEchoSrv,
setLocationService, setLocationService,
setPluginLinksHook,
} from '@grafana/runtime'; } from '@grafana/runtime';
import { DataSourceRef } from '@grafana/schema'; import { DataSourceRef } from '@grafana/schema';
import { GrafanaContext } from 'app/core/context/GrafanaContext'; import { GrafanaContext } from 'app/core/context/GrafanaContext';
@ -89,7 +89,7 @@ export function setupExplore(options?: SetupOptions): {
request: jest.fn().mockRejectedValue(undefined), request: jest.fn().mockRejectedValue(undefined),
}); });
setPluginExtensionsHook(() => ({ extensions: [], isLoading: false })); setPluginLinksHook(() => ({ links: [], isLoading: false }));
// Clear this up otherwise it persists data source selection // Clear this up otherwise it persists data source selection
// TODO: probably add test for that too // TODO: probably add test for that too

View File

@ -11,7 +11,7 @@ jest.mock('@grafana/runtime', () => ({
useChromeHeaderHeight: jest.fn(), useChromeHeaderHeight: jest.fn(),
getBackendSrv: () => ({ get: getMock }), getBackendSrv: () => ({ get: getMock }),
getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }), getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), usePluginLinks: jest.fn().mockReturnValue({ links: [] }),
})); }));
describe('Dashboard reload', () => { describe('Dashboard reload', () => {

View File

@ -12,7 +12,7 @@ jest.mock('@grafana/runtime', () => ({
useChromeHeaderHeight: jest.fn(), useChromeHeaderHeight: jest.fn(),
getBackendSrv: () => ({ get: getMock }), getBackendSrv: () => ({ get: getMock }),
getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }), getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), usePluginLinks: jest.fn().mockReturnValue({ links: [] }),
})); }));
const runTest = async (passScopes: boolean, kubernetesApi: boolean) => { const runTest = async (passScopes: boolean, kubernetesApi: boolean) => {

View File

@ -28,7 +28,7 @@ jest.mock('@grafana/runtime', () => ({
useChromeHeaderHeight: jest.fn(), useChromeHeaderHeight: jest.fn(),
getBackendSrv: () => ({ get: getMock }), getBackendSrv: () => ({ get: getMock }),
getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }), getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), usePluginLinks: jest.fn().mockReturnValue({ links: [] }),
})); }));
describe('Dashboards list', () => { describe('Dashboards list', () => {

View File

@ -11,7 +11,7 @@ jest.mock('@grafana/runtime', () => ({
useChromeHeaderHeight: jest.fn(), useChromeHeaderHeight: jest.fn(),
getBackendSrv: () => ({ get: getMock }), getBackendSrv: () => ({ get: getMock }),
getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }), getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), usePluginLinks: jest.fn().mockReturnValue({ links: [] }),
})); }));
describe('Feature flag off', () => { describe('Feature flag off', () => {

View File

@ -15,7 +15,7 @@ jest.mock('@grafana/runtime', () => ({
useChromeHeaderHeight: jest.fn(), useChromeHeaderHeight: jest.fn(),
getBackendSrv: () => ({ get: getMock }), getBackendSrv: () => ({ get: getMock }),
getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }), getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), usePluginLinks: jest.fn().mockReturnValue({ links: [] }),
})); }));
describe('Selector', () => { describe('Selector', () => {

View File

@ -46,7 +46,7 @@ jest.mock('@grafana/runtime', () => ({
useChromeHeaderHeight: jest.fn(), useChromeHeaderHeight: jest.fn(),
getBackendSrv: () => ({ get: getMock }), getBackendSrv: () => ({ get: getMock }),
getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }), getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), usePluginLinks: jest.fn().mockReturnValue({ links: [] }),
})); }));
describe('Tree', () => { describe('Tree', () => {

View File

@ -14,7 +14,7 @@ jest.mock('@grafana/runtime', () => ({
useChromeHeaderHeight: jest.fn(), useChromeHeaderHeight: jest.fn(),
getBackendSrv: () => ({ get: getMock }), getBackendSrv: () => ({ get: getMock }),
getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }), getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), usePluginLinks: jest.fn().mockReturnValue({ links: [] }),
})); }));
describe('View mode', () => { describe('View mode', () => {

View File

@ -4,7 +4,7 @@ import { Provider } from 'react-redux';
import { byRole, byText } from 'testing-library-selector'; import { byRole, byText } from 'testing-library-selector';
import { FieldConfigSource, getDefaultTimeRange, LoadingState, PanelProps, PluginExtensionTypes } from '@grafana/data'; import { FieldConfigSource, getDefaultTimeRange, LoadingState, PanelProps, PluginExtensionTypes } from '@grafana/data';
import { TimeRangeUpdatedEvent, usePluginLinkExtensions } from '@grafana/runtime'; import { TimeRangeUpdatedEvent, usePluginLinks } from '@grafana/runtime';
import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { setupMswServer } from 'app/features/alerting/unified/mockApi';
import { mockPromRulesApiResponse } from 'app/features/alerting/unified/mocks/grafanaRulerApi'; import { mockPromRulesApiResponse } from 'app/features/alerting/unified/mocks/grafanaRulerApi';
import { mockRulerRulesApiResponse } from 'app/features/alerting/unified/mocks/rulerApi'; import { mockRulerRulesApiResponse } from 'app/features/alerting/unified/mocks/rulerApi';
@ -56,12 +56,12 @@ const grafanaRuleMock = {
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'), ...jest.requireActual('@grafana/runtime'),
usePluginLinkExtensions: jest.fn(), usePluginLinks: jest.fn(),
})); }));
jest.mock('app/features/alerting/unified/api/alertmanager'); jest.mock('app/features/alerting/unified/api/alertmanager');
const mocks = { const mocks = {
usePluginLinkExtensionsMock: jest.mocked(usePluginLinkExtensions), usePluginLinksMock: jest.mocked(usePluginLinks),
}; };
const fakeResponse: PromRulesResponse = { const fakeResponse: PromRulesResponse = {
@ -84,8 +84,8 @@ beforeEach(() => {
mockRulerRulesApiResponse(server, 'grafana', { mockRulerRulesApiResponse(server, 'grafana', {
'folder-one': [{ name: 'group1', interval: '20s', rules: [originRule] }], 'folder-one': [{ name: 'group1', interval: '20s', rules: [originRule] }],
}); });
mocks.usePluginLinkExtensionsMock.mockReturnValue({ mocks.usePluginLinksMock.mockReturnValue({
extensions: [ links: [
{ {
pluginId: 'grafana-ml-app', pluginId: 'grafana-ml-app',
id: '1', id: '1',