Commit d91cacb2 authored by Samantha Ming's avatar Samantha Ming Committed by Jose Vargas

Create configuration security training basic ui

Add security training ui to security configuration.
This is within the vulnerability management tab.
parent c8776ac7
......@@ -8,6 +8,7 @@ import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
import FeatureCard from './feature_card.vue';
import TrainingProviderList from './training_provider_list.vue';
import SectionLayout from './section_layout.vue';
import UpgradeBanner from './upgrade_banner.vue';
......@@ -28,8 +29,28 @@ export const i18n = {
securityTraining: s__('SecurityConfiguration|Security training'),
};
// This will be removed and replaced with GraphQL query:
// https://gitlab.com/gitlab-org/gitlab/-/issues/346480
export const TRAINING_PROVIDERS = [
{
id: 101,
name: __('Kontra'),
description: __('Interactive developer security education.'),
url: 'https://application.security/',
isEnabled: false,
},
{
id: 102,
name: __('SecureCodeWarrior'),
description: __('Security training with guide and learning pathways.'),
url: 'https://www.securecodewarrior.com/',
isEnabled: true,
},
];
export default {
i18n,
TRAINING_PROVIDERS,
components: {
AutoDevOpsAlert,
AutoDevOpsEnabledAlert,
......@@ -43,6 +64,7 @@ export default {
SectionLayout,
UpgradeBanner,
UserCalloutDismisser,
TrainingProviderList,
},
mixins: [glFeatureFlagsMixin()],
inject: ['projectPath'],
......@@ -240,7 +262,11 @@ export default {
data-testid="vulnerability-management-tab"
:title="$options.i18n.vulnerabilityManagement"
>
<section-layout :heading="$options.i18n.securityTraining" />
<section-layout :heading="$options.i18n.securityTraining">
<template #features>
<training-provider-list :providers="$options.TRAINING_PROVIDERS" />
</template>
</section-layout>
</gl-tab>
</gl-tabs>
</article>
......
<script>
import { GlCard, GlToggle, GlLink } from '@gitlab/ui';
export default {
components: {
GlCard,
GlToggle,
GlLink,
},
props: {
providers: {
type: Array,
required: true,
},
},
};
</script>
<template>
<ul class="gl-list-style-none gl-m-0 gl-p-0">
<li v-for="{ id, isEnabled, name, description, url } in providers" :key="id" class="gl-mb-6">
<gl-card>
<div class="gl-display-flex">
<gl-toggle :value="isEnabled" :label="__('Training mode')" label-position="hidden" />
<div class="gl-ml-5">
<h3 class="gl-font-lg gl-m-0 gl-mb-2">{{ name }}</h3>
<p>
{{ description }}
<gl-link :href="url" target="_blank">{{ __('Learn more.') }}</gl-link>
</p>
</div>
</div>
</gl-card>
</li>
</ul>
</template>
......@@ -18846,6 +18846,9 @@ msgstr ""
msgid "Integrations|can't exceed %{recipients_limit}"
msgstr ""
msgid "Interactive developer security education."
msgstr ""
msgid "Interactive mode"
msgstr ""
......@@ -20199,6 +20202,9 @@ msgstr ""
msgid "Ki"
msgstr ""
msgid "Kontra"
msgstr ""
msgid "Kroki"
msgstr ""
......@@ -30674,6 +30680,9 @@ msgstr ""
msgid "Secure token that identifies an external storage request."
msgstr ""
msgid "SecureCodeWarrior"
msgstr ""
msgid "Security"
msgstr ""
......@@ -30698,6 +30707,9 @@ msgstr ""
msgid "Security report is out of date. Run %{newPipelineLinkStart}a new pipeline%{newPipelineLinkEnd} for the target branch (%{targetBranchName})"
msgstr ""
msgid "Security training with guide and learning pathways."
msgstr ""
msgid "SecurityApprovals|A merge request approval is required when a security report contains a new vulnerability."
msgstr ""
......@@ -36609,6 +36621,9 @@ msgstr ""
msgid "Track your GitLab projects with GitLab for Slack."
msgstr ""
msgid "Training mode"
msgstr ""
msgid "Transfer"
msgstr ""
......
......@@ -5,7 +5,10 @@ import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
import stubChildren from 'helpers/stub_children';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import SecurityConfigurationApp, { i18n } from '~/security_configuration/components/app.vue';
import SecurityConfigurationApp, {
i18n,
TRAINING_PROVIDERS,
} from '~/security_configuration/components/app.vue';
import AutoDevopsAlert from '~/security_configuration/components/auto_dev_ops_alert.vue';
import AutoDevopsEnabledAlert from '~/security_configuration/components/auto_dev_ops_enabled_alert.vue';
import {
......@@ -20,6 +23,7 @@ import {
AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
} from '~/security_configuration/components/constants';
import FeatureCard from '~/security_configuration/components/feature_card.vue';
import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue';
import {
......@@ -78,6 +82,7 @@ describe('App component', () => {
const findTabs = () => wrapper.findAllComponents(GlTab);
const findByTestId = (id) => wrapper.findByTestId(id);
const findFeatureCards = () => wrapper.findAllComponents(FeatureCard);
const findTrainingProviderList = () => wrapper.findComponent(TrainingProviderList);
const findManageViaMRErrorAlert = () => wrapper.findByTestId('manage-via-mr-error-alert');
const findLink = ({ href, text, container = wrapper }) => {
const selector = `a[href="${href}"]`;
......@@ -180,6 +185,10 @@ describe('App component', () => {
expect(findComplianceViewHistoryLink().exists()).toBe(false);
expect(findSecurityViewHistoryLink().exists()).toBe(false);
});
it('renders training provider list with correct props', () => {
expect(findTrainingProviderList().props('providers')).toEqual(TRAINING_PROVIDERS);
});
});
describe('Manage via MR Error Alert', () => {
......
import { GlLink, GlToggle, GlCard } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
import { TRAINING_PROVIDERS } from '~/security_configuration/components/app.vue';
const DEFAULT_PROPS = {
providers: TRAINING_PROVIDERS,
};
describe('TrainingProviderList component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(TrainingProviderList, {
propsData: {
...DEFAULT_PROPS,
...props,
},
});
};
const findCards = () => wrapper.findAllComponents(GlCard);
const findLinks = () => wrapper.findAllComponents(GlLink);
const findToggles = () => wrapper.findAllComponents(GlToggle);
afterEach(() => {
wrapper.destroy();
});
describe('basic structure', () => {
beforeEach(() => {
createComponent();
});
it('renders correct amount of cards', () => {
expect(findCards()).toHaveLength(DEFAULT_PROPS.providers.length);
});
DEFAULT_PROPS.providers.forEach(({ name, description, url, isEnabled }, index) => {
it(`shows the name for card ${index}`, () => {
expect(findCards().at(index).text()).toContain(name);
});
it(`shows the description for card ${index}`, () => {
expect(findCards().at(index).text()).toContain(description);
});
it(`shows the learn more link for card ${index}`, () => {
expect(findLinks().at(index).attributes()).toEqual({
target: '_blank',
href: url,
});
});
it(`shows the toggle with the correct value for card ${index}`, () => {
expect(findToggles().at(index).props('value')).toEqual(isEnabled);
});
});
});
});
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