Commit c2e1c077 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '346057-config-sec-training' into 'master'

Create configuration security training providers UI

See merge request gitlab-org/gitlab!75348
parents 4c209af8 d91cacb2
......@@ -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>
......@@ -18891,6 +18891,9 @@ msgstr ""
msgid "Integrations|can't exceed %{recipients_limit}"
msgstr ""
msgid "Interactive developer security education."
msgstr ""
msgid "Interactive mode"
msgstr ""
......@@ -20238,6 +20241,9 @@ msgstr ""
msgid "Ki"
msgstr ""
msgid "Kontra"
msgstr ""
msgid "Kroki"
msgstr ""
......@@ -30704,6 +30710,9 @@ msgstr ""
msgid "Secure token that identifies an external storage request."
msgstr ""
msgid "SecureCodeWarrior"
msgstr ""
msgid "Security"
msgstr ""
......@@ -30728,6 +30737,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 ""
......@@ -36639,6 +36651,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