620 lines
23 KiB
TypeScript
620 lines
23 KiB
TypeScript
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/language_utils.test.ts
|
|
import { Moment } from 'moment';
|
|
|
|
import { AbstractLabelOperator, AbstractQuery, DateTime, dateTime, TimeRange } from '@grafana/data';
|
|
|
|
import {
|
|
escapeLabelValueInExactSelector,
|
|
escapeLabelValueInRegexSelector,
|
|
expandRecordingRules,
|
|
fixSummariesMetadata,
|
|
getPrometheusTime,
|
|
getRangeSnapInterval,
|
|
parseSelector,
|
|
processLabels,
|
|
toPromLikeQuery,
|
|
truncateResult,
|
|
} from './language_utils';
|
|
import { PrometheusCacheLevel } from './types';
|
|
|
|
describe('parseSelector()', () => {
|
|
let parsed;
|
|
|
|
it('returns a clean selector from an empty selector', () => {
|
|
parsed = parseSelector('{}', 1);
|
|
expect(parsed.selector).toBe('{}');
|
|
expect(parsed.labelKeys).toEqual([]);
|
|
});
|
|
|
|
it('returns a clean selector from an unclosed selector', () => {
|
|
const parsed = parseSelector('{foo');
|
|
expect(parsed.selector).toBe('{}');
|
|
});
|
|
|
|
it('returns the selector sorted by label key', () => {
|
|
parsed = parseSelector('{foo="bar"}');
|
|
expect(parsed.selector).toBe('{foo="bar"}');
|
|
expect(parsed.labelKeys).toEqual(['foo']);
|
|
|
|
parsed = parseSelector('{foo="bar",baz="xx"}');
|
|
expect(parsed.selector).toBe('{baz="xx",foo="bar"}');
|
|
});
|
|
|
|
it('returns a clean selector from an incomplete one', () => {
|
|
parsed = parseSelector('{foo}');
|
|
expect(parsed.selector).toBe('{}');
|
|
|
|
parsed = parseSelector('{foo="bar",baz}');
|
|
expect(parsed.selector).toBe('{foo="bar"}');
|
|
|
|
parsed = parseSelector('{foo="bar",baz="}');
|
|
expect(parsed.selector).toBe('{foo="bar"}');
|
|
|
|
// Cursor in value area counts as incomplete
|
|
parsed = parseSelector('{foo="bar",baz=""}', 16);
|
|
expect(parsed.selector).toBe('{foo="bar"}');
|
|
|
|
parsed = parseSelector('{foo="bar",baz="4"}', 17);
|
|
expect(parsed.selector).toBe('{foo="bar"}');
|
|
});
|
|
|
|
it('throws if not inside a selector', () => {
|
|
expect(() => parseSelector('foo{}', 0)).toThrow();
|
|
expect(() => parseSelector('foo{} + bar{}', 5)).toThrow();
|
|
});
|
|
|
|
it('returns the selector nearest to the cursor offset', () => {
|
|
expect(() => parseSelector('{foo="bar"} + {foo="bar"}', 0)).toThrow();
|
|
|
|
parsed = parseSelector('{foo="bar"} + {foo="bar"}', 1);
|
|
expect(parsed.selector).toBe('{foo="bar"}');
|
|
|
|
parsed = parseSelector('{foo="bar"} + {baz="xx"}', 1);
|
|
expect(parsed.selector).toBe('{foo="bar"}');
|
|
|
|
parsed = parseSelector('{baz="xx"} + {foo="bar"}', 16);
|
|
expect(parsed.selector).toBe('{foo="bar"}');
|
|
});
|
|
|
|
it('returns a selector with metric if metric is given', () => {
|
|
parsed = parseSelector('bar{foo}', 4);
|
|
expect(parsed.selector).toBe('{__name__="bar"}');
|
|
|
|
parsed = parseSelector('baz{foo="bar"}', 13);
|
|
expect(parsed.selector).toBe('{__name__="baz",foo="bar"}');
|
|
|
|
parsed = parseSelector('bar:metric:1m{}', 14);
|
|
expect(parsed.selector).toBe('{__name__="bar:metric:1m"}');
|
|
});
|
|
});
|
|
|
|
describe('fixSummariesMetadata', () => {
|
|
const synthetics = {
|
|
ALERTS: {
|
|
type: 'gauge',
|
|
help: 'Time series showing pending and firing alerts. The sample value is set to 1 as long as the alert is in the indicated active (pending or firing) state.',
|
|
},
|
|
};
|
|
it('returns only synthetics on empty metadata', () => {
|
|
expect(fixSummariesMetadata({})).toEqual({ ...synthetics });
|
|
});
|
|
|
|
it('returns unchanged metadata if no summary is present', () => {
|
|
const metadataRaw = {
|
|
foo: [{ type: 'not_a_summary', help: 'foo help' }],
|
|
};
|
|
|
|
const metadata = {
|
|
foo: { type: 'not_a_summary', help: 'foo help' },
|
|
};
|
|
expect(fixSummariesMetadata(metadataRaw)).toEqual({ ...metadata, ...synthetics });
|
|
});
|
|
|
|
it('returns metadata with added count and sum for a summary', () => {
|
|
const metadata = {
|
|
foo: [{ type: 'not_a_summary', help: 'foo help' }],
|
|
bar: [{ type: 'summary', help: 'bar help' }],
|
|
};
|
|
const expected = {
|
|
foo: { type: 'not_a_summary', help: 'foo help' },
|
|
bar: { type: 'summary', help: 'bar help' },
|
|
bar_count: {
|
|
type: 'counter',
|
|
help: 'Count of events that have been observed for the base metric (bar help)',
|
|
},
|
|
bar_sum: { type: 'counter', help: 'Total sum of all observed values for the base metric (bar help)' },
|
|
};
|
|
expect(fixSummariesMetadata(metadata)).toEqual({ ...expected, ...synthetics });
|
|
});
|
|
|
|
it('returns metadata with added bucket/count/sum for a histogram', () => {
|
|
const metadata = {
|
|
foo: [{ type: 'not_a_histogram', help: 'foo help' }],
|
|
bar: [{ type: 'histogram', help: 'bar help' }],
|
|
};
|
|
const expected = {
|
|
foo: { type: 'not_a_histogram', help: 'foo help' },
|
|
bar: { type: 'histogram', help: 'bar help' },
|
|
bar_bucket: { type: 'counter', help: 'Cumulative counters for the observation buckets (bar help)' },
|
|
bar_count: {
|
|
type: 'counter',
|
|
help: 'Count of events that have been observed for the histogram metric (bar help)',
|
|
},
|
|
bar_sum: { type: 'counter', help: 'Total sum of all observed values for the histogram metric (bar help)' },
|
|
};
|
|
expect(fixSummariesMetadata(metadata)).toEqual({ ...expected, ...synthetics });
|
|
});
|
|
});
|
|
|
|
describe('expandRecordingRules()', () => {
|
|
it('returns query w/o recording rules as is', () => {
|
|
expect(expandRecordingRules('metric', {})).toBe('metric');
|
|
expect(expandRecordingRules('metric + metric', {})).toBe('metric + metric');
|
|
expect(expandRecordingRules('metric{}', {})).toBe('metric{}');
|
|
});
|
|
|
|
it('does not modify recording rules name in label values', () => {
|
|
expect(
|
|
expandRecordingRules('{__name__="metric"} + bar', {
|
|
metric: { expandedQuery: 'foo' },
|
|
bar: { expandedQuery: 'super' },
|
|
})
|
|
).toBe('{__name__="metric"} + super');
|
|
});
|
|
|
|
it('returns query with expanded recording rules', () => {
|
|
expect(expandRecordingRules('metric', { metric: { expandedQuery: 'foo' } })).toBe('foo');
|
|
expect(expandRecordingRules('metric + metric', { metric: { expandedQuery: 'foo' } })).toBe('foo + foo');
|
|
expect(expandRecordingRules('metric{}', { metric: { expandedQuery: 'foo' } })).toBe('foo{}');
|
|
expect(expandRecordingRules('metric[]', { metric: { expandedQuery: 'foo' } })).toBe('foo[]');
|
|
expect(
|
|
expandRecordingRules('metric + foo', {
|
|
metric: { expandedQuery: 'foo' },
|
|
foo: { expandedQuery: 'bar' },
|
|
})
|
|
).toBe('foo + bar');
|
|
});
|
|
|
|
it('returns query with labels with expanded recording rules', () => {
|
|
expect(
|
|
expandRecordingRules('metricA{label1="value1"} / metricB{label2="value2"}', {
|
|
metricA: { expandedQuery: 'fooA' },
|
|
metricB: { expandedQuery: 'fooB' },
|
|
})
|
|
).toBe('fooA{label1="value1"} / fooB{label2="value2"}');
|
|
expect(
|
|
expandRecordingRules('metricA{label1="value1",label2="value,2"}', {
|
|
metricA: { expandedQuery: 'rate(fooA[])' },
|
|
})
|
|
).toBe('rate(fooA{label1="value1", label2="value,2"}[])');
|
|
expect(
|
|
expandRecordingRules('metricA{label1="value1"} / metricB{label2="value2"}', {
|
|
metricA: { expandedQuery: 'rate(fooA[])' },
|
|
metricB: { expandedQuery: 'rate(fooB[])' },
|
|
})
|
|
).toBe('rate(fooA{label1="value1"}[]) / rate(fooB{label2="value2"}[])');
|
|
expect(
|
|
expandRecordingRules('metricA{label1="value1",label2="value2"} / metricB{label3="value3"}', {
|
|
metricA: { expandedQuery: 'rate(fooA[])' },
|
|
metricB: { expandedQuery: 'rate(fooB[])' },
|
|
})
|
|
).toBe('rate(fooA{label1="value1", label2="value2"}[]) / rate(fooB{label3="value3"}[])');
|
|
});
|
|
|
|
it('expands the query even it is wrapped with parentheses', () => {
|
|
expect(
|
|
expandRecordingRules('sum (metric{label1="value1"}) by (env)', {
|
|
metric: { expandedQuery: 'foo{labelInside="valueInside"}' },
|
|
})
|
|
).toBe('sum (foo{labelInside="valueInside", label1="value1"}) by (env)');
|
|
});
|
|
|
|
it('expands the query with regex match', () => {
|
|
expect(
|
|
expandRecordingRules('sum (metric{label1=~"/value1/(sa|sb)"}) by (env)', {
|
|
metric: { expandedQuery: 'foo{labelInside="valueInside"}' },
|
|
})
|
|
).toBe('sum (foo{labelInside="valueInside", label1=~"/value1/(sa|sb)"}) by (env)');
|
|
});
|
|
|
|
it('ins:metric:per{pid="val-42", comp="api"}', () => {
|
|
const query = `aaa:111{pid="val-42", comp="api"} + bbb:222{pid="val-42"}`;
|
|
const mapping = {
|
|
'aaa:111': {
|
|
expandedQuery:
|
|
'(max without (mp) (targetMetric{device=~"/dev/(sda1|sdb)"}) / max without (mp) (targetMetric2{device=~"/dev/(sda1|sdb)"}))',
|
|
},
|
|
'bbb:222': { expandedQuery: '(targetMetric2{device=~"/dev/(sda1|sdb)"})' },
|
|
};
|
|
const expected = `(max without (mp) (targetMetric{device=~"/dev/(sda1|sdb)", pid="val-42", comp="api"}) / max without (mp) (targetMetric2{device=~"/dev/(sda1|sdb)", pid="val-42", comp="api"})) + (targetMetric2{device=~"/dev/(sda1|sdb)", pid="val-42"})`;
|
|
const result = expandRecordingRules(query, mapping);
|
|
expect(result).toBe(expected);
|
|
});
|
|
|
|
it('when there is an identifier, identifier must be removed from expanded query', () => {
|
|
const query = `ins:metric:per{uuid="111", comp="api"}`;
|
|
const mapping = {
|
|
'ins:metric:per': {
|
|
expandedQuery: 'targetMetric{device="some_device"}',
|
|
identifier: 'uuid',
|
|
identifierValue: '111',
|
|
},
|
|
};
|
|
const expected = `targetMetric{device="some_device", comp="api"}`;
|
|
const result = expandRecordingRules(query, mapping);
|
|
expect(result).toBe(expected);
|
|
});
|
|
|
|
it('when there is an identifier, identifier must be removed from complex expanded query', () => {
|
|
const query = `instance_path:requests:rate5m{uuid="111", four="tops"} + instance_path:requests:rate15m{second="album", uuid="222"}`;
|
|
const mapping = {
|
|
'instance_path:requests:rate5m': {
|
|
expandedQuery: `rate(prometheus_http_requests_total{job="prometheus"}`,
|
|
identifier: 'uuid',
|
|
identifierValue: '111',
|
|
},
|
|
'instance_path:requests:rate15m': {
|
|
expandedQuery: `prom_http_requests_sum{job="prometheus"}`,
|
|
identifier: 'uuid',
|
|
identifierValue: '222',
|
|
},
|
|
};
|
|
const expected = `rate(prometheus_http_requests_total{job="prometheus", four="tops"} + prom_http_requests_sum{job="prometheus", second="album"}`;
|
|
const result = expandRecordingRules(query, mapping);
|
|
expect(result).toBe(expected);
|
|
});
|
|
});
|
|
|
|
describe('escapeLabelValueInExactSelector()', () => {
|
|
it('handles newline characters', () => {
|
|
expect(escapeLabelValueInExactSelector('t\nes\nt')).toBe('t\\nes\\nt');
|
|
});
|
|
|
|
it('handles backslash characters', () => {
|
|
expect(escapeLabelValueInExactSelector('t\\es\\t')).toBe('t\\\\es\\\\t');
|
|
});
|
|
|
|
it('handles double-quote characters', () => {
|
|
expect(escapeLabelValueInExactSelector('t"es"t')).toBe('t\\"es\\"t');
|
|
});
|
|
|
|
it('handles all together', () => {
|
|
expect(escapeLabelValueInExactSelector('t\\e"st\nl\nab"e\\l')).toBe('t\\\\e\\"st\\nl\\nab\\"e\\\\l');
|
|
});
|
|
});
|
|
|
|
describe('escapeLabelValueInRegexSelector()', () => {
|
|
it('handles newline characters', () => {
|
|
expect(escapeLabelValueInRegexSelector('t\nes\nt')).toBe('t\\nes\\nt');
|
|
});
|
|
|
|
it('handles backslash characters', () => {
|
|
expect(escapeLabelValueInRegexSelector('t\\es\\t')).toBe('t\\\\\\\\es\\\\\\\\t');
|
|
});
|
|
|
|
it('handles double-quote characters', () => {
|
|
expect(escapeLabelValueInRegexSelector('t"es"t')).toBe('t\\"es\\"t');
|
|
});
|
|
|
|
it('handles regex-meaningful characters', () => {
|
|
expect(escapeLabelValueInRegexSelector('t+es$t')).toBe('t\\\\+es\\\\$t');
|
|
});
|
|
|
|
it('handles all together', () => {
|
|
expect(escapeLabelValueInRegexSelector('t\\e"s+t\nl\n$ab"e\\l')).toBe(
|
|
't\\\\\\\\e\\"s\\\\+t\\nl\\n\\\\$ab\\"e\\\\\\\\l'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getRangeSnapInterval', () => {
|
|
it('will not change input if set to no cache', () => {
|
|
const intervalSeconds = 10 * 60; // 10 minutes
|
|
const now = new Date().valueOf();
|
|
|
|
const expectedFrom = dateTime(now - intervalSeconds * 1000);
|
|
const expectedTo = dateTime(now);
|
|
|
|
const range: TimeRange = {
|
|
from: expectedFrom,
|
|
to: expectedTo,
|
|
} as TimeRange;
|
|
|
|
expect(getRangeSnapInterval(PrometheusCacheLevel.None, range)).toEqual({
|
|
start: getPrometheusTime(expectedFrom, false).toString(),
|
|
end: getPrometheusTime(expectedTo, true).toString(),
|
|
});
|
|
});
|
|
|
|
it('will snap range to closest minute', () => {
|
|
const queryDurationMinutes = 10;
|
|
const intervalSeconds = queryDurationMinutes * 60; // 10 minutes
|
|
const now = 1680901009826;
|
|
const nowPlusOneMinute = now + 1000 * 60;
|
|
const nowPlusTwoMinute = now + 1000 * 60 * 2;
|
|
|
|
const nowTime = dateTime(now) as Moment;
|
|
|
|
const expectedFrom = nowTime.clone().startOf('minute').subtract(queryDurationMinutes, 'minute');
|
|
const expectedTo = nowTime.clone().startOf('minute').add(1, 'minute');
|
|
|
|
const range: TimeRange = {
|
|
from: dateTime(now - intervalSeconds * 1000),
|
|
to: dateTime(now),
|
|
} as TimeRange;
|
|
|
|
const range2: TimeRange = {
|
|
from: dateTime(nowPlusOneMinute - intervalSeconds * 1000),
|
|
to: dateTime(nowPlusOneMinute),
|
|
raw: {
|
|
from: dateTime(nowPlusOneMinute - intervalSeconds * 1000),
|
|
to: dateTime(nowPlusOneMinute),
|
|
},
|
|
};
|
|
const range3: TimeRange = {
|
|
from: dateTime(nowPlusTwoMinute - intervalSeconds * 1000),
|
|
to: dateTime(nowPlusTwoMinute),
|
|
raw: {
|
|
from: dateTime(nowPlusTwoMinute - intervalSeconds * 1000),
|
|
to: dateTime(nowPlusTwoMinute),
|
|
},
|
|
};
|
|
|
|
const first = getRangeSnapInterval(PrometheusCacheLevel.Low, range);
|
|
const second = getRangeSnapInterval(PrometheusCacheLevel.Low, range2);
|
|
const third = getRangeSnapInterval(PrometheusCacheLevel.Low, range3);
|
|
|
|
expect(first).toEqual({
|
|
start: getPrometheusTime(expectedFrom as DateTime, false).toString(10),
|
|
end: getPrometheusTime(expectedTo as DateTime, false).toString(10),
|
|
});
|
|
|
|
expect(second).toEqual({
|
|
start: getPrometheusTime(expectedFrom.clone().add(1, 'minute') as DateTime, false).toString(10),
|
|
end: getPrometheusTime(expectedTo.clone().add(1, 'minute') as DateTime, false).toString(10),
|
|
});
|
|
|
|
expect(third).toEqual({
|
|
start: getPrometheusTime(expectedFrom.clone().add(2, 'minute') as DateTime, false).toString(10),
|
|
end: getPrometheusTime(expectedTo.clone().add(2, 'minute') as DateTime, false).toString(10),
|
|
});
|
|
});
|
|
|
|
it('will snap range to closest 10 minute', () => {
|
|
const queryDurationMinutes = 60;
|
|
const intervalSeconds = queryDurationMinutes * 60; // 10 minutes
|
|
const now = 1680901009826;
|
|
const nowPlusOneMinute = now + 1000 * 60;
|
|
const nowPlusTwoMinute = now + 1000 * 60 * 2;
|
|
|
|
const nowTime = dateTime(now) as Moment;
|
|
const nowTimePlusOne = dateTime(nowPlusOneMinute) as Moment;
|
|
const nowTimePlusTwo = dateTime(nowPlusTwoMinute) as Moment;
|
|
|
|
const calculateClosest10 = (date: Moment): Moment => {
|
|
const numberOfMinutes = Math.floor(date.minutes() / 10) * 10;
|
|
const numberOfHours = numberOfMinutes < 60 ? date.hours() : date.hours() + 1;
|
|
return date
|
|
.clone()
|
|
.minutes(numberOfMinutes % 60)
|
|
.hours(numberOfHours);
|
|
};
|
|
|
|
const expectedFromFirst = calculateClosest10(
|
|
nowTime.clone().startOf('minute').subtract(queryDurationMinutes, 'minute')
|
|
);
|
|
const expectedToFirst = calculateClosest10(nowTime.clone().startOf('minute').add(1, 'minute'));
|
|
|
|
const expectedFromSecond = calculateClosest10(
|
|
nowTimePlusOne.clone().startOf('minute').subtract(queryDurationMinutes, 'minute')
|
|
);
|
|
const expectedToSecond = calculateClosest10(nowTimePlusOne.clone().startOf('minute').add(1, 'minute'));
|
|
|
|
const expectedFromThird = calculateClosest10(
|
|
nowTimePlusTwo.clone().startOf('minute').subtract(queryDurationMinutes, 'minute')
|
|
);
|
|
const expectedToThird = calculateClosest10(nowTimePlusTwo.clone().startOf('minute').add(1, 'minute'));
|
|
|
|
const range: TimeRange = {
|
|
from: dateTime(now - intervalSeconds * 1000),
|
|
to: dateTime(now),
|
|
} as TimeRange;
|
|
|
|
const range2: TimeRange = {
|
|
from: dateTime(nowPlusOneMinute - intervalSeconds * 1000),
|
|
to: dateTime(nowPlusOneMinute),
|
|
raw: {
|
|
from: dateTime(nowPlusOneMinute - intervalSeconds * 1000),
|
|
to: dateTime(nowPlusOneMinute),
|
|
},
|
|
};
|
|
const range3: TimeRange = {
|
|
from: dateTime(nowPlusTwoMinute - intervalSeconds * 1000),
|
|
to: dateTime(nowPlusTwoMinute),
|
|
raw: {
|
|
from: dateTime(nowPlusTwoMinute - intervalSeconds * 1000),
|
|
to: dateTime(nowPlusTwoMinute),
|
|
},
|
|
};
|
|
|
|
const first = getRangeSnapInterval(PrometheusCacheLevel.Medium, range);
|
|
const second = getRangeSnapInterval(PrometheusCacheLevel.Medium, range2);
|
|
const third = getRangeSnapInterval(PrometheusCacheLevel.Medium, range3);
|
|
|
|
expect(first).toEqual({
|
|
start: getPrometheusTime(expectedFromFirst as DateTime, false).toString(10),
|
|
end: getPrometheusTime(expectedToFirst as DateTime, false).toString(10),
|
|
});
|
|
|
|
expect(second).toEqual({
|
|
start: getPrometheusTime(expectedFromSecond.clone() as DateTime, false).toString(10),
|
|
end: getPrometheusTime(expectedToSecond.clone() as DateTime, false).toString(10),
|
|
});
|
|
|
|
expect(third).toEqual({
|
|
start: getPrometheusTime(expectedFromThird.clone() as DateTime, false).toString(10),
|
|
end: getPrometheusTime(expectedToThird.clone() as DateTime, false).toString(10),
|
|
});
|
|
});
|
|
|
|
it('will snap range to closest 60 minute', () => {
|
|
const queryDurationMinutes = 120;
|
|
const intervalSeconds = queryDurationMinutes * 60;
|
|
const now = 1680901009826;
|
|
const nowPlusOneMinute = now + 1000 * 60;
|
|
const nowPlusTwoMinute = now + 1000 * 60 * 2;
|
|
|
|
const nowTime = dateTime(now) as Moment;
|
|
const nowTimePlusOne = dateTime(nowPlusOneMinute) as Moment;
|
|
const nowTimePlusTwo = dateTime(nowPlusTwoMinute) as Moment;
|
|
|
|
const calculateClosest60 = (date: Moment): Moment => {
|
|
const numberOfMinutes = Math.floor(date.minutes() / 60) * 60;
|
|
const numberOfHours = numberOfMinutes < 60 ? date.hours() : date.hours() + 1;
|
|
return date
|
|
.clone()
|
|
.minutes(numberOfMinutes % 60)
|
|
.hours(numberOfHours);
|
|
};
|
|
|
|
const expectedFromFirst = calculateClosest60(
|
|
nowTime.clone().startOf('minute').subtract(queryDurationMinutes, 'minute')
|
|
);
|
|
const expectedToFirst = calculateClosest60(nowTime.clone().startOf('minute').add(1, 'minute'));
|
|
|
|
const expectedFromSecond = calculateClosest60(
|
|
nowTimePlusOne.clone().startOf('minute').subtract(queryDurationMinutes, 'minute')
|
|
);
|
|
const expectedToSecond = calculateClosest60(nowTimePlusOne.clone().startOf('minute').add(1, 'minute'));
|
|
|
|
const expectedFromThird = calculateClosest60(
|
|
nowTimePlusTwo.clone().startOf('minute').subtract(queryDurationMinutes, 'minute')
|
|
);
|
|
const expectedToThird = calculateClosest60(nowTimePlusTwo.clone().startOf('minute').add(1, 'minute'));
|
|
|
|
const range: TimeRange = {
|
|
from: dateTime(now - intervalSeconds * 1000),
|
|
to: dateTime(now),
|
|
} as TimeRange;
|
|
|
|
const range2: TimeRange = {
|
|
from: dateTime(nowPlusOneMinute - intervalSeconds * 1000),
|
|
to: dateTime(nowPlusOneMinute),
|
|
raw: {
|
|
from: dateTime(nowPlusOneMinute - intervalSeconds * 1000),
|
|
to: dateTime(nowPlusOneMinute),
|
|
},
|
|
};
|
|
const range3: TimeRange = {
|
|
from: dateTime(nowPlusTwoMinute - intervalSeconds * 1000),
|
|
to: dateTime(nowPlusTwoMinute),
|
|
raw: {
|
|
from: dateTime(nowPlusTwoMinute - intervalSeconds * 1000),
|
|
to: dateTime(nowPlusTwoMinute),
|
|
},
|
|
};
|
|
|
|
const first = getRangeSnapInterval(PrometheusCacheLevel.High, range);
|
|
const second = getRangeSnapInterval(PrometheusCacheLevel.High, range2);
|
|
const third = getRangeSnapInterval(PrometheusCacheLevel.High, range3);
|
|
|
|
expect(first).toEqual({
|
|
start: getPrometheusTime(expectedFromFirst as DateTime, false).toString(10),
|
|
end: getPrometheusTime(expectedToFirst as DateTime, false).toString(10),
|
|
});
|
|
|
|
expect(second).toEqual({
|
|
start: getPrometheusTime(expectedFromSecond.clone() as DateTime, false).toString(10),
|
|
end: getPrometheusTime(expectedToSecond.clone() as DateTime, false).toString(10),
|
|
});
|
|
|
|
expect(third).toEqual({
|
|
start: getPrometheusTime(expectedFromThird.clone() as DateTime, false).toString(10),
|
|
end: getPrometheusTime(expectedToThird.clone() as DateTime, false).toString(10),
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('toPromLikeQuery', () => {
|
|
it('export abstract query to PromQL-like query', () => {
|
|
const abstractQuery: AbstractQuery = {
|
|
refId: 'bar',
|
|
labelMatchers: [
|
|
{ name: 'label1', operator: AbstractLabelOperator.Equal, value: 'value1' },
|
|
{ name: 'label2', operator: AbstractLabelOperator.NotEqual, value: 'value2' },
|
|
{ name: 'label3', operator: AbstractLabelOperator.EqualRegEx, value: 'value3' },
|
|
{ name: 'label4', operator: AbstractLabelOperator.NotEqualRegEx, value: 'value4' },
|
|
],
|
|
};
|
|
|
|
expect(toPromLikeQuery(abstractQuery)).toMatchObject({
|
|
refId: 'bar',
|
|
expr: '{label1="value1", label2!="value2", label3=~"value3", label4!~"value4"}',
|
|
range: true,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('truncateResult', () => {
|
|
it('truncates array longer then 1k from the start of array', () => {
|
|
// creates an array of 1k + 1 elements with values from 0 to 1k
|
|
const array = Array.from(Array(1001).keys());
|
|
expect(array[1000]).toBe(1000);
|
|
truncateResult(array);
|
|
expect(array.length).toBe(1000);
|
|
expect(array[0]).toBe(0);
|
|
expect(array[999]).toBe(999);
|
|
});
|
|
});
|
|
|
|
describe('processLabels', () => {
|
|
it('export abstract query to expr', () => {
|
|
const labels: Array<{ [key: string]: string }> = [
|
|
{ label1: 'value1' },
|
|
{ label2: 'value2' },
|
|
{ label3: 'value3' },
|
|
{ label1: 'value1' },
|
|
{ label1: 'value1b' },
|
|
];
|
|
|
|
expect(processLabels(labels)).toEqual({
|
|
keys: ['label1', 'label2', 'label3'],
|
|
values: { label1: ['value1', 'value1b'], label2: ['value2'], label3: ['value3'] },
|
|
});
|
|
});
|
|
|
|
it('dont wrap utf8 label values with quotes', () => {
|
|
const labels: Array<{ [key: string]: string }> = [
|
|
{ label1: 'value1' },
|
|
{ label2: 'value2' },
|
|
{ label3: 'value3 with space' },
|
|
{ label4: 'value4.with.dot' },
|
|
];
|
|
|
|
expect(processLabels(labels)).toEqual({
|
|
keys: ['label1', 'label2', 'label3', 'label4'],
|
|
values: {
|
|
label1: ['value1'],
|
|
label2: ['value2'],
|
|
label3: [`value3 with space`],
|
|
label4: [`value4.with.dot`],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('dont wrap utf8 labels with quotes', () => {
|
|
const labels: Array<{ [key: string]: string }> = [
|
|
{ 'label1 with space': 'value1' },
|
|
{ 'label2.with.dot': 'value2' },
|
|
];
|
|
|
|
expect(processLabels(labels)).toEqual({
|
|
keys: ['label1 with space', 'label2.with.dot'],
|
|
values: {
|
|
'label1 with space': ['value1'],
|
|
'label2.with.dot': ['value2'],
|
|
},
|
|
});
|
|
});
|
|
});
|