提交 b7715c54 编写于 作者: M Miguel Rincon

Add the anomaly chart to the dashboard

- Add anomaly chart to panel types
- Refactor dropdown into a single component
- Add specs
上级 7eda144e
<script>
import { __ } from '~/locale';
import { GlDropdown, GlDropdownItem, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
Icon,
GlDropdown,
GlDropdownItem,
},
directives: {
GlModal: GlModalDirective,
GlTooltip: GlTooltipDirective,
},
props: {
csvText: {
type: String,
required: false,
default: null,
},
chartLink: {
type: String,
required: false,
default: null,
},
alertModalId: {
type: String,
required: false,
default: null,
},
},
computed: {
csvHref() {
const data = new Blob([this.csvText], { type: 'text/plain' });
return window.URL.createObjectURL(data);
},
},
methods: {
showToast() {
this.$toast.show(__('Link copied to clipboard'));
},
},
};
</script>
<template>
<gl-dropdown
v-gl-tooltip
class="mx-2"
toggle-class="btn btn-transparent border-0"
:right="true"
:no-caret="true"
:title="__('More actions')"
>
<template slot="button-content">
<icon name="ellipsis_v" class="text-secondary" />
</template>
<gl-dropdown-item
v-if="csvText"
:href="csvHref"
download="chart_metrics.csv"
class="js-csv-dl-link"
>
{{ __('Download CSV') }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="chartLink"
:data-clipboard-text="chartLink"
class="js-chart-link"
@click="showToast"
>
{{ __('Generate link to chart') }}
</gl-dropdown-item>
<gl-dropdown-item v-if="alertModalId" v-gl-modal="alertModalId" class="js-alert-link">
{{ __('Alerts') }}
</gl-dropdown-item>
</gl-dropdown>
</template>
......@@ -11,14 +11,18 @@ import {
} from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import MonitorTimeSeriesChart from './charts/time_series.vue';
import MonitorAnomalyChart from './charts/anomaly.vue';
import MonitorSingleStatChart from './charts/single_stat.vue';
import MonitorEmptyChart from './charts/empty_chart.vue';
import MonitorChartDropdown from './chart_dropdown.vue';
export default {
components: {
MonitorSingleStatChart,
MonitorTimeSeriesChart,
MonitorAnomalyChart,
MonitorEmptyChart,
MonitorChartDropdown,
Icon,
GlDropdown,
GlDropdownItem,
......@@ -64,10 +68,6 @@ export default {
return `${csv}${row}\r\n`;
}, header);
},
downloadCsv() {
const data = new Blob([this.csvText], { type: 'text/plain' });
return window.URL.createObjectURL(data);
},
},
methods: {
getGraphAlerts(queries) {
......@@ -92,6 +92,31 @@ export default {
v-if="isPanelType('single-stat') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-anomaly-chart
v-else-if="isPanelType('anomaly') && graphDataHasMetrics"
:graph-data="graphData"
:deployment-data="deploymentData"
:project-path="projectPath"
:thresholds="getGraphAlertValues(graphData.queries)"
:container-width="dashboardWidth"
group-id="monitor-anomaly-chart"
>
<div class="d-flex align-items-center">
<alert-widget
v-if="alertWidgetAvailable && graphData"
:modal-id="`alert-modal-${index}`"
:alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.queries"
:alerts-to-manage="getGraphAlerts(graphData.queries)"
@setAlerts="setAlerts"
/>
<monitor-chart-dropdown
:csv-text="csvText"
:chart-link="clipboardText"
:alert-modal-id="alertWidgetAvailable ? `alert-modal-${index}` : null"
/>
</div>
</monitor-anomaly-chart>
<monitor-time-series-chart
v-else-if="graphDataHasMetrics"
:graph-data="graphData"
......@@ -110,31 +135,11 @@ export default {
:alerts-to-manage="getGraphAlerts(graphData.queries)"
@setAlerts="setAlerts"
/>
<gl-dropdown
v-gl-tooltip
class="mx-2"
toggle-class="btn btn-transparent border-0"
:right="true"
:no-caret="true"
:title="__('More actions')"
>
<template slot="button-content">
<icon name="ellipsis_v" class="text-secondary" />
</template>
<gl-dropdown-item :href="downloadCsv" download="chart_metrics.csv">
{{ __('Download CSV') }}
</gl-dropdown-item>
<gl-dropdown-item
class="js-chart-link"
:data-clipboard-text="clipboardText"
@click="showToast"
>
{{ __('Generate link to chart') }}
</gl-dropdown-item>
<gl-dropdown-item v-if="alertWidgetAvailable" v-gl-modal="`alert-modal-${index}`">
{{ __('Alerts') }}
</gl-dropdown-item>
</gl-dropdown>
<monitor-chart-dropdown
:csv-text="csvText"
:chart-link="clipboardText"
:alert-modal-id="alertWidgetAvailable ? `alert-modal-${index}` : null"
/>
</div>
</monitor-time-series-chart>
<monitor-empty-chart v-else :graph-title="graphData.title" />
......
import MonitorChartDropdown from '~/monitoring/components/chart_dropdown.vue';
import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
describe('Chart Dropdown component', () => {
const glModal = jest.fn((el, binding) => binding.value);
let originalCreateObjectURL;
let dropdown;
beforeAll(() => {
// createObjectURL is not available yet in jsdom, but support is on the way
// see https://github.com/jsdom/jsdom/issues/1721
originalCreateObjectURL = window.URL.createObjectURL;
window.URL.createObjectURL = window.URL.createObjectURL || (() => {});
});
beforeEach(() => {
dropdown = shallowMount(MonitorChartDropdown, {
directives: {
'gl-modal': glModal,
},
mocks: {
$toast: {
show: jest.fn(),
},
},
});
});
it('renders', () => {
expect(dropdown.exists()).toBe(true);
expect(dropdown.isVueInstance()).toBe(true);
});
describe('csv download link', () => {
const csvText = 'MOCK_CSV_TEXT';
beforeEach(() => {
jest.spyOn(window.URL, 'createObjectURL').mockReturnValue(`blob:${csvText}`);
dropdown.setProps({ csvText });
});
it('is displayed', () => {
const csvLinkComp = dropdown.find('.js-csv-dl-link');
expect(csvLinkComp.isVueInstance()).toBe(true);
expect(csvLinkComp.isEmpty()).toBe(false);
expect(csvLinkComp.attributes('href')).toEqual(`blob:${csvText}`);
});
afterEach(() => {
dropdown.setProps({ csvText: undefined });
window.URL.createObjectURL.mockRestore();
});
});
describe('chart link', () => {
const chartUrl = `${TEST_HOST}/chart`;
let chartLink;
beforeEach(() => {
dropdown.setProps({ chartLink: chartUrl });
chartLink = dropdown.find('.js-chart-link');
jest.spyOn(dropdown.vm.$toast, 'show');
});
it('is displayed', () => {
expect(chartLink.isVueInstance()).toBe(true);
expect(chartLink.isEmpty()).toBe(false);
expect(chartLink.attributes('data-clipboard-text')).toEqual(chartUrl);
});
it('shows a toast on click', () => {
chartLink.vm.$emit('click');
expect(dropdown.vm.$toast.show).toHaveBeenCalled();
});
afterEach(() => {
dropdown.vm.$toast.show.mockReset();
dropdown.setProps({ csvText: undefined });
});
});
describe('alert link', () => {
const alertModalId = `modal-1-2`;
let alertLink;
beforeEach(() => {
glModal.mockClear();
dropdown.setProps({ alertModalId });
alertLink = dropdown.find('.js-alert-link');
});
it('is displayed', () => {
expect(alertLink.isVueInstance()).toBe(true);
expect(alertLink.isEmpty()).toBe(false);
});
it('can open a modal with correct id', () => {
expect(glModal).toHaveReturnedWith(alertModalId);
});
afterEach(() => {
dropdown.setProps({ alertModalId: undefined });
});
});
afterAll(() => {
window.URL.createObjectURL = originalCreateObjectURL;
});
});
......@@ -2,7 +2,9 @@ import { shallowMount } from '@vue/test-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 { graphDataPrometheusQueryRange } from './mock_data';
import AnomalyChart from '~/monitoring/components/charts/anomaly.vue';
import ChartDropdown from '~/monitoring/components/chart_dropdown.vue';
import { graphDataPrometheusQueryRange, graphDataPrometheusQueryAnomalyResult } from './mock_data';
import { createStore } from '~/monitoring/stores';
describe('Panel Type component', () => {
......@@ -52,27 +54,44 @@ describe('Panel Type component', () => {
beforeEach(() => {
store = createStore();
panelType = shallowMount(PanelType, {
propsData: {
clipboardText: exampleText,
dashboardWidth,
graphData: graphDataPrometheusQueryRange,
},
store,
});
panelType = type =>
shallowMount(PanelType, {
propsData: {
clipboardText: exampleText,
dashboardWidth,
graphData: { ...graphDataPrometheusQueryAnomalyResult, type },
},
store,
});
});
describe('Time Series Chart panel type', () => {
it('is rendered', () => {
expect(panelType.find(TimeSeriesChart).isVueInstance()).toBe(true);
expect(panelType.find(TimeSeriesChart).exists()).toBe(true);
const areaPanelType = panelType('area');
expect(areaPanelType.find(TimeSeriesChart).isVueInstance()).toBe(true);
expect(areaPanelType.find(TimeSeriesChart).exists()).toBe(true);
});
it('sets clipboard text on the dropdown', () => {
const dropdown = () => panelType('area').find(ChartDropdown);
expect(dropdown().props('chartLink')).toEqual(exampleText);
});
});
describe('Anomaly Chart panel type', () => {
it('is rendered', () => {
const anomalyChart = panelType('anomaly');
expect(anomalyChart.find(AnomalyChart).isVueInstance()).toBe(true);
expect(anomalyChart.find(AnomalyChart).exists()).toBe(true);
});
it('sets clipboard text on the dropdown', () => {
const link = () => panelType.find('.js-chart-link');
const clipboardText = () => link().element.dataset.clipboardText;
const dropdown = () => panelType('anomaly').find(ChartDropdown);
expect(clipboardText()).toBe(exampleText);
expect(dropdown().props('chartLink')).toEqual(exampleText);
});
});
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册