mirror of https://github.com/grafana/grafana.git
Scenes: Add support for viewing variable dependency graph in settings (#87577)
* Scenes: Add support for viewing variable dependency graph in settings * Fix failing e2e test * Add test for createDependencyNodes/Edges * Refactor for better separation between old architecture and scenes * Add internationalization * run maker --------- Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com>
This commit is contained in:
parent
34c40f959f
commit
195c17da60
|
|
@ -8,6 +8,8 @@ import { SceneVariable, SceneVariableState } from '@grafana/scenes';
|
||||||
import { useStyles2, Stack, Button, EmptyState, TextLink } from '@grafana/ui';
|
import { useStyles2, Stack, Button, EmptyState, TextLink } from '@grafana/ui';
|
||||||
import { t, Trans } from 'app/core/internationalization';
|
import { t, Trans } from 'app/core/internationalization';
|
||||||
|
|
||||||
|
import { VariablesDependenciesButton } from '../../variables/VariablesDependenciesButton';
|
||||||
|
|
||||||
import { VariableEditorListRow } from './VariableEditorListRow';
|
import { VariableEditorListRow } from './VariableEditorListRow';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
|
@ -81,6 +83,7 @@ export function VariableEditorList({
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<Stack>
|
<Stack>
|
||||||
|
<VariablesDependenciesButton variables={variables} />
|
||||||
<Button
|
<Button
|
||||||
data-testid={selectors.pages.Dashboard.Settings.Variables.List.newButton}
|
data-testid={selectors.pages.Dashboard.Settings.Variables.List.newButton}
|
||||||
onClick={onAdd}
|
onClick={onAdd}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
|
import { SceneVariable, SceneVariableState } from '@grafana/scenes';
|
||||||
|
import { Button } from '@grafana/ui';
|
||||||
|
import { Trans, t } from 'app/core/internationalization';
|
||||||
|
import { NetworkGraphModal } from 'app/features/variables/inspect/NetworkGraphModal';
|
||||||
|
|
||||||
|
import { createDependencyEdges, createDependencyNodes, filterNodesWithDependencies } from './utils';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
variables: Array<SceneVariable<SceneVariableState>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VariablesDependenciesButton = ({ variables }: Props) => {
|
||||||
|
const nodes = useMemo(() => createDependencyNodes(variables), [variables]);
|
||||||
|
const edges = useMemo(() => createDependencyEdges(variables), [variables]);
|
||||||
|
|
||||||
|
if (!edges.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NetworkGraphModal
|
||||||
|
show={false}
|
||||||
|
title={t('dashboards.settings.variables.dependencies.title', 'Dependencies')}
|
||||||
|
nodes={filterNodesWithDependencies(nodes, edges)}
|
||||||
|
edges={edges}
|
||||||
|
>
|
||||||
|
{({ showModal }) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
reportInteraction('Show variable dependencies');
|
||||||
|
showModal();
|
||||||
|
}}
|
||||||
|
icon="channel-add"
|
||||||
|
variant="secondary"
|
||||||
|
>
|
||||||
|
<Trans i18nKey={'dashboards.settings.variables.dependencies.button'}>Show dependencies</Trans>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</NetworkGraphModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { TestVariable } from '@grafana/scenes';
|
||||||
|
import { variableAdapters } from 'app/features/variables/adapters';
|
||||||
|
import { createCustomVariableAdapter } from 'app/features/variables/custom/adapter';
|
||||||
|
import { createDataSourceVariableAdapter } from 'app/features/variables/datasource/adapter';
|
||||||
|
import { createQueryVariableAdapter } from 'app/features/variables/query/adapter';
|
||||||
|
|
||||||
|
import { createDependencyEdges, createDependencyNodes } from './utils';
|
||||||
|
|
||||||
|
variableAdapters.setInit(() => [
|
||||||
|
createDataSourceVariableAdapter(),
|
||||||
|
createCustomVariableAdapter(),
|
||||||
|
createQueryVariableAdapter(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
describe('createDependencyNodes', () => {
|
||||||
|
it('should create node for each variable', () => {
|
||||||
|
const variables = [
|
||||||
|
new TestVariable({ name: 'A', query: 'A.*', value: '', text: '', options: [] }),
|
||||||
|
new TestVariable({ name: 'B', query: 'B.*', value: '', text: '', options: [] }),
|
||||||
|
new TestVariable({ name: 'C', query: 'C.*', value: '', text: '', options: [] }),
|
||||||
|
];
|
||||||
|
const graphNodes = createDependencyNodes(variables);
|
||||||
|
expect(graphNodes[0].id).toBe('A');
|
||||||
|
expect(graphNodes[1].id).toBe('B');
|
||||||
|
expect(graphNodes[2].id).toBe('C');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDependencyEdges', () => {
|
||||||
|
it('should create edges for variable dependencies', () => {
|
||||||
|
const variables = [
|
||||||
|
new TestVariable({ name: 'A', query: 'A.*', value: '', text: '', options: [] }),
|
||||||
|
new TestVariable({ name: 'B', query: '${A}.*', value: '', text: '', options: [] }),
|
||||||
|
new TestVariable({ name: 'C', query: '${B}.*', value: '', text: '', options: [] }),
|
||||||
|
];
|
||||||
|
const graphEdges = createDependencyEdges(variables);
|
||||||
|
expect(graphEdges).toContainEqual({ from: 'B', to: 'A' });
|
||||||
|
expect(graphEdges).toContainEqual({ from: 'C', to: 'B' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { SceneVariable, SceneVariableState } from '@grafana/scenes';
|
||||||
|
import { GraphEdge, GraphNode } from 'app/features/variables/inspect/utils';
|
||||||
|
|
||||||
|
export function createDependencyNodes(variables: Array<SceneVariable<SceneVariableState>>): GraphNode[] {
|
||||||
|
return variables.map((variable) => ({ id: variable.state.name, label: `${variable.state.name}` }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterNodesWithDependencies(nodes: GraphNode[], edges: GraphEdge[]): GraphNode[] {
|
||||||
|
return nodes.filter((node) => edges.some((edge) => edge.from === node.id || edge.to === node.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createDependencyEdges = (variables: Array<SceneVariable<SceneVariableState>>): GraphEdge[] => {
|
||||||
|
const edges: GraphEdge[] = [];
|
||||||
|
for (const variable of variables) {
|
||||||
|
for (const other of variables) {
|
||||||
|
if (variable === other) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependsOn = variable.variableDependency?.hasDependencyOn(other.state.name);
|
||||||
|
if (dependsOn) {
|
||||||
|
edges.push({ from: variable.state.name, to: other.state.name });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return edges;
|
||||||
|
};
|
||||||
|
|
@ -1,26 +1,17 @@
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
|
|
||||||
|
import { TypedVariableModel } from '@grafana/data';
|
||||||
import { reportInteraction } from '@grafana/runtime';
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
import { Button } from '@grafana/ui';
|
import { Button } from '@grafana/ui';
|
||||||
|
|
||||||
import { store } from '../../../store/store';
|
|
||||||
import { VariableModel } from '../types';
|
|
||||||
|
|
||||||
import { NetworkGraphModal } from './NetworkGraphModal';
|
import { NetworkGraphModal } from './NetworkGraphModal';
|
||||||
import { createDependencyEdges, createDependencyNodes, filterNodesWithDependencies } from './utils';
|
import { createDependencyEdges, createDependencyNodes, filterNodesWithDependencies } from './utils';
|
||||||
|
|
||||||
interface OwnProps {
|
interface Props {
|
||||||
variables: VariableModel[];
|
variables: TypedVariableModel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConnectedProps {}
|
export const VariablesDependenciesButton = ({ variables }: Props) => {
|
||||||
|
|
||||||
interface DispatchProps {}
|
|
||||||
|
|
||||||
type Props = OwnProps & ConnectedProps & DispatchProps;
|
|
||||||
|
|
||||||
export const UnProvidedVariablesDependenciesButton = ({ variables }: Props) => {
|
|
||||||
const nodes = useMemo(() => createDependencyNodes(variables), [variables]);
|
const nodes = useMemo(() => createDependencyNodes(variables), [variables]);
|
||||||
const edges = useMemo(() => createDependencyEdges(variables), [variables]);
|
const edges = useMemo(() => createDependencyEdges(variables), [variables]);
|
||||||
|
|
||||||
|
|
@ -52,9 +43,3 @@ export const UnProvidedVariablesDependenciesButton = ({ variables }: Props) => {
|
||||||
</NetworkGraphModal>
|
</NetworkGraphModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VariablesDependenciesButton = (props: Props) => (
|
|
||||||
<Provider store={store}>
|
|
||||||
<UnProvidedVariablesDependenciesButton {...props} />
|
|
||||||
</Provider>
|
|
||||||
);
|
|
||||||
|
|
|
||||||
|
|
@ -453,6 +453,16 @@
|
||||||
"title": "Versions"
|
"title": "Versions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dashboards": {
|
||||||
|
"settings": {
|
||||||
|
"variables": {
|
||||||
|
"dependencies": {
|
||||||
|
"button": "Show dependencies",
|
||||||
|
"title": "Dependencies"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"data-source-list": {
|
"data-source-list": {
|
||||||
"empty-state": {
|
"empty-state": {
|
||||||
"button-title": "Add data source",
|
"button-title": "Add data source",
|
||||||
|
|
|
||||||
|
|
@ -453,6 +453,16 @@
|
||||||
"title": "Vęřşįőʼnş"
|
"title": "Vęřşįőʼnş"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dashboards": {
|
||||||
|
"settings": {
|
||||||
|
"variables": {
|
||||||
|
"dependencies": {
|
||||||
|
"button": "Ŝĥőŵ đępęʼnđęʼnčįęş",
|
||||||
|
"title": "Đępęʼnđęʼnčįęş"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"data-source-list": {
|
"data-source-list": {
|
||||||
"empty-state": {
|
"empty-state": {
|
||||||
"button-title": "Åđđ đäŧä şőūřčę",
|
"button-title": "Åđđ đäŧä şőūřčę",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue