TestData: Query variable support (nested + glob queries) (#18413)

* TestData: added support for nested data source variable queries, and test dashboard

* Added drilldown dashboards

* Fixed typescript issue
This commit is contained in:
Torkel Ödegaard
2019-08-06 18:17:12 +02:00
committed by GitHub
parent 63c3743de6
commit 832b67db38
8 changed files with 719 additions and 15 deletions
+2 -2
View File
@@ -11,7 +11,7 @@ import { SelectableValue } from '@grafana/data';
// Types
import { QueryEditorProps } from '@grafana/ui';
import { TestDataDatasource } from './datasource';
import { TestDataDataSource } from './datasource';
import { TestDataQuery, Scenario } from './types';
interface State {
@@ -19,7 +19,7 @@ interface State {
current: Scenario | null;
}
type Props = QueryEditorProps<TestDataDatasource, TestDataQuery>;
type Props = QueryEditorProps<TestDataDataSource, TestDataQuery>;
export class QueryEditor extends PureComponent<Props> {
backendSrv = getBackendSrv();
+2 -2
View File
@@ -3,9 +3,9 @@ import React, { PureComponent } from 'react';
// Types
import { PluginConfigPageProps, DataSourcePlugin } from '@grafana/ui';
import { TestDataDatasource } from './datasource';
import { TestDataDataSource } from './datasource';
interface Props extends PluginConfigPageProps<DataSourcePlugin<TestDataDatasource>> {}
interface Props extends PluginConfigPageProps<DataSourcePlugin<TestDataDataSource>> {}
export class TestInfoTab extends PureComponent<Props> {
constructor(props: Props) {
+22 -9
View File
@@ -1,10 +1,17 @@
import _ from 'lodash';
import { DataSourceApi, DataQueryRequest, DataSourceInstanceSettings, DataStreamObserver } from '@grafana/ui';
import {
DataSourceApi,
DataQueryRequest,
DataSourceInstanceSettings,
DataStreamObserver,
MetricFindValue,
} from '@grafana/ui';
import { TableData, TimeSeries } from '@grafana/data';
import { TestDataQuery, Scenario } from './types';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { StreamHandler } from './StreamHandler';
import { queryMetricTree } from './metricTree';
import templateSrv from 'app/features/templating/template_srv';
type TestData = TimeSeries | TableData;
@@ -12,7 +19,7 @@ export interface TestDataRegistry {
[key: string]: TestData[];
}
export class TestDataDatasource extends DataSourceApi<TestDataQuery> {
export class TestDataDataSource extends DataSourceApi<TestDataQuery> {
streams = new StreamHandler();
/** @ngInject */
@@ -23,15 +30,11 @@ export class TestDataDatasource extends DataSourceApi<TestDataQuery> {
query(options: DataQueryRequest<TestDataQuery>, observer: DataStreamObserver) {
const queries = options.targets.map(item => {
return {
refId: item.refId,
scenarioId: item.scenarioId,
...item,
intervalMs: options.intervalMs,
maxDataPoints: options.maxDataPoints,
datasourceId: this.id,
stringInput: item.stringInput,
points: item.points,
alias: item.alias,
...item,
alias: templateSrv.replace(item.alias || ''),
};
});
@@ -122,4 +125,14 @@ export class TestDataDatasource extends DataSourceApi<TestDataQuery> {
getScenarios(): Promise<Scenario[]> {
return getBackendSrv().get('/api/tsdb/testdata/scenarios');
}
metricFindQuery(query: string) {
return new Promise<MetricFindValue[]>((resolve, reject) => {
setTimeout(() => {
const children = queryMetricTree(templateSrv.replace(query));
const items = children.map(item => ({ value: item.name, text: item.name }));
resolve(items);
}, 100);
});
}
}
@@ -0,0 +1,19 @@
import { queryMetricTree } from './metricTree';
describe('MetricTree', () => {
it('queryMetric tree return right tree nodes', () => {
const nodes = queryMetricTree('*');
expect(nodes[0].children[0].name).toBe('AA');
expect(nodes[0].children[1].name).toBe('AB');
});
it('queryMetric tree return right tree nodes', () => {
const nodes = queryMetricTree('A.AB.ABC.*');
expect(nodes[0].name).toBe('ABCA');
});
it('queryMetric tree supports glob paths', () => {
const nodes = queryMetricTree('A.{AB,AC}.*').map(i => i.name);
expect(nodes).toEqual(['ABA', 'ABB', 'ABC', 'ACA', 'ACB', 'ACC']);
});
});
+65
View File
@@ -0,0 +1,65 @@
export interface TreeNode {
name: string;
children: TreeNode[];
}
/*
* Builds a nested tree like
* [
* {
* name: 'A',
* children: [
* { name: 'AA', children: [] },
* { name: 'AB', children: [] },
* ]
* }
* ]
*/
function buildMetricTree(parent: string, depth: number): TreeNode[] {
const chars = ['A', 'B', 'C'];
const children: TreeNode[] = [];
if (depth > 3) {
return [];
}
for (const letter of chars) {
const nodeName = `${parent}${letter}`;
children.push({
name: nodeName,
children: buildMetricTree(nodeName, depth + 1),
});
}
return children;
}
function queryTree(children: TreeNode[], query: string[], queryIndex: number): TreeNode[] {
if (query[queryIndex] === '*') {
return children;
}
const nodeQuery = query[queryIndex];
let result: TreeNode[] = [];
let namesToMatch = [nodeQuery];
// handle glob queries
if (nodeQuery.startsWith('{')) {
namesToMatch = nodeQuery.replace(/\{|\}/g, '').split(',');
}
for (const node of children) {
for (const nameToMatch of namesToMatch) {
if (node.name === nameToMatch) {
result = result.concat(queryTree(node.children, query, queryIndex + 1));
}
}
}
return result;
}
export function queryMetricTree(query: string): TreeNode[] {
const children = buildMetricTree('', 0);
return queryTree(children, query.split('.'), 0);
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { DataSourcePlugin } from '@grafana/ui';
import { TestDataDatasource } from './datasource';
import { TestDataDataSource } from './datasource';
import { TestDataQueryCtrl } from './query_ctrl';
import { TestInfoTab } from './TestInfoTab';
import { ConfigEditor } from './ConfigEditor';
@@ -10,7 +10,7 @@ class TestDataAnnotationsQueryCtrl {
static template = '<h2>Annotation scenario</h2>';
}
export const plugin = new DataSourcePlugin(TestDataDatasource)
export const plugin = new DataSourcePlugin(TestDataDataSource)
.setConfigEditor(ConfigEditor)
.setQueryCtrl(TestDataQueryCtrl)
.setAnnotationQueryCtrl(TestDataAnnotationsQueryCtrl)