diff --git a/ee/app/assets/javascripts/vulnerabilities/components/dependency_path_drawer.vue b/ee/app/assets/javascripts/vulnerabilities/components/dependency_path_drawer.vue new file mode 100644 index 0000000000000000000000000000000000000000..171ac1a664ea3d0fb295faf80500cf6604f11cef --- /dev/null +++ b/ee/app/assets/javascripts/vulnerabilities/components/dependency_path_drawer.vue @@ -0,0 +1,78 @@ +<script> +import { MountingPortal } from 'portal-vue'; +import { GlButton, GlDrawer } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import { DRAWER_Z_INDEX } from '~/lib/utils/constants'; +import { getContentWrapperHeight } from '~/lib/utils/dom_utils'; + +// This is temporary and will be deleted +// Will be replaced with proper API data once the BE completes +export const TEST_PATHS = [ + ['@jest@1.2.3', '@jest-internal-whatever@1.2.3', '@babel/core@7.47.7'], + ['@react@0.13.1', '@babel/core@7.47.7'], +]; + +export default { + name: 'DependencyPathsDrawer', + components: { + MountingPortal, + GlButton, + GlDrawer, + }, + data() { + return { + isDrawerOpen: false, + }; + }, + methods: { + openDrawer() { + this.isDrawerOpen = true; + }, + closeDrawer() { + this.isDrawerOpen = false; + }, + }, + i18n: { + buttonText: s__('Vulnerability|View dependency paths'), + drawerTitle: s__('Vulnerability|Dependency paths'), + }, + getContentWrapperHeight, + DRAWER_Z_INDEX, + TEST_PATHS, +}; +</script> + +<template> + <div> + <gl-button size="small" @click="openDrawer">{{ $options.i18n.buttonText }}</gl-button> + <!-- Mount GlDrawer outside .md to fix z-index so it shows above navbar. + More info: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181949#note_2360144489 --> + <mounting-portal mount-to="#js-dependency-paths-drawer-portal"> + <gl-drawer + :header-height="$options.getContentWrapperHeight()" + :open="isDrawerOpen" + :title="$options.i18n.drawerTitle" + :z-index="$options.DRAWER_Z_INDEX" + @close="closeDrawer" + > + <template #title> + <h2 + data-testid="dependency-path-drawer-title" + class="gl-my-0 gl-text-size-h2 gl-leading-24" + > + {{ $options.i18n.drawerTitle }} + </h2> + </template> + <ul class="gl-list-none gl-p-2"> + <li + v-for="(path, index) in $options.TEST_PATHS" + :key="index" + class="gl-border-b gl-py-5 first:!gl-pt-0" + > + <div class="">{{ path.join(' / ') }}</div> + </li> + </ul> + </gl-drawer> + </mounting-portal> + </div> +</template> diff --git a/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_details.vue b/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_details.vue index c817e0034a33164f53283d7b3628570f25c08dc7..51350cd7aaed5fcbb7612b695f2de7c9f14c5ffe 100644 --- a/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_details.vue +++ b/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_details.vue @@ -28,6 +28,7 @@ import FalsePositiveAlert from './false_positive_alert.vue'; import VulnerabilityDetailSection from './vulnerability_detail_section.vue'; import VulnerabilityTraining from './vulnerability_training.vue'; import VulnerabilityFileContents from './vulnerability_file_contents.vue'; +import DependencyPathDrawer from './dependency_path_drawer.vue'; // These colors are taken from: // https://gitlab.com/gitlab-org/gitlab/-/issues/427441/designs/design_1730952836577.png @@ -56,6 +57,7 @@ export default { GlButton, GlLabel, HelpPopover, + DependencyPathDrawer, }, directives: { SafeHtml, @@ -514,6 +516,7 @@ export default { <code-block :code="location.stacktraceSnippet" max-height="225px" /> </detail-item> </ul> + <dependency-path-drawer v-if="glFeatures.dependencyPaths" /> </template> <gl-button diff --git a/ee/app/controllers/projects/security/vulnerabilities_controller.rb b/ee/app/controllers/projects/security/vulnerabilities_controller.rb index efff2a1a3cd0f27dd4f1890f0807dbb20c4c6f57..6f98f019c8304acaedc5a0cd526a663d862bb56e 100644 --- a/ee/app/controllers/projects/security/vulnerabilities_controller.rb +++ b/ee/app/controllers/projects/security/vulnerabilities_controller.rb @@ -24,6 +24,7 @@ class VulnerabilitiesController < Projects::ApplicationController def show push_frontend_ability(ability: :explain_vulnerability_with_ai, resource: vulnerability, user: current_user) push_frontend_ability(ability: :resolve_vulnerability_with_ai, resource: vulnerability, user: current_user) + push_frontend_feature_flag(:dependency_paths, project.group) pipeline = vulnerability.finding.first_finding_pipeline @pipeline = pipeline if can?(current_user, :read_pipeline, pipeline) diff --git a/ee/app/views/projects/security/vulnerabilities/show.html.haml b/ee/app/views/projects/security/vulnerabilities/show.html.haml index 199081d43fd724a14967dda737327036a39c6aed..59f3ef22ae4251770e660f400c9c84cf1a6177fb 100644 --- a/ee/app/views/projects/security/vulnerabilities/show.html.haml +++ b/ee/app/views/projects/security/vulnerabilities/show.html.haml @@ -5,4 +5,6 @@ - add_page_specific_style 'page_bundles/security_dashboard' - add_page_specific_style 'page_bundles/merge_request' +#js-dependency-paths-drawer-portal + #js-vulnerability-main{ data: vulnerability_details_app_data(@vulnerability, @pipeline, @project) } diff --git a/ee/config/feature_flags/gitlab_com_derisk/dependency_paths.yml b/ee/config/feature_flags/gitlab_com_derisk/dependency_paths.yml new file mode 100644 index 0000000000000000000000000000000000000000..e92fc8a68fd419cb4cb720325cf58a88d928622d --- /dev/null +++ b/ee/config/feature_flags/gitlab_com_derisk/dependency_paths.yml @@ -0,0 +1,9 @@ +--- +name: dependency_paths +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/519962 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181949 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/520269 +milestone: '17.10' +group: group::security insights +type: gitlab_com_derisk +default_enabled: false diff --git a/ee/spec/frontend/vulnerabilities/dependency_path_drawer_spec.js b/ee/spec/frontend/vulnerabilities/dependency_path_drawer_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..6410bc2b4b76b1c29279fb1f968ff557f0d15ee1 --- /dev/null +++ b/ee/spec/frontend/vulnerabilities/dependency_path_drawer_spec.js @@ -0,0 +1,81 @@ +import { GlDrawer, GlButton } from '@gitlab/ui'; +import { MountingPortal } from 'portal-vue'; +import { nextTick } from 'vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import DependencyPathDrawer from 'ee/vulnerabilities/components/dependency_path_drawer.vue'; +import { RENDER_ALL_SLOTS_TEMPLATE, stubComponent } from 'helpers/stub_component'; + +jest.mock('~/lib/utils/dom_utils', () => ({ getContentWrapperHeight: jest.fn() })); + +describe('Dependency paths drawer component', () => { + let wrapper; + + const createComponent = () => { + wrapper = shallowMountExtended(DependencyPathDrawer, { + stubs: { + GlDrawer: stubComponent(GlDrawer, { template: RENDER_ALL_SLOTS_TEMPLATE }), + MountingPortal: stubComponent(MountingPortal), + }, + }); + }; + + const findDrawer = () => wrapper.findComponent(GlDrawer); + const findButton = () => wrapper.findComponent(GlButton); + const findMountingPortal = () => wrapper.findComponent(MountingPortal); + + it('renders into the mounting portal', () => { + createComponent(); + + expect(findMountingPortal().attributes()).toMatchObject({ + 'mount-to': '#js-dependency-paths-drawer-portal', + }); + }); + + describe('button', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders the button', () => { + createComponent(); + + expect(findButton().props()).toMatchObject({ size: 'small' }); + expect(findButton().text()).toBe('View dependency paths'); + }); + + it('opens the drawer on click', async () => { + expect(findDrawer().props('open')).toBe(false); + + findButton().vm.$emit('click'); + await nextTick(); + + expect(findDrawer().props('open')).toBe(true); + }); + }); + + describe('drawer', () => { + beforeEach(() => { + createComponent(); + findButton().vm.$emit('click'); + }); + + it('renders the drawer on', () => { + expect(findDrawer().props()).toMatchObject( + expect.objectContaining({ open: true, zIndex: 252 }), + ); + }); + + it('renders the drawer title', () => { + expect(wrapper.findByTestId('dependency-path-drawer-title').text()).toBe('Dependency paths'); + }); + + it('closes the drawer on click', async () => { + expect(findDrawer().props('open')).toBe(true); + + findDrawer().vm.$emit('close'); + await nextTick(); + + expect(findDrawer().props('open')).toBe(false); + }); + }); +}); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d6b1364c62d5b03e5558b41bff6b47b94e374b3f..ded9e631331746b798d239b7fbf76157d36f5387 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -64169,6 +64169,9 @@ msgstr "" msgid "Vulnerability|Create a merge request to implement this solution, or download and apply the patch manually." msgstr "" +msgid "Vulnerability|Dependency paths" +msgstr "" + msgid "Vulnerability|Description" msgstr "" @@ -64319,6 +64322,9 @@ msgstr "" msgid "Vulnerability|View code flow" msgstr "" +msgid "Vulnerability|View dependency paths" +msgstr "" + msgid "Vulnerability|View training" msgstr ""