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