diff options
68 files changed, 1095 insertions, 103 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 4e7539f83b2..46258bf6b7b 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -1465,6 +1465,11 @@ ee/lib/ee/api/entities/project.rb /lib/sidebars/ /ee/lib/sidebars/ +[Global Search] @gitlab-org/search-team/migration-maintainers +/ee/elastic/migrate +/ee/spec/elastic/migrate +/ee/spec/support/elastic.rb + # JiHu GitLab rules. See https://gitlab.com/gitlab-jh/gitlab-jh-enablement/-/issues/213#note_1024367528 [JH Frontend] @jihulab/maintainers/frontend @@ -172,7 +172,7 @@ gem 'seed-fu', '~> 2.3.7' gem 'elasticsearch-model', '~> 7.2' gem 'elasticsearch-rails', '~> 7.2', require: 'elasticsearch/rails/instrumentation' gem 'elasticsearch-api', '7.13.3' -gem 'aws-sdk-core', '~> 3.172.0' +gem 'aws-sdk-core', '~> 3.173.0' gem 'aws-sdk-cloudformation', '~> 1' gem 'aws-sdk-s3', '~> 1.122.0' gem 'faraday_middleware-aws-sigv4', '~>0.3.0' @@ -510,7 +510,7 @@ gem 'gitaly', '~> 15.9.0-rc3' # KAS GRPC protocol definitions gem 'kas-grpc', '~> 0.1.0' -gem 'grpc', '~> 1.42.0' +gem 'grpc', '~> 1.54.2' gem 'google-protobuf', '~> 3.23', '>= 3.23.1' diff --git a/Gemfile.checksum b/Gemfile.checksum index d039c336581..517bb29d8c0 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -37,7 +37,7 @@ {"name":"aws-eventstream","version":"1.2.0","platform":"ruby","checksum":"ffa53482c92880b001ff2fb06919b9bb82fd847cbb0fa244985d2ebb6dd0d1df"}, {"name":"aws-partitions","version":"1.761.0","platform":"ruby","checksum":"291e444e1edfc92c5521a6dbdd1236ccc3f122b3520163b2be6ec5b6ef350ef2"}, {"name":"aws-sdk-cloudformation","version":"1.41.0","platform":"ruby","checksum":"31e47539719734413671edf9b1a31f8673fbf9688549f50c41affabbcb1c6b26"}, -{"name":"aws-sdk-core","version":"3.172.0","platform":"ruby","checksum":"ddc03774a8957033ca2f1564349d5b273a4b615c2cb756407e1b3b8983754c1b"}, +{"name":"aws-sdk-core","version":"3.173.0","platform":"ruby","checksum":"e704c18580e64dcb1db3bb9757e2d609fa044f723077a4665783a749fe7ab281"}, {"name":"aws-sdk-kms","version":"1.64.0","platform":"ruby","checksum":"40de596c95047bfc6e1aacea24f3df6241aa716b6f7ce08ac4c5f7e3120395ad"}, {"name":"aws-sdk-s3","version":"1.122.0","platform":"ruby","checksum":"3d1fb6d0a6c55cf568ff66d657b4a6e7eaa718675f1f3156d7b44e3791f39905"}, {"name":"aws-sigv4","version":"1.5.1","platform":"ruby","checksum":"d68c87fff4ee843b4b92b23c7f31f957f254ec6eb064181f7119124aab8b8bb4"}, @@ -269,12 +269,13 @@ {"name":"graphql","version":"1.13.12","platform":"ruby","checksum":"1d82666cf201193a8d0cb54cea38576b820418db4869b549f61a35f3a2d97ac3"}, {"name":"graphql-client","version":"0.17.0","platform":"ruby","checksum":"5aaf02ce8f2dbc8e3ba05a7eaeb3ad9336762c4424c6093f4438fbb9490eeb5d"}, {"name":"graphql-docs","version":"2.1.0","platform":"ruby","checksum":"7eb82402f8fda455104b2b60364e9ada145d79d3121a8f915790d49da38bb576"}, -{"name":"grpc","version":"1.42.0","platform":"ruby","checksum":"b3d2649e67c6a636544996843d9ec191699c54c1aca797dbfea4dff36c14584a"}, -{"name":"grpc","version":"1.42.0","platform":"x64-mingw32","checksum":"6aac1b6576134b0a83e000b1269f60d502eb24aee96c64e2658c3f24f8e32ac0"}, -{"name":"grpc","version":"1.42.0","platform":"x86-linux","checksum":"4aa50538aa929f1f3bcefb11c65ee1a1606b5aef838ea4d4e93c100b5f4263a5"}, -{"name":"grpc","version":"1.42.0","platform":"x86-mingw32","checksum":"eeb2a9381bea43fafe879b6ddaa011351a44d0894d48bdc965a07bcb67c6eb56"}, -{"name":"grpc","version":"1.42.0","platform":"x86_64-darwin","checksum":"20fa202d46d8a055628260622e98fb6439529fbac283f0552af620b909f78535"}, -{"name":"grpc","version":"1.42.0","platform":"x86_64-linux","checksum":"92e2ceb2aca335d5755163dd8030082091d5b0e63c117b1ca07051b66c53eb2e"}, +{"name":"grpc","version":"1.54.2","platform":"ruby","checksum":"036f931bcf8d43e1b65eb1c1dc010e1ab24fb64ed7e8703948a8288eda61812f"}, +{"name":"grpc","version":"1.54.2","platform":"x64-mingw-ucrt","checksum":"adbfa4b6004d229ecd75d8f33e07be8dcdf185b30263724288a75154dc9a3637"}, +{"name":"grpc","version":"1.54.2","platform":"x64-mingw32","checksum":"5c1c1ff87bdf928c442ec4c7166fb8abd4fcce00d7ec6e60313f488919f5d4f5"}, +{"name":"grpc","version":"1.54.2","platform":"x86-linux","checksum":"e8cf13e91714f33bba0ad06a3f4b9d98f056d09d7300e92ccb499cc95cc5cb9d"}, +{"name":"grpc","version":"1.54.2","platform":"x86-mingw32","checksum":"7e28b9a2e60d1fea6a5a774e20978628b27220ac9fe0a2a9c9e9cf2874700de4"}, +{"name":"grpc","version":"1.54.2","platform":"x86_64-darwin","checksum":"57b9effbdc25c43ebd706c4e9575baf31fd975a52cde67d41d8d33a7264e508e"}, +{"name":"grpc","version":"1.54.2","platform":"x86_64-linux","checksum":"d8ca04d8741b7d646c0634292f68925e95e29cad9805bcf19e820dd67d3e4bbb"}, {"name":"gssapi","version":"1.3.1","platform":"ruby","checksum":"c51cf30842ee39bd93ce7fc33e20405ff8a04cda9dec6092071b61258284aee1"}, {"name":"guard","version":"2.16.2","platform":"ruby","checksum":"71ba7abaddecc8be91ab77bbaf78f767246603652ebbc7b976fda497ebdc8fbb"}, {"name":"guard-compat","version":"1.2.1","platform":"ruby","checksum":"3ad21ab0070107f92edfd82610b5cdc2fb8e368851e72362ada9703443d646fe"}, diff --git a/Gemfile.lock b/Gemfile.lock index 4ee7c59ba2d..3a22ab98c5b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -203,7 +203,7 @@ GEM aws-sdk-cloudformation (1.41.0) aws-sdk-core (~> 3, >= 3.99.0) aws-sigv4 (~> 1.1) - aws-sdk-core (3.172.0) + aws-sdk-core (3.173.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) @@ -750,8 +750,8 @@ GEM graphql (~> 1.12) html-pipeline (~> 2.9) sass (~> 3.4) - grpc (1.42.0) - google-protobuf (~> 3.18) + grpc (1.54.2) + google-protobuf (~> 3.21) googleapis-common-protos-types (~> 1.0) gssapi (1.3.1) ffi (>= 1.0.1) @@ -1669,7 +1669,7 @@ DEPENDENCIES autoprefixer-rails (= 10.2.5.1) awesome_print aws-sdk-cloudformation (~> 1) - aws-sdk-core (~> 3.172.0) + aws-sdk-core (~> 3.173.0) aws-sdk-s3 (~> 1.122.0) axe-core-rspec babosa (~> 1.0.4) @@ -1777,7 +1777,7 @@ DEPENDENCIES graphlyte (~> 1.0.0) graphql (~> 1.13.12) graphql-docs (~> 2.1.0) - grpc (~> 1.42.0) + grpc (~> 1.54.2) gssapi (~> 1.3.1) guard-rspec haml_lint (~> 0.40.0) diff --git a/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue b/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue index 022f5df9c96..c37251af0ab 100644 --- a/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue +++ b/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue @@ -12,7 +12,7 @@ import { GlFormTextarea, } from '@gitlab/ui'; import axios from '~/lib/utils/axios_utils'; -import { s__ } from '~/locale'; +import { s__, __ } from '~/locale'; import { createAlert, VARIANT_DANGER } from '~/alert'; import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; @@ -71,6 +71,7 @@ export default { addError: s__('BroadcastMessages|There was an error adding broadcast message.'), update: s__('BroadcastMessages|Update broadcast message'), updateError: s__('BroadcastMessages|There was an error updating broadcast message.'), + cancel: __('Cancel'), }, messageThemes: THEMES, messageTypes: TYPES, @@ -256,9 +257,13 @@ export default { :loading="loading" :disabled="messageBlank" data-testid="submit-button" + class="gl-mr-2" > {{ isAddForm ? $options.i18n.add : $options.i18n.update }} </gl-button> + <gl-button v-if="!isAddForm" :href="messagesPath" data-testid="cancel-button"> + {{ $options.i18n.cancel }} + </gl-button> </div> </gl-form> </template> diff --git a/app/assets/javascripts/integrations/constants.js b/app/assets/javascripts/integrations/constants.js index 6b5a828c009..259166c71b7 100644 --- a/app/assets/javascripts/integrations/constants.js +++ b/app/assets/javascripts/integrations/constants.js @@ -104,4 +104,25 @@ export const placeholderForType = { [INTEGRATION_TYPE_MATTERMOST]: __('my-channel'), }; +export const INTEGRATION_FORM_TYPE_JIRA = 'jira'; export const INTEGRATION_FORM_TYPE_SLACK = 'gitlab_slack_application'; + +export const jiraIntegrationAuthFields = { + AUTH_TYPE: 'jira_auth_type', + USERNAME: 'username', + PASSWORD: 'password', +}; +export const jiraAuthTypeFieldProps = [ + { + username: s__('JiraService|Email or username'), + password: s__('JiraService|API token or password'), + passwordHelp: s__( + 'JiraService|API token for Jira Cloud or password for Jira Data Center and Jira Server', + ), + nonEmptyPassword: s__('JiraService|New API token or password'), + }, + { + password: s__('JiraService|Jira personal access token'), + nonEmptyPassword: s__('JiraService|New Jira personal access token'), + }, +]; diff --git a/app/assets/javascripts/integrations/edit/components/dynamic_field.vue b/app/assets/javascripts/integrations/edit/components/dynamic_field.vue index 904e5639cac..0a29906d5aa 100644 --- a/app/assets/javascripts/integrations/edit/components/dynamic_field.vue +++ b/app/assets/javascripts/integrations/edit/components/dynamic_field.vue @@ -97,7 +97,7 @@ export default { return isEmpty(this.value) && this.required; }, options() { - return this.choices.map((choice) => { + return this.choices?.map((choice) => { return { value: choice[1], text: choice[0], diff --git a/app/assets/javascripts/integrations/edit/components/jira_auth_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_auth_fields.vue new file mode 100644 index 00000000000..30a39e48959 --- /dev/null +++ b/app/assets/javascripts/integrations/edit/components/jira_auth_fields.vue @@ -0,0 +1,152 @@ +<script> +import { mapGetters } from 'vuex'; +import { isEmpty } from 'lodash'; +import { GlFormGroup, GlFormRadio, GlFormRadioGroup } from '@gitlab/ui'; +import { __, s__ } from '~/locale'; + +import { jiraIntegrationAuthFields, jiraAuthTypeFieldProps } from '~/integrations/constants'; +import DynamicField from './dynamic_field.vue'; + +const authTypeOptions = [ + { + value: 0, + text: s__('JiraService|Basic'), + }, + { + value: 1, + text: s__('JiraService|Jira personal access token'), + help: s__('JiraService|Recommended. Only available for Jira Data Center and Jira Server.'), + }, +]; + +export default { + name: 'JiraAuthFields', + + components: { + GlFormGroup, + GlFormRadio, + GlFormRadioGroup, + DynamicField, + }, + + props: { + isValidated: { + type: Boolean, + required: false, + default: false, + }, + + fields: { + type: Array, + required: false, + default: () => [], + }, + }, + + data() { + return { + authType: 0, + }; + }, + + computed: { + ...mapGetters(['currentKey', 'isInheriting']), + + isAuthTypeBasic() { + return this.authType === 0; + }, + + isNonEmptyPassword() { + return !isEmpty(this.passwordField?.value); + }, + + authTypeProps() { + return jiraAuthTypeFieldProps[this.authType]; + }, + + authTypeField() { + return this.findFieldByName(jiraIntegrationAuthFields.AUTH_TYPE); + }, + + usernameField() { + return this.findFieldByName(jiraIntegrationAuthFields.USERNAME); + }, + + passwordField() { + return this.findFieldByName(jiraIntegrationAuthFields.PASSWORD); + }, + + usernameProps() { + return { + ...this.usernameField, + ...(this.isAuthTypeBasic ? { required: true } : {}), + title: this.authTypeProps.username, + }; + }, + + passwordProps() { + const extraProps = this.isNonEmptyPassword + ? { title: this.authTypeProps.nonEmptyPassword } + : { title: this.authTypeProps.password, help: this.authTypeProps.passwordHelp }; + + return { + ...this.passwordField, + ...extraProps, + }; + }, + }, + + mounted() { + const authTypeValue = this.authTypeField?.value; + if (authTypeValue) { + this.authType = parseInt(authTypeValue, 10); + } + }, + + methods: { + findFieldByName(name) { + return this.fields.find((field) => field.name === name); + }, + }, + + authTypeOptions, + + i18n: { + authTypeLabel: __('Authentication method'), + }, +}; +</script> + +<template> + <gl-form-group :label="$options.i18n.authTypeLabel" label-for="service[jira_auth_type]"> + <input name="service[jira_auth_type]" type="hidden" :value="authType" /> + <gl-form-radio-group v-model="authType" :disabled="isInheriting"> + <gl-form-radio + v-for="option in $options.authTypeOptions" + :key="option.value" + :value="option.value" + > + <template v-if="option.help" #help> + {{ option.help }} + </template> + {{ option.text }} + </gl-form-radio> + </gl-form-radio-group> + + <div class="gl-ml-6 gl-mt-3"> + <dynamic-field + v-if="isAuthTypeBasic" + :key="`${currentKey}-${usernameProps.name}`" + data-testid="jira-auth-username" + v-bind="usernameProps" + :is-validated="isValidated" + /> + <dynamic-field + :key="`${currentKey}-${passwordProps.name}`" + data-testid="jira-auth-password" + v-bind="passwordProps" + :is-validated="isValidated" + /> + </div> + </gl-form-group> +</template> diff --git a/app/assets/javascripts/integrations/edit/components/sections/connection.vue b/app/assets/javascripts/integrations/edit/components/sections/connection.vue index 364e9324e43..6237f7983a6 100644 --- a/app/assets/javascripts/integrations/edit/components/sections/connection.vue +++ b/app/assets/javascripts/integrations/edit/components/sections/connection.vue @@ -1,5 +1,6 @@ <script> import { mapGetters } from 'vuex'; +import { INTEGRATION_FORM_TYPE_JIRA, jiraIntegrationAuthFields } from '~/integrations/constants'; import ActiveCheckbox from '../active_checkbox.vue'; import DynamicField from '../dynamic_field.vue'; @@ -9,6 +10,10 @@ export default { components: { ActiveCheckbox, DynamicField, + JiraAuthFields: () => + import( + /* webpackChunkName: 'integrationJiraAuthFields' */ '~/integrations/edit/components/jira_auth_fields.vue' + ), }, props: { fields: { @@ -24,6 +29,29 @@ export default { }, computed: { ...mapGetters(['currentKey', 'propsSource']), + + isJiraIntegration() { + return this.propsSource.type === INTEGRATION_FORM_TYPE_JIRA; + }, + + filteredFields() { + if (!this.isJiraIntegration) { + return this.fields; + } + + return this.fields.filter( + (field) => !Object.values(jiraIntegrationAuthFields).includes(field.name), + ); + }, + jiraAuthFields() { + if (!this.isJiraIntegration) { + return []; + } + + return this.fields.filter((field) => + Object.values(jiraIntegrationAuthFields).includes(field.name), + ); + }, }, }; </script> @@ -36,10 +64,16 @@ export default { @toggle-integration-active="$emit('toggle-integration-active', $event)" /> <dynamic-field - v-for="field in fields" + v-for="field in filteredFields" :key="`${currentKey}-${field.name}`" v-bind="field" :is-validated="isValidated" /> + <jira-auth-fields + v-if="isJiraIntegration" + :key="`${currentKey}-jira-auth-fields`" + :is-validated="isValidated" + :fields="jiraAuthFields" + /> </div> </template> diff --git a/app/assets/javascripts/monitoring/components/dashboard_header.vue b/app/assets/javascripts/monitoring/components/dashboard_header.vue index 44dde454983..f4dc29f2184 100644 --- a/app/assets/javascripts/monitoring/components/dashboard_header.vue +++ b/app/assets/javascripts/monitoring/components/dashboard_header.vue @@ -105,7 +105,7 @@ export default { return ( this.customMetricsAvailable && !this.shouldShowEmptyState && - // Custom metrics only avaialble on system dashboards because + // Custom metrics only available on system dashboards because // they are stored in the database. This can be improved. See: // https://gitlab.com/gitlab-org/gitlab/-/issues/28241 this.selectedDashboard?.out_of_the_box_dashboard diff --git a/app/models/integrations/jira.rb b/app/models/integrations/jira.rb index 2520d3bfc9c..318d30d8b2e 100644 --- a/app/models/integrations/jira.rb +++ b/app/models/integrations/jira.rb @@ -81,13 +81,13 @@ module Integrations section: SECTION_TYPE_CONNECTION, required: false, title: -> { s_('JiraService|Email or username') }, - help: -> { s_('JiraService|Only required for Basic authentication. Email for Jira Cloud or username for Jira Data Center and Jira Server') } + help: -> { s_('JiraService|Email for Jira Cloud or username for Jira Data Center and Jira Server') } field :password, section: SECTION_TYPE_CONNECTION, required: true, - title: -> { s_('JiraService|Password or API token') }, - non_empty_password_title: -> { s_('JiraService|New API token, password, or Jira personal access token') }, + title: -> { s_('JiraService|API token or password') }, + non_empty_password_title: -> { s_('JiraService|New API token or password') }, non_empty_password_help: -> { s_('JiraService|Leave blank to use your current configuration') }, help: -> { s_('JiraService|API token for Jira Cloud or password for Jira Data Center and Jira Server') }, is_secret: true diff --git a/data/removals/16_0/16.0-pre-clone-script-gitlab-saas.yml b/data/removals/16_0/16.0-pre-clone-script-gitlab-saas.yml new file mode 100644 index 00000000000..062c86e1fd3 --- /dev/null +++ b/data/removals/16_0/16.0-pre-clone-script-gitlab-saas.yml @@ -0,0 +1,9 @@ +- title: "`CI_PRE_CLONE_SCRIPT` variable on GitLab SaaS Runners has been removed" # (required) Clearly explain the change. For example, "The `confidential` field for a `Note` is removed" or "CI/CD job names are limited to 250 characters." + announcement_milestone: "15.9" # (required) The milestone when this feature was deprecated. + removal_milestone: "16.0" # (required) The milestone when this feature is being removed. + breaking_change: true # (required) Change to false if this is not a breaking change. + reporter: DarrenEastman # (required) GitLab username of the person reporting the removal + stage: Verify # (required) String value of the stage that the feature was created in. e.g., Growth + issue_url: https://gitlab.com/gitlab-org/gitlab-runner/-/issues/29405 # (required) Link to the deprecation issue in GitLab + body: | # (required) Do not modify this line, instead modify the lines below. + In GitLab 16.0 and later, the `CI_PRE_CLONE_SCRIPT` variable option on GitLab SaaS Runners has been removed. The `CI_PRE_CLONE_SCRIPT` variable enabled you to run commands in your CI/CD job before the runner executed `git-init` and `git-fetch`. You should use the `pre_get_sources_script` hook instead. For more information, see the blog post, [Guide to pre_clone_script changes on GitLab SaaS Linux Runners](https://about.gitlab.com/blog/2023/03/27/changes-to-the-preclonescript/). diff --git a/db/docs/batched_background_migrations/backfill_code_suggestions_namespace_settings.yml b/db/docs/batched_background_migrations/backfill_code_suggestions_namespace_settings.yml new file mode 100644 index 00000000000..d0803735d0b --- /dev/null +++ b/db/docs/batched_background_migrations/backfill_code_suggestions_namespace_settings.yml @@ -0,0 +1,6 @@ +--- +migration_job_name: BackfillCodeSuggestionsNamespaceSettings +description: Updates default value of code_suggestions on namespace_settings table +feature_category: code_suggestions +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121126 +milestone: 16.1 diff --git a/db/post_migrate/20230518071251_queue_backfill_code_suggestions_namespace_settings.rb b/db/post_migrate/20230518071251_queue_backfill_code_suggestions_namespace_settings.rb new file mode 100644 index 00000000000..bae143ddbb1 --- /dev/null +++ b/db/post_migrate/20230518071251_queue_backfill_code_suggestions_namespace_settings.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class QueueBackfillCodeSuggestionsNamespaceSettings < Gitlab::Database::Migration[2.1] + MIGRATION = "BackfillCodeSuggestionsNamespaceSettings" + DELAY_INTERVAL = 2.minutes + BATCH_SIZE = 50_000 + + restrict_gitlab_migration gitlab_schema: :gitlab_main + + def up + queue_batched_background_migration( + MIGRATION, + :namespace_settings, + :namespace_id, + job_interval: DELAY_INTERVAL, + batch_size: BATCH_SIZE + ) + end + + def down + delete_batched_background_migration(MIGRATION, :namespace_settings, :namespace_id, []) + end +end diff --git a/db/schema_migrations/20230518071251 b/db/schema_migrations/20230518071251 new file mode 100644 index 00000000000..eb84686e802 --- /dev/null +++ b/db/schema_migrations/20230518071251 @@ -0,0 +1 @@ +eabab8fcee59ed11ddaca9a19d5d16b257b9665f4189843cdd4177e9f5680169
\ No newline at end of file diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index c0a175c76cd..6b6712cdd01 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -221,9 +221,6 @@ configuration option in `gitlab.yml`. These metrics are served from the | `geo_lfs_objects_verified` | Gauge | 14.6 | Number of LFS objects successfully verified on secondary | `url` | | `geo_lfs_objects_verification_failed` | Gauge | 14.6 | Number of LFS objects that failed verifications on secondary | `url` | | `geo_lfs_objects_verification_total` | Gauge | 14.6 | Number of LFS objects to attempt to verify on secondary | `url` | -| `geo_attachments` | Gauge | 10.2 | Total number of file attachments available on primary | `url` | -| `geo_attachments_synced` | Gauge | 10.2 | Number of attachments synced on secondary | `url` | -| `geo_attachments_failed` | Gauge | 10.2 | Number of attachments failed to sync on secondary | `url` | | `geo_last_event_id` | Gauge | 10.2 | Database ID of the latest event log entry on the primary | `url` | | `geo_last_event_timestamp` | Gauge | 10.2 | UNIX timestamp of the latest event log entry on the primary | `url` | | `geo_cursor_last_event_id` | Gauge | 10.2 | Last database ID of the event log processed by the secondary | `url` | diff --git a/doc/ci/secrets/id_token_authentication.md b/doc/ci/secrets/id_token_authentication.md index 12e0402be25..58bdfc7e603 100644 --- a/doc/ci/secrets/id_token_authentication.md +++ b/doc/ci/secrets/id_token_authentication.md @@ -74,6 +74,8 @@ The token also includes custom claims provided by GitLab: | `runner_id` | Always | ID of the runner executing the job. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/404722) in GitLab 16.0. | | `runner_environment` | Always | The type of runner used by the job. Can be either `gitlab-hosted` or `self-hosted`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/404722) in GitLab 16.0. | | `sha` | Always | The commit SHA for the job. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/404722) in GitLab 16.0. | +| `pipeline_ref` | Always | Fully qualified URL for the pipeline definition (e.g. `https://gitlab.example.com/my-group/my-project/-/blob/714a629c0b401fdce83e847fc9589983fc6f46bc/.gitlab-ci.yml`). Might point to a remote location [depending on project configuration](../pipelines/settings.md#specify-a-custom-cicd-configuration-file). | +| `pipeline_sha` | Always | Git commit SHA for the project where the pipeline definition is stored. Only populated when the pipeline definition is stored in the project running the job. | ```json { @@ -101,6 +103,8 @@ The token also includes custom claims provided by GitLab: "runner_id": 1, "runner_environment": "self-hosted", "sha": "714a629c0b401fdce83e847fc9589983fc6f46bc", + "pipeline_ref": "https://gitlab.example.com/my-group/my-project/-/blob/714a629c0b401fdce83e847fc9589983fc6f46bc/.gitlab-ci.yml", + "pipeline_sha": "714a629c0b401fdce83e847fc9589983fc6f46bc", "jti": "235b3a54-b797-45c7-ae9a-f72d7bc6ef5b", "iss": "https://gitlab.example.com", "iat": 1681395193, diff --git a/doc/integration/advanced_search/elasticsearch_troubleshooting.md b/doc/integration/advanced_search/elasticsearch_troubleshooting.md index 0c4895f34fa..3d1592b772f 100644 --- a/doc/integration/advanced_search/elasticsearch_troubleshooting.md +++ b/doc/integration/advanced_search/elasticsearch_troubleshooting.md @@ -17,7 +17,7 @@ See [Starting a Rails console session](../../administration/operations/rails_con To list all available attributes: -1. Open the Rails console (`gitlab rails c`). +1. Open the Rails console (`sudo gitlab-rails console`). 1. Run the following command: ```ruby diff --git a/doc/integration/jira/configure.md b/doc/integration/jira/configure.md index 8223154e223..973ca8080a0 100644 --- a/doc/integration/jira/configure.md +++ b/doc/integration/jira/configure.md @@ -45,17 +45,15 @@ To configure your project settings in GitLab: this GitLab project (for example, `https://jira.example.com`). - **Jira API URL**: Base URL for the Jira instance API (for example, `https://jira-api.example.com`). If this URL is not set, the **Web URL** value is used by default. For Jira Cloud, leave **Jira API URL** blank. - - **Authentication type**: From the dropdown list, select: - - **Basic** - - **Jira personal access token (Jira Data Center and Jira Server only)** - - **Email or username** (relevant to **Basic** authentication only): - - For Jira Cloud, enter an email. - - For Jira Data Center or Jira Server, enter a username. - - **New API token, password, or Jira personal access token**: - - For **Basic** authentication: - - For Jira Cloud, enter an API token. - - For Jira Data Center or Jira Server, enter a password. - - For **Jira personal access token** authentication, enter a personal access token. + - **Authentication method**: + - **Basic**: + - **Email or username**: + - For Jira Cloud, enter an email. + - For Jira Data Center or Jira Server, enter a username. + - **API token or password**: + - For Jira Cloud, enter an API token. + - For Jira Data Center or Jira Server, enter a password. + - **Jira personal access token** (only available for Jira Data Center and Jira Server): Enter a personal access token. 1. To enable users to [view Jira issues](issues.md#view-jira-issues) inside the GitLab project, select **Enable Jira issues** and enter a Jira project key. diff --git a/doc/update/removals.md b/doc/update/removals.md index 4f048c9277d..9a2a3ac55fa 100644 --- a/doc/update/removals.md +++ b/doc/update/removals.md @@ -814,6 +814,16 @@ The predefined CI/CD variables that start with `CI_BUILD_*` were deprecated in G | `CI_BUILD_TOKEN` | `CI_JOB_TOKEN` | | `CI_BUILD_TRIGGERED` | `CI_PIPELINE_TRIGGERED` | +### `CI_PRE_CLONE_SCRIPT` variable on GitLab SaaS Runners has been removed + +<div class="deprecation-notes"> +- Announced in: GitLab <span class="milestone">15.9</span> +- This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/). Review the details carefully before upgrading. +- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/29405). +</div> + +In GitLab 16.0 and later, the `CI_PRE_CLONE_SCRIPT` variable option on GitLab SaaS Runners has been removed. The `CI_PRE_CLONE_SCRIPT` variable enabled you to run commands in your CI/CD job before the runner executed `git-init` and `git-fetch`. You should use the `pre_get_sources_script` hook instead. For more information, see the blog post, [Guide to pre_clone_script changes on GitLab SaaS Linux Runners](https://about.gitlab.com/blog/2023/03/27/changes-to-the-preclonescript/). + ### `POST /projects/:id/merge_requests/:merge_request_iid/approvals` removed <div class="deprecation-notes"> diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md index 5a1dcc840ca..293441ed084 100644 --- a/doc/user/application_security/secret_detection/index.md +++ b/doc/user/application_security/secret_detection/index.md @@ -160,7 +160,7 @@ your GitLab CI/CD configuration file is complex. ```yaml include: - - template: Jobs/Secret-Detection.gitlab-ci.yml + - template: Security/Secret-Detection.gitlab-ci.yml ``` 1. Select the **Validate** tab, then select **Validate pipeline**. diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 4090077ed4a..af2ebbb471f 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -316,12 +316,13 @@ For a web developer writing a webpage for your company's website: ## Filter activity in a merge request -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/115383) in GitLab 15.11 [with a flag](../../../administration/feature_flags.md) named `mr_activity_filters`. Disabled by default. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/115383) in GitLab 15.11 [with a flag](../../../administration/feature_flags.md) named `mr_activity_filters`. Disabled by default. +> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/387070) in GitLab 16.0. Available to GitLab team members only. FLAG: On self-managed GitLab, by default this feature is not available. To make it available per user, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `mr_activity_filters` for individual or groups of users. -On GitLab.com, this feature unavailable. +On GitLab.com, this feature is enabled for GitLab team members only. To understand the history of a merge request, filter its activity feed to show you only the items that are relevant to you. diff --git a/doc/user/project/repository/code_suggestions.md b/doc/user/project/repository/code_suggestions.md index 5de55db5ad4..5e79f33542a 100644 --- a/doc/user/project/repository/code_suggestions.md +++ b/doc/user/project/repository/code_suggestions.md @@ -11,14 +11,15 @@ type: index, reference > - [Enabled](https://gitlab.com/gitlab-org/gitlab/-/issues/408104) as opt-in with GitLab 15.11 as [Beta](/ee/policy/alpha-beta-support.md#beta). > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/408158) from GitLab Ultimate to GitLab Premium in 16.0. > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/410801) from GitLab Premium to GitLab Free in 16.0. +> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121079) in GitLab 16.1. WARNING: This feature is in [Beta](/ee/policy/alpha-beta-support.md#beta). -Code Suggestions use generative AI to suggest code while you're developing. -Due to high demand, this feature will have unscheduled downtime and code suggestions in VS Code may be delayed. +Due to high demand, this feature may have unscheduled downtime and code suggestions in IDEs may be delayed. Code Suggestions may produce [low-quality or incomplete suggestions](#model-accuracy-and-quality). Beta users should read about the [known limitations](#known-limitations). We look forward to hearing your feedback. +Code Suggestions use generative AI to suggest code while you're developing. Use Code Suggestions to write code more efficiently by viewing code suggestions as you type. Depending on the cursor position, the extension either: @@ -27,11 +28,13 @@ as you type. Depending on the cursor position, the extension either: To accept a suggestion, press <kbd>Tab</kbd>. -Code Suggestions are available in Visual Studio Code when you have the GitLab Workflow extension installed. +Code Suggestions are available in Visual Studio Code when you have the GitLab Workflow extension installed. [Additional IDE extension support](https://gitlab.com/groups/gitlab-org/-/epics/10542) is planned for the near future. ## Supported languages -Code Suggestions may produce [low-quality or incomplete suggestions](#model-accuracy-and-quality). The best results from Code Suggestions are expected for these languages: +Code Suggestions may produce [low-quality or incomplete suggestions](#model-accuracy-and-quality). + +The best results from Code Suggestions are expected for these languages: - C/C++ - C# @@ -47,7 +50,9 @@ Code Suggestions may produce [low-quality or incomplete suggestions](#model-accu Suggestions may be mixed for other languages. Using natural language code comments to request completions may also not function as expected. -GitLab is continuously improving the model and expects to support an additional seven languages soon, as well as natural language code comments. +Suggestions are best when writing new code. Editing existing functions or 'fill in the middle' of a function may not perform as expected. + +We are making improvements to the Code Suggestions underlying AI model weekly to improve the quality of suggestions. Please remember that AI is non-deterministic, so you may not get the same suggestion week to week. Usage of Code Suggestions is governed by the [GitLab Testing Agreement](https://about.gitlab.com/handbook/legal/testing-agreement/). Learn about [data usage when using Code Suggestions](#code-suggestions-data-usage). @@ -56,7 +61,7 @@ Usage of Code Suggestions is governed by the [GitLab Testing Agreement](https:// Prerequisites: - Your group owner must enable the [group level code suggestions setting](../../group/manage.md#group-code-suggestions). -- You must have [a personal access token](../../profile/personal_access_tokens.md#create-a-personal-access-token) with the `read_api` and `read_user` scopes. +- If using a [personal access token](../../profile/personal_access_tokens.md#create-a-personal-access-token), the token must have the `read_api` and `read_user` scopes. To enable Code Suggestions in VS Code: @@ -66,9 +71,20 @@ To enable Code Suggestions in VS Code: 1. In **GitLab: Add Account to VS Code on Mac**, add your GitLab work account to the VS Code extension: - In macOS, press <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>P</kbd>. - In Windows, press <kbd>Shift</kbd> + <kbd>Control</kbd> + <kbd>P</kbd>. -1. Provide your GitLab instance URL. A default is provided. -1. Provide your personal access token. -1. After your GitLab account connects successfully, in the left sidebar, select **Extensions**. +1. You can Authenticate with OAuth to GitLab.com or you can use a personal access token. + + 1. To use OAuth (recommended): + + 1. Type `GitLab: Authenticate with GitLab.com`. It appears as a quick action. Select it. + 1. Follow the alert links to open a GitLab authorization URL in your browser. + 1. In your browser, select the alert to open the URL with VS Code. + + 1. To use a personal access token: + + 1. Type `GitLab: Add Account to VS Code`. It appears as a quick action. Select it. + 1. Provide your GitLab instance URL. A default is provided. + 1. Provide your personal access token. +1. Regardless of the method you use to authenticate, after your GitLab account connects successfully, in the left sidebar, select **Extensions**. 1. Find the **GitLab workflow** extension, select **Settings** (**{settings}**), and select **Extension Settings**. 1. Enable **GitLab > AI Assisted Code Suggestions**. @@ -81,6 +97,14 @@ Start typing and receive suggestions for your GitLab projects. <iframe src="https://www.youtube-nocookie.com/embed/WnxBYxN2-p4" frameborder="0" allowfullscreen> </iframe> </figure> +## Stability and performance + +This feature is currently in [Beta](/ee/policy/alpha-beta-support.md#beta). +While the Code Suggestions inference API operates completely within the GitLab.com enterprise infrastructure, +we expect a high demand for this Beta feature, which may cause degraded performance or unexpected downtime +of the feature. We have built this feature to gracefully degrade and have controls in place to allow us to +mitigate abuse or misuse. GitLab may disable this feature for any or all customers at any time at our discretion. + ## Code Suggestions data usage Code Suggestions is a generative artificial intelligence (AI) model hosted on GitLab.com. @@ -113,36 +137,18 @@ We then process this raw dataset against heuristics that aim to increase the qua The Code Suggestions model is not trained on GitLab customer data. -### Off by default - -Code Suggestions are off by default and require a group owner to enable the feature with a group-level setting. - -After the group-level setting is enabled, developers using Visual Studio Code with the -[GitLab Workflow extension](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow) can connect -to GitLab.com by using a GitLab -[personal access token](../../profile/personal_access_tokens.md#create-a-personal-access-token) with the `read_api` -and `read_user` scopes. - ## Progressive enhancement -This feature is designed as a progressive enhancement to the existing VS Code GitLab Workflow plugin. +This feature is designed as a progressive enhancement to developers IDEs. Code Suggestions offer a completion if the machine learning engine can generate a recommendation. In the event of a connection issue or model inference failure, the feature gracefully degrades. -Code Suggestions do not prevent you from writing code in VS Code. +Code Suggestions do not prevent you from writing code in your IDE. ### Internet connectivity Code Suggestions only work when you have internet connectivity and can access GitLab.com. Code Suggestions are not available for self-managed customers, nor customers operating within an offline environment. -### Stability and performance - -This feature is currently in [Beta](/ee/policy/alpha-beta-support.md#beta). -While the Code Suggestions inference API operates completely within the GitLab.com enterprise infrastructure, -we expect a high demand for this Beta feature, which may cause degraded performance or unexpected downtime -of the feature. We have built this feature to gracefully degrade and have controls in place to allow us to -mitigate abuse or misuse. GitLab may disable this feature for any or all customers at any time at our discretion. - ### Model accuracy and quality While in Beta, Code Suggestions can generate low-quality, incomplete, and possibly insecure code. @@ -174,6 +180,7 @@ However, Code Suggestions may generate suggestions that are: We are also aware of specific situations that can produce unexpected or incoherent results including: +- Suggestions written in the middle of existing functions, or "fill in the middle." - Suggestions based on natural language code comments. - Suggestions that mixed programming languages in unexpected ways. diff --git a/lib/api/ci/helpers/runner.rb b/lib/api/ci/helpers/runner.rb index 7ca8b2df3dd..94c1942a244 100644 --- a/lib/api/ci/helpers/runner.rb +++ b/lib/api/ci/helpers/runner.rb @@ -146,6 +146,12 @@ module API # noop: overridden in EE end + def check_if_backoff_required! + return unless Gitlab::Database::Migrations::RunnerBackoff::Communicator.backoff_runner? + + too_many_requests!('Executing database migrations. Please retry later.', retry_after: 1.minute) + end + private def get_runner_config_from_request diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index 0247ce301e2..25ac1780a36 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -7,6 +7,8 @@ module API content_type :txt, 'text/plain' + before { check_if_backoff_required! } + resource :runners do desc 'Register a new runner' do detail "Register a new runner for the instance" diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 9fa0923d914..83641e824ae 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -486,6 +486,12 @@ module API render_api_error!('413 Request Entity Too Large', 413) end + def too_many_requests!(message = nil, retry_after: 1.minute) + header['Retry-After'] = retry_after.to_i if retry_after + + render_api_error!(message || '429 Too Many Requests', 429) + end + def not_modified! render_api_error!('304 Not Modified', 304) end diff --git a/lib/gitlab/background_migration/backfill_code_suggestions_namespace_settings.rb b/lib/gitlab/background_migration/backfill_code_suggestions_namespace_settings.rb new file mode 100644 index 00000000000..2d3bb4bbafa --- /dev/null +++ b/lib/gitlab/background_migration/backfill_code_suggestions_namespace_settings.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # This class sets default `code_suggestions` values on the namespace_settings table. + # For group namespace, set this to enabled. + # For user namespace, set this to disabled. + class BackfillCodeSuggestionsNamespaceSettings < BatchedMigrationJob + feature_category :code_suggestions + operation_name :update_all + + TYPE_VALUE_PAIRS = [ + { type: 'Group', value: true }, + { type: 'User', value: false } + ].freeze + + NAMESPACES_JOIN = <<~SQL + INNER JOIN namespaces + ON namespaces.id = namespace_settings.namespace_id + SQL + + def perform + TYPE_VALUE_PAIRS.each do |pair| + each_sub_batch do |sub_batch| + sub_batch.joins(NAMESPACES_JOIN) + .where(namespaces: { type: pair[:type] }) + .update_all(code_suggestions: pair[:value]) + end + end + end + end + end +end diff --git a/lib/gitlab/ci/jwt_v2.rb b/lib/gitlab/ci/jwt_v2.rb index aff30455d09..34549f4e019 100644 --- a/lib/gitlab/ci/jwt_v2.rb +++ b/lib/gitlab/ci/jwt_v2.rb @@ -45,10 +45,30 @@ module Gitlab super.merge( runner_id: runner&.id, runner_environment: runner_environment, - sha: pipeline.sha + sha: pipeline.sha, + pipeline_ref: pipeline_ref, + pipeline_sha: pipeline.repository_source? ? pipeline.sha : nil ) end + def pipeline_ref + project_config = Gitlab::Ci::ProjectConfig.new( + project: project, + sha: pipeline.sha, + pipeline_source: pipeline.source&.to_sym, + pipeline_source_bridge: pipeline.source_bridge, + pipeline: pipeline + ) + + project_config&.url + + # Errors are rescued to mitigate risk. This can be removed if no errors are observed. + # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117923#note_1387660746 for context. + rescue StandardError => e + Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, pipeline_id: pipeline.id) + nil + end + def runner_environment return unless runner diff --git a/lib/gitlab/ci/project_config.rb b/lib/gitlab/ci/project_config.rb index 00b2ad58428..0eed673f266 100644 --- a/lib/gitlab/ci/project_config.rb +++ b/lib/gitlab/ci/project_config.rb @@ -21,11 +21,14 @@ module Gitlab ProjectConfig::AutoDevops ].freeze - def initialize(project:, sha:, custom_content: nil, pipeline_source: nil, pipeline_source_bridge: nil) - @config = find_config(project, sha, custom_content, pipeline_source, pipeline_source_bridge) + def initialize( + project:, sha:, custom_content: nil, pipeline_source: nil, + pipeline_source_bridge: nil, pipeline: nil + ) + @config = find_config(project, sha, custom_content, pipeline_source, pipeline_source_bridge, pipeline) end - delegate :content, :source, to: :@config, allow_nil: true + delegate :content, :source, :url, to: :@config, allow_nil: true delegate :internal_include_prepended?, to: :@config def exists? @@ -34,9 +37,9 @@ module Gitlab private - def find_config(project, sha, custom_content, pipeline_source, pipeline_source_bridge) + def find_config(project, sha, custom_content, pipeline_source, pipeline_source_bridge, pipeline) sources.each do |source| - config = source.new(project, sha, custom_content, pipeline_source, pipeline_source_bridge) + config = source.new(project, sha, custom_content, pipeline_source, pipeline_source_bridge, pipeline) return config if config.exists? end diff --git a/lib/gitlab/ci/project_config/auto_devops.rb b/lib/gitlab/ci/project_config/auto_devops.rb index c5f010ebaea..24e19a1183b 100644 --- a/lib/gitlab/ci/project_config/auto_devops.rb +++ b/lib/gitlab/ci/project_config/auto_devops.rb @@ -21,6 +21,10 @@ module Gitlab :auto_devops_source end + def url + Gitlab::Source.blob_url('lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml') + end + private def template_name diff --git a/lib/gitlab/ci/project_config/bridge.rb b/lib/gitlab/ci/project_config/bridge.rb index c342ab2c215..4c64e43b8a5 100644 --- a/lib/gitlab/ci/project_config/bridge.rb +++ b/lib/gitlab/ci/project_config/bridge.rb @@ -13,6 +13,19 @@ module Gitlab def source :bridge_source end + + def url + source_pipeline = pipeline_source_bridge.pipeline + + Repository.new( + source_pipeline.project, + source_pipeline.sha, + custom_content, + source_pipeline.source.to_sym, + source_pipeline.source_bridge, + source_pipeline + ).url + end end end end diff --git a/lib/gitlab/ci/project_config/external_project.rb b/lib/gitlab/ci/project_config/external_project.rb index 0afdab23886..c65d1e658b9 100644 --- a/lib/gitlab/ci/project_config/external_project.rb +++ b/lib/gitlab/ci/project_config/external_project.rb @@ -25,6 +25,17 @@ module Gitlab :external_project_source end + def url + path_file, path_project, ref = extract_location_tokens + ref ||= 'HEAD' + + namespace, _, project = path_project.partition('/') + return unless namespace.present? && project.present? + + blob = File.join(ref, path_file) + Rails.application.routes.url_helpers.namespace_project_blob_url(namespace, project, blob) + end + private # Example: path/to/.gitlab-ci.yml@another-group/another-project diff --git a/lib/gitlab/ci/project_config/parameter.rb b/lib/gitlab/ci/project_config/parameter.rb index 69e699c27f1..4a7b68c59e8 100644 --- a/lib/gitlab/ci/project_config/parameter.rb +++ b/lib/gitlab/ci/project_config/parameter.rb @@ -15,6 +15,10 @@ module Gitlab def source :parameter_source end + + def url + nil + end end end end diff --git a/lib/gitlab/ci/project_config/remote.rb b/lib/gitlab/ci/project_config/remote.rb index 19cbf8e9c1e..dabb107aee7 100644 --- a/lib/gitlab/ci/project_config/remote.rb +++ b/lib/gitlab/ci/project_config/remote.rb @@ -19,6 +19,10 @@ module Gitlab def source :remote_source end + + def url + ci_config_path + end end end end diff --git a/lib/gitlab/ci/project_config/repository.rb b/lib/gitlab/ci/project_config/repository.rb index 272425fd546..e5afb2565db 100644 --- a/lib/gitlab/ci/project_config/repository.rb +++ b/lib/gitlab/ci/project_config/repository.rb @@ -20,6 +20,10 @@ module Gitlab :repository_source end + def url + Rails.application.routes.url_helpers.project_blob_url(project, File.join(sha, ci_config_path)) + end + private def file_in_repository? diff --git a/lib/gitlab/ci/project_config/source.rb b/lib/gitlab/ci/project_config/source.rb index 9a4a6394fa1..195a77f2f3d 100644 --- a/lib/gitlab/ci/project_config/source.rb +++ b/lib/gitlab/ci/project_config/source.rb @@ -6,12 +6,13 @@ module Gitlab class Source include Gitlab::Utils::StrongMemoize - def initialize(project, sha, custom_content, pipeline_source, pipeline_source_bridge) + def initialize(project, sha, custom_content, pipeline_source, pipeline_source_bridge, pipeline) @project = project @sha = sha @custom_content = custom_content @pipeline_source = pipeline_source @pipeline_source_bridge = pipeline_source_bridge + @pipeline = pipeline end def exists? @@ -33,9 +34,14 @@ module Gitlab raise NotImplementedError end + # Used to populate the pipeline_ref claim in Ci::JwtV2 + def url + raise NotImplementedError + end + private - attr_reader :project, :sha, :custom_content, :pipeline_source, :pipeline_source_bridge + attr_reader :project, :sha, :custom_content, :pipeline_source, :pipeline_source_bridge, :pipeline def ci_config_path @ci_config_path ||= project.ci_config_path_or_default diff --git a/lib/gitlab/error_tracking/processor/grpc_error_processor.rb b/lib/gitlab/error_tracking/processor/grpc_error_processor.rb index ab0df39e512..c141398bee0 100644 --- a/lib/gitlab/error_tracking/processor/grpc_error_processor.rb +++ b/lib/gitlab/error_tracking/processor/grpc_error_processor.rb @@ -6,7 +6,8 @@ module Gitlab module GrpcErrorProcessor extend Gitlab::ErrorTracking::Processor::Concerns::ProcessesExceptions - DEBUG_ERROR_STRING_REGEX = RE2('(.*) debug_error_string:(.*)') + # Braces added by gRPC Ruby code: https://github.com/grpc/grpc/blob/0e38b075ffff72ab2ad5326e3f60ba6dcc234f46/src/ruby/lib/grpc/errors.rb#L46 + DEBUG_ERROR_STRING_REGEX = RE2('(.*) debug_error_string:\{(.*)\}') class << self def call(event) diff --git a/lib/gitlab/source.rb b/lib/gitlab/source.rb index 0e9fb39156d..0944b5cfc51 100644 --- a/lib/gitlab/source.rb +++ b/lib/gitlab/source.rb @@ -19,6 +19,12 @@ module Gitlab Gitlab::Utils.append_path(host_url, path) end + def blob_url(source_file_path) + blob_id = File.join(ref, source_file_path) + + host_url + url_helpers.namespace_project_blob_path(group, project, blob_id) + end + private def host_url diff --git a/locale/gitlab.pot b/locale/gitlab.pot index fc0840bfc30..b4c4de6927b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -25316,6 +25316,9 @@ msgstr "" msgid "JiraService|API token for Jira Cloud or password for Jira Data Center and Jira Server" msgstr "" +msgid "JiraService|API token or password" +msgstr "" + msgid "JiraService|An error occurred while fetching issue list" msgstr "" @@ -25346,6 +25349,9 @@ msgstr "" msgid "JiraService|Displaying Jira issues while leaving GitLab issues also enabled might be confusing. Consider %{gitlab_issues_link_start}disabling GitLab issues%{link_end} if they won't otherwise be used." msgstr "" +msgid "JiraService|Email for Jira Cloud or username for Jira Data Center and Jira Server" +msgstr "" + msgid "JiraService|Email or username" msgstr "" @@ -25415,6 +25421,9 @@ msgstr "" msgid "JiraService|Jira issues" msgstr "" +msgid "JiraService|Jira personal access token" +msgstr "" + msgid "JiraService|Jira personal access token (Jira Data Center and Jira Server only)" msgstr "" @@ -25427,24 +25436,24 @@ msgstr "" msgid "JiraService|Move to Done" msgstr "" -msgid "JiraService|New API token, password, or Jira personal access token" +msgid "JiraService|New API token or password" msgstr "" -msgid "JiraService|Only required for Basic authentication. Email for Jira Cloud or username for Jira Data Center and Jira Server" +msgid "JiraService|New Jira personal access token" msgstr "" msgid "JiraService|Open Jira" msgstr "" -msgid "JiraService|Password or API token" -msgstr "" - msgid "JiraService|Project key changed, refresh list" msgstr "" msgid "JiraService|Project key is required to generate issue types" msgstr "" +msgid "JiraService|Recommended. Only available for Jira Data Center and Jira Server." +msgstr "" + msgid "JiraService|Select issue type" msgstr "" diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb index 379499662c2..ecf4826262b 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb @@ -1,7 +1,11 @@ # frozen_string_literal: true module QA - RSpec.describe 'Verify', :runner, product_group: :pipeline_execution do + RSpec.describe 'Verify', :runner, product_group: :pipeline_execution, + quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/411510', + type: :flaky + } do context 'when pipeline is blocked' do let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" } diff --git a/spec/features/groups/integrations/group_integrations_spec.rb b/spec/features/groups/integrations/group_integrations_spec.rb index 8cddda91e89..de6403210bb 100644 --- a/spec/features/groups/integrations/group_integrations_spec.rb +++ b/spec/features/groups/integrations/group_integrations_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Group integrations', :js do +RSpec.describe 'Group integrations', :js, feature_category: :integrations do include_context 'group integration activation' before do diff --git a/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb b/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb index 9ff344bcc88..458f4925051 100644 --- a/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb +++ b/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb @@ -22,17 +22,17 @@ RSpec.describe 'User uses inherited settings', :js, feature_category: :integrati expect(page).not_to have_button('Use custom settings') expect(page).to have_field('Web URL', with: parent_settings[:url], readonly: true) - expect(page).to have_field('New API token, password, or Jira personal access token', with: '', readonly: true) + expect(page).to have_field('New API token or password', with: '', readonly: true) click_on 'Use default settings' click_on 'Use custom settings' expect(page).not_to have_button('Use default settings') expect(page).to have_field('Web URL', with: project_settings[:url], readonly: false) - expect(page).to have_field('New API token, password, or Jira personal access token', with: '', readonly: false) + expect(page).to have_field('New API token or password', with: '', readonly: false) fill_in 'Web URL', with: 'http://custom.com' - fill_in 'New API token, password, or Jira personal access token', with: 'custom' + fill_in 'New API token or password', with: 'custom' click_save_integration @@ -53,14 +53,14 @@ RSpec.describe 'User uses inherited settings', :js, feature_category: :integrati expect(page).not_to have_button('Use default settings') expect(page).to have_field('URL', with: project_settings[:url], readonly: false) - expect(page).to have_field('New API token, password, or Jira personal access token', with: '', readonly: false) + expect(page).to have_field('New API token or password', with: '', readonly: false) click_on 'Use custom settings' click_on 'Use default settings' expect(page).not_to have_button('Use custom settings') expect(page).to have_field('URL', with: parent_settings[:url], readonly: true) - expect(page).to have_field('New API token, password, or Jira personal access token', with: '', readonly: true) + expect(page).to have_field('New API token or password', with: '', readonly: true) click_save_integration diff --git a/spec/frontend/admin/broadcast_messages/components/message_form_spec.js b/spec/frontend/admin/broadcast_messages/components/message_form_spec.js index 212f26b8faf..ad5f4f6c3c1 100644 --- a/spec/frontend/admin/broadcast_messages/components/message_form_spec.js +++ b/spec/frontend/admin/broadcast_messages/components/message_form_spec.js @@ -34,6 +34,7 @@ describe('MessageForm', () => { const findDismissable = () => wrapper.findComponent('[data-testid=dismissable-checkbox]'); const findTargetRoles = () => wrapper.findComponent('[data-testid=target-roles-checkboxes]'); const findSubmitButton = () => wrapper.findComponent('[data-testid=submit-button]'); + const findCancelButton = () => wrapper.findComponent('[data-testid=cancel-button]'); const findForm = () => wrapper.findComponent(GlForm); function createComponent({ broadcastMessage = {} } = {}) { @@ -127,6 +128,14 @@ describe('MessageForm', () => { }); }); + describe('form cancel button', () => { + it('renders when the editing a message and has href back to message index page', () => { + createComponent({ broadcastMessage: { id: 100 } }); + expect(wrapper.text()).toContain('Cancel'); + expect(findCancelButton().attributes('href')).toBe(wrapper.vm.messagesPath); + }); + }); + describe('form submission', () => { const defaultPayload = { message: defaultProps.message, diff --git a/spec/frontend/integrations/edit/components/jira_auth_fields_spec.js b/spec/frontend/integrations/edit/components/jira_auth_fields_spec.js new file mode 100644 index 00000000000..dcae2ceeeaa --- /dev/null +++ b/spec/frontend/integrations/edit/components/jira_auth_fields_spec.js @@ -0,0 +1,142 @@ +import { GlFormRadio, GlFormRadioGroup } from '@gitlab/ui'; +import { nextTick } from 'vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; + +import JiraAuthFields from '~/integrations/edit/components/jira_auth_fields.vue'; +import { jiraAuthTypeFieldProps } from '~/integrations/constants'; +import { createStore } from '~/integrations/edit/store'; + +import { mockJiraAuthFields } from '../mock_data'; + +describe('JiraAuthFields', () => { + let wrapper; + + const defaultProps = { + fields: mockJiraAuthFields, + }; + + const createComponent = ({ props } = {}) => { + const store = createStore(); + + wrapper = shallowMountExtended(JiraAuthFields, { + propsData: { ...defaultProps, ...props }, + store, + }); + }; + + const findAuthTypeRadio = () => wrapper.findComponent(GlFormRadioGroup); + const findAuthTypeOptions = () => wrapper.findAllComponents(GlFormRadio); + const findUsernameField = () => wrapper.findByTestId('jira-auth-username'); + const findPasswordField = () => wrapper.findByTestId('jira-auth-password'); + + const selectRadioOption = (index) => findAuthTypeRadio().vm.$emit('input', index); + + describe('template', () => { + const mockFieldsWithPasswordValue = [ + mockJiraAuthFields[0], + mockJiraAuthFields[1], + { + ...mockJiraAuthFields[2], + value: 'hidden', + }, + ]; + + beforeEach(() => { + createComponent(); + }); + + it('renders auth type as radio buttons with correct options', () => { + expect(findAuthTypeRadio().exists()).toBe(true); + + findAuthTypeOptions().wrappers.forEach((option, index) => { + expect(option.text()).toBe(JiraAuthFields.authTypeOptions[index].text); + }); + }); + + it('selects "Basic" authentication by default', () => { + expect(findAuthTypeRadio().attributes('checked')).toBe('0'); + }); + + it('selects correct authentication when passed from backend', async () => { + createComponent({ + props: { + fields: [ + { + ...mockJiraAuthFields[0], + value: 1, + }, + mockJiraAuthFields[1], + mockJiraAuthFields[2], + ], + }, + }); + await nextTick(); + + expect(findAuthTypeRadio().attributes('checked')).toBe('1'); + }); + + describe('when "Basic" authentication is selected', () => { + it('renders username field as required', () => { + expect(findUsernameField().exists()).toBe(true); + expect(findUsernameField().props()).toMatchObject({ + title: jiraAuthTypeFieldProps[0].username, + required: true, + }); + }); + + it('renders password field with help', () => { + expect(findPasswordField().exists()).toBe(true); + expect(findPasswordField().props()).toMatchObject({ + title: jiraAuthTypeFieldProps[0].password, + help: jiraAuthTypeFieldProps[0].passwordHelp, + }); + }); + + it('renders new password title when value is present', () => { + createComponent({ + props: { + fields: mockFieldsWithPasswordValue, + }, + }); + + expect(findPasswordField().props('title')).toBe(jiraAuthTypeFieldProps[0].nonEmptyPassword); + }); + }); + + describe('when "Jira personal access token" authentication is selected', () => { + beforeEach(() => { + createComponent(); + + selectRadioOption(1); + }); + + it('selects "Jira personal access token" authentication', () => { + expect(findAuthTypeRadio().attributes('checked')).toBe('1'); + }); + + it('does not render username field', () => { + expect(findUsernameField().exists()).toBe(false); + }); + + it('renders password field without help', () => { + expect(findPasswordField().exists()).toBe(true); + expect(findPasswordField().props()).toMatchObject({ + title: jiraAuthTypeFieldProps[1].password, + help: null, + }); + }); + + it('renders new password title when value is present', async () => { + createComponent({ + props: { + fields: mockFieldsWithPasswordValue, + }, + }); + + await selectRadioOption(1); + + expect(findPasswordField().props('title')).toBe(jiraAuthTypeFieldProps[1].nonEmptyPassword); + }); + }); + }); +}); diff --git a/spec/frontend/integrations/edit/components/sections/connection_spec.js b/spec/frontend/integrations/edit/components/sections/connection_spec.js index a24253d542d..7bd08a15ec1 100644 --- a/spec/frontend/integrations/edit/components/sections/connection_spec.js +++ b/spec/frontend/integrations/edit/components/sections/connection_spec.js @@ -1,15 +1,21 @@ import { shallowMount } from '@vue/test-utils'; +import { stubComponent } from 'helpers/stub_component'; import IntegrationSectionConnection from '~/integrations/edit/components/sections/connection.vue'; import ActiveCheckbox from '~/integrations/edit/components/active_checkbox.vue'; import DynamicField from '~/integrations/edit/components/dynamic_field.vue'; +import JiraAuthFields from '~/integrations/edit/components/jira_auth_fields.vue'; import { createStore } from '~/integrations/edit/store'; -import { mockIntegrationProps } from '../../mock_data'; +import { mockIntegrationProps, mockJiraAuthFields, mockField } from '../../mock_data'; describe('IntegrationSectionConnection', () => { let wrapper; + const JiraAuthFieldsStub = stubComponent(JiraAuthFields, { + template: `<div />`, + }); + const createComponent = ({ customStateProps = {}, props = {} } = {}) => { const store = createStore({ customState: { ...mockIntegrationProps, ...customStateProps }, @@ -17,11 +23,15 @@ describe('IntegrationSectionConnection', () => { wrapper = shallowMount(IntegrationSectionConnection, { propsData: { ...props }, store, + stubs: { + JiraAuthFields: JiraAuthFieldsStub, + }, }); }; const findActiveCheckbox = () => wrapper.findComponent(ActiveCheckbox); const findAllDynamicFields = () => wrapper.findAllComponents(DynamicField); + const findJiraAuthFields = () => wrapper.findComponent(JiraAuthFields); describe('template', () => { describe('ActiveCheckbox', () => { @@ -63,11 +73,42 @@ describe('IntegrationSectionConnection', () => { }); }); - it('does not render DynamicField when field is empty', () => { + it('does not render DynamicField when fields is empty', () => { createComponent(); expect(findAllDynamicFields()).toHaveLength(0); }); }); + + describe('when integration is not Jira', () => { + it('does not render JiraAuthFields', () => { + createComponent(); + + expect(findJiraAuthFields().exists()).toBe(false); + }); + }); + + describe('when integration is Jira', () => { + beforeEach(() => { + createComponent({ + customStateProps: { + type: 'jira', + }, + props: { + fields: [mockField, ...mockJiraAuthFields], + }, + }); + }); + + it('renders JiraAuthFields', () => { + expect(findJiraAuthFields().exists()).toBe(true); + expect(findJiraAuthFields().props('fields')).toEqual(mockJiraAuthFields); + }); + + it('filters out Jira auth fields for DynamicField', () => { + expect(findAllDynamicFields()).toHaveLength(1); + expect(findAllDynamicFields().at(0).props('name')).toBe(mockField.name); + }); + }); }); }); diff --git a/spec/frontend/integrations/edit/mock_data.js b/spec/frontend/integrations/edit/mock_data.js index c276d2e7364..31526eddd36 100644 --- a/spec/frontend/integrations/edit/mock_data.js +++ b/spec/frontend/integrations/edit/mock_data.js @@ -26,6 +26,24 @@ export const mockJiraIssueTypes = [ { id: '3', name: 'epic', description: 'epic' }, ]; +export const mockJiraAuthFields = [ + { + name: 'jira_auth_type', + type: 'select', + title: 'Authentication type', + }, + { + name: 'username', + type: 'text', + help: 'Email for Jira Cloud or username for Jira Data Center and Jira Server', + }, + { + name: 'password', + type: 'password', + help: 'API token for Jira Cloud or password for Jira Data Center and Jira Server', + }, +]; + export const mockField = { help: 'The URL of the project', name: 'project_url', diff --git a/spec/lib/api/ci/helpers/runner_spec.rb b/spec/lib/api/ci/helpers/runner_spec.rb index 06ec0396ab1..62b79c77b4a 100644 --- a/spec/lib/api/ci/helpers/runner_spec.rb +++ b/spec/lib/api/ci/helpers/runner_spec.rb @@ -134,4 +134,36 @@ RSpec.describe API::Ci::Helpers::Runner do .and not_change { success_counter.get(runner_type: 'project_type') } end end + + describe '#check_if_backoff_required!' do + subject { helper.check_if_backoff_required! } + + let(:backoff_runner) { false } + + before do + allow(Gitlab::Database::Migrations::RunnerBackoff::Communicator) + .to receive(:backoff_runner?) + .and_return(backoff_runner) + end + + context 'when migrations are running' do + let(:backoff_runner) { true } + + it 'denies requests' do + expect(helper).to receive(:too_many_requests!) + + subject + end + end + + context 'when migrations are not running' do + let(:backoff_runner) { false } + + it 'allows requests' do + expect(helper).not_to receive(:too_many_requests!) + + subject + end + end + end end diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb index b70bcb5ab0d..f8d40d6e181 100644 --- a/spec/lib/api/helpers_spec.rb +++ b/spec/lib/api/helpers_spec.rb @@ -952,6 +952,42 @@ RSpec.describe API::Helpers, feature_category: :shared do end end + describe '#too_many_requests!', :aggregate_failures do + let(:headers) { instance_double(Hash) } + + before do + allow(helper).to receive(:header).and_return(headers) + end + + it 'renders 429' do + expect(helper).to receive(:render_api_error!).with('429 Too Many Requests', 429) + expect(headers).to receive(:[]=).with('Retry-After', 60) + + helper.too_many_requests! + end + + it 'renders 429 with a custom message' do + expect(helper).to receive(:render_api_error!).with('custom message', 429) + expect(headers).to receive(:[]=).with('Retry-After', 60) + + helper.too_many_requests!('custom message') + end + + it 'renders 429 with a custom Retry-After value' do + expect(helper).to receive(:render_api_error!).with('429 Too Many Requests', 429) + expect(headers).to receive(:[]=).with('Retry-After', 120) + + helper.too_many_requests!(retry_after: 2.minutes) + end + + it 'renders 429 without a Retry-After value' do + expect(helper).to receive(:render_api_error!).with('429 Too Many Requests', 429) + expect(headers).not_to receive(:[]=) + + helper.too_many_requests!(retry_after: nil) + end + end + describe '#authenticate_by_gitlab_shell_token!' do include GitlabShellHelpers diff --git a/spec/lib/gitlab/background_migration/backfill_code_suggestions_namespace_settings_spec.rb b/spec/lib/gitlab/background_migration/backfill_code_suggestions_namespace_settings_spec.rb new file mode 100644 index 00000000000..1cfdf0ab09a --- /dev/null +++ b/spec/lib/gitlab/background_migration/backfill_code_suggestions_namespace_settings_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::BackfillCodeSuggestionsNamespaceSettings, schema: 20230518071251, feature_category: :code_suggestions do # rubocop:disable Layout/LineLength + let(:namespaces_table) { table(:namespaces) } + let(:namespace_settings_table) { table(:namespace_settings) } + + let(:group_namespace) { namespaces_table.create!(name: 'Group#1', type: 'Group', path: 'group') } + let(:user_namespace) { namespaces_table.create!(name: 'User#1', type: 'User', path: 'user') } + let(:project_namespace) { namespaces_table.create!(name: 'Project#1', type: 'Project', path: 'project') } + + subject(:perform_migration) do + described_class.new( + start_id: namespace_settings_table.minimum(:namespace_id), + end_id: namespace_settings_table.maximum(:namespace_id), + batch_table: :namespace_settings, + batch_column: :namespace_id, + sub_batch_size: 3, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform + end + + before do + namespace_settings_table.create!(namespace_id: group_namespace.id, code_suggestions: false) + namespace_settings_table.create!(namespace_id: user_namespace.id, code_suggestions: true) + namespace_settings_table.create!(namespace_id: project_namespace.id, code_suggestions: true) + end + + it 'updates the code suggestions values only for group and user namespace', :aggregate_failures do + expect { perform_migration } + .to change { namespace_settings_table.find_by_namespace_id(group_namespace.id).code_suggestions }.to(true) + .and change { namespace_settings_table.find_by_namespace_id(user_namespace.id).code_suggestions }.to(false) + + expect(namespace_settings_table.find_by_namespace_id(project_namespace.id).code_suggestions).to eq(true) + end +end diff --git a/spec/lib/gitlab/ci/jwt_v2_spec.rb b/spec/lib/gitlab/ci/jwt_v2_spec.rb index 528be4b5da7..a2aa80648b4 100644 --- a/spec/lib/gitlab/ci/jwt_v2_spec.rb +++ b/spec/lib/gitlab/ci/jwt_v2_spec.rb @@ -111,6 +111,79 @@ RSpec.describe Gitlab::Ci::JwtV2, feature_category: :continuous_integration do expect(payload[:sha]).to eq(pipeline.sha) end end + + describe 'pipeline_ref' do + let(:project_config) { instance_double(Gitlab::Ci::ProjectConfig, url: url) } + let(:url) { 'https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab-ci.yml' } + + before do + allow(Gitlab::Ci::ProjectConfig).to receive(:new).with( + project: project, + sha: pipeline.sha, + pipeline_source: pipeline.source.to_sym, + pipeline_source_bridge: pipeline.source_bridge, + pipeline: build.pipeline + ).and_return(project_config) + end + + it 'delegates to ProjectConfig#url' do + expect(payload[:pipeline_ref]).to eq(url) + end + + context 'when project config is nil' do + before do + allow(Gitlab::Ci::ProjectConfig).to receive(:new).and_return(nil) + end + + it 'is nil' do + expect(payload[:pipeline_ref]).to be_nil + end + end + + context 'when ProjectConfig#url raises an error' do + before do + allow_next_instance_of(Gitlab::Ci::ProjectConfig) do |instance| + allow(instance).to receive(:url).and_raise(RuntimeError) + end + end + + it 'raises the same error' do + expect { payload }.to raise_error(RuntimeError) + end + + context 'in production' do + before do + stub_rails_env('production') + end + + it 'is nil' do + expect(payload[:pipeline_ref]).to be_nil + end + end + end + end + + describe 'pipeline_sha' do + context 'when pipeline config_source is repository' do + before do + pipeline.config_source = Enums::Ci::Pipeline.config_sources[:repository_source] + end + + it 'is the pipeline\'s sha' do + expect(payload[:pipeline_sha]).to eq(pipeline.sha) + end + end + + context 'when pipeline config_source is not repository' do + before do + pipeline.config_source = Enums::Ci::Pipeline.config_sources[:unknown_source] + end + + it 'is nil' do + expect(payload[:pipeline_sha]).to eq(nil) + end + end + end end end end diff --git a/spec/lib/gitlab/ci/project_config/repository_spec.rb b/spec/lib/gitlab/ci/project_config/repository_spec.rb index e8a997a7e43..d4d6e46152c 100644 --- a/spec/lib/gitlab/ci/project_config/repository_spec.rb +++ b/spec/lib/gitlab/ci/project_config/repository_spec.rb @@ -3,11 +3,12 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::ProjectConfig::Repository, feature_category: :continuous_integration do + let(:files) { { 'README.md' => 'hello' } } let(:project) { create(:project, :custom_repo, files: files) } let(:sha) { project.repository.head_commit.sha } - let(:files) { { 'README.md' => 'hello' } } + let(:pipeline) { create(:ci_pipeline, project: project) } - subject(:config) { described_class.new(project, sha, nil, nil, nil) } + subject(:config) { described_class.new(project, sha, nil, nil, nil, pipeline) } describe '#content' do subject(:content) { config.content } @@ -50,4 +51,10 @@ RSpec.describe Gitlab::Ci::ProjectConfig::Repository, feature_category: :continu it { is_expected.to eq(true) } end + + describe '#url' do + subject { config.url } + + it { is_expected.to eq("#{Settings.gitlab.base_url}/#{project.full_path}/-/blob/#{sha}/.gitlab-ci.yml") } + end end diff --git a/spec/lib/gitlab/ci/project_config/source_spec.rb b/spec/lib/gitlab/ci/project_config/source_spec.rb index eefabe1babb..57bb064dce6 100644 --- a/spec/lib/gitlab/ci/project_config/source_spec.rb +++ b/spec/lib/gitlab/ci/project_config/source_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Gitlab::Ci::ProjectConfig::Source, feature_category: :continuous_ let_it_be(:project) { build_stubbed(:project) } let_it_be(:sha) { '123456' } - subject(:custom_config) { custom_config_class.new(project, sha, nil, nil, nil) } + subject(:custom_config) { custom_config_class.new(project, sha, nil, nil, nil, nil) } describe '#content' do subject(:content) { custom_config.content } @@ -26,4 +26,10 @@ RSpec.describe Gitlab::Ci::ProjectConfig::Source, feature_category: :continuous_ it { expect(internal_include_prepended).to eq(false) } end + + describe '#url' do + subject(:url) { custom_config.url } + + it { expect { url }.to raise_error(NotImplementedError) } + end end diff --git a/spec/lib/gitlab/ci/project_config_spec.rb b/spec/lib/gitlab/ci/project_config_spec.rb index c4b179c9ef5..f00801d62d8 100644 --- a/spec/lib/gitlab/ci/project_config_spec.rb +++ b/spec/lib/gitlab/ci/project_config_spec.rb @@ -2,8 +2,9 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::ProjectConfig do - let(:project) { create(:project, :empty_repo, ci_config_path: ci_config_path) } +RSpec.describe Gitlab::Ci::ProjectConfig, feature_category: :pipeline_composition do + let_it_be(:project) { create(:project, :empty_repo) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } let(:sha) { '123456' } let(:content) { nil } let(:source) { :push } @@ -11,12 +12,17 @@ RSpec.describe Gitlab::Ci::ProjectConfig do subject(:config) do described_class.new(project: project, sha: sha, - custom_content: content, pipeline_source: source, pipeline_source_bridge: bridge) + custom_content: content, pipeline_source: source, + pipeline_source_bridge: bridge, pipeline: pipeline) + end + + before do + project.ci_config_path = ci_config_path end context 'when bridge job is passed in as parameter' do let(:ci_config_path) { nil } - let(:bridge) { create(:ci_bridge) } + let(:bridge) { build_stubbed(:ci_bridge) } before do allow(bridge).to receive(:yaml_for_downstream).and_return('the-yaml') @@ -26,6 +32,25 @@ RSpec.describe Gitlab::Ci::ProjectConfig do expect(config.source).to eq(:bridge_source) expect(config.content).to eq('the-yaml') end + + it 'delegates url to upstream pipeline' do + pipeline = bridge.pipeline + + expected_args = [ + pipeline.project, + pipeline.sha, + nil, + pipeline.source.to_sym, + pipeline.source_bridge, + pipeline + ] + + expect_next_instance_of(Gitlab::Ci::ProjectConfig::Repository, *expected_args) do |instance| + expect(instance).to receive(:url) + end + + config.url + end end context 'when config is defined in a custom path in the repository' do @@ -48,6 +73,7 @@ RSpec.describe Gitlab::Ci::ProjectConfig do it 'returns root config including the local custom file' do expect(config.source).to eq(:repository_source) expect(config.content).to eq(config_content_result) + expect(config.url).to eq("#{Settings.gitlab.base_url}/#{project.full_path}/-/blob/#{sha}/#{ci_config_path}") end end @@ -64,6 +90,7 @@ RSpec.describe Gitlab::Ci::ProjectConfig do it 'returns root config including the remote config' do expect(config.source).to eq(:remote_source) expect(config.content).to eq(config_content_result) + expect(config.url).to eq(ci_config_path) end end @@ -81,6 +108,9 @@ RSpec.describe Gitlab::Ci::ProjectConfig do it 'returns root config including the path to another repository' do expect(config.source).to eq(:external_project_source) expect(config.content).to eq(config_content_result) + expect(config.url).to eq(<<~TEXT.chomp) + #{Settings.gitlab.base_url}/another-group/another-repo/-/blob/HEAD/path/to/.gitlab-ci.yml + TEXT end context 'when path specifies a refname' do @@ -98,6 +128,17 @@ RSpec.describe Gitlab::Ci::ProjectConfig do it 'returns root config including the path and refname to another repository' do expect(config.source).to eq(:external_project_source) expect(config.content).to eq(config_content_result) + expect(config.url).to eq(<<~TEXT.chomp) + #{Settings.gitlab.base_url}/another-group/another-repo/-/blob/refname/path/to/.gitlab-ci.yml + TEXT + end + end + + context 'when config path is malformed' do + let(:ci_config_path) { 'path/to/.gitlab-ci.yml@malformed' } + + it 'returns nil' do + expect(config.url).to be_nil end end end @@ -122,6 +163,7 @@ RSpec.describe Gitlab::Ci::ProjectConfig do it 'returns root config including the canonical CI config file' do expect(config.source).to eq(:repository_source) expect(config.content).to eq(config_content_result) + expect(config.url).to eq("#{Settings.gitlab.base_url}/#{project.full_path}/-/blob/#{sha}/.gitlab-ci.yml") end end @@ -143,6 +185,12 @@ RSpec.describe Gitlab::Ci::ProjectConfig do expect(config.source).to eq(:auto_devops_source) expect(config.content).to eq(config_content_result) end + + it 'delegates url to Gitlab::Source' do + expect(Gitlab::Source).to receive(:blob_url).with('lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml') + + config.url + end end context 'when config is passed as a parameter' do @@ -159,6 +207,7 @@ RSpec.describe Gitlab::Ci::ProjectConfig do it 'returns the parameter content' do expect(config.source).to eq(:parameter_source) expect(config.content).to eq(content) + expect(config.url).to be_nil end end @@ -172,6 +221,7 @@ RSpec.describe Gitlab::Ci::ProjectConfig do it 'returns nil' do expect(config.source).to be_nil expect(config.content).to be_nil + expect(config.url).to be_nil end end end diff --git a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb index 33d322d0d44..3399c6dd9f4 100644 --- a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb +++ b/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor, :sentry do +RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor, :sentry, feature_category: :integrations do describe '.call' do let(:raven_required_options) do { diff --git a/spec/lib/gitlab/source_spec.rb b/spec/lib/gitlab/source_spec.rb index 0b2515baf2b..d00b946d954 100644 --- a/spec/lib/gitlab/source_spec.rb +++ b/spec/lib/gitlab/source_spec.rb @@ -54,4 +54,30 @@ RSpec.describe Gitlab::Source, feature_category: :shared do end end end + + describe '.blob_url' do + let(:source_file_path) { 'lib/gitlab/source.rb' } + + subject(:blob_url) { described_class.blob_url(source_file_path) } + + context 'when not on a pre-release' do + before do + stub_version('15.0.0-ee', 'a123a123') + end + + it 'returns blob url with tag as ref' do + expect(blob_url).to match("https://gitlab.com/gitlab-org/gitlab(-foss)?/-/blob/v15.0.0-ee/#{source_file_path}") + end + end + + context 'when on a pre-release' do + before do + stub_version('15.0.0-pre', 'a123a123') + end + + it 'returns blob url with commit as ref' do + expect(blob_url).to match("https://gitlab.com/gitlab-org/gitlab(-foss)?/-/blob/a123a123/#{source_file_path}") + end + end + end end diff --git a/spec/migrations/20230518071251_queue_backfill_code_suggestions_namespace_settings_spec.rb b/spec/migrations/20230518071251_queue_backfill_code_suggestions_namespace_settings_spec.rb new file mode 100644 index 00000000000..1be3f84a6a1 --- /dev/null +++ b/spec/migrations/20230518071251_queue_backfill_code_suggestions_namespace_settings_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe QueueBackfillCodeSuggestionsNamespaceSettings, feature_category: :code_suggestions do + let!(:batched_migration) { described_class::MIGRATION } + + it 'schedules a new batched migration' do + reversible_migration do |migration| + migration.before -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + + migration.after -> { + expect(batched_migration).to have_scheduled_batched_migration( + table_name: :namespace_settings, + column_name: :namespace_id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + } + end + end +end diff --git a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb index 596af1110cc..3b1b68d00bb 100644 --- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb +++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb @@ -137,6 +137,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego let(:send_request) { subject } end + it_behaves_like 'runner migrations backoff' do + let(:request) { subject } + end + it "doesn't update runner info" do expect { subject }.not_to change { runner.reload.contacted_at } end @@ -299,6 +303,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego end end + it_behaves_like 'runner migrations backoff' do + let(:request) { upload_artifacts(file_upload, headers_with_token) } + end + it "doesn't update runner info" do expect { upload_artifacts(file_upload, headers_with_token) }.not_to change { runner.reload.contacted_at } end @@ -901,6 +909,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego let(:send_request) { download_artifact } end + it_behaves_like 'runner migrations backoff' do + let(:request) { download_artifact } + end + it "doesn't update runner info" do expect { download_artifact }.not_to change { runner.reload.contacted_at } end diff --git a/spec/requests/api/ci/runner/jobs_put_spec.rb b/spec/requests/api/ci/runner/jobs_put_spec.rb index ab7ab4e74f8..65489ea7015 100644 --- a/spec/requests/api/ci/runner/jobs_put_spec.rb +++ b/spec/requests/api/ci/runner/jobs_put_spec.rb @@ -38,6 +38,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego let(:send_request) { update_job(state: 'success') } end + it_behaves_like 'runner migrations backoff' do + let(:request) { update_job(state: 'success') } + end + it 'updates runner info' do expect { update_job(state: 'success') }.to change { runner.reload.contacted_at } .and change { runner_manager.reload.contacted_at } diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb index 0164eda7680..ca57208eb1d 100644 --- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb +++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb @@ -90,6 +90,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego end end + it_behaves_like 'runner migrations backoff' do + let(:request) { post api('/jobs/request') } + end + context 'when no token is provided' do it 'returns 400 error' do post api('/jobs/request') diff --git a/spec/requests/api/ci/runner/jobs_trace_spec.rb b/spec/requests/api/ci/runner/jobs_trace_spec.rb index de67cec0a27..ee00fc5a793 100644 --- a/spec/requests/api/ci/runner/jobs_trace_spec.rb +++ b/spec/requests/api/ci/runner/jobs_trace_spec.rb @@ -45,6 +45,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_trace_chunks, feature_catego let(:send_request) { patch_the_trace } end + it_behaves_like 'runner migrations backoff' do + let(:request) { patch_the_trace } + end + it 'updates runner info' do runner.update!(contacted_at: 1.year.ago) diff --git a/spec/requests/api/ci/runner/runners_delete_spec.rb b/spec/requests/api/ci/runner/runners_delete_spec.rb index 681dd4d701e..d1488828bad 100644 --- a/spec/requests/api/ci/runner/runners_delete_spec.rb +++ b/spec/requests/api/ci/runner/runners_delete_spec.rb @@ -21,6 +21,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego end describe 'DELETE /api/v4/runners' do + it_behaves_like 'runner migrations backoff' do + let(:request) { delete api('/runners') } + end + context 'when no token is provided' do it 'returns 400 error' do delete api('/runners') diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb index a36ea2115cf..c5e49e9ac54 100644 --- a/spec/requests/api/ci/runner/runners_post_spec.rb +++ b/spec/requests/api/ci/runner/runners_post_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_category: :runner_fleet do describe '/api/v4/runners' do describe 'POST /api/v4/runners' do + it_behaves_like 'runner migrations backoff' do + let(:request) { post api('/runners') } + end + context 'when no token is provided' do it 'returns 400 error' do post api('/runners') diff --git a/spec/requests/api/ci/runner/runners_reset_spec.rb b/spec/requests/api/ci/runner/runners_reset_spec.rb index 2d1e366e820..03cb6238fc1 100644 --- a/spec/requests/api/ci/runner/runners_reset_spec.rb +++ b/spec/requests/api/ci/runner/runners_reset_spec.rb @@ -19,6 +19,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], token_expires_at: 1.day.from_now) } describe 'POST /runners/reset_authentication_token', :freeze_time do + it_behaves_like 'runner migrations backoff' do + let(:request) { post api("/runners/reset_authentication_token") } + end + context 'current token provided' do it "resets authentication token when token doesn't have an expiration" do expect do diff --git a/spec/requests/api/ci/runner/runners_verify_post_spec.rb b/spec/requests/api/ci/runner/runners_verify_post_spec.rb index f1b33826f5e..e6af61ca7e0 100644 --- a/spec/requests/api/ci/runner/runners_verify_post_spec.rb +++ b/spec/requests/api/ci/runner/runners_verify_post_spec.rb @@ -24,6 +24,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego subject(:verify) { post api('/runners/verify'), params: params } + it_behaves_like 'runner migrations backoff' do + let(:request) { verify } + end + context 'when no token is provided' do it 'returns 400 error' do post api('/runners/verify') diff --git a/spec/serializers/integrations/field_entity_spec.rb b/spec/serializers/integrations/field_entity_spec.rb index 4d190b9a98e..25ac0aa4911 100644 --- a/spec/serializers/integrations/field_entity_spec.rb +++ b/spec/serializers/integrations/field_entity_spec.rb @@ -25,8 +25,7 @@ RSpec.describe Integrations::FieldEntity, feature_category: :integrations do name: 'username', title: 'Email or username', placeholder: nil, - help: 'Only required for Basic authentication. ' \ - 'Email for Jira Cloud or username for Jira Data Center and Jira Server', + help: 'Email for Jira Cloud or username for Jira Data Center and Jira Server', required: false, choices: nil, value: 'jira_username', @@ -45,7 +44,7 @@ RSpec.describe Integrations::FieldEntity, feature_category: :integrations do section: 'connection', type: 'password', name: 'password', - title: 'New API token, password, or Jira personal access token', + title: 'New API token or password', placeholder: nil, help: 'Leave blank to use your current configuration', required: true, diff --git a/spec/support/shared_contexts/features/integrations/project_integrations_jira_context.rb b/spec/support/shared_contexts/features/integrations/project_integrations_jira_context.rb index f16d19e5858..fadd46a7e12 100644 --- a/spec/support/shared_contexts/features/integrations/project_integrations_jira_context.rb +++ b/spec/support/shared_contexts/features/integrations/project_integrations_jira_context.rb @@ -10,6 +10,5 @@ RSpec.shared_context 'project integration Jira context' do fill_in 'service_url', with: url fill_in 'service_username', with: 'username' fill_in 'service_password', with: 'password' - select('Basic', from: 'service_jira_auth_type') end end diff --git a/spec/support/shared_examples/ci/runner_migrations_backoff_shared_examples.rb b/spec/support/shared_examples/ci/runner_migrations_backoff_shared_examples.rb new file mode 100644 index 00000000000..06a8e8811b7 --- /dev/null +++ b/spec/support/shared_examples/ci/runner_migrations_backoff_shared_examples.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'runner migrations backoff' do + context 'when executing locking database migrations' do + it 'returns 429 error', :aggregate_failures do + expect(Gitlab::Database::Migrations::RunnerBackoff::Communicator) + .to receive(:backoff_runner?) + .and_return(true) + + request + + expect(response).to have_gitlab_http_status(:too_many_requests) + expect(response.headers['Retry-After']).to eq(60) + expect(json_response).to match({ "message" => "Executing database migrations. Please retry later." }) + end + + context 'with runner_migrations_backoff disabled' do + before do + stub_feature_flags(runner_migrations_backoff: false) + end + + it 'does not return 429' do + expect(Gitlab::ExclusiveLease).not_to receive(:new) + .with(Gitlab::Database::Migrations::RunnerBackoff::Communicator::KEY, + timeout: Gitlab::Database::Migrations::RunnerBackoff::Communicator::EXPIRY) + + request + + expect(response).not_to have_gitlab_http_status(:too_many_requests) + end + end + end +end diff --git a/spec/support/shared_examples/integrations/integration_settings_form.rb b/spec/support/shared_examples/integrations/integration_settings_form.rb index c43bdfa53ff..1d7f74837f2 100644 --- a/spec/support/shared_examples/integrations/integration_settings_form.rb +++ b/spec/support/shared_examples/integrations/integration_settings_form.rb @@ -20,6 +20,8 @@ RSpec.shared_examples 'integration settings form' do fields = parse_json(fields_for_integration(integration)) fields.each do |field| + next if exclude_field?(integration, field) + field_name = field[:name] expect(page).to have_field(field[:title], wait: 0), "#{integration.title} field #{field_name} not present" @@ -54,6 +56,11 @@ RSpec.shared_examples 'integration settings form' do Gitlab::Json.parse(json, symbolize_names: true) end + # Fields that have specific handling on the frontend + def exclude_field?(integration, field) + integration.is_a?(Integrations::Jira) && field[:name] == 'jira_auth_type' + end + def trigger_event_title(name) # Should match `integrationTriggerEventTitles` in app/assets/javascripts/integrations/constants.js event_titles = { |