diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-17 06:13:50 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-17 06:13:50 +0300 |
commit | eac5de031737387f9e00be37ae429a426fe7d19d (patch) | |
tree | dee06fb1b4d18c362de32b41f30e77fd577154f7 | |
parent | e7ddd83a4484ff9fdf355d8eb726db9bfd3dd521 (diff) |
Add latest changes from gitlab-org/gitlab@master
20 files changed, 337 insertions, 166 deletions
diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue index ccfe773b01f..33ae747df22 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue @@ -29,8 +29,10 @@ import { EVENT_ACTION, EXPANDED_VARIABLES_NOTE, FLAG_LINK_TITLE, + MASKED_VALUE_MIN_LENGTH, VARIABLE_ACTIONS, variableOptions, + WHITESPACE_REG_EX, } from '../constants'; import CiEnvironmentsDropdown from './ci_environments_dropdown.vue'; import { awsTokenList } from './ci_variable_autocomplete_tokens'; @@ -54,21 +56,30 @@ export const i18n = { maskedDescription: s__( 'CiVariables|Variable will be masked in job logs. Requires values to meet regular expression requirements.', ), + maskedValueMinLengthValidationText: s__( + 'CiVariables|The value must have at least %{charsAmount} characters.', + ), modalDeleteMessage: s__('CiVariables|Do you want to delete the variable %{key}?'), protectedField: s__('CiVariables|Protect variable'), protectedDescription: s__( 'CiVariables|Export variable to pipelines running on protected branches and tags only.', ), + unsupportedCharsValidationText: s__( + 'CiVariables|This value cannot be masked because it contains the following characters: %{unsupportedChars}.', + ), + unsupportedAndWhitespaceCharsValidationText: s__( + 'CiVariables|This value cannot be masked because it contains the following characters: %{unsupportedChars} and whitespace characters.', + ), valueFeedback: { rawHelpText: s__('CiVariables|Variable value will be evaluated as raw string.'), - maskedReqsNotMet: s__( - 'CiVariables|This variable value does not meet the masking requirements.', - ), }, variableReferenceTitle: s__('CiVariables|Value might contain a variable reference'), variableReferenceDescription: s__( 'CiVariables|Unselect "Expand variable reference" if you want to use the variable value as a raw string.', ), + whitespaceCharsValidationText: s__( + 'CiVariables|This value cannot be masked because it contains the following characters: whitespace characters.', + ), type: __('Type'), value: __('Value'), }; @@ -169,11 +180,76 @@ export default { isEditing() { return this.mode === EDIT_VARIABLE_ACTION; }, + isMaskedValueContainsWhitespaceChars() { + return this.isValueMaskable && WHITESPACE_REG_EX.test(this.variable.value); + }, maskedRegexToUse() { return this.variable.raw ? this.maskableRawRegex : this.maskableRegex; }, - maskedReqsNotMetText() { - return !this.isMaskedReqsMet ? this.$options.i18n.valueFeedback.maskedReqsNotMet : ''; + maskedSupportedCharsRegEx() { + const supportedChars = this.maskedRegexToUse.replace('^', '').replace(/{(\d,)}\$/, ''); + return new RegExp(supportedChars, 'g'); + }, + maskedValueMinLengthValidationText() { + return sprintf(this.$options.i18n.maskedValueMinLengthValidationText, { + charsAmount: MASKED_VALUE_MIN_LENGTH, + }); + }, + unsupportedCharsList() { + if (this.isMaskedReqsMet) { + return []; + } + + return [ + ...new Set( + this.variable.value + .replace(WHITESPACE_REG_EX, '') + .replace(this.maskedSupportedCharsRegEx, '') + .split(''), + ), + ]; + }, + unsupportedChars() { + return this.unsupportedCharsList.join(', '); + }, + unsupportedCharsValidationText() { + return sprintf( + this.$options.i18n.unsupportedCharsValidationText, + { + unsupportedChars: this.unsupportedChars, + }, + false, + ); + }, + unsupportedAndWhitespaceCharsValidationText() { + return sprintf( + this.$options.i18n.unsupportedAndWhitespaceCharsValidationText, + { + unsupportedChars: this.unsupportedChars, + }, + false, + ); + }, + maskedValidationIssuesText() { + if (this.isMaskedReqsMet) { + return ''; + } + + let validationIssuesText = ''; + + if (this.unsupportedCharsList.length && !this.isMaskedValueContainsWhitespaceChars) { + validationIssuesText = this.unsupportedCharsValidationText; + } else if (this.unsupportedCharsList.length && this.isMaskedValueContainsWhitespaceChars) { + validationIssuesText = this.unsupportedAndWhitespaceCharsValidationText; + } else if (!this.unsupportedCharsList.length && this.isMaskedValueContainsWhitespaceChars) { + validationIssuesText = this.$options.i18n.whitespaceCharsValidationText; + } + + if (this.variable.value.length < MASKED_VALUE_MIN_LENGTH) { + validationIssuesText += ` ${this.maskedValueMinLengthValidationText}`; + } + + return validationIssuesText.trim(); }, modalActionText() { return this.isEditing ? this.$options.i18n.editVariable : this.$options.i18n.addVariable; @@ -218,9 +294,7 @@ export default { let property; if (this.isValueMaskable) { - const supportedChars = this.maskedRegexToUse.replace('^', '').replace(/{(\d,)}\$/, ''); - const regex = new RegExp(supportedChars, 'g'); - property = this.variable.value.replace(regex, ''); + property = this.variable.value.replace(this.maskedSupportedCharsRegEx, ''); } else if (this.hasVariableReference) { property = '$'; } @@ -382,7 +456,7 @@ export default { label-for="ci-variable-value" class="gl-border-none gl-mb-n2" data-testid="ci-variable-value-label" - :invalid-feedback="maskedReqsNotMetText" + :invalid-feedback="maskedValidationIssuesText" :state="isValueValid" > <gl-form-textarea diff --git a/app/assets/javascripts/ci/ci_variable_list/constants.js b/app/assets/javascripts/ci/ci_variable_list/constants.js index d85827b8220..4ec7333f465 100644 --- a/app/assets/javascripts/ci/ci_variable_list/constants.js +++ b/app/assets/javascripts/ci/ci_variable_list/constants.js @@ -2,6 +2,10 @@ import { __, s__, sprintf } from '~/locale'; export const ENVIRONMENT_QUERY_LIMIT = 30; +export const MASKED_VALUE_MIN_LENGTH = 8; + +export const WHITESPACE_REG_EX = /\s/; + export const SORT_DIRECTIONS = { ASC: 'KEY_ASC', DESC: 'KEY_DESC', diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml index ca1fef6eb32..ee1516facbc 100644 --- a/app/views/projects/_new_project_fields.html.haml +++ b/app/views/projects/_new_project_fields.html.haml @@ -25,7 +25,7 @@ input_name: 'project[namespace_id]', root_url: root_url, track_label: track_label, - user_namespace_id: current_user.namespace.id } } + user_namespace_id: current_user.namespace_id } } - else .input-group-prepend.static-namespace.flex-shrink-0.has-tooltip{ title: user_url(current_user.username) + '/' } .input-group-text.border-0 diff --git a/config/feature_flags/development/import_fallback_to_db_empty_cache.yml b/config/feature_flags/development/use_500_page_size_for_contribution_analytics.yml index d97adc841fc..a52dacbe229 100644 --- a/config/feature_flags/development/import_fallback_to_db_empty_cache.yml +++ b/config/feature_flags/development/use_500_page_size_for_contribution_analytics.yml @@ -1,8 +1,8 @@ --- -name: import_fallback_to_db_empty_cache -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133914 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/428700 -milestone: '16.6' +name: use_500_page_size_for_contribution_analytics +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136724 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/431595 +milestone: '16.7' type: development -group: group::import and integrate +group: group::optimize default_enabled: false diff --git a/doc/development/database/not_null_constraints.md b/doc/development/database/not_null_constraints.md index b6e8786b9f3..7ffc1fba1a0 100644 --- a/doc/development/database/not_null_constraints.md +++ b/doc/development/database/not_null_constraints.md @@ -204,11 +204,118 @@ If you have to clean up a nullable column for a [high-traffic table](../migratio it needs an additional [batched background migration cleaning up](batched_background_migrations.md#cleaning-up-a-batched-background-migration) in the release after adding the data migration. -In that rare case you need 3 releases end-to-end: - -1. Release `N.M` - Add the `NOT NULL` constraint and the background-migration to fix the existing records. -1. Release `N.M+1` - Cleanup the background migration. -1. Release `N.M+2` - Validate the `NOT NULL` constraint. +In this case the number of releases depends on the amount of time needed to migrate existing records. The cleanup is +scheduled after the background migration has completed, which could be several releases after the constraint was added. + +1. Release `N.M`: + - Add the `NOT NULL` constraint without validating it: + + ```ruby + # db/post_migrate/ + class AddMergeRequestDiffsProjectIdNotNullConstraint < Gitlab::Database::Migration[2.2] + disable_ddl_transaction! + milestone '16.7' + + def up + add_not_null_constraint :merge_request_diffs, :project_id, validate: false + end + + def down + remove_not_null_constraint :merge_request_diffs, :project_id + end + end + ``` + + - Add the background-migration to fix the existing records: + + ```ruby + # db/post_migrate/ + class QueueBackfillMergeRequestDiffsProjectId < Gitlab::Database::Migration[2.2] + milestone '16.7' + restrict_gitlab_migration gitlab_schema: :gitlab_main + + MIGRATION = 'BackfillMergeRequestDiffsProjectId' + DELAY_INTERVAL = 2.minutes + + def up + queue_batched_background_migration( + MIGRATION, + :merge_request_diffs, + :id, + job_interval: DELAY_INTERVAL, + queued_migration_version: '20231114043522' + ) + end + + def down + delete_batched_background_migration(MIGRATION, :merge_request_diffs, :id, []) + end + end + ``` + +1. Release `N.M+X`, where `X` is the number of releases the migration was running: + - Cleanup the background migration: + + ```ruby + # db/post_migrate/ + class FinalizeMergeRequestDiffsProjectIdBackfill < Gitlab::Database::Migration[2.2] + disable_ddl_transaction! + milestone '16.10' + restrict_gitlab_migration gitlab_schema: :gitlab_main + + MIGRATION = 'BackfillMergeRequestDiffsProjectId' + + def up + ensure_batched_background_migration_is_finished( + job_class_name: MIGRATION, + table_name: :merge_request_diffs, + column_name: :id, + job_arguments: [], + finalize: true + ) + end + + def down + # no-op + end + end + ``` + + - **Optional.** For very large tables, schedule asynchronous validation of the `NOT NULL` constraint: + + ```ruby + # db/post_migrate/ + class PrepareMergeRequestDiffsProjectIdNotNullValidation < Gitlab::Database::Migration[2.2] + milestone '16.10' + + CONSTRAINT_NAME = 'check_11c5f029ad' + + def up + prepare_async_check_constraint_validation :merge_request_diffs, name: CONSTRAINT_NAME + end + + def down + unprepare_async_check_constraint_validation :merge_request_diffs, name: CONSTRAINT_NAME + end + end + ``` + +1. Validate the `NOT NULL` constraint (if the constraint was validated asynchronously, wait for this validation to finish): + + ```ruby + # db/post_migrate/ + class ValidateMergeRequestDiffsProjectIdNullConstraint < Gitlab::Database::Migration[2.2] + milestone '16.10' + + def up + validate_not_null_constraint :merge_request_diffs, :project_id + end + + def down + # no-op + end + end + ``` For these cases, consult the database team early in the update cycle. The `NOT NULL` constraint may not be required or other options could exist that do not affect really large diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md index 0e67ee0eea8..6b2beb17880 100644 --- a/doc/development/i18n/externalization.md +++ b/doc/development/i18n/externalization.md @@ -258,6 +258,11 @@ expect(wrapper.text()).toBe('There was an error: Please refresh and hope for the For more details you can see how we [keep translations dynamic](#keep-translations-dynamic). +## Making changes to translated strings + +If you change the source strings in GitLab, you must [update the `pot` file](#updating-the-po-files-with-the-new-content) before pushing your changes. +If the `pot` file is out of date, pre-push checks and a pipeline job for `gettext` fail. + ## Working with special content ### Interpolation diff --git a/doc/development/i18n/index.md b/doc/development/i18n/index.md index 0fb4412a98a..9e39d5554ab 100644 --- a/doc/development/i18n/index.md +++ b/doc/development/i18n/index.md @@ -29,6 +29,10 @@ strings. See [Externalization for GitLab](externalization.md). +### Editing externalized strings + +If you edit externalized strings in GitLab, you must [update the `pot` file](externalization.md#updating-the-po-files-with-the-new-content) before pushing your changes. + ## Translate strings The translation process is managed at [https://crowdin.com/project/gitlab-ee](https://crowdin.com/project/gitlab-ee) diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md index 25fa1f5cbaf..cf8d9637d73 100644 --- a/doc/user/application_security/index.md +++ b/doc/user/application_security/index.md @@ -270,7 +270,9 @@ In the Free tier, the reports above aren't parsed by GitLab. As a result, the wi A merge request contains a security widget which displays a summary of the _new_ results. New results are determined by comparing the findings of the merge request against the findings of the most recent completed pipeline (`success`, `failed`, `canceled` or `skipped`) for the commit when the feature branch was created from the target branch. -GitLab checks the last 10 pipelines for the commit when the feature was created from the target branch to find one with security reports to use in comparison logic. If security scans have not run for the last 10 completed pipelines in the target branch when the feature branch was created, there is no base for comparison. The vulnerabilities from the merge request findings are listed as new in the merge request security widget. We recommend you run a scan of the `default` (target) branch before enabling feature branch scans for your developers. +GitLab checks the last 10 pipelines for the commit when the feature branch was created from the target branch to find one with security reports to use in comparison logic. If security scans have not run for the last 10 completed pipelines in the target branch when the feature branch was created, there is no base for comparison. The vulnerabilities from the merge request findings are listed as _new_ in the merge request security widget. We recommend you run a scan of the `default` (target) branch before enabling feature branch scans for your developers. + +The MR security widget considers all supported pipeline sources (based on the [`CI_PIPELINE_SOURCE` variable](../../ci/variables/predefined_variables.md)) when comparing results from both the source and target branches when determining if a merge request requires approval. Pipeline sources `webide` and `parent_pipeline` are not supported. The merge request security widget displays only a subset of the vulnerabilities in the generated JSON artifact because it contains both new and existing findings. diff --git a/doc/user/application_security/policies/scan-execution-policies.md b/doc/user/application_security/policies/scan-execution-policies.md index f6ef8a2c49e..f03b0633096 100644 --- a/doc/user/application_security/policies/scan-execution-policies.md +++ b/doc/user/application_security/policies/scan-execution-policies.md @@ -224,10 +224,9 @@ Note the following: policy project's repository must have access to the scanner and site profiles. Otherwise, the scan is not scheduled successfully. - For a secret detection scan, only rules with the default ruleset are supported. [Custom rulesets](../secret_detection/index.md#custom-rulesets) - are not supported. -- A secret detection scan runs in `default` mode when executed as part of a pipeline, and in - [`historic`](../secret_detection/index.md#full-history-secret-detection) - mode when executed as part of a scheduled scan. + are not supported. Alternatively, you may configure a [remote configuration file](../secret_detection/index.md#specify-a-remote-configuration-file) and set the `SECRET_DETECTION_RULESET_GIT_REFERENCE` variable. +- By default, for `scheduled` scan execution policies, secret detection scans configured without any CI variables defined run first in `historic` mode (`SECRET_DETECTION_HISTORIC_SCAN` = `true`). All subsequent scheduled scans run in default mode with `SECRET_DETECTION_LOG_OPTIONS` set to the commit range between last run and current SHA. CI variables provided in the scan execution policy can override this behavior. Learn more about [historic mode](../secret_detection/index.md#full-history-secret-detection). +- For `triggered` scan execution policies, secret detection works just like regular scan [configured manually in the `.gitlab-ci.yml`](../secret_detection/index.md#edit-the-gitlab-ciyml-file-manually). - A container scanning scan that is configured for the `pipeline` rule type ignores the agent defined in the `agents` object. The `agents` object is only considered for `schedule` rule types. An agent with a name provided in the `agents` object must be created and configured for the project. - Variables defined in a Scan Execution Policy follow the standard [CI/CD variable precedence](../../../ci/variables/index.md#cicd-variable-precedence). diff --git a/lib/gitlab/github_import/issuable_finder.rb b/lib/gitlab/github_import/issuable_finder.rb index 0780ba6119f..5ce50e5b4e7 100644 --- a/lib/gitlab/github_import/issuable_finder.rb +++ b/lib/gitlab/github_import/issuable_finder.rb @@ -26,8 +26,6 @@ module Gitlab def database_id val = Gitlab::Cache::Import::Caching.read_integer(cache_key, timeout: timeout) - return val if Feature.disabled?(:import_fallback_to_db_empty_cache, project) - return if val == CACHE_OBJECT_NOT_FOUND return val if val.present? diff --git a/lib/gitlab/github_import/label_finder.rb b/lib/gitlab/github_import/label_finder.rb index d0bbd2bc7cf..87d3195eb93 100644 --- a/lib/gitlab/github_import/label_finder.rb +++ b/lib/gitlab/github_import/label_finder.rb @@ -19,8 +19,6 @@ module Gitlab cache_key = cache_key_for(name) val = Gitlab::Cache::Import::Caching.read_integer(cache_key) - return val if Feature.disabled?(:import_fallback_to_db_empty_cache, project) - return if val == CACHE_OBJECT_NOT_FOUND return val if val.present? diff --git a/lib/gitlab/github_import/milestone_finder.rb b/lib/gitlab/github_import/milestone_finder.rb index dcb679fda6d..fd60fa86e82 100644 --- a/lib/gitlab/github_import/milestone_finder.rb +++ b/lib/gitlab/github_import/milestone_finder.rb @@ -24,8 +24,6 @@ module Gitlab val = Gitlab::Cache::Import::Caching.read_integer(cache_key) - return val if Feature.disabled?(:import_fallback_to_db_empty_cache, project) - return if val == CACHE_OBJECT_NOT_FOUND return val if val.present? diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 48159e6a13b..e39cf624d93 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10561,13 +10561,22 @@ msgstr "" msgid "CiVariables|State" msgstr "" +msgid "CiVariables|The value must have at least %{charsAmount} characters." +msgstr "" + msgid "CiVariables|There was an error fetching the inherited CI variables." msgstr "" msgid "CiVariables|This %{entity} has %{currentVariableCount} defined CI/CD variables. The maximum number of variables per %{entity} is %{maxVariableLimit}. To add new variables, you must reduce the number of defined variables." msgstr "" -msgid "CiVariables|This variable value does not meet the masking requirements." +msgid "CiVariables|This value cannot be masked because it contains the following characters: %{unsupportedChars} and whitespace characters." +msgstr "" + +msgid "CiVariables|This value cannot be masked because it contains the following characters: %{unsupportedChars}." +msgstr "" + +msgid "CiVariables|This value cannot be masked because it contains the following characters: whitespace characters." msgstr "" msgid "CiVariables|Type" diff --git a/package.json b/package.json index e479d528aca..ec0af3047d7 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@gitlab/svgs": "3.71.0", "@gitlab/ui": "^68.5.0", "@gitlab/visual-review-tools": "1.7.3", - "@gitlab/web-ide": "0.0.1-dev-20231004090414", + "@gitlab/web-ide": "^0.0.1-dev-20231116214726", "@mattiasbuelens/web-streams-adapter": "^0.1.0", "@rails/actioncable": "7.0.8", "@rails/ujs": "7.0.8", diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_drawer_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_drawer_spec.js index 610aae3946f..113e946908a 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_variable_drawer_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_drawer_spec.js @@ -20,6 +20,8 @@ describe('CI Variable Drawer', () => { let wrapper; let trackingSpy; + const itif = (condition) => (condition ? it : it.skip); + const mockProjectVariable = mockVariablesWithScopes(projectString)[0]; const mockProjectVariableFileType = mockVariablesWithScopes(projectString)[1]; const mockEnvScope = 'staging'; @@ -284,52 +286,106 @@ describe('CI Variable Drawer', () => { expect(findConfirmBtn().attributes('disabled')).toBeUndefined(); }); - describe.each` - value | canSubmit | trackingErrorProperty - ${'secretValue'} | ${true} | ${null} - ${'~v@lid:symbols.'} | ${true} | ${null} - ${'short'} | ${false} | ${null} - ${'multiline\nvalue'} | ${false} | ${'\n'} - ${'dollar$ign'} | ${false} | ${'$'} - ${'unsupported|char'} | ${false} | ${'|'} - `('masking requirements', ({ value, canSubmit, trackingErrorProperty }) => { - beforeEach(async () => { - createComponent(); - - trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - await findKeyField().vm.$emit('input', 'NEW_VARIABLE'); - await findValueField().vm.$emit('input', value); - await findMaskedCheckbox().vm.$emit('input', true); - }); + const invalidValues = { + short: 'short', + multiLine: 'multiline\nvalue', + unsupportedChar: 'unsupported|char', + twoUnsupportedChars: 'unsupported|chars!', + threeUnsupportedChars: '%unsupported|chars!', + shortAndMultiLine: 'sho\nrt', + shortAndUnsupportedChar: 'short!', + shortAndMultiLineAndUnsupportedChar: 'short\n!', + multiLineAndUnsupportedChar: 'multiline\nvalue!', + }; + const maskedValidationIssuesText = { + short: 'The value must have at least 8 characters.', + multiLine: + 'This value cannot be masked because it contains the following characters: whitespace characters.', + unsupportedChar: + 'This value cannot be masked because it contains the following characters: |.', + unsupportedDollarChar: + 'This value cannot be masked because it contains the following characters: $.', + twoUnsupportedChars: + 'This value cannot be masked because it contains the following characters: |, !.', + threeUnsupportedChars: + 'This value cannot be masked because it contains the following characters: %, |, !.', + shortAndMultiLine: + 'This value cannot be masked because it contains the following characters: whitespace characters. The value must have at least 8 characters.', + shortAndUnsupportedChar: + 'This value cannot be masked because it contains the following characters: !. The value must have at least 8 characters.', + shortAndMultiLineAndUnsupportedChar: + 'This value cannot be masked because it contains the following characters: ! and whitespace characters. The value must have at least 8 characters.', + multiLineAndUnsupportedChar: + 'This value cannot be masked because it contains the following characters: ! and whitespace characters.', + }; - it(`${ - canSubmit ? 'can submit' : 'shows validation errors and disables submit button' - } when value is '${value}'`, () => { - if (canSubmit) { + describe.each` + value | canSubmit | trackingErrorProperty | validationIssueKey + ${'secretValue'} | ${true} | ${null} | ${''} + ${'~v@lid:symbols.'} | ${true} | ${null} | ${''} + ${invalidValues.short} | ${false} | ${null} | ${'short'} + ${invalidValues.multiLine} | ${false} | ${'\n'} | ${'multiLine'} + ${'dollar$ign'} | ${false} | ${'$'} | ${'unsupportedDollarChar'} + ${invalidValues.unsupportedChar} | ${false} | ${'|'} | ${'unsupportedChar'} + ${invalidValues.twoUnsupportedChars} | ${false} | ${'|!'} | ${'twoUnsupportedChars'} + ${invalidValues.threeUnsupportedChars} | ${false} | ${'%|!'} | ${'threeUnsupportedChars'} + ${invalidValues.shortAndMultiLine} | ${false} | ${'\n'} | ${'shortAndMultiLine'} + ${invalidValues.shortAndUnsupportedChar} | ${false} | ${'!'} | ${'shortAndUnsupportedChar'} + ${invalidValues.shortAndMultiLineAndUnsupportedChar} | ${false} | ${'\n!'} | ${'shortAndMultiLineAndUnsupportedChar'} + ${invalidValues.multiLineAndUnsupportedChar} | ${false} | ${'\n!'} | ${'multiLineAndUnsupportedChar'} + `( + 'masking requirements', + ({ value, canSubmit, trackingErrorProperty, validationIssueKey }) => { + beforeEach(() => { + createComponent(); + + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + findKeyField().vm.$emit('input', 'NEW_VARIABLE'); + findValueField().vm.$emit('input', value); + findMaskedCheckbox().vm.$emit('input', true); + }); + + itif(canSubmit)(`can submit when value is ${value}`, () => { + /* eslint-disable jest/no-standalone-expect */ expect(findValueLabel().attributes('invalid-feedback')).toBe(''); expect(findConfirmBtn().attributes('disabled')).toBeUndefined(); - } else { - expect(findValueLabel().attributes('invalid-feedback')).toBe( - 'This variable value does not meet the masking requirements.', - ); - expect(findConfirmBtn().attributes('disabled')).toBeDefined(); - } - }); + /* eslint-enable jest/no-standalone-expect */ + }); + + itif(!canSubmit)( + `shows validation errors and disables submit button when value is ${value}`, + () => { + const validationIssueText = maskedValidationIssuesText[validationIssueKey] || ''; + + /* eslint-disable jest/no-standalone-expect */ + expect(findValueLabel().attributes('invalid-feedback')).toBe(validationIssueText); + expect(findConfirmBtn().attributes('disabled')).toBeDefined(); + /* eslint-enable jest/no-standalone-expect */ + }, + ); + + itif(trackingErrorProperty)( + `sends the correct variable validation tracking event when value is ${value}`, + () => { + /* eslint-disable jest/no-standalone-expect */ + expect(trackingSpy).toHaveBeenCalledTimes(1); + expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTION, { + label: DRAWER_EVENT_LABEL, + property: trackingErrorProperty, + }); + /* eslint-enable jest/no-standalone-expect */ + }, + ); - it(`${ - trackingErrorProperty ? 'sends the correct' : 'does not send the' - } variable validation tracking event when value is '${value}'`, () => { - const trackingEventSent = trackingErrorProperty ? 1 : 0; - expect(trackingSpy).toHaveBeenCalledTimes(trackingEventSent); - - if (trackingErrorProperty) { - expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTION, { - label: DRAWER_EVENT_LABEL, - property: trackingErrorProperty, - }); - } - }); - }); + itif(!trackingErrorProperty)( + `does not send the the correct variable validation tracking event when value is ${value}`, + () => { + // eslint-disable-next-line jest/no-standalone-expect + expect(trackingSpy).toHaveBeenCalledTimes(0); + }, + ); + }, + ); it('only sends the tracking event once', async () => { trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); diff --git a/spec/lib/gitlab/github_import/issuable_finder_spec.rb b/spec/lib/gitlab/github_import/issuable_finder_spec.rb index 977fef95d64..3fe07923a50 100644 --- a/spec/lib/gitlab/github_import/issuable_finder_spec.rb +++ b/spec/lib/gitlab/github_import/issuable_finder_spec.rb @@ -48,34 +48,6 @@ RSpec.describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache, expect { finder.database_id }.to raise_error(TypeError) end - context 'with FF import_fallback_to_db_empty_cache disabled' do - before do - stub_feature_flags(import_fallback_to_db_empty_cache: false) - end - - it 'returns nil if object does not exist' do - missing_issue = double(:issue, issuable_type: 'MergeRequest', issuable_id: 999) - - expect(described_class.new(project, missing_issue).database_id).to be_nil - end - - it 'does not fetch object id from database if not in cache' do - expect(finder.database_id).to eq(nil) - end - - it 'fetches object id from cache if present' do - finder.cache_database_id(10) - - expect(finder.database_id).to eq(10) - end - - it 'returns -1 if cache is -1' do - finder.cache_database_id(-1) - - expect(finder.database_id).to eq(-1) - end - end - context 'when group is present' do context 'when settings single_endpoint_notes_import is enabled' do let(:single_endpoint_optional_stage) { true } diff --git a/spec/lib/gitlab/github_import/label_finder_spec.rb b/spec/lib/gitlab/github_import/label_finder_spec.rb index e46595974d1..4c01e2b65da 100644 --- a/spec/lib/gitlab/github_import/label_finder_spec.rb +++ b/spec/lib/gitlab/github_import/label_finder_spec.rb @@ -49,34 +49,6 @@ RSpec.describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache, fea expect(finder.id_for(feature.name)).to eq(feature.id) end end - - context 'with FF import_fallback_to_db_empty_cache disabled' do - before do - stub_feature_flags(import_fallback_to_db_empty_cache: false) - end - - it 'returns nil for a non existing label name' do - expect(finder.id_for('kittens')).to be_nil - end - - it 'does not fetch object id from database if not in cache' do - expect(finder.id_for(feature.name)).to be_nil - end - - it 'fetches object id from cache if present' do - finder.build_cache - - expect(finder.id_for(feature.name)).to eq(feature.id) - end - - it 'returns -1 if cache is -1' do - key = finder.cache_key_for(bug.name) - - Gitlab::Cache::Import::Caching.write(key, -1) - - expect(finder.id_for(bug.name)).to eq(-1) - end - end end describe '#build_cache' do diff --git a/spec/lib/gitlab/github_import/milestone_finder_spec.rb b/spec/lib/gitlab/github_import/milestone_finder_spec.rb index 62886981de1..91f1c3b8cb9 100644 --- a/spec/lib/gitlab/github_import/milestone_finder_spec.rb +++ b/spec/lib/gitlab/github_import/milestone_finder_spec.rb @@ -57,36 +57,6 @@ RSpec.describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache, expect(finder.id_for(issuable)).to eq(milestone.id) end end - - context 'with FF import_fallback_to_db_empty_cache disabled' do - before do - stub_feature_flags(import_fallback_to_db_empty_cache: false) - end - - it 'returns nil if object does not exist' do - missing_issuable = double(:issuable, milestone_number: 999) - - expect(finder.id_for(missing_issuable)).to be_nil - end - - it 'does not fetch object id from database if not in cache' do - expect(finder.id_for(issuable)).to be_nil - end - - it 'fetches object id from cache if present' do - finder.build_cache - - expect(finder.id_for(issuable)).to eq(milestone.id) - end - - it 'returns -1 if cache is -1' do - key = finder.cache_key_for(milestone.iid) - - Gitlab::Cache::Import::Caching.write(key, -1) - - expect(finder.id_for(issuable)).to eq(-1) - end - end end describe '#build_cache' do diff --git a/spec/support/shared_examples/features/variable_list_drawer_shared_examples.rb b/spec/support/shared_examples/features/variable_list_drawer_shared_examples.rb index b438a23aafd..04e73cfeee7 100644 --- a/spec/support/shared_examples/features/variable_list_drawer_shared_examples.rb +++ b/spec/support/shared_examples/features/variable_list_drawer_shared_examples.rb @@ -140,7 +140,10 @@ RSpec.shared_examples 'variable list drawer' do toggle_masked fill_variable('EMPTY_MASK_KEY', '???') - expect(page).to have_content('This variable value does not meet the masking requirements.') + # rubocop:disable Layout/LineLength -- error message is too long + expect(page).to have_content('This value cannot be masked because it contains the following characters: ?. The value must have at least 8 characters.') + # rubocop:enable Layout/LineLength + page.within('[data-testid="ci-variable-drawer"]') do expect(find_button('Add variable', disabled: true)).to be_present end diff --git a/yarn.lock b/yarn.lock index ba3dd5db54b..298d39fc322 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1292,10 +1292,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.7.3.tgz#9ea641146436da388ffbad25d7f2abe0df52c235" integrity sha512-NMV++7Ew1FSBDN1xiZaauU9tfeSfgDHcOLpn+8bGpP+O5orUPm2Eu66R5eC5gkjBPaXosNAxNWtriee+aFk4+g== -"@gitlab/web-ide@0.0.1-dev-20231004090414": - version "0.0.1-dev-20231004090414" - resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20231004090414.tgz#925c9e942917acbbb17c4ea77b0841649ae186ad" - integrity sha512-NfkRzvz5Z3uDceQC5QN8W5u1ETlHbbEXjrnKxiYM4TD1d+bOV567GYglRKjyOhQMTYRGpqbisLSAPgvShAQB6g== +"@gitlab/web-ide@^0.0.1-dev-20231116214726": + version "0.0.1-dev-20231116214726" + resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20231116214726.tgz#3b80e3de4dce8d07e6acd913d287de0c772b4bb3" + integrity sha512-sc/LVSKqcSyKQ5rQlgMfvAMxiJvOkhAI5ZJqA8pHqnKK/sAx7C66lBtYfkWGvj+Dw31ElkZgV/7tS2wQXLnwjA== "@graphql-eslint/eslint-plugin@3.20.1": version "3.20.1" |