From 772acb5e9137926c4d2e98e708464c298734f03c Mon Sep 17 00:00:00 2001 From: samdbeckham Date: Wed, 28 Nov 2018 18:59:01 +0000 Subject: [PATCH 1/8] Adds the charts to the GSD Where GSD is the group security dashboard - Adds the vuex store for vulnerbilities timeline - Hooks up dummy data - Adds a vulnerabilties-chart component - Adds a vulnerabilties-chart-tooltip component - Adds a vulnerabilties-chart-label component - Adds Tests for all of the above --- .../security_dashboard/components/app.vue | 6 +- .../components/vulnerability_chart.vue | 190 ++++++ .../components/vulnerability_chart_label.vue | 33 + .../vulnerability_chart_tooltip.vue | 38 ++ .../store/modules/vulnerabilities/actions.js | 38 ++ .../modules/vulnerabilities/mutation_types.js | 5 + .../modules/vulnerabilities/mutations.js | 15 + .../store/modules/vulnerabilities/state.js | 8 +- .../vulnerability_chart_label_spec.js | 72 +++ .../components/vulnerability_chart_spec.js | 42 ++ .../vulnerability_chart_tooltip_spec.js | 54 ++ .../store/vulnerabilities/actions_spec.js | 128 ++++ .../mock_data_vulnerabilities_timeline.json | 566 ++++++++++++++++++ .../store/vulnerabilities/mutations_spec.js | 60 ++ 14 files changed, 1252 insertions(+), 3 deletions(-) create mode 100644 ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue create mode 100644 ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart_label.vue create mode 100644 ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart_tooltip.vue create mode 100644 ee/spec/javascripts/security_dashboard/components/vulnerability_chart_label_spec.js create mode 100644 ee/spec/javascripts/security_dashboard/components/vulnerability_chart_spec.js create mode 100644 ee/spec/javascripts/security_dashboard/components/vulnerability_chart_tooltip_spec.js create mode 100644 ee/spec/javascripts/security_dashboard/store/vulnerabilities/data/mock_data_vulnerabilities_timeline.json diff --git a/ee/app/assets/javascripts/security_dashboard/components/app.vue b/ee/app/assets/javascripts/security_dashboard/components/app.vue index 00b4e36fed34fa..9e5d696fb47581 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/app.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/app.vue @@ -6,6 +6,7 @@ import Tabs from '~/vue_shared/components/tabs/tabs'; import Tab from '~/vue_shared/components/tabs/tab.vue'; import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue'; import SecurityDashboardTable from './security_dashboard_table.vue'; +import VulnerabilityChart from './vulnerability_chart.vue'; import VulnerabilityCountList from './vulnerability_count_list.vue'; import Icon from '~/vue_shared/components/icon.vue'; import popover from '~/vue_shared/directives/popover'; @@ -21,6 +22,7 @@ export default { SecurityDashboardTable, Tab, Tabs, + VulnerabilityChart, VulnerabilityCountList, }, props: { @@ -108,7 +110,9 @@ export default { -
{{ __('Vulnerability List') }}
+

{{ __('Vulnerability Chart') }}

+ +

{{ __('Vulnerability List') }}

