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 { t, Trans } from 'app/core/internationalization';
|
||||
|
||||
import { VariablesDependenciesButton } from '../../variables/VariablesDependenciesButton';
|
||||
|
||||
import { VariableEditorListRow } from './VariableEditorListRow';
|
||||
|
||||
export interface Props {
|
||||
|
|
@ -81,6 +83,7 @@ export function VariableEditorList({
|
|||
</table>
|
||||
</div>
|
||||
<Stack>
|
||||
<VariablesDependenciesButton variables={variables} />
|
||||
<Button
|
||||
data-testid={selectors.pages.Dashboard.Settings.Variables.List.newButton}
|
||||
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 { Provider } from 'react-redux';
|
||||
|
||||
import { TypedVariableModel } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { Button } from '@grafana/ui';
|
||||
|
||||
import { store } from '../../../store/store';
|
||||
import { VariableModel } from '../types';
|
||||
|
||||
import { NetworkGraphModal } from './NetworkGraphModal';
|
||||
import { createDependencyEdges, createDependencyNodes, filterNodesWithDependencies } from './utils';
|
||||
|
||||
interface OwnProps {
|
||||
variables: VariableModel[];
|
||||
interface Props {
|
||||
variables: TypedVariableModel[];
|
||||
}
|
||||
|
||||
interface ConnectedProps {}
|
||||
|
||||
interface DispatchProps {}
|
||||
|
||||
type Props = OwnProps & ConnectedProps & DispatchProps;
|
||||
|
||||
export const UnProvidedVariablesDependenciesButton = ({ variables }: Props) => {
|
||||
export const VariablesDependenciesButton = ({ variables }: Props) => {
|
||||
const nodes = useMemo(() => createDependencyNodes(variables), [variables]);
|
||||
const edges = useMemo(() => createDependencyEdges(variables), [variables]);
|
||||
|
||||
|
|
@ -52,9 +43,3 @@ export const UnProvidedVariablesDependenciesButton = ({ variables }: Props) => {
|
|||
</NetworkGraphModal>
|
||||
);
|
||||
};
|
||||
|
||||
export const VariablesDependenciesButton = (props: Props) => (
|
||||
<Provider store={store}>
|
||||
<UnProvidedVariablesDependenciesButton {...props} />
|
||||
</Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -453,6 +453,16 @@
|
|||
"title": "Versions"
|
||||
}
|
||||
},
|
||||
"dashboards": {
|
||||
"settings": {
|
||||
"variables": {
|
||||
"dependencies": {
|
||||
"button": "Show dependencies",
|
||||
"title": "Dependencies"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"data-source-list": {
|
||||
"empty-state": {
|
||||
"button-title": "Add data source",
|
||||
|
|
|
|||
|
|
@ -453,6 +453,16 @@
|
|||
"title": "Vęřşįőʼnş"
|
||||
}
|
||||
},
|
||||
"dashboards": {
|
||||
"settings": {
|
||||
"variables": {
|
||||
"dependencies": {
|
||||
"button": "Ŝĥőŵ đępęʼnđęʼnčįęş",
|
||||
"title": "Đępęʼnđęʼnčįęş"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"data-source-list": {
|
||||
"empty-state": {
|
||||
"button-title": "Åđđ đäŧä şőūřčę",
|
||||
|
|
|
|||
Loading…
Reference in New Issue