diff options
Diffstat (limited to 'spec/javascripts/monitoring')
| -rw-r--r-- | spec/javascripts/monitoring/charts/area_spec.js | 22 | ||||
| -rw-r--r-- | spec/javascripts/monitoring/charts/column_spec.js | 58 | ||||
| -rw-r--r-- | spec/javascripts/monitoring/charts/empty_chart_spec.js | 29 | ||||
| -rw-r--r-- | spec/javascripts/monitoring/charts/single_stat_spec.js | 9 | ||||
| -rw-r--r-- | spec/javascripts/monitoring/dashboard_spec.js | 221 | ||||
| -rw-r--r-- | spec/javascripts/monitoring/dashboard_state_spec.js | 101 | ||||
| -rw-r--r-- | spec/javascripts/monitoring/mock_data.js | 85 | ||||
| -rw-r--r-- | spec/javascripts/monitoring/panel_type_spec.js | 78 | ||||
| -rw-r--r-- | spec/javascripts/monitoring/store/actions_spec.js | 29 | ||||
| -rw-r--r-- | spec/javascripts/monitoring/store/mutations_spec.js | 17 | ||||
| -rw-r--r-- | spec/javascripts/monitoring/store/utils_spec.js | 37 | ||||
| -rw-r--r-- | spec/javascripts/monitoring/utils_spec.js | 55 |
12 files changed, 592 insertions, 149 deletions
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js index ac7e0bb12a1..57f99a09002 100644 --- a/spec/javascripts/monitoring/charts/area_spec.js +++ b/spec/javascripts/monitoring/charts/area_spec.js @@ -1,21 +1,26 @@ import { shallowMount } from '@vue/test-utils'; +import { createStore } from '~/monitoring/stores'; +import { GlLink } from '@gitlab/ui'; import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper'; import Area from '~/monitoring/components/charts/area.vue'; -import { createStore } from '~/monitoring/stores'; import * as types from '~/monitoring/stores/mutation_types'; +import { TEST_HOST } from 'spec/test_constants'; import MonitoringMock, { deploymentData } from '../mock_data'; describe('Area component', () => { + const mockSha = 'mockSha'; const mockWidgets = 'mockWidgets'; const mockSvgPathContent = 'mockSvgPathContent'; + const projectPath = `${TEST_HOST}/path/to/project`; + const commitUrl = `${projectPath}/commit/${mockSha}`; let mockGraphData; let areaChart; let spriteSpy; + let store; beforeEach(() => { - const store = createStore(); - + store = createStore(); store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data); store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData); @@ -26,10 +31,12 @@ describe('Area component', () => { graphData: mockGraphData, containerWidth: 0, deploymentData: store.state.monitoringDashboard.deploymentData, + projectPath, }, slots: { default: mockWidgets, }, + store, }); spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake( @@ -88,11 +95,14 @@ describe('Area component', () => { ); }); - it('renders commit sha in tooltip content', () => { - const mockSha = 'mockSha'; + it('renders clickable commit sha in tooltip content', () => { areaChart.vm.tooltip.sha = mockSha; + areaChart.vm.tooltip.commitUrl = commitUrl; + + const commitLink = areaChart.find(GlLink); - expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipContent', mockSha)).toBe(true); + expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true); + expect(commitLink.attributes('href')).toEqual(commitUrl); }); }); }); diff --git a/spec/javascripts/monitoring/charts/column_spec.js b/spec/javascripts/monitoring/charts/column_spec.js new file mode 100644 index 00000000000..d8ac68b9484 --- /dev/null +++ b/spec/javascripts/monitoring/charts/column_spec.js @@ -0,0 +1,58 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlColumnChart } from '@gitlab/ui/dist/charts'; +import ColumnChart from '~/monitoring/components/charts/column.vue'; + +describe('Column component', () => { + let columnChart; + + beforeEach(() => { + columnChart = shallowMount(ColumnChart, { + propsData: { + graphData: { + queries: [ + { + x_label: 'Time', + y_label: 'Usage', + result: [ + { + metric: {}, + values: [ + [1495700554.925, '8.0390625'], + [1495700614.925, '8.0390625'], + [1495700674.925, '8.0390625'], + ], + }, + ], + }, + ], + }, + containerWidth: 100, + }, + }); + }); + + afterEach(() => { + columnChart.destroy(); + }); + + describe('wrapped components', () => { + describe('GitLab UI column chart', () => { + let glColumnChart; + + beforeEach(() => { + glColumnChart = columnChart.find(GlColumnChart); + }); + + it('is a Vue instance', () => { + expect(glColumnChart.isVueInstance()).toBe(true); + }); + + it('receives data properties needed for proper chart render', () => { + const props = glColumnChart.props(); + + expect(props.data).toBe(columnChart.vm.chartData); + expect(props.option).toBe(columnChart.vm.chartOptions); + }); + }); + }); +}); diff --git a/spec/javascripts/monitoring/charts/empty_chart_spec.js b/spec/javascripts/monitoring/charts/empty_chart_spec.js new file mode 100644 index 00000000000..bbfca27dc5a --- /dev/null +++ b/spec/javascripts/monitoring/charts/empty_chart_spec.js @@ -0,0 +1,29 @@ +import { shallowMount } from '@vue/test-utils'; +import EmptyChart from '~/monitoring/components/charts/empty_chart.vue'; + +describe('Empty Chart component', () => { + let emptyChart; + const graphTitle = 'Memory Usage'; + + beforeEach(() => { + emptyChart = shallowMount(EmptyChart, { + propsData: { + graphTitle, + }, + }); + }); + + afterEach(() => { + emptyChart.destroy(); + }); + + it('render the chart title', () => { + expect(emptyChart.find({ ref: 'graphTitle' }).text()).toBe(graphTitle); + }); + + describe('Computed props', () => { + it('sets the height for the svg container', () => { + expect(emptyChart.vm.svgContainerStyle.height).toBe('300px'); + }); + }); +}); diff --git a/spec/javascripts/monitoring/charts/single_stat_spec.js b/spec/javascripts/monitoring/charts/single_stat_spec.js index 12b73002f97..127a4a7955a 100644 --- a/spec/javascripts/monitoring/charts/single_stat_spec.js +++ b/spec/javascripts/monitoring/charts/single_stat_spec.js @@ -1,5 +1,6 @@ import { shallowMount } from '@vue/test-utils'; import SingleStatChart from '~/monitoring/components/charts/single_stat.vue'; +import { graphDataPrometheusQuery } from '../mock_data'; describe('Single Stat Chart component', () => { let singleStatChart; @@ -7,9 +8,7 @@ describe('Single Stat Chart component', () => { beforeEach(() => { singleStatChart = shallowMount(SingleStatChart, { propsData: { - title: 'Time to render', - value: 1, - unit: 'sec', + graphData: graphDataPrometheusQuery, }, }); }); @@ -19,9 +18,9 @@ describe('Single Stat Chart component', () => { }); describe('computed', () => { - describe('valueWithUnit', () => { + describe('engineeringNotation', () => { it('should interpolate the value and unit props', () => { - expect(singleStatChart.vm.valueWithUnit).toBe('1sec'); + expect(singleStatChart.vm.engineeringNotation).toBe('91MB'); }); }); }); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index f1d578648b8..02c3f303912 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -1,17 +1,21 @@ import Vue from 'vue'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { GlToast } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; import Dashboard from '~/monitoring/components/dashboard.vue'; import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants'; import * as types from '~/monitoring/stores/mutation_types'; import { createStore } from '~/monitoring/stores'; import axios from '~/lib/utils/axios_utils'; -import { +import MonitoringMock, { metricsGroupsAPIResponse, mockApiEndpoint, environmentData, singleGroupResponse, + dashboardGitResponse, } from './mock_data'; +const localVue = createLocalVue(); const propsData = { hasMetrics: false, documentationPath: '/path/to/docs', @@ -20,7 +24,7 @@ const propsData = { tagsPath: '/path/to/tags', projectPath: '/path/to/project', metricsEndpoint: mockApiEndpoint, - deploymentEndpoint: null, + deploymentsEndpoint: null, emptyGettingStartedSvgPath: '/path/to/getting-started.svg', emptyLoadingSvgPath: '/path/to/loading.svg', emptyNoDataSvgPath: '/path/to/no-data.svg', @@ -39,6 +43,7 @@ describe('Dashboard', () => { let mock; let store; let component; + let mockGraphData; beforeEach(() => { setFixtures(` @@ -57,21 +62,41 @@ describe('Dashboard', () => { }); afterEach(() => { - component.$destroy(); + if (component) { + component.$destroy(); + } mock.restore(); }); describe('no metrics are available yet', () => { - it('shows a getting started empty state when no metrics are present', () => { + beforeEach(() => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData }, store, }); + }); + it('shows a getting started empty state when no metrics are present', () => { expect(component.$el.querySelector('.prometheus-graphs')).toBe(null); expect(component.emptyState).toEqual('gettingStarted'); }); + + it('shows the environment selector', () => { + expect(component.$el.querySelector('.js-environments-dropdown')).toBeTruthy(); + }); + }); + + describe('no data found', () => { + it('shows the environment selector dropdown', () => { + component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { ...propsData, showEmptyState: true }, + store, + }); + + expect(component.$el.querySelector('.js-environments-dropdown')).toBeTruthy(); + }); }); describe('requests information to the server', () => { @@ -150,14 +175,24 @@ describe('Dashboard', () => { singleGroupResponse, ); - setTimeout(() => { - const dropdownMenuEnvironments = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item', - ); + Vue.nextTick() + .then(() => { + const dropdownMenuEnvironments = component.$el.querySelectorAll( + '.js-environments-dropdown .dropdown-item', + ); - expect(dropdownMenuEnvironments.length).toEqual(component.environments.length); - done(); - }); + expect(component.environments.length).toEqual(environmentData.length); + expect(dropdownMenuEnvironments.length).toEqual(component.environments.length); + + Array.from(dropdownMenuEnvironments).forEach((value, index) => { + if (environmentData[index].metrics_path) { + expect(value).toHaveAttr('href', environmentData[index].metrics_path); + } + }); + + done(); + }) + .catch(done.fail); }); it('hides the environments dropdown list when there is no environments', done => { @@ -212,7 +247,7 @@ describe('Dashboard', () => { Vue.nextTick() .then(() => { const dropdownItems = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item[active="true"]', + '.js-environments-dropdown .dropdown-item.active', ); expect(dropdownItems.length).toEqual(1); @@ -278,13 +313,9 @@ describe('Dashboard', () => { }); spyOn(component.$store, 'dispatch').and.stub(); - const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff'); + const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff').and.callThrough(); component.$store.commit( - `monitoringDashboard/${types.SET_ENVIRONMENTS_ENDPOINT}`, - '/environments', - ); - component.$store.commit( `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, environmentData, ); @@ -294,7 +325,7 @@ describe('Dashboard', () => { Vue.nextTick() .then(() => { expect(component.$store.dispatch).toHaveBeenCalled(); - expect(getTimeDiffSpy).toHaveBeenCalledWith(component.selectedTimeWindow); + expect(getTimeDiffSpy).toHaveBeenCalled(); done(); }) @@ -302,7 +333,17 @@ describe('Dashboard', () => { }); it('shows a specific time window selected from the url params', done => { - spyOnDependency(Dashboard, 'getParameterValues').and.returnValue(['thirtyMinutes']); + const start = 1564439536; + const end = 1564441336; + spyOnDependency(Dashboard, 'getTimeDiff').and.returnValue({ + start, + end, + }); + spyOnDependency(Dashboard, 'getParameterValues').and.callFake(param => { + if (param === 'start') return [start]; + if (param === 'end') return [end]; + return []; + }); component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), @@ -337,6 +378,71 @@ describe('Dashboard', () => { }); }); + describe('link to chart', () => { + let wrapper; + const currentDashboard = 'TEST_DASHBOARD'; + localVue.use(GlToast); + const link = () => wrapper.find('.js-chart-link'); + const clipboardText = () => link().element.dataset.clipboardText; + + beforeEach(done => { + mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); + + wrapper = shallowMount(DashboardComponent, { + localVue, + sync: false, + attachToDocument: true, + propsData: { ...propsData, hasMetrics: true, currentDashboard }, + store, + }); + + setTimeout(done); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('adds a copy button to the dropdown', () => { + expect(link().text()).toContain('Generate link to chart'); + }); + + it('contains a link to the dashboard', () => { + expect(clipboardText()).toContain(`dashboard=${currentDashboard}`); + expect(clipboardText()).toContain(`group=`); + expect(clipboardText()).toContain(`title=`); + expect(clipboardText()).toContain(`y_label=`); + }); + + it('undefined parameter is stripped', done => { + wrapper.setProps({ currentDashboard: undefined }); + + wrapper.vm.$nextTick(() => { + expect(clipboardText()).not.toContain(`dashboard=`); + expect(clipboardText()).toContain(`y_label=`); + done(); + }); + }); + + it('null parameter is stripped', done => { + wrapper.setProps({ currentDashboard: null }); + + wrapper.vm.$nextTick(() => { + expect(clipboardText()).not.toContain(`dashboard=`); + expect(clipboardText()).toContain(`y_label=`); + done(); + }); + }); + + it('creates a toast when clicked', () => { + spyOn(wrapper.vm.$toast, 'show').and.stub(); + + link().vm.$emit('click'); + + expect(wrapper.vm.$toast.show).toHaveBeenCalled(); + }); + }); + describe('when the window resizes', () => { beforeEach(() => { mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); @@ -402,4 +508,81 @@ describe('Dashboard', () => { }); }); }); + + describe('Dashboard dropdown', () => { + beforeEach(() => { + mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); + + component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + }, + store, + }); + + component.$store.dispatch('monitoringDashboard/setFeatureFlags', { + prometheusEndpoint: false, + multipleDashboardsEnabled: true, + }); + + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, + environmentData, + ); + + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, + singleGroupResponse, + ); + + component.$store.commit( + `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, + dashboardGitResponse, + ); + }); + + it('shows the dashboard dropdown', done => { + setTimeout(() => { + const dashboardDropdown = component.$el.querySelector('.js-dashboards-dropdown'); + + expect(dashboardDropdown).not.toEqual(null); + done(); + }); + }); + }); + + describe('when downloading metrics data as CSV', () => { + beforeEach(() => { + component = new DashboardComponent({ + propsData: { + ...propsData, + }, + store, + }); + store.commit( + `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, + MonitoringMock.data, + ); + [mockGraphData] = component.$store.state.monitoringDashboard.groups[0].metrics; + }); + + describe('csvText', () => { + it('converts metrics data from json to csv', () => { + const header = `timestamp,${mockGraphData.y_label}`; + const data = mockGraphData.queries[0].result[0].values; + const firstRow = `${data[0][0]},${data[0][1]}`; + + expect(component.csvText(mockGraphData)).toMatch(`^${header}\r\n${firstRow}`); + }); + }); + + describe('downloadCsv', () => { + it('produces a link with a Blob', () => { + expect(component.downloadCsv(mockGraphData)).toContain(`blob:`); + }); + }); + }); }); diff --git a/spec/javascripts/monitoring/dashboard_state_spec.js b/spec/javascripts/monitoring/dashboard_state_spec.js deleted file mode 100644 index 6b2be83aa8c..00000000000 --- a/spec/javascripts/monitoring/dashboard_state_spec.js +++ /dev/null @@ -1,101 +0,0 @@ -import Vue from 'vue'; -import EmptyState from '~/monitoring/components/empty_state.vue'; -import { statePaths } from './mock_data'; - -function createComponent(props) { - const Component = Vue.extend(EmptyState); - - return new Component({ - propsData: { - ...props, - settingsPath: statePaths.settingsPath, - clustersPath: statePaths.clustersPath, - documentationPath: statePaths.documentationPath, - emptyGettingStartedSvgPath: '/path/to/getting-started.svg', - emptyLoadingSvgPath: '/path/to/loading.svg', - emptyNoDataSvgPath: '/path/to/no-data.svg', - emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', - }, - }).$mount(); -} - -function getTextFromNode(component, selector) { - return component.$el.querySelector(selector).firstChild.nodeValue.trim(); -} - -describe('EmptyState', () => { - describe('Computed props', () => { - it('currentState', () => { - const component = createComponent({ - selectedState: 'gettingStarted', - }); - - expect(component.currentState).toBe(component.states.gettingStarted); - }); - - it('showButtonDescription returns a description with a link for the unableToConnect state', () => { - const component = createComponent({ - selectedState: 'unableToConnect', - }); - - expect(component.showButtonDescription).toEqual(true); - }); - - it('showButtonDescription returns the description without a link for any other state', () => { - const component = createComponent({ - selectedState: 'loading', - }); - - expect(component.showButtonDescription).toEqual(false); - }); - }); - - it('should show the gettingStarted state', () => { - const component = createComponent({ - selectedState: 'gettingStarted', - }); - - expect(component.$el.querySelector('svg')).toBeDefined(); - expect(getTextFromNode(component, '.state-title')).toEqual( - component.states.gettingStarted.title, - ); - - expect(getTextFromNode(component, '.state-description')).toEqual( - component.states.gettingStarted.description, - ); - - expect(getTextFromNode(component, '.btn-success')).toEqual( - component.states.gettingStarted.buttonText, - ); - }); - - it('should show the loading state', () => { - const component = createComponent({ - selectedState: 'loading', - }); - - expect(component.$el.querySelector('svg')).toBeDefined(); - expect(getTextFromNode(component, '.state-title')).toEqual(component.states.loading.title); - expect(getTextFromNode(component, '.state-description')).toEqual( - component.states.loading.description, - ); - - expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.loading.buttonText); - }); - - it('should show the unableToConnect state', () => { - const component = createComponent({ - selectedState: 'unableToConnect', - }); - - expect(component.$el.querySelector('svg')).toBeDefined(); - expect(getTextFromNode(component, '.state-title')).toEqual( - component.states.unableToConnect.title, - ); - - expect(component.$el.querySelector('.state-description a')).toBeDefined(); - expect(getTextFromNode(component, '.btn-success')).toEqual( - component.states.unableToConnect.buttonText, - ); - }); -}); diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index 82e42fe9ade..85e660d3925 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -922,3 +922,88 @@ export const metricsDashboardResponse = { }, status: 'success', }; + +export const dashboardGitResponse = [ + { + path: 'config/prometheus/common_metrics.yml', + display_name: 'Common Metrics', + default: true, + }, + { + path: '.gitlab/dashboards/super.yml', + display_name: 'Custom Dashboard 1', + default: false, + }, +]; + +export const graphDataPrometheusQuery = { + title: 'Super Chart A2', + type: 'single-stat', + weight: 2, + metrics: [ + { + id: 'metric_a1', + metric_id: 2, + query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024', + unit: 'MB', + label: 'Total Consumption', + prometheus_endpoint_path: + '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024', + }, + ], + queries: [ + { + metricId: null, + id: 'metric_a1', + metric_id: 2, + query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024', + unit: 'MB', + label: 'Total Consumption', + prometheus_endpoint_path: + '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024', + result: [ + { + metric: { job: 'prometheus' }, + value: ['2019-06-26T21:03:20.881Z', 91], + }, + ], + }, + ], +}; + +export const graphDataPrometheusQueryRange = { + title: 'Super Chart A1', + type: 'area', + weight: 2, + metrics: [ + { + id: 'metric_a1', + metric_id: 2, + query_range: + 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024', + unit: 'MB', + label: 'Total Consumption', + prometheus_endpoint_path: + '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024', + }, + ], + queries: [ + { + metricId: null, + id: 'metric_a1', + metric_id: 2, + query_range: + 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024', + unit: 'MB', + label: 'Total Consumption', + prometheus_endpoint_path: + '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024', + result: [ + { + metric: {}, + values: [[1495700554.925, '8.0390625'], [1495700614.925, '8.0390625']], + }, + ], + }, + ], +}; diff --git a/spec/javascripts/monitoring/panel_type_spec.js b/spec/javascripts/monitoring/panel_type_spec.js new file mode 100644 index 00000000000..086be628093 --- /dev/null +++ b/spec/javascripts/monitoring/panel_type_spec.js @@ -0,0 +1,78 @@ +import { shallowMount } from '@vue/test-utils'; +import PanelType from '~/monitoring/components/panel_type.vue'; +import EmptyChart from '~/monitoring/components/charts/empty_chart.vue'; +import AreaChart from '~/monitoring/components/charts/area.vue'; +import { graphDataPrometheusQueryRange } from './mock_data'; +import { createStore } from '~/monitoring/stores'; + +describe('Panel Type component', () => { + let store; + let panelType; + const dashboardWidth = 100; + + describe('When no graphData is available', () => { + let glEmptyChart; + // Deep clone object before modifying + const graphDataNoResult = JSON.parse(JSON.stringify(graphDataPrometheusQueryRange)); + graphDataNoResult.queries[0].result = []; + + beforeEach(() => { + panelType = shallowMount(PanelType, { + propsData: { + clipboardText: 'dashboard_link', + dashboardWidth, + graphData: graphDataNoResult, + }, + }); + }); + + afterEach(() => { + panelType.destroy(); + }); + + describe('Empty Chart component', () => { + beforeEach(() => { + glEmptyChart = panelType.find(EmptyChart); + }); + + it('is a Vue instance', () => { + expect(glEmptyChart.isVueInstance()).toBe(true); + }); + + it('it receives a graph title', () => { + const props = glEmptyChart.props(); + + expect(props.graphTitle).toBe(panelType.vm.graphData.title); + }); + }); + }); + + describe('when Graph data is available', () => { + const exampleText = 'example_text'; + + beforeEach(() => { + store = createStore(); + panelType = shallowMount(PanelType, { + propsData: { + clipboardText: exampleText, + dashboardWidth, + graphData: graphDataPrometheusQueryRange, + }, + store, + }); + }); + + describe('Area Chart panel type', () => { + it('is rendered', () => { + expect(panelType.find(AreaChart).exists()).toBe(true); + }); + + it('sets clipboard text on the dropdown', () => { + const link = () => panelType.find('.js-chart-link'); + const clipboardText = () => link().element.dataset.clipboardText; + + expect(clipboardText()).toBe(exampleText); + }); + }); + }); +}); diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js index 8c02e21eda2..955a39e03a5 100644 --- a/spec/javascripts/monitoring/store/actions_spec.js +++ b/spec/javascripts/monitoring/store/actions_spec.js @@ -22,6 +22,7 @@ import { environmentData, metricsDashboardResponse, metricsGroupsAPIResponse, + dashboardGitResponse, } from '../mock_data'; describe('Monitoring store actions', () => { @@ -51,9 +52,9 @@ describe('Monitoring store actions', () => { it('commits RECEIVE_DEPLOYMENTS_DATA_SUCCESS on error', done => { const dispatch = jasmine.createSpy(); const { state } = store; - state.deploymentEndpoint = '/success'; + state.deploymentsEndpoint = '/success'; - mock.onGet(state.deploymentEndpoint).reply(200, { + mock.onGet(state.deploymentsEndpoint).reply(200, { deployments: deploymentData, }); @@ -68,9 +69,9 @@ describe('Monitoring store actions', () => { it('commits RECEIVE_DEPLOYMENTS_DATA_FAILURE on error', done => { const dispatch = jasmine.createSpy(); const { state } = store; - state.deploymentEndpoint = '/error'; + state.deploymentsEndpoint = '/error'; - mock.onGet(state.deploymentEndpoint).reply(500); + mock.onGet(state.deploymentsEndpoint).reply(500); fetchDeploymentsData({ state, dispatch }) .then(() => { @@ -212,17 +213,19 @@ describe('Monitoring store actions', () => { describe('receiveMetricsDashboardSuccess', () => { let commit; let dispatch; + let state; beforeEach(() => { commit = jasmine.createSpy(); dispatch = jasmine.createSpy(); + state = storeState(); }); it('stores groups ', () => { const params = {}; const response = metricsDashboardResponse; - receiveMetricsDashboardSuccess({ commit, dispatch }, { response, params }); + receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response, params }); expect(commit).toHaveBeenCalledWith( types.RECEIVE_METRICS_DATA_SUCCESS, @@ -231,6 +234,18 @@ describe('Monitoring store actions', () => { expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetrics', params); }); + + it('sets the dashboards loaded from the repository', () => { + const params = {}; + const response = metricsDashboardResponse; + + response.all_dashboards = dashboardGitResponse; + state.multipleDashboardsEnabled = true; + + receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response, params }); + + expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse); + }); }); describe('receiveMetricsDashboardFailure', () => { @@ -298,8 +313,8 @@ describe('Monitoring store actions', () => { it('commits prometheus query result', done => { const commit = jasmine.createSpy(); const params = { - start: '1557216349.469', - end: '1557218149.469', + start: '2019-08-06T12:40:02.184Z', + end: '2019-08-06T20:40:02.184Z', }; const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0]; const state = storeState(); diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/javascripts/monitoring/store/mutations_spec.js index 02ff5847b34..43776b1b7f2 100644 --- a/spec/javascripts/monitoring/store/mutations_spec.js +++ b/spec/javascripts/monitoring/store/mutations_spec.js @@ -1,7 +1,12 @@ import mutations from '~/monitoring/stores/mutations'; import * as types from '~/monitoring/stores/mutation_types'; import state from '~/monitoring/stores/state'; -import { metricsGroupsAPIResponse, deploymentData, metricsDashboardResponse } from '../mock_data'; +import { + metricsGroupsAPIResponse, + deploymentData, + metricsDashboardResponse, + dashboardGitResponse, +} from '../mock_data'; describe('Monitoring mutations', () => { let stateCopy; @@ -110,12 +115,14 @@ describe('Monitoring mutations', () => { environmentsEndpoint: 'environments.json', deploymentsEndpoint: 'deployments.json', dashboardEndpoint: 'dashboard.json', + projectPath: '/gitlab-org/gitlab-ce', }); expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json'); expect(stateCopy.environmentsEndpoint).toEqual('environments.json'); expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json'); expect(stateCopy.dashboardEndpoint).toEqual('dashboard.json'); + expect(stateCopy.projectPath).toEqual('/gitlab-org/gitlab-ce'); }); }); @@ -156,4 +163,12 @@ describe('Monitoring mutations', () => { expect(stateCopy.metricsWithData).toEqual([]); }); }); + + describe('SET_ALL_DASHBOARDS', () => { + it('stores the dashboards loaded from the git repository', () => { + mutations[types.SET_ALL_DASHBOARDS](stateCopy, dashboardGitResponse); + + expect(stateCopy.allDashboards).toEqual(dashboardGitResponse); + }); + }); }); diff --git a/spec/javascripts/monitoring/store/utils_spec.js b/spec/javascripts/monitoring/store/utils_spec.js new file mode 100644 index 00000000000..73dd370ffb3 --- /dev/null +++ b/spec/javascripts/monitoring/store/utils_spec.js @@ -0,0 +1,37 @@ +import { groupQueriesByChartInfo } from '~/monitoring/stores/utils'; + +describe('groupQueriesByChartInfo', () => { + let input; + let output; + + it('groups metrics with the same chart title and y_axis label', () => { + input = [ + { title: 'title', y_label: 'MB', queries: [{}] }, + { title: 'title', y_label: 'MB', queries: [{}] }, + { title: 'new title', y_label: 'MB', queries: [{}] }, + ]; + + output = [ + { title: 'title', y_label: 'MB', queries: [{ metricId: null }, { metricId: null }] }, + { title: 'new title', y_label: 'MB', queries: [{ metricId: null }] }, + ]; + + expect(groupQueriesByChartInfo(input)).toEqual(output); + }); + + // Functionality associated with the /additional_metrics endpoint + it("associates a chart's stringified metric_id with the metric", () => { + input = [{ id: 3, title: 'new title', y_label: 'MB', queries: [{}] }]; + output = [{ id: 3, title: 'new title', y_label: 'MB', queries: [{ metricId: '3' }] }]; + + expect(groupQueriesByChartInfo(input)).toEqual(output); + }); + + // Functionality associated with the /metrics_dashboard endpoint + it('aliases a stringified metrics_id on the metric to the metricId key', () => { + input = [{ title: 'new title', y_label: 'MB', queries: [{ metric_id: 3 }] }]; + output = [{ title: 'new title', y_label: 'MB', queries: [{ metricId: '3', metric_id: 3 }] }]; + + expect(groupQueriesByChartInfo(input)).toEqual(output); + }); +}); diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js index e3c455d1686..e22e8cdc03d 100644 --- a/spec/javascripts/monitoring/utils_spec.js +++ b/spec/javascripts/monitoring/utils_spec.js @@ -1,29 +1,64 @@ -import { getTimeDiff } from '~/monitoring/utils'; +import { getTimeDiff, graphDataValidatorForValues } from '~/monitoring/utils'; import { timeWindows } from '~/monitoring/constants'; +import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data'; describe('getTimeDiff', () => { + function secondsBetween({ start, end }) { + return (new Date(end) - new Date(start)) / 1000; + } + + function minutesBetween(timeRange) { + return secondsBetween(timeRange) / 60; + } + + function hoursBetween(timeRange) { + return minutesBetween(timeRange) / 60; + } + it('defaults to an 8 hour (28800s) difference', () => { const params = getTimeDiff(); - expect(params.end - params.start).toEqual(28800); + expect(hoursBetween(params)).toEqual(8); }); it('accepts time window as an argument', () => { - const params = getTimeDiff(timeWindows.thirtyMinutes); + const params = getTimeDiff('thirtyMinutes'); - expect(params.end - params.start).not.toEqual(28800); + expect(minutesBetween(params)).toEqual(30); }); it('returns a value for every defined time window', () => { const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours'); - nonDefaultWindows.forEach(window => { - const params = getTimeDiff(timeWindows[window]); - const diff = params.end - params.start; + nonDefaultWindows.forEach(timeWindow => { + const params = getTimeDiff(timeWindow); - // Ensure we're not returning the default, 28800 (the # of seconds in 8 hrs) - expect(diff).not.toEqual(28800); - expect(typeof diff).toEqual('number'); + // Ensure we're not returning the default + expect(hoursBetween(params)).not.toEqual(8); }); }); }); + +describe('graphDataValidatorForValues', () => { + /* + * When dealing with a metric using the query format, e.g. + * query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024' + * the validator will look for the `value` key instead of `values` + */ + it('validates data with the query format', () => { + const validGraphData = graphDataValidatorForValues(true, graphDataPrometheusQuery); + + expect(validGraphData).toBe(true); + }); + + /* + * When dealing with a metric using the query?range format, e.g. + * query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024', + * the validator will look for the `values` key instead of `value` + */ + it('validates data with the query_range format', () => { + const validGraphData = graphDataValidatorForValues(false, graphDataPrometheusQueryRange); + + expect(validGraphData).toBe(true); + }); +}); |
