Commit 32f4d602 authored by Tim Zallmann's avatar Tim Zallmann

Merge branch '6709-security-dashboard-error-states' into 'master'

Adds split error states for the group sec dashboard

Closes #7981

See merge request gitlab-org/gitlab-ee!8208
parents d8dcd0c2 acaa9827
...@@ -7,7 +7,6 @@ import Tab from '~/vue_shared/components/tabs/tab.vue'; ...@@ -7,7 +7,6 @@ import Tab from '~/vue_shared/components/tabs/tab.vue';
import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue'; import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue';
import SecurityDashboardTable from './security_dashboard_table.vue'; import SecurityDashboardTable from './security_dashboard_table.vue';
import VulnerabilityCountList from './vulnerability_count_list.vue'; import VulnerabilityCountList from './vulnerability_count_list.vue';
import SvgBlankState from '~/pipelines/components/blank_state.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import popover from '~/vue_shared/directives/popover'; import popover from '~/vue_shared/directives/popover';
...@@ -20,7 +19,6 @@ export default { ...@@ -20,7 +19,6 @@ export default {
Icon, Icon,
IssueModal, IssueModal,
SecurityDashboardTable, SecurityDashboardTable,
SvgBlankState,
Tab, Tab,
Tabs, Tabs,
VulnerabilityCountList, VulnerabilityCountList,
...@@ -30,14 +28,6 @@ export default { ...@@ -30,14 +28,6 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
emptyStateSvgPath: {
type: String,
required: true,
},
errorStateSvgPath: {
type: String,
required: true,
},
vulnerabilitiesEndpoint: { vulnerabilitiesEndpoint: {
type: String, type: String,
required: true, required: true,
...@@ -49,7 +39,7 @@ export default { ...@@ -49,7 +39,7 @@ export default {
}, },
computed: { computed: {
...mapGetters('vulnerabilities', ['vulnerabilitiesCountByReportType']), ...mapGetters('vulnerabilities', ['vulnerabilitiesCountByReportType']),
...mapState('vulnerabilities', ['hasError', 'modal']), ...mapState('vulnerabilities', ['modal']),
sastCount() { sastCount() {
return this.vulnerabilitiesCountByReportType('sast'); return this.vulnerabilitiesCountByReportType('sast');
}, },
...@@ -96,50 +86,39 @@ export default { ...@@ -96,50 +86,39 @@ export default {
<template> <template>
<div> <div>
<div class="flash-container"></div> <vulnerability-count-list />
<svg-blank-state <tabs stop-propagation>
v-if="hasError" <tab active>
:svg-path="errorStateSvgPath" <template slot="title">
:message="s__(`Security Reports|There was an error fetching the dashboard. <span>{{ __('SAST') }}</span>
Please try again in a few moments or contact your support team.`)" <span
/> v-if="sastCount"
<div v-else> class="badge badge-pill"
<vulnerability-count-list /> >
<tabs stop-propagation> {{ sastCount }}
<tab active> </span>
<template slot="title"> <span
<span>{{ __('SAST') }}</span> v-popover="popoverOptions"
<span class="text-muted prepend-left-4"
v-if="sastCount" :aria-label="__('help')"
class="badge badge-pill" >
> <icon
{{ sastCount }} name="question"
</span> class="vertical-align-middle"
<span />
v-popover="popoverOptions" </span>
class="text-muted prepend-left-4" </template>
:aria-label="__('help')"
>
<icon
name="question"
class="vertical-align-middle"
/>
</span>
</template>
<security-dashboard-table <security-dashboard-table />
:empty-state-svg-path="emptyStateSvgPath" </tab>
/> </tabs>
</tab> <issue-modal
</tabs> :modal="modal"
<issue-modal :can-create-issue-permission="true"
:modal="modal" :can-create-feedback-permission="true"
:can-create-issue-permission="true" @createNewIssue="createIssue({ vulnerability: modal.vulnerability })"
:can-create-feedback-permission="true" @dismissIssue="dismissVulnerability({ vulnerability: modal.vulnerability })"
@createNewIssue="createIssue({ vulnerability: modal.vulnerability })" @revertDismissIssue="undoDismissal({ vulnerability: modal.vulnerability })"
@dismissIssue="dismissVulnerability({ vulnerability: modal.vulnerability })" />
@revertDismissIssue="undoDismissal({ vulnerability: modal.vulnerability })"
/>
</div>
</div> </div>
</template> </template>
<script> <script>
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import Pagination from '~/vue_shared/components/pagination_links.vue'; import Pagination from '~/vue_shared/components/pagination_links.vue';
import SecurityDashboardTableRow from './security_dashboard_table_row.vue'; import SecurityDashboardTableRow from './security_dashboard_table_row.vue';
...@@ -11,6 +11,7 @@ export default { ...@@ -11,6 +11,7 @@ export default {
}, },
computed: { computed: {
...mapState('vulnerabilities', ['vulnerabilities', 'pageInfo', 'isLoadingVulnerabilities']), ...mapState('vulnerabilities', ['vulnerabilities', 'pageInfo', 'isLoadingVulnerabilities']),
...mapGetters('vulnerabilities', ['dashboardListError']),
showPagination() { showPagination() {
return this.pageInfo && this.pageInfo.total; return this.pageInfo && this.pageInfo.total;
}, },
...@@ -49,6 +50,16 @@ export default { ...@@ -49,6 +50,16 @@ export default {
{{ s__('Reports|Confidence') }} {{ s__('Reports|Confidence') }}
</div> </div>
</div> </div>
<div class="flash-container">
<div
v-if="dashboardListError"
class="flash-alert">
<div class="flash-text container-fluid container-limited limit-container-width">
{{ s__('Security Dashboard|Error fetching the vulnerability list. Please check your network connection and try again.') }}
</div>
</div>
</div>
<div v-if="isLoadingVulnerabilities"> <div v-if="isLoadingVulnerabilities">
<security-dashboard-table-row <security-dashboard-table-row
......
...@@ -33,7 +33,7 @@ export default { ...@@ -33,7 +33,7 @@ export default {
{{ severity }} {{ severity }}
</div> </div>
<div class="vulnerability-count-body"> <div class="vulnerability-count-body">
<span v-if="isLoading">&nbsp;</span> <span v-if="isLoading">&mdash;</span>
<span v-else>{{ count }}</span> <span v-else>{{ count }}</span>
</div> </div>
</div> </div>
......
...@@ -11,7 +11,11 @@ export default { ...@@ -11,7 +11,11 @@ export default {
VulnerabilityCount, VulnerabilityCount,
}, },
computed: { computed: {
...mapGetters('vulnerabilities', ['vulnerabilitiesCountBySeverity']), ...mapGetters('vulnerabilities', [
'vulnerabilitiesCountBySeverity',
'dashboardCountError',
'dashboardError',
]),
...mapState('vulnerabilities', ['isLoadingVulnerabilitiesCount']), ...mapState('vulnerabilities', ['isLoadingVulnerabilitiesCount']),
counts() { counts() {
return SEVERITIES.map(severity => { return SEVERITIES.map(severity => {
...@@ -25,6 +29,16 @@ export default { ...@@ -25,6 +29,16 @@ export default {
<template> <template>
<div class="vulnerabilities-count-list"> <div class="vulnerabilities-count-list">
<div class="flash-container">
<div
v-if="dashboardError"
class="flash-alert">
<div class="flash-text container-fluid container-limited limit-container-width">
{{ s__('Security Dashboard|Error fetching the dashboard data. Please check your network connection and try again.') }}
</div>
</div>
</div>
<div class="row"> <div class="row">
<div <div
v-for="count in counts" v-for="count in counts"
...@@ -38,6 +52,16 @@ export default { ...@@ -38,6 +52,16 @@ export default {
/> />
</div> </div>
</div> </div>
<div class="flash-container">
<div
v-if="dashboardCountError"
class="flash-alert">
<div class="flash-text container-fluid container-limited limit-container-width">
{{ s__('Security Dashboard|Error fetching the vulnerability counts. Please check your network connection and try again.') }}
</div>
</div>
</div>
</div> </div>
</template> </template>
......
...@@ -15,8 +15,6 @@ export default () => { ...@@ -15,8 +15,6 @@ export default () => {
return createElement('group-security-dashboard-app', { return createElement('group-security-dashboard-app', {
props: { props: {
dashboardDocumentation: el.dataset.dashboardDocumentation, dashboardDocumentation: el.dataset.dashboardDocumentation,
errorStateSvgPath: el.dataset.errorStateSvgPath,
emptyStateSvgPath: el.dataset.emptyStateSvgPath,
vulnerabilitiesEndpoint: el.dataset.vulnerabilitiesEndpoint, vulnerabilitiesEndpoint: el.dataset.vulnerabilitiesEndpoint,
vulnerabilitiesCountEndpoint: el.dataset.vulnerabilitiesSummaryEndpoint, vulnerabilitiesCountEndpoint: el.dataset.vulnerabilitiesSummaryEndpoint,
}, },
......
...@@ -112,9 +112,16 @@ export const receiveCreateIssueSuccess = ({ commit }, payload) => { ...@@ -112,9 +112,16 @@ export const receiveCreateIssueSuccess = ({ commit }, payload) => {
commit(types.RECEIVE_CREATE_ISSUE_SUCCESS, payload); commit(types.RECEIVE_CREATE_ISSUE_SUCCESS, payload);
}; };
export const receiveCreateIssueError = ({ commit }) => { export const receiveCreateIssueError = ({ commit }, { flashError }) => {
commit(types.RECEIVE_CREATE_ISSUE_ERROR); commit(types.RECEIVE_CREATE_ISSUE_ERROR);
createFlash(s__('Security Reports|There was an error creating the issue.'));
if (flashError) {
createFlash(
s__('Security Reports|There was an error creating the issue.'),
'alert',
document.querySelector('.ci-table'),
);
}
}; };
export const dismissVulnerability = ({ dispatch }, { vulnerability, flashError }) => { export const dismissVulnerability = ({ dispatch }, { vulnerability, flashError }) => {
...@@ -152,7 +159,11 @@ export const receiveDismissVulnerabilitySuccess = ({ commit }, payload) => { ...@@ -152,7 +159,11 @@ export const receiveDismissVulnerabilitySuccess = ({ commit }, payload) => {
export const receiveDismissVulnerabilityError = ({ commit }, { flashError }) => { export const receiveDismissVulnerabilityError = ({ commit }, { flashError }) => {
commit(types.RECEIVE_DISMISS_VULNERABILITY_ERROR); commit(types.RECEIVE_DISMISS_VULNERABILITY_ERROR);
if (flashError) { if (flashError) {
createFlash(s__('Security Reports|There was an error dismissing the issue.')); createFlash(
s__('Security Reports|There was an error dismissing the vulnerability.'),
'alert',
document.querySelector('.ci-table'),
);
} }
}; };
...@@ -185,7 +196,11 @@ export const receiveUndoDismissalSuccess = ({ commit }, payload) => { ...@@ -185,7 +196,11 @@ export const receiveUndoDismissalSuccess = ({ commit }, payload) => {
export const receiveUndoDismissalError = ({ commit }, { flashError }) => { export const receiveUndoDismissalError = ({ commit }, { flashError }) => {
commit(types.RECEIVE_UNDO_DISMISSAL_ERROR); commit(types.RECEIVE_UNDO_DISMISSAL_ERROR);
if (flashError) { if (flashError) {
createFlash(s__('Security Reports|There was an error undoing this dismissal.')); createFlash(
s__('Security Reports|There was an error undoing this dismissal.'),
'alert',
document.querySelector('.ci-table'),
);
} }
}; };
......
...@@ -8,5 +8,11 @@ export const vulnerabilitiesCountByReportType = state => type => { ...@@ -8,5 +8,11 @@ export const vulnerabilitiesCountByReportType = state => type => {
const counts = state.vulnerabilitiesCount[type]; const counts = state.vulnerabilitiesCount[type];
return counts ? Object.values(counts).reduce(sum, 0) : 0; return counts ? Object.values(counts).reduce(sum, 0) : 0;
}; };
export const dashboardError = state =>
state.errorLoadingVulnerabilities && state.errorLoadingVulnerabilitiesCount;
export const dashboardListError = state =>
state.errorLoadingVulnerabilities && !state.errorLoadingVulnerabilitiesCount;
export const dashboardCountError = state =>
!state.errorLoadingVulnerabilities && state.errorLoadingVulnerabilitiesCount;
export default () => {}; export default () => {};
...@@ -9,7 +9,7 @@ export default { ...@@ -9,7 +9,7 @@ export default {
}, },
[types.REQUEST_VULNERABILITIES](state) { [types.REQUEST_VULNERABILITIES](state) {
state.isLoadingVulnerabilities = true; state.isLoadingVulnerabilities = true;
state.hasError = false; state.errorLoadingVulnerabilities = false;
}, },
[types.RECEIVE_VULNERABILITIES_SUCCESS](state, payload) { [types.RECEIVE_VULNERABILITIES_SUCCESS](state, payload) {
state.isLoadingVulnerabilities = false; state.isLoadingVulnerabilities = false;
...@@ -18,14 +18,14 @@ export default { ...@@ -18,14 +18,14 @@ export default {
}, },
[types.RECEIVE_VULNERABILITIES_ERROR](state) { [types.RECEIVE_VULNERABILITIES_ERROR](state) {
state.isLoadingVulnerabilities = false; state.isLoadingVulnerabilities = false;
state.hasError = true; state.errorLoadingVulnerabilities = true;
}, },
[types.SET_VULNERABILITIES_COUNT_ENDPOINT](state, payload) { [types.SET_VULNERABILITIES_COUNT_ENDPOINT](state, payload) {
state.vulnerabilitiesCountEndpoint = payload; state.vulnerabilitiesCountEndpoint = payload;
}, },
[types.REQUEST_VULNERABILITIES_COUNT](state) { [types.REQUEST_VULNERABILITIES_COUNT](state) {
state.isLoadingVulnerabilitiesCount = true; state.isLoadingVulnerabilitiesCount = true;
state.hasError = false; state.errorLoadingVulnerabilitiesCount = false;
}, },
[types.RECEIVE_VULNERABILITIES_COUNT_SUCCESS](state, payload) { [types.RECEIVE_VULNERABILITIES_COUNT_SUCCESS](state, payload) {
state.isLoadingVulnerabilitiesCount = false; state.isLoadingVulnerabilitiesCount = false;
...@@ -33,7 +33,7 @@ export default { ...@@ -33,7 +33,7 @@ export default {
}, },
[types.RECEIVE_VULNERABILITIES_COUNT_ERROR](state) { [types.RECEIVE_VULNERABILITIES_COUNT_ERROR](state) {
state.isLoadingVulnerabilitiesCount = false; state.isLoadingVulnerabilitiesCount = false;
state.hasError = true; state.errorLoadingVulnerabilitiesCount = true;
}, },
[types.SET_MODAL_DATA](state, payload) { [types.SET_MODAL_DATA](state, payload) {
const { vulnerability } = payload; const { vulnerability } = payload;
......
import { s__ } from '~/locale'; import { s__ } from '~/locale';
export default () => ({ export default () => ({
hasError: false,
isLoadingVulnerabilities: true, isLoadingVulnerabilities: true,
errorLoadingVulnerabilities: false,
isLoadingVulnerabilitiesCount: true, isLoadingVulnerabilitiesCount: true,
errorLoadingVulnerabilitiesCount: false,
pageInfo: {}, pageInfo: {},
vulnerabilities: [], vulnerabilities: [],
vulnerabilitiesCount: {}, vulnerabilitiesCount: {},
......
...@@ -3,6 +3,4 @@ ...@@ -3,6 +3,4 @@
#js-group-security-dashboard{ data: { vulnerabilities_endpoint: 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_summary_endpoint: summary_group_security_vulnerabilities_path(@group),
dashboard_documentation: help_page_path('user/group/security_dashboard'), dashboard_documentation: help_page_path('user/group/security_dashboard') } }
empty_state_svg_path: image_path('illustrations/security-dashboard-empty-state.svg'),
error_state_svg_path: image_path('illustrations/security-dashboard-api-error-empty-state.svg') } }
---
title: Adds split error states for the group security dashboard
merge_request: 8208
author:
type: changed
...@@ -48,4 +48,57 @@ describe('vulnerabilities module getters', () => { ...@@ -48,4 +48,57 @@ describe('vulnerabilities module getters', () => {
expect(result).toBe(0); expect(result).toBe(0);
}); });
}); });
describe('dashboardError', () => {
it('should return true when both error states exist', () => {
const errorLoadingVulnerabilities = true;
const errorLoadingVulnerabilitiesCount = true;
const state = { errorLoadingVulnerabilities, errorLoadingVulnerabilitiesCount };
const result = getters.dashboardError(state);
expect(result).toBe(true);
});
});
describe('dashboardCountError', () => {
it('should return true if the count error exists', () => {
const state = {
errorLoadingVulnerabilitiesCount: true,
};
const result = getters.dashboardCountError(state);
expect(result).toBe(true);
});
it('should return false if the list error exists as well', () => {
const state = {
errorLoadingVulnerabilities: true,
errorLoadingVulnerabilitiesCount: true,
};
const result = getters.dashboardCountError(state);
expect(result).toBe(false);
});
});
describe('dashboardListError', () => {
it('should return true when the list error exists', () => {
const state = {
errorLoadingVulnerabilities: true,
};
const result = getters.dashboardListError(state);
expect(result).toBe(true);
});
it('should return false if the count error exists as well', () => {
const state = {
errorLoadingVulnerabilities: true,
errorLoadingVulnerabilitiesCount: true,
};
const result = getters.dashboardListError(state);
expect(result).toBe(false);
});
});
}); });
...@@ -21,7 +21,7 @@ describe('vulnerabilities module mutations', () => { ...@@ -21,7 +21,7 @@ describe('vulnerabilities module mutations', () => {
beforeEach(() => { beforeEach(() => {
state = { state = {
...createState(), ...createState(),
hasError: true, errorLoadingVulnerabilities: true,
}; };
mutations[types.REQUEST_VULNERABILITIES](state); mutations[types.REQUEST_VULNERABILITIES](state);
}); });
...@@ -30,8 +30,8 @@ describe('vulnerabilities module mutations', () => { ...@@ -30,8 +30,8 @@ describe('vulnerabilities module mutations', () => {
expect(state.isLoadingVulnerabilities).toBeTruthy(); expect(state.isLoadingVulnerabilities).toBeTruthy();
}); });
it('should set `hasError` to `false`', () => { it('should set `errorLoadingVulnerabilities` to `false`', () => {
expect(state.hasError).toBeFalsy(); expect(state.errorLoadingVulnerabilities).toBeFalsy();
}); });
}); });
...@@ -88,7 +88,7 @@ describe('vulnerabilities module mutations', () => { ...@@ -88,7 +88,7 @@ describe('vulnerabilities module mutations', () => {
beforeEach(() => { beforeEach(() => {
state = { state = {
...createState(), ...createState(),
hasError: true, errorLoadingVulnerabilitiesCount: true,
}; };
mutations[types.REQUEST_VULNERABILITIES_COUNT](state); mutations[types.REQUEST_VULNERABILITIES_COUNT](state);
}); });
...@@ -97,8 +97,8 @@ describe('vulnerabilities module mutations', () => { ...@@ -97,8 +97,8 @@ describe('vulnerabilities module mutations', () => {
expect(state.isLoadingVulnerabilitiesCount).toBeTruthy(); expect(state.isLoadingVulnerabilitiesCount).toBeTruthy();
}); });
it('should set `hasError` to `false`', () => { it('should set `errorLoadingVulnerabilitiesCount` to `false`', () => {
expect(state.hasError).toBeFalsy(); expect(state.errorLoadingVulnerabilitiesCount).toBeFalsy();
}); });
}); });
......
...@@ -7091,6 +7091,15 @@ msgstr "" ...@@ -7091,6 +7091,15 @@ msgstr ""
msgid "Security Dashboard" msgid "Security Dashboard"
msgstr "" msgstr ""
msgid "Security Dashboard|Error fetching the dashboard data. Please check your network connection and try again."
msgstr ""
msgid "Security Dashboard|Error fetching the vulnerability counts. Please check your network connection and try again."
msgstr ""
msgid "Security Dashboard|Error fetching the vulnerability list. Please check your network connection and try again."
msgstr ""
msgid "Security Dashboard|Issue Created" msgid "Security Dashboard|Issue Created"
msgstr "" msgstr ""
...@@ -7112,15 +7121,9 @@ msgstr "" ...@@ -7112,15 +7121,9 @@ msgstr ""
msgid "Security Reports|There was an error creating the issue." msgid "Security Reports|There was an error creating the issue."
msgstr "" msgstr ""
msgid "Security Reports|There was an error dismissing the issue."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability." msgid "Security Reports|There was an error dismissing the vulnerability."
msgstr "" msgstr ""
msgid "Security Reports|There was an error fetching the dashboard. Please try again in a few moments or contact your support team."
msgstr ""
msgid "Security Reports|There was an error undoing the dismissal." msgid "Security Reports|There was an error undoing the dismissal."
msgstr "" 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