mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 04:22:13 +08:00
DashList: Update links with time range and variables change (#77850)
* user essentials mob! 🔱
lastFile:public/app/plugins/panel/dashlist/DashList.tsx
* DashList: Update variables in URL when they change
Co-authored-by: eledobleefe <laura.fernandez@grafana.com>
Co-authored-by: Joao Silva <joao.silva@grafana.com>
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
* add e2e test to check dashlist variables are always correctly passed
* add comment with link to issue
* Alternative solution
* Minor tweaks
* revert accidental change
* fix
* New solution
* Fix test
* refinement
* Added unit test
* Update devenv/dev-dashboards/panel-dashlist/dashlist.json
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
---------
Co-authored-by: Joao Silva <joao.silva@grafana.com>
Co-authored-by: joshhunt <josh@trtr.co>
Co-authored-by: eledobleefe <laura.fernandez@grafana.com>
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
This commit is contained in:
86
devenv/dev-dashboards/panel-dashlist/dashlist.json
Normal file
86
devenv/dev-dashboards/panel-dashlist/dashlist.json
Normal file
@ -0,0 +1,86 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 12,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"folderUID": "",
|
||||
"includeVars": true,
|
||||
"keepTime": false,
|
||||
"maxItems": 10,
|
||||
"query": "",
|
||||
"showHeadings": true,
|
||||
"showRecentlyViewed": true,
|
||||
"showSearch": false,
|
||||
"showStarred": false,
|
||||
"tags": []
|
||||
},
|
||||
"pluginVersion": "10.3.0-pre",
|
||||
"title": "Include time range and variables enabled",
|
||||
"type": "dashlist"
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 39,
|
||||
"tags": ["gdev", "panel-tests"],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"selected": true,
|
||||
"text": "A",
|
||||
"value": "A"
|
||||
},
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"multi": true,
|
||||
"name": "server",
|
||||
"query": "A,B,C,D",
|
||||
"queryValue": "",
|
||||
"skipUrlSync": false,
|
||||
"type": "custom"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Panel Tests - DashList",
|
||||
"uid": "a6801696-cc53-4196-b1f9-2403e3909185",
|
||||
"version": 1,
|
||||
"weekStart": ""
|
||||
}
|
@ -149,6 +149,13 @@ local dashboard = grafana.dashboard;
|
||||
id: 0,
|
||||
}
|
||||
},
|
||||
dashboard.new('dashlist', import '../dev-dashboards/panel-dashlist/dashlist.json') +
|
||||
resource.addMetadata('folder', 'dev-dashboards') +
|
||||
{
|
||||
spec+: {
|
||||
id: 0,
|
||||
}
|
||||
},
|
||||
dashboard.new('datadata-macros', import '../dev-dashboards/feature-templating/datadata-macros.json') +
|
||||
resource.addMetadata('folder', 'dev-dashboards') +
|
||||
{
|
||||
|
37
e2e/panels-suite/dashlist.spec.ts
Normal file
37
e2e/panels-suite/dashlist.spec.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { e2e } from '../utils';
|
||||
const PAGE_UNDER_TEST = 'a6801696-cc53-4196-b1f9-2403e3909185/panel-tests-dashlist-variables';
|
||||
|
||||
describe('DashList panel', () => {
|
||||
beforeEach(() => {
|
||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
|
||||
});
|
||||
|
||||
// this is to prevent the fix for https://github.com/grafana/grafana/issues/76800 from regressing
|
||||
it('should pass current variable values correctly when `Include current template variable values` is set', () => {
|
||||
e2e.flows.openDashboard({ uid: PAGE_UNDER_TEST });
|
||||
|
||||
// check the initial value of the urls contain the variable value correctly
|
||||
e2e.components.Panels.Panel.title('Include time range and variables enabled')
|
||||
.should('be.visible')
|
||||
.within(() => {
|
||||
cy.get('a').each(($el) => {
|
||||
cy.wrap($el).should('have.attr', 'href').and('contain', 'var-server=A');
|
||||
});
|
||||
});
|
||||
|
||||
// update variable to b
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemLabels('server').click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('B').click();
|
||||
// blur the dropdown
|
||||
cy.get('body').click();
|
||||
|
||||
// check the urls are updated with the new variable value
|
||||
e2e.components.Panels.Panel.title('Include time range and variables enabled')
|
||||
.should('be.visible')
|
||||
.within(() => {
|
||||
cy.get('a').each(($el) => {
|
||||
cy.wrap($el).should('have.attr', 'href').and('contain', 'var-server=B');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,8 +1,18 @@
|
||||
import { CoreApp } from '@grafana/data';
|
||||
import { sceneGraph, SceneGridItem, SceneGridLayout, SceneQueryRunner, VizPanel } from '@grafana/scenes';
|
||||
import {
|
||||
sceneGraph,
|
||||
SceneGridItem,
|
||||
SceneGridLayout,
|
||||
SceneQueryRunner,
|
||||
SceneVariableSet,
|
||||
TestVariable,
|
||||
VizPanel,
|
||||
} from '@grafana/scenes';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { VariablesChanged } from 'app/features/variables/types';
|
||||
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
import { DashboardScene, DashboardSceneState } from './DashboardScene';
|
||||
|
||||
describe('DashboardScene', () => {
|
||||
describe('DashboardSrv.getCurrent compatibility', () => {
|
||||
@ -59,9 +69,27 @@ describe('DashboardScene', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('When variables change', () => {
|
||||
it('A change to griditem pos should set isDirty true', () => {
|
||||
const varA = new TestVariable({ name: 'A', query: 'A.*', value: 'A.AA', text: '', options: [], delayMs: 0 });
|
||||
const scene = buildTestScene({
|
||||
$variables: new SceneVariableSet({ variables: [varA] }),
|
||||
});
|
||||
|
||||
function buildTestScene() {
|
||||
scene.activate();
|
||||
|
||||
const eventHandler = jest.fn();
|
||||
appEvents.subscribe(VariablesChanged, eventHandler);
|
||||
|
||||
varA.changeValueTo('A.AB');
|
||||
|
||||
expect(eventHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function buildTestScene(overrides?: Partial<DashboardSceneState>) {
|
||||
const scene = new DashboardScene({
|
||||
title: 'hello',
|
||||
uid: 'dash-1',
|
||||
@ -86,6 +114,7 @@ function buildTestScene() {
|
||||
}),
|
||||
],
|
||||
}),
|
||||
...overrides,
|
||||
});
|
||||
|
||||
return scene;
|
||||
|
@ -13,10 +13,14 @@ import {
|
||||
SceneObjectState,
|
||||
SceneObjectStateChangedEvent,
|
||||
sceneUtils,
|
||||
SceneVariable,
|
||||
SceneVariableDependencyConfigLike,
|
||||
} from '@grafana/scenes';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { newBrowseDashboardsEnabled } from 'app/features/browse-dashboards/featureFlag';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { VariablesChanged } from 'app/features/variables/types';
|
||||
import { DashboardMeta } from 'app/types';
|
||||
|
||||
import { DashboardSceneRenderer } from '../scene/DashboardSceneRenderer';
|
||||
@ -62,6 +66,11 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
* Handles url sync
|
||||
*/
|
||||
protected _urlSync = new DashboardSceneUrlSync(this);
|
||||
/**
|
||||
* Get notified when variables change
|
||||
*/
|
||||
protected _variableDependency = new DashboardVariableDependency();
|
||||
|
||||
/**
|
||||
* State before editing started
|
||||
*/
|
||||
@ -285,3 +294,22 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
return Boolean(meta.canEdit || meta.canMakeEditable);
|
||||
}
|
||||
}
|
||||
|
||||
export class DashboardVariableDependency implements SceneVariableDependencyConfigLike {
|
||||
private _emptySet = new Set<string>();
|
||||
|
||||
getNames(): Set<string> {
|
||||
return this._emptySet;
|
||||
}
|
||||
|
||||
public hasDependencyOn(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public variableUpdatesCompleted(changedVars: Set<SceneVariable>) {
|
||||
if (changedVars.size > 0) {
|
||||
// Temp solution for some core panels (like dashlist) to know that variables have changed
|
||||
appEvents.publish(new VariablesChanged({ refreshAll: true, panelIds: [] }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,25 @@
|
||||
import { take } from 'lodash';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { DateTime, InterpolateFunction, PanelProps, textUtil, UrlQueryValue, urlUtil } from '@grafana/data';
|
||||
import {
|
||||
DataLinkBuiltInVars,
|
||||
DateTime,
|
||||
InterpolateFunction,
|
||||
PanelProps,
|
||||
textUtil,
|
||||
UrlQueryValue,
|
||||
urlUtil,
|
||||
} from '@grafana/data';
|
||||
import { CustomScrollbar, useStyles2, IconButton } from '@grafana/ui';
|
||||
import { getConfig } from 'app/core/config';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { useBusEvent } from 'app/core/hooks/useBusEvent';
|
||||
import { setStarred } from 'app/core/reducers/navBarTree';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import impressionSrv from 'app/core/services/impression_srv';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { DashboardSearchItem } from 'app/features/search/types';
|
||||
import { getVariablesUrlParams } from 'app/features/variables/getAllVariableValuesForUrl';
|
||||
import { VariablesChanged } from 'app/features/variables/types';
|
||||
import { useDispatch } from 'app/types';
|
||||
|
||||
import { Options } from './panelcfg.gen';
|
||||
@ -26,6 +35,7 @@ interface DashboardGroup {
|
||||
|
||||
async function fetchDashboards(options: Options, replaceVars: InterpolateFunction) {
|
||||
let starredDashboards: Promise<DashboardSearchItem[]> = Promise.resolve([]);
|
||||
|
||||
if (options.showStarred) {
|
||||
const params = { limit: options.maxItems, starred: 'true' };
|
||||
starredDashboards = getBackendSrv().search(params);
|
||||
@ -92,6 +102,7 @@ async function fetchDashboards(options: Options, replaceVars: InterpolateFunctio
|
||||
export function DashList(props: PanelProps<Options>) {
|
||||
const [dashboards, setDashboards] = useState(new Map<string, Dashboard>());
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
fetchDashboards(props.options, props.replaceVariables).then((dashes) => {
|
||||
setDashboards(dashes);
|
||||
@ -140,27 +151,14 @@ export function DashList(props: PanelProps<Options>) {
|
||||
];
|
||||
|
||||
const css = useStyles2(getStyles);
|
||||
const urlParams = useDashListUrlParams(props);
|
||||
|
||||
const renderList = (dashboards: Dashboard[]) => (
|
||||
<ul>
|
||||
{dashboards.map((dash) => {
|
||||
let url = dash.url;
|
||||
let params: { [key: string]: string | DateTime | UrlQueryValue } = {};
|
||||
|
||||
if (props.options.keepTime) {
|
||||
const range = getTimeSrv().timeRangeForUrl();
|
||||
params['from'] = range.from;
|
||||
params['to'] = range.to;
|
||||
}
|
||||
|
||||
if (props.options.includeVars) {
|
||||
params = {
|
||||
...params,
|
||||
...getVariablesUrlParams(),
|
||||
};
|
||||
}
|
||||
|
||||
url = urlUtil.appendQueryToUrl(url, urlUtil.toUrlParams(params));
|
||||
url = urlUtil.appendQueryToUrl(url, urlParams);
|
||||
url = getConfig().disableSanitizeHtml ? url : textUtil.sanitizeUrl(url);
|
||||
|
||||
return (
|
||||
@ -199,3 +197,22 @@ export function DashList(props: PanelProps<Options>) {
|
||||
</CustomScrollbar>
|
||||
);
|
||||
}
|
||||
|
||||
function useDashListUrlParams(props: PanelProps<Options>) {
|
||||
// We don't care about the payload just want to get re-render when this event is published
|
||||
useBusEvent(appEvents, VariablesChanged);
|
||||
|
||||
let params: { [key: string]: string | DateTime | UrlQueryValue } = {};
|
||||
|
||||
if (props.options.keepTime) {
|
||||
params[`\$${DataLinkBuiltInVars.keepTime}`] = true;
|
||||
}
|
||||
|
||||
if (props.options.includeVars) {
|
||||
params[`\$${DataLinkBuiltInVars.includeVars}`] = true;
|
||||
}
|
||||
|
||||
const urlParms = props.replaceVariables(urlUtil.toUrlParams(params));
|
||||
|
||||
return urlParms;
|
||||
}
|
||||
|
Reference in New Issue
Block a user