Commit 375c56a6 authored by Daniel Tian's avatar Daniel Tian Committed by Vitaly Slobodin

Remove unused vulnerability chart from Vuex vulnerability list

Remove unused vulnerability chart from Vuex vulnerability list, which
is only used on the pipeline security tab
parent cf8034d1
...@@ -11,10 +11,9 @@ import { ...@@ -11,10 +11,9 @@ import {
} from '~/lib/utils/datetime_utility'; } from '~/lib/utils/datetime_utility';
import { formattedChangeInPercent } from '~/lib/utils/number_utils'; import { formattedChangeInPercent } from '~/lib/utils/number_utils';
import ChartButtons from './vulnerability_chart_buttons.vue'; import ChartButtons from './vulnerability_chart_buttons.vue';
import { SEVERITY_LEVELS } from '../store/constants'; import { SEVERITY_LEVELS, DAYS } from '../store/constants';
const ISO_DATE = 'isoDate'; const ISO_DATE = 'isoDate';
const DAYS = { thirty: 30, sixty: 60, ninety: 90 };
export default { export default {
components: { components: {
......
...@@ -4,7 +4,6 @@ import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue'; ...@@ -4,7 +4,6 @@ import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue';
import Filters from './filters.vue'; import Filters from './filters.vue';
import SecurityDashboardLayout from './security_dashboard_layout.vue'; import SecurityDashboardLayout from './security_dashboard_layout.vue';
import SecurityDashboardTable from './security_dashboard_table.vue'; import SecurityDashboardTable from './security_dashboard_table.vue';
import VulnerabilityChart from './vulnerability_chart.vue';
import FuzzingArtifactsDownload from './fuzzing_artifacts_download.vue'; import FuzzingArtifactsDownload from './fuzzing_artifacts_download.vue';
import LoadingError from './loading_error.vue'; import LoadingError from './loading_error.vue';
...@@ -14,7 +13,6 @@ export default { ...@@ -14,7 +13,6 @@ export default {
IssueModal, IssueModal,
SecurityDashboardLayout, SecurityDashboardLayout,
SecurityDashboardTable, SecurityDashboardTable,
VulnerabilityChart,
FuzzingArtifactsDownload, FuzzingArtifactsDownload,
LoadingError, LoadingError,
}, },
...@@ -23,11 +21,6 @@ export default { ...@@ -23,11 +21,6 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
vulnerabilitiesHistoryEndpoint: {
type: String,
required: false,
default: '',
},
pipelineId: { pipelineId: {
type: Number, type: Number,
required: false, required: false,
...@@ -67,19 +60,11 @@ export default { ...@@ -67,19 +60,11 @@ export default {
vulnerability() { vulnerability() {
return this.modal.vulnerability; return this.modal.vulnerability;
}, },
shouldShowAside() {
return this.shouldShowChart;
},
shouldShowChart() {
return Boolean(this.vulnerabilitiesHistoryEndpoint);
},
}, },
created() { created() {
this.setPipelineId(this.pipelineId); this.setPipelineId(this.pipelineId);
this.setVulnerabilitiesEndpoint(this.vulnerabilitiesEndpoint); this.setVulnerabilitiesEndpoint(this.vulnerabilitiesEndpoint);
this.setVulnerabilitiesHistoryEndpoint(this.vulnerabilitiesHistoryEndpoint);
this.fetchVulnerabilities({ ...this.filters, page: this.pageInfo.page }); this.fetchVulnerabilities({ ...this.filters, page: this.pageInfo.page });
this.fetchVulnerabilitiesHistory(this.filters);
this.fetchPipelineJobs(); this.fetchPipelineJobs();
}, },
methods: { methods: {
...@@ -91,11 +76,9 @@ export default { ...@@ -91,11 +76,9 @@ export default {
'createMergeRequest', 'createMergeRequest',
'dismissVulnerability', 'dismissVulnerability',
'fetchVulnerabilities', 'fetchVulnerabilities',
'fetchVulnerabilitiesHistory',
'openDismissalCommentBox', 'openDismissalCommentBox',
'setPipelineId', 'setPipelineId',
'setVulnerabilitiesEndpoint', 'setVulnerabilitiesEndpoint',
'setVulnerabilitiesHistoryEndpoint',
'showDismissalDeleteButtons', 'showDismissalDeleteButtons',
'hideDismissalDeleteButtons', 'hideDismissalDeleteButtons',
'undoDismiss', 'undoDismiss',
...@@ -133,10 +116,6 @@ export default { ...@@ -133,10 +116,6 @@ export default {
<slot name="empty-state"></slot> <slot name="empty-state"></slot>
</template> </template>
</security-dashboard-table> </security-dashboard-table>
<template v-if="shouldShowAside" #aside>
<vulnerability-chart v-if="shouldShowChart" class="mb-3" />
</template>
</security-dashboard-layout> </security-dashboard-layout>
<issue-modal <issue-modal
......
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import dateFormat from 'dateformat';
import { GlTooltipDirective, GlTable } from '@gitlab/ui';
import { GlSparklineChart } from '@gitlab/ui/dist/charts';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import { s__, sprintf } from '~/locale';
import { firstAndLastY } from '~/lib/utils/chart_utils';
import { formattedChangeInPercent } from '~/lib/utils/number_utils';
import { differenceInMilliseconds, millisecondsPerDay } from '~/lib/utils/datetime_utility';
import ChartButtons from './vulnerability_chart_buttons.vue';
import { DAYS } from '../store/modules/vulnerabilities/constants';
import { SEVERITY_LEVELS } from '../store/constants';
export default {
name: 'VulnerabilityChart',
components: {
ChartButtons,
GlSparklineChart,
GlTable,
SeverityBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
},
DAYS,
fields: [
{ key: 'severityLevel', label: s__('VulnerabilityChart|Severity'), tdClass: 'border-0' },
{ key: 'chartData', label: '', tdClass: 'border-0 w-100' },
{ key: 'changeInPercent', label: '%', thClass: 'text-right', tdClass: 'border-0 text-right' },
{
key: 'currentVulnerabilitiesCount',
label: '#',
thClass: 'text-right',
tdClass: 'border-0 text-right',
},
],
severityLevels: [
SEVERITY_LEVELS.critical,
SEVERITY_LEVELS.high,
SEVERITY_LEVELS.medium,
SEVERITY_LEVELS.low,
],
computed: {
...mapState('vulnerabilities', [
'vulnerabilitiesHistory',
'vulnerabilitiesHistoryDayRange',
'vulnerabilitiesHistoryMaxDayInterval',
]),
...mapGetters('vulnerabilities', ['getFilteredVulnerabilitiesHistory']),
charts() {
const { severityLevels } = this.$options;
return severityLevels.map(severityLevel => {
const history = this.getFilteredVulnerabilitiesHistory(severityLevel);
const chartData = history.length ? history : this.emptyDataSet;
const [pastVulnerabilitiesCount, currentVulnerabilitiesCount] = firstAndLastY(chartData);
const changeInPercent = formattedChangeInPercent(
pastVulnerabilitiesCount,
currentVulnerabilitiesCount,
);
return {
severityLevel,
chartData,
currentVulnerabilitiesCount,
changeInPercent,
};
});
},
startDate() {
return differenceInMilliseconds(millisecondsPerDay * this.vulnerabilitiesHistoryDayRange);
},
dateInfo() {
const formattedStartDate = dateFormat(this.startDate, 'mmmm dS');
return sprintf(s__('VulnerabilityChart|%{formattedStartDate} to today'), {
formattedStartDate,
});
},
days() {
const { $options } = this;
return [$options.DAYS.THIRTY, $options.DAYS.SIXTY, $options.DAYS.NINETY];
},
emptyDataSet() {
const format = 'isoDate';
const formattedStartDate = dateFormat(this.startDate, format);
const formattedEndDate = dateFormat(Date.now(), format);
return [[formattedStartDate, 0], [formattedEndDate, 0]];
},
},
methods: {
...mapActions('vulnerabilities', ['setVulnerabilitiesHistoryDayRange']),
},
};
</script>
<template>
<section class="border rounded p-0">
<div class="p-3">
<header id="vulnerability-chart-header">
<h4 class="my-0">
{{ __('Vulnerabilities over time') }}
</h4>
<p ref="timeInfo" class="text-secondary mt-0 js-vulnerabilities-chart-time-info">
{{ dateInfo }}
</p>
</header>
<chart-buttons
:days="days"
:active-day="vulnerabilitiesHistoryDayRange"
@click="setVulnerabilitiesHistoryDayRange"
/>
</div>
<gl-table
:fields="$options.fields"
:items="charts"
:borderless="true"
thead-class="thead-white"
class="js-vulnerabilities-chart-severity-level-breakdown mb-2"
>
<template #head(changeInPercent)="{ label }">
<span v-gl-tooltip :title="__('Difference between start date and now')">{{ label }}</span>
</template>
<template #head(currentVulnerabilitiesCount)="{ label }">
<span v-gl-tooltip :title="__('Current vulnerabilities count')">{{ label }}</span>
</template>
<template #cell(severityLevel)="{ value }">
<severity-badge :ref="`severityBadge${value}`" :severity="value" />
</template>
<template #cell(chartData)="{ item }">
<div class="position-relative h-32-px">
<gl-sparkline-chart
:ref="`sparklineChart${item.severityLevel}`"
:height="32"
:data="item.chartData"
:tooltip-label="__('Vulnerabilities')"
:show-last-y-value="false"
class="position-absolute w-100 position-top-0 position-left-0"
/>
</div>
</template>
<template #cell(changeInPercent)="{ value }">
<span ref="changeInPercent">{{ value }}</span>
</template>
<template #cell(currentVulnerabilitiesCount)="{ value }">
<span ref="currentVulnerabilitiesCount">{{ value }}</span>
</template>
</gl-table>
</section>
</template>
...@@ -36,3 +36,5 @@ export const UNSCANNED_PROJECTS_DATE_RANGES = [ ...@@ -36,3 +36,5 @@ export const UNSCANNED_PROJECTS_DATE_RANGES = [
]; ];
export const PRIMARY_IDENTIFIER_TYPE = 'cve'; export const PRIMARY_IDENTIFIER_TYPE = 'cve';
export const DAYS = { thirty: 30, sixty: 60, ninety: 90 };
...@@ -477,46 +477,6 @@ export const receiveCreateMergeRequestError = ({ commit }, { flashError }) => { ...@@ -477,46 +477,6 @@ export const receiveCreateMergeRequestError = ({ commit }, { flashError }) => {
} }
}; };
export const setVulnerabilitiesHistoryEndpoint = ({ commit }, endpoint) => {
commit(types.SET_VULNERABILITIES_HISTORY_ENDPOINT, endpoint);
};
export const fetchVulnerabilitiesHistory = ({ state, dispatch }, params = {}) => {
if (!state.vulnerabilitiesHistoryEndpoint) {
return;
}
dispatch('requestVulnerabilitiesHistory');
axios({
method: 'GET',
url: state.vulnerabilitiesHistoryEndpoint,
params,
})
.then(response => {
const { data } = response;
dispatch('receiveVulnerabilitiesHistorySuccess', { data });
})
.catch(() => {
dispatch('receiveVulnerabilitiesHistoryError');
});
};
export const setVulnerabilitiesHistoryDayRange = ({ commit }, days) => {
commit(types.SET_VULNERABILITIES_HISTORY_DAY_RANGE, days);
};
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);
};
export const openDismissalCommentBox = ({ commit }) => { export const openDismissalCommentBox = ({ commit }) => {
commit(types.OPEN_DISMISSAL_COMMENT_BOX); commit(types.OPEN_DISMISSAL_COMMENT_BOX);
}; };
......
...@@ -10,12 +10,6 @@ export { ...@@ -10,12 +10,6 @@ export {
SEVERITIES, SEVERITIES,
} from '~/vulnerabilities/constants'; } from '~/vulnerabilities/constants';
export const DAYS = {
THIRTY: 30,
SIXTY: 60,
NINETY: 90,
};
export const LOADING_VULNERABILITIES_ERROR_CODES = { export const LOADING_VULNERABILITIES_ERROR_CODES = {
UNAUTHORIZED: httpStatusCodes.UNAUTHORIZED, UNAUTHORIZED: httpStatusCodes.UNAUTHORIZED,
FORBIDDEN: httpStatusCodes.FORBIDDEN, FORBIDDEN: httpStatusCodes.FORBIDDEN,
......
...@@ -13,29 +13,6 @@ export const loadingVulnerabilitiesFailedWithRecognizedErrorCode = state => ...@@ -13,29 +13,6 @@ export const loadingVulnerabilitiesFailedWithRecognizedErrorCode = state =>
state.loadingVulnerabilitiesErrorCode, state.loadingVulnerabilitiesErrorCode,
); );
export const getVulnerabilityHistoryByName = state => name =>
state.vulnerabilitiesHistory[name.toLowerCase()];
export const getFilteredVulnerabilitiesHistory = (state, getters) => name => {
const history = getters.getVulnerabilityHistoryByName(name);
const days = state.vulnerabilitiesHistoryDayRange;
if (!history) {
return [];
}
const data = Object.entries(history);
const currentDate = new Date();
const startDate = new Date();
startDate.setDate(currentDate.getDate() - days);
return data.filter(date => {
const parsedDate = Date.parse(date[0]);
return parsedDate > startDate;
});
};
export const selectedVulnerabilitiesCount = state => export const selectedVulnerabilitiesCount = state =>
Object.keys(state.selectedVulnerabilities).length; Object.keys(state.selectedVulnerabilities).length;
......
...@@ -6,12 +6,6 @@ export const REQUEST_VULNERABILITIES = 'REQUEST_VULNERABILITIES'; ...@@ -6,12 +6,6 @@ export const REQUEST_VULNERABILITIES = 'REQUEST_VULNERABILITIES';
export const RECEIVE_VULNERABILITIES_SUCCESS = 'RECEIVE_VULNERABILITIES_SUCCESS'; export const RECEIVE_VULNERABILITIES_SUCCESS = 'RECEIVE_VULNERABILITIES_SUCCESS';
export const RECEIVE_VULNERABILITIES_ERROR = 'RECEIVE_VULNERABILITIES_ERROR'; export const RECEIVE_VULNERABILITIES_ERROR = 'RECEIVE_VULNERABILITIES_ERROR';
export const SET_VULNERABILITIES_HISTORY_ENDPOINT = 'SET_VULNERABILITIES_HISTORY_ENDPOINT';
export const SET_VULNERABILITIES_HISTORY_DAY_RANGE = 'SET_VULNERABILITIES_HISTORY_DAY_RANGE';
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 SET_MODAL_DATA = 'SET_MODAL_DATA';
export const REQUEST_CREATE_ISSUE = 'REQUEST_CREATE_ISSUE'; export const REQUEST_CREATE_ISSUE = 'REQUEST_CREATE_ISSUE';
......
...@@ -2,7 +2,6 @@ import Vue from 'vue'; ...@@ -2,7 +2,6 @@ import Vue from 'vue';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { DAYS } from './constants';
import { isSameVulnerability } from './utils'; import { isSameVulnerability } from './utils';
export default { export default {
...@@ -34,30 +33,6 @@ export default { ...@@ -34,30 +33,6 @@ export default {
[types.SET_VULNERABILITIES_PAGE](state, payload) { [types.SET_VULNERABILITIES_PAGE](state, payload) {
state.pageInfo = { ...state.pageInfo, page: payload }; state.pageInfo = { ...state.pageInfo, page: payload };
}, },
[types.SET_VULNERABILITIES_HISTORY_ENDPOINT](state, payload) {
state.vulnerabilitiesHistoryEndpoint = payload;
},
[types.SET_VULNERABILITIES_HISTORY_DAY_RANGE](state, days) {
state.vulnerabilitiesHistoryDayRange = days;
if (days <= DAYS.THIRTY) {
state.vulnerabilitiesHistoryMaxDayInterval = 7;
} else if (days > DAYS.SIXTY) {
state.vulnerabilitiesHistoryMaxDayInterval = 14;
}
},
[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) { [types.SET_MODAL_DATA](state, payload) {
const { vulnerability } = payload; const { vulnerability } = payload;
......
...@@ -5,14 +5,8 @@ export default () => ({ ...@@ -5,14 +5,8 @@ export default () => ({
vulnerabilities: [], vulnerabilities: [],
errorLoadingVulnerabilitiesCount: false, errorLoadingVulnerabilitiesCount: false,
vulnerabilitiesCount: {}, vulnerabilitiesCount: {},
isLoadingVulnerabilitiesHistory: true,
errorLoadingVulnerabilitiesHistory: false,
vulnerabilitiesHistory: {},
vulnerabilitiesHistoryDayRange: 90,
vulnerabilitiesHistoryMaxDayInterval: 7,
pageInfo: {}, pageInfo: {},
pipelineId: null, pipelineId: null,
vulnerabilitiesHistoryEndpoint: null,
vulnerabilitiesEndpoint: null, vulnerabilitiesEndpoint: null,
activeVulnerability: null, activeVulnerability: null,
sourceBranch: null, sourceBranch: null,
......
...@@ -5,7 +5,6 @@ const refreshTypes = [`filters/${SET_FILTER}`, `filters/${SET_HIDE_DISMISSED}`]; ...@@ -5,7 +5,6 @@ const refreshTypes = [`filters/${SET_FILTER}`, `filters/${SET_HIDE_DISMISSED}`];
export default store => { export default store => {
const refreshVulnerabilities = payload => { const refreshVulnerabilities = payload => {
store.dispatch('vulnerabilities/fetchVulnerabilities', payload); store.dispatch('vulnerabilities/fetchVulnerabilities', payload);
store.dispatch('vulnerabilities/fetchVulnerabilitiesHistory', payload);
}; };
store.subscribe(({ type }) => { store.subscribe(({ type }) => {
......
...@@ -6,7 +6,6 @@ import LoadingError from 'ee/security_dashboard/components/loading_error.vue'; ...@@ -6,7 +6,6 @@ import LoadingError from 'ee/security_dashboard/components/loading_error.vue';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue'; import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import SecurityDashboardTable from 'ee/security_dashboard/components/security_dashboard_table.vue'; import SecurityDashboardTable from 'ee/security_dashboard/components/security_dashboard_table.vue';
import SecurityDashboard from 'ee/security_dashboard/components/security_dashboard_vuex.vue'; import SecurityDashboard from 'ee/security_dashboard/components/security_dashboard_vuex.vue';
import VulnerabilityChart from 'ee/security_dashboard/components/vulnerability_chart.vue';
import createStore from 'ee/security_dashboard/store'; import createStore from 'ee/security_dashboard/store';
import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue'; import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue';
...@@ -15,7 +14,6 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -15,7 +14,6 @@ import axios from '~/lib/utils/axios_utils';
const pipelineId = 123; const pipelineId = 123;
const vulnerabilitiesEndpoint = `${TEST_HOST}/vulnerabilities`; const vulnerabilitiesEndpoint = `${TEST_HOST}/vulnerabilities`;
const vulnerabilitiesHistoryEndpoint = `${TEST_HOST}/vulnerabilities_history`;
jest.mock('~/lib/utils/url_utility', () => ({ jest.mock('~/lib/utils/url_utility', () => ({
getParameterValues: jest.fn().mockReturnValue([]), getParameterValues: jest.fn().mockReturnValue([]),
...@@ -41,7 +39,6 @@ describe('Security Dashboard component', () => { ...@@ -41,7 +39,6 @@ describe('Security Dashboard component', () => {
propsData: { propsData: {
dashboardDocumentation: '', dashboardDocumentation: '',
vulnerabilitiesEndpoint, vulnerabilitiesEndpoint,
vulnerabilitiesHistoryEndpoint,
pipelineId, pipelineId,
...props, ...props,
}, },
...@@ -75,10 +72,6 @@ describe('Security Dashboard component', () => { ...@@ -75,10 +72,6 @@ describe('Security Dashboard component', () => {
expect(wrapper.find(SecurityDashboardTable).exists()).toBe(true); expect(wrapper.find(SecurityDashboardTable).exists()).toBe(true);
}); });
it('renders the vulnerability chart', () => {
expect(wrapper.find(VulnerabilityChart).exists()).toBe(true);
});
it('sets the pipeline id', () => { it('sets the pipeline id', () => {
expect(setPipelineIdSpy).toHaveBeenCalledWith(pipelineId); expect(setPipelineIdSpy).toHaveBeenCalledWith(pipelineId);
}); });
...@@ -143,21 +136,6 @@ describe('Security Dashboard component', () => { ...@@ -143,21 +136,6 @@ describe('Security Dashboard component', () => {
); );
}); });
describe.each`
endpointProp | Component
${'vulnerabilitiesHistoryEndpoint'} | ${VulnerabilityChart}
`('with an empty $endpointProp', ({ endpointProp, Component }) => {
beforeEach(() => {
createComponent({
[endpointProp]: '',
});
});
it(`does not show the ${Component.name}`, () => {
expect(wrapper.find(Component).exists()).toBe(false);
});
});
describe('on error', () => { describe('on error', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
......
import { shallowMount, mount, createLocalVue } from '@vue/test-utils'; import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
import Vue from 'vue'; import Vue from 'vue';
import component from 'ee/security_dashboard/components/vulnerability_chart_buttons.vue'; import component from 'ee/security_dashboard/components/vulnerability_chart_buttons.vue';
import { DAYS } from 'ee/security_dashboard/store/modules/vulnerabilities/constants'; import { DAYS } from 'ee/security_dashboard/store/constants';
const localVue = createLocalVue(); const localVue = createLocalVue();
describe('Vulnerability Chart Buttons', () => { describe('Vulnerability Chart Buttons', () => {
let wrapper; let wrapper;
const Component = Vue.extend(component); const Component = Vue.extend(component);
const days = [DAYS.THIRTY, DAYS.SIXTY, DAYS.NINETY]; const days = Object.values(DAYS);
const createWrapper = (props = {}, mountfn = shallowMount) => { const createWrapper = (props = {}, mountfn = shallowMount) => {
wrapper = mountfn(localVue.extend(Component), { wrapper = mountfn(localVue.extend(Component), {
...@@ -23,7 +23,7 @@ describe('Vulnerability Chart Buttons', () => { ...@@ -23,7 +23,7 @@ describe('Vulnerability Chart Buttons', () => {
describe('when rendering the buttons', () => { describe('when rendering the buttons', () => {
it('should render with 90 days selected', () => { it('should render with 90 days selected', () => {
const activeDay = DAYS.NINETY; const activeDay = DAYS.ninety;
createWrapper({ activeDay }); createWrapper({ activeDay });
const activeButton = wrapper.find('[data-days="90"].active'); const activeButton = wrapper.find('[data-days="90"].active');
...@@ -32,7 +32,7 @@ describe('Vulnerability Chart Buttons', () => { ...@@ -32,7 +32,7 @@ describe('Vulnerability Chart Buttons', () => {
}); });
it('should render with 60 days selected', () => { it('should render with 60 days selected', () => {
const activeDay = DAYS.SIXTY; const activeDay = DAYS.sixty;
createWrapper({ activeDay }); createWrapper({ activeDay });
const activeButton = wrapper.find('[data-days="60"].active'); const activeButton = wrapper.find('[data-days="60"].active');
...@@ -41,7 +41,7 @@ describe('Vulnerability Chart Buttons', () => { ...@@ -41,7 +41,7 @@ describe('Vulnerability Chart Buttons', () => {
}); });
it('should render with 30 days selected', () => { it('should render with 30 days selected', () => {
const activeDay = DAYS.THIRTY; const activeDay = DAYS.thirty;
createWrapper({ activeDay }); createWrapper({ activeDay });
const activeButton = wrapper.find('[data-days="30"].active'); const activeButton = wrapper.find('[data-days="30"].active');
...@@ -51,7 +51,7 @@ describe('Vulnerability Chart Buttons', () => { ...@@ -51,7 +51,7 @@ describe('Vulnerability Chart Buttons', () => {
}); });
describe('when clicking the button', () => { describe('when clicking the button', () => {
const activeDay = DAYS.THIRTY; const activeDay = DAYS.thirty;
beforeEach(() => { beforeEach(() => {
createWrapper({ activeDay }, mount); createWrapper({ activeDay }, mount);
...@@ -59,15 +59,15 @@ describe('Vulnerability Chart Buttons', () => { ...@@ -59,15 +59,15 @@ describe('Vulnerability Chart Buttons', () => {
it('should call the clickHandler', () => { it('should call the clickHandler', () => {
jest.spyOn(wrapper.vm, 'clickHandler'); jest.spyOn(wrapper.vm, 'clickHandler');
wrapper.find('[data-days="30"].active').trigger('click', DAYS.THIRTY); wrapper.find('[data-days="30"].active').trigger('click', DAYS.thirty);
expect(wrapper.vm.clickHandler).toHaveBeenCalledWith(DAYS.THIRTY); expect(wrapper.vm.clickHandler).toHaveBeenCalledWith(DAYS.thirty);
}); });
it('should emit a click event', () => { it('should emit a click event', () => {
wrapper.find('[data-days="30"].active').trigger('click', DAYS.THIRTY); wrapper.find('[data-days="30"].active').trigger('click', DAYS.thirty);
expect(wrapper.emitted().click[0]).toEqual([DAYS.THIRTY]); expect(wrapper.emitted().click[0]).toEqual([DAYS.thirty]);
}); });
}); });
}); });
import { GlSparklineChart } from '@gitlab/ui/dist/charts';
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import Chart from 'ee/security_dashboard/components/vulnerability_chart.vue';
import ChartButtons from 'ee/security_dashboard/components/vulnerability_chart_buttons.vue';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import stubChildren from 'helpers/stub_children';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Vulnerability Chart component', () => {
let actions;
let getters;
let state;
let store;
let wrapper;
const findTimeInfo = () => wrapper.find({ ref: 'timeInfo' });
const findSeverityBadgeForLevel = severityLevel =>
wrapper.find(SeverityBadge, { ref: `severityBadge${severityLevel}` });
const findSparklineChartForLevel = severityLevel =>
wrapper.find(GlSparklineChart, { ref: `sparklineChart${severityLevel}` });
const findChangeInPercent = () => wrapper.find({ ref: 'changeInPercent' });
const findCurrentVulnerabilitiesCount = () =>
wrapper.find({ ref: 'currentVulnerabilitiesCount' });
const factory = ({ vulnerabilitiesCount = [], stubs = {} } = {}) => {
actions = {
setVulnerabilitiesHistoryDayRange: jest.fn(),
};
getters = {
getFilteredVulnerabilitiesHistory: () => () =>
vulnerabilitiesCount.map(c => ['some-date', c]),
getVulnerabilityHistoryByName: () => () => [],
};
state = {
vulnerabilitiesHistory: {},
vulnerabilitiesHistoryDayRange: 90,
vulnerabilitiesHistoryMaxDayInterval: 7,
};
store = new Vuex.Store({
modules: {
vulnerabilities: {
namespaced: true,
actions,
getters,
state,
},
},
});
wrapper = mount(Chart, {
localVue,
store,
stubs: {
...stubChildren(Chart),
...stubs,
},
});
};
afterEach(() => {
wrapper.destroy();
jest.restoreAllMocks();
});
describe('header', () => {
it.each`
mockDate | dayRange | expectedStartDate
${'2000-01-01T00:00:00Z'} | ${90} | ${'October 3rd'}
${'2000-01-01T00:00:00Z'} | ${60} | ${'November 2nd'}
${'2000-01-01T00:00:00Z'} | ${30} | ${'December 2nd'}
`(
'shows "$expectedStartDate" when the date range is set to "$dayRange" days',
({ mockDate, dayRange, expectedStartDate }) => {
jest.spyOn(global.Date, 'now').mockImplementation(() => new Date(mockDate));
factory();
store.state.vulnerabilities.vulnerabilitiesHistoryDayRange = dayRange;
return wrapper.vm.$nextTick().then(() => {
expect(findTimeInfo().text()).toContain(expectedStartDate);
});
},
);
});
describe('date range selectors', () => {
beforeEach(factory);
it('shows a set of buttons to select the supported day ranges', () => {
const supportedDayRanges = [30, 60, 90];
expect(wrapper.find(ChartButtons).props('days')).toEqual(supportedDayRanges);
});
it('dispatches "setVulnerabilitiesHistoryDayRange" when a day range is selected', () => {
const selectedDayRange = 30;
wrapper.find(ChartButtons).vm.$emit('click', selectedDayRange);
expect(actions.setVulnerabilitiesHistoryDayRange).toHaveBeenCalledTimes(1);
expect(actions.setVulnerabilitiesHistoryDayRange.mock.calls[0][1]).toBe(selectedDayRange);
});
});
describe('charts table', () => {
describe.each(['Critical', 'Medium', 'High', 'Low'])(
'for the given severity level "%s"',
severityLevel => {
beforeEach(() => {
factory({ stubs: { GlTable: false } });
});
it('shows a severity badge', () => {
expect(findSeverityBadgeForLevel(severityLevel).exists()).toBe(true);
});
it('shows a chart', () => {
expect(findSparklineChartForLevel(severityLevel).exists()).toBe(true);
});
},
);
it.each`
countPast | countCurrent | expectedOutput
${1} | ${2} | ${'+100%'}
${100} | ${1} | ${'-99%'}
${1} | ${1} | ${'+0%'}
${0} | ${1} | ${'-'}
`(
'shows "$expectedOutput" when the vulnerabilities changed from "$countPast" to "$countCurrent"',
({ countPast, countCurrent, expectedOutput }) => {
factory({
vulnerabilitiesCount: [countPast, countCurrent],
stubs: {
GlTable: false,
},
});
expect(findChangeInPercent().text()).toBe(expectedOutput);
},
);
it.each`
vulnerabilitiesCount | expectedOutput
${[1, 2, 3]} | ${'3'}
`('shows the current vulnerabilities count', ({ vulnerabilitiesCount, expectedOutput }) => {
factory({ vulnerabilitiesCount, stubs: { GlTable: false } });
expect(findCurrentVulnerabilitiesCount().text()).toBe(expectedOutput);
});
});
});
import { DAYS } from 'ee/security_dashboard/store/modules/vulnerabilities/constants';
import * as getters from 'ee/security_dashboard/store/modules/vulnerabilities/getters'; import * as getters from 'ee/security_dashboard/store/modules/vulnerabilities/getters';
import createState from 'ee/security_dashboard/store/modules/vulnerabilities/state';
import mockHistoryData from './data/mock_data_vulnerabilities_history.json';
describe('vulnerabilities module getters', () => { describe('vulnerabilities module getters', () => {
describe('dashboardError', () => { describe('dashboardError', () => {
...@@ -57,55 +54,6 @@ describe('vulnerabilities module getters', () => { ...@@ -57,55 +54,6 @@ describe('vulnerabilities module getters', () => {
}); });
}); });
describe('getFilteredVulnerabilitiesHistory', () => {
let state;
const mockedGetters = () => {
const getVulnerabilityHistoryByName = name =>
getters.getVulnerabilityHistoryByName(state)(name);
return { getVulnerabilityHistoryByName };
};
beforeEach(() => {
state = createState();
state.vulnerabilitiesHistory = mockHistoryData;
const mockDate = new Date(2019, 1, 2);
const originalDate = Date;
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
global.Date.now = originalDate.now;
global.Date.parse = originalDate.parse;
global.Date.UTC = originalDate.UTC;
});
it('should filter the data to the last 30 days and days we have data for', () => {
state.vulnerabilitiesHistoryDayRange = DAYS.THIRTY;
const filteredResults = getters.getFilteredVulnerabilitiesHistory(state, mockedGetters())(
'critical',
);
expect(filteredResults).toHaveLength(28);
});
it('should filter the data to the last 60 days and days we have data for', () => {
state.vulnerabilitiesHistoryDayRange = DAYS.SIXTY;
const filteredResults = getters.getFilteredVulnerabilitiesHistory(state, mockedGetters())(
'critical',
);
expect(filteredResults).toHaveLength(58);
});
it('should filter the data to the last 90 days and days we have data for', () => {
state.vulnerabilitiesHistoryDayRange = DAYS.NINETY;
const filteredResults = getters.getFilteredVulnerabilitiesHistory(state, mockedGetters())(
'critical',
);
expect(filteredResults).toHaveLength(88);
});
});
describe('isSelectingVulnerabilities', () => { describe('isSelectingVulnerabilities', () => {
it('should return true if we have selected vulnerabilities', () => { it('should return true if we have selected vulnerabilities', () => {
const mockedGetters = { selectedVulnerabilitiesCount: 3 }; const mockedGetters = { selectedVulnerabilitiesCount: 3 };
......
import { DAYS } from 'ee/security_dashboard/store/modules/vulnerabilities/constants';
import * as types from 'ee/security_dashboard/store/modules/vulnerabilities/mutation_types'; import * as types from 'ee/security_dashboard/store/modules/vulnerabilities/mutation_types';
import mutations from 'ee/security_dashboard/store/modules/vulnerabilities/mutations'; import mutations from 'ee/security_dashboard/store/modules/vulnerabilities/mutations';
import createState from 'ee/security_dashboard/store/modules/vulnerabilities/state'; import createState from 'ee/security_dashboard/store/modules/vulnerabilities/state';
...@@ -113,76 +112,6 @@ describe('vulnerabilities module mutations', () => { ...@@ -113,76 +112,6 @@ describe('vulnerabilities module mutations', () => {
}); });
}); });
describe('SET_VULNERABILITIES_HISTORY_ENDPOINT', () => {
it('should set `vulnerabilitiesHistoryEndpoint` to `fakepath.json`', () => {
const endpoint = 'fakepath.json';
mutations[types.SET_VULNERABILITIES_HISTORY_ENDPOINT](state, endpoint);
expect(state.vulnerabilitiesHistoryEndpoint).toBe(endpoint);
});
});
describe('REQUEST_VULNERABILITIES_HISTORY', () => {
beforeEach(() => {
state.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;
beforeEach(() => {
payload = mockData;
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`', () => {
mutations[types.RECEIVE_VULNERABILITIES_HISTORY_ERROR](state);
expect(state.isLoadingVulnerabilitiesHistory).toBeFalsy();
});
});
describe('SET_VULNERABILITIES_HISTORY_DAY_RANGE', () => {
it('should set the vulnerabilitiesHistoryDayRange to number of days', () => {
mutations[types.SET_VULNERABILITIES_HISTORY_DAY_RANGE](state, DAYS.THIRTY);
expect(state.vulnerabilitiesHistoryDayRange).toBe(DAYS.THIRTY);
});
it('should set the vulnerabilitiesHistoryMaxDayInterval to 7 if days are 60 and under', () => {
mutations[types.SET_VULNERABILITIES_HISTORY_DAY_RANGE](state, DAYS.THIRTY);
expect(state.vulnerabilitiesHistoryMaxDayInterval).toBe(7);
});
it('should set the vulnerabilitiesHistoryMaxDayInterval to 14 if over 60', () => {
mutations[types.SET_VULNERABILITIES_HISTORY_DAY_RANGE](state, DAYS.NINETY);
expect(state.vulnerabilitiesHistoryMaxDayInterval).toBe(14);
});
});
describe('SET_MODAL_DATA', () => { describe('SET_MODAL_DATA', () => {
describe('with all the data', () => { describe('with all the data', () => {
const vulnerability = mockData[0]; const vulnerability = mockData[0];
......
...@@ -5,12 +5,8 @@ import { ...@@ -5,12 +5,8 @@ import {
} from 'ee/security_dashboard/store/modules/filters/mutation_types'; } from 'ee/security_dashboard/store/modules/filters/mutation_types';
function expectRefreshDispatches(store, payload) { function expectRefreshDispatches(store, payload) {
expect(store.dispatch).toHaveBeenCalledTimes(2); expect(store.dispatch).toHaveBeenCalledTimes(1);
expect(store.dispatch).toHaveBeenCalledWith('vulnerabilities/fetchVulnerabilities', payload); expect(store.dispatch).toHaveBeenCalledWith('vulnerabilities/fetchVulnerabilities', payload);
expect(store.dispatch).toHaveBeenCalledWith(
'vulnerabilities/fetchVulnerabilitiesHistory',
payload,
);
} }
describe('mediator', () => { describe('mediator', () => {
......
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