diff options
author | Miguel Rincon <mrincon@gitlab.com> | 2019-09-11 14:57:30 +0200 |
---|---|---|
committer | Miguel Rincon <mrincon@gitlab.com> | 2019-09-11 14:57:30 +0200 |
commit | b7715c54b4c7f748d947fab4ed4a7b9ac1264131 (patch) | |
tree | 717c5128a68268fde3ac2008003e0eded0ff3d28 | |
parent | 7eda144e3e6557b7f42d02a1eeff157d580ca9d1 (diff) | |
download | gitlab-ce-5366-display-anomaly-deviation-boundaries-on-dashboard-ce-integration.tar.gz |
Add the anomaly chart to the dashboard5366-display-anomaly-deviation-boundaries-on-dashboard-ce-integration
- Add anomaly chart to panel types
- Refactor dropdown into a single component
- Add specs
4 files changed, 255 insertions, 43 deletions
diff --git a/app/assets/javascripts/monitoring/components/chart_dropdown.vue b/app/assets/javascripts/monitoring/components/chart_dropdown.vue new file mode 100644 index 00000000000..5b201b803c6 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/chart_dropdown.vue @@ -0,0 +1,78 @@ +<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> diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue index 73ff651d510..0ff7561c110 100644 --- a/app/assets/javascripts/monitoring/components/panel_type.vue +++ b/app/assets/javascripts/monitoring/components/panel_type.vue @@ -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" /> diff --git a/spec/frontend/monitoring/components/chart_dropdown_spec.js b/spec/frontend/monitoring/components/chart_dropdown_spec.js new file mode 100644 index 00000000000..f45dbef94e2 --- /dev/null +++ b/spec/frontend/monitoring/components/chart_dropdown_spec.js @@ -0,0 +1,110 @@ +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; + }); +}); diff --git a/spec/javascripts/monitoring/panel_type_spec.js b/spec/javascripts/monitoring/panel_type_spec.js index a2366e74d43..19a237bd010 100644 --- a/spec/javascripts/monitoring/panel_type_spec.js +++ b/spec/javascripts/monitoring/panel_type_spec.js @@ -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); }); }); }); |