Skip to content
Snippets Groups Projects
Commit 8c967a14 authored by Kushal Pandya's avatar Kushal Pandya
Browse files

Merge branch 'jnnkl-sbom-survey-banner' into 'master'

Add Sbom Survey Banner

See merge request gitlab-org/gitlab!76446
parents b3f5e1e5 3c70b090
No related branches found
No related tags found
No related merge requests found
Showing
with 255 additions and 8 deletions
---
name: sbom_survey
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76446
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348181
milestone: '14.6'
type: development
group: group::secure
default_enabled: false
Loading
Loading
@@ -2,6 +2,7 @@
import { GlEmptyState, GlIcon, GlLoadingIcon, GlSprintf, GlLink } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
import { __ } from '~/locale';
import SbomBanner from 'ee/sbom_banner/components/app.vue';
import { DEPENDENCY_LIST_TYPES } from '../store/constants';
import { REPORT_STATUS } from '../store/modules/list/constants';
import DependenciesActions from './dependencies_actions.vue';
Loading
Loading
@@ -18,6 +19,7 @@ export default {
GlLoadingIcon,
GlSprintf,
GlLink,
SbomBanner,
DependencyListIncompleteAlert,
DependencyListJobFailedAlert,
PaginatedDependenciesTable,
Loading
Loading
@@ -27,6 +29,10 @@ export default {
type: String,
required: true,
},
sbomSurveySvgPath: {
type: String,
required: true,
},
emptyStateSvgPath: {
type: String,
required: true,
Loading
Loading
@@ -133,6 +139,7 @@ export default {
</gl-empty-state>
 
<section v-else>
<sbom-banner :sbom-survey-svg-path="sbomSurveySvgPath" />
<dependency-list-incomplete-alert
v-if="isIncomplete && !isIncompleteAlertDismissed"
@dismiss="dismissIncompleteListAlert"
Loading
Loading
Loading
Loading
@@ -4,16 +4,23 @@ import createStore from './store';
 
export default () => {
const el = document.querySelector('#js-dependencies-app');
const { endpoint, emptyStateSvgPath, documentationPath, supportDocumentationPath } = el.dataset;
const {
endpoint,
emptyStateSvgPath,
documentationPath,
supportDocumentationPath,
sbomSurveySvgPath,
} = el.dataset;
 
const store = createStore();
 
return new Vue({
el,
store,
components: {
DependenciesApp,
},
store,
render(createElement) {
return createElement(DependenciesApp, {
props: {
Loading
Loading
@@ -21,6 +28,7 @@ export default () => {
emptyStateSvgPath,
documentationPath,
supportDocumentationPath,
sbomSurveySvgPath,
},
});
},
Loading
Loading
Loading
Loading
@@ -11,6 +11,7 @@ import {
} from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex';
import LicenseManagement from 'ee/vue_shared/license_compliance/license_management.vue';
import SbomBanner from 'ee/sbom_banner/components/app.vue';
import { LICENSE_MANAGEMENT } from 'ee/vue_shared/license_compliance/store/constants';
import { getLocationHash } from '~/lib/utils/url_utility';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
Loading
Loading
@@ -31,6 +32,7 @@ export default {
GlTabs,
GlBadge,
GlAlert,
SbomBanner,
LicenseManagement,
},
mixins: [glFeatureFlagsMixin()],
Loading
Loading
@@ -39,6 +41,10 @@ export default {
type: String,
required: true,
},
sbomSurveySvgPath: {
type: String,
required: true,
},
documentationPath: {
type: String,
required: true,
Loading
Loading
@@ -119,7 +125,7 @@ export default {
)
}}
</gl-alert>
<sbom-banner :sbom-survey-svg-path="sbomSurveySvgPath" />
<header class="my-3">
<h2 class="h4 mb-1 gl-display-flex gl-align-items-center">
{{ s__('Licenses|License Compliance') }}
Loading
Loading
Loading
Loading
@@ -18,6 +18,7 @@ export default () => {
approvalsDocumentationPath,
lockedApprovalsRuleName,
softwareLicenses,
sbomSurveySvgPath,
} = el.dataset;
 
const storeSettings = {
Loading
Loading
@@ -47,6 +48,7 @@ export default () => {
render(createElement) {
return createElement(LicenseComplianceApp, {
props: {
sbomSurveySvgPath,
emptyStateSvgPath,
documentationPath,
},
Loading
Loading
<script>
import {
SBOM_BANNER_LOCAL_STORAGE_KEY,
SBOM_BANNER_CURRENT_ID,
SBOM_SURVEY_LINK,
SBOM_SURVEY_DAYS_TO_ASK_LATER,
SBOM_SURVEY_TITLE,
SBOM_SURVEY_BUTTON_TEXT,
SBOM_SURVEY_DESCRIPTION,
SBOM_SURVEY_TOAST_MESSAGE,
} from 'ee/vue_shared/constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import SurveyBanner from 'ee/vue_shared/survey_banner/survey_banner.vue';
export default {
name: 'SbomBanner',
components: {
SurveyBanner,
},
mixins: [glFeatureFlagsMixin()],
props: {
sbomSurveySvgPath: {
type: String,
required: true,
},
},
computed: {
shouldShowSbomSurvey() {
return this.glFeatures.sbomSurvey;
},
},
storageKey: SBOM_BANNER_LOCAL_STORAGE_KEY,
bannerId: SBOM_BANNER_CURRENT_ID,
surveyLink: SBOM_SURVEY_LINK,
daysToAskLater: SBOM_SURVEY_DAYS_TO_ASK_LATER,
title: SBOM_SURVEY_TITLE,
buttonText: SBOM_SURVEY_BUTTON_TEXT,
description: SBOM_SURVEY_DESCRIPTION,
toastMessage: SBOM_SURVEY_TOAST_MESSAGE,
};
</script>
<template>
<survey-banner
v-if="shouldShowSbomSurvey"
:svg-path="sbomSurveySvgPath"
:survey-link="$options.surveyLink"
:days-to-ask-later="$options.daysToAskLater"
:title="$options.title"
:button-text="$options.buttonText"
:description="$options.description"
:toast-message="$options.toastMessage"
:storage-key="$options.storageKey"
:banner-id="$options.bannerId"
class="gl-mt-5"
/>
</template>
Loading
Loading
@@ -51,7 +51,7 @@ export default {
</script>
 
<template>
<security-dashboard-layout>
<security-dashboard-layout :show-sbom-survey="false">
<template v-if="shouldShowEmptyState" #empty-state>
<report-not-configured />
</template>
Loading
Loading
<script>
import { s__ } from '~/locale';
import SbomBanner from 'ee/sbom_banner/components/app.vue';
import SurveyRequestBanner from './survey_request_banner.vue';
 
export default {
components: { SurveyRequestBanner },
components: { SurveyRequestBanner, SbomBanner },
i18n: {
title: s__('SecurityReports|Security Dashboard'),
},
inject: ['sbomSurveySvgPath'],
props: {
// this prop is needed since the sbom survey banner should not be shown
// on the instance security dashboard
showSbomSurvey: {
type: Boolean,
required: false,
default: true,
},
},
};
</script>
 
<template>
<div>
<slot name="loading"></slot>
<!-- TODO: this component needs to be refactored to use the shared survey-banner component, tracked here: https://gitlab.com/gitlab-org/gitlab/-/issues/348190 -->
<survey-request-banner v-if="!$slots.loading" class="gl-mt-5" />
<sbom-banner
v-if="!$slots.loading && showSbomSurvey"
:sbom-survey-svg-path="sbomSurveySvgPath"
/>
<template v-if="$slots.default">
<h2 data-testid="title">{{ $options.i18n.title }}</h2>
<div class="security-charts gl-display-flex gl-flex-wrap">
Loading
Loading
Loading
Loading
@@ -121,6 +121,7 @@ export default {
<template>
<div>
<template v-if="!isDashboardConfigured">
<!-- TODO: this component needs to be refactored to use the shared survey-banner component, tracked here: https://gitlab.com/gitlab-org/gitlab/-/issues/348190 -->
<survey-request-banner v-if="shouldShowSurvey" class="gl-mt-5" />
<report-not-configured-group v-if="isGroup" />
<report-not-configured-instance v-else-if="isInstance" />
Loading
Loading
@@ -134,6 +135,7 @@ export default {
/>
<vulnerability-report-layout>
<template v-if="!isPipeline" #header>
<!-- TODO: this component needs to be refactored to use the shared survey-banner component, tracked here: https://gitlab.com/gitlab-org/gitlab/-/issues/348190 -->
<survey-request-banner class="gl-mt-5" />
<header class="gl-mt-6 gl-mb-3 gl-display-flex gl-align-items-center">
<h2 class="gl-flex-grow-1 gl-my-0">
Loading
Loading
Loading
Loading
@@ -58,6 +58,7 @@ export default {
 
<template>
<div>
<!-- TODO: this component needs to be refactored to use the shared survey-banner component, tracked here: https://gitlab.com/gitlab-org/gitlab/-/issues/348190 -->
<survey-request-banner class="gl-mt-5" />
 
<vulnerability-report-header />
Loading
Loading
Loading
Loading
@@ -36,6 +36,7 @@ export default (el, dashboardType) => {
securityConfigurationPath: el.dataset.securityConfigurationPath,
surveyRequestSvgPath: el.dataset.surveyRequestSvgPath,
securityDashboardHelpPath: el.dataset.securityDashboardHelpPath,
sbomSurveySvgPath: el.dataset.sbomSurveySvgPath,
};
 
let component;
Loading
Loading
import { __ } from '~/locale';
import { s__, __ } from '~/locale';
 
export const noneEpic = {
id: 0,
Loading
Loading
@@ -9,3 +9,18 @@ export const placeholderEpic = {
id: -1,
title: __('Select epic'),
};
export const SBOM_BANNER_LOCAL_STORAGE_KEY = 'sbom_survey_request';
// NOTE: This string needs to parse to an invalid date. Do not put any characters in between the
// word 'survey' and the number, or else it will parse to a valid date.
export const SBOM_BANNER_CURRENT_ID = 'sbom1';
export const SBOM_SURVEY_LINK = 'https://gitlab.fra1.qualtrics.com/jfe/form/SV_es038rUv1VFqmXk';
export const SBOM_SURVEY_DAYS_TO_ASK_LATER = 7;
export const SBOM_SURVEY_TITLE = s__('SecurityReports|Security Dashboard');
export const SBOM_SURVEY_BUTTON_TEXT = s__('SecurityReports|Take survey');
export const SBOM_SURVEY_DESCRIPTION = s__(
`SecurityReports|The Composition Analysis group is planning significant updates to how we make available the list of software and container dependency information in your projects. Therefore, we ask that you assist us by taking a short -no longer than 5 minute- survey to help align our direction with your needs.`,
);
export const SBOM_SURVEY_TOAST_MESSAGE = s__(
'SecurityReports|Your feedback is important to us! We will ask again in 7 days.',
);
<script>
import { GlButton, GlBanner, GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import showToast from '~/vue_shared/plugins/global_toast';
export default {
components: { GlButton, GlBanner, GlSprintf, LocalStorageSync },
props: {
surveyLink: {
type: String,
required: true,
},
daysToAskLater: {
type: Number,
required: true,
},
title: {
type: String,
required: true,
},
buttonText: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
toastMessage: {
type: String,
required: true,
},
storageKey: {
type: String,
required: true,
},
bannerId: {
type: String,
required: true,
},
svgPath: {
type: String,
required: true,
},
},
data: () => ({
surveyShowDate: null,
}),
computed: {
shouldShowSurvey() {
const { surveyShowDate } = this;
const date = new Date(surveyShowDate);
// Survey is not enabled or user dismissed the survey by clicking the close icon.
if (surveyShowDate === this.$props.bannerId) {
return false;
}
// Date is invalid, we should show the survey.
else if (Number.isNaN(date.getDate())) {
return true;
}
return date <= Date.now();
},
},
methods: {
hideSurvey() {
this.surveyShowDate = this.$props.bannerId;
},
askLater() {
const date = new Date();
date.setDate(date.getDate() + this.daysToAskLater);
this.surveyShowDate = date.toISOString();
showToast(this.$props.toastMessage);
},
},
i18n: {
askAgainLater: __('Ask again later'),
},
};
</script>
<template>
<local-storage-sync v-model="surveyShowDate" :storage-key="storageKey">
<gl-banner
v-if="shouldShowSurvey"
:title="title"
:button-text="buttonText"
:svg-path="svgPath"
:button-link="surveyLink"
@close="hideSurvey"
>
<p>
<gl-sprintf :message="description">
<template #bold="{ content }">
<span class="gl-font-weight-bold">{{ content }}</span>
</template>
</gl-sprintf>
</p>
<template #actions>
<gl-button variant="link" class="gl-ml-5" data-testid="ask-later-button" @click="askLater">
{{ $options.i18n.askAgainLater }}
</gl-button>
</template>
</gl-banner>
</local-storage-sync>
</template>
Loading
Loading
@@ -6,6 +6,7 @@ class Groups::Security::DashboardController < Groups::ApplicationController
 
before_action do
push_frontend_feature_flag(:vulnerability_management_survey, type: :ops, default_enabled: :yaml)
push_frontend_feature_flag(:sbom_survey, @user, default_enabled: :yaml)
end
 
def show
Loading
Loading
Loading
Loading
@@ -4,6 +4,10 @@ module Projects
class DependenciesController < Projects::ApplicationController
include SecurityAndCompliancePermissions
 
before_action do
push_frontend_feature_flag(:sbom_survey, @user, default_enabled: :yaml)
end
before_action :authorize_read_dependency_list!
 
feature_category :dependency_scanning
Loading
Loading
Loading
Loading
@@ -4,6 +4,10 @@ module Projects
class LicensesController < Projects::ApplicationController
include SecurityAndCompliancePermissions
 
before_action do
push_frontend_feature_flag(:sbom_survey, @user, default_enabled: :yaml)
end
before_action :authorize_read_licenses!, only: [:index]
before_action :authorize_admin_software_license_policy!, only: [:create, :update]
 
Loading
Loading
@@ -103,6 +107,7 @@ def licenses_app_data
write_license_policies_endpoint: write_license_policies_endpoint,
documentation_path: help_page_path('user/compliance/license_compliance/index'),
empty_state_svg_path: helpers.image_path('illustrations/Dependency-list-empty-state.svg'),
sbom_survey_svg_path: helpers.image_path('illustrations/monitoring/tracing.svg'),
software_licenses: SoftwareLicense.unclassified_licenses_for(project).pluck_names,
project_id: @project.id,
project_path: expose_path(api_v4_projects_path(id: @project.id)),
Loading
Loading
Loading
Loading
@@ -11,6 +11,7 @@ class DashboardController < Projects::ApplicationController
before_action only: [:index] do
push_frontend_feature_flag(:security_auto_fix, project, default_enabled: false)
push_frontend_feature_flag(:vulnerability_management_survey, type: :ops, default_enabled: :yaml)
push_frontend_feature_flag(:sbom_survey, @user, default_enabled: :yaml)
end
 
feature_category :vulnerability_management
Loading
Loading
Loading
Loading
@@ -175,6 +175,7 @@ def project_security_dashboard_config(project)
operational_empty_state_svg_path: image_path('illustrations/security-dashboard_empty.svg'),
operational_help_path: help_page_path('user/application_security/policies/index'),
survey_request_svg_path: image_path('illustrations/security-dashboard_empty.svg'),
sbom_survey_svg_path: image_path('illustrations/monitoring/tracing.svg'),
security_dashboard_help_path: help_page_path('user/application_security/security_dashboard/index'),
no_vulnerabilities_svg_path: image_path('illustrations/issues.svg'),
project_full_path: project.full_path,
Loading
Loading
@@ -189,6 +190,7 @@ def project_security_dashboard_config(project)
vulnerabilities_export_endpoint: api_v4_security_projects_vulnerability_exports_path(id: project.id),
empty_state_svg_path: image_path('illustrations/security-dashboard-empty-state.svg'),
survey_request_svg_path: image_path('illustrations/security-dashboard_empty.svg'),
sbom_survey_svg_path: image_path('illustrations/monitoring/tracing.svg'),
no_vulnerabilities_svg_path: image_path('illustrations/issues.svg'),
dashboard_documentation: help_page_path('user/application_security/security_dashboard/index'),
not_enabled_scanners_help_path: help_page_path('user/application_security/index', anchor: 'quick-start'),
Loading
Loading
Loading
Loading
@@ -25,6 +25,7 @@ def group_level_security_dashboard_data(group)
operational_empty_state_svg_path: image_path('illustrations/security-dashboard_empty.svg'),
operational_help_path: help_page_path('user/application_security/policies/index'),
survey_request_svg_path: image_path('illustrations/security-dashboard_empty.svg'),
sbom_survey_svg_path: image_path('illustrations/monitoring/tracing.svg'),
dashboard_documentation: help_page_path('user/application_security/security_dashboard/index'),
vulnerabilities_export_endpoint: expose_path(api_v4_security_groups_vulnerability_exports_path(id: group.id)),
scanners: VulnerabilityScanners::ListService.new(group).execute.to_json,
Loading
Loading
Loading
Loading
@@ -28,6 +28,7 @@ def can_view_false_positive?
def security_dashboard_unavailable_view_data
{
empty_state_svg_path: image_path('illustrations/security-dashboard-empty-state.svg'),
sbom_survey_svg_path: image_path('illustrations/monitoring/tracing.svg'),
dashboard_documentation: help_page_path('user/application_security/security_dashboard/index'),
is_unavailable: "true"
}
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment