2020-01-01 13:55:28 +05:30
|
|
|
import { shallowMount } from '@vue/test-utils';
|
|
|
|
import { TEST_HOST } from 'helpers/test_constants';
|
2019-12-26 22:10:19 +05:30
|
|
|
import Anomaly from '~/monitoring/components/charts/anomaly.vue';
|
|
|
|
|
|
|
|
import { colorValues } from '~/monitoring/constants';
|
|
|
|
import {
|
|
|
|
anomalyDeploymentData,
|
|
|
|
mockProjectDir,
|
|
|
|
anomalyMockGraphData,
|
|
|
|
anomalyMockResultValues,
|
|
|
|
} from '../../mock_data';
|
|
|
|
import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
|
|
|
|
|
|
|
|
const mockProjectPath = `${TEST_HOST}${mockProjectDir}`;
|
|
|
|
|
|
|
|
jest.mock('~/lib/utils/icon_utils'); // mock getSvgIconPathContent
|
|
|
|
|
|
|
|
const makeAnomalyGraphData = (datasetName, template = anomalyMockGraphData) => {
|
2020-01-01 13:55:28 +05:30
|
|
|
const metrics = anomalyMockResultValues[datasetName].map((values, index) => ({
|
|
|
|
...template.metrics[index],
|
2019-12-26 22:10:19 +05:30
|
|
|
result: [
|
|
|
|
{
|
|
|
|
metrics: {},
|
|
|
|
values,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
}));
|
2020-01-01 13:55:28 +05:30
|
|
|
return { ...template, metrics };
|
2019-12-26 22:10:19 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
describe('Anomaly chart component', () => {
|
|
|
|
let wrapper;
|
|
|
|
|
|
|
|
const setupAnomalyChart = props => {
|
|
|
|
wrapper = shallowMount(Anomaly, {
|
|
|
|
propsData: { ...props },
|
|
|
|
});
|
|
|
|
};
|
|
|
|
const findTimeSeries = () => wrapper.find(MonitorTimeSeriesChart);
|
|
|
|
const getTimeSeriesProps = () => findTimeSeries().props();
|
|
|
|
|
|
|
|
describe('wrapped monitor-time-series-chart component', () => {
|
|
|
|
const dataSetName = 'noAnomaly';
|
|
|
|
const dataSet = anomalyMockResultValues[dataSetName];
|
|
|
|
const inputThresholds = ['some threshold'];
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
setupAnomalyChart({
|
|
|
|
graphData: makeAnomalyGraphData(dataSetName),
|
|
|
|
deploymentData: anomalyDeploymentData,
|
|
|
|
thresholds: inputThresholds,
|
|
|
|
projectPath: mockProjectPath,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('is a Vue instance', () => {
|
|
|
|
expect(findTimeSeries().exists()).toBe(true);
|
|
|
|
expect(findTimeSeries().isVueInstance()).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('receives props correctly', () => {
|
|
|
|
describe('graph-data', () => {
|
|
|
|
it('receives a single "metric" series', () => {
|
|
|
|
const { graphData } = getTimeSeriesProps();
|
2020-01-01 13:55:28 +05:30
|
|
|
expect(graphData.metrics.length).toBe(1);
|
2019-12-26 22:10:19 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('receives "metric" with all data', () => {
|
|
|
|
const { graphData } = getTimeSeriesProps();
|
2020-01-01 13:55:28 +05:30
|
|
|
const query = graphData.metrics[0];
|
|
|
|
const expectedQuery = makeAnomalyGraphData(dataSetName).metrics[0];
|
2019-12-26 22:10:19 +05:30
|
|
|
expect(query).toEqual(expectedQuery);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('receives the "metric" results', () => {
|
|
|
|
const { graphData } = getTimeSeriesProps();
|
2020-01-01 13:55:28 +05:30
|
|
|
const { result } = graphData.metrics[0];
|
2019-12-26 22:10:19 +05:30
|
|
|
const { values } = result[0];
|
|
|
|
const [metricDataset] = dataSet;
|
|
|
|
expect(values).toEqual(expect.any(Array));
|
|
|
|
|
|
|
|
values.forEach(([, y], index) => {
|
|
|
|
expect(y).toBeCloseTo(metricDataset[index][1]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('option', () => {
|
|
|
|
let option;
|
|
|
|
let series;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
({ option } = getTimeSeriesProps());
|
|
|
|
({ series } = option);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('contains a boundary band', () => {
|
|
|
|
expect(series).toEqual(expect.any(Array));
|
|
|
|
expect(series.length).toEqual(2); // 1 upper + 1 lower boundaries
|
|
|
|
expect(series[0].stack).toEqual(series[1].stack);
|
|
|
|
|
|
|
|
series.forEach(s => {
|
|
|
|
expect(s.type).toBe('line');
|
|
|
|
expect(s.lineStyle.width).toBe(0);
|
|
|
|
expect(s.lineStyle.color).toMatch(/rgba\(.+\)/);
|
|
|
|
expect(s.lineStyle.color).toMatch(s.color);
|
|
|
|
expect(s.symbol).toEqual('none');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('upper boundary values are stacked on top of lower boundary', () => {
|
|
|
|
const [lowerSeries, upperSeries] = series;
|
|
|
|
const [, upperDataset, lowerDataset] = dataSet;
|
|
|
|
|
|
|
|
lowerSeries.data.forEach(([, y], i) => {
|
|
|
|
expect(y).toBeCloseTo(lowerDataset[i][1]);
|
|
|
|
});
|
|
|
|
|
|
|
|
upperSeries.data.forEach(([, y], i) => {
|
|
|
|
expect(y).toBeCloseTo(upperDataset[i][1] - lowerDataset[i][1]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('series-config', () => {
|
|
|
|
let seriesConfig;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
({ seriesConfig } = getTimeSeriesProps());
|
|
|
|
});
|
|
|
|
|
|
|
|
it('display symbols is enabled', () => {
|
|
|
|
expect(seriesConfig).toEqual(
|
|
|
|
expect.objectContaining({
|
|
|
|
type: 'line',
|
|
|
|
symbol: 'circle',
|
|
|
|
showSymbol: true,
|
|
|
|
symbolSize: expect.any(Function),
|
|
|
|
itemStyle: {
|
|
|
|
color: expect.any(Function),
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
it('does not display anomalies', () => {
|
|
|
|
const { symbolSize, itemStyle } = seriesConfig;
|
|
|
|
const [metricDataset] = dataSet;
|
|
|
|
|
|
|
|
metricDataset.forEach((v, dataIndex) => {
|
|
|
|
const size = symbolSize(null, { dataIndex });
|
|
|
|
const color = itemStyle.color({ dataIndex });
|
|
|
|
|
|
|
|
// normal color and small size
|
|
|
|
expect(size).toBeCloseTo(0);
|
|
|
|
expect(color).toBe(colorValues.primaryColor);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('can format y values (to use in tooltips)', () => {
|
|
|
|
expect(parseFloat(wrapper.vm.yValueFormatted(0, 0))).toEqual(dataSet[0][0][1]);
|
|
|
|
expect(parseFloat(wrapper.vm.yValueFormatted(1, 0))).toEqual(dataSet[1][0][1]);
|
|
|
|
expect(parseFloat(wrapper.vm.yValueFormatted(2, 0))).toEqual(dataSet[2][0][1]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('inherited properties', () => {
|
|
|
|
it('"deployment-data" keeps the same value', () => {
|
|
|
|
const { deploymentData } = getTimeSeriesProps();
|
|
|
|
expect(deploymentData).toEqual(anomalyDeploymentData);
|
|
|
|
});
|
|
|
|
it('"thresholds" keeps the same value', () => {
|
|
|
|
const { thresholds } = getTimeSeriesProps();
|
|
|
|
expect(thresholds).toEqual(inputThresholds);
|
|
|
|
});
|
|
|
|
it('"projectPath" keeps the same value', () => {
|
|
|
|
const { projectPath } = getTimeSeriesProps();
|
|
|
|
expect(projectPath).toEqual(mockProjectPath);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('with no boundary data', () => {
|
|
|
|
const dataSetName = 'noBoundary';
|
|
|
|
const dataSet = anomalyMockResultValues[dataSetName];
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
setupAnomalyChart({
|
|
|
|
graphData: makeAnomalyGraphData(dataSetName),
|
|
|
|
deploymentData: anomalyDeploymentData,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('option', () => {
|
|
|
|
let option;
|
|
|
|
let series;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
({ option } = getTimeSeriesProps());
|
|
|
|
({ series } = option);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not display a boundary band', () => {
|
|
|
|
expect(series).toEqual(expect.any(Array));
|
|
|
|
expect(series.length).toEqual(0); // no boundaries
|
|
|
|
});
|
|
|
|
|
|
|
|
it('can format y values (to use in tooltips)', () => {
|
|
|
|
expect(parseFloat(wrapper.vm.yValueFormatted(0, 0))).toEqual(dataSet[0][0][1]);
|
|
|
|
expect(wrapper.vm.yValueFormatted(1, 0)).toBe(''); // missing boundary
|
|
|
|
expect(wrapper.vm.yValueFormatted(2, 0)).toBe(''); // missing boundary
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('with one anomaly', () => {
|
|
|
|
const dataSetName = 'oneAnomaly';
|
|
|
|
const dataSet = anomalyMockResultValues[dataSetName];
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
setupAnomalyChart({
|
|
|
|
graphData: makeAnomalyGraphData(dataSetName),
|
|
|
|
deploymentData: anomalyDeploymentData,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('series-config', () => {
|
|
|
|
it('displays one anomaly', () => {
|
|
|
|
const { seriesConfig } = getTimeSeriesProps();
|
|
|
|
const { symbolSize, itemStyle } = seriesConfig;
|
|
|
|
const [metricDataset] = dataSet;
|
|
|
|
|
|
|
|
const bigDots = metricDataset.filter((v, dataIndex) => {
|
|
|
|
const size = symbolSize(null, { dataIndex });
|
|
|
|
return size > 0.1;
|
|
|
|
});
|
|
|
|
const redDots = metricDataset.filter((v, dataIndex) => {
|
|
|
|
const color = itemStyle.color({ dataIndex });
|
|
|
|
return color === colorValues.anomalySymbol;
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(bigDots.length).toBe(1);
|
|
|
|
expect(redDots.length).toBe(1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('with offset', () => {
|
|
|
|
const dataSetName = 'negativeBoundary';
|
|
|
|
const dataSet = anomalyMockResultValues[dataSetName];
|
|
|
|
const expectedOffset = 4; // Lowst point in mock data is -3.70, it gets rounded
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
setupAnomalyChart({
|
|
|
|
graphData: makeAnomalyGraphData(dataSetName),
|
|
|
|
deploymentData: anomalyDeploymentData,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('receives props correctly', () => {
|
|
|
|
describe('graph-data', () => {
|
|
|
|
it('receives a single "metric" series', () => {
|
|
|
|
const { graphData } = getTimeSeriesProps();
|
2020-01-01 13:55:28 +05:30
|
|
|
expect(graphData.metrics.length).toBe(1);
|
2019-12-26 22:10:19 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('receives "metric" results and applies the offset to them', () => {
|
|
|
|
const { graphData } = getTimeSeriesProps();
|
2020-01-01 13:55:28 +05:30
|
|
|
const { result } = graphData.metrics[0];
|
2019-12-26 22:10:19 +05:30
|
|
|
const { values } = result[0];
|
|
|
|
const [metricDataset] = dataSet;
|
|
|
|
expect(values).toEqual(expect.any(Array));
|
|
|
|
|
|
|
|
values.forEach(([, y], index) => {
|
|
|
|
expect(y).toBeCloseTo(metricDataset[index][1] + expectedOffset);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('option', () => {
|
|
|
|
it('upper boundary values are stacked on top of lower boundary, plus the offset', () => {
|
|
|
|
const { option } = getTimeSeriesProps();
|
|
|
|
const { series } = option;
|
|
|
|
const [lowerSeries, upperSeries] = series;
|
|
|
|
const [, upperDataset, lowerDataset] = dataSet;
|
|
|
|
|
|
|
|
lowerSeries.data.forEach(([, y], i) => {
|
|
|
|
expect(y).toBeCloseTo(lowerDataset[i][1] + expectedOffset);
|
|
|
|
});
|
|
|
|
|
|
|
|
upperSeries.data.forEach(([, y], i) => {
|
|
|
|
expect(y).toBeCloseTo(upperDataset[i][1] - lowerDataset[i][1]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|