Commit 1c6c3b8b authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch 'secure-config-page-compliance-tab' into 'master'

Add compliance tab to redesigned Security-Configuration Page

See merge request gitlab-org/gitlab!63078
parents 0b1b1de1 b321b40a
......@@ -2,6 +2,7 @@
import { GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import FeatureCard from './feature_card.vue';
import SectionLayout from './section_layout.vue';
export const i18n = {
compliance: s__('SecurityConfiguration|Compliance'),
......@@ -23,12 +24,18 @@ export default {
GlTabs,
GlSprintf,
FeatureCard,
SectionLayout,
},
props: {
augmentedSecurityFeatures: {
type: Array,
required: true,
},
augmentedComplianceFeatures: {
type: Array,
required: true,
},
latestPipelinePath: {
type: String,
required: false,
......@@ -46,12 +53,11 @@ export default {
<gl-tabs content-class="gl-pt-6">
<gl-tab data-testid="security-testing-tab" :title="$options.i18n.securityTesting">
<div class="row">
<div class="col-lg-5">
<h2 class="gl-font-size-h2 gl-mt-0">{{ $options.i18n.securityTesting }}</h2>
<section-layout :heading="$options.i18n.securityTesting">
<template #description>
<p
v-if="latestPipelinePath"
data-testid="latest-pipeline-info"
data-testid="latest-pipeline-info-security"
class="gl-line-height-20"
>
<gl-sprintf :message="$options.i18n.securityTestingDescription">
......@@ -60,16 +66,42 @@ export default {
</template>
</gl-sprintf>
</p>
</div>
<div class="col-lg-7">
</template>
<template #features>
<feature-card
v-for="feature in augmentedSecurityFeatures"
:key="feature.type"
:feature="feature"
class="gl-mb-6"
/>
</div>
</div>
</template>
</section-layout>
</gl-tab>
<gl-tab data-testid="compliance-testing-tab" :title="$options.i18n.compliance">
<section-layout :heading="$options.i18n.compliance">
<template #description>
<p
v-if="latestPipelinePath"
class="gl-line-height-20"
data-testid="latest-pipeline-info-compliance"
>
<gl-sprintf :message="$options.i18n.securityTestingDescription">
<template #link="{ content }">
<gl-link :href="latestPipelinePath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</template>
<template #features>
<feature-card
v-for="feature in augmentedComplianceFeatures"
:key="feature.type"
:feature="feature"
class="gl-mb-6"
/>
</template>
</section-layout>
</gl-tab>
</gl-tabs>
</article>
......
<script>
export default {
name: 'SectionLayout',
props: {
heading: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="row">
<div class="col-lg-5">
<h2 class="gl-font-size-h2 gl-mt-0">{{ heading }}</h2>
<slot name="description"></slot>
</div>
<div class="col-lg-7">
<slot name="features"></slot>
</div>
</div>
</template>
......@@ -20,7 +20,7 @@ export const initStaticSecurityConfiguration = (el) => {
const { projectPath, upgradePath, features, latestPipelinePath } = el.dataset;
if (gon.features.securityConfigurationRedesign) {
const { augmentedSecurityFeatures } = augmentFeatures(
const { augmentedSecurityFeatures, augmentedComplianceFeatures } = augmentFeatures(
securityFeatures,
complianceFeatures,
features ? JSON.parse(features) : [],
......@@ -36,6 +36,7 @@ export const initStaticSecurityConfiguration = (el) => {
render(createElement) {
return createElement(RedesignedSecurityConfigurationApp, {
props: {
augmentedComplianceFeatures,
augmentedSecurityFeatures,
latestPipelinePath,
},
......
import { GlTab, GlTabs } from '@gitlab/ui';
import { GlTab } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import {
......@@ -7,14 +7,20 @@ import {
SAST_DESCRIPTION,
SAST_HELP_PATH,
SAST_CONFIG_HELP_PATH,
LICENSE_COMPLIANCE_NAME,
LICENSE_COMPLIANCE_DESCRIPTION,
LICENSE_COMPLIANCE_HELP_PATH,
} from '~/security_configuration/components/constants';
import FeatureCard from '~/security_configuration/components/feature_card.vue';
import RedesignedSecurityConfigurationApp, {
i18n,
} from '~/security_configuration/components/redesigned_app.vue';
import { REPORT_TYPE_SAST } from '~/vue_shared/security_reports/constants';
import {
REPORT_TYPE_LICENSE_COMPLIANCE,
REPORT_TYPE_SAST,
} from '~/vue_shared/security_reports/constants';
describe('NewApp component', () => {
describe('redesigned App component', () => {
let wrapper;
const createComponent = (propsData) => {
......@@ -26,9 +32,8 @@ describe('NewApp component', () => {
};
const findMainHeading = () => wrapper.find('h1');
const findSubHeading = () => wrapper.find('h2');
const findTab = () => wrapper.findComponent(GlTab);
const findTabs = () => wrapper.findAllComponents(GlTabs);
const findTabs = () => wrapper.findAllComponents(GlTab);
const findByTestId = (id) => wrapper.findByTestId(id);
const findFeatureCards = () => wrapper.findAllComponents(FeatureCard);
......@@ -44,6 +49,16 @@ describe('NewApp component', () => {
},
];
const complianceFeaturesMock = [
{
name: LICENSE_COMPLIANCE_NAME,
description: LICENSE_COMPLIANCE_DESCRIPTION,
helpPath: LICENSE_COMPLIANCE_HELP_PATH,
type: REPORT_TYPE_LICENSE_COMPLIANCE,
configurationHelpPath: LICENSE_COMPLIANCE_HELP_PATH,
},
];
afterEach(() => {
wrapper.destroy();
});
......@@ -52,6 +67,7 @@ describe('NewApp component', () => {
beforeEach(() => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
});
});
......@@ -66,23 +82,22 @@ describe('NewApp component', () => {
});
it('renders right amount of tabs with correct title ', () => {
expect(findTabs().length).toEqual(1);
expect(findTabs()).toHaveLength(2);
});
it('renders security-testing tab', () => {
expect(findByTestId('security-testing-tab')).toExist();
expect(findByTestId('security-testing-tab').exists()).toBe(true);
});
it('renders sub-heading with correct text', () => {
const subHeading = findSubHeading();
expect(subHeading).toExist();
expect(subHeading.text()).toContain(i18n.securityTesting);
it('renders compliance-testing tab', () => {
expect(findByTestId('compliance-testing-tab').exists()).toBe(true);
});
it('renders right amount of feature cards for given props with correct props', () => {
const cards = findFeatureCards();
expect(cards.length).toEqual(1);
expect(cards).toHaveLength(2);
expect(cards.at(0).props()).toEqual({ feature: securityFeaturesMock[0] });
expect(cards.at(1).props()).toEqual({ feature: complianceFeaturesMock[0] });
});
it('should not show latest pipeline link when latestPipelinePath is not defined', () => {
......@@ -94,16 +109,29 @@ describe('NewApp component', () => {
beforeEach(() => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
latestPipelinePath: 'test/path',
});
});
it('should show latest pipeline info with correct link when latestPipelinePath is defined', () => {
expect(findByTestId('latest-pipeline-info').exists()).toBe(true);
expect(findByTestId('latest-pipeline-info').text()).toMatchInterpolatedText(
it('should show latest pipeline info on the security tab with correct link when latestPipelinePath is defined', () => {
const latestPipelineInfoSecurity = findByTestId('latest-pipeline-info-security');
expect(latestPipelineInfoSecurity.exists()).toBe(true);
expect(latestPipelineInfoSecurity.text()).toMatchInterpolatedText(
i18n.securityTestingDescription,
);
expect(latestPipelineInfoSecurity.find('a').attributes('href')).toBe('test/path');
});
it('should show latest pipeline info on the compliance tab with correct link when latestPipelinePath is defined', () => {
const latestPipelineInfoCompliance = findByTestId('latest-pipeline-info-compliance');
expect(latestPipelineInfoCompliance.exists()).toBe(true);
expect(latestPipelineInfoCompliance.text()).toMatchInterpolatedText(
i18n.securityTestingDescription,
);
expect(findByTestId('latest-pipeline-info').find('a').attributes('href')).toBe('test/path');
expect(latestPipelineInfoCompliance.find('a').attributes('href')).toBe('test/path');
});
});
});
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import SectionLayout from '~/security_configuration/components/section_layout.vue';
describe('Section Layout component', () => {
let wrapper;
const createComponent = (propsData) => {
wrapper = extendedWrapper(
mount(SectionLayout, {
propsData,
scopedSlots: {
description: '<span>foo</span>',
features: '<span>bar</span>',
},
}),
);
};
const findHeading = () => wrapper.find('h2');
afterEach(() => {
wrapper.destroy();
});
describe('basic structure', () => {
beforeEach(() => {
createComponent({ heading: 'testheading' });
});
const slots = {
description: 'foo',
features: 'bar',
};
it('should render heading when passed in as props', () => {
expect(findHeading().exists()).toBe(true);
expect(findHeading().text()).toBe('testheading');
});
Object.keys(slots).forEach((slot) => {
it('renders the slots', () => {
const slotContent = slots[slot];
createComponent({ heading: '' });
expect(wrapper.text()).toContain(slotContent);
});
});
});
});
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