diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js index 874d51a3577884cba48d655397130d7b0f345440..5cb9277040daff3191d8b4fe0945274d9005979a 100644 --- a/app/assets/javascripts/security_configuration/components/constants.js +++ b/app/assets/javascripts/security_configuration/components/constants.js @@ -1,7 +1,6 @@ import { helpPagePath } from '~/helpers/help_page_helper'; import { __, s__ } from '~/locale'; -import configureSastMutation from '~/security_configuration/graphql/configure_sast.mutation.graphql'; import { REPORT_TYPE_SAST, REPORT_TYPE_DAST, @@ -15,6 +14,9 @@ import { REPORT_TYPE_LICENSE_COMPLIANCE, } from '~/vue_shared/security_reports/constants'; +import configureSastMutation from '../graphql/configure_sast.mutation.graphql'; +import configureSecretDetectionMutation from '../graphql/configure_secret_detection.mutation.graphql'; + /** * Translations & helpPagePaths for Static Security Configuration Page */ @@ -214,6 +216,10 @@ export const securityFeatures = [ helpPath: DEPENDENCY_SCANNING_HELP_PATH, configurationHelpPath: DEPENDENCY_SCANNING_CONFIG_HELP_PATH, type: REPORT_TYPE_DEPENDENCY_SCANNING, + + // This field will eventually come from the backend, the progress is + // tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/331621 + canEnableByMergeRequest: window.gon.features?.secDependencyScanningUiEnable, }, { name: CONTAINER_SCANNING_NAME, @@ -235,7 +241,16 @@ export const securityFeatures = [ helpPath: SECRET_DETECTION_HELP_PATH, configurationHelpPath: SECRET_DETECTION_CONFIG_HELP_PATH, type: REPORT_TYPE_SECRET_DETECTION, + + // This field is currently hardcoded because Secret Detection is always + // available. It will eventually come from the Backend, the progress is + // tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/333113 available: true, + + // This field is currently hardcoded because SAST can always be enabled via MR + // It will eventually come from the Backend, the progress is tracked in + // https://gitlab.com/gitlab-org/gitlab/-/issues/331621 + canEnableByMergeRequest: true, }, { name: API_FUZZING_NAME, @@ -273,4 +288,15 @@ export const featureToMutationMap = { }, }), }, + [REPORT_TYPE_SECRET_DETECTION]: { + mutationId: 'configureSecretDetection', + getMutationPayload: (projectPath) => ({ + mutation: configureSecretDetectionMutation, + variables: { + input: { + projectPath, + }, + }, + }), + }, }; diff --git a/app/assets/javascripts/security_configuration/components/feature_card.vue b/app/assets/javascripts/security_configuration/components/feature_card.vue index 518a6ede3de8b9a9df8fdcfdfca3d7327d1b4c29..23cffde1f835a55e967280d39de9f902f66264b6 100644 --- a/app/assets/javascripts/security_configuration/components/feature_card.vue +++ b/app/assets/javascripts/security_configuration/components/feature_card.vue @@ -46,8 +46,7 @@ export default { return button; }, showManageViaMr() { - const { available, configured, canEnableByMergeRequest } = this.feature; - return canEnableByMergeRequest && available && !configured; + return ManageViaMr.canRender(this.feature); }, cardClasses() { return { 'gl-bg-gray-10': !this.available }; diff --git a/ee/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql b/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql similarity index 100% rename from ee/app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql rename to app/assets/javascripts/security_configuration/graphql/configure_secret_detection.mutation.graphql diff --git a/app/assets/javascripts/vue_shared/security_configuration/components/manage_via_mr.vue b/app/assets/javascripts/vue_shared/security_configuration/components/manage_via_mr.vue index ff335696d6165ae129ec5a46e6466bf0f2337c07..0ff858e6afc72d8a83b96da1892fca550e4b1109 100644 --- a/app/assets/javascripts/vue_shared/security_configuration/components/manage_via_mr.vue +++ b/app/assets/javascripts/vue_shared/security_configuration/components/manage_via_mr.vue @@ -5,6 +5,10 @@ import { redirectTo } from '~/lib/utils/url_utility'; import { sprintf, s__ } from '~/locale'; import apolloProvider from '../provider'; +function mutationSettingsForFeatureType(type) { + return featureToMutationMap[type]; +} + export default { apolloProvider, components: { @@ -33,17 +37,19 @@ export default { }; }, computed: { - featureSettings() { - return featureToMutationMap[this.feature.type]; + mutationSettings() { + return mutationSettingsForFeatureType(this.feature.type); }, }, methods: { async mutate() { this.isLoading = true; try { - const mutation = this.featureSettings; - const { data } = await this.$apollo.mutate(mutation.getMutationPayload(this.projectPath)); - const { errors, successPath } = data[mutation.mutationId]; + const { mutationSettings } = this; + const { data } = await this.$apollo.mutate( + mutationSettings.getMutationPayload(this.projectPath), + ); + const { errors, successPath } = data[mutationSettings.mutationId]; if (errors.length > 0) { throw new Error(errors[0]); @@ -62,6 +68,22 @@ export default { } }, }, + /** + * Returns a boolean representing whether this component can be rendered for + * the given feature. Useful for parent components to determine whether or + * not to render this component. + * @param {Object} feature The feature to check. + * @returns {boolean} + */ + canRender(feature) { + const { available, configured, canEnableByMergeRequest, type } = feature; + return ( + canEnableByMergeRequest && + available && + !configured && + Boolean(mutationSettingsForFeatureType(type)) + ); + }, i18n: { buttonLabel: s__('SecurityConfiguration|Configure via Merge Request'), noSuccessPathError: s__( diff --git a/ee/app/assets/javascripts/security_configuration/components/constants.js b/ee/app/assets/javascripts/security_configuration/components/constants.js index 2fae567bf0a96b7f261c0b4d1600fd6fd90548ef..bd9175957ecb364470a3149b9cf77e3093d313f4 100644 --- a/ee/app/assets/javascripts/security_configuration/components/constants.js +++ b/ee/app/assets/javascripts/security_configuration/components/constants.js @@ -1,11 +1,7 @@ import { s__ } from '~/locale'; import { featureToMutationMap as featureToMutationMapCE } from '~/security_configuration/components/constants'; -import { - REPORT_TYPE_DEPENDENCY_SCANNING, - REPORT_TYPE_SECRET_DETECTION, -} from '~/vue_shared/security_reports/constants'; +import { REPORT_TYPE_DEPENDENCY_SCANNING } from '~/vue_shared/security_reports/constants'; import configureDependencyScanningMutation from '../graphql/configure_dependency_scanning.mutation.graphql'; -import configureSecretDetectionMutation from '../graphql/configure_secret_detection.mutation.graphql'; export const SMALL = 'SMALL'; export const MEDIUM = 'MEDIUM'; @@ -36,17 +32,6 @@ export const featureToMutationMap = { }, }), }, - [REPORT_TYPE_SECRET_DETECTION]: { - mutationId: 'configureSecretDetection', - getMutationPayload: (projectPath) => ({ - mutation: configureSecretDetectionMutation, - variables: { - input: { - projectPath, - }, - }, - }), - }, }; export const CONFIGURATION_SNIPPET_MODAL_ID = 'CONFIGURATION_SNIPPET_MODAL_ID'; diff --git a/spec/frontend/security_configuration/components/feature_card_spec.js b/spec/frontend/security_configuration/components/feature_card_spec.js index c69e135012e5d782f3135e307c3d859e51a4c394..3658dbb5ef2c159e56dd52dc95a2363a2415f651 100644 --- a/spec/frontend/security_configuration/components/feature_card_spec.js +++ b/spec/frontend/security_configuration/components/feature_card_spec.js @@ -3,6 +3,7 @@ import { mount } from '@vue/test-utils'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import FeatureCard from '~/security_configuration/components/feature_card.vue'; import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue'; +import { REPORT_TYPE_SAST } from '~/vue_shared/security_reports/constants'; import { makeFeature } from './utils'; describe('FeatureCard component', () => { @@ -126,21 +127,23 @@ describe('FeatureCard component', () => { describe('actions', () => { describe.each` - context | available | configured | configurationPath | canEnableByMergeRequest | action - ${'unavailable'} | ${false} | ${false} | ${null} | ${false} | ${null} - ${'available'} | ${true} | ${false} | ${null} | ${false} | ${'guide'} - ${'configured'} | ${true} | ${true} | ${null} | ${false} | ${'guide'} - ${'available, can enable by MR'} | ${true} | ${false} | ${null} | ${true} | ${'create-mr'} - ${'configured, can enable by MR'} | ${true} | ${true} | ${null} | ${true} | ${'guide'} - ${'available with config path'} | ${true} | ${false} | ${'foo'} | ${false} | ${'enable'} - ${'available with config path, can enable by MR'} | ${true} | ${false} | ${'foo'} | ${true} | ${'enable'} - ${'configured with config path'} | ${true} | ${true} | ${'foo'} | ${false} | ${'configure'} - ${'configured with config path, can enable by MR'} | ${true} | ${true} | ${'foo'} | ${true} | ${'configure'} + context | type | available | configured | configurationPath | canEnableByMergeRequest | action + ${'unavailable'} | ${REPORT_TYPE_SAST} | ${false} | ${false} | ${null} | ${false} | ${null} + ${'available'} | ${REPORT_TYPE_SAST} | ${true} | ${false} | ${null} | ${false} | ${'guide'} + ${'configured'} | ${REPORT_TYPE_SAST} | ${true} | ${true} | ${null} | ${false} | ${'guide'} + ${'available, can enable by MR'} | ${REPORT_TYPE_SAST} | ${true} | ${false} | ${null} | ${true} | ${'create-mr'} + ${'available, can enable by MR, unknown type'} | ${'foo'} | ${true} | ${false} | ${null} | ${true} | ${'guide'} + ${'configured, can enable by MR'} | ${REPORT_TYPE_SAST} | ${true} | ${true} | ${null} | ${true} | ${'guide'} + ${'available with config path'} | ${REPORT_TYPE_SAST} | ${true} | ${false} | ${'foo'} | ${false} | ${'enable'} + ${'available with config path, can enable by MR'} | ${REPORT_TYPE_SAST} | ${true} | ${false} | ${'foo'} | ${true} | ${'enable'} + ${'configured with config path'} | ${REPORT_TYPE_SAST} | ${true} | ${true} | ${'foo'} | ${false} | ${'configure'} + ${'configured with config path, can enable by MR'} | ${REPORT_TYPE_SAST} | ${true} | ${true} | ${'foo'} | ${true} | ${'configure'} `( 'given $context feature', - ({ available, configured, configurationPath, canEnableByMergeRequest, action }) => { + ({ type, available, configured, configurationPath, canEnableByMergeRequest, action }) => { beforeEach(() => { feature = makeFeature({ + type, available, configured, configurationPath, diff --git a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js index 517eee6a729c807f3cac181dff864e83173b2ae8..facbd51168cb82de3cd39d83798ba5e5d30ce132 100644 --- a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js +++ b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js @@ -9,6 +9,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import { humanize } from '~/lib/utils/text_utility'; import { redirectTo } from '~/lib/utils/url_utility'; import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue'; +import { REPORT_TYPE_SAST } from '~/vue_shared/security_reports/constants'; import { buildConfigureSecurityFeatureMockFactory } from './apollo_mocks'; jest.mock('~/lib/utils/url_utility'); @@ -169,6 +170,29 @@ describe('ManageViaMr component', () => { }, ); + describe('canRender static method', () => { + it.each` + context | type | available | configured | canEnableByMergeRequest | expectedValue + ${'an unconfigured feature'} | ${REPORT_TYPE_SAST} | ${true} | ${false} | ${true} | ${true} + ${'a configured feature'} | ${REPORT_TYPE_SAST} | ${true} | ${true} | ${true} | ${false} + ${'an unavailable feature'} | ${REPORT_TYPE_SAST} | ${false} | ${false} | ${true} | ${false} + ${'a feature which cannot be enabled via MR'} | ${REPORT_TYPE_SAST} | ${true} | ${false} | ${false} | ${false} + ${'an unknown feature'} | ${'foo'} | ${true} | ${false} | ${true} | ${false} + `( + 'given $context returns $expectedValue', + ({ type, available, configured, canEnableByMergeRequest, expectedValue }) => { + expect( + ManageViaMr.canRender({ + type, + available, + configured, + canEnableByMergeRequest, + }), + ).toBe(expectedValue); + }, + ); + }); + describe('button props', () => { it('passes the variant and category props to the GlButton', () => { const variant = 'danger';