diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-14 03:10:28 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-14 03:10:28 +0300 |
commit | 968e01a6dd8f4765223f4d1ffb096483f5cce3eb (patch) | |
tree | cca2d6ae4a68559da95528f848ff3f1530350698 | |
parent | 3825437c53474e3d6f3a7d82ef4f26583212531e (diff) |
Add latest changes from gitlab-org/gitlab@master
21 files changed, 395 insertions, 107 deletions
diff --git a/app/assets/javascripts/lib/utils/keys.js b/app/assets/javascripts/lib/utils/keys.js index 8e5420e87ea..b7ba6d3a071 100644 --- a/app/assets/javascripts/lib/utils/keys.js +++ b/app/assets/javascripts/lib/utils/keys.js @@ -2,3 +2,4 @@ export const ESC_KEY = 'Escape'; export const ESC_KEY_IE11 = 'Esc'; // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key +export const ENTER_KEY = 'Enter'; diff --git a/app/assets/javascripts/releases/components/app_edit_new.vue b/app/assets/javascripts/releases/components/app_edit_new.vue index 1710abe72ef..7b7c80a6269 100644 --- a/app/assets/javascripts/releases/components/app_edit_new.vue +++ b/app/assets/javascripts/releases/components/app_edit_new.vue @@ -85,7 +85,7 @@ export default { saveButtonLabel() { return this.isExistingRelease ? __('Save changes') : __('Create release'); }, - isSaveChangesDisabled() { + isFormSubmissionDisabled() { return this.isUpdatingRelease || !this.isValid; }, milestoneComboboxExtraLinks() { @@ -116,13 +116,18 @@ export default { 'updateReleaseNotes', 'updateReleaseMilestones', ]), + submitForm() { + if (!this.isFormSubmissionDisabled) { + this.saveRelease(); + } + }, }, }; </script> <template> <div class="d-flex flex-column"> <p class="pt-3 js-subtitle-text" v-html="subtitleText"></p> - <form v-if="showForm" @submit.prevent="saveRelease()"> + <form v-if="showForm" class="js-quick-submit" @submit.prevent="submitForm"> <tag-field /> <gl-form-group> <label for="release-title">{{ __('Release title') }}</label> @@ -134,7 +139,7 @@ export default { class="form-control" /> </gl-form-group> - <gl-form-group class="w-50"> + <gl-form-group class="w-50" @keydown.enter.prevent.capture> <label>{{ __('Milestones') }}</label> <div class="d-flex flex-column col-md-6 col-sm-10 pl-0"> <milestone-combobox @@ -163,8 +168,6 @@ export default { data-supports-quick-actions="false" :aria-label="__('Release notes')" :placeholder="__('Write your release notes or drag your files hereā¦')" - @keydown.meta.enter="saveRelease()" - @keydown.ctrl.enter="saveRelease()" ></textarea> </template> </markdown-field> @@ -179,7 +182,7 @@ export default { category="primary" variant="success" type="submit" - :disabled="isSaveChangesDisabled" + :disabled="isFormSubmissionDisabled" data-testid="submit-button" > {{ saveButtonLabel }} diff --git a/app/assets/javascripts/releases/components/asset_links_form.vue b/app/assets/javascripts/releases/components/asset_links_form.vue index d0d1485d8e7..07fab840067 100644 --- a/app/assets/javascripts/releases/components/asset_links_form.vue +++ b/app/assets/javascripts/releases/components/asset_links_form.vue @@ -49,6 +49,12 @@ export default { this.removeAssetLink(linkId); this.ensureAtLeastOneLink(); }, + updateUrl(link, newUrl) { + this.updateAssetLinkUrl({ linkIdToUpdate: link.id, newUrl }); + }, + updateName(link, newName) { + this.updateAssetLinkName({ linkIdToUpdate: link.id, newName }); + }, hasDuplicateUrl(link) { return Boolean(this.getLinkErrors(link).isDuplicate); }, @@ -138,7 +144,9 @@ export default { type="text" class="form-control" :state="isUrlValid(link)" - @change="updateAssetLinkUrl({ linkIdToUpdate: link.id, newUrl: $event })" + @change="updateUrl(link, $event)" + @keydown.ctrl.enter="updateUrl(link, $event.target.value)" + @keydown.meta.enter="updateUrl(link, $event.target.value)" /> <template #invalid-feedback> <span v-if="hasEmptyUrl(link)" class="invalid-feedback d-inline"> @@ -175,7 +183,9 @@ export default { type="text" class="form-control" :state="isNameValid(link)" - @change="updateAssetLinkName({ linkIdToUpdate: link.id, newName: $event })" + @change="updateName(link, $event)" + @keydown.ctrl.enter="updateName(link, $event.target.value)" + @keydown.meta.enter="updateName(link, $event.target.value)" /> <template #invalid-feedback> <span v-if="hasEmptyName(link)" class="invalid-feedback d-inline"> diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js index 319b6c333f4..dc8a6b56d58 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js @@ -32,5 +32,8 @@ export default { isMergeImmediatelyDangerous() { return false; }, + shouldRenderMergeTrainHelperText() { + return false; + }, }, }; diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index f3a47b03e2e..9b246979860 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -33,7 +33,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true) push_frontend_feature_flag(:merge_ref_head_comments, @project, default_enabled: true) push_frontend_feature_flag(:mr_commit_neighbor_nav, @project, default_enabled: true) - push_frontend_feature_flag(:multiline_comments, @project) + push_frontend_feature_flag(:multiline_comments, @project, default_enabled: true) push_frontend_feature_flag(:file_identifier_hash) push_frontend_feature_flag(:batch_suggestions, @project, default_enabled: true) push_frontend_feature_flag(:auto_expand_collapsed_diffs, @project, default_enabled: true) diff --git a/app/graphql/resolvers/ci_configuration/sast_resolver.rb b/app/graphql/resolvers/ci_configuration/sast_resolver.rb new file mode 100644 index 00000000000..e8c42076ea2 --- /dev/null +++ b/app/graphql/resolvers/ci_configuration/sast_resolver.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "json" + +module Resolvers + module CiConfiguration + class SastResolver < BaseResolver + SAST_UI_SCHEMA_PATH = 'app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json' + + type ::Types::CiConfiguration::Sast::Type, null: true + + def resolve(**args) + Gitlab::Json.parse(File.read(Rails.root.join(SAST_UI_SCHEMA_PATH))) + end + end + end +end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 5562db69de6..523e019b955 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -175,6 +175,10 @@ module Types description: 'A single environment of the project', resolver: Resolvers::EnvironmentsResolver.single + field :sast_ci_configuration, ::Types::CiConfiguration::Sast::Type, null: true, + description: 'SAST CI configuration for the project', + resolver: ::Resolvers::CiConfiguration::SastResolver + field :issue, Types::IssueType, null: true, diff --git a/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json b/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json index aa4dd60a9fb..cce2b28529f 100644 --- a/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json +++ b/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json @@ -4,43 +4,83 @@ "field" : "SECURE_ANALYZERS_PREFIX", "label" : "Image prefix", "type": "string", - "default_value": "", - "value": "", - "description": "Analyzer image's registry prefix (or Name of the registry providing the analyzers' image)" + "default_value": "registry.gitlab.com/gitlab-org/security-products/analyzers", + "value": "" }, { "field" : "SAST_EXCLUDED_PATHS", "label" : "Excluded Paths", "type": "string", - "default_value": "", - "value": "", - "description": "Comma-separated list of paths to be excluded from analyzer output. Patterns can be globs, file paths, or folder paths." + "default_value": "spec, test, tests, tmp", + "value": "" }, { "field" : "SAST_ANALYZER_IMAGE_TAG", "label" : "Image tag", "type": "string", - "default_value": "", - "value": "", - "description": "Analyzer image's tag" + "options": [], + "default_value": "2", + "value": "" + }, + { + "field" : "SAST_DISABLED", + "label" : "Disable SAST", + "type": "options", + "options": [ + { + "value" :"true", + "label" : "true (disables SAST)" + }, + { + "value":"false", + "label":"false (enables SAST)" + } + ], + "default_value": "false", + "value": "" } ], "pipeline": [ { "field" : "stage", "label" : "Stage", - "type": "string", - "default_value": "", - "value": "", - "description": "Pipeline stage in which the scan jobs run" + "type": "dropdown", + "options": [ + { + "value" :"test", + "label" : "test" + }, + { + "value":"build", + "label":"build" + } + ], + "default_value": "test", + "value": "" }, { - "field" : "SEARCH_MAX_DEPTH", - "label" : "Search maximum depth", - "type": "string", + "field" : "allow_failure", + "label" : "Allow Failure", + "type": "options", + "options": [ + { + "value" :"true", + "label" : "Allows pipeline failure" + }, + { + "value": "false", + "label": "Does not allow pipeline failure" + } + ], + "default_value": "true", + "value": "" + }, + { + "field" : "rules", + "label" : "Rules", + "type": "multiline", "default_value": "", - "value": "", - "description": "Maximum depth of language and framework detection" + "value": "" } ], "analyzers": [ diff --git a/changelogs/unreleased/jdb-mlc-enable-default.yml b/changelogs/unreleased/jdb-mlc-enable-default.yml new file mode 100644 index 00000000000..5a9972bfa79 --- /dev/null +++ b/changelogs/unreleased/jdb-mlc-enable-default.yml @@ -0,0 +1,5 @@ +--- +title: Enable Multiline Comments by default +merge_request: 39370 +author: +type: changed diff --git a/changelogs/unreleased/nfriend-prevent-submission-of-invalid-form-on-new-release-page.yml b/changelogs/unreleased/nfriend-prevent-submission-of-invalid-form-on-new-release-page.yml new file mode 100644 index 00000000000..8a7b08b46be --- /dev/null +++ b/changelogs/unreleased/nfriend-prevent-submission-of-invalid-form-on-new-release-page.yml @@ -0,0 +1,5 @@ +--- +title: Improve submission behavior of the New/Edit Release page +merge_request: 39145 +author: +type: added diff --git a/doc/.vale/gitlab/Contractions.yml b/doc/.vale/gitlab/ContractionsDiscard.yml index c6b970ab6e9..698fda86b5b 100644 --- a/doc/.vale/gitlab/Contractions.yml +++ b/doc/.vale/gitlab/ContractionsDiscard.yml @@ -1,7 +1,7 @@ --- -# Suggestion: gitlab.Contractions +# Suggestion: gitlab.ContractionsDiscard # -# Checks for use of common and uncommon contractions. +# Suggests a list of agreed-upon contractions to discard. # # For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles extends: substitution @@ -12,18 +12,6 @@ nonword: false ignorecase: true swap: - # Common contractions are ok - it is: it's - can not: can't - cannot: can't - do not: don't - have not: haven't - that is: that's - we are: we're - would not: wouldn't - you are: you're - you have: you've - # Uncommon contractions are not ok aren't: are not couldn't: could not diff --git a/doc/.vale/gitlab/ContractionsKeep.yml b/doc/.vale/gitlab/ContractionsKeep.yml new file mode 100644 index 00000000000..eeaf65e0829 --- /dev/null +++ b/doc/.vale/gitlab/ContractionsKeep.yml @@ -0,0 +1,25 @@ +--- +# Suggestion: gitlab.ContractionsKeep +# +# Suggests a list of agreed-upon contractions to keep. +# +# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles +extends: substitution +message: 'Use "%s" instead of "%s", for a friendly, informal tone.' +link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#language +level: suggestion +nonword: false +ignorecase: true +swap: + + # Common contractions are ok + it is: it's + can not: can't + cannot: can't + do not: don't + have not: haven't + that is: that's + we are: we're + would not: wouldn't + you are: you're + you have: you've diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md index 3040c31de7f..13878e72162 100644 --- a/doc/user/group/saml_sso/index.md +++ b/doc/user/group/saml_sso/index.md @@ -80,6 +80,7 @@ Please note that the certificate [fingerprint algorithm](#additional-providers-a With this option enabled, users must go through your group's GitLab single sign-on URL. They may also be added via SCIM, if configured. Users cannot be added manually, and may only access project/group resources via the UI by signing in through the SSO URL. However, users will not be prompted to sign in through SSO on each visit. GitLab will check whether a user has authenticated through SSO, and will only prompt the user to sign in via SSO if the session has expired. +You can see more information about how long a session is valid in our [user profile documentation](../../profile/#why-do-i-keep-getting-signed-out). We intend to add a similar SSO requirement for [Git and API activity](https://gitlab.com/gitlab-org/gitlab/-/issues/9152). diff --git a/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md b/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md index 496525c147c..4e821145339 100644 --- a/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md +++ b/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md @@ -151,11 +151,12 @@ in a Merge Request. To do so, click the **{comment}** **comment** icon in the gu ### Commenting on multiple lines > - [Introduced](https://gitlab.com/gitlab-org/ux-research/-/issues/870) in GitLab 13.2. -> - It's deployed behind a feature flag, disabled by default. +> - It's deployed behind a feature flag, enabled by default. +> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/221268) on GitLab 13.3. > - It's enabled on GitLab.com. -> - It can be enabled or disabled per-project. +> - It can be disabled or enabled per-project. > - It's recommended for production use. -> - For GitLab self-managed instances, GitLab administrators can opt to [enable it](#enable-or-disable-multiline-comments-core-only). **(CORE ONLY)** +> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-multiline-comments-core-only). **(CORE ONLY)** GitLab provides a way to select which lines of code a comment refers to. After starting a comment a dropdown selector is shown to select the first line that this comment refers to. @@ -178,16 +179,16 @@ It is deployed behind a feature flag that is **disabled by default**. [GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) can opt to enable it for your instance. -To enable it: +To disable it: ```ruby -Feature.enable(:multiline_comments) +Feature.disable(:multiline_comments) ``` -To disable it: +To enable it: ```ruby -Feature.disable(:multiline_comments) +Feature.enable(:multiline_comments) ``` ## Pipeline status in merge requests widgets diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb index c878ee7329f..0ca626381d4 100644 --- a/spec/features/global_search_spec.rb +++ b/spec/features/global_search_spec.rb @@ -36,7 +36,7 @@ RSpec.describe 'Global search' do end end - it 'closes the dropdown on blur', :js do + it 'closes the dropdown on blur', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/201841' do fill_in 'search', with: "a" dropdown = find('.js-dashboard-search-options') diff --git a/spec/frontend/releases/components/app_edit_new_spec.js b/spec/frontend/releases/components/app_edit_new_spec.js index 4e0fa9d265c..b789040a27c 100644 --- a/spec/frontend/releases/components/app_edit_new_spec.js +++ b/spec/frontend/releases/components/app_edit_new_spec.js @@ -83,11 +83,10 @@ describe('Release edit/new component', () => { }); const findSubmitButton = () => wrapper.find('button[type=submit]'); + const findForm = () => wrapper.find('form'); describe(`basic functionality tests: all tests unrelated to the "${BACK_URL_PARAM}" parameter`, () => { - beforeEach(() => { - factory(); - }); + beforeEach(factory); it('calls initializeRelease when the component is created', () => { expect(actions.initializeRelease).toHaveBeenCalledTimes(1); @@ -122,15 +121,14 @@ describe('Release edit/new component', () => { }); it('calls saveRelease when the form is submitted', () => { - wrapper.find('form').trigger('submit'); + findForm().trigger('submit'); + expect(actions.saveRelease).toHaveBeenCalledTimes(1); }); }); describe(`when the URL does not contain a "${BACK_URL_PARAM}" parameter`, () => { - beforeEach(() => { - factory(); - }); + beforeEach(factory); it(`renders a "Cancel" button with an href pointing to "${BACK_URL_PARAM}"`, () => { const cancelButton = wrapper.find('.js-cancel-button'); @@ -246,6 +244,12 @@ describe('Release edit/new component', () => { it('renders the submit button as disabled', () => { expect(findSubmitButton().attributes('disabled')).toBe('disabled'); }); + + it('does not allow the form to be submitted', () => { + findForm().trigger('submit'); + + expect(actions.saveRelease).not.toHaveBeenCalled(); + }); }); }); }); diff --git a/spec/frontend/releases/components/asset_links_form_spec.js b/spec/frontend/releases/components/asset_links_form_spec.js index e1f8592270e..727d593d851 100644 --- a/spec/frontend/releases/components/asset_links_form_spec.js +++ b/spec/frontend/releases/components/asset_links_form_spec.js @@ -3,6 +3,7 @@ import { mount, createLocalVue } from '@vue/test-utils'; import AssetLinksForm from '~/releases/components/asset_links_form.vue'; import { release as originalRelease } from '../mock_data'; import * as commonUtils from '~/lib/utils/common_utils'; +import { ENTER_KEY } from '~/lib/utils/keys'; import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants'; const localVue = createLocalVue(); @@ -91,42 +92,128 @@ describe('Release edit component', () => { expect(actions.removeAssetLink).toHaveBeenCalledTimes(1); }); - it('calls the "updateAssetLinkUrl" store method when text is entered into the "URL" input field', () => { - const linkIdToUpdate = release.assets.links[0].id; - const newUrl = 'updated url'; + describe('URL input field', () => { + let input; + let linkIdToUpdate; + let newUrl; - expect(actions.updateAssetLinkUrl).not.toHaveBeenCalled(); + beforeEach(() => { + input = wrapper.find({ ref: 'urlInput' }).element; + linkIdToUpdate = release.assets.links[0].id; + newUrl = 'updated url'; + }); - wrapper.find({ ref: 'urlInput' }).vm.$emit('change', newUrl); + const expectStoreMethodNotToBeCalled = () => { + expect(actions.updateAssetLinkUrl).not.toHaveBeenCalled(); + }; - expect(actions.updateAssetLinkUrl).toHaveBeenCalledTimes(1); - expect(actions.updateAssetLinkUrl).toHaveBeenCalledWith( - expect.anything(), - { - linkIdToUpdate, - newUrl, - }, - undefined, - ); + const dispatchKeydowEvent = eventParams => { + const event = new KeyboardEvent('keydown', eventParams); + + input.dispatchEvent(event); + }; + + const expectStoreMethodToBeCalled = () => { + expect(actions.updateAssetLinkUrl).toHaveBeenCalledTimes(1); + expect(actions.updateAssetLinkUrl).toHaveBeenCalledWith( + expect.anything(), + { + linkIdToUpdate, + newUrl, + }, + undefined, + ); + }; + + it('calls the "updateAssetLinkUrl" store method when text is entered into the "URL" input field', () => { + expectStoreMethodNotToBeCalled(); + + wrapper.find({ ref: 'urlInput' }).vm.$emit('change', newUrl); + + expectStoreMethodToBeCalled(); + }); + + it('calls the "updateAssetLinkUrl" store method when Ctrl+Enter is pressed inside the "URL" input field', () => { + expectStoreMethodNotToBeCalled(); + + input.value = newUrl; + + dispatchKeydowEvent({ key: ENTER_KEY, ctrlKey: true }); + + expectStoreMethodToBeCalled(); + }); + + it('calls the "updateAssetLinkUrl" store method when Cmd+Enter is pressed inside the "URL" input field', () => { + expectStoreMethodNotToBeCalled(); + + input.value = newUrl; + + dispatchKeydowEvent({ key: ENTER_KEY, metaKey: true }); + + expectStoreMethodToBeCalled(); + }); }); - it('calls the "updateAssetLinkName" store method when text is entered into the "Link title" input field', () => { - const linkIdToUpdate = release.assets.links[0].id; - const newName = 'updated name'; + describe('Link title field', () => { + let input; + let linkIdToUpdate; + let newName; - expect(actions.updateAssetLinkName).not.toHaveBeenCalled(); + beforeEach(() => { + input = wrapper.find({ ref: 'nameInput' }).element; + linkIdToUpdate = release.assets.links[0].id; + newName = 'updated name'; + }); - wrapper.find({ ref: 'nameInput' }).vm.$emit('change', newName); + const expectStoreMethodNotToBeCalled = () => { + expect(actions.updateAssetLinkUrl).not.toHaveBeenCalled(); + }; - expect(actions.updateAssetLinkName).toHaveBeenCalledTimes(1); - expect(actions.updateAssetLinkName).toHaveBeenCalledWith( - expect.anything(), - { - linkIdToUpdate, - newName, - }, - undefined, - ); + const dispatchKeydowEvent = eventParams => { + const event = new KeyboardEvent('keydown', eventParams); + + input.dispatchEvent(event); + }; + + const expectStoreMethodToBeCalled = () => { + expect(actions.updateAssetLinkName).toHaveBeenCalledTimes(1); + expect(actions.updateAssetLinkName).toHaveBeenCalledWith( + expect.anything(), + { + linkIdToUpdate, + newName, + }, + undefined, + ); + }; + + it('calls the "updateAssetLinkName" store method when text is entered into the "Link title" input field', () => { + expectStoreMethodNotToBeCalled(); + + wrapper.find({ ref: 'nameInput' }).vm.$emit('change', newName); + + expectStoreMethodToBeCalled(); + }); + + it('calls the "updateAssetLinkName" store method when Ctrl+Enter is pressed inside the "Link title" input field', () => { + expectStoreMethodNotToBeCalled(); + + input.value = newName; + + dispatchKeydowEvent({ key: ENTER_KEY, ctrlKey: true }); + + expectStoreMethodToBeCalled(); + }); + + it('calls the "updateAssetLinkName" store method when Cmd+Enter is pressed inside the "Link title" input field', () => { + expectStoreMethodNotToBeCalled(); + + input.value = newName; + + dispatchKeydowEvent({ key: ENTER_KEY, metaKey: true }); + + expectStoreMethodToBeCalled(); + }); }); it('calls the "updateAssetLinkType" store method when an option is selected from the "Type" dropdown', () => { diff --git a/spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb b/spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb new file mode 100644 index 00000000000..de69ad5d450 --- /dev/null +++ b/spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::CiConfiguration::SastResolver do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + + describe '#resolve' do + subject(:sast_config) { resolve(described_class, ctx: { current_user: user }, obj: project) } + + it 'returns global variable informations related to SAST' do + expect(sast_config['global'].first['field']).to eql("SECURE_ANALYZERS_PREFIX") + expect(sast_config['global'].first['label']).to eql("Image prefix") + expect(sast_config['global'].first['type']).to eql("string") + + expect(sast_config['pipeline'].first['field']).to eql("stage") + expect(sast_config['pipeline'].first['label']).to eql("Stage") + expect(sast_config['pipeline'].first['type']).to eql("dropdown") + + expect(sast_config['analyzers'].first['name']).to eql("brakeman") + expect(sast_config['analyzers'].first['label']).to eql("Brakeman") + expect(sast_config['analyzers'].first['enabled']).to be true + end + end +end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 8a5d0cdf12d..a0b6858fc99 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -26,7 +26,7 @@ RSpec.describe GitlabSchema.types['Project'] do grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments environment boards jira_import_status jira_imports services releases release alert_management_alerts alert_management_alert alert_management_alert_status_counts - container_expiration_policy service_desk_enabled service_desk_address + container_expiration_policy sast_ci_configuration service_desk_enabled service_desk_address issue_status_counts ] @@ -150,5 +150,93 @@ RSpec.describe GitlabSchema.types['Project'] do it { is_expected.to have_graphql_type(Types::ContainerExpirationPolicyType) } end + describe 'sast_ci_configuration' do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let_it_be(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + sastCiConfiguration { + global { + nodes { + type + options { + nodes { + label + value + } + } + field + label + defaultValue + value + } + } + pipeline { + nodes { + type + options { + nodes { + label + value + } + } + field + label + defaultValue + value + } + } + analyzers { + nodes { + name + label + enabled + } + } + } + } + } + ) + end + + subject { GitlabSchema.execute(query, context: { current_user: user }).as_json } + + before do + project.add_developer(user) + end + + it "returns the project's sast configuration for global variables" do + query_result = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes') + first_config = query_result.first + fourth_config = query_result[3] + expect(first_config['type']).to eq('string') + expect(first_config['field']).to eq('SECURE_ANALYZERS_PREFIX') + expect(first_config['label']).to eq('Image prefix') + expect(first_config['defaultValue']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers') + expect(first_config['value']).to eq('') + expect(first_config['options']).to be_nil + expect(fourth_config['options']['nodes']).to match([{ "value" => "true", "label" => "true (disables SAST)" }, + { "value" => "false", "label" => "false (enables SAST)" }]) + end + + it "returns the project's sast configuration for pipeline variables" do + configuration = subject.dig('data', 'project', 'sastCiConfiguration', 'pipeline', 'nodes').first + expect(configuration['type']).to eq('dropdown') + expect(configuration['field']).to eq('stage') + expect(configuration['label']).to eq('Stage') + expect(configuration['defaultValue']).to eq('test') + expect(configuration['value']).to eq('') + end + + it "returns the project's sast configuration for analyzer variables" do + configuration = subject.dig('data', 'project', 'sastCiConfiguration', 'analyzers', 'nodes').first + expect(configuration['name']).to eq('brakeman') + expect(configuration['label']).to eq('Brakeman') + expect(configuration['enabled']).to eq(true) + end + end + it_behaves_like 'a GraphQL type with labels' end diff --git a/spec/support/gitlab_stubs/gitlab_ci_for_sast.yml b/spec/support/gitlab_stubs/gitlab_ci_for_sast.yml deleted file mode 100644 index 4134660e4b9..00000000000 --- a/spec/support/gitlab_stubs/gitlab_ci_for_sast.yml +++ /dev/null @@ -1,13 +0,0 @@ -include: - - template: SAST.gitlab-ci.yml - -variables: - SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers2" - SAST_EXCLUDED_PATHS: "spec, executables" - -stages: - - our_custom_security_stage -sast: - stage: our_custom_security_stage - variables: - SEARCH_MAX_DEPTH: 8 diff --git a/spec/support/shared_contexts/read_ci_configuration_shared_context.rb b/spec/support/shared_contexts/read_ci_configuration_shared_context.rb deleted file mode 100644 index f8f33e2a745..00000000000 --- a/spec/support/shared_contexts/read_ci_configuration_shared_context.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_context 'read ci configuration for sast enabled project' do - let_it_be(:gitlab_ci_yml_content) do - File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_for_sast.yml')) - end - - let_it_be(:project) { create(:project, :repository) } -end |