chronograf/ui/test/tempVars/utils/graph.test.ts

425 lines
10 KiB
TypeScript

import {
getDependencyNames,
graphFromTemplates,
hydrateTemplates,
TemplateQueryFetcher,
topologicalSort,
} from 'src/tempVars/utils/graph'
import {TemplateType, TemplateValueType} from 'src/types'
function expectInOrder(xs, a, b) {
expect(xs.indexOf(a)).toBeLessThan(xs.indexOf(b))
}
describe('getDependencyNames', () => {
test('can extract template dependency names from query backed template', () => {
const template = {
id: '',
label: '',
tempVar: '',
type: TemplateType.MetaQuery,
query: {
influxql: `SELECT :foo: FROM :bar: WHERE :baz: ~= /:yo:/`,
},
values: [],
}
const expected = [':foo:', ':bar:', ':baz:', ':yo:']
const actual = getDependencyNames(template)
expect(new Set(actual)).toEqual(new Set(expected))
})
test('can extract template dependency names from flux backed template', () => {
const template = {
id: '',
label: '',
tempVar: '',
type: TemplateType.FluxQuery,
query: {
flux: `from(bucket: ":foo:").range(start: 0).filter(fn: (r) => r.:bar: == ":baz:")`,
},
values: [],
}
const expected = [':foo:', ':bar:', ':baz:']
const actual = getDependencyNames(template)
expect(new Set(actual)).toEqual(new Set(expected))
})
test('can extract template dependency names from a non-query backed template', () => {
const template = {
id: '',
label: '',
tempVar: '',
type: TemplateType.CSV,
query: {},
values: [
{
value: ':function:(:fieldKey:)',
type: TemplateValueType.CSV,
selected: true,
localSelected: false,
},
{
value: ':function:(mean(:fieldKey:))',
type: TemplateValueType.CSV,
selected: false,
localSelected: true,
},
],
}
const expected = [':function:', ':fieldKey:']
const actual = getDependencyNames(template)
expect(new Set(actual)).toEqual(new Set(expected))
})
})
describe('topologicalSort', () => {
test('can sort a simple graph', () => {
// Attempt to sort the following graph:
//
// a +---> b
// |
// +---> c +---> d +---> e
//
const templates = [
{
id: 'a',
label: '',
tempVar: ':a:',
type: TemplateType.MetaQuery,
query: {
influxql: ':b: :c:',
},
values: [],
},
{
id: 'b',
label: '',
tempVar: ':b:',
type: TemplateType.MetaQuery,
query: {
influxql: '',
},
values: [],
},
{
id: 'c',
label: '',
tempVar: ':c:',
type: TemplateType.MetaQuery,
query: {
influxql: ':d:',
},
values: [],
},
{
id: 'd',
label: '',
tempVar: ':d:',
type: TemplateType.FluxQuery,
query: {
flux: ':e:',
},
values: [],
},
{
id: 'e',
label: '',
tempVar: ':e:',
type: TemplateType.MetaQuery,
query: {
influxql: '',
},
values: [],
},
]
const graph = graphFromTemplates(templates)
const a = graph.find(n => n.initialTemplate.id === 'a')
const b = graph.find(n => n.initialTemplate.id === 'b')
const c = graph.find(n => n.initialTemplate.id === 'c')
const d = graph.find(n => n.initialTemplate.id === 'd')
const e = graph.find(n => n.initialTemplate.id === 'e')
const sorted = topologicalSort(graph)
expectInOrder(sorted, a, b)
expectInOrder(sorted, a, c)
expectInOrder(sorted, c, d)
expectInOrder(sorted, d, e)
})
})
describe('graphFromTemplates', () => {
test('can construct a graph with deep branches, isolated nodes, and diamonds', () => {
// Attempt to construct the following graph:
//
// a +-----> b
// |
// +-----> c +---> d
//
// e
//
// f +-----> g +---> i
// |
// | ^
// | |
// +-----> h +-----+
//
const templates = [
{
id: 'a',
type: TemplateType.MetaQuery,
label: '',
tempVar: ':a:',
query: {
influxql: ':b: :c:',
},
values: [],
},
{
id: 'b',
type: TemplateType.MetaQuery,
label: '',
tempVar: ':b:',
query: {
influxql: '',
},
values: [],
},
{
id: 'c',
type: TemplateType.MetaQuery,
label: '',
tempVar: ':c:',
query: {
influxql: ':d:',
},
values: [],
},
{
id: 'd',
type: TemplateType.MetaQuery,
label: '',
tempVar: ':d:',
query: {
influxql: '',
},
values: [],
},
{
id: 'e',
type: TemplateType.MetaQuery,
label: '',
tempVar: ':e:',
query: {
influxql: '',
},
values: [],
},
{
id: 'f',
type: TemplateType.MetaQuery,
label: '',
tempVar: ':f:',
query: {
influxql: ':g: :h:',
},
values: [],
},
{
id: 'g',
type: TemplateType.MetaQuery,
label: '',
tempVar: ':g:',
query: {
influxql: ':i:',
},
values: [],
},
{
id: 'h',
type: TemplateType.FluxQuery,
label: '',
tempVar: ':h:',
query: {
flux: ':i:',
},
values: [],
},
{
id: 'i',
type: TemplateType.MetaQuery,
label: '',
tempVar: ':i:',
query: {
influxql: '',
},
values: [],
},
]
const graph = graphFromTemplates(templates)
const a = graph.find(n => n.initialTemplate === templates[0])
const b = graph.find(n => n.initialTemplate === templates[1])
const c = graph.find(n => n.initialTemplate === templates[2])
const d = graph.find(n => n.initialTemplate === templates[3])
const e = graph.find(n => n.initialTemplate === templates[4])
const f = graph.find(n => n.initialTemplate === templates[5])
const g = graph.find(n => n.initialTemplate === templates[6])
const h = graph.find(n => n.initialTemplate === templates[7])
const i = graph.find(n => n.initialTemplate === templates[8])
expect(new Set(a.parents)).toEqual(new Set([]))
expect(new Set(a.children)).toEqual(new Set([b, c]))
expect(new Set(b.parents)).toEqual(new Set([a]))
expect(new Set(b.children)).toEqual(new Set([]))
expect(new Set(c.parents)).toEqual(new Set([a]))
expect(new Set(c.children)).toEqual(new Set([d]))
expect(new Set(d.parents)).toEqual(new Set([c]))
expect(new Set(d.children)).toEqual(new Set([]))
expect(new Set(e.parents)).toEqual(new Set([]))
expect(new Set(e.children)).toEqual(new Set([]))
expect(new Set(f.parents)).toEqual(new Set([]))
expect(new Set(f.children)).toEqual(new Set([g, h]))
expect(new Set(g.parents)).toEqual(new Set([f]))
expect(new Set(g.children)).toEqual(new Set([i]))
expect(new Set(h.parents)).toEqual(new Set([f]))
expect(new Set(h.children)).toEqual(new Set([i]))
expect(new Set(i.parents)).toEqual(new Set([g, h]))
expect(new Set(i.children)).toEqual(new Set())
})
test('throws an error if attempting to create graph with cycles', () => {
const templates = [
{
id: 'a',
type: TemplateType.MetaQuery,
label: '',
tempVar: ':a:',
query: {
influxql: ':b:',
},
values: [],
},
{
id: 'b',
type: TemplateType.FluxQuery,
label: '',
tempVar: ':b:',
query: {
flux: ':c:',
},
values: [],
},
{
id: 'c',
type: TemplateType.MetaQuery,
label: '',
tempVar: ':c:',
query: {
influxql: ':a:',
},
values: [],
},
]
expect(() => graphFromTemplates(templates)).toThrow(/cyclic/)
})
})
describe('hydrateTemplates', () => {
test('it can hydrate a simple template graph', async () => {
const templates = [
{
id: 'a',
type: TemplateType.MetaQuery,
label: '',
tempVar: ':a:',
query: {
influxql: 'query for a depending on :b: and :c:',
},
values: [],
},
{
id: 'b',
type: TemplateType.MetaQuery,
label: '',
tempVar: ':b:',
query: {
influxql: 'query for b',
},
values: [
{
value: 'selected b value',
type: TemplateValueType.MetaQuery,
selected: true,
localSelected: true,
},
],
},
{
id: 'c',
type: TemplateType.FluxQuery,
label: '',
tempVar: ':c:',
query: {
flux: 'query for c',
},
values: [
{
value: 'selected c value',
type: TemplateValueType.FluxQuery,
selected: true,
localSelected: true,
},
],
},
]
const fakeResults = (query: string): Promise<string[]> => {
const results = {
'query for b': ['selected b value'],
'query for c': ['selected c value'],
'query for a depending on selected b value and selected c value': [
'success',
],
}
const queryResults = results[query]
if (!queryResults) {
throw new Error('Ran unexpected query')
}
return Promise.resolve(queryResults)
}
const fakeFetcher: TemplateQueryFetcher = {
fetch: fakeResults,
}
const result = await hydrateTemplates(templates, [], {fetcher: fakeFetcher})
expect(result.find(t => t.id === 'a').values).toContainEqual({
value: 'success',
type: TemplateValueType.MetaQuery,
selected: true,
localSelected: true,
})
})
})