mirror of
https://github.com/grafana/grafana.git
synced 2025-02-11 08:05:43 -06:00
DashboardScene: Panel menu tracking, adding explore menu action and unit tests (#74867)
* DashboardScene: Panel menu updates, adding explore action * DashboardScene: Panel menu updates, adding explore action * Fix test * Update test
This commit is contained in:
parent
3fdf96d241
commit
ed3fb71f7b
@ -339,7 +339,7 @@ abstract class DataSourceApi<
|
|||||||
|
|
||||||
getVersion?(optionalOptions?: any): Promise<string>;
|
getVersion?(optionalOptions?: any): Promise<string>;
|
||||||
|
|
||||||
interpolateVariablesInQueries?(queries: TQuery[], scopedVars: ScopedVars | {}): TQuery[];
|
interpolateVariablesInQueries?(queries: TQuery[], scopedVars: ScopedVars): TQuery[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An annotation processor allows explicit control for how annotations are managed.
|
* An annotation processor allows explicit control for how annotations are managed.
|
||||||
|
@ -11,7 +11,6 @@ import { ShareModal } from 'app/features/dashboard/components/ShareModal';
|
|||||||
import { DashboardModel } from 'app/features/dashboard/state';
|
import { DashboardModel } from 'app/features/dashboard/state';
|
||||||
|
|
||||||
import { getTimeSrv } from '../../features/dashboard/services/TimeSrv';
|
import { getTimeSrv } from '../../features/dashboard/services/TimeSrv';
|
||||||
import { getDatasourceSrv } from '../../features/plugins/datasource_srv';
|
|
||||||
import {
|
import {
|
||||||
RemovePanelEvent,
|
RemovePanelEvent,
|
||||||
ShiftTimeEvent,
|
ShiftTimeEvent,
|
||||||
@ -261,8 +260,9 @@ export class KeybindingSrv {
|
|||||||
this.bindWithPanelId('p x', async (panelId) => {
|
this.bindWithPanelId('p x', async (panelId) => {
|
||||||
const panel = dashboard.getPanelById(panelId)!;
|
const panel = dashboard.getPanelById(panelId)!;
|
||||||
const url = await getExploreUrl({
|
const url = await getExploreUrl({
|
||||||
panel,
|
queries: panel.targets,
|
||||||
datasourceSrv: getDatasourceSrv(),
|
dsRef: panel.datasource,
|
||||||
|
scopedVars: panel.scopedVars,
|
||||||
timeRange: getTimeSrv().timeRange(),
|
timeRange: getTimeSrv().timeRange(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -71,31 +71,20 @@ describe('state functions', () => {
|
|||||||
|
|
||||||
describe('getExploreUrl', () => {
|
describe('getExploreUrl', () => {
|
||||||
const args = {
|
const args = {
|
||||||
panel: {
|
queries: [
|
||||||
getSavedId: () => 1,
|
{ refId: 'A', expr: 'query1', legendFormat: 'legendFormat1' },
|
||||||
targets: [
|
{ refId: 'B', expr: 'query2', datasource: { type: '__expr__', uid: '__expr__' } },
|
||||||
{ refId: 'A', expr: 'query1', legendFormat: 'legendFormat1' },
|
],
|
||||||
{ refId: 'B', expr: 'query2', datasource: { type: '__expr__', uid: '__expr__' } },
|
dsRef: {
|
||||||
],
|
uid: 'ds1',
|
||||||
},
|
|
||||||
datasourceSrv: {
|
|
||||||
get() {
|
|
||||||
return {
|
|
||||||
getRef: jest.fn(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getDataSourceById: jest.fn(),
|
|
||||||
},
|
},
|
||||||
timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1h', to: 'now' } },
|
timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1h', to: 'now' } },
|
||||||
} as unknown as GetExploreUrlArguments;
|
} as unknown as GetExploreUrlArguments;
|
||||||
it('should use raw range in explore url', async () => {
|
it('should use raw range in explore url', async () => {
|
||||||
expect(getExploreUrl(args).then((data) => expect(data).toMatch(/from%22:%22now-1h%22,%22to%22:%22now/g)));
|
expect(await getExploreUrl(args)).toMatch(/from%22:%22now-1h%22,%22to%22:%22now/g);
|
||||||
});
|
});
|
||||||
it('should omit legendFormat in explore url', () => {
|
it('should omit expression target in explore url', async () => {
|
||||||
expect(getExploreUrl(args).then((data) => expect(data).not.toMatch(/legendFormat1/g)));
|
expect(await getExploreUrl(args)).not.toMatch(/__expr__/g);
|
||||||
});
|
|
||||||
it('should omit expression target in explore url', () => {
|
|
||||||
expect(getExploreUrl(args).then((data) => expect(data).not.toMatch(/__expr__/g)));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { nanoid } from '@reduxjs/toolkit';
|
import { nanoid } from '@reduxjs/toolkit';
|
||||||
import { omit } from 'lodash';
|
|
||||||
import { Unsubscribable } from 'rxjs';
|
import { Unsubscribable } from 'rxjs';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
@ -17,15 +16,15 @@ import {
|
|||||||
LogsSortOrder,
|
LogsSortOrder,
|
||||||
rangeUtil,
|
rangeUtil,
|
||||||
RawTimeRange,
|
RawTimeRange,
|
||||||
|
ScopedVars,
|
||||||
TimeRange,
|
TimeRange,
|
||||||
TimeZone,
|
TimeZone,
|
||||||
toURLRange,
|
toURLRange,
|
||||||
urlUtil,
|
urlUtil,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { DataSourceSrv, getDataSourceSrv } from '@grafana/runtime';
|
import { getDataSourceSrv } from '@grafana/runtime';
|
||||||
import { RefreshPicker } from '@grafana/ui';
|
import { RefreshPicker } from '@grafana/ui';
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
import { PanelModel } from 'app/features/dashboard/state';
|
|
||||||
import { ExpressionDatasourceUID } from 'app/features/expressions/types';
|
import { ExpressionDatasourceUID } from 'app/features/expressions/types';
|
||||||
import { QueryOptions, QueryTransaction } from 'app/types/explore';
|
import { QueryOptions, QueryTransaction } from 'app/types/explore';
|
||||||
|
|
||||||
@ -47,10 +46,10 @@ export const setLastUsedDatasourceUID = (orgId: number, datasourceUID: string) =
|
|||||||
store.setObject(lastUsedDatasourceKeyForOrgId(orgId), datasourceUID);
|
store.setObject(lastUsedDatasourceKeyForOrgId(orgId), datasourceUID);
|
||||||
|
|
||||||
export interface GetExploreUrlArguments {
|
export interface GetExploreUrlArguments {
|
||||||
panel: PanelModel;
|
queries: DataQuery[];
|
||||||
/** Datasource service to query other datasources in case the panel datasource is mixed */
|
dsRef: DataSourceRef | null | undefined;
|
||||||
datasourceSrv: DataSourceSrv;
|
|
||||||
timeRange: TimeRange;
|
timeRange: TimeRange;
|
||||||
|
scopedVars: ScopedVars | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateExploreId() {
|
export function generateExploreId() {
|
||||||
@ -61,27 +60,23 @@ export function generateExploreId() {
|
|||||||
* Returns an Explore-URL that contains a panel's queries and the dashboard time range.
|
* Returns an Explore-URL that contains a panel's queries and the dashboard time range.
|
||||||
*/
|
*/
|
||||||
export async function getExploreUrl(args: GetExploreUrlArguments): Promise<string | undefined> {
|
export async function getExploreUrl(args: GetExploreUrlArguments): Promise<string | undefined> {
|
||||||
const { panel, datasourceSrv, timeRange } = args;
|
const { queries, dsRef, timeRange, scopedVars } = args;
|
||||||
let exploreDatasource = await datasourceSrv.get(panel.datasource);
|
let exploreDatasource = await getDataSourceSrv().get(dsRef);
|
||||||
|
|
||||||
/** In Explore, we don't have legend formatter and we don't want to keep
|
/*
|
||||||
* legend formatting as we can't change it
|
* Explore does not support expressions so filter those out
|
||||||
*
|
|
||||||
* We also don't have expressions, so filter those out
|
|
||||||
*/
|
*/
|
||||||
let exploreTargets: DataQuery[] = panel.targets
|
let exploreTargets: DataQuery[] = queries.filter((t) => t.datasource?.uid !== ExpressionDatasourceUID);
|
||||||
.map((t) => omit(t, 'legendFormat'))
|
|
||||||
.filter((t) => t.datasource?.uid !== ExpressionDatasourceUID);
|
|
||||||
let url: string | undefined;
|
let url: string | undefined;
|
||||||
|
|
||||||
if (exploreDatasource) {
|
if (exploreDatasource) {
|
||||||
let state: Partial<ExploreUrlState> = { range: toURLRange(timeRange.raw) };
|
let state: Partial<ExploreUrlState> = { range: toURLRange(timeRange.raw) };
|
||||||
if (exploreDatasource.interpolateVariablesInQueries) {
|
if (exploreDatasource.interpolateVariablesInQueries) {
|
||||||
const scopedVars = panel.scopedVars || {};
|
|
||||||
state = {
|
state = {
|
||||||
...state,
|
...state,
|
||||||
datasource: exploreDatasource.uid,
|
datasource: exploreDatasource.uid,
|
||||||
queries: exploreDatasource.interpolateVariablesInQueries(exploreTargets, scopedVars),
|
queries: exploreDatasource.interpolateVariablesInQueries(exploreTargets, scopedVars ?? {}),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
state = {
|
state = {
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||||
|
import { locationService } from '@grafana/runtime';
|
||||||
|
import { SceneGridItem, SceneGridLayout, SceneQueryRunner, VizPanel, VizPanelMenu } from '@grafana/scenes';
|
||||||
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
|
import { GetExploreUrlArguments } from 'app/core/utils/explore';
|
||||||
|
|
||||||
|
import { DashboardScene } from './DashboardScene';
|
||||||
|
import { panelMenuBehavior } from './PanelMenuBehavior';
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
contextSrv: jest.mocked(contextSrv),
|
||||||
|
getExploreUrl: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('app/core/utils/explore', () => ({
|
||||||
|
...jest.requireActual('app/core/utils/explore'),
|
||||||
|
getExploreUrl: (options: GetExploreUrlArguments) => {
|
||||||
|
return mocks.getExploreUrl(options);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('app/core/services/context_srv');
|
||||||
|
|
||||||
|
describe('panelMenuBehavior', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
locationService.push('/scenes/dashboard/dash-1?from=now-5m&to=now');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Given standard panel', async () => {
|
||||||
|
const { menu, panel } = await buildTestScene({});
|
||||||
|
|
||||||
|
Object.assign(panel, 'getPlugin', () => getPanelPlugin({}));
|
||||||
|
|
||||||
|
mocks.contextSrv.hasAccessToExplore.mockReturnValue(true);
|
||||||
|
mocks.getExploreUrl.mockReturnValue(Promise.resolve('/explore'));
|
||||||
|
|
||||||
|
menu.activate();
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 1));
|
||||||
|
|
||||||
|
expect(menu.state.items?.length).toBe(4);
|
||||||
|
// verify view panel url keeps url params and adds viewPanel=<panel-key>
|
||||||
|
expect(menu.state.items?.[0].href).toBe('/scenes/dashboard/dash-1?from=now-5m&to=now&viewPanel=panel-12');
|
||||||
|
// verify edit url keeps url time range
|
||||||
|
expect(menu.state.items?.[1].href).toBe('/scenes/dashboard/dash-1/panel-edit/12?from=now-5m&to=now');
|
||||||
|
// verify explore url
|
||||||
|
expect(menu.state.items?.[2].href).toBe('/explore');
|
||||||
|
|
||||||
|
// Verify explore url is called with correct arguments
|
||||||
|
const getExploreArgs: GetExploreUrlArguments = mocks.getExploreUrl.mock.calls[0][0];
|
||||||
|
expect(getExploreArgs.dsRef).toEqual({ uid: 'my-uid' });
|
||||||
|
expect(getExploreArgs.queries).toEqual([{ query: 'buu', refId: 'A' }]);
|
||||||
|
expect(getExploreArgs.scopedVars?.__sceneObject?.value).toBe(panel);
|
||||||
|
|
||||||
|
// verify inspect url keeps url params and adds inspect=<panel-key>
|
||||||
|
expect(menu.state.items?.[3].href).toBe('/scenes/dashboard/dash-1?from=now-5m&to=now&inspect=panel-12');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
interface SceneOptions {}
|
||||||
|
|
||||||
|
async function buildTestScene(options: SceneOptions) {
|
||||||
|
const menu = new VizPanelMenu({
|
||||||
|
$behaviors: [panelMenuBehavior],
|
||||||
|
});
|
||||||
|
|
||||||
|
const panel = new VizPanel({
|
||||||
|
title: 'Panel A',
|
||||||
|
pluginId: 'table',
|
||||||
|
key: 'panel-12',
|
||||||
|
menu,
|
||||||
|
$data: new SceneQueryRunner({
|
||||||
|
datasource: { uid: 'my-uid' },
|
||||||
|
queries: [{ query: 'buu', refId: 'A' }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const scene = new DashboardScene({
|
||||||
|
title: 'hello',
|
||||||
|
uid: 'dash-1',
|
||||||
|
body: new SceneGridLayout({
|
||||||
|
children: [
|
||||||
|
new SceneGridItem({
|
||||||
|
key: 'griditem-1',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 10,
|
||||||
|
height: 12,
|
||||||
|
body: panel,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 1));
|
||||||
|
|
||||||
|
return { scene, panel, menu };
|
||||||
|
}
|
@ -1,9 +1,12 @@
|
|||||||
import { locationUtil, PanelMenuItem } from '@grafana/data';
|
import { locationUtil, PanelMenuItem } from '@grafana/data';
|
||||||
import { locationService } from '@grafana/runtime';
|
import { locationService, reportInteraction } from '@grafana/runtime';
|
||||||
import { VizPanel, VizPanelMenu } from '@grafana/scenes';
|
import { sceneGraph, VizPanel, VizPanelMenu } from '@grafana/scenes';
|
||||||
|
import { contextSrv } from 'app/core/core';
|
||||||
import { t } from 'app/core/internationalization';
|
import { t } from 'app/core/internationalization';
|
||||||
|
import { getExploreUrl } from 'app/core/utils/explore';
|
||||||
|
import { InspectTab } from 'app/features/inspector/types';
|
||||||
|
|
||||||
import { getDashboardUrl, getPanelIdForVizPanel } from '../utils/utils';
|
import { getDashboardUrl, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils';
|
||||||
|
|
||||||
import { DashboardScene } from './DashboardScene';
|
import { DashboardScene } from './DashboardScene';
|
||||||
|
|
||||||
@ -11,50 +14,68 @@ import { DashboardScene } from './DashboardScene';
|
|||||||
* Behavior is called when VizPanelMenu is activated (ie when it's opened).
|
* Behavior is called when VizPanelMenu is activated (ie when it's opened).
|
||||||
*/
|
*/
|
||||||
export function panelMenuBehavior(menu: VizPanelMenu) {
|
export function panelMenuBehavior(menu: VizPanelMenu) {
|
||||||
// hm.. add another generic param to SceneObject to specify parent type?
|
const asyncFunc = async () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
// hm.. add another generic param to SceneObject to specify parent type?
|
||||||
const panel = menu.parent as VizPanel;
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
const location = locationService.getLocation();
|
const panel = menu.parent as VizPanel;
|
||||||
const items: PanelMenuItem[] = [];
|
const location = locationService.getLocation();
|
||||||
const panelId = getPanelIdForVizPanel(panel);
|
const items: PanelMenuItem[] = [];
|
||||||
const dashboard = panel.getRoot();
|
const panelId = getPanelIdForVizPanel(panel);
|
||||||
|
const dashboard = panel.getRoot();
|
||||||
|
const panelPlugin = panel.getPlugin();
|
||||||
|
const queryRunner = getQueryRunnerFor(panel);
|
||||||
|
|
||||||
// TODO
|
if (dashboard instanceof DashboardScene) {
|
||||||
// Add tracking via reportInteraction (but preserve the fact that these are normal links)
|
items.push({
|
||||||
|
text: t('panel.header-menu.view', `View`),
|
||||||
|
iconClassName: 'eye',
|
||||||
|
shortcut: 'v',
|
||||||
|
onClick: () => reportInteraction('dashboards_panelheader_menu', { item: 'view' }),
|
||||||
|
href: locationUtil.getUrlForPartial(location, { viewPanel: panel.state.key }),
|
||||||
|
});
|
||||||
|
|
||||||
|
// We could check isEditing here but I kind of think this should always be in the menu,
|
||||||
|
// and going into panel edit should make the dashboard go into edit mode is it's not already
|
||||||
|
items.push({
|
||||||
|
text: t('panel.header-menu.edit', `Edit`),
|
||||||
|
iconClassName: 'eye',
|
||||||
|
shortcut: 'v',
|
||||||
|
onClick: () => reportInteraction('dashboards_panelheader_menu', { item: 'edit' }),
|
||||||
|
href: getDashboardUrl({
|
||||||
|
uid: dashboard.state.uid,
|
||||||
|
subPath: `/panel-edit/${panelId}`,
|
||||||
|
currentQueryParams: location.search,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contextSrv.hasAccessToExplore() && !panelPlugin?.meta.skipDataQuery && queryRunner) {
|
||||||
|
const timeRange = sceneGraph.getTimeRange(panel);
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
text: t('panel.header-menu.explore', `Explore`),
|
||||||
|
iconClassName: 'compass',
|
||||||
|
shortcut: 'p x',
|
||||||
|
onClick: () => reportInteraction('dashboards_panelheader_menu', { item: 'explore' }),
|
||||||
|
href: await getExploreUrl({
|
||||||
|
queries: queryRunner.state.queries,
|
||||||
|
dsRef: queryRunner.state.datasource,
|
||||||
|
timeRange: timeRange.state.value,
|
||||||
|
scopedVars: { __sceneObject: { value: panel } },
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (dashboard instanceof DashboardScene) {
|
|
||||||
items.push({
|
items.push({
|
||||||
text: t('panel.header-menu.view', `View`),
|
text: t('panel.header-menu.inspect', `Inspect`),
|
||||||
iconClassName: 'eye',
|
iconClassName: 'info-circle',
|
||||||
shortcut: 'v',
|
shortcut: 'i',
|
||||||
href: getDashboardUrl({
|
onClick: () => reportInteraction('dashboards_panelheader_menu', { item: 'inspect', tab: InspectTab.Data }),
|
||||||
uid: dashboard.state.uid,
|
href: locationUtil.getUrlForPartial(location, { inspect: panel.state.key }),
|
||||||
currentQueryParams: location.search,
|
|
||||||
updateQuery: { filter: null, new: 'A' },
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// We could check isEditing here but I kind of think this should always be in the menu,
|
menu.setState({ items });
|
||||||
// and going into panel edit should make the dashboard go into edit mode is it's not already
|
};
|
||||||
items.push({
|
|
||||||
text: t('panel.header-menu.edit', `Edit`),
|
|
||||||
iconClassName: 'eye',
|
|
||||||
shortcut: 'v',
|
|
||||||
href: getDashboardUrl({
|
|
||||||
uid: dashboard.state.uid,
|
|
||||||
subPath: `/panel-edit/${panelId}`,
|
|
||||||
currentQueryParams: location.search,
|
|
||||||
updateQuery: { filter: null, new: 'A' },
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
items.push({
|
asyncFunc();
|
||||||
text: t('panel.header-menu.inspect', `Inspect`),
|
|
||||||
iconClassName: 'info-circle',
|
|
||||||
shortcut: 'i',
|
|
||||||
href: locationUtil.getUrlForPartial(location, { inspect: panel.state.key }),
|
|
||||||
});
|
|
||||||
|
|
||||||
menu.setState({ items });
|
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,7 @@ import {
|
|||||||
PluginExtensionPoints,
|
PluginExtensionPoints,
|
||||||
type PluginExtensionPanelContext,
|
type PluginExtensionPanelContext,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import {
|
import { AngularComponent, locationService, reportInteraction, getPluginLinkExtensions } from '@grafana/runtime';
|
||||||
AngularComponent,
|
|
||||||
getDataSourceSrv,
|
|
||||||
locationService,
|
|
||||||
reportInteraction,
|
|
||||||
getPluginLinkExtensions,
|
|
||||||
} from '@grafana/runtime';
|
|
||||||
import { PanelCtrl } from 'app/angular/panel/panel_ctrl';
|
import { PanelCtrl } from 'app/angular/panel/panel_ctrl';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { t } from 'app/core/internationalization';
|
import { t } from 'app/core/internationalization';
|
||||||
@ -112,7 +106,6 @@ export function getPanelMenu(
|
|||||||
event.ctrlKey || event.metaKey ? (url: string) => window.open(`${config.appSubUrl}${url}`) : undefined;
|
event.ctrlKey || event.metaKey ? (url: string) => window.open(`${config.appSubUrl}${url}`) : undefined;
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
navigateToExplore(panel, {
|
navigateToExplore(panel, {
|
||||||
getDataSourceSrv,
|
|
||||||
timeRange: getTimeSrv().timeRange(),
|
timeRange: getTimeSrv().timeRange(),
|
||||||
getExploreUrl,
|
getExploreUrl,
|
||||||
openInNewWindow,
|
openInNewWindow,
|
||||||
|
@ -20,19 +20,17 @@ const getNavigateToExploreContext = async (openInNewWindow?: (url: string) => vo
|
|||||||
};
|
};
|
||||||
const datasource = new MockDataSourceApi(panel.datasource!.uid!);
|
const datasource = new MockDataSourceApi(panel.datasource!.uid!);
|
||||||
const get = jest.fn().mockResolvedValue(datasource);
|
const get = jest.fn().mockResolvedValue(datasource);
|
||||||
const getDataSourceSrv = jest.fn().mockReturnValue({ get });
|
|
||||||
const getExploreUrl = jest.fn().mockResolvedValue(url);
|
const getExploreUrl = jest.fn().mockResolvedValue(url);
|
||||||
const timeRange = { from: dateTime(), to: dateTime() };
|
const timeRange = { from: dateTime(), to: dateTime() };
|
||||||
|
|
||||||
const dispatchedActions = await thunkTester({})
|
const dispatchedActions = await thunkTester({})
|
||||||
.givenThunk(navigateToExplore)
|
.givenThunk(navigateToExplore)
|
||||||
.whenThunkIsDispatched(panel, { getDataSourceSrv, timeRange, getExploreUrl, openInNewWindow });
|
.whenThunkIsDispatched(panel, { timeRange, getExploreUrl, openInNewWindow });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url,
|
url,
|
||||||
panel,
|
panel,
|
||||||
get,
|
get,
|
||||||
getDataSourceSrv,
|
|
||||||
timeRange,
|
timeRange,
|
||||||
getExploreUrl,
|
getExploreUrl,
|
||||||
dispatchedActions,
|
dispatchedActions,
|
||||||
@ -47,20 +45,14 @@ describe('navigateToExplore', () => {
|
|||||||
expect(locationService.getLocation().pathname).toEqual(url);
|
expect(locationService.getLocation().pathname).toEqual(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('then getDataSourceSrv should have been once', async () => {
|
|
||||||
const { getDataSourceSrv } = await getNavigateToExploreContext();
|
|
||||||
|
|
||||||
expect(getDataSourceSrv).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('then getExploreUrl should have been called with correct arguments', async () => {
|
it('then getExploreUrl should have been called with correct arguments', async () => {
|
||||||
const { getExploreUrl, panel, getDataSourceSrv, timeRange } = await getNavigateToExploreContext();
|
const { getExploreUrl, panel, timeRange } = await getNavigateToExploreContext();
|
||||||
|
|
||||||
expect(getExploreUrl).toHaveBeenCalledTimes(1);
|
expect(getExploreUrl).toHaveBeenCalledTimes(1);
|
||||||
expect(getExploreUrl).toHaveBeenCalledWith({
|
expect(getExploreUrl).toHaveBeenCalledWith({
|
||||||
panel,
|
queries: panel.targets,
|
||||||
datasourceSrv: getDataSourceSrv(),
|
|
||||||
timeRange,
|
timeRange,
|
||||||
|
dsRef: panel.datasource,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -73,22 +65,14 @@ describe('navigateToExplore', () => {
|
|||||||
expect(dispatchedActions).toEqual([]);
|
expect(dispatchedActions).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('then getDataSourceSrv should have been once', async () => {
|
|
||||||
const { getDataSourceSrv } = await getNavigateToExploreContext(openInNewWindow);
|
|
||||||
|
|
||||||
expect(getDataSourceSrv).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('then getExploreUrl should have been called with correct arguments', async () => {
|
it('then getExploreUrl should have been called with correct arguments', async () => {
|
||||||
const { getExploreUrl, panel, getDataSourceSrv, timeRange } = await getNavigateToExploreContext(
|
const { getExploreUrl, panel, timeRange } = await getNavigateToExploreContext(openInNewWindow);
|
||||||
openInNewWindow
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(getExploreUrl).toHaveBeenCalledTimes(1);
|
expect(getExploreUrl).toHaveBeenCalledTimes(1);
|
||||||
expect(getExploreUrl).toHaveBeenCalledWith({
|
expect(getExploreUrl).toHaveBeenCalledWith({
|
||||||
panel,
|
queries: panel.targets,
|
||||||
datasourceSrv: getDataSourceSrv(),
|
|
||||||
timeRange,
|
timeRange,
|
||||||
|
dsRef: panel.datasource,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { createAction } from '@reduxjs/toolkit';
|
|||||||
import { AnyAction } from 'redux';
|
import { AnyAction } from 'redux';
|
||||||
|
|
||||||
import { SplitOpenOptions, TimeRange } from '@grafana/data';
|
import { SplitOpenOptions, TimeRange } from '@grafana/data';
|
||||||
import { DataSourceSrv, locationService } from '@grafana/runtime';
|
import { locationService } from '@grafana/runtime';
|
||||||
import { generateExploreId, GetExploreUrlArguments } from 'app/core/utils/explore';
|
import { generateExploreId, GetExploreUrlArguments } from 'app/core/utils/explore';
|
||||||
import { PanelModel } from 'app/features/dashboard/state';
|
import { PanelModel } from 'app/features/dashboard/state';
|
||||||
import { ExploreItemState, ExploreState } from 'app/types/explore';
|
import { ExploreItemState, ExploreState } from 'app/types/explore';
|
||||||
@ -105,7 +105,6 @@ const createNewSplitOpenPane = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export interface NavigateToExploreDependencies {
|
export interface NavigateToExploreDependencies {
|
||||||
getDataSourceSrv: () => DataSourceSrv;
|
|
||||||
timeRange: TimeRange;
|
timeRange: TimeRange;
|
||||||
getExploreUrl: (args: GetExploreUrlArguments) => Promise<string | undefined>;
|
getExploreUrl: (args: GetExploreUrlArguments) => Promise<string | undefined>;
|
||||||
openInNewWindow?: (url: string) => void;
|
openInNewWindow?: (url: string) => void;
|
||||||
@ -116,11 +115,12 @@ export const navigateToExplore = (
|
|||||||
dependencies: NavigateToExploreDependencies
|
dependencies: NavigateToExploreDependencies
|
||||||
): ThunkResult<void> => {
|
): ThunkResult<void> => {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
const { getDataSourceSrv, timeRange, getExploreUrl, openInNewWindow } = dependencies;
|
const { timeRange, getExploreUrl, openInNewWindow } = dependencies;
|
||||||
const datasourceSrv = getDataSourceSrv();
|
|
||||||
const path = await getExploreUrl({
|
const path = await getExploreUrl({
|
||||||
panel,
|
queries: panel.targets,
|
||||||
datasourceSrv,
|
dsRef: panel.datasource,
|
||||||
|
scopedVars: panel.scopedVars,
|
||||||
timeRange,
|
timeRange,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -162,6 +162,11 @@ export class TestDataDataSource extends DataSourceWithBackend<TestData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyTemplateVariables(query: TestData, scopedVars: ScopedVars): TestData {
|
||||||
|
this.resolveTemplateVariables(query, scopedVars);
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
annotationDataTopicTest(target: TestData, req: DataQueryRequest<TestData>): Observable<DataQueryResponse> {
|
annotationDataTopicTest(target: TestData, req: DataQueryRequest<TestData>): Observable<DataQueryResponse> {
|
||||||
const events = this.buildFakeAnnotationEvents(req.range, target.lines ?? 10);
|
const events = this.buildFakeAnnotationEvents(req.range, target.lines ?? 10);
|
||||||
const dataFrame = new ArrayDataFrame(events);
|
const dataFrame = new ArrayDataFrame(events);
|
||||||
|
Loading…
Reference in New Issue
Block a user