Commit ade92598 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch...

Merge branch '34910-display-security-navigation-in-left-sidebar-to-a-cohort-of-net-new-com-signups' into 'master'

Security navigation in left sidebar

See merge request gitlab-org/gitlab!21658
parents 75c0e83b 10c2f5c1
......@@ -15,6 +15,7 @@
@import 'framework/badges';
@import 'framework/calendar';
@import 'framework/callout';
@import 'framework/carousel';
@import 'framework/common';
@import 'framework/dropdowns';
@import 'framework/files';
......
// Notes on the classes:
//
// 1. .carousel.pointer-event should ideally be pan-y (to allow for users to scroll vertically)
// even when their scroll action started on a carousel, but for compatibility (with Firefox)
// we're preventing all actions instead
// 2. The .carousel-item-left and .carousel-item-right is used to indicate where
// the active slide is heading.
// 3. .active.carousel-item is the current slide.
// 4. .active.carousel-item-left and .active.carousel-item-right is the current
// slide in its in-transition state. Only one of these occurs at a time.
// 5. .carousel-item-next.carousel-item-left and .carousel-item-prev.carousel-item-right
// is the upcoming slide in transition.
.carousel {
position: relative;
&.pointer-event {
touch-action: pan-y;
}
}
.carousel-inner {
position: relative;
width: 100%;
overflow: hidden;
@include clearfix();
}
.carousel-item {
position: relative;
display: none;
float: left;
width: 100%;
margin-right: -100%;
backface-visibility: hidden;
@include transition($carousel-transition);
}
.carousel-item.active,
.carousel-item-next,
.carousel-item-prev {
display: block;
}
.carousel-item-next:not(.carousel-item-left),
.active.carousel-item-right {
transform: translateX(100%);
}
.carousel-item-prev:not(.carousel-item-right),
.active.carousel-item-left {
transform: translateX(-100%);
}
//
// Alternate transitions
//
.carousel-fade {
.carousel-item {
opacity: 0;
transition-property: opacity;
transform: none;
}
.carousel-item.active,
.carousel-item-next.carousel-item-left,
.carousel-item-prev.carousel-item-right {
z-index: 1;
opacity: 1;
}
.active.carousel-item-left,
.active.carousel-item-right {
z-index: 0;
opacity: 0;
@include transition(0s $carousel-transition-duration opacity);
}
}
//
// Left/right controls for nav
//
.carousel-control-prev,
.carousel-control-next {
position: absolute;
top: 0;
bottom: 0;
z-index: 1;
// Use flex for alignment (1-3)
display: flex; // 1. allow flex styles
align-items: center; // 2. vertically center contents
justify-content: center; // 3. horizontally center contents
width: $carousel-control-width;
color: $carousel-control-color;
text-align: center;
opacity: $carousel-control-opacity;
@include transition($carousel-control-transition);
// Hover/focus state
@include hover-focus {
color: $carousel-control-color;
text-decoration: none;
outline: 0;
opacity: $carousel-control-hover-opacity;
}
}
.carousel-control-prev {
left: 0;
@if $enable-gradients {
background: linear-gradient(90deg, rgba($black, 0.25), rgba($black, 0.001));
}
}
.carousel-control-next {
right: 0;
@if $enable-gradients {
background: linear-gradient(270deg, rgba($black, 0.25), rgba($black, 0.001));
}
}
// Icons for within
.carousel-control-prev-icon,
.carousel-control-next-icon {
display: inline-block;
width: $carousel-control-icon-width;
height: $carousel-control-icon-width;
background: no-repeat 50% / 100% 100%;
}
.carousel-control-prev-icon {
background-image: $carousel-control-prev-icon-bg;
}
.carousel-control-next-icon {
background-image: $carousel-control-next-icon-bg;
}
// Optional indicator pips
//
// Add an ordered list with the following class and add a list item for each
// slide your carousel holds.
.carousel-indicators {
position: absolute;
right: 0;
bottom: 0;
left: 0;
z-index: 15;
display: flex;
justify-content: center;
padding-left: 0; // override <ol> default
// Use the .carousel-control's width as margin so we don't overlay those
margin-right: $carousel-control-width;
margin-left: $carousel-control-width;
list-style: none;
li {
box-sizing: content-box;
flex: 0 1 auto;
width: $carousel-indicator-width;
height: $carousel-indicator-height;
margin-right: $carousel-indicator-spacer;
margin-left: $carousel-indicator-spacer;
text-indent: -999px;
cursor: pointer;
background-color: $carousel-indicator-active-bg;
background-clip: padding-box;
// Use transparent borders to increase the hit area by 10px on top and bottom.
border-top: $carousel-indicator-hit-area-height solid transparent;
border-bottom: $carousel-indicator-hit-area-height solid transparent;
opacity: 0.5;
@include transition($carousel-indicator-transition);
}
.active {
opacity: 1;
}
}
// Optional captions
//
//
.carousel-caption {
position: absolute;
right: (100% - $carousel-caption-width) / 2;
bottom: 20px;
left: (100% - $carousel-caption-width) / 2;
z-index: 10;
padding-top: 20px;
padding-bottom: 20px;
color: $carousel-caption-color;
text-align: center;
}
import initCardSecurityDiscover from 'ee/vue_shared/discover/card_security_discover_bundle';
document.addEventListener('DOMContentLoaded', initCardSecurityDiscover);
import initCardSecurityDiscover from 'ee/vue_shared/discover/card_security_discover_bundle';
document.addEventListener('DOMContentLoaded', initCardSecurityDiscover);
<script>
import { BCarousel, BCarouselSlide } from 'bootstrap-vue';
import { GlNewButton, GlTooltipDirective } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import Tracking from '~/tracking';
import securityDependencyImageUrl from 'ee_images/promotions/security-dependencies.png';
import securityScanningImageUrl from 'ee_images/promotions/security-scanning.png';
import securityDashboardImageUrl from 'ee_images/promotions/security-dashboard.png';
export default {
directives: {
GlTooltip: GlTooltipDirective,
},
components: {
GlNewButton,
BCarousel,
BCarouselSlide,
},
mixins: [Tracking.mixin()],
props: {
project: {
type: Object,
required: false,
default: null,
},
group: {
type: Object,
required: false,
default: null,
},
linkMain: {
type: String,
required: false,
default: '',
},
linkSecondary: {
type: String,
required: false,
default: '',
},
linkFeedback: {
type: String,
required: false,
default: '',
},
},
data: () => ({
slide: 0,
textSlide: 0,
carouselImages: [
{
index: 0,
imageUrl: securityDependencyImageUrl,
},
{
index: 1,
imageUrl: securityScanningImageUrl,
},
{
index: 2,
imageUrl: securityDashboardImageUrl,
},
],
}),
computed: {
discoverButtonProps() {
return {
variant: 'info',
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
rel: 'noopener noreferrer',
class: 'discover-button justify-content-center',
'data-track-event': 'click_button',
};
},
},
methods: {
onSlideStart(slide) {
this.track('click_button', {
label: 'security-discover-carousel',
value: `sliding${this.slide}-${slide}`,
});
this.textSlide = slide;
},
},
i18n: {
discoverTitle: s__(
'Discover|Security capabilities, integrated into your development lifecycle',
),
discoverFeedbackLabel: s__('Discover|Give feedback for this page'),
discoverUpgradeLabel: s__('Discover|Upgrade now'),
discoverTrialLabel: s__('Discover|Start a free trial'),
carouselCaptions: [
{
index: 0,
caption: s__(
'Discover|Check your application for security vulnerabilities that may lead to unauthorized access, data leaks, and denial of services.',
),
},
{
index: 1,
caption: s__(
'Discover|GitLab will perform static and dynamic tests on the code of your application, looking for known flaws and report them in the merge request so you can fix them before merging.',
),
},
{
index: 2,
caption: s__(
"Discover|For code that's already live in production, our dashboards give you an easy way to prioritize any issues that are found, empowering your team to ship quickly and securely.",
),
},
],
discoverPlanCaption: sprintf(
s__('Discover|See the other features of the %{linkStart}gold plan%{linkEnd}'),
{
linkStart:
'<a href="https://about.gitlab.com/pricing/saas/feature-comparison/" target="_blank" rel="noopener noreferrer">',
linkEnd: '</a>',
},
false,
),
},
};
</script>
<template>
<div class="discover-box">
<h4 class="discover-title center gl-text-gray-900">
{{ $options.i18n.discoverTitle }}
</h4>
<b-carousel
v-model="slide"
class="discover-carousel"
:no-wrap="true"
controls
:interval="0"
indicators
img-width="1440"
img-height="700"
@sliding-start="onSlideStart"
>
<b-carousel-slide v-for="{ index, imageUrl } in carouselImages" :key="index" img-blank>
<img
:src="imageUrl"
class="discover-carousel-img w-100 box-shadow-default image-fluid d-block"
/>
</b-carousel-slide>
</b-carousel>
<b-carousel
ref="textCarousel"
v-model="textSlide"
class="discover-carousel discover-text-carousel"
:no-wrap="true"
:interval="0"
img-width="1440"
img-height="200"
>
<b-carousel-slide
v-for="{ index, caption } in $options.i18n.carouselCaptions"
:key="index"
img-blank
>
<p class="gl-text-gray-900 text-left">
{{ caption }}
</p>
</b-carousel-slide>
</b-carousel>
<div class="discover-footer d-flex flex-nowrap flex-row justify-content-between mx-auto my-0">
<p class="gl-text-gray-900 text-left mb-5" v-html="$options.i18n.discoverPlanCaption"></p>
</div>
<div class="discover-buttons d-flex flex-nowrap flex-row justify-content-between mx-auto my-0">
<gl-new-button
class="discover-button-upgrade"
v-bind="discoverButtonProps"
category="secondary"
data-track-label="security-discover-upgrade-cta"
:data-track-property="slide"
:href="linkSecondary"
>
{{ $options.i18n.discoverUpgradeLabel }}
</gl-new-button>
<gl-new-button
class="discover-button-trial"
v-bind="discoverButtonProps"
category="primary"
data-track-label="security-discover-trial-cta"
:data-track-property="slide"
:href="linkMain"
>
{{ $options.i18n.discoverTrialLabel }}
</gl-new-button>
</div>
<div id="tooltipcontainer" class="discover-feedback w-30p position-fixed">
<gl-new-button
v-gl-tooltip:tooltipcontainer.left
:title="$options.i18n.discoverFeedbackLabel"
icon="slight-smile"
size="medium"
class="discover-feedback-icon"
category="secondary"
variant="default"
target="_blank"
rel="noopener noreferrer"
data-track-event="click_button"
data-track-label="security-discover-feedback-cta"
:data-track-property="slide"
:href="linkFeedback"
/>
</div>
</div>
</template>
import Vue from 'vue';
import SecurityDiscoverApp from 'ee/vue_shared/discover/card_security_discover_app.vue';
export default () => {
const securityTab = document.getElementById('js-security-discover-app');
const {
groupId,
groupName,
projectId,
projectName,
linkMain,
linkSecondary,
linkFeedback,
} = securityTab.dataset;
const props = {
project: {
id: projectId,
name: projectName,
},
group: {
id: groupId,
name: groupName,
},
linkMain,
linkSecondary,
linkFeedback,
};
return new Vue({
el: securityTab,
components: {
SecurityDiscoverApp,
},
render(createElement) {
return createElement('security-discover-app', {
props,
});
},
});
};
......@@ -191,3 +191,80 @@
}
}
}
.discover-box {
.discover-title {
margin: 40px auto 2px;
max-width: 500px;
}
.discover-carousel-img {
margin-bottom: 12px;
border-radius: $border-radius-default;
}
.discover-button {
width: 45% !important;
}
.discover-buttons {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
margin: 0 auto;
max-width: 520px;
}
.discover-footer {
margin: 40px auto 2px;
max-width: 500px;
}
.discover-feedback {
bottom: 16px;
right: 16px;
text-align: right;
}
.discover-feedback-icon {
padding-bottom: 0.5rem 0.7rem 0.7rem;
&:hover {
color: $blue-500;
}
}
.discover-text-carousel {
.carousel-caption {
height: 100%;
}
}
.discover-carousel {
margin: 0 auto;
max-width: 720px;
.carousel-indicators {
li {
background-color: $gray-300;
width: 8px;
height: 8px;
border-radius: 100%;
margin-right: 16px;
}
.active {
background-color: $gray-400;
}
}
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23bababa' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E");
}
.carousel-control-next-icon {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23bababa' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E");
}
}
}
# frozen_string_literal: true
class Groups::Security::DiscoverController < Groups::ApplicationController
layout 'group'
def show
render_404 unless helpers.show_discover_group_security?(@group)
end
end
# frozen_string_literal: true
module Projects
module Security
class DiscoverController < Projects::ApplicationController
def show
render_404 unless helpers.show_discover_project_security?(@project)
end
end
end
end
......@@ -78,6 +78,15 @@ module EE
::Gitlab::CurrentSettings.deletion_adjourned_period
end
def show_discover_group_security?(group)
!!::Feature.enabled?(:discover_security) &&
::Gitlab.com? &&
!!current_user &&
current_user.created_at > DateTime.new(2020, 1, 20) &&
!@group.feature_available?(:security_dashboard) &&
can?(current_user, :admin_group, @group)
end
private
def get_group_sidebar_links
......
......@@ -257,6 +257,15 @@ module EE
tabs.any? { |tab| project_nav_tab?(tab) }
end
def show_discover_project_security?(project)
!!::Feature.enabled?(:discover_security) &&
::Gitlab.com? &&
!!current_user &&
current_user.created_at > DateTime.new(2020, 1, 20) &&
!project.feature_available?(:security_dashboard) &&
can?(current_user, :admin_namespace, project.root_ancestor)
end
def settings_operations_available?
return true if super
......
- breadcrumb_title _("Security")
- page_title _("Security")
- linkFeedback = 'https://gitlab.com/gitlab-org/growth/ui-ux/issues/25'
- linkUpgrade = group_billings_path(@group.root_ancestor)
#js-security-discover-app{ data: { group: { id: @group.id, name: @group.name }, link: { main: new_trial_registration_path, secondary: linkUpgrade, feedback: linkFeedback } } }
- return unless @group.feature_available?(:group_level_compliance_dashboard) || @group.feature_available?(:security_dashboard)
- compliance_dashboard_available = @group.feature_available?(:group_level_compliance_dashboard)
- security_dashboard_available = @group.feature_available?(:security_dashboard)
- if compliance_dashboard_available || security_dashboard_available
- main_path = compliance_dashboard_available ? group_security_compliance_dashboard_path(@group) : group_security_dashboard_path(@group)
= nav_link(path: %w[groups/security/compliance_dashboard#show groups/security/dashboard#show]) do
= link_to main_path, data: { qa_selector: 'security_compliance_link' } do
.nav-icon-container
= sprite_icon('shield')
%span.nav-item-name
= _('Security & Compliance')
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_secure_submenu' } }
- if security_dashboard_available
= nav_link(path: 'groups/security/dashboard#show') do
= link_to group_security_dashboard_path(@group), title: _('Security'), data: { qa_selector: 'security_dashboard_link' } do
%span= _('Security')
- main_path = @group.feature_available?(:group_level_compliance_dashboard) ? group_security_compliance_dashboard_path(@group) : group_security_dashboard_path(@group)
= nav_link(path: %w[groups/security/compliance_dashboard#show groups/security/dashboard#show]) do
= link_to main_path, data: { qa_selector: 'security_compliance_link' } do
.nav-icon-container
= sprite_icon('shield')
%span.nav-item-name
= _('Security & Compliance')
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_secure_submenu' } }
- if @group.feature_available?(:security_dashboard)
= nav_link(path: 'groups/security/dashboard#show') do
= link_to group_security_dashboard_path(@group), title: _('Security'), data: { qa_selector: 'security_dashboard_link' } do
%span= _('Security')
- if @group.feature_available?(:group_level_compliance_dashboard)
= nav_link(path: 'groups/security/compliance_dashboard#show') do
= link_to group_security_compliance_dashboard_path(@group), title: _('Compliance') do
%span= _('Compliance')
- if compliance_dashboard_available
= nav_link(path: 'groups/security/compliance_dashboard#show') do
= link_to group_security_compliance_dashboard_path(@group), title: _('Compliance') do
%span= _('Compliance')
- elsif show_discover_group_security?(@group)
= nav_link(path: group_security_discover_path(@group)) do
= link_to group_security_discover_path(@group) do
.nav-icon-container
= sprite_icon('shield')
%span.nav-item-name
= _('Security')
- return unless any_project_nav_tab?([:security, :dependencies, :licenses])
- top_level_link = project_nav_tab?(:security) ? project_security_dashboard_index_path(@project) : project_dependencies_path(@project)
- top_level_qa_selector = project_nav_tab?(:security) ? 'security_dashboard_link' : 'dependency_list_link'
= nav_link(path: sidebar_security_paths) do
= link_to top_level_link, data: { qa_selector: top_level_qa_selector } do
.nav-icon-container
= sprite_icon('shield')
%span.nav-item-name
= _('Security & Compliance')
%ul.sidebar-sub-level-items
= nav_link(path: sidebar_security_paths, html_options: { class: "fly-out-top-item" } ) do
= link_to top_level_link do
%strong.fly-out-top-item-name
= _('Security & Compliance')
%li.divider.fly-out-top-item
- if project_nav_tab?(:security)
= nav_link(path: 'projects/security/dashboard#show') do
= link_to project_security_dashboard_index_path(@project), title: _('Security Dashboard') do
%span= _('Security Dashboard')
- if project_nav_tab?(:dependencies)
= nav_link(path: 'projects/dependencies#index') do
= link_to project_dependencies_path(@project), title: _('Dependency List'), data: { qa_selector: 'dependency_list_link' } do
%span= _('Dependency List')
- if project_nav_tab?(:licenses)
= nav_link(path: 'projects/licenses#show') do
= link_to project_licenses_path(@project), title: _('License Compliance'), data: { qa_selector: 'licenses_list_link' } do
%span= _('License Compliance')
- if project_nav_tab?(:security_configuration)
= nav_link(path: 'projects/security/configuration#show') do
= link_to project_security_configuration_path(@project), title: _('Configuration'), data: { qa_selector: 'security_configuration_link'} do
%span= _('Configuration')
- if project_nav_tab?(:threat_monitoring)
= nav_link(path: 'projects/threat_monitoring#show') do
= link_to project_threat_monitoring_path(@project), title: _('Threat Monitoring') do
%span= _('Threat Monitoring')
- if any_project_nav_tab?([:security, :dependencies, :licenses])
- top_level_link = project_nav_tab?(:security) ? project_security_dashboard_index_path(@project) : project_dependencies_path(@project)
- top_level_qa_selector = project_nav_tab?(:security) ? 'security_dashboard_link' : 'dependency_list_link'
= nav_link(path: sidebar_security_paths) do
= link_to top_level_link, data: { qa_selector: top_level_qa_selector } do
.nav-icon-container
= sprite_icon('shield')
%span.nav-item-name
= _('Security & Compliance')
%ul.sidebar-sub-level-items
= nav_link(path: sidebar_security_paths, html_options: { class: "fly-out-top-item" } ) do
= link_to top_level_link do
%strong.fly-out-top-item-name
= _('Security & Compliance')
%li.divider.fly-out-top-item
- if project_nav_tab?(:security)
= nav_link(path: 'projects/security/dashboard#show') do
= link_to project_security_dashboard_index_path(@project), title: _('Security Dashboard') do
%span= _('Security Dashboard')
- if project_nav_tab?(:dependencies)
= nav_link(path: 'projects/dependencies#index') do
= link_to project_dependencies_path(@project), title: _('Dependency List'), data: { qa_selector: 'dependency_list_link' } do
%span= _('Dependency List')
- if project_nav_tab?(:licenses)
= nav_link(path: 'projects/licenses#show') do
= link_to project_licenses_path(@project), title: _('License Compliance'), data: { qa_selector: 'licenses_list_link' } do
%span= _('License Compliance')
- if project_nav_tab?(:security_configuration)
= nav_link(path: 'projects/security/configuration#show') do
= link_to project_security_configuration_path(@project), title: _('Configuration'), data: { qa_selector: 'security_configuration_link'} do
%span= _('Configuration')
- if project_nav_tab?(:threat_monitoring)
= nav_link(path: 'projects/threat_monitoring#show') do
= link_to project_threat_monitoring_path(@project), title: _('Threat Monitoring') do
%span= _('Threat Monitoring')
- elsif show_discover_project_security?(@project)
= nav_link(path: project_security_discover_path(@project)) do
= link_to project_security_discover_path(@project) do
.nav-icon-container
= sprite_icon('shield')
%span.nav-item-name
= _('Security & Compliance')
- breadcrumb_title _("Security Dashboard")
- page_title _("Security Dashboard")
- linkFeedback = 'https://gitlab.com/gitlab-org/growth/ui-ux/issues/25'
- linkUpgrade = @project.personal? ? profile_billings_path(@project.group) : group_billings_path(@project.root_ancestor)
#js-security-discover-app{ data: { project: { id: @project.id, name: @project.name }, link: { main: new_trial_registration_path, secondary: linkUpgrade, feedback: linkFeedback } } }
......@@ -115,6 +115,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resource :dashboard, only: [:show], controller: :dashboard
resource :compliance_dashboard, only: [:show]
resources :vulnerable_projects, only: [:index]
resource :discover, only: [:show], controller: :discover
resources :vulnerability_findings, only: [:index] do
collection do
......
......@@ -167,6 +167,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
namespace :security do
resources :dashboard, only: [:show, :index], controller: :dashboard
resource :configuration, only: [:show], controller: :configuration
resource :discover, only: [:show], controller: :discover
resources :vulnerability_findings, only: [:index] do
collection do
......
import { shallowMount } from '@vue/test-utils';
import { mockTracking } from 'helpers/tracking_helper';
import CardSecurityDiscoverApp from 'ee/vue_shared/discover/card_security_discover_app.vue';
describe('Card security discover app', () => {
let wrapper;
const createComponent = propsData => {
wrapper = shallowMount(CardSecurityDiscoverApp, {
propsData,
});
};
afterEach(() => {
wrapper.destroy();
});
describe('Project discover carousel', () => {
beforeEach(() => {
const propsData = {
project: {
id: 1,
name: 'Awesome Project',
},
linkMain: '/link/main',
linkSecondary: '/link/secondary',
linkFeedback: 'link/feedback',
};
createComponent(propsData);
});
it('renders component properly', () => {
expect(wrapper.find(CardSecurityDiscoverApp).exists()).toBe(true);
});
it('renders discover title properly', () => {
expect(wrapper.find('.discover-title').html()).toContain(
'Security capabilities, integrated into your development lifecycle',
);
});
it('renders feedback icon link properly', () => {
expect(wrapper.find('.discover-feedback-icon').html()).toContain(
'Give feedback for this page',
);
});
it('renders discover upgrade links properly', () => {
expect(wrapper.find('.discover-button-upgrade').html()).toContain('Upgrade now');
});
it('renders discover trial links properly', () => {
expect(wrapper.find('.discover-button-trial').html()).toContain('Start a free trial');
});
describe('Tracking', () => {
let spy;
beforeEach(() => {
spy = mockTracking('_category_', wrapper.element, jest.spyOn);
});
it('tracks an event when clicked on upgrade', () => {
wrapper.find('.discover-button-upgrade').trigger('click');
expect(spy).toHaveBeenCalledWith('_category_', 'click_button', {
label: 'security-discover-upgrade-cta',
property: '0',
});
});
it('tracks an event when clicked on trial', () => {
wrapper.find('.discover-button-trial').trigger('click');
expect(spy).toHaveBeenCalledWith('_category_', 'click_button', {
label: 'security-discover-trial-cta',
property: '0',
});
});
it('tracks an event when clicked on feedback', () => {
wrapper.find('.discover-feedback-icon').trigger('click');
expect(spy).toHaveBeenCalledWith('_category_', 'click_button', {
label: 'security-discover-feedback-cta',
property: '0',
});
});
});
});
});
......@@ -91,4 +91,33 @@ describe GroupsHelper do
end
end
end
describe '#show_discover_group_security?' do
using RSpec::Parameterized::TableSyntax
where(
gitlab_com?: [true, false],
user?: [true, false],
created_at: [Time.mktime(2010, 1, 20), Time.mktime(2030, 1, 20)],
discover_security_feature_enabled?: [true, false],
security_dashboard_feature_available?: [true, false],
can_admin_group?: [true, false]
)
with_them do
it 'returns the expected value' do
allow(::Gitlab).to receive(:com?) { gitlab_com? }
allow(helper).to receive(:current_user) { user? ? user : nil }
allow(user).to receive(:created_at) { created_at }
allow(::Feature).to receive(:enabled?).with(:discover_security) { discover_security_feature_enabled? }
allow(group).to receive(:feature_available?) { security_dashboard_feature_available? }
allow(helper).to receive(:can?) { can_admin_group? }
expected_value = gitlab_com? && user? && created_at > DateTime.new(2020, 1, 20) &&
discover_security_feature_enabled? && !security_dashboard_feature_available? && can_admin_group?
expect(helper.show_discover_group_security?(group)).to eq(expected_value)
end
end
end
end
......@@ -187,4 +187,34 @@ describe ProjectsHelper do
end
end
end
describe '#show_discover_project_security?' do
using RSpec::Parameterized::TableSyntax
let(:user) { create(:user) }
where(
gitlab_com?: [true, false],
user?: [true, false],
created_at: [Time.mktime(2010, 1, 20), Time.mktime(2030, 1, 20)],
discover_security_feature_enabled?: [true, false],
security_dashboard_feature_available?: [true, false],
can_admin_namespace?: [true, false]
)
with_them do
it 'returns the expected value' do
allow(::Gitlab).to receive(:com?) { gitlab_com? }
allow(helper).to receive(:current_user) { user? ? user : nil }
allow(user).to receive(:created_at) { created_at }
allow(::Feature).to receive(:enabled?).with(:discover_security) { discover_security_feature_enabled? }
allow(project).to receive(:feature_available?) { security_dashboard_feature_available? }
allow(helper).to receive(:can?) { can_admin_namespace? }
expected_value = gitlab_com? && user? && created_at > DateTime.new(2020, 1, 20) &&
discover_security_feature_enabled? && !security_dashboard_feature_available? && can_admin_namespace?
expect(helper.show_discover_project_security?(project)).to eq(expected_value)
end
end
end
end
......@@ -6594,6 +6594,30 @@ msgstr ""
msgid "Discover projects, groups and snippets. Share your projects with others"
msgstr ""
msgid "Discover|Check your application for security vulnerabilities that may lead to unauthorized access, data leaks, and denial of services."
msgstr ""
msgid "Discover|For code that's already live in production, our dashboards give you an easy way to prioritize any issues that are found, empowering your team to ship quickly and securely."
msgstr ""
msgid "Discover|GitLab will perform static and dynamic tests on the code of your application, looking for known flaws and report them in the merge request so you can fix them before merging."
msgstr ""
msgid "Discover|Give feedback for this page"
msgstr ""
msgid "Discover|Security capabilities, integrated into your development lifecycle"
msgstr ""
msgid "Discover|See the other features of the %{linkStart}gold plan%{linkEnd}"
msgstr ""
msgid "Discover|Start a free trial"
msgstr ""
msgid "Discover|Upgrade now"
msgstr ""
msgid "Discuss a specific suggestion or question"
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