debian-mirror-gitlab/spec/frontend/monitoring/components/panel_type_spec.js
2020-04-22 19:07:51 +05:30

409 lines
11 KiB
JavaScript

import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import { setTestTimeout } from 'helpers/timeout';
import invalidUrl from '~/lib/utils/invalid_url';
import axios from '~/lib/utils/axios_utils';
import PanelType from '~/monitoring/components/panel_type.vue';
import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
import TimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
import AnomalyChart from '~/monitoring/components/charts/anomaly.vue';
import {
anomalyMockGraphData,
mockLogsHref,
mockLogsPath,
mockNamespace,
mockNamespacedData,
mockTimeRange,
} from '../mock_data';
import { graphData, graphDataEmpty } from '../fixture_data';
import { createStore, monitoringDashboard } from '~/monitoring/stores';
import { createStore as createEmbedGroupStore } from '~/monitoring/stores/embed_group';
global.URL.createObjectURL = jest.fn();
const mocks = {
$toast: {
show: jest.fn(),
},
};
describe('Panel Type component', () => {
let axiosMock;
let store;
let state;
let wrapper;
const exampleText = 'example_text';
const findCopyLink = () => wrapper.find({ ref: 'copyChartLink' });
const findTimeChart = () => wrapper.find({ ref: 'timeChart' });
const findTitle = () => wrapper.find({ ref: 'graphTitle' });
const findContextualMenu = () => wrapper.find({ ref: 'contextualMenu' });
const createWrapper = props => {
wrapper = shallowMount(PanelType, {
propsData: {
graphData,
...props,
},
store,
mocks,
});
};
beforeEach(() => {
setTestTimeout(1000);
store = createStore();
state = store.state.monitoringDashboard;
axiosMock = new AxiosMockAdapter(axios);
});
afterEach(() => {
axiosMock.reset();
});
describe('When no graphData is available', () => {
beforeEach(() => {
createWrapper({
graphData: graphDataEmpty,
});
});
afterEach(() => {
wrapper.destroy();
});
describe('Empty Chart component', () => {
it('renders the chart title', () => {
expect(findTitle().text()).toBe(graphDataEmpty.title);
});
it('renders the no download csv link', () => {
expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(false);
});
it('does not contain graph widgets', () => {
expect(findContextualMenu().exists()).toBe(false);
});
it('is a Vue instance', () => {
expect(wrapper.find(EmptyChart).exists()).toBe(true);
expect(wrapper.find(EmptyChart).isVueInstance()).toBe(true);
});
});
});
describe('when graph data is available', () => {
beforeEach(() => {
createWrapper();
});
afterEach(() => {
wrapper.destroy();
});
it('renders the chart title', () => {
expect(findTitle().text()).toBe(graphData.title);
});
it('contains graph widgets', () => {
expect(findContextualMenu().exists()).toBe(true);
expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(true);
});
it('sets no clipboard copy link on dropdown by default', () => {
expect(findCopyLink().exists()).toBe(false);
});
it('should emit `timerange` event when a zooming in/out in a chart occcurs', () => {
const timeRange = {
start: '2020-01-01T00:00:00.000Z',
end: '2020-01-01T01:00:00.000Z',
};
jest.spyOn(wrapper.vm, '$emit');
findTimeChart().vm.$emit('datazoom', timeRange);
return wrapper.vm.$nextTick(() => {
expect(wrapper.vm.$emit).toHaveBeenCalledWith('timerangezoom', timeRange);
});
});
describe('Time Series Chart panel type', () => {
it('is rendered', () => {
expect(wrapper.find(TimeSeriesChart).isVueInstance()).toBe(true);
expect(wrapper.find(TimeSeriesChart).exists()).toBe(true);
});
it('includes a default group id', () => {
expect(wrapper.vm.groupId).toBe('panel-type-chart');
});
});
describe('Anomaly Chart panel type', () => {
beforeEach(() => {
wrapper.setProps({
graphData: anomalyMockGraphData,
});
return wrapper.vm.$nextTick();
});
it('is rendered with an anomaly chart', () => {
expect(wrapper.find(AnomalyChart).isVueInstance()).toBe(true);
expect(wrapper.find(AnomalyChart).exists()).toBe(true);
});
});
});
describe('Edit custom metric dropdown item', () => {
const findEditCustomMetricLink = () => wrapper.find({ ref: 'editMetricLink' });
beforeEach(() => {
createWrapper();
return wrapper.vm.$nextTick();
});
it('is not present if the panel is not a custom metric', () => {
expect(findEditCustomMetricLink().exists()).toBe(false);
});
it('is present when the panel contains an edit_path property', () => {
wrapper.setProps({
graphData: {
...graphData,
metrics: [
{
...graphData.metrics[0],
edit_path: '/root/kubernetes-gke-project/prometheus/metrics/23/edit',
},
],
},
});
return wrapper.vm.$nextTick(() => {
expect(findEditCustomMetricLink().exists()).toBe(true);
expect(findEditCustomMetricLink().text()).toBe('Edit metric');
});
});
it('shows an "Edit metrics" link for a panel with multiple metrics', () => {
wrapper.setProps({
graphData: {
...graphData,
metrics: [
{
...graphData.metrics[0],
edit_path: '/root/kubernetes-gke-project/prometheus/metrics/23/edit',
},
{
...graphData.metrics[0],
edit_path: '/root/kubernetes-gke-project/prometheus/metrics/23/edit',
},
],
},
});
return wrapper.vm.$nextTick(() => {
expect(findEditCustomMetricLink().text()).toBe('Edit metrics');
});
});
});
describe('View Logs dropdown item', () => {
const findViewLogsLink = () => wrapper.find({ ref: 'viewLogsLink' });
beforeEach(() => {
createWrapper();
return wrapper.vm.$nextTick();
});
it('is not present by default', () =>
wrapper.vm.$nextTick(() => {
expect(findViewLogsLink().exists()).toBe(false);
}));
it('is not present if a time range is not set', () => {
state.logsPath = mockLogsPath;
state.timeRange = null;
return wrapper.vm.$nextTick(() => {
expect(findViewLogsLink().exists()).toBe(false);
});
});
it('is not present if the logs path is default', () => {
state.logsPath = invalidUrl;
state.timeRange = mockTimeRange;
return wrapper.vm.$nextTick(() => {
expect(findViewLogsLink().exists()).toBe(false);
});
});
it('is not present if the logs path is not set', () => {
state.logsPath = null;
state.timeRange = mockTimeRange;
return wrapper.vm.$nextTick(() => {
expect(findViewLogsLink().exists()).toBe(false);
});
});
it('is present when logs path and time a range is present', () => {
state.logsPath = mockLogsPath;
state.timeRange = mockTimeRange;
return wrapper.vm.$nextTick(() => {
expect(findViewLogsLink().attributes('href')).toMatch(mockLogsHref);
});
});
it('it is overriden when a datazoom event is received', () => {
state.logsPath = mockLogsPath;
state.timeRange = mockTimeRange;
const zoomedTimeRange = {
start: '2020-01-01T00:00:00.000Z',
end: '2020-01-01T01:00:00.000Z',
};
findTimeChart().vm.$emit('datazoom', zoomedTimeRange);
return wrapper.vm.$nextTick(() => {
const start = encodeURIComponent(zoomedTimeRange.start);
const end = encodeURIComponent(zoomedTimeRange.end);
expect(findViewLogsLink().attributes('href')).toMatch(
`${mockLogsPath}?start=${start}&end=${end}`,
);
});
});
});
describe('when cliboard data is available', () => {
const clipboardText = 'A value to copy.';
beforeEach(() => {
createWrapper({
clipboardText,
});
});
afterEach(() => {
wrapper.destroy();
});
it('sets clipboard text on the dropdown', () => {
expect(findCopyLink().exists()).toBe(true);
expect(findCopyLink().element.dataset.clipboardText).toBe(clipboardText);
});
it('adds a copy button to the dropdown', () => {
expect(findCopyLink().text()).toContain('Copy link to chart');
});
it('opens a toast on click', () => {
findCopyLink().vm.$emit('click');
expect(wrapper.vm.$toast.show).toHaveBeenCalled();
});
});
describe('when downloading metrics data as CSV', () => {
beforeEach(() => {
wrapper = shallowMount(PanelType, {
propsData: {
clipboardText: exampleText,
graphData: {
y_label: 'metric',
...graphData,
},
},
store,
});
return wrapper.vm.$nextTick();
});
afterEach(() => {
wrapper.destroy();
});
describe('csvText', () => {
it('converts metrics data from json to csv', () => {
const header = `timestamp,${graphData.y_label}`;
const data = graphData.metrics[0].result[0].values;
const firstRow = `${data[0][0]},${data[0][1]}`;
const secondRow = `${data[1][0]},${data[1][1]}`;
expect(wrapper.vm.csvText).toMatch(`${header}\r\n${firstRow}\r\n${secondRow}\r\n`);
});
});
describe('downloadCsv', () => {
it('produces a link with a Blob', () => {
expect(global.URL.createObjectURL).toHaveBeenLastCalledWith(expect.any(Blob));
expect(global.URL.createObjectURL).toHaveBeenLastCalledWith(
expect.objectContaining({
size: wrapper.vm.csvText.length,
type: 'text/plain',
}),
);
});
});
});
describe('when using dynamic modules', () => {
const { mockDeploymentData, mockProjectPath } = mockNamespacedData;
beforeEach(() => {
store = createEmbedGroupStore();
store.registerModule(mockNamespace, monitoringDashboard);
store.state.embedGroup.modules.push(mockNamespace);
wrapper = shallowMount(PanelType, {
propsData: {
graphData,
namespace: mockNamespace,
},
store,
mocks,
});
});
it('handles namespaced time range and logs path state', () => {
store.state[mockNamespace].timeRange = mockTimeRange;
store.state[mockNamespace].logsPath = mockLogsPath;
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find({ ref: 'viewLogsLink' }).attributes().href).toBe(mockLogsHref);
});
});
it('handles namespaced deployment data state', () => {
store.state[mockNamespace].deploymentData = mockDeploymentData;
return wrapper.vm.$nextTick().then(() => {
expect(findTimeChart().props().deploymentData).toEqual(mockDeploymentData);
});
});
it('handles namespaced project path state', () => {
store.state[mockNamespace].projectPath = mockProjectPath;
return wrapper.vm.$nextTick().then(() => {
expect(findTimeChart().props().projectPath).toBe(mockProjectPath);
});
});
it('it renders a time series chart with no errors', () => {
expect(wrapper.find(TimeSeriesChart).isVueInstance()).toBe(true);
expect(wrapper.find(TimeSeriesChart).exists()).toBe(true);
});
});
});