Commit fd1f8554 authored by Mark Florian's avatar Mark Florian Committed by Martin Wortschack

Remove feature flag and update documentation

This removes the sast_configuration_ui_analyzers feature flag and
associated code paths, and updates the documentation.

Addresses https://gitlab.com/gitlab-org/gitlab/-/issues/238602
parent e085c9cf
...@@ -147,6 +147,7 @@ always take the latest SAST artifact available. ...@@ -147,6 +147,7 @@ always take the latest SAST artifact available.
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3659) in GitLab Ultimate 13.3. > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3659) in GitLab Ultimate 13.3.
> - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/232862) in GitLab Ultimate 13.4. > - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/232862) in GitLab Ultimate 13.4.
> - [Improved](https://gitlab.com/groups/gitlab-org/-/epics/3635) in GitLab Ultimate 13.5.
You can enable and configure SAST with a basic configuration using the **SAST Configuration** You can enable and configure SAST with a basic configuration using the **SAST Configuration**
page: page:
...@@ -154,9 +155,11 @@ page: ...@@ -154,9 +155,11 @@ page:
1. From the project's home page, go to **Security & Compliance** > **Configuration** in the 1. From the project's home page, go to **Security & Compliance** > **Configuration** in the
left sidebar. left sidebar.
1. If the project does not have a `gitlab-ci.yml` file, click **Enable** in the Static Application Security Testing (SAST) row, otherwise click **Configure**. 1. If the project does not have a `gitlab-ci.yml` file, click **Enable** in the Static Application Security Testing (SAST) row, otherwise click **Configure**.
1. Enter the custom SAST values, then click **Create Merge Request**. 1. Enter the custom SAST values.
Custom values are stored in the `.gitlab-ci.yml` file. For variables not in the SAST Configuration page, their values are left unchanged. Default values are inherited from the GitLab SAST template. Custom values are stored in the `.gitlab-ci.yml` file. For variables not in the SAST Configuration page, their values are left unchanged. Default values are inherited from the GitLab SAST template.
1. Optionally, expand the **SAST analyzers** section, select individual [SAST analyzers](./analyzers.md) and enter custom analyzer values.
1. Click **Create Merge Request**.
1. Review and merge the merge request. 1. Review and merge the merge request.
### Customizing the SAST settings ### Customizing the SAST settings
......
...@@ -3,7 +3,6 @@ import { GlAlert, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui'; ...@@ -3,7 +3,6 @@ import { GlAlert, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import sastCiConfigurationQuery from '../graphql/sast_ci_configuration.query.graphql'; import sastCiConfigurationQuery from '../graphql/sast_ci_configuration.query.graphql';
import sastCiConfigurationWithAnalyzersQuery from '../graphql/sast_ci_configuration_with_analyzers.query.graphql';
import ConfigurationForm from './configuration_form.vue'; import ConfigurationForm from './configuration_form.vue';
export default { export default {
...@@ -27,11 +26,7 @@ export default { ...@@ -27,11 +26,7 @@ export default {
}, },
apollo: { apollo: {
sastCiConfiguration: { sastCiConfiguration: {
query() { query: sastCiConfigurationQuery,
return this.glFeatures.sastConfigurationUiAnalyzers
? sastCiConfigurationWithAnalyzersQuery
: sastCiConfigurationQuery;
},
variables() { variables() {
return { return {
fullPath: this.projectPath, fullPath: this.projectPath,
......
...@@ -4,7 +4,6 @@ import * as Sentry from '@sentry/browser'; ...@@ -4,7 +4,6 @@ import * as Sentry from '@sentry/browser';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import AnalyzerConfiguration from './analyzer_configuration.vue'; import AnalyzerConfiguration from './analyzer_configuration.vue';
import DynamicFields from './dynamic_fields.vue'; import DynamicFields from './dynamic_fields.vue';
import ExpandableSection from './expandable_section.vue'; import ExpandableSection from './expandable_section.vue';
...@@ -24,7 +23,6 @@ export default { ...@@ -24,7 +23,6 @@ export default {
GlIcon, GlIcon,
GlLink, GlLink,
}, },
mixins: [glFeatureFlagsMixin()],
inject: { inject: {
createSastMergeRequestPath: { createSastMergeRequestPath: {
from: 'createSastMergeRequestPath', from: 'createSastMergeRequestPath',
...@@ -54,18 +52,14 @@ export default { ...@@ -54,18 +52,14 @@ export default {
return { return {
globalConfiguration: cloneDeep(this.sastCiConfiguration.global.nodes), globalConfiguration: cloneDeep(this.sastCiConfiguration.global.nodes),
pipelineConfiguration: cloneDeep(this.sastCiConfiguration.pipeline.nodes), pipelineConfiguration: cloneDeep(this.sastCiConfiguration.pipeline.nodes),
analyzersConfiguration: this.glFeatures.sastConfigurationUiAnalyzers analyzersConfiguration: cloneDeep(this.sastCiConfiguration.analyzers.nodes),
? cloneDeep(this.sastCiConfiguration.analyzers.nodes)
: [],
hasSubmissionError: false, hasSubmissionError: false,
isSubmitting: false, isSubmitting: false,
}; };
}, },
computed: { computed: {
shouldRenderAnalyzersSection() { shouldRenderAnalyzersSection() {
return Boolean( return this.analyzersConfiguration.length > 0;
this.glFeatures.sastConfigurationUiAnalyzers && this.analyzersConfiguration.length > 0,
);
}, },
}, },
methods: { methods: {
...@@ -100,18 +94,11 @@ export default { ...@@ -100,18 +94,11 @@ export default {
}); });
}, },
getMutationConfiguration() { getMutationConfiguration() {
const configuration = { return {
global: this.globalConfiguration.map(toSastCiConfigurationEntityInput), global: this.globalConfiguration.map(toSastCiConfigurationEntityInput),
pipeline: this.pipelineConfiguration.map(toSastCiConfigurationEntityInput), pipeline: this.pipelineConfiguration.map(toSastCiConfigurationEntityInput),
analyzers: this.analyzersConfiguration.map(toSastCiConfigurationAnalyzerEntityInput),
}; };
if (this.glFeatures.sastConfigurationUiAnalyzers) {
configuration.analyzers = this.analyzersConfiguration.map(
toSastCiConfigurationAnalyzerEntityInput,
);
}
return configuration;
}, },
onAnalyzerChange(name, updatedAnalyzer) { onAnalyzerChange(name, updatedAnalyzer) {
const index = this.analyzersConfiguration.findIndex(analyzer => analyzer.name === name); const index = this.analyzersConfiguration.findIndex(analyzer => analyzer.name === name);
......
#import "./sast_ci_configuration_entity.fragment.graphql"
fragment SastCiConfigurationFragment on SastCiConfiguration {
global {
nodes {
...SastCiConfigurationEntityFragment
}
}
pipeline {
nodes {
...SastCiConfigurationEntityFragment
}
}
}
#import "./sast_ci_configuration.fragment.graphql" #import "./sast_ci_configuration_entity.fragment.graphql"
query sastCiConfiguration($fullPath: ID!) { query sastCiConfiguration($fullPath: ID!) {
project(fullPath: $fullPath) { project(fullPath: $fullPath) {
sastCiConfiguration { sastCiConfiguration {
...SastCiConfigurationFragment global {
nodes {
...SastCiConfigurationEntityFragment
}
}
pipeline {
nodes {
...SastCiConfigurationEntityFragment
}
}
analyzers {
nodes {
description
enabled
label
name
variables {
nodes {
...SastCiConfigurationEntityFragment
}
}
}
}
} }
} }
} }
#import "./sast_ci_configuration.fragment.graphql"
#import "./sast_ci_configuration_entity.fragment.graphql"
query sastCiConfiguration($fullPath: ID!) {
project(fullPath: $fullPath) {
sastCiConfiguration {
...SastCiConfigurationFragment
analyzers {
nodes {
description
enabled
label
name
variables {
nodes {
...SastCiConfigurationEntityFragment
}
}
}
}
}
}
}
...@@ -11,10 +11,6 @@ module Projects ...@@ -11,10 +11,6 @@ module Projects
before_action :ensure_sast_configuration_enabled!, except: [:create] before_action :ensure_sast_configuration_enabled!, except: [:create]
before_action :authorize_edit_tree!, only: [:create] before_action :authorize_edit_tree!, only: [:create]
before_action only: [:show] do
push_frontend_feature_flag(:sast_configuration_ui_analyzers, project)
end
def show def show
end end
......
---
title: Expose analyzer configuration in SAST Configuration UI
merge_request: 42593
author:
type: added
---
name: sast_configuration_ui_analyzers
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42214
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/238602
group: group::static analysis
type: development
default_enabled: false
...@@ -71,7 +71,7 @@ describe('ConfigurationForm component', () => { ...@@ -71,7 +71,7 @@ describe('ConfigurationForm component', () => {
const findAnalyzerConfigurations = () => wrapper.findAll(AnalyzerConfiguration); const findAnalyzerConfigurations = () => wrapper.findAll(AnalyzerConfiguration);
const findAnalyzersSection = () => wrapper.find('[data-testid="analyzers-section"]'); const findAnalyzersSection = () => wrapper.find('[data-testid="analyzers-section"]');
const expectPayloadForEntities = ({ withAnalyzers = false } = {}) => { const expectPayloadForEntities = () => {
const expectedPayload = { const expectedPayload = {
mutation: configureSastMutation, mutation: configureSastMutation,
variables: { variables: {
...@@ -92,13 +92,7 @@ describe('ConfigurationForm component', () => { ...@@ -92,13 +92,7 @@ describe('ConfigurationForm component', () => {
value: 'value1', value: 'value1',
}, },
], ],
}, analyzers: [
},
},
};
if (withAnalyzers) {
expectedPayload.variables.input.configuration.analyzers = [
{ {
name: 'nameValue0', name: 'nameValue0',
enabled: true, enabled: true,
...@@ -110,8 +104,11 @@ describe('ConfigurationForm component', () => { ...@@ -110,8 +104,11 @@ describe('ConfigurationForm component', () => {
}, },
], ],
}, },
]; ],
} },
},
},
};
expect(wrapper.vm.$apollo.mutate.mock.calls).toEqual([[expectedPayload]]); expect(wrapper.vm.$apollo.mutate.mock.calls).toEqual([[expectedPayload]]);
}; };
...@@ -162,24 +159,8 @@ describe('ConfigurationForm component', () => { ...@@ -162,24 +159,8 @@ describe('ConfigurationForm component', () => {
}); });
describe('the analyzers section', () => { describe('the analyzers section', () => {
describe('given the sastConfigurationUiAnalyzers feature flag is disabled', () => {
beforeEach(() => {
createComponent();
});
it('does not render', () => {
expect(findAnalyzersSection().exists()).toBe(false);
});
});
describe('given the sastConfigurationUiAnalyzers feature flag is enabled', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
provide: {
glFeatures: {
sastConfigurationUiAnalyzers: true,
},
},
stubs: { stubs: {
ExpandableSection, ExpandableSection,
}, },
...@@ -225,7 +206,6 @@ describe('ConfigurationForm component', () => { ...@@ -225,7 +206,6 @@ describe('ConfigurationForm component', () => {
}); });
}); });
}); });
});
describe('when submitting the form', () => { describe('when submitting the form', () => {
beforeEach(() => { beforeEach(() => {
...@@ -233,30 +213,23 @@ describe('ConfigurationForm component', () => { ...@@ -233,30 +213,23 @@ describe('ConfigurationForm component', () => {
}); });
describe.each` describe.each`
context | successPath | errors | sastConfigurationUiAnalyzers context | successPath | errors
${'no successPath'} | ${''} | ${[]} | ${false} ${'no successPath'} | ${''} | ${[]}
${'any errors'} | ${''} | ${['an error']} | ${false} ${'any errors'} | ${''} | ${['an error']}
${'no successPath'} | ${''} | ${[]} | ${true} `('given an unsuccessful endpoint response due to $context', ({ successPath, errors }) => {
${'any errors'} | ${''} | ${['an error']} | ${true}
`(
'given an unsuccessful endpoint response due to $context',
({ successPath, errors, sastConfigurationUiAnalyzers }) => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
mutationResult: { mutationResult: {
successPath, successPath,
errors, errors,
}, },
provide: {
glFeatures: { sastConfigurationUiAnalyzers },
},
}); });
findForm().trigger('submit'); findForm().trigger('submit');
}); });
it('includes the value of each entity in the payload', () => { it('includes the value of each entity in the payload', () => {
expectPayloadForEntities({ withAnalyzers: sastConfigurationUiAnalyzers }); expectPayloadForEntities();
}); });
it(`sets the submit button's loading prop to true`, () => { it(`sets the submit button's loading prop to true`, () => {
...@@ -294,39 +267,28 @@ describe('ConfigurationForm component', () => { ...@@ -294,39 +267,28 @@ describe('ConfigurationForm component', () => {
}); });
}); });
}); });
}, });
);
describe.each([true, false])( describe('given a successful endpoint response', () => {
'given a successful endpoint response with sastConfigurationUiAnalyzers = %p',
sastConfigurationUiAnalyzers => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
mutationResult: { mutationResult: {
successPath: newMergeRequestPath, successPath: newMergeRequestPath,
errors: [], errors: [],
}, },
provide: {
glFeatures: { sastConfigurationUiAnalyzers },
},
}); });
findForm().trigger('submit'); findForm().trigger('submit');
}); });
// See https://github.com/jest-community/eslint-plugin-jest/issues/229
// for a similar reason for disabling the rule on the next line
// eslint-disable-next-line jest/no-identical-title
it('includes the value of each entity in the payload', () => { it('includes the value of each entity in the payload', () => {
expectPayloadForEntities({ withAnalyzers: sastConfigurationUiAnalyzers }); expectPayloadForEntities();
}); });
// eslint-disable-next-line jest/no-identical-title
it(`sets the submit button's loading prop to true`, () => { it(`sets the submit button's loading prop to true`, () => {
expect(findSubmitButton().props().loading).toBe(true); expect(findSubmitButton().props().loading).toBe(true);
}); });
// eslint-disable-next-line jest/no-identical-title
describe('after async tasks', () => { describe('after async tasks', () => {
beforeEach(fulfillPendingPromises); beforeEach(fulfillPendingPromises);
...@@ -351,8 +313,7 @@ describe('ConfigurationForm component', () => { ...@@ -351,8 +313,7 @@ describe('ConfigurationForm component', () => {
expect(findSubmitButton().props().loading).toBe(true); expect(findSubmitButton().props().loading).toBe(true);
}); });
}); });
}, });
);
}); });
describe('the cancel button', () => { describe('the cancel button', () => {
......
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