Commit e61e29a0 authored by Daniel Tian's avatar Daniel Tian

Update header and table for security config page

Update the header and use GlTable for the the security configuration
page
parent 041f190f
<script>
import { GlLink } from '@gitlab/ui';
import { GlLink, GlSprintf, GlTable } from '@gitlab/ui';
import { s__, __, sprintf } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import AutoFixSettings from './auto_fix_settings.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import AutoFixSettings from './auto_fix_settings.vue';
export default {
components: {
GlLink,
Icon,
GlSprintf,
GlTable,
AutoFixSettings,
},
mixins: [glFeatureFlagsMixin()],
......@@ -41,33 +41,40 @@ export default {
},
},
computed: {
headerContent() {
const body = __('Configure Security %{wordBreakOpportunity}and Compliance');
const wordBreakOpportunity = '<wbr />';
return sprintf(body, { wordBreakOpportunity }, false);
devopsMessage() {
return this.autoDevopsEnabled
? __(
'All security scans are enabled because %{linkStart}Auto DevOps%{linkEnd} is enabled on this project',
)
: __(
`The status of the table below only applies to the default branch and is based on the %{linkStart}latest pipeline%{linkEnd}. Once you've enabled a scan for the default branch, any subsequent feature branch you create will include the scan.`,
);
},
callOutLink() {
devopsUrl() {
return this.autoDevopsEnabled ? this.autoDevopsHelpPagePath : this.latestPipelinePath;
},
calloutContent() {
const bodyDefault = __(`The status of the table below only applies to the default branch and
is based on the %{linkStart}latest pipeline%{linkEnd}.
Once you've enabled a scan for the default branch, any subsequent feature branch you create will include the scan.`);
const bodyAutoDevopsEnabled = __(
'All security scans are enabled because %{linkStart}Auto DevOps%{linkEnd} is enabled on this project',
);
const body = this.autoDevopsEnabled ? bodyAutoDevopsEnabled : bodyDefault;
const linkStart = `<a href="${this.callOutLink}" target="_blank" rel="noopener">`;
const linkEnd = '</a>';
return sprintf(body, { linkStart, linkEnd }, false);
fields() {
return [
{
key: 'feature',
label: s__('SecurityConfiguration|Security Control'),
thClass: 'gl-text-gray-900 bg-transparent border-bottom',
},
{
key: 'configured',
label: s__('SecurityConfiguration|Status'),
thClass: 'gl-text-gray-900 bg-transparent border-bottom',
formatter: this.getStatusText,
},
];
},
},
methods: {
getStatusText(value) {
return value
? s__('SecurityConfiguration|Enabled')
: s__('SecurityConfiguration|Not yet enabled');
},
getFeatureDocumentationLinkLabel(featureName) {
return sprintf(s__('SecurityConfiguration|Feature documentation for %{featureName}'), {
featureName,
......@@ -80,73 +87,32 @@ export default {
<template>
<article>
<header>
<h2 class="h4 my-3">
<span v-html="headerContent"></span>
<gl-link
target="_blank"
:href="helpPagePath"
:aria-label="__('Security configuration help link')"
>
<icon name="question" />
</gl-link>
</h2>
<h4 class="my-3">{{ __('Security Configuration') }}</h4>
<h5 class="gl-font-lg mt-5">{{ s__('SecurityConfiguration|Testing & Compliance') }}</h5>
<p>
<gl-sprintf :message="devopsMessage">
<template #link="{ content }">
<gl-link ref="pipelinesLink" :href="devopsUrl" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</header>
<section
ref="callout"
class="bs-callout bs-callout-info mb-3 m-md-1 text-secondary"
v-html="calloutContent"
></section>
<section ref="featuresTable" class="mt-0">
<div
class="gl-responsive-table-row table-row-header text-2 font-weight-bold px-2 gl-text-gray-900"
role="row"
>
<div class="table-section section-80">
{{ s__('SecurityConfiguration|Secure features') }}
</div>
<div class="table-section section-20">{{ s__('SecurityConfiguration|Status') }}</div>
</div>
<div
v-for="feature in features"
ref="featureRow"
:key="feature.name"
class="gl-responsive-table-row flex-md-column align-items-md-stretch px-2"
>
<div class="d-md-flex align-items-center">
<div class="table-section section-80 section-wrap pr-md-3">
<div role="rowheader" class="table-mobile-header">
{{ s__('SecurityConfiguration|Feature') }}
</div>
<div class="table-mobile-content">
<div class="d-flex align-items-center justify-content-end justify-content-md-start">
<div class="text-2 gl-text-gray-900">{{ feature.name }}</div>
</div>
<div class="text-secondary">
{{ feature.description }}
<gl-table ref="securityControlTable" :items="features" :fields="fields" stacked="md">
<template #cell(feature)="{ item }">
<div class="gl-text-gray-900">{{ item.name }}</div>
<div>
{{ item.description }}
<gl-link
target="_blank"
:href="feature.link"
:aria-label="getFeatureDocumentationLinkLabel(feature.name)"
>{{ __('More information') }}</gl-link
:href="item.link"
:aria-label="getFeatureDocumentationLinkLabel(item.name)"
>
{{ __('More information') }}
</gl-link>
</div>
</div>
</div>
<div class="table-section section-20 section-wrap pr-md-3">
<div role="rowheader" class="table-mobile-header">
{{ s__('SecurityConfiguration|Status') }}
</div>
<div ref="featureConfigStatus" class="table-mobile-content">
{{
feature.configured
? s__('SecurityConfiguration|Enabled')
: s__('SecurityConfiguration|Not yet enabled')
}}
</div>
</div>
</div>
</div>
</section>
</template>
</gl-table>
<auto-fix-settings v-if="glFeatures.securityAutoFix" v-bind="autoFixSettingsProps" />
</article>
</template>
---
title: Update header for security config page and change to GlTable
merge_request: 31471
author:
type: changed
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Security Configuration App features table displays a given feature 1`] = `
<section
class="mt-0"
>
<div
class="gl-responsive-table-row table-row-header text-2 font-weight-bold px-2 gl-text-gray-900"
role="row"
>
<div
class="table-section section-80"
>
Secure features
</div>
<div
class="table-section section-20"
>
Status
</div>
</div>
<div
class="gl-responsive-table-row flex-md-column align-items-md-stretch px-2"
>
<div
class="d-md-flex align-items-center"
>
<div
class="table-section section-80 section-wrap pr-md-3"
>
<div
class="table-mobile-header"
role="rowheader"
>
Feature
</div>
<div
class="table-mobile-content"
>
<div
class="d-flex align-items-center justify-content-end justify-content-md-start"
>
<div
class="text-2 gl-text-gray-900"
>
name-feature-0
</div>
</div>
<div
class="text-secondary"
>
description-feature-0
<gl-link-stub
aria-label="Feature documentation for name-feature-0"
href="link-feature-0"
target="_blank"
>
More information
</gl-link-stub>
</div>
</div>
</div>
<div
class="table-section section-20 section-wrap pr-md-3"
>
<div
class="table-mobile-header"
role="rowheader"
>
Status
</div>
<div
class="table-mobile-content"
>
Not yet enabled
</div>
</div>
</div>
</div>
</section>
`;
import { shallowMount } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import SecurityConfigurationApp from 'ee/security_configuration/components/app.vue';
import stubChildren from 'helpers/stub_children';
describe('Security Configuration App', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(SecurityConfigurationApp, {
wrapper = mount(SecurityConfigurationApp, {
stubs: {
...stubChildren(SecurityConfigurationApp),
GlTable: false,
GlSprintf: false,
},
propsData: {
features: [],
autoDevopsEnabled: false,
......@@ -23,28 +28,19 @@ describe('Security Configuration App', () => {
wrapper.destroy();
});
const generateFeatures = n =>
[...Array(n).keys()].map(i => ({
const generateFeatures = n => {
return [...Array(n).keys()].map(i => ({
name: `name-feature-${i}`,
description: `description-feature-${i}`,
link: `link-feature-${i}`,
configured: i % 2 === 0,
}));
};
const getHelpLink = () => wrapper.find('header').find(GlLink);
const getNotification = () => wrapper.find({ ref: 'callout' });
const getPipelinesLink = () => getNotification().find('a');
const getFeaturesTable = () => wrapper.find({ ref: 'featuresTable' });
const getFeatureConfigStatus = () => wrapper.find({ ref: 'featureConfigStatus' });
const getPipelinesLink = () => wrapper.find({ ref: 'pipelinesLink' });
const getFeaturesTable = () => wrapper.find({ ref: 'securityControlTable' });
describe('header', () => {
it('displays a link to the given help page', () => {
const helpPagePath = 'http://foo';
createComponent({ helpPagePath });
expect(getHelpLink().attributes('href')).toBe(helpPagePath);
});
it.each`
autoDevopsEnabled | expectedUrl
${true} | ${'http://autoDevopsHelpPagePath'}
......@@ -55,41 +51,28 @@ describe('Security Configuration App', () => {
createComponent({ autoDevopsEnabled });
expect(getPipelinesLink().attributes('href')).toBe(expectedUrl);
expect(getPipelinesLink().attributes('rel')).toBe('noopener');
expect(getPipelinesLink().attributes('target')).toBe('_blank');
},
);
});
describe('features table', () => {
it('displays a row for each given feature', () => {
it('passes the expected data to the GlTable', () => {
const features = generateFeatures(5);
createComponent({ features });
expect(wrapper.findAll({ ref: 'featureRow' })).toHaveLength(5);
});
it('displays a given feature', () => {
const features = generateFeatures(1);
createComponent({ features });
expect(getFeaturesTable().element).toMatchSnapshot();
expect(getFeaturesTable().classes('b-table-stacked-md')).toBeTruthy();
const rows = getFeaturesTable().findAll('tbody tr');
expect(rows).toHaveLength(5);
for (let i = 0; i < features.length; i += 1) {
const [feature, status] = rows.at(i).findAll('td').wrappers;
expect(feature.text()).toMatch(features[i].name);
expect(feature.text()).toMatch(features[i].description);
expect(feature.find(GlLink).attributes('href')).toBe(features[i].link);
expect(status.text()).toMatch(features[i].configured ? 'Enabled' : 'Not yet enabled');
}
});
it.each`
configured | statusText
${true} | ${'Enabled'}
${false} | ${'Not yet enabled'}
`(
`displays "$statusText" if the given feature's configuration status is: "$configured"`,
({ configured, statusText }) => {
const features = [{ configured }];
createComponent({ features });
expect(getFeatureConfigStatus().text()).toBe(statusText);
},
);
});
});
......@@ -5684,9 +5684,6 @@ msgstr ""
msgid "Configure Prometheus"
msgstr ""
msgid "Configure Security %{wordBreakOpportunity}and Compliance"
msgstr ""
msgid "Configure Tracing"
msgstr ""
......@@ -18928,9 +18925,6 @@ msgstr ""
msgid "Security Dashboard"
msgstr ""
msgid "Security configuration help link"
msgstr ""
msgid "Security dashboard"
msgstr ""
......@@ -18943,21 +18937,21 @@ msgstr ""
msgid "SecurityConfiguration|Enabled"
msgstr ""
msgid "SecurityConfiguration|Feature"
msgstr ""
msgid "SecurityConfiguration|Feature documentation for %{featureName}"
msgstr ""
msgid "SecurityConfiguration|Not yet enabled"
msgstr ""
msgid "SecurityConfiguration|Secure features"
msgid "SecurityConfiguration|Security Control"
msgstr ""
msgid "SecurityConfiguration|Status"
msgstr ""
msgid "SecurityConfiguration|Testing & Compliance"
msgstr ""
msgid "SecurityReports|%{firstProject} and %{secondProject}"
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