425 lines
10 KiB
TypeScript
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,
|
|
})
|
|
})
|
|
})
|