Commit 679e3061 authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch '287761-remove-unused-vulnerabilities-history-endpoint' into 'master'

Remove unused code for vulnerability chart on pipeline security tab

See merge request gitlab-org/gitlab!48422
parents 74355ad5 375c56a6
...@@ -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