Commit a9a7547b authored by Mike Greiling's avatar Mike Greiling

Merge branch '6954-dashboard-chart' into 'master'

Resolve "Group Security Dashboard metrics MVC"

See merge request gitlab-org/gitlab-ee!8631
parents 110004ce 54f7a47e
......@@ -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: {
......@@ -40,6 +42,10 @@ export default {
type: String,
required: true,
},
vulnerabilitiesHistoryEndpoint: {
type: String,
required: true,
},
vulnerabilityFeedbackHelpPath: {
type: String,
required: true,
......@@ -73,15 +79,20 @@ export default {
html: true,
};
},
chartFlagEnabled() {
return gon.features && gon.features.groupSecurityDashboardHistory;
},
},
created() {
this.setVulnerabilitiesEndpoint(this.vulnerabilitiesEndpoint);
this.setVulnerabilitiesCountEndpoint(this.vulnerabilitiesCountEndpoint);
this.setVulnerabilitiesHistoryEndpoint(this.vulnerabilitiesHistoryEndpoint);
this.fetchVulnerabilitiesCount();
},
methods: {
...mapActions('vulnerabilities', [
'setVulnerabilitiesCountEndpoint',
'setVulnerabilitiesHistoryEndpoint',
'setVulnerabilitiesEndpoint',
'fetchVulnerabilitiesCount',
'createIssue',
......@@ -108,7 +119,11 @@ export default {
</span>
</template>
<vulnerability-count-list />
<h5 class="mt-4 mb-4">{{ __('Vulnerability List') }}</h5>
<template v-if="chartFlagEnabled">
<h4 class="my-4">{{ __('Vulnerability Chart') }}</h4>
<vulnerability-chart />
</template>
<h4 class="my-4">{{ __('Vulnerability List') }}</h4>
<security-dashboard-table
:dashboard-documentation="dashboardDocumentation"
:empty-state-svg-path="emptyStateSvgPath"
......
<script>
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: 'Total',
color: '#1F78D1',
},
],
}),
computed: {
...mapState('vulnerabilities', ['vulnerabilitiesHistory']),
series() {
return this.lines.map(line => {
const { name, color } = line;
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() {
return {
grid: {
bottom: 85,
left: 75,
right: 15,
top: 10,
},
tooltip: {
backgroundColor: '#fff',
borderColor: 'rgba(0, 0, 0, 0.1)',
borderWidth: 1,
confine: true,
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.fetchVulnerabilitiesHistory();
},
methods: {
...mapActions('vulnerabilities', ['fetchVulnerabilitiesHistory']),
renderTooltip(params, ticket, callback) {
this.tooltipTitle = dateFormat(params[0].axisValue, 'd mmmm');
this.tooltipEntries = params;
this.$nextTick(() => callback(ticket, this.$refs.tooltip.$el.innerHTML));
return ' ';
},
},
};
</script>
<template>
<div class="vulnerabilities-chart">
<div class="vulnerabilities-chart-wrapper">
<gl-chart :options="options" :width="1240" />
<chart-tooltip v-show="false" ref="tooltip" :title="tooltipTitle" :entries="tooltipEntries" />
</div>
</div>
</template>
<script>
export default {
name: 'VulnerabilityChartLabel',
props: {
name: {
type: String,
required: true,
},
color: {
type: String,
required: true,
},
value: {
type: [Number],
required: false,
default: null,
},
},
};
</script>
<template>
<div class="d-flex align-items-center mb-1 js-chart-label">
<div class="js-color" :style="{ backgroundColor: color, width: '12px', height: '4px' }"></div>
<strong class="ml-2 mr-3 text-capitalize js-name">{{ name }}</strong>
<span v-if="value !== null" class="ml-auto js-value">{{ value }}</span>
</div>
</template>
<script>
import VulnerabilityChartLabel from './vulnerability_chart_label.vue';
export default {
name: 'VulnerabilityChartTooltip',
components: {
VulnerabilityChartLabel,
},
props: {
title: {
type: String,
required: false,
default: '',
},
entries: {
type: Array,
required: true,
},
},
};
</script>
<template>
<div class="card">
<div class="card-header">
<strong> {{ title }} </strong>
</div>
<div class="card-body">
<vulnerability-chart-label
v-for="entry in entries"
:key="entry.seriesId + entry.dataIndex"
:name="entry.seriesName"
:value="entry.data[1]"
:color="entry.color"
/>
</div>
</div>
</template>
......@@ -19,6 +19,7 @@ export default () => {
vulnerabilityFeedbackHelpPath: el.dataset.vulnerabilityFeedbackHelpPath,
vulnerabilitiesEndpoint: el.dataset.vulnerabilitiesEndpoint,
vulnerabilitiesCountEndpoint: el.dataset.vulnerabilitiesSummaryEndpoint,
vulnerabilitiesHistoryEndpoint: el.dataset.vulnerabilitiesHistoryEndpoint,
},
});
},
......
......@@ -204,4 +204,38 @@ export const receiveRevertDismissalError = ({ commit }, { flashError }) => {
}
};
export const setVulnerabilitiesHistoryEndpoint = ({ commit }, endpoint) => {
commit(types.SET_VULNERABILITIES_HISTORY_ENDPOINT, endpoint);
};
export const fetchVulnerabilitiesHistory = ({ state, dispatch }) => {
dispatch('requestVulnerabilitiesHistory');
axios({
method: 'GET',
url: state.vulnerabilitiesHistoryEndpoint,
})
.then(response => {
const { data } = response;
dispatch('receiveVulnerabilitiesHistorySuccess', { data });
})
.catch(() => {
dispatch('receiveVulnerabilitiesHistoryError');
});
};
export const requestVulnerabilitiesHistory = ({ commit }) => {
commit(types.REQUEST_VULNERABILITIES_HISTORY);
};
export const receiveVulnerabilitiesHistorySuccess = ({ commit }, { data }) => {
commit(types.RECEIVE_VULNERABILITIES_HISTORY_SUCCESS, data);
};
export const receiveVulnerabilitiesHistoryError = ({ commit }) => {
commit(types.RECEIVE_VULNERABILITIES_HISTORY_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 () => {};
......@@ -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_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';
export const REQUEST_CREATE_ISSUE = 'REQUEST_CREATE_ISSUE';
......
......@@ -35,6 +35,21 @@ export default {
state.isLoadingVulnerabilitiesCount = false;
state.errorLoadingVulnerabilitiesCount = true;
},
[types.SET_VULNERABILITIES_HISTORY_ENDPOINT](state, payload) {
state.vulnerabilitiesHistoryEndpoint = payload;
},
[types.REQUEST_VULNERABILITIES_HISTORY](state) {
state.isLoadingVulnerabilitiesHistory = true;
state.errorLoadingVulnerabilitiesHistory = false;
},
[types.RECEIVE_VULNERABILITIES_HISTORY_SUCCESS](state, payload) {
state.isLoadingVulnerabilitiesHistory = false;
state.vulnerabilitiesHistory = payload;
},
[types.RECEIVE_VULNERABILITIES_HISTORY_ERROR](state) {
state.isLoadingVulnerabilitiesHistory = false;
state.errorLoadingVulnerabilitiesHistory = true;
},
[types.SET_MODAL_DATA](state, payload) {
const { vulnerability } = payload;
......
......@@ -3,12 +3,16 @@ import { s__ } from '~/locale';
export default () => ({
isLoadingVulnerabilities: true,
errorLoadingVulnerabilities: false,
vulnerabilities: [],
isLoadingVulnerabilitiesCount: true,
errorLoadingVulnerabilitiesCount: false,
pageInfo: {},
vulnerabilities: [],
vulnerabilitiesCount: {},
isLoadingVulnerabilitiesHistory: true,
errorLoadingVulnerabilitiesHistory: false,
vulnerabilitiesHistory: {},
pageInfo: {},
vulnerabilitiesCountEndpoint: null,
vulnerabilitiesHistoryEndpoint: null,
vulnerabilitiesEndpoint: null,
activeVulnerability: null,
modal: {
......
$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;
}
}
# 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
- 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') } }
---
title: Adds group security dashboard metrics chart
merge_request: 8631
author:
type: added
import Vue from 'vue';
import component from 'ee/security_dashboard/components/vulnerability_chart_label.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
function hexToRgb(hex) {
const cleanHex = hex.replace('#', '');
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})`;
}
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);
});
});
});
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 mockDataVulnerabilitiesHistory from '../store/vulnerabilities/data/mock_data_vulnerabilities_history.json';
describe('Vulnerabilities Chart', () => {
const Component = Vue.extend(component);
const vulnerabilitiesHistoryEndpoint = '/vulnerabilitiesEndpoint.json';
let store;
let mock;
let vm;
beforeEach(() => {
store = createStore();
store.state.vulnerabilities.vulnerabilitiesHistoryEndpoint = vulnerabilitiesHistoryEndpoint;
mock = new MockAdapater(axios);
mock.onGet(vulnerabilitiesHistoryEndpoint).replyOnce(200, mockDataVulnerabilitiesHistory);
vm = mountComponentWithStore(Component, { store });
});
afterEach(() => {
resetStore(store);
vm.$destroy();
mock.restore();
});
it('should render the e-chart instance', done => {
waitForPromises()
.then(() => {
expect(vm.$el.querySelector('[_echarts_instance_]')).not.toBeNull();
done();
})
.catch(done.fail);
});
});
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);
const props = {
title: 'Tooltip Title',
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],
},
],
};
let vm;
beforeEach(() => {
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);
});
});
......@@ -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 mockDataVulnerabilitiesHistory from './data/mock_data_vulnerabilities_history.json';
describe('vulnerabiliites count actions', () => {
const data = mockDataVulnerabilitiesCount;
......@@ -634,3 +635,130 @@ describe('revert vulnerability dismissal', () => {
});
});
});
describe('vulnerabiliites timeline actions', () => {
const data = mockDataVulnerabilitiesHistory;
describe('setVulnerabilitiesHistoryEndpoint', () => {
it('should commit the correct mutuation', done => {
const state = initialState;
const endpoint = 'fakepath.json';
testAction(
actions.setVulnerabilitiesHistoryEndpoint,
endpoint,
state,
[
{
type: types.SET_VULNERABILITIES_HISTORY_ENDPOINT,
payload: endpoint,
},
],
[],
done,
);
});
});
describe('fetchVulnerabilitesTimeline', () => {
let mock;
const state = initialState;
beforeEach(() => {
state.vulnerabilitiesCountEndpoint = `${TEST_HOST}/vulnerabilitIES_HISTORY.json`;
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('on success', () => {
beforeEach(() => {
mock.onGet(state.vulnerabilitiesHistoryEndpoint).replyOnce(200, data);
});
it('should dispatch the request and success actions', done => {
testAction(
actions.fetchVulnerabilitiesHistory,
{},
state,
[],
[
{ type: 'requestVulnerabilitiesHistory' },
{
type: 'receiveVulnerabilitiesHistorySuccess',
payload: { data },
},
],
done,
);
});
});
describe('on error', () => {
beforeEach(() => {
mock.onGet(state.vulnerabilitiesHistoryEndpoint).replyOnce(404, {});
});
it('should dispatch the request and error actions', done => {
testAction(
actions.fetchVulnerabilitiesHistory,
{},
state,
[],
[
{ type: 'requestVulnerabilitiesHistory' },
{ type: 'receiveVulnerabilitiesHistoryError' },
],
done,
);
});
});
});
describe('requestVulnerabilitesTimeline', () => {
it('should commit the request mutation', done => {
const state = initialState;
testAction(
actions.requestVulnerabilitiesHistory,
{},
state,
[{ type: types.REQUEST_VULNERABILITIES_HISTORY }],
[],
done,
);
});
});
describe('receiveVulnerabilitesTimelineSuccess', () => {
it('should commit the success mutation', done => {
const state = initialState;
testAction(
actions.receiveVulnerabilitiesHistorySuccess,
{ data },
state,
[{ type: types.RECEIVE_VULNERABILITIES_HISTORY_SUCCESS, payload: data }],
[],
done,
);
});
});
describe('receivetVulnerabilitesTimelineError', () => {
it('should commit the error mutation', done => {
const state = initialState;
testAction(
actions.receiveVulnerabilitiesHistoryError,
{},
state,
[{ type: types.RECEIVE_VULNERABILITIES_HISTORY_ERROR }],
[],
done,
);
});
});
});
{
"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
......@@ -131,6 +131,66 @@ describe('vulnerabilities module mutations', () => {
});
});
describe('SET_VULNERABILITIES_HISTORY_ENDPOINT', () => {
it('should set `vulnerabilitiesHistoryEndpoint` to `fakepath.json`', () => {
const state = createState();
const endpoint = 'fakepath.json';
mutations[types.SET_VULNERABILITIES_HISTORY_ENDPOINT](state, endpoint);
expect(state.vulnerabilitiesHistoryEndpoint).toEqual(endpoint);
});
});
describe('REQUEST_VULNERABILITIES_HISTORY', () => {
let state;
beforeEach(() => {
state = {
...createState(),
errorLoadingVulnerabilitiesHistory: true,
};
mutations[types.REQUEST_VULNERABILITIES_HISTORY](state);
});
it('should set `isLoadingVulnerabilitiesHistory` to `true`', () => {
expect(state.isLoadingVulnerabilitiesHistory).toBeTruthy();
});
it('should set `errorLoadingVulnerabilitiesHistory` to `false`', () => {
expect(state.errorLoadingVulnerabilitiesHistory).toBeFalsy();
});
});
describe('RECEIVE_VULNERABILITIES_HISTORY_SUCCESS', () => {
let payload;
let state;
beforeEach(() => {
payload = mockData;
state = createState();
mutations[types.RECEIVE_VULNERABILITIES_HISTORY_SUCCESS](state, payload);
});
it('should set `isLoadingVulnerabilitiesHistory` to `false`', () => {
expect(state.isLoadingVulnerabilitiesHistory).toBeFalsy();
});
it('should set `vulnerabilitiesHistory`', () => {
expect(state.vulnerabilitiesHistory).toBe(payload);
});
});
describe('RECEIVE_VULNERABILITIES_HISTORY_ERROR', () => {
it('should set `isLoadingVulnerabilitiesHistory` to `false`', () => {
const state = createState();
mutations[types.RECEIVE_VULNERABILITIES_HISTORY_ERROR](state);
expect(state.isLoadingVulnerabilitiesHistory).toBeFalsy();
});
});
describe('SET_MODAL_DATA', () => {
describe('with all the data', () => {
const vulnerability = mockData[0];
......
......@@ -9349,6 +9349,9 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
msgid "Vulnerability Chart"
msgstr ""
msgid "Vulnerability List"
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment