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>
pull/77577/head
Torkel Ödegaard 2 years ago committed by GitHub
parent 4a6b209d64
commit 848efba0de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 86
      devenv/dev-dashboards/panel-dashlist/dashlist.json
  2. 7
      devenv/jsonnet/dev-dashboards.libsonnet
  3. 37
      e2e/panels-suite/dashlist.spec.ts
  4. 35
      public/app/features/dashboard-scene/scene/DashboardScene.test.tsx
  5. 28
      public/app/features/dashboard-scene/scene/DashboardScene.tsx
  6. 55
      public/app/plugins/panel/dashlist/DashList.tsx

@ -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') +
{

@ -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] }),
});
scene.activate();
const eventHandler = jest.fn();
appEvents.subscribe(VariablesChanged, eventHandler);
varA.changeValueTo('A.AB');
expect(eventHandler).toHaveBeenCalledTimes(1);
});
});
});
function buildTestScene() {
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;
}

Loading…
Cancel
Save