+import dateFormat from 'dateformat'; +import { mapState, mapActions } from 'vuex'; +import { GlChart } from '@gitlab/ui'; +import ChartTooltip from './vulnerability_chart_tooltip.vue'; + +export default { + name: 'VulnerabilityChart', + components: { + GlChart, + ChartTooltip, + }, + data: () => ({ + tooltipTitle: '', + tooltipEntries: [], + lines: [ + { + name: 'Critical', + color: '#C0341D', + }, + { + name: 'High', + color: '#DE7E00', + }, + { + name: 'Medium', + color: '#6E49CB', + }, + { + name: 'Low', + color: '#4F4F4F', + }, + { + name: 'All', + color: '#1F78D1', + }, + ], + }), + computed: { + ...mapState('vulnerabilities', ['vulnerabilitiesTimeline']), + series() { + return this.lines.map(line => { + const { name, color } = line; + const timeline = this.vulnerabilitiesTimeline[name.toLowerCase()]; + const data = Object.entries(timeline); + return timeline + ? { + borderWidth: 2, + color, + data, + name, + symbol: 'circle', + symbolSize: 6, + type: 'line', + } + : null; + }); + }, + options() { + return { + grid: { + bottom: 85, + left: 75, + right: 15, + top: 5, + }, + tooltip: { + backgroundColor: '#fff', + borderWidth: 0, + extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);', + formatter: this.renderTooltip, + padding: 0, + textStyle: { + color: '#4F4F4F', + }, + trigger: 'axis', + }, + xAxis: { + axisLabel: { + color: '#707070', + formatter: date => dateFormat(date, 'd mmm'), + margin: 8, + rotate: 45, + }, + axisLine: { + lineStyle: { + color: '#dedede', + width: 2, + }, + }, + axisTick: { + show: false, + }, + maxInterval: 1000 * 60 * 60 * 24 * 7, + min: Date.now() - 1000 * 60 * 60 * 24 * 28, + name: 'Date', + nameGap: 50, + nameLocation: 'center', + nameTextStyle: { + color: '#2e2e2e', + fontWeight: 'bold', + }, + splitNumber: 12, + type: 'time', + }, + yAxis: { + axisLabel: { + color: '#707070', + }, + axisLine: { + lineStyle: { + color: '#dedede', + width: 2, + }, + }, + axisTick: { + show: false, + }, + interval: 25, + name: 'Vulnerabilities', + nameGap: 42, + nameLocation: 'center', + nameRotation: 90, + nameTextStyle: { + color: '#2e2e2e', + fontWeight: 'bold', + }, + type: 'value', + }, + legend: { + bottom: 0, + icon: 'path://M0,0H120V40H0Z', + itemGap: 15, + left: 70, + textStyle: { + color: '#4F4F4F', + fontWeight: 'bold', + }, + type: 'scroll', + }, + series: this.series, + }; + }, + }, + created() { + this.fetchVulnerabilitiesTimeline(); + }, + methods: { + ...mapActions('vulnerabilities', ['fetchVulnerabilitiesTimeline']), + renderTooltip(params, ticket, callback) { + this.tooltipTitle = dateFormat(params[0].axisValueLabel, 'd mmmm'); + this.tooltipEntries = params; + this.$nextTick(() => callback(ticket, this.$refs.tooltip.$el.innerHTML)); + return ' '; + }, + }, +}; + + + + + diff --git a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart_label.vue b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart_label.vue new file mode 100644 index 00000000000000..ec399c7fb799f5 --- /dev/null +++ b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart_label.vue @@ -0,0 +1,33 @@ + + + diff --git a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart_tooltip.vue b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart_tooltip.vue new file mode 100644 index 00000000000000..11894a11c6a689 --- /dev/null +++ b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart_tooltip.vue @@ -0,0 +1,38 @@ + + + diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/actions.js b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/actions.js index adb8a1597c000a..adbd33b97de6e5 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/actions.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/actions.js @@ -5,6 +5,9 @@ import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; import { s__ } from '~/locale'; import createFlash from '~/flash'; +// TODO: Remove this mock +import mockData from '../../../../../../../spec/javascripts/security_dashboard/store/vulnerabilities/data/mock_data_vulnerabilities_timeline.json'; + export const setVulnerabilitiesEndpoint = ({ commit }, endpoint) => { commit(types.SET_VULNERABILITIES_ENDPOINT, endpoint); }; @@ -204,4 +207,39 @@ export const receiveRevertDismissalError = ({ commit }, { flashError }) => { } }; +export const setVulnerabilitiesTimelineEndpoint = ({ commit }, endpoint) => { + commit(types.SET_VULNERABILITIES_TIMELINE_ENDPOINT, endpoint); +}; + +export const fetchVulnerabilitiesTimeline = ({ state, dispatch }) => { + dispatch('requestVulnerabilitiesTimeline'); + + // TODO: Remove this mocking + return dispatch('receiveVulnerabilitiesTimelineSuccess', { data: mockData }); + + axios({ + method: 'GET', + url: state.vulnerabilitiesTimelineEndpoint, + }) + .then(response => { + const { data } = response; + dispatch('receiveVulnerabilitiesTimelineSuccess', { data }); + }) + .catch(() => { + dispatch('receiveVulnerabilitiesTimelineError'); + }); +}; + +export const requestVulnerabilitiesTimeline = ({ commit }) => { + commit(types.REQUEST_VULNERABILITIES_TIMELINE); +}; + +export const receiveVulnerabilitiesTimelineSuccess = ({ commit }, { data }) => { + commit(types.RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS, data); +}; + +export const receiveVulnerabilitiesTimelineError = ({ commit }) => { + commit(types.RECEIVE_VULNERABILITIES_TIMELINE_ERROR); +}; + export default () => {}; diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutation_types.js b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutation_types.js index e3d2c93aebc29e..1d1eddd0880192 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutation_types.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutation_types.js @@ -8,6 +8,11 @@ export const REQUEST_VULNERABILITIES_COUNT = 'REQUEST_VULNERABILITIES_COUNT'; export const RECEIVE_VULNERABILITIES_COUNT_SUCCESS = 'RECEIVE_VULNERABILITIES_COUNT_SUCCESS'; export const RECEIVE_VULNERABILITIES_COUNT_ERROR = 'RECEIVE_VULNERABILITIES_COUNT_ERROR'; +export const SET_VULNERABILITIES_TIMELINE_ENDPOINT = 'SET_VULNERABILITIES_TIMELINE_ENDPOINT'; +export const REQUEST_VULNERABILITIES_TIMELINE = 'REQUEST_VULNERABILITIES_TIMELINE'; +export const RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS = 'RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS'; +export const RECEIVE_VULNERABILITIES_TIMELINE_ERROR = 'RECEIVE_VULNERABILITIES_TIMELINE_ERROR'; + export const SET_MODAL_DATA = 'SET_MODAL_DATA'; export const REQUEST_CREATE_ISSUE = 'REQUEST_CREATE_ISSUE'; diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutations.js b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutations.js index 757bc1bb9ad1c4..377d0eb3d8cca2 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutations.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutations.js @@ -35,6 +35,21 @@ export default { state.isLoadingVulnerabilitiesCount = false; state.errorLoadingVulnerabilitiesCount = true; }, + [types.SET_VULNERABILITIES_TIMELINE_ENDPOINT](state, payload) { + state.vulnerabilitiesTimelineEndpoint = payload; + }, + [types.REQUEST_VULNERABILITIES_TIMELINE](state) { + state.isLoadingVulnerabilitiesTimeline = true; + state.errorLoadingVulnerabilitiesTimeline = false; + }, + [types.RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS](state, payload) { + state.isLoadingVulnerabilitiesTimeline = false; + state.vulnerabilitiesTimeline = payload; + }, + [types.RECEIVE_VULNERABILITIES_TIMELINE_ERROR](state) { + state.isLoadingVulnerabilitiesTimeline = false; + state.errorLoadingVulnerabilitiesTimeline = true; + }, [types.SET_MODAL_DATA](state, payload) { const { vulnerability } = payload; diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/state.js b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/state.js index 7a642b99ca8293..322b1e6f0bc95c 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/state.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/state.js @@ -3,12 +3,16 @@ import { s__ } from '~/locale'; export default () => ({ isLoadingVulnerabilities: true, errorLoadingVulnerabilities: false, + vulnerabilities: [], isLoadingVulnerabilitiesCount: true, errorLoadingVulnerabilitiesCount: false, - pageInfo: {}, - vulnerabilities: [], vulnerabilitiesCount: {}, + isLoadingVulnerabilitiesTimeline: true, + errorLoadingVulnerabilitiesTimeline: false, + vulnerabilitiesTimeline: {}, + pageInfo: {}, vulnerabilitiesCountEndpoint: null, + vulnerabilitiesTimelineEndpoint: null, vulnerabilitiesEndpoint: null, activeVulnerability: null, modal: { diff --git a/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_label_spec.js b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_label_spec.js new file mode 100644 index 00000000000000..4fdc7faca0833a --- /dev/null +++ b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_label_spec.js @@ -0,0 +1,72 @@ +import Vue from 'vue'; +import component from 'ee/security_dashboard/components/vulnerability_chart_label.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +// TODO: Move this to a helper if it's deemed a sensible approach +function hexToRgb(hex) { + /* eslint-disable no-bitwise */ + const cleanHex = hex.replace('#', ''); + const bigint = parseInt(cleanHex, 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; + /* eslint-enable no-bitwise */ + + return `rgb(${r}, ${g}, ${b})`; +} + +describe('Vulnerability Chart Label component', () => { + const Component = Vue.extend(component); + let vm; + const props = { + name: 'Chuck Norris', + color: '#BADA55', + value: 42, + }; + + describe('default', () => { + beforeEach(() => { + vm = mountComponent(Component, props); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render the name', () => { + const name = vm.$el.querySelector('.js-name'); + + expect(name.textContent).toContain(props.name); + }); + + it('should render the value', () => { + const value = vm.$el.querySelector('.js-value'); + + expect(value.textContent).toContain(props.value); + }); + + it('should render the color', () => { + const color = vm.$el.querySelector('.js-color'); + + expect(color.style.backgroundColor).toBe(hexToRgb(props.color)); + }); + }); + + describe('when the value is 0', () => { + const newProps = { ...props, value: 0 }; + + beforeEach(() => { + vm = mountComponent(Component, newProps); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should still render the value, but show a "0"', () => { + const value = vm.$el.querySelector('.js-value'); + + expect(value.textContent).toContain(newProps.value); + }); + }); +}); diff --git a/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_spec.js b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_spec.js new file mode 100644 index 00000000000000..72bfe6281d1251 --- /dev/null +++ b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_spec.js @@ -0,0 +1,42 @@ +import Vue from 'vue'; +import MockAdapater from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; + +import component from 'ee/security_dashboard/components/vulnerability_chart.vue'; +import createStore from 'ee/security_dashboard/store'; +import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import waitForPromises from 'spec/helpers/wait_for_promises'; + +import { resetStore } from '../helpers'; +import mockDataVulnerabilitiesTimeline from '../store/vulnerabilities/data/mock_data_vulnerabilities_timeline.json'; + +describe('Vulnerabilities Chart', () => { + const Component = Vue.extend(component); + const vulnerabilitiesTimelineEndpoint = '/vulnerabilitiesEndpoint.json'; + let store; + let mock; + let vm; + + beforeEach(() => { + store = createStore(); + store.state.vulnerabilities.vulnerabilitiesTimelineEndpoint = vulnerabilitiesTimelineEndpoint; + mock = new MockAdapater(axios); + mock.onGet(vulnerabilitiesTimelineEndpoint).replyOnce(200, mockDataVulnerabilitiesTimeline); + vm = mountComponentWithStore(Component, { store }); + }); + + afterEach(() => { + resetStore(store); + vm.$destroy(); + mock.restore(); + }); + + it('should render the e-chart canvas', done => { + waitForPromises() + .then(() => { + expect(vm.$el.querySelector('canvas')).not.toBeNull(); + done(); + }) + .catch(done.fail); + }); +}); diff --git a/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_tooltip_spec.js b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_tooltip_spec.js new file mode 100644 index 00000000000000..4a36df3803085e --- /dev/null +++ b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_tooltip_spec.js @@ -0,0 +1,54 @@ +import Vue from 'vue'; +import component from 'ee/security_dashboard/components/vulnerability_chart_tooltip.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +describe('Vulnerability Chart Tooltip component', () => { + const Component = Vue.extend(component); + let vm; + let props; + + beforeEach(() => { + const entries = [ + { + dataIndex: 1, + seriesId: 'critical_0', + seriesName: 'critical', + color: '#00f', + data: ['critical', 32], + }, + { + dataIndex: 1, + seriesId: 'high_0', + seriesName: 'high', + color: '#0f0', + data: ['high', 22], + }, + { + dataIndex: 1, + seriesId: 'low_0', + seriesName: 'low', + color: '#f00', + data: ['low', 2], + }, + ]; + const title = 'Tooltip Title'; + props = { title, entries }; + vm = mountComponent(Component, props); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render the title', () => { + const header = vm.$el.querySelector('.card-header'); + + expect(header.textContent).toContain(props.title); + }); + + it('should render three legends', () => { + const legends = vm.$el.querySelectorAll('.js-chart-label'); + + expect(legends).toHaveLength(3); + }); +}); diff --git a/ee/spec/javascripts/security_dashboard/store/vulnerabilities/actions_spec.js b/ee/spec/javascripts/security_dashboard/store/vulnerabilities/actions_spec.js index 33597f81ecf86a..a34d411c3012c3 100644 --- a/ee/spec/javascripts/security_dashboard/store/vulnerabilities/actions_spec.js +++ b/ee/spec/javascripts/security_dashboard/store/vulnerabilities/actions_spec.js @@ -9,6 +9,7 @@ import * as actions from 'ee/security_dashboard/store/modules/vulnerabilities/ac import mockDataVulnerabilities from './data/mock_data_vulnerabilities.json'; import mockDataVulnerabilitiesCount from './data/mock_data_vulnerabilities_count.json'; +import mockDataVulnerabilitiesTimeline from './data/mock_data_vulnerabilities_timeline.json'; describe('vulnerabiliites count actions', () => { const data = mockDataVulnerabilitiesCount; @@ -634,3 +635,130 @@ describe('revert vulnerability dismissal', () => { }); }); }); + +describe('vulnerabiliites timeline actions', () => { + const data = mockDataVulnerabilitiesTimeline; + + describe('setVulnerabilitiesTimelineEndpoint', () => { + it('should commit the correct mutuation', done => { + const state = initialState; + const endpoint = 'fakepath.json'; + + testAction( + actions.setVulnerabilitiesTimelineEndpoint, + endpoint, + state, + [ + { + type: types.SET_VULNERABILITIES_TIMELINE_ENDPOINT, + payload: endpoint, + }, + ], + [], + done, + ); + }); + }); + + describe('fetchVulnerabilitesTimeline', () => { + let mock; + const state = initialState; + + beforeEach(() => { + state.vulnerabilitiesCountEndpoint = `${TEST_HOST}/vulnerabilities_timeline.json`; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('on success', () => { + beforeEach(() => { + mock.onGet(state.vulnerabilitiesTimelineEndpoint).replyOnce(200, data); + }); + + it('should dispatch the request and success actions', done => { + testAction( + actions.fetchVulnerabilitiesTimeline, + {}, + state, + [], + [ + { type: 'requestVulnerabilitiesTimeline' }, + { + type: 'receiveVulnerabilitiesTimelineSuccess', + payload: { data }, + }, + ], + done, + ); + }); + }); + + describe('on error', () => { + beforeEach(() => { + mock.onGet(state.vulnerabilitiesTimelineEndpoint).replyOnce(404, {}); + }); + + it('should dispatch the request and error actions', done => { + testAction( + actions.fetchVulnerabilitiesTimeline, + {}, + state, + [], + [ + { type: 'requestVulnerabilitiesTimeline' }, + { type: 'receiveVulnerabilitiesTimelineError' }, + ], + done, + ); + }); + }); + }); + + describe('requestVulnerabilitesTimeline', () => { + it('should commit the request mutation', done => { + const state = initialState; + + testAction( + actions.requestVulnerabilitiesTimeline, + {}, + state, + [{ type: types.REQUEST_VULNERABILITIES_TIMELINE }], + [], + done, + ); + }); + }); + + describe('receiveVulnerabilitesTimelineSuccess', () => { + it('should commit the success mutation', done => { + const state = initialState; + + testAction( + actions.receiveVulnerabilitiesTimelineSuccess, + { data }, + state, + [{ type: types.RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS, payload: data }], + [], + done, + ); + }); + }); + + describe('receivetVulnerabilitesTimelineError', () => { + it('should commit the error mutation', done => { + const state = initialState; + + testAction( + actions.receiveVulnerabilitiesTimelineError, + {}, + state, + [{ type: types.RECEIVE_VULNERABILITIES_TIMELINE_ERROR }], + [], + done, + ); + }); + }); +}); diff --git a/ee/spec/javascripts/security_dashboard/store/vulnerabilities/data/mock_data_vulnerabilities_timeline.json b/ee/spec/javascripts/security_dashboard/store/vulnerabilities/data/mock_data_vulnerabilities_timeline.json new file mode 100644 index 00000000000000..64fa912aefd8e0 --- /dev/null +++ b/ee/spec/javascripts/security_dashboard/store/vulnerabilities/data/mock_data_vulnerabilities_timeline.json @@ -0,0 +1,566 @@ +{ + "low": { + "2018-10-1": 87, + "2018-10-2": 88, + "2018-10-3": 90, + "2018-10-4": 89, + "2018-10-5": 89, + "2018-10-6": 80, + "2018-10-7": 85, + "2018-10-8": 67, + "2018-10-9": 84, + "2018-10-10": 72, + "2018-10-11": 67, + "2018-10-12": 86, + "2018-10-13": 70, + "2018-10-14": 68, + "2018-10-15": 61, + "2018-10-16": 74, + "2018-10-17": 67, + "2018-10-18": 78, + "2018-10-19": 65, + "2018-10-20": 72, + "2018-10-21": 78, + "2018-10-22": 81, + "2018-10-23": 62, + "2018-10-24": 86, + "2018-10-25": 79, + "2018-10-26": 86, + "2018-10-27": 78, + "2018-10-28": 75, + "2018-10-29": 67, + "2018-10-30": 87, + "2018-10-31": 86, + "2018-11-1": 75, + "2018-11-2": 81, + "2018-11-3": 88, + "2018-11-4": 82, + "2018-11-5": 76, + "2018-11-6": 76, + "2018-11-7": 68, + "2018-11-8": 86, + "2018-11-9": 70, + "2018-11-10": 74, + "2018-11-11": 60, + "2018-11-12": 61, + "2018-11-13": 73, + "2018-11-14": 90, + "2018-11-15": 69, + "2018-11-16": 78, + "2018-11-17": 81, + "2018-11-18": 60, + "2018-11-19": 86, + "2018-11-20": 72, + "2018-11-21": 73, + "2018-11-22": 60, + "2018-11-23": 88, + "2018-11-24": 70, + "2018-11-25": 60, + "2018-11-26": 72, + "2018-11-27": 71, + "2018-11-28": 77, + "2018-11-29": 77, + "2018-11-30": 70, + "2018-12-1": 69, + "2018-12-2": 80, + "2018-12-3": 73, + "2018-12-4": 71, + "2018-12-5": 84, + "2018-12-6": 82, + "2018-12-7": 68, + "2018-12-8": 66, + "2018-12-9": 76, + "2018-12-10": 81, + "2018-12-11": 61, + "2018-12-12": 78, + "2018-12-13": 85, + "2018-12-14": 74, + "2018-12-15": 65, + "2018-12-16": 90, + "2018-12-17": 87, + "2018-12-18": 83, + "2018-12-19": 72, + "2018-12-20": 79, + "2018-12-21": 83, + "2018-12-22": 70, + "2018-12-23": 75, + "2018-12-24": 77, + "2018-12-25": 68, + "2018-12-26": 86, + "2018-12-27": 76, + "2018-12-28": 86, + "2018-12-29": 89, + "2018-12-30": 73, + "2018-12-31": 70 + }, + "medium": { + "2018-10-1": 73, + "2018-10-2": 76, + "2018-10-3": 101, + "2018-10-4": 84, + "2018-10-5": 90, + "2018-10-6": 97, + "2018-10-7": 77, + "2018-10-8": 81, + "2018-10-9": 98, + "2018-10-10": 83, + "2018-10-11": 82, + "2018-10-12": 70, + "2018-10-13": 99, + "2018-10-14": 83, + "2018-10-15": 81, + "2018-10-16": 80, + "2018-10-17": 82, + "2018-10-18": 89, + "2018-10-19": 89, + "2018-10-20": 71, + "2018-10-21": 73, + "2018-10-22": 74, + "2018-10-23": 83, + "2018-10-24": 91, + "2018-10-25": 85, + "2018-10-26": 90, + "2018-10-27": 77, + "2018-10-28": 102, + "2018-10-29": 75, + "2018-10-30": 78, + "2018-10-31": 70, + "2018-11-1": 90, + "2018-11-2": 96, + "2018-11-3": 98, + "2018-11-4": 88, + "2018-11-5": 79, + "2018-11-6": 91, + "2018-11-7": 101, + "2018-11-8": 75, + "2018-11-9": 75, + "2018-11-10": 84, + "2018-11-11": 70, + "2018-11-12": 89, + "2018-11-13": 104, + "2018-11-14": 90, + "2018-11-15": 81, + "2018-11-16": 102, + "2018-11-17": 86, + "2018-11-18": 80, + "2018-11-19": 71, + "2018-11-20": 72, + "2018-11-21": 103, + "2018-11-22": 89, + "2018-11-23": 83, + "2018-11-24": 79, + "2018-11-25": 87, + "2018-11-26": 79, + "2018-11-27": 104, + "2018-11-28": 70, + "2018-11-29": 103, + "2018-11-30": 86, + "2018-12-1": 86, + "2018-12-2": 77, + "2018-12-3": 96, + "2018-12-4": 95, + "2018-12-5": 74, + "2018-12-6": 99, + "2018-12-7": 101, + "2018-12-8": 78, + "2018-12-9": 83, + "2018-12-10": 76, + "2018-12-11": 77, + "2018-12-12": 105, + "2018-12-13": 81, + "2018-12-14": 82, + "2018-12-15": 90, + "2018-12-16": 88, + "2018-12-17": 78, + "2018-12-18": 82, + "2018-12-19": 83, + "2018-12-20": 105, + "2018-12-21": 70, + "2018-12-22": 85, + "2018-12-23": 91, + "2018-12-24": 89, + "2018-12-25": 83, + "2018-12-26": 73, + "2018-12-27": 91, + "2018-12-28": 77, + "2018-12-29": 101, + "2018-12-30": 83, + "2018-12-31": 94 + }, + "high": { + "2018-10-1": 43, + "2018-10-2": 42, + "2018-10-3": 42, + "2018-10-4": 49, + "2018-10-5": 44, + "2018-10-6": 59, + "2018-10-7": 49, + "2018-10-8": 53, + "2018-10-9": 44, + "2018-10-10": 51, + "2018-10-11": 43, + "2018-10-12": 53, + "2018-10-13": 52, + "2018-10-14": 43, + "2018-10-15": 60, + "2018-10-16": 53, + "2018-10-17": 57, + "2018-10-18": 42, + "2018-10-19": 46, + "2018-10-20": 43, + "2018-10-21": 43, + "2018-10-22": 41, + "2018-10-23": 47, + "2018-10-24": 44, + "2018-10-25": 43, + "2018-10-26": 60, + "2018-10-27": 43, + "2018-10-28": 59, + "2018-10-29": 55, + "2018-10-30": 45, + "2018-10-31": 51, + "2018-11-1": 55, + "2018-11-2": 50, + "2018-11-3": 43, + "2018-11-4": 41, + "2018-11-5": 51, + "2018-11-6": 49, + "2018-11-7": 49, + "2018-11-8": 60, + "2018-11-9": 60, + "2018-11-10": 43, + "2018-11-11": 57, + "2018-11-12": 42, + "2018-11-13": 59, + "2018-11-14": 41, + "2018-11-15": 53, + "2018-11-16": 53, + "2018-11-17": 43, + "2018-11-18": 53, + "2018-11-19": 48, + "2018-11-20": 56, + "2018-11-21": 51, + "2018-11-22": 42, + "2018-11-23": 60, + "2018-11-24": 50, + "2018-11-25": 49, + "2018-11-26": 47, + "2018-11-27": 46, + "2018-11-28": 40, + "2018-11-29": 41, + "2018-11-30": 57, + "2018-12-1": 57, + "2018-12-2": 45, + "2018-12-3": 52, + "2018-12-4": 46, + "2018-12-5": 56, + "2018-12-6": 48, + "2018-12-7": 58, + "2018-12-8": 59, + "2018-12-9": 47, + "2018-12-10": 58, + "2018-12-11": 50, + "2018-12-12": 45, + "2018-12-13": 59, + "2018-12-14": 40, + "2018-12-15": 40, + "2018-12-16": 48, + "2018-12-17": 44, + "2018-12-18": 54, + "2018-12-19": 44, + "2018-12-20": 57, + "2018-12-21": 54, + "2018-12-22": 44, + "2018-12-23": 59, + "2018-12-24": 41, + "2018-12-25": 52, + "2018-12-26": 52, + "2018-12-27": 50, + "2018-12-28": 49, + "2018-12-29": 45, + "2018-12-30": 44, + "2018-12-31": 60 + }, + "critical": { + "2018-10-1": 54, + "2018-10-2": 67, + "2018-10-3": 62, + "2018-10-4": 63, + "2018-10-5": 51, + "2018-10-6": 56, + "2018-10-7": 66, + "2018-10-8": 69, + "2018-10-9": 58, + "2018-10-10": 61, + "2018-10-11": 69, + "2018-10-12": 73, + "2018-10-13": 68, + "2018-10-14": 64, + "2018-10-15": 69, + "2018-10-16": 63, + "2018-10-17": 72, + "2018-10-18": 71, + "2018-10-19": 56, + "2018-10-20": 71, + "2018-10-21": 59, + "2018-10-22": 55, + "2018-10-23": 51, + "2018-10-24": 74, + "2018-10-25": 68, + "2018-10-26": 74, + "2018-10-27": 53, + "2018-10-28": 73, + "2018-10-29": 54, + "2018-10-30": 53, + "2018-10-31": 53, + "2018-11-1": 68, + "2018-11-2": 71, + "2018-11-3": 57, + "2018-11-4": 59, + "2018-11-5": 58, + "2018-11-6": 67, + "2018-11-7": 56, + "2018-11-8": 74, + "2018-11-9": 54, + "2018-11-10": 67, + "2018-11-11": 61, + "2018-11-12": 73, + "2018-11-13": 58, + "2018-11-14": 56, + "2018-11-15": 55, + "2018-11-16": 72, + "2018-11-17": 53, + "2018-11-18": 68, + "2018-11-19": 52, + "2018-11-20": 64, + "2018-11-21": 72, + "2018-11-22": 50, + "2018-11-23": 59, + "2018-11-24": 56, + "2018-11-25": 74, + "2018-11-26": 71, + "2018-11-27": 66, + "2018-11-28": 55, + "2018-11-29": 51, + "2018-11-30": 63, + "2018-12-1": 54, + "2018-12-2": 63, + "2018-12-3": 64, + "2018-12-4": 51, + "2018-12-5": 66, + "2018-12-6": 61, + "2018-12-7": 62, + "2018-12-8": 59, + "2018-12-9": 69, + "2018-12-10": 73, + "2018-12-11": 67, + "2018-12-12": 58, + "2018-12-13": 69, + "2018-12-14": 71, + "2018-12-15": 69, + "2018-12-16": 72, + "2018-12-17": 73, + "2018-12-18": 59, + "2018-12-19": 60, + "2018-12-20": 52, + "2018-12-21": 71, + "2018-12-22": 56, + "2018-12-23": 61, + "2018-12-24": 61, + "2018-12-25": 72, + "2018-12-26": 66, + "2018-12-27": 67, + "2018-12-28": 72, + "2018-12-29": 58, + "2018-12-30": 68, + "2018-12-31": 54 + }, + "unknown": { + "2018-10-1": 39, + "2018-10-2": 44, + "2018-10-3": 35, + "2018-10-4": 34, + "2018-10-5": 38, + "2018-10-6": 34, + "2018-10-7": 34, + "2018-10-8": 43, + "2018-10-9": 41, + "2018-10-10": 45, + "2018-10-11": 41, + "2018-10-12": 37, + "2018-10-13": 34, + "2018-10-14": 41, + "2018-10-15": 45, + "2018-10-16": 33, + "2018-10-17": 40, + "2018-10-18": 31, + "2018-10-19": 42, + "2018-10-20": 33, + "2018-10-21": 44, + "2018-10-22": 33, + "2018-10-23": 35, + "2018-10-24": 37, + "2018-10-25": 43, + "2018-10-26": 33, + "2018-10-27": 43, + "2018-10-28": 39, + "2018-10-29": 37, + "2018-10-30": 36, + "2018-10-31": 37, + "2018-11-1": 42, + "2018-11-2": 41, + "2018-11-3": 36, + "2018-11-4": 31, + "2018-11-5": 41, + "2018-11-6": 37, + "2018-11-7": 42, + "2018-11-8": 42, + "2018-11-9": 45, + "2018-11-10": 34, + "2018-11-11": 30, + "2018-11-12": 40, + "2018-11-13": 39, + "2018-11-14": 44, + "2018-11-15": 36, + "2018-11-16": 35, + "2018-11-17": 30, + "2018-11-18": 31, + "2018-11-19": 34, + "2018-11-20": 31, + "2018-11-21": 36, + "2018-11-22": 37, + "2018-11-23": 41, + "2018-11-24": 38, + "2018-11-25": 42, + "2018-11-26": 41, + "2018-11-27": 36, + "2018-11-28": 32, + "2018-11-29": 43, + "2018-11-30": 36, + "2018-12-1": 44, + "2018-12-2": 34, + "2018-12-3": 42, + "2018-12-4": 32, + "2018-12-5": 44, + "2018-12-6": 31, + "2018-12-7": 39, + "2018-12-8": 37, + "2018-12-9": 33, + "2018-12-10": 37, + "2018-12-11": 38, + "2018-12-12": 35, + "2018-12-13": 34, + "2018-12-14": 40, + "2018-12-15": 35, + "2018-12-16": 42, + "2018-12-17": 44, + "2018-12-18": 40, + "2018-12-19": 40, + "2018-12-20": 30, + "2018-12-21": 44, + "2018-12-22": 32, + "2018-12-23": 39, + "2018-12-24": 37, + "2018-12-25": 35, + "2018-12-26": 39, + "2018-12-27": 38, + "2018-12-28": 44, + "2018-12-29": 42, + "2018-12-30": 37, + "2018-12-31": 35 + }, + "all": { + "2018-10-1": 143, + "2018-10-2": 130, + "2018-10-3": 139, + "2018-10-4": 134, + "2018-10-5": 138, + "2018-10-6": 131, + "2018-10-7": 137, + "2018-10-8": 144, + "2018-10-9": 140, + "2018-10-10": 134, + "2018-10-11": 142, + "2018-10-12": 132, + "2018-10-13": 136, + "2018-10-14": 141, + "2018-10-15": 134, + "2018-10-16": 139, + "2018-10-17": 141, + "2018-10-18": 134, + "2018-10-19": 131, + "2018-10-20": 141, + "2018-10-21": 139, + "2018-10-22": 145, + "2018-10-23": 142, + "2018-10-24": 143, + "2018-10-25": 143, + "2018-10-26": 135, + "2018-10-27": 136, + "2018-10-28": 143, + "2018-10-29": 142, + "2018-10-30": 131, + "2018-10-31": 141, + "2018-11-1": 134, + "2018-11-2": 134, + "2018-11-3": 130, + "2018-11-4": 137, + "2018-11-5": 145, + "2018-11-6": 137, + "2018-11-7": 135, + "2018-11-8": 145, + "2018-11-9": 132, + "2018-11-10": 134, + "2018-11-11": 139, + "2018-11-12": 139, + "2018-11-13": 130, + "2018-11-14": 137, + "2018-11-15": 136, + "2018-11-16": 145, + "2018-11-17": 130, + "2018-11-18": 143, + "2018-11-19": 134, + "2018-11-20": 145, + "2018-11-21": 137, + "2018-11-22": 140, + "2018-11-23": 138, + "2018-11-24": 132, + "2018-11-25": 143, + "2018-11-26": 131, + "2018-11-27": 130, + "2018-11-28": 144, + "2018-11-29": 139, + "2018-11-30": 143, + "2018-12-1": 139, + "2018-12-2": 137, + "2018-12-3": 142, + "2018-12-4": 137, + "2018-12-5": 134, + "2018-12-6": 133, + "2018-12-7": 137, + "2018-12-8": 140, + "2018-12-9": 130, + "2018-12-10": 132, + "2018-12-11": 134, + "2018-12-12": 143, + "2018-12-13": 130, + "2018-12-14": 133, + "2018-12-15": 137, + "2018-12-16": 141, + "2018-12-17": 139, + "2018-12-18": 145, + "2018-12-19": 141, + "2018-12-20": 137, + "2018-12-21": 139, + "2018-12-22": 131, + "2018-12-23": 134, + "2018-12-24": 144, + "2018-12-25": 140, + "2018-12-26": 145, + "2018-12-27": 138, + "2018-12-28": 136, + "2018-12-29": 144, + "2018-12-30": 131, + "2018-12-31": 142 + } +} \ No newline at end of file diff --git a/ee/spec/javascripts/security_dashboard/store/vulnerabilities/mutations_spec.js b/ee/spec/javascripts/security_dashboard/store/vulnerabilities/mutations_spec.js index d4cc58efd3774c..0d4daa1608e168 100644 --- a/ee/spec/javascripts/security_dashboard/store/vulnerabilities/mutations_spec.js +++ b/ee/spec/javascripts/security_dashboard/store/vulnerabilities/mutations_spec.js @@ -131,6 +131,66 @@ describe('vulnerabilities module mutations', () => { }); }); + describe('SET_VULNERABILITIES_TIMELINE_ENDPOINT', () => { + it('should set `vulnerabilitiesTimelineEndpoint` to `fakepath.json`', () => { + const state = createState(); + const endpoint = 'fakepath.json'; + + mutations[types.SET_VULNERABILITIES_TIMELINE_ENDPOINT](state, endpoint); + + expect(state.vulnerabilitiesTimelineEndpoint).toEqual(endpoint); + }); + }); + + describe('REQUEST_VULNERABILITIES_TIMELINE', () => { + let state; + + beforeEach(() => { + state = { + ...createState(), + errorLoadingVulnerabilitiesTimeline: true, + }; + mutations[types.REQUEST_VULNERABILITIES_TIMELINE](state); + }); + + it('should set `isLoadingVulnerabilitiesTimeline` to `true`', () => { + expect(state.isLoadingVulnerabilitiesTimeline).toBeTruthy(); + }); + + it('should set `errorLoadingVulnerabilitiesTimeline` to `false`', () => { + expect(state.errorLoadingVulnerabilitiesTimeline).toBeFalsy(); + }); + }); + + describe('RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS', () => { + let payload; + let state; + + beforeEach(() => { + payload = mockData; + state = createState(); + mutations[types.RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS](state, payload); + }); + + it('should set `isLoadingVulnerabilitiesTimeline` to `false`', () => { + expect(state.isLoadingVulnerabilitiesTimeline).toBeFalsy(); + }); + + it('should set `vulnerabilitiesTimeline`', () => { + expect(state.vulnerabilitiesTimeline).toBe(payload); + }); + }); + + describe('RECEIVE_VULNERABILITIES_TIMELINE_ERROR', () => { + it('should set `isLoadingVulnerabilitiesTimeline` to `false`', () => { + const state = createState(); + + mutations[types.RECEIVE_VULNERABILITIES_TIMELINE_ERROR](state); + + expect(state.isLoadingVulnerabilitiesTimeline).toBeFalsy(); + }); + }); + describe('SET_MODAL_DATA', () => { describe('with all the data', () => { const vulnerability = mockData[0]; -- GitLab From cbc504840f1211fb7b743b125da5bbd3fbd241c0 Mon Sep 17 00:00:00 2001 From: samdbeckham Date: Tue, 4 Dec 2018 13:43:38 +0000 Subject: [PATCH 2/8] Adds changes from @adrielsantiago review - Created a better hex code checker - Updated a failing test - Moved test props outside the `beforeEach` loop - Bumped the graph down 10px --- .../components/vulnerability_chart.vue | 2 +- .../components/vulnerability_chart_label.vue | 11 +++-------- .../store/modules/vulnerabilities/actions.js | 2 ++ .../components/vulnerability_chart_label_spec.js | 12 +++++------- .../components/vulnerability_chart_spec.js | 4 ++-- .../vulnerability_chart_tooltip_spec.js | 16 ++++++++-------- 6 files changed, 21 insertions(+), 26 deletions(-) diff --git a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue index 071f10b2d63e06..b0705e0349c74b 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue @@ -62,7 +62,7 @@ export default { bottom: 85, left: 75, right: 15, - top: 5, + top: 10, }, tooltip: { backgroundColor: '#fff', diff --git a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart_label.vue b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart_label.vue index ec399c7fb799f5..3863045591fc55 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart_label.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart_label.vue @@ -11,14 +11,9 @@ export default { required: true, }, value: { - type: [Number, Boolean], + type: [Number], required: false, - default: false, - }, - }, - computed: { - valueIsPassed() { - return this.value || this.value === 0; + default: null, }, }, }; @@ -28,6 +23,6 @@ export default {
{{ name }} - {{ value }} + {{ value }}
diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/actions.js b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/actions.js index adbd33b97de6e5..2fd8a5fa493eeb 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/actions.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/actions.js @@ -242,4 +242,6 @@ export const receiveVulnerabilitiesTimelineError = ({ commit }) => { commit(types.RECEIVE_VULNERABILITIES_TIMELINE_ERROR); }; +// prevent babel-plugin-rewire from generating an invalid default during karma tests +// This is no longer needed after gitlab-ce#52179 is merged export default () => {}; diff --git a/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_label_spec.js b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_label_spec.js index 4fdc7faca0833a..03c07c845e9a07 100644 --- a/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_label_spec.js +++ b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_label_spec.js @@ -2,15 +2,13 @@ import Vue from 'vue'; import component from 'ee/security_dashboard/components/vulnerability_chart_label.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -// TODO: Move this to a helper if it's deemed a sensible approach function hexToRgb(hex) { - /* eslint-disable no-bitwise */ const cleanHex = hex.replace('#', ''); - const bigint = parseInt(cleanHex, 16); - const r = (bigint >> 16) & 255; - const g = (bigint >> 8) & 255; - const b = bigint & 255; - /* eslint-enable no-bitwise */ + const [r, g, b] = [ + cleanHex.substring(0, 2), + cleanHex.substring(2, 4), + cleanHex.substring(4, 6), + ].map(rgb => parseInt(rgb, 16)); return `rgb(${r}, ${g}, ${b})`; } diff --git a/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_spec.js b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_spec.js index 72bfe6281d1251..a3f8988368a3f4 100644 --- a/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_spec.js +++ b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_spec.js @@ -31,10 +31,10 @@ describe('Vulnerabilities Chart', () => { mock.restore(); }); - it('should render the e-chart canvas', done => { + it('should render the e-chart instance', done => { waitForPromises() .then(() => { - expect(vm.$el.querySelector('canvas')).not.toBeNull(); + expect(vm.$el.querySelector('[_echarts_instance_]')).not.toBeNull(); done(); }) .catch(done.fail); diff --git a/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_tooltip_spec.js b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_tooltip_spec.js index 4a36df3803085e..870e4e70d59893 100644 --- a/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_tooltip_spec.js +++ b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_tooltip_spec.js @@ -4,11 +4,9 @@ import mountComponent from 'spec/helpers/vue_mount_component_helper'; describe('Vulnerability Chart Tooltip component', () => { const Component = Vue.extend(component); - let vm; - let props; - - beforeEach(() => { - const entries = [ + const props = { + title: 'Tooltip Title', + entries: [ { dataIndex: 1, seriesId: 'critical_0', @@ -30,9 +28,11 @@ describe('Vulnerability Chart Tooltip component', () => { color: '#f00', data: ['low', 2], }, - ]; - const title = 'Tooltip Title'; - props = { title, entries }; + ], + }; + let vm; + + beforeEach(() => { vm = mountComponent(Component, props); }); -- GitLab From 76ce00bea74a42bfb0841c8a807323d6d2fbc5a5 Mon Sep 17 00:00:00 2001 From: samdbeckham Date: Tue, 4 Dec 2018 18:11:32 +0000 Subject: [PATCH 3/8] Changes from @andyvolpe review - Adds a confine rule to the tooltip - re-works the tooltip border to prevent clipping --- .../security_dashboard/components/vulnerability_chart.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue index b0705e0349c74b..7aff407f85202d 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue @@ -66,8 +66,9 @@ export default { }, tooltip: { backgroundColor: '#fff', - borderWidth: 0, - extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);', + borderColor: 'rgba(0, 0, 0, 0.1)', + borderWidth: 1, + confine: true, formatter: this.renderTooltip, padding: 0, textStyle: { -- GitLab From 2a76b5aa49f4ee3601940c02e82abc9d038e59f9 Mon Sep 17 00:00:00 2001 From: samdbeckham Date: Wed, 5 Dec 2018 11:34:57 +0000 Subject: [PATCH 4/8] Hides the dashboard chart behind a feature flag --- .../javascripts/security_dashboard/components/app.vue | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ee/app/assets/javascripts/security_dashboard/components/app.vue b/ee/app/assets/javascripts/security_dashboard/components/app.vue index 9e5d696fb47581..ffb866ffe33a29 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/app.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/app.vue @@ -75,6 +75,9 @@ export default { html: true, }; }, + chartFlagEnabled() { + return gon.features && gon.features.groupSecurityDashboardHistory; + }, }, created() { this.setVulnerabilitiesEndpoint(this.vulnerabilitiesEndpoint); @@ -110,8 +113,10 @@ export default { -

{{ __('Vulnerability Chart') }}

- +

{{ __('Vulnerability List') }}

Date: Tue, 4 Dec 2018 13:34:18 +0000 Subject: [PATCH 5/8] Hooks up the dashboard history API - Requests the endpoint if the feature flag is enabled - Requests timeline data from that endpoint - Renames vulnerabilitiesTimeline to vulnerabilitesHistory --- .../security_dashboard/components/app.vue | 6 +++ .../components/vulnerability_chart.vue | 33 +++++++-------- .../javascripts/security_dashboard/index.js | 1 + .../store/modules/vulnerabilities/actions.js | 32 ++++++-------- .../modules/vulnerabilities/mutation_types.js | 8 ++-- .../modules/vulnerabilities/mutations.js | 22 +++++----- .../store/modules/vulnerabilities/state.js | 8 ++-- .../groups/security/dashboard_controller.rb | 4 ++ .../groups/security/dashboard/show.html.haml | 2 + .../components/vulnerability_chart_spec.js | 8 ++-- .../store/vulnerabilities/actions_spec.js | 40 +++++++++--------- ...=> mock_data_vulnerabilities_history.json} | 0 .../store/vulnerabilities/mutations_spec.js | 42 +++++++++---------- 13 files changed, 106 insertions(+), 100 deletions(-) rename ee/spec/javascripts/security_dashboard/store/vulnerabilities/data/{mock_data_vulnerabilities_timeline.json => mock_data_vulnerabilities_history.json} (100%) diff --git a/ee/app/assets/javascripts/security_dashboard/components/app.vue b/ee/app/assets/javascripts/security_dashboard/components/app.vue index ffb866ffe33a29..f250cdb650d27f 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/app.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/app.vue @@ -42,6 +42,10 @@ export default { type: String, required: true, }, + vulnerabilitiesHistoryEndpoint: { + type: String, + required: true, + }, vulnerabilityFeedbackHelpPath: { type: String, required: true, @@ -82,11 +86,13 @@ export default { created() { this.setVulnerabilitiesEndpoint(this.vulnerabilitiesEndpoint); this.setVulnerabilitiesCountEndpoint(this.vulnerabilitiesCountEndpoint); + this.setVulnerabilitiesHistoryEndpoint(this.vulnerabilitiesHistoryEndpoint); this.fetchVulnerabilitiesCount(); }, methods: { ...mapActions('vulnerabilities', [ 'setVulnerabilitiesCountEndpoint', + 'setVulnerabilitiesHistoryEndpoint', 'setVulnerabilitiesEndpoint', 'fetchVulnerabilitiesCount', 'createIssue', diff --git a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue index 7aff407f85202d..33915c4e84d67e 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue @@ -31,29 +31,28 @@ export default { color: '#4F4F4F', }, { - name: 'All', + name: 'Total', color: '#1F78D1', }, ], }), computed: { - ...mapState('vulnerabilities', ['vulnerabilitiesTimeline']), + ...mapState('vulnerabilities', ['vulnerabilitiesHistory']), series() { return this.lines.map(line => { const { name, color } = line; - const timeline = this.vulnerabilitiesTimeline[name.toLowerCase()]; - const data = Object.entries(timeline); - return timeline - ? { - borderWidth: 2, - color, - data, - name, - symbol: 'circle', - symbolSize: 6, - type: 'line', - } - : null; + const history = this.vulnerabilitiesHistory[name.toLowerCase()]; + const data = history ? Object.entries(history) : []; + + return { + borderWidth: 2, + color, + data, + name, + symbol: 'circle', + symbolSize: 6, + type: 'line', + }; }); }, options() { @@ -144,10 +143,10 @@ export default { }, }, created() { - this.fetchVulnerabilitiesTimeline(); + this.fetchVulnerabilitiesHistory(); }, methods: { - ...mapActions('vulnerabilities', ['fetchVulnerabilitiesTimeline']), + ...mapActions('vulnerabilities', ['fetchVulnerabilitiesHistory']), renderTooltip(params, ticket, callback) { this.tooltipTitle = dateFormat(params[0].axisValueLabel, 'd mmmm'); this.tooltipEntries = params; diff --git a/ee/app/assets/javascripts/security_dashboard/index.js b/ee/app/assets/javascripts/security_dashboard/index.js index af45b79524aed7..d421fc1569a174 100644 --- a/ee/app/assets/javascripts/security_dashboard/index.js +++ b/ee/app/assets/javascripts/security_dashboard/index.js @@ -19,6 +19,7 @@ export default () => { vulnerabilityFeedbackHelpPath: el.dataset.vulnerabilityFeedbackHelpPath, vulnerabilitiesEndpoint: el.dataset.vulnerabilitiesEndpoint, vulnerabilitiesCountEndpoint: el.dataset.vulnerabilitiesSummaryEndpoint, + vulnerabilitiesHistoryEndpoint: el.dataset.vulnerabilitiesHistoryEndpoint, }, }); }, diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/actions.js b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/actions.js index 2fd8a5fa493eeb..06f8fe9675e695 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/actions.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/actions.js @@ -5,9 +5,6 @@ import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; import { s__ } from '~/locale'; import createFlash from '~/flash'; -// TODO: Remove this mock -import mockData from '../../../../../../../spec/javascripts/security_dashboard/store/vulnerabilities/data/mock_data_vulnerabilities_timeline.json'; - export const setVulnerabilitiesEndpoint = ({ commit }, endpoint) => { commit(types.SET_VULNERABILITIES_ENDPOINT, endpoint); }; @@ -207,39 +204,36 @@ export const receiveRevertDismissalError = ({ commit }, { flashError }) => { } }; -export const setVulnerabilitiesTimelineEndpoint = ({ commit }, endpoint) => { - commit(types.SET_VULNERABILITIES_TIMELINE_ENDPOINT, endpoint); +export const setVulnerabilitiesHistoryEndpoint = ({ commit }, endpoint) => { + commit(types.SET_VULNERABILITIES_HISTORY_ENDPOINT, endpoint); }; -export const fetchVulnerabilitiesTimeline = ({ state, dispatch }) => { - dispatch('requestVulnerabilitiesTimeline'); - - // TODO: Remove this mocking - return dispatch('receiveVulnerabilitiesTimelineSuccess', { data: mockData }); +export const fetchVulnerabilitiesHistory = ({ state, dispatch }) => { + dispatch('requestVulnerabilitiesHistory'); axios({ method: 'GET', - url: state.vulnerabilitiesTimelineEndpoint, + url: state.vulnerabilitiesHistoryEndpoint, }) .then(response => { const { data } = response; - dispatch('receiveVulnerabilitiesTimelineSuccess', { data }); + dispatch('receiveVulnerabilitiesHistorySuccess', { data }); }) .catch(() => { - dispatch('receiveVulnerabilitiesTimelineError'); + dispatch('receiveVulnerabilitiesHistoryError'); }); }; -export const requestVulnerabilitiesTimeline = ({ commit }) => { - commit(types.REQUEST_VULNERABILITIES_TIMELINE); +export const requestVulnerabilitiesHistory = ({ commit }) => { + commit(types.REQUEST_VULNERABILITIES_HISTORY); }; -export const receiveVulnerabilitiesTimelineSuccess = ({ commit }, { data }) => { - commit(types.RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS, data); +export const receiveVulnerabilitiesHistorySuccess = ({ commit }, { data }) => { + commit(types.RECEIVE_VULNERABILITIES_HISTORY_SUCCESS, data); }; -export const receiveVulnerabilitiesTimelineError = ({ commit }) => { - commit(types.RECEIVE_VULNERABILITIES_TIMELINE_ERROR); +export const receiveVulnerabilitiesHistoryError = ({ commit }) => { + commit(types.RECEIVE_VULNERABILITIES_HISTORY_ERROR); }; // prevent babel-plugin-rewire from generating an invalid default during karma tests diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutation_types.js b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutation_types.js index 1d1eddd0880192..823ad75a670bbf 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutation_types.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutation_types.js @@ -8,10 +8,10 @@ export const REQUEST_VULNERABILITIES_COUNT = 'REQUEST_VULNERABILITIES_COUNT'; export const RECEIVE_VULNERABILITIES_COUNT_SUCCESS = 'RECEIVE_VULNERABILITIES_COUNT_SUCCESS'; export const RECEIVE_VULNERABILITIES_COUNT_ERROR = 'RECEIVE_VULNERABILITIES_COUNT_ERROR'; -export const SET_VULNERABILITIES_TIMELINE_ENDPOINT = 'SET_VULNERABILITIES_TIMELINE_ENDPOINT'; -export const REQUEST_VULNERABILITIES_TIMELINE = 'REQUEST_VULNERABILITIES_TIMELINE'; -export const RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS = 'RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS'; -export const RECEIVE_VULNERABILITIES_TIMELINE_ERROR = 'RECEIVE_VULNERABILITIES_TIMELINE_ERROR'; +export const SET_VULNERABILITIES_HISTORY_ENDPOINT = 'SET_VULNERABILITIES_HISTORY_ENDPOINT'; +export const REQUEST_VULNERABILITIES_HISTORY = 'REQUEST_VULNERABILITIES_HISTORY'; +export const RECEIVE_VULNERABILITIES_HISTORY_SUCCESS = 'RECEIVE_VULNERABILITIES_HISTORY_SUCCESS'; +export const RECEIVE_VULNERABILITIES_HISTORY_ERROR = 'RECEIVE_VULNERABILITIES_HISTORY_ERROR'; export const SET_MODAL_DATA = 'SET_MODAL_DATA'; diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutations.js b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutations.js index 377d0eb3d8cca2..ff3f40d88dcb7e 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutations.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutations.js @@ -35,20 +35,20 @@ export default { state.isLoadingVulnerabilitiesCount = false; state.errorLoadingVulnerabilitiesCount = true; }, - [types.SET_VULNERABILITIES_TIMELINE_ENDPOINT](state, payload) { - state.vulnerabilitiesTimelineEndpoint = payload; + [types.SET_VULNERABILITIES_HISTORY_ENDPOINT](state, payload) { + state.vulnerabilitiesHistoryEndpoint = payload; }, - [types.REQUEST_VULNERABILITIES_TIMELINE](state) { - state.isLoadingVulnerabilitiesTimeline = true; - state.errorLoadingVulnerabilitiesTimeline = false; + [types.REQUEST_VULNERABILITIES_HISTORY](state) { + state.isLoadingVulnerabilitiesHistory = true; + state.errorLoadingVulnerabilitiesHistory = false; }, - [types.RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS](state, payload) { - state.isLoadingVulnerabilitiesTimeline = false; - state.vulnerabilitiesTimeline = payload; + [types.RECEIVE_VULNERABILITIES_HISTORY_SUCCESS](state, payload) { + state.isLoadingVulnerabilitiesHistory = false; + state.vulnerabilitiesHistory = payload; }, - [types.RECEIVE_VULNERABILITIES_TIMELINE_ERROR](state) { - state.isLoadingVulnerabilitiesTimeline = false; - state.errorLoadingVulnerabilitiesTimeline = true; + [types.RECEIVE_VULNERABILITIES_HISTORY_ERROR](state) { + state.isLoadingVulnerabilitiesHistory = false; + state.errorLoadingVulnerabilitiesHistory = true; }, [types.SET_MODAL_DATA](state, payload) { const { vulnerability } = payload; diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/state.js b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/state.js index 322b1e6f0bc95c..5ef220e3ba8bd5 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/state.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/state.js @@ -7,12 +7,12 @@ export default () => ({ isLoadingVulnerabilitiesCount: true, errorLoadingVulnerabilitiesCount: false, vulnerabilitiesCount: {}, - isLoadingVulnerabilitiesTimeline: true, - errorLoadingVulnerabilitiesTimeline: false, - vulnerabilitiesTimeline: {}, + isLoadingVulnerabilitiesHistory: true, + errorLoadingVulnerabilitiesHistory: false, + vulnerabilitiesHistory: {}, pageInfo: {}, vulnerabilitiesCountEndpoint: null, - vulnerabilitiesTimelineEndpoint: null, + vulnerabilitiesHistoryEndpoint: null, vulnerabilitiesEndpoint: null, activeVulnerability: null, modal: { diff --git a/ee/app/controllers/groups/security/dashboard_controller.rb b/ee/app/controllers/groups/security/dashboard_controller.rb index c9b0d7f0820ff3..c4a7d0b85c38bf 100644 --- a/ee/app/controllers/groups/security/dashboard_controller.rb +++ b/ee/app/controllers/groups/security/dashboard_controller.rb @@ -1,4 +1,8 @@ # frozen_string_literal: true class Groups::Security::DashboardController < Groups::Security::ApplicationController layout 'group' + + before_action do + push_frontend_feature_flag(:group_security_dashboard_history, group) + end end diff --git a/ee/app/views/groups/security/dashboard/show.html.haml b/ee/app/views/groups/security/dashboard/show.html.haml index b435b1b2e8bba6..1d9ff08a1c86bc 100644 --- a/ee/app/views/groups/security/dashboard/show.html.haml +++ b/ee/app/views/groups/security/dashboard/show.html.haml @@ -1,8 +1,10 @@ - breadcrumb_title _("Security Dashboard") - page_title _("Security Dashboard") +- vulnerabilities_history_endpoint = Feature.enabled?(:group_security_dashboard_history, @group) ? history_group_security_vulnerabilities_path(@group) : '' #js-group-security-dashboard{ data: { vulnerabilities_endpoint: group_security_vulnerabilities_path(@group), vulnerabilities_summary_endpoint: summary_group_security_vulnerabilities_path(@group), + vulnerabilities_history_endpoint: vulnerabilities_history_endpoint, vulnerability_feedback_help_path: help_page_path("user/project/merge_requests/index", anchor: "interacting-with-security-reports-ultimate"), empty_state_svg_path: image_path('illustrations/security-dashboard-empty-state.svg'), dashboard_documentation: help_page_path('user/group/security_dashboard/index') } } diff --git a/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_spec.js b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_spec.js index a3f8988368a3f4..67f36c54dc426c 100644 --- a/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_spec.js +++ b/ee/spec/javascripts/security_dashboard/components/vulnerability_chart_spec.js @@ -8,20 +8,20 @@ import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper import waitForPromises from 'spec/helpers/wait_for_promises'; import { resetStore } from '../helpers'; -import mockDataVulnerabilitiesTimeline from '../store/vulnerabilities/data/mock_data_vulnerabilities_timeline.json'; +import mockDataVulnerabilitiesHistory from '../store/vulnerabilities/data/mock_data_vulnerabilities_history.json'; describe('Vulnerabilities Chart', () => { const Component = Vue.extend(component); - const vulnerabilitiesTimelineEndpoint = '/vulnerabilitiesEndpoint.json'; + const vulnerabilitiesHistoryEndpoint = '/vulnerabilitiesEndpoint.json'; let store; let mock; let vm; beforeEach(() => { store = createStore(); - store.state.vulnerabilities.vulnerabilitiesTimelineEndpoint = vulnerabilitiesTimelineEndpoint; + store.state.vulnerabilities.vulnerabilitiesHistoryEndpoint = vulnerabilitiesHistoryEndpoint; mock = new MockAdapater(axios); - mock.onGet(vulnerabilitiesTimelineEndpoint).replyOnce(200, mockDataVulnerabilitiesTimeline); + mock.onGet(vulnerabilitiesHistoryEndpoint).replyOnce(200, mockDataVulnerabilitiesHistory); vm = mountComponentWithStore(Component, { store }); }); diff --git a/ee/spec/javascripts/security_dashboard/store/vulnerabilities/actions_spec.js b/ee/spec/javascripts/security_dashboard/store/vulnerabilities/actions_spec.js index a34d411c3012c3..eff5ee1891bdbb 100644 --- a/ee/spec/javascripts/security_dashboard/store/vulnerabilities/actions_spec.js +++ b/ee/spec/javascripts/security_dashboard/store/vulnerabilities/actions_spec.js @@ -9,7 +9,7 @@ import * as actions from 'ee/security_dashboard/store/modules/vulnerabilities/ac import mockDataVulnerabilities from './data/mock_data_vulnerabilities.json'; import mockDataVulnerabilitiesCount from './data/mock_data_vulnerabilities_count.json'; -import mockDataVulnerabilitiesTimeline from './data/mock_data_vulnerabilities_timeline.json'; +import mockDataVulnerabilitiesHistory from './data/mock_data_vulnerabilities_history.json'; describe('vulnerabiliites count actions', () => { const data = mockDataVulnerabilitiesCount; @@ -637,20 +637,20 @@ describe('revert vulnerability dismissal', () => { }); describe('vulnerabiliites timeline actions', () => { - const data = mockDataVulnerabilitiesTimeline; + const data = mockDataVulnerabilitiesHistory; - describe('setVulnerabilitiesTimelineEndpoint', () => { + describe('setVulnerabilitiesHistoryEndpoint', () => { it('should commit the correct mutuation', done => { const state = initialState; const endpoint = 'fakepath.json'; testAction( - actions.setVulnerabilitiesTimelineEndpoint, + actions.setVulnerabilitiesHistoryEndpoint, endpoint, state, [ { - type: types.SET_VULNERABILITIES_TIMELINE_ENDPOINT, + type: types.SET_VULNERABILITIES_HISTORY_ENDPOINT, payload: endpoint, }, ], @@ -665,7 +665,7 @@ describe('vulnerabiliites timeline actions', () => { const state = initialState; beforeEach(() => { - state.vulnerabilitiesCountEndpoint = `${TEST_HOST}/vulnerabilities_timeline.json`; + state.vulnerabilitiesCountEndpoint = `${TEST_HOST}/vulnerabilitIES_HISTORY.json`; mock = new MockAdapter(axios); }); @@ -675,19 +675,19 @@ describe('vulnerabiliites timeline actions', () => { describe('on success', () => { beforeEach(() => { - mock.onGet(state.vulnerabilitiesTimelineEndpoint).replyOnce(200, data); + mock.onGet(state.vulnerabilitiesHistoryEndpoint).replyOnce(200, data); }); it('should dispatch the request and success actions', done => { testAction( - actions.fetchVulnerabilitiesTimeline, + actions.fetchVulnerabilitiesHistory, {}, state, [], [ - { type: 'requestVulnerabilitiesTimeline' }, + { type: 'requestVulnerabilitiesHistory' }, { - type: 'receiveVulnerabilitiesTimelineSuccess', + type: 'receiveVulnerabilitiesHistorySuccess', payload: { data }, }, ], @@ -698,18 +698,18 @@ describe('vulnerabiliites timeline actions', () => { describe('on error', () => { beforeEach(() => { - mock.onGet(state.vulnerabilitiesTimelineEndpoint).replyOnce(404, {}); + mock.onGet(state.vulnerabilitiesHistoryEndpoint).replyOnce(404, {}); }); it('should dispatch the request and error actions', done => { testAction( - actions.fetchVulnerabilitiesTimeline, + actions.fetchVulnerabilitiesHistory, {}, state, [], [ - { type: 'requestVulnerabilitiesTimeline' }, - { type: 'receiveVulnerabilitiesTimelineError' }, + { type: 'requestVulnerabilitiesHistory' }, + { type: 'receiveVulnerabilitiesHistoryError' }, ], done, ); @@ -722,10 +722,10 @@ describe('vulnerabiliites timeline actions', () => { const state = initialState; testAction( - actions.requestVulnerabilitiesTimeline, + actions.requestVulnerabilitiesHistory, {}, state, - [{ type: types.REQUEST_VULNERABILITIES_TIMELINE }], + [{ type: types.REQUEST_VULNERABILITIES_HISTORY }], [], done, ); @@ -737,10 +737,10 @@ describe('vulnerabiliites timeline actions', () => { const state = initialState; testAction( - actions.receiveVulnerabilitiesTimelineSuccess, + actions.receiveVulnerabilitiesHistorySuccess, { data }, state, - [{ type: types.RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS, payload: data }], + [{ type: types.RECEIVE_VULNERABILITIES_HISTORY_SUCCESS, payload: data }], [], done, ); @@ -752,10 +752,10 @@ describe('vulnerabiliites timeline actions', () => { const state = initialState; testAction( - actions.receiveVulnerabilitiesTimelineError, + actions.receiveVulnerabilitiesHistoryError, {}, state, - [{ type: types.RECEIVE_VULNERABILITIES_TIMELINE_ERROR }], + [{ type: types.RECEIVE_VULNERABILITIES_HISTORY_ERROR }], [], done, ); diff --git a/ee/spec/javascripts/security_dashboard/store/vulnerabilities/data/mock_data_vulnerabilities_timeline.json b/ee/spec/javascripts/security_dashboard/store/vulnerabilities/data/mock_data_vulnerabilities_history.json similarity index 100% rename from ee/spec/javascripts/security_dashboard/store/vulnerabilities/data/mock_data_vulnerabilities_timeline.json rename to ee/spec/javascripts/security_dashboard/store/vulnerabilities/data/mock_data_vulnerabilities_history.json diff --git a/ee/spec/javascripts/security_dashboard/store/vulnerabilities/mutations_spec.js b/ee/spec/javascripts/security_dashboard/store/vulnerabilities/mutations_spec.js index 0d4daa1608e168..1e557d2230371c 100644 --- a/ee/spec/javascripts/security_dashboard/store/vulnerabilities/mutations_spec.js +++ b/ee/spec/javascripts/security_dashboard/store/vulnerabilities/mutations_spec.js @@ -131,63 +131,63 @@ describe('vulnerabilities module mutations', () => { }); }); - describe('SET_VULNERABILITIES_TIMELINE_ENDPOINT', () => { - it('should set `vulnerabilitiesTimelineEndpoint` to `fakepath.json`', () => { + describe('SET_VULNERABILITIES_HISTORY_ENDPOINT', () => { + it('should set `vulnerabilitiesHistoryEndpoint` to `fakepath.json`', () => { const state = createState(); const endpoint = 'fakepath.json'; - mutations[types.SET_VULNERABILITIES_TIMELINE_ENDPOINT](state, endpoint); + mutations[types.SET_VULNERABILITIES_HISTORY_ENDPOINT](state, endpoint); - expect(state.vulnerabilitiesTimelineEndpoint).toEqual(endpoint); + expect(state.vulnerabilitiesHistoryEndpoint).toEqual(endpoint); }); }); - describe('REQUEST_VULNERABILITIES_TIMELINE', () => { + describe('REQUEST_VULNERABILITIES_HISTORY', () => { let state; beforeEach(() => { state = { ...createState(), - errorLoadingVulnerabilitiesTimeline: true, + errorLoadingVulnerabilitiesHistory: true, }; - mutations[types.REQUEST_VULNERABILITIES_TIMELINE](state); + mutations[types.REQUEST_VULNERABILITIES_HISTORY](state); }); - it('should set `isLoadingVulnerabilitiesTimeline` to `true`', () => { - expect(state.isLoadingVulnerabilitiesTimeline).toBeTruthy(); + it('should set `isLoadingVulnerabilitiesHistory` to `true`', () => { + expect(state.isLoadingVulnerabilitiesHistory).toBeTruthy(); }); - it('should set `errorLoadingVulnerabilitiesTimeline` to `false`', () => { - expect(state.errorLoadingVulnerabilitiesTimeline).toBeFalsy(); + it('should set `errorLoadingVulnerabilitiesHistory` to `false`', () => { + expect(state.errorLoadingVulnerabilitiesHistory).toBeFalsy(); }); }); - describe('RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS', () => { + describe('RECEIVE_VULNERABILITIES_HISTORY_SUCCESS', () => { let payload; let state; beforeEach(() => { payload = mockData; state = createState(); - mutations[types.RECEIVE_VULNERABILITIES_TIMELINE_SUCCESS](state, payload); + mutations[types.RECEIVE_VULNERABILITIES_HISTORY_SUCCESS](state, payload); }); - it('should set `isLoadingVulnerabilitiesTimeline` to `false`', () => { - expect(state.isLoadingVulnerabilitiesTimeline).toBeFalsy(); + it('should set `isLoadingVulnerabilitiesHistory` to `false`', () => { + expect(state.isLoadingVulnerabilitiesHistory).toBeFalsy(); }); - it('should set `vulnerabilitiesTimeline`', () => { - expect(state.vulnerabilitiesTimeline).toBe(payload); + it('should set `vulnerabilitiesHistory`', () => { + expect(state.vulnerabilitiesHistory).toBe(payload); }); }); - describe('RECEIVE_VULNERABILITIES_TIMELINE_ERROR', () => { - it('should set `isLoadingVulnerabilitiesTimeline` to `false`', () => { + describe('RECEIVE_VULNERABILITIES_HISTORY_ERROR', () => { + it('should set `isLoadingVulnerabilitiesHistory` to `false`', () => { const state = createState(); - mutations[types.RECEIVE_VULNERABILITIES_TIMELINE_ERROR](state); + mutations[types.RECEIVE_VULNERABILITIES_HISTORY_ERROR](state); - expect(state.isLoadingVulnerabilitiesTimeline).toBeFalsy(); + expect(state.isLoadingVulnerabilitiesHistory).toBeFalsy(); }); }); -- GitLab From 57bea1744acc265eed9bd20c9d94bb68c92a0f69 Mon Sep 17 00:00:00 2001 From: samdbeckham Date: Wed, 5 Dec 2018 12:47:15 +0000 Subject: [PATCH 6/8] Moves the styles out to a seperate stylesheet --- .../components/vulnerability_chart.vue | 23 ------------------- .../components/vulnerability_chart.scss | 23 +++++++++++++++++++ 2 files changed, 23 insertions(+), 23 deletions(-) create mode 100644 ee/app/assets/stylesheets/components/vulnerability_chart.scss diff --git a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue index 33915c4e84d67e..ea3718f538580e 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue @@ -165,26 +165,3 @@ export default { - - diff --git a/ee/app/assets/stylesheets/components/vulnerability_chart.scss b/ee/app/assets/stylesheets/components/vulnerability_chart.scss new file mode 100644 index 00000000000000..a3f9c419480eaf --- /dev/null +++ b/ee/app/assets/stylesheets/components/vulnerability_chart.scss @@ -0,0 +1,23 @@ +$trans-white: rgba(255, 255, 255, 0); + +.vulnerabilities-chart-wrapper { + -webkit-overflow-scrolling: touch; + overflow: scroll; +} + +@media screen and (max-width: 1240px) { + .vulnerabilities-chart { + position: relative; + } + + .vulnerabilities-chart::after { + background-image: linear-gradient(to right, $trans-white, $gl-gray-350); + bottom: 0; + content: ''; + height: 310px; + position: absolute; + right: -1px; + top: 10px; + width: 32px; + } +} -- GitLab From 19ac5b822a36ea7e60a363445ec35ff97a60f12e Mon Sep 17 00:00:00 2001 From: samdbeckham Date: Wed, 5 Dec 2018 13:11:30 +0000 Subject: [PATCH 7/8] Adds the changelog --- ee/changelogs/unreleased/6954-dashboard-chart.yml | 5 +++++ locale/gitlab.pot | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 ee/changelogs/unreleased/6954-dashboard-chart.yml diff --git a/ee/changelogs/unreleased/6954-dashboard-chart.yml b/ee/changelogs/unreleased/6954-dashboard-chart.yml new file mode 100644 index 00000000000000..e18ec71e645c00 --- /dev/null +++ b/ee/changelogs/unreleased/6954-dashboard-chart.yml @@ -0,0 +1,5 @@ +--- +title: Adds group security dashboard metrics chart +merge_request: 8631 +author: +type: added diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 768c0b172a8e6a..de20a941eb0876 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9340,6 +9340,9 @@ msgstr "" msgid "VisibilityLevel|Unknown" msgstr "" +msgid "Vulnerability Chart" +msgstr "" + msgid "Vulnerability List" msgstr "" -- GitLab From 05e3a99fa125f286bab7af7eb4c42fbec6f16588 Mon Sep 17 00:00:00 2001 From: samdbeckham Date: Thu, 6 Dec 2018 10:14:57 +0000 Subject: [PATCH 8/8] Fixes date formatting issues with safari --- .../security_dashboard/components/vulnerability_chart.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue index ea3718f538580e..7b7336ada9998e 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/vulnerability_chart.vue @@ -148,7 +148,7 @@ export default { methods: { ...mapActions('vulnerabilities', ['fetchVulnerabilitiesHistory']), renderTooltip(params, ticket, callback) { - this.tooltipTitle = dateFormat(params[0].axisValueLabel, 'd mmmm'); + this.tooltipTitle = dateFormat(params[0].axisValue, 'd mmmm'); this.tooltipEntries = params; this.$nextTick(() => callback(ticket, this.$refs.tooltip.$el.innerHTML)); return ' '; -- GitLab