Commit d4840519 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '6953-instance-lvl-security-dashboard-app-container-ee' into 'master'

Add Instance Security Dashboard component

See merge request gitlab-org/gitlab!16496
parents f4056979 a473923c
<script>
import { mapActions, mapState } from 'vuex';
import { GlButton, GlEmptyState, GlLink, GlLoadingIcon } from '@gitlab/ui';
import { s__ } from '~/locale';
import SecurityDashboard from './app.vue';
export default {
name: 'InstanceSecurityDashboard',
components: {
GlButton,
GlEmptyState,
GlLink,
GlLoadingIcon,
SecurityDashboard,
},
props: {
dashboardDocumentation: {
type: String,
required: true,
},
emptyStateSvgPath: {
type: String,
required: true,
},
emptyDashboardStateSvgPath: {
type: String,
required: true,
},
projectsEndpoint: {
type: String,
required: true,
},
vulnerabilitiesEndpoint: {
type: String,
required: true,
},
vulnerabilitiesCountEndpoint: {
type: String,
required: true,
},
vulnerabilitiesHistoryEndpoint: {
type: String,
required: true,
},
vulnerabilityFeedbackHelpPath: {
type: String,
required: true,
},
},
data() {
return {
isInitialized: false,
showProjectSelector: false,
};
},
computed: {
...mapState('projects', ['projects']),
toggleButtonProps() {
return this.showProjectSelector
? {
variant: 'success',
text: s__('SecurityDashboard|Return to dashboard'),
}
: {
variant: 'secondary',
text: s__('SecurityDashboard|Edit dashboard'),
};
},
shouldShowEmptyState() {
return this.isInitialized && this.projects.length === 0;
},
},
created() {
this.setProjectsEndpoint(this.projectsEndpoint);
this.fetchProjects()
// Failure to fetch projects will be handled in the store, so do nothing here.
.catch(() => {})
.finally(() => {
this.isInitialized = true;
});
},
methods: {
...mapActions('projects', ['setProjectsEndpoint', 'fetchProjects']),
toggleProjectSelector() {
this.showProjectSelector = !this.showProjectSelector;
},
},
};
</script>
<template>
<article>
<header class="page-title-holder flex-fill d-flex align-items-center">
<h2 class="page-title">{{ s__('SecurityDashboard|Security Dashboard') }}</h2>
<gl-button
v-if="isInitialized"
new-style
class="page-title-controls js-project-selector-toggle"
:variant="toggleButtonProps.variant"
@click="toggleProjectSelector"
v-text="toggleButtonProps.text"
/>
</header>
<template v-if="isInitialized">
<section v-if="showProjectSelector" class="js-dashboard-project-selector">
<h3>{{ s__('SecurityDashboard|Add or remove projects from your dashboard') }}</h3>
</section>
<template v-else>
<gl-empty-state
v-if="shouldShowEmptyState"
:title="s__('SecurityDashboard|Add a project to your dashboard')"
:svg-path="emptyStateSvgPath"
>
<template #description>
{{
s__(
'SecurityDashboard|The security dashboard displays the latest security findings for projects you wish to monitor. Select "Edit dashboard" to add and remove projects.',
)
}}
<gl-link :href="dashboardDocumentation">{{
s__('SecurityDashboard|More information')
}}</gl-link
>.
</template>
<template #actions>
<gl-button new-style variant="success" @click="toggleProjectSelector">
{{ s__('SecurityDashboard|Add projects') }}
</gl-button>
</template>
</gl-empty-state>
<security-dashboard
v-else
:dashboard-documentation="dashboardDocumentation"
:empty-state-svg-path="emptyDashboardStateSvgPath"
:vulnerabilities-endpoint="vulnerabilitiesEndpoint"
:vulnerabilities-count-endpoint="vulnerabilitiesCountEndpoint"
:vulnerabilities-history-endpoint="vulnerabilitiesHistoryEndpoint"
:vulnerability-feedback-help-path="vulnerabilityFeedbackHelpPath"
/>
</template>
</template>
<gl-loading-icon v-else size="md" />
</article>
</template>
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import InstanceSecurityDashboard from 'ee/security_dashboard/components/instance_security_dashboard.vue';
import SecurityDashboard from 'ee/security_dashboard/components/app.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
const dashboardDocumentation = '/help/docs';
const emptyStateSvgPath = '/svgs/empty.svg';
const emptyDashboardStateSvgPath = '/svgs/empty-dash.svg';
const projectsEndpoint = '/projects';
const vulnerabilitiesEndpoint = '/vulnerabilities';
const vulnerabilitiesCountEndpoint = '/vulnerabilities_summary';
const vulnerabilitiesHistoryEndpoint = '/vulnerabilities_history';
const vulnerabilityFeedbackHelpPath = '/vulnerabilities_feedback_help';
describe('Instance Security Dashboard component', () => {
let store;
let wrapper;
let actionResolvers;
const factory = ({ projects = [] } = {}) => {
store = new Vuex.Store({
modules: {
projects: {
namespaced: true,
actions: {
fetchProjects() {},
setProjectsEndpoint() {},
},
state: {
projects,
},
},
},
});
actionResolvers = [];
jest.spyOn(store, 'dispatch').mockImplementation(
() =>
new Promise(resolve => {
actionResolvers.push(resolve);
}),
);
wrapper = shallowMount(InstanceSecurityDashboard, {
localVue,
store,
sync: false,
propsData: {
dashboardDocumentation,
emptyStateSvgPath,
emptyDashboardStateSvgPath,
projectsEndpoint,
vulnerabilitiesEndpoint,
vulnerabilitiesCountEndpoint,
vulnerabilitiesHistoryEndpoint,
vulnerabilityFeedbackHelpPath,
},
});
};
const resolveActions = () => {
actionResolvers.forEach(resolve => resolve());
};
const findProjectSelectorToggleButton = () => wrapper.find('.js-project-selector-toggle');
const clickProjectSelectorToggleButton = () => {
findProjectSelectorToggleButton().vm.$emit('click');
return wrapper.vm.$nextTick();
};
const expectComponentWithProps = (Component, props) => {
const componentWrapper = wrapper.find(Component);
expect(componentWrapper.exists()).toBe(true);
expect(componentWrapper.props()).toEqual(expect.objectContaining(props));
};
const expectProjectSelectorState = () => {
expect(findProjectSelectorToggleButton().exists()).toBe(true);
expect(wrapper.find(GlEmptyState).exists()).toBe(false);
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find(SecurityDashboard).exists()).toBe(false);
};
afterEach(() => {
wrapper.destroy();
});
describe('on creation', () => {
beforeEach(() => {
factory();
});
it('dispatches the expected actions', () => {
expect(store.dispatch.mock.calls).toEqual([
['projects/setProjectsEndpoint', projectsEndpoint],
['projects/fetchProjects', undefined],
]);
});
it('displays the initial loading state', () => {
expect(findProjectSelectorToggleButton().exists()).toBe(false);
expect(wrapper.find(GlEmptyState).exists()).toBe(false);
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.find(SecurityDashboard).exists()).toBe(false);
});
});
describe('given there are no projects', () => {
beforeEach(() => {
factory();
resolveActions();
});
it('renders the empty state', () => {
expect(findProjectSelectorToggleButton().exists()).toBe(true);
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find(SecurityDashboard).exists()).toBe(false);
expectComponentWithProps(GlEmptyState, {
svgPath: emptyStateSvgPath,
});
});
describe('after clicking the project selector toggle button', () => {
beforeEach(clickProjectSelectorToggleButton);
it('renders the project selector state', () => {
expectProjectSelectorState();
});
});
});
describe('given there are projects', () => {
beforeEach(() => {
factory({ projects: [{ name: 'foo', id: 1 }] });
resolveActions();
});
it('renders the security dashboard state', () => {
expect(findProjectSelectorToggleButton().exists()).toBe(true);
expect(wrapper.find(GlEmptyState).exists()).toBe(false);
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
expectComponentWithProps(SecurityDashboard, {
dashboardDocumentation,
emptyStateSvgPath: emptyDashboardStateSvgPath,
vulnerabilitiesEndpoint,
vulnerabilitiesCountEndpoint,
vulnerabilitiesHistoryEndpoint,
vulnerabilityFeedbackHelpPath,
});
});
describe('after clicking the project selector toggle button', () => {
beforeEach(clickProjectSelectorToggleButton);
it('renders the project selector state', () => {
expectProjectSelectorState();
});
});
});
});
......@@ -14063,15 +14063,30 @@ msgstr ""
msgid "SecurityDashboard|%{firstProject}, %{secondProject}, and %{rest}"
msgstr ""
msgid "SecurityDashboard|Add a project to your dashboard"
msgstr ""
msgid "SecurityDashboard|Add or remove projects from your dashboard"
msgstr ""
msgid "SecurityDashboard|Add projects"
msgstr ""
msgid "SecurityDashboard|Confidence"
msgstr ""
msgid "SecurityDashboard|Edit dashboard"
msgstr ""
msgid "SecurityDashboard|Hide dismissed"
msgstr ""
msgid "SecurityDashboard|Monitor vulnerabilities in your code"
msgstr ""
msgid "SecurityDashboard|More information"
msgstr ""
msgid "SecurityDashboard|Pipeline %{pipelineLink} triggered"
msgstr ""
......@@ -14081,9 +14096,18 @@ msgstr ""
msgid "SecurityDashboard|Report type"
msgstr ""
msgid "SecurityDashboard|Return to dashboard"
msgstr ""
msgid "SecurityDashboard|Security Dashboard"
msgstr ""
msgid "SecurityDashboard|Severity"
msgstr ""
msgid "SecurityDashboard|The security dashboard displays the latest security findings for projects you wish to monitor. Select \"Edit dashboard\" to add and remove projects."
msgstr ""
msgid "SecurityDashboard|Unable to add %{invalidProjects}"
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