DashboardLinks: values in links are updated when variables change (#27926)
* DashboardLinks: values in links are updated when variable changes * Tests: adds e2e test that verifies links
This commit is contained in:
+132
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"iteration": 1601526910610,
|
||||
"links": [
|
||||
{
|
||||
"icon": "external link",
|
||||
"includeVars": true,
|
||||
"tags": [],
|
||||
"title": "Grafana",
|
||||
"tooltip": "",
|
||||
"type": "link",
|
||||
"url": "http://www.grafana.com"
|
||||
},
|
||||
{
|
||||
"asDropdown": true,
|
||||
"icon": "external link",
|
||||
"includeVars": true,
|
||||
"tags": ["templating"],
|
||||
"title": "Link as DropDown",
|
||||
"type": "dashboards"
|
||||
},
|
||||
{
|
||||
"icon": "external link",
|
||||
"includeVars": true,
|
||||
"tags": ["demo"],
|
||||
"type": "dashboards"
|
||||
}
|
||||
],
|
||||
"panels": [
|
||||
{
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"content": "# ${custom.text}\n ",
|
||||
"mode": "markdown"
|
||||
},
|
||||
"pluginVersion": "7.3.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "${custom.text}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 26,
|
||||
"style": "dark",
|
||||
"tags": ["gdev", "templating"],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"allValue": null,
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": "All",
|
||||
"value": "$__all"
|
||||
},
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": null,
|
||||
"multi": true,
|
||||
"name": "custom",
|
||||
"options": [
|
||||
{
|
||||
"selected": true,
|
||||
"text": "All",
|
||||
"value": "$__all"
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "p1",
|
||||
"value": "p1"
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "p2",
|
||||
"value": "p2"
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "p3",
|
||||
"value": "p3"
|
||||
}
|
||||
],
|
||||
"query": "p1,p2,p3",
|
||||
"skipUrlSync": false,
|
||||
"type": "custom"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Templating - Dashboard Links and Variables",
|
||||
"uid": "yBCC3aKGk",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
e2e.scenario({
|
||||
describeName: 'Templating',
|
||||
itName: 'Tests dashboard links and variables in links',
|
||||
addScenarioDataSource: false,
|
||||
addScenarioDashBoard: false,
|
||||
skipScenario: false,
|
||||
scenario: () => {
|
||||
e2e.flows.openDashboard({ uid: 'yBCC3aKGk' });
|
||||
e2e().server();
|
||||
e2e()
|
||||
.route({
|
||||
method: 'GET',
|
||||
url: '/api/search?tag=templating&limit=100',
|
||||
})
|
||||
.as('tagsTemplatingSearch');
|
||||
e2e()
|
||||
.route({
|
||||
method: 'GET',
|
||||
url: '/api/search?tag=demo&limit=100',
|
||||
})
|
||||
.as('tagsDemoSearch');
|
||||
|
||||
// waiting for links to render, couldn't find a better way using routes for instance
|
||||
e2e().wait(1000);
|
||||
|
||||
const verifyLinks = (variableValue: string) => {
|
||||
e2e.components.DashboardLinks.link()
|
||||
.should('be.visible')
|
||||
.and(links => {
|
||||
expect(links).to.have.length(13);
|
||||
|
||||
for (let index = 0; index < links.length; index++) {
|
||||
expect(Cypress.$(links[index]).attr('href')).contains(`var-custom=${variableValue}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
e2e.components.DashboardLinks.dropDown()
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.wait('@tagsTemplatingSearch')
|
||||
.wait('@tagsDemoSearch');
|
||||
|
||||
// verify all links, should have All value
|
||||
verifyLinks('All');
|
||||
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('p2')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
e2e.pages.Dashboard.Toolbar.navBar().click();
|
||||
|
||||
e2e.components.DashboardLinks.dropDown()
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.wait('@tagsTemplatingSearch')
|
||||
.wait('@tagsDemoSearch');
|
||||
|
||||
// verify all links, should have p2 value
|
||||
verifyLinks('p2');
|
||||
},
|
||||
});
|
||||
@@ -153,4 +153,9 @@ export const Components = {
|
||||
section: 'Search section',
|
||||
items: 'Search items',
|
||||
},
|
||||
DashboardLinks: {
|
||||
container: 'Dashboard link container',
|
||||
dropDown: 'Dashboard link dropdown',
|
||||
link: 'Dashboard link',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DashboardLink } from '../../state/DashboardModel';
|
||||
import { iconMap } from '../DashLinks/DashLinksEditorCtrl';
|
||||
import { useEffectOnce } from 'react-use';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
export interface Props {
|
||||
dashboard: DashboardModel;
|
||||
@@ -42,14 +43,19 @@ export const DashboardLinks: FC<Props> = ({ dashboard, links }) => {
|
||||
}
|
||||
|
||||
const linkElement = (
|
||||
<a className="gf-form-label" href={sanitizeUrl(linkInfo.href)} target={link.targetBlank ? '_blank' : '_self'}>
|
||||
<a
|
||||
className="gf-form-label"
|
||||
href={sanitizeUrl(linkInfo.href)}
|
||||
target={link.targetBlank ? '_blank' : '_self'}
|
||||
aria-label={selectors.components.DashboardLinks.link}
|
||||
>
|
||||
<Icon name={iconMap[link.icon] as IconName} style={{ marginRight: '4px' }} />
|
||||
<span>{sanitize(linkInfo.title)}</span>
|
||||
</a>
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={key} className="gf-form">
|
||||
<div key={key} className="gf-form" aria-label={selectors.components.DashboardLinks.container}>
|
||||
{link.tooltip ? <Tooltip content={link.tooltip}>{linkElement}</Tooltip> : linkElement}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getLinkSrv } from '../../../panel/panellinks/link_srv';
|
||||
import { DashboardLink } from '../../state/DashboardModel';
|
||||
import { DashboardSearchHit } from 'app/features/search/types';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
interface Props {
|
||||
link: DashboardLink;
|
||||
@@ -24,7 +25,7 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<Props>) {
|
||||
if (this.props.link !== prevProps.link) {
|
||||
if (this.props.link !== prevProps.link || this.props.linkInfo !== prevProps.linkInfo) {
|
||||
this.searchForDashboards();
|
||||
}
|
||||
}
|
||||
@@ -38,11 +39,11 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> {
|
||||
this.setState({ resolvedLinks });
|
||||
};
|
||||
|
||||
renderElement = (linkElement: JSX.Element, key: string) => {
|
||||
renderElement = (linkElement: JSX.Element, key: string, selector: string) => {
|
||||
const { link } = this.props;
|
||||
|
||||
return (
|
||||
<div className="gf-form" key={key}>
|
||||
<div className="gf-form" key={key} aria-label={selector}>
|
||||
{link.tooltip && <Tooltip content={link.tooltip}>{linkElement}</Tooltip>}
|
||||
{!link.tooltip && <>{linkElement}</>}
|
||||
</div>
|
||||
@@ -58,12 +59,21 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> {
|
||||
{resolvedLinks.length > 0 &&
|
||||
resolvedLinks.map((resolvedLink, index) => {
|
||||
const linkElement = (
|
||||
<a className="gf-form-label" href={resolvedLink.url} target={link.targetBlank ? '_blank' : '_self'}>
|
||||
<a
|
||||
className="gf-form-label"
|
||||
href={resolvedLink.url}
|
||||
target={link.targetBlank ? '_blank' : '_self'}
|
||||
aria-label={selectors.components.DashboardLinks.link}
|
||||
>
|
||||
<Icon name="apps" style={{ marginRight: '4px' }} />
|
||||
<span>{resolvedLink.title}</span>
|
||||
</a>
|
||||
);
|
||||
return this.renderElement(linkElement, `dashlinks-list-item-${resolvedLink.id}-${index}`);
|
||||
return this.renderElement(
|
||||
linkElement,
|
||||
`dashlinks-list-item-${resolvedLink.id}-${index}`,
|
||||
selectors.components.DashboardLinks.container
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
@@ -89,7 +99,11 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> {
|
||||
resolvedLinks.map((resolvedLink, index) => {
|
||||
return (
|
||||
<li key={`dashlinks-dropdown-item-${resolvedLink.id}-${index}`}>
|
||||
<a href={resolvedLink.url} target={link.targetBlank ? '_blank' : '_self'}>
|
||||
<a
|
||||
href={resolvedLink.url}
|
||||
target={link.targetBlank ? '_blank' : '_self'}
|
||||
aria-label={selectors.components.DashboardLinks.link}
|
||||
>
|
||||
{resolvedLink.title}
|
||||
</a>
|
||||
</li>
|
||||
@@ -99,7 +113,7 @@ export class DashboardLinksDashboard extends PureComponent<Props, State> {
|
||||
</>
|
||||
);
|
||||
|
||||
return this.renderElement(linkElement, 'dashlinks-dropdown');
|
||||
return this.renderElement(linkElement, 'dashlinks-dropdown', selectors.components.DashboardLinks.dropDown);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
Reference in New Issue
Block a user