Commit 8a8828a8 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '343912-agent-vulnerability-list' into 'master'

Create agent-specific vulnerability list

See merge request gitlab-org/gitlab!76713
parents 55bbd7d1 cdf7104d
...@@ -51,16 +51,7 @@ export default { ...@@ -51,16 +51,7 @@ export default {
TokenTable, TokenTable,
ActivityEvents, ActivityEvents,
}, },
props: { inject: ['agentName', 'projectPath'],
agentName: {
required: true,
type: String,
},
projectPath: {
required: true,
type: String,
},
},
data() { data() {
return { return {
cursor: { cursor: {
...@@ -131,12 +122,12 @@ export default { ...@@ -131,12 +122,12 @@ export default {
</p> </p>
<gl-tabs sync-active-tab-with-query-params lazy> <gl-tabs sync-active-tab-with-query-params lazy>
<slot name="ee-security-tab"></slot>
<gl-tab :title="$options.i18n.activity" query-param-value="activity"> <gl-tab :title="$options.i18n.activity" query-param-value="activity">
<activity-events :agent-name="agentName" :project-path="projectPath" /> <activity-events :agent-name="agentName" :project-path="projectPath" />
</gl-tab> </gl-tab>
<slot name="ee-security-tab"></slot>
<gl-tab query-param-value="tokens"> <gl-tab query-param-value="tokens">
<template #title> <template #title>
<span data-testid="cluster-agent-token-count"> <span data-testid="cluster-agent-token-count">
......
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { vulnerabilityLocationTypes } from '~/graphql_shared/fragment_types/vulnerability_location_types';
Vue.use(VueApollo);
// We create a fragment matcher so that we can create a fragment from an interface
// Without this, Apollo throws a heuristic fragment matcher warning
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData: vulnerabilityLocationTypes,
});
const defaultClient = createDefaultClient(
{},
{
cacheConfig: {
fragmentMatcher,
},
},
);
export default new VueApollo({
defaultClient,
});
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import AgentShowPage from 'ee_else_ce/clusters/agents/components/show.vue'; import AgentShowPage from 'ee_else_ce/clusters/agents/components/show.vue';
import apolloProvider from './graphql/provider';
Vue.use(VueApollo);
export default () => { export default () => {
const el = document.querySelector('#js-cluster-agent-details'); const el = document.querySelector('#js-cluster-agent-details');
...@@ -12,20 +9,19 @@ export default () => { ...@@ -12,20 +9,19 @@ export default () => {
return null; return null;
} }
const defaultClient = createDefaultClient(); const { activityEmptyStateImage, agentName, emptyStateSvgPath, projectPath } = el.dataset;
const { agentName, projectPath, activityEmptyStateImage } = el.dataset;
return new Vue({ return new Vue({
el, el,
apolloProvider: new VueApollo({ defaultClient }), apolloProvider,
provide: { agentName, projectPath, activityEmptyStateImage }, provide: {
activityEmptyStateImage,
agentName,
emptyStateSvgPath,
projectPath,
},
render(createElement) { render(createElement) {
return createElement(AgentShowPage, { return createElement(AgentShowPage);
props: {
agentName,
projectPath,
},
});
}, },
}); });
}; };
export const vulnerabilityLocationTypes = {
__schema: {
types: [
{
kind: 'UNION',
name: 'VulnerabilityLocation',
possibleTypes: [
{ name: 'VulnerabilityLocationContainerScanning' },
{ name: 'VulnerabilityLocationDast' },
{ name: 'VulnerabilityLocationDependencyScanning' },
{ name: 'VulnerabilityLocationSast' },
{ name: 'VulnerabilityLocationSecretDetection' },
],
},
],
},
};
...@@ -3,9 +3,10 @@ ...@@ -3,9 +3,10 @@
module Projects::ClusterAgentsHelper module Projects::ClusterAgentsHelper
def js_cluster_agent_details_data(agent_name, project) def js_cluster_agent_details_data(agent_name, project)
{ {
activity_empty_state_image: image_path('illustrations/empty-state/empty-state-agents.svg'),
agent_name: agent_name, agent_name: agent_name,
project_path: project.full_path, empty_state_svg_path: image_path('illustrations/operations-dashboard_empty.svg'),
activity_empty_state_image: image_path('illustrations/empty-state/empty-state-agents.svg') project_path: project.full_path
} }
end end
end end
...@@ -3,23 +3,18 @@ import { GlTab } from '@gitlab/ui'; ...@@ -3,23 +3,18 @@ import { GlTab } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import AgentShowPage from '~/clusters/agents/components/show.vue'; import AgentShowPage from '~/clusters/agents/components/show.vue';
import AgentVulnerabilityReport from 'ee/security_dashboard/components/agent/agent_vulnerability_report.vue';
export default { export default {
i18n: { i18n: {
securityTabTitle: s__('ClusterAgents|Security'), securityTabTitle: s__('ClusterAgents|Security'),
}, },
components: { AgentShowPage, GlTab }, components: {
mixins: [glFeatureFlagMixin()], AgentShowPage,
props: { GlTab,
agentName: { AgentVulnerabilityReport,
required: true,
type: String,
},
projectPath: {
required: true,
type: String,
},
}, },
mixins: [glFeatureFlagMixin()],
computed: { computed: {
showSecurityTab() { showSecurityTab() {
return ( return (
...@@ -31,10 +26,11 @@ export default { ...@@ -31,10 +26,11 @@ export default {
</script> </script>
<template> <template>
<agent-show-page v-bind="$props"> <agent-show-page>
<template v-if="showSecurityTab" #ee-security-tab> <template v-if="showSecurityTab" #ee-security-tab>
<!-- Placeholder for https://gitlab.com/gitlab-org/gitlab/-/issues/343912--> <gl-tab :title="$options.i18n.securityTabTitle">
<gl-tab :title="$options.i18n.securityTabTitle"><div></div></gl-tab> <agent-vulnerability-report />
</gl-tab>
</template> </template>
</agent-show-page> </agent-show-page>
</template> </template>
<script>
import { helpPagePath } from '~/helpers/help_page_helper';
import VulnerabilityListGraphql from '../shared/vulnerability_report/vulnerability_list_graphql.vue';
import VulnerabilityFilters from '../shared/vulnerability_report/vulnerability_filters.vue';
import {
FIELD_PRESETS,
FILTER_PRESETS,
REPORT_TAB,
REPORT_TYPE_PRESETS,
} from '../shared/vulnerability_report/constants';
import projectVulnerabilitiesQuery from '../../graphql/queries/project_vulnerabilities.query.graphql';
import { DASHBOARD_TYPES } from '../../store/constants';
export default {
components: {
VulnerabilityFilters,
VulnerabilityListGraphql,
},
/**
* Normally we should only use provide when bootstrapping an application, but this is an exception
* because there is a CE/EE split in `app/assets/javascripts/clusters/agents/index.js` (where
* these values are only needed for EE) and there is necessary rename/variable naming that only
* makes sense in the context of being in the `app/assets/javascripts/security_dashboard`
* directory.
*/
provide() {
return {
dashboardDocumentation: helpPagePath('user/application_security/security_dashboard/index'),
dashboardType: DASHBOARD_TYPES.PROJECT,
canAdminVulnerability: true,
fullPath: this.projectPath,
canViewFalsePositive: false,
hasJiraVulnerabilitiesIntegrationEnabled: false,
};
},
inject: ['projectPath'],
data() {
return {
graphqlFilters: undefined,
};
},
computed: {
filtersToShow() {
return FILTER_PRESETS[REPORT_TAB.OPERATIONAL];
},
},
methods: {
updateGraphqlFilters(graphqlFilters) {
this.graphqlFilters = graphqlFilters;
this.graphqlFilters.reportType = REPORT_TYPE_PRESETS.OPERATIONAL;
},
},
fieldsToShow: FIELD_PRESETS[REPORT_TAB.OPERATIONAL],
REPORT_TAB,
projectVulnerabilitiesQuery,
};
</script>
<template>
<div>
<vulnerability-filters
:filters="filtersToShow"
class="security-dashboard-filters gl-mt-7"
@filters-changed="updateGraphqlFilters"
/>
<vulnerability-list-graphql
:query="$options.projectVulnerabilitiesQuery"
:fields="$options.fieldsToShow"
:filters="graphqlFilters"
/>
</div>
</template>
{"__schema":{"types":[{"kind":"UNION","name":"VulnerabilityLocation","possibleTypes":[{"name":"VulnerabilityLocationContainerScanning"},{"name":"VulnerabilityLocationDast"},{"name":"VulnerabilityLocationDependencyScanning"},{"name":"VulnerabilityLocationSast"},{"name":"VulnerabilityLocationSecretDetection"}]}]}}
...@@ -2,14 +2,14 @@ import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; ...@@ -2,14 +2,14 @@ import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import introspectionQueryResultData from './fragmentTypes.json'; import { vulnerabilityLocationTypes } from '~/graphql_shared/fragment_types/vulnerability_location_types';
Vue.use(VueApollo); Vue.use(VueApollo);
// We create a fragment matcher so that we can create a fragment from an interface // We create a fragment matcher so that we can create a fragment from an interface
// Without this, Apollo throws a heuristic fragment matcher warning // Without this, Apollo throws a heuristic fragment matcher warning
const fragmentMatcher = new IntrospectionFragmentMatcher({ const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData, introspectionQueryResultData: vulnerabilityLocationTypes,
}); });
const defaultClient = createDefaultClient( const defaultClient = createDefaultClient(
......
...@@ -3,24 +3,19 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -3,24 +3,19 @@ import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ClusterAgentShow from 'ee/clusters/agents/components/show.vue'; import ClusterAgentShow from 'ee/clusters/agents/components/show.vue';
import CEClusterAgentShowPage from '~/clusters/agents/components/show.vue';
describe('ClusterAgentShow', () => { describe('ClusterAgentShow', () => {
let wrapper; let wrapper;
const agentName = 'best-agent';
const projectPath = 'path/to/project';
const createWrapper = ({ glFeatures = {} } = {}) => { const createWrapper = ({ glFeatures = {} } = {}) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
shallowMount(ClusterAgentShow, { shallowMount(ClusterAgentShow, {
propsData: { agentName, projectPath },
provide: { glFeatures }, provide: { glFeatures },
}), }),
); );
}; };
const findTab = () => wrapper.findComponent(GlTab); const findTab = () => wrapper.findComponent(GlTab);
const findCEClusterAgentShowPage = () => wrapper.findComponent(CEClusterAgentShowPage);
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -36,7 +31,6 @@ describe('ClusterAgentShow', () => { ...@@ -36,7 +31,6 @@ describe('ClusterAgentShow', () => {
`('$title', async ({ glFeatures, tabStatus }) => { `('$title', async ({ glFeatures, tabStatus }) => {
createWrapper({ glFeatures }); createWrapper({ glFeatures });
await nextTick(); await nextTick();
expect(findCEClusterAgentShowPage().props()).toStrictEqual({ agentName, projectPath });
expect(findTab().exists()).toBe(tabStatus); expect(findTab().exists()).toBe(tabStatus);
}); });
}); });
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Agent vulnerability report component renders 1`] = `
<div>
<vulnerability-filters-stub
class="security-dashboard-filters gl-mt-7"
filters="[object Object],[object Object],[object Object]"
/>
<vulnerability-list-graphql-stub
fields="[object Object],[object Object],[object Object],[object Object],,[object Object]"
query="[object Object]"
/>
</div>
`;
import { shallowMount } from '@vue/test-utils';
import AgentVulnerabilityReport from 'ee/security_dashboard/components/agent/agent_vulnerability_report.vue';
describe('Agent vulnerability report component', () => {
let wrapper;
const provide = { agentName: 'primary-agent', projectPath: '/path/to/project/' };
const createWrapper = () => {
wrapper = shallowMount(AgentVulnerabilityReport, { provide });
};
afterEach(() => {
wrapper.destroy();
});
it('renders', () => {
createWrapper();
expect(wrapper.element).toMatchSnapshot();
});
});
...@@ -19,7 +19,7 @@ describe('ClusterAgentShow', () => { ...@@ -19,7 +19,7 @@ describe('ClusterAgentShow', () => {
let wrapper; let wrapper;
useFakeDate([2021, 2, 15]); useFakeDate([2021, 2, 15]);
const propsData = { const provide = {
agentName: 'cluster-agent', agentName: 'cluster-agent',
projectPath: 'path/to/project', projectPath: 'path/to/project',
}; };
...@@ -49,7 +49,7 @@ describe('ClusterAgentShow', () => { ...@@ -49,7 +49,7 @@ describe('ClusterAgentShow', () => {
shallowMount(ClusterAgentShow, { shallowMount(ClusterAgentShow, {
localVue, localVue,
apolloProvider, apolloProvider,
propsData, provide,
stubs: { GlSprintf, TimeAgoTooltip, GlTab }, stubs: { GlSprintf, TimeAgoTooltip, GlTab },
}), }),
); );
...@@ -60,7 +60,7 @@ describe('ClusterAgentShow', () => { ...@@ -60,7 +60,7 @@ describe('ClusterAgentShow', () => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
shallowMount(ClusterAgentShow, { shallowMount(ClusterAgentShow, {
propsData, provide,
mocks: { $apollo, clusterAgent }, mocks: { $apollo, clusterAgent },
slots, slots,
stubs: { GlTab }, stubs: { GlTab },
...@@ -85,7 +85,7 @@ describe('ClusterAgentShow', () => { ...@@ -85,7 +85,7 @@ describe('ClusterAgentShow', () => {
}); });
it('displays the agent name', () => { it('displays the agent name', () => {
expect(wrapper.text()).toContain(propsData.agentName); expect(wrapper.text()).toContain(provide.agentName);
}); });
it('displays agent create information', () => { it('displays agent create information', () => {
......
...@@ -17,5 +17,10 @@ RSpec.describe Projects::ClusterAgentsHelper do ...@@ -17,5 +17,10 @@ RSpec.describe Projects::ClusterAgentsHelper do
it 'returns project path' do it 'returns project path' do
expect(subject[:project_path]).to eq(project.full_path) expect(subject[:project_path]).to eq(project.full_path)
end end
it 'returns string contants' do
expect(subject[:activity_empty_state_image]).to be_kind_of(String)
expect(subject[:empty_state_svg_path]).to be_kind_of(String)
end
end end
end end
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