Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2024-01-16 09:08:05 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-16 09:08:05 +0300
commit2bc11b8442e9f68800cfed57f9abad3f2dfc0b78 (patch)
tree373aa469b310bbd2f30dfa60ebdc2c6e18c4e4e9
parent21de0d5578ba4b6e4f7ad0667ecdaaf0810f4235 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/qa-common/rules.gitlab-ci.yml6
-rw-r--r--.gitlab/ci/test-on-gdk/main.gitlab-ci.yml123
-rw-r--r--.rubocop_todo/rspec/before_all_role_assignment.yml1
-rw-r--r--.rubocop_todo/rspec/named_subject.yml3
-rw-r--r--app/assets/javascripts/commons/vue.js2
-rw-r--r--app/assets/javascripts/diffs/components/app.vue10
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue15
-rw-r--r--app/assets/javascripts/diffs/index.js1
-rw-r--r--app/assets/javascripts/import/constants.js6
-rw-r--r--app/workers/all_queues.yml9
-rw-r--r--app/workers/concerns/worker_attributes.rb7
-rw-r--r--app/workers/concurrency_limit/resume_worker.rb64
-rw-r--r--config/events/1669818009_IncidentManagement__TimelineEvents__CreateService_incident_management_timeline_event_.yml1
-rw-r--r--config/events/schema.json12
-rw-r--r--config/events/unique_users_visiting_ci_catalog.yml3
-rw-r--r--config/feature_flags/ops/sidekiq_concurrency_limit_middleware.yml8
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--db/migrate/20240109031624_add_unique_index_to_system_note_metadata_on_id_convert_to_bigint.rb18
-rw-r--r--db/schema_migrations/202401090316241
-rw-r--r--db/structure.sql2
-rw-r--r--doc/administration/gitaly/praefect.md2
-rw-r--r--doc/administration/postgresql/replication_and_failover.md4
-rw-r--r--doc/api/graphql/reference/index.md10
-rw-r--r--doc/api/packages.md4
-rw-r--r--doc/ci/runners/runners_scope.md2
-rw-r--r--doc/ci/secrets/index.md2
-rw-r--r--doc/development/sidekiq/limited_capacity_worker.md5
-rw-r--r--doc/development/sidekiq/worker_attributes.md41
-rw-r--r--doc/user/ai_features.md4
-rw-r--r--doc/user/group/settings/group_access_tokens.md2
-rw-r--r--doc/user/packages/generic_packages/index.md2
-rw-r--r--doc/user/project/merge_requests/ai_in_merge_requests.md28
-rw-r--r--lib/gitlab/redis/multi_store.rb1
-rw-r--r--lib/gitlab/sidekiq_logging/concurrency_limit_logger.rb30
-rw-r--r--lib/gitlab/sidekiq_middleware.rb2
-rw-r--r--lib/gitlab/sidekiq_middleware/concurrency_limit/client.rb13
-rw-r--r--lib/gitlab/sidekiq_middleware/concurrency_limit/concurrency_limit_service.rb102
-rw-r--r--lib/gitlab/sidekiq_middleware/concurrency_limit/middleware.rb85
-rw-r--r--lib/gitlab/sidekiq_middleware/concurrency_limit/server.rb13
-rw-r--r--lib/gitlab/sidekiq_middleware/concurrency_limit/workers_concurrency.rb38
-rw-r--r--lib/gitlab/sidekiq_middleware/concurrency_limit/workers_map.rb47
-rw-r--r--locale/gitlab.pot18
-rwxr-xr-xqa/gdk/entrypoint25
-rw-r--r--qa/gdk/gdk.yml8
-rw-r--r--qa/qa/flow/saml.rb1
-rw-r--r--qa/qa/page/settings/common.rb2
-rw-r--r--qa/qa/runtime/env.rb7
-rw-r--r--qa/qa/service/docker_run/base.rb38
-rw-r--r--qa/qa/service/docker_run/gitlab_runner.rb2
-rw-r--r--qa/qa/service/docker_run/saml_idp.rb29
-rw-r--r--qa/qa/service/docker_run/smocker.rb29
-rw-r--r--qa/qa/support/knapsack_report.rb4
-rw-r--r--qa/spec/service/docker_run/smocker_spec.rb36
-rw-r--r--spec/frontend/diffs/components/app_spec.js2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/concurrency_limit/client_spec.rb58
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/concurrency_limit/concurrency_limit_service_spec.rb190
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/concurrency_limit/server_spec.rb107
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_concurrency_spec.rb93
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_map_spec.rb75
-rw-r--r--spec/lib/gitlab/tracking/event_definition_spec.rb8
-rw-r--r--vite.config.js33
61 files changed, 1217 insertions, 280 deletions
diff --git a/.gitlab/ci/qa-common/rules.gitlab-ci.yml b/.gitlab/ci/qa-common/rules.gitlab-ci.yml
index 9150c0fd97b..fd8a02a786b 100644
--- a/.gitlab/ci/qa-common/rules.gitlab-ci.yml
+++ b/.gitlab/ci/qa-common/rules.gitlab-ci.yml
@@ -152,12 +152,6 @@ include:
- <<: *if-schedule-pipeline
when: never
-.rules:test:gdk-load-balancer-changes:
- rules:
- - changes:
- - ".gitlab/ci/test-on-gdk/**"
- - "lib/gitlab/database/load_balancing/**/*"
-
.rules:test:qa-default-branch:
rules:
- *qa-run-all-e2e-label
diff --git a/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml b/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml
index 186025eff71..711d5bf4a02 100644
--- a/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml
+++ b/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml
@@ -21,11 +21,11 @@ include:
.rules:gdk:qa-parallel:
rules:
+ # To account for cases where a group label is set which may trigger selective execution
+ # But we want to execute full reliable suite on gdk in case of code-pattern-changes
- <<: *code-pattern-changes
variables:
QA_TESTS: ""
- # To account for cases where a group label is set which may trigger selective execution
- # But we want to execute full reliable suite on gdk in case of code-pattern-changes
- !reference [.rules:test:qa-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::Blocking/
- !reference [.rules:test:manual, rules]
@@ -41,14 +41,32 @@ include:
- if: $QA_SUITES =~ /Test::Instance::Smoke/
- !reference [.rules:test:manual, rules]
+.rules:test:gdk-load-balancer-changes:
+ rules:
+ - when: manual
+ allow_failure: true
+ changes:
+ - ".gitlab/ci/test-on-gdk/**"
+ - "lib/gitlab/database/load_balancing/**/*"
+
+.with-gdk-log:
+ after_script:
+ - mv $CI_BUILDS_DIR/*.log $CI_PROJECT_DIR/
+
.gdk-qa-base:
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}:bundler-${BUNDLER_VERSION}-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23
extends:
- .qa-cache
- .default-retry
+ - .gitlab-qa-report
stage: test
services:
- - docker:${DOCKER_VERSION}-dind
+ - name: docker:${DOCKER_VERSION}-dind
+ - name: ${GDK_IMAGE}
+ alias: gdk.test
+ # CI setup will tail each component in to separate file
+ # Override default command so we don't tail all of the logs to stdout unnecessarily
+ command: [gdk, tail, rails-web]
tags:
- e2e
variables:
@@ -56,51 +74,29 @@ include:
QA_GENERATE_ALLURE_REPORT: "true"
QA_CAN_TEST_PRAEFECT: "false"
QA_INTERCEPT_REQUESTS: "false"
+ QA_SUITE_STATUS_ENV_FILE: "$CI_PROJECT_DIR/suite_status.env"
+ QA_DOCKER_NETWORK: host
GITLAB_QA_ADMIN_ACCESS_TOKEN: $QA_ADMIN_ACCESS_TOKEN
RSPEC_REPORT_OPTS: "--order random --force-color --format documentation --format RspecJunitFormatter --out tmp/rspec-${CI_JOB_ID}.xml --format QA::Support::JsonFormatter --out tmp/rspec-${CI_JOB_ID}.json --format html --out tmp/rspec-${CI_JOB_ID}.htm"
- FF_NETWORK_PER_BUILD: 1
GDK_URL: http://gdk.test:3000
- KNAPSACK_TEST_FILE_PATTERN: "qa/specs/features/**/*_spec.rb"
- QA_SUITE_STATUS_ENV_FILE: "$CI_PROJECT_DIR/suite_status.env"
+ FF_NETWORK_PER_BUILD: "true"
before_script:
- echo "SUITE_RAN=true" > "$QA_SUITE_STATUS_ENV_FILE"
- - echo -e "\e[0Ksection_start:`date +%s`:pull_image[collapsed=true]\r\e[0KPull GDK QA image"
- - docker pull ${GDK_IMAGE}
- - echo -e "\e[0Ksection_end:`date +%s`:pull_image\r\e[0K"
- # Despite `incremental: false` and `static: true`, GDK sometimes fails to start without increasing max user watches
- # This is why we're not running the GDK container as a service
- - sysctl -n -w fs.inotify.max_user_watches=524288
- - echo -e "\e[0Ksection_start:`date +%s`:launch_gdk[collapsed=true]\r\e[0KLaunch GDK"
- - mkdir -p $CI_PROJECT_DIR/log/gdk $CI_PROJECT_DIR/log/gitlab
- # This command matches the permissions of the user that runs GDK inside the container.
- - chown -R 1000:1000 $CI_PROJECT_DIR/log
- - |
- docker run -d --rm --name gdk --network host \
- --volume $CI_PROJECT_DIR/log/gdk:/home/gdk/gitlab-development-kit/log \
- --volume $CI_PROJECT_DIR/log/gitlab:/home/gdk/gitlab-development-kit/gitlab/log \
- ${GDK_IMAGE}
- # With `FF_NETWORK_PER_BUILD=1` and `--network host` the IP of the gdk container should be 172.18.0.2, but we get it
- # dynamically just in case
- - echo "$(docker exec gdk bash -c "getent hosts \$HOSTNAME" | awk '{print $1}') gdk.test" >> /etc/hosts
- - echo -e "\e[0Ksection_end:`date +%s`:launch_gdk\r\e[0K"
- echo -e "\e[0Ksection_start:`date +%s`:install_gems[collapsed=true]\r\e[0KInstall gems"
- source scripts/utils.sh
- cd qa && bundle config set --local without 'development' && bundle install
- echo -e "\e[0Ksection_end:`date +%s`:install_gems\r\e[0K"
script:
- - echo -e "\e[0Ksection_start:`date +%s`:healthcheck[collapsed=true]\r\e[0KWait for gdk to start"
- - retry_times_sleep 100 3 test_url $GDK_URL/users/sign_in
- - echo -e "\e[0Ksection_end:`date +%s`:healthcheck\r\e[0K"
- - echo -e "\e[0Ksection_start:`date +%s`:run_tests\r\e[0KRun E2E tests"
- export QA_COMMAND="bundle exec bin/qa ${QA_SCENARIO:=Test::Instance::All} $GDK_URL $GITLAB_QA_OPTS -- $QA_TESTS $QA_RSPEC_TAGS $RSPEC_REPORT_OPTS"
- echo "Running - '$QA_COMMAND'"
- eval "$QA_COMMAND"
- - echo -e "\e[0Ksection_end:`date +%s`:run_tests\r\e[0K"
+ after_script:
+ - !reference [.with-gdk-log, after_script]
+ - !reference [.gitlab-qa-report, after_script]
artifacts:
paths:
- qa/tmp
- - log/gitlab
- - log/gdk/*/current
+ - ${CI_PROJECT_DIR}/*.log
reports:
junit: qa/tmp/rspec-*.xml
dotenv: "$QA_SUITE_STATUS_ENV_FILE"
@@ -111,15 +107,8 @@ include:
# balancing. Adding 5s lag to 1 of the replicas to validate robustness of
# the load balancer.
.gdk-with-load-balancer-setup:
- before_script:
- - !reference [".gdk-qa-base", "before_script"]
- - |
- docker exec gdk bash -c "
- gdk config set postgresql.replica.enabled true &&\
- gdk config set postgresql.replica_2.enabled true &&\
- gdk config set load_balancing.enabled true &&\
- gdk reconfigure &&\
- gdk restart"
+ variables:
+ WITH_LOAD_BALANCER: "true"
# ==========================================
# Pre stage
@@ -141,37 +130,21 @@ download-knapsack-report:
# ==========================================
# Test stage
# ==========================================
+
+# ------------------------------------------
+# Blocking tests
+# ------------------------------------------
gdk-qa-smoke:
extends:
- .gdk-qa-base
- - .gitlab-qa-report
- .rules:gdk:qa-smoke
variables:
QA_SCENARIO: Test::Instance::Smoke
QA_RUN_TYPE: gdk-qa-smoke
-gdk-qa-smoke-with-load-balancer:
- extends:
- - .gdk-qa-base
- - .gdk-with-load-balancer-setup
- variables:
- QA_SCENARIO: Test::Instance::Smoke
- QA_RUN_TYPE: gdk-qa-smoke
- artifacts:
- paths:
- - log
- reports:
- dotenv: ""
- rules:
- - !reference [".rules:test:never-schedule-pipeline", rules]
- - !reference [".rules:test:gdk-load-balancer-changes", rules]
- allow_failure: true
-
gdk-qa-reliable:
extends:
- .gdk-qa-base
- - .gitlab-qa-report
- - .parallel
- .rules:gdk:qa-parallel
variables:
QA_SCENARIO: Test::Instance::Blocking
@@ -181,20 +154,40 @@ gdk-qa-reliable:
gdk-qa-reliable-selective:
extends:
- .gdk-qa-base
- - .gitlab-qa-report
- .rules:gdk:qa-selective
variables:
QA_SCENARIO: Test::Instance::Blocking
QA_RUN_TYPE: gdk-qa-blocking
+# ------------------------------------------
+# Non Blocking tests
+# ------------------------------------------
+gdk-qa-smoke-with-load-balancer:
+ extends:
+ - .gdk-qa-base
+ - .gdk-with-load-balancer-setup
+ - .with-gdk-log
+ variables:
+ QA_SCENARIO: Test::Instance::Smoke
+ QA_RUN_TYPE: gdk-qa-smoke
+ artifacts:
+ paths:
+ - gdk.log
+ reports:
+ dotenv: ""
+ rules:
+ - !reference [".rules:test:never-schedule-pipeline", rules]
+ - !reference [".rules:test:gdk-load-balancer-changes", rules]
+
gdk-qa-reliable-with-load-balancer:
extends:
- .gdk-qa-base
- .gdk-with-load-balancer-setup
- - .parallel
+ - .with-gdk-log
variables:
QA_SCENARIO: Test::Instance::Blocking
QA_RUN_TYPE: gdk-qa-blocking
+ parallel: 5
artifacts:
paths:
- log
@@ -203,15 +196,15 @@ gdk-qa-reliable-with-load-balancer:
rules:
- !reference [".rules:test:never-schedule-pipeline", rules]
- !reference [".rules:test:gdk-load-balancer-changes", rules]
- allow_failure: true
gdk-qa-non-blocking:
extends:
- .gdk-qa-base
- - .parallel
+ - .with-gdk-log
variables:
QA_SCENARIO: Test::Instance::NonBlocking
QA_RUN_TYPE: gdk-qa-non-blocking
+ parallel: 5
rules:
- when: manual
allow_failure: true
diff --git a/.rubocop_todo/rspec/before_all_role_assignment.yml b/.rubocop_todo/rspec/before_all_role_assignment.yml
index 1d015869b37..c689e1d711d 100644
--- a/.rubocop_todo/rspec/before_all_role_assignment.yml
+++ b/.rubocop_todo/rspec/before_all_role_assignment.yml
@@ -679,7 +679,6 @@ RSpec/BeforeAllRoleAssignment:
- 'ee/spec/services/llm/generate_commit_message_service_spec.rb'
- 'ee/spec/services/llm/generate_description_service_spec.rb'
- 'ee/spec/services/llm/generate_summary_service_spec.rb'
- - 'ee/spec/services/llm/generate_test_file_service_spec.rb'
- 'ee/spec/services/llm/git_command_service_spec.rb'
- 'ee/spec/services/llm/merge_requests/summarize_diff_service_spec.rb'
- 'ee/spec/services/llm/merge_requests/summarize_review_service_spec.rb'
diff --git a/.rubocop_todo/rspec/named_subject.yml b/.rubocop_todo/rspec/named_subject.yml
index f4135a8960e..dd03eedb923 100644
--- a/.rubocop_todo/rspec/named_subject.yml
+++ b/.rubocop_todo/rspec/named_subject.yml
@@ -476,14 +476,12 @@ RSpec/NamedSubject:
- 'ee/spec/lib/gitlab/llm/templates/explain_vulnerability_spec.rb'
- 'ee/spec/lib/gitlab/llm/templates/fill_in_merge_request_template_spec.rb'
- 'ee/spec/lib/gitlab/llm/templates/generate_commit_message_spec.rb'
- - 'ee/spec/lib/gitlab/llm/templates/generate_test_file_spec.rb'
- 'ee/spec/lib/gitlab/llm/templates/summarize_merge_request_spec.rb'
- 'ee/spec/lib/gitlab/llm/templates/summarize_review_spec.rb'
- 'ee/spec/lib/gitlab/llm/templates/summarize_submitted_review_spec.rb'
- 'ee/spec/lib/gitlab/llm/vertex_ai/completions/analyze_ci_job_failure_spec.rb'
- 'ee/spec/lib/gitlab/llm/vertex_ai/completions/fill_in_merge_request_template_spec.rb'
- 'ee/spec/lib/gitlab/llm/vertex_ai/completions/generate_commit_message_spec.rb'
- - 'ee/spec/lib/gitlab/llm/vertex_ai/completions/generate_test_file_spec.rb'
- 'ee/spec/lib/gitlab/llm/vertex_ai/completions/summarize_merge_request_spec.rb'
- 'ee/spec/lib/gitlab/llm/vertex_ai/completions/summarize_review_spec.rb'
- 'ee/spec/lib/gitlab/llm/vertex_ai/completions/summarize_submitted_review_spec.rb'
@@ -1080,7 +1078,6 @@ RSpec/NamedSubject:
- 'ee/spec/services/llm/explain_code_service_spec.rb'
- 'ee/spec/services/llm/explain_vulnerability_service_spec.rb'
- 'ee/spec/services/llm/generate_commit_message_service_spec.rb'
- - 'ee/spec/services/llm/generate_test_file_service_spec.rb'
- 'ee/spec/services/llm/git_command_service_spec.rb'
- 'ee/spec/services/llm/resolve_vulnerability_service_spec.rb'
- 'ee/spec/services/merge_request_approval_settings/update_service_spec.rb'
diff --git a/app/assets/javascripts/commons/vue.js b/app/assets/javascripts/commons/vue.js
index 09d2e065e58..cd24a503631 100644
--- a/app/assets/javascripts/commons/vue.js
+++ b/app/assets/javascripts/commons/vue.js
@@ -9,4 +9,4 @@ if (process.env.NODE_ENV !== 'production') {
Vue.use(GlFeatureFlagsPlugin);
Vue.use(Translate);
-Vue.config.ignoredElements = ['gl-emoji', 'copy-code'];
+Vue.config.ignoredElements = ['gl-emoji'];
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 698fd3909ed..978e6731e9d 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -85,8 +85,6 @@ export default {
GlPagination,
GlSprintf,
GlAlert,
- GenerateTestFileDrawer: () =>
- import('ee_component/ai/components/generate_test_file_drawer.vue'),
},
mixins: [glFeatureFlagsMixin()],
alerts: {
@@ -240,7 +238,6 @@ export default {
'showWhitespace',
'targetBranchName',
'branchName',
- 'generateTestFilePath',
]),
...mapGetters('diffs', [
'whichCollapsedTypes',
@@ -443,7 +440,6 @@ export default {
'navigateToDiffFileIndex',
'setFileByFile',
'disableVirtualScroller',
- 'setGenerateTestFilePath',
'fetchPinnedFile',
]),
...mapActions('findingsDrawer', ['setDrawer']),
@@ -818,11 +814,5 @@ export default {
</div>
</div>
</div>
- <generate-test-file-drawer
- v-if="getNoteableData.id"
- :resource-id="resourceId"
- :file-path="generateTestFilePath"
- @close="() => setGenerateTestFilePath('')"
- />
</div>
</template>
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 97db0fc1c24..3dd52777267 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -54,11 +54,6 @@ export default {
compareButtonLabel: __('Compare submodule commit revisions'),
fileModeTooltip: __('File permissions'),
},
- inject: {
- showGenerateTestFileButton: {
- default: false,
- },
- },
props: {
discussionPath: {
type: String,
@@ -225,7 +220,6 @@ export default {
'reviewFile',
'setFileCollapsedByUser',
'setFileForcedOpen',
- 'setGenerateTestFilePath',
'toggleFileCommentForm',
'unpinFile',
]),
@@ -470,15 +464,6 @@ export default {
{{ __('Open in Web IDE') }}
</gl-dropdown-item>
<gl-dropdown-item
- v-if="showGenerateTestFileButton"
- @click="setGenerateTestFilePath(diffFile.new_path)"
- >
- <span class="gl-display-flex gl-justify-content-space-between gl-align-items-center">
- {{ __('Suggest test cases') }}
- <gl-icon name="tanuki-ai" class="gl-text-purple-600 gl-mr-n3" />
- </span>
- </gl-dropdown-item>
- <gl-dropdown-item
v-if="diffFile.replaced_view_path"
ref="replacedFileButton"
:href="diffFile.replaced_view_path"
diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js
index b219b5499c9..303f010e56f 100644
--- a/app/assets/javascripts/diffs/index.js
+++ b/app/assets/javascripts/diffs/index.js
@@ -28,7 +28,6 @@ export default function initDiffsApp(store = notesStore) {
apolloProvider,
provide: {
newCommentTemplatePath: dataset.newCommentTemplatePath,
- showGenerateTestFileButton: parseBoolean(dataset.showGenerateTestFileButton),
},
data() {
return {
diff --git a/app/assets/javascripts/import/constants.js b/app/assets/javascripts/import/constants.js
index cbb01d0bbf1..c7917334ffd 100644
--- a/app/assets/javascripts/import/constants.js
+++ b/app/assets/javascripts/import/constants.js
@@ -19,14 +19,14 @@ export const BULK_IMPORT_STATIC_ITEMS = {
const STATISTIC_ITEMS = {
diff_note: __('Diff notes'),
issue: __('Issues'),
- issue_attachment: s__('GithubImporter|Issue links'),
+ issue_attachment: s__('GithubImporter|Issue attachments'),
issue_event: __('Issue events'),
label: __('Labels'),
lfs_object: __('LFS objects'),
merge_request_attachment: s__('GithubImporter|PR attachments'),
milestone: __('Milestones'),
note: __('Notes'),
- note_attachment: s__('GithubImporter|Note links'),
+ note_attachment: s__('GithubImporter|Note attachments'),
protected_branch: __('Protected branches'),
collaborator: s__('GithubImporter|Collaborators'),
pull_request: s__('GithubImporter|Pull requests'),
@@ -34,7 +34,7 @@ const STATISTIC_ITEMS = {
pull_request_review: s__('GithubImporter|PR reviews'),
pull_request_review_request: s__('GithubImporter|PR reviewers'),
release: __('Releases'),
- release_attachment: s__('GithubImporter|Release links'),
+ release_attachment: s__('GithubImporter|Release attachments'),
};
// support both camel case and snake case versions
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index dfad9f7f673..0f13af61e3d 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -363,6 +363,15 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: cronjob:concurrency_limit_resume
+ :worker_name: ConcurrencyLimit::ResumeWorker
+ :feature_category: :global_search
+ :has_external_dependencies: false
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: cronjob:container_expiration_policy
:worker_name: ContainerExpirationPolicyWorker
:feature_category: :container_registry
diff --git a/app/workers/concerns/worker_attributes.rb b/app/workers/concerns/worker_attributes.rb
index 28c82a5a38e..a1c5039dc22 100644
--- a/app/workers/concerns/worker_attributes.rb
+++ b/app/workers/concerns/worker_attributes.rb
@@ -159,6 +159,13 @@ module WorkerAttributes
::Gitlab::SidekiqMiddleware::PauseControl::WorkersMap.strategy_for(worker: self)
end
+ def concurrency_limit(max_jobs)
+ ::Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersMap.set_limit_for(
+ worker: self,
+ max_jobs: max_jobs
+ )
+ end
+
def get_weight
get_class_attribute(:weight) ||
NAMESPACE_WEIGHTS[queue_namespace] ||
diff --git a/app/workers/concurrency_limit/resume_worker.rb b/app/workers/concurrency_limit/resume_worker.rb
new file mode 100644
index 00000000000..e88277dc581
--- /dev/null
+++ b/app/workers/concurrency_limit/resume_worker.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module ConcurrencyLimit
+ class ResumeWorker
+ include ApplicationWorker
+ include CronjobQueue # rubocop:disable Scalability/CronWorkerContext -- There is no onward scheduling and this cron handles work from across the
+ # application, so there's no useful context to add.
+
+ DEFAULT_LIMIT = 1_000
+ RESCHEDULE_DELAY = 1.second
+
+ feature_category :global_search
+ data_consistency :sticky
+ idempotent!
+ urgency :low
+
+ def perform
+ reschedule_job = false
+
+ workers.each do |worker|
+ next unless jobs_in_the_queue?(worker)
+
+ reschedule_job = true
+
+ limit = ::Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersMap.limit_for(worker: worker)&.call
+
+ processing_limit = if limit
+ current = current_concurrency(worker: worker)
+ limit - current
+ else
+ DEFAULT_LIMIT
+ end
+
+ next unless processing_limit > 0
+
+ resume_processing!(worker, limit: processing_limit)
+ end
+
+ self.class.perform_in(RESCHEDULE_DELAY) if reschedule_job
+ end
+
+ private
+
+ def current_concurrency(worker:)
+ @current_concurrency ||= ::Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersConcurrency.workers(
+ skip_cache: true
+ )
+
+ @current_concurrency[worker.name].to_i
+ end
+
+ def jobs_in_the_queue?(worker)
+ Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService.has_jobs_in_queue?(worker.name)
+ end
+
+ def resume_processing!(worker, limit:)
+ Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService.resume_processing!(worker.name, limit: limit)
+ end
+
+ def workers
+ Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersMap.workers
+ end
+ end
+end
diff --git a/config/events/1669818009_IncidentManagement__TimelineEvents__CreateService_incident_management_timeline_event_.yml b/config/events/1669818009_IncidentManagement__TimelineEvents__CreateService_incident_management_timeline_event_.yml
index c9dbf2eda20..98d8f25e93c 100644
--- a/config/events/1669818009_IncidentManagement__TimelineEvents__CreateService_incident_management_timeline_event_.yml
+++ b/config/events/1669818009_IncidentManagement__TimelineEvents__CreateService_incident_management_timeline_event_.yml
@@ -13,7 +13,6 @@ identifiers:
product_section: ops
product_stage: monitor
product_group: respond
-value_type: number
milestone: "15.7"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105223
distributions:
diff --git a/config/events/schema.json b/config/events/schema.json
index dc98353fbfa..dd92b36bc8e 100644
--- a/config/events/schema.json
+++ b/config/events/schema.json
@@ -1,8 +1,18 @@
{
"type": "object",
"required": [
-
+ "description",
+ "category",
+ "action",
+ "product_section",
+ "product_stage",
+ "product_group",
+ "introduced_by_url",
+ "milestone",
+ "tiers",
+ "distributions"
],
+ "additionalProperties": false,
"properties": {
"description": {
"type": "string"
diff --git a/config/events/unique_users_visiting_ci_catalog.yml b/config/events/unique_users_visiting_ci_catalog.yml
index f6abc839688..206e223b4e9 100644
--- a/config/events/unique_users_visiting_ci_catalog.yml
+++ b/config/events/unique_users_visiting_ci_catalog.yml
@@ -19,6 +19,3 @@ tiers:
- free
- premium
- ultimate
-events:
- - name: unique_users_visiting_ci_catalog
- unique: user.id
diff --git a/config/feature_flags/ops/sidekiq_concurrency_limit_middleware.yml b/config/feature_flags/ops/sidekiq_concurrency_limit_middleware.yml
new file mode 100644
index 00000000000..eb144d57d82
--- /dev/null
+++ b/config/feature_flags/ops/sidekiq_concurrency_limit_middleware.yml
@@ -0,0 +1,8 @@
+---
+name: sidekiq_concurrency_limit_middleware
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139851
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/435391
+milestone: '16.9'
+type: ops
+group: group::global search
+default_enabled: false
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 4b279db68b8..fd2a7ab6743 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -801,6 +801,9 @@ Gitlab.ee do
Settings.cron_jobs['pause_control_resume_worker'] ||= {}
Settings.cron_jobs['pause_control_resume_worker']['cron'] ||= '*/5 * * * *'
Settings.cron_jobs['pause_control_resume_worker']['job_class'] ||= 'PauseControl::ResumeWorker'
+ Settings.cron_jobs['concurrency_limit_resume_worker'] ||= {}
+ Settings.cron_jobs['concurrency_limit_resume_worker']['cron'] ||= '*/1 * * * *'
+ Settings.cron_jobs['concurrency_limit_resume_worker']['job_class'] ||= 'ConcurrencyLimit::ResumeWorker'
Settings.cron_jobs['sync_seat_link_worker'] ||= {}
Settings.cron_jobs['sync_seat_link_worker']['cron'] ||= "#{rand(60)} #{rand(3..4)} * * * UTC"
Settings.cron_jobs['sync_seat_link_worker']['job_class'] = 'SyncSeatLinkWorker'
diff --git a/db/migrate/20240109031624_add_unique_index_to_system_note_metadata_on_id_convert_to_bigint.rb b/db/migrate/20240109031624_add_unique_index_to_system_note_metadata_on_id_convert_to_bigint.rb
new file mode 100644
index 00000000000..d48832705af
--- /dev/null
+++ b/db/migrate/20240109031624_add_unique_index_to_system_note_metadata_on_id_convert_to_bigint.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddUniqueIndexToSystemNoteMetadataOnIdConvertToBigint < Gitlab::Database::Migration[2.2]
+ disable_ddl_transaction!
+
+ milestone '16.9'
+
+ TABLE_NAME = :system_note_metadata
+ INDEX_NAME = 'index_system_note_metadata_pkey_on_id_convert_to_bigint'
+
+ def up
+ add_concurrent_index TABLE_NAME, :id_convert_to_bigint, unique: true, name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index TABLE_NAME, :id_convert_to_bigint, unique: true, name: INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20240109031624 b/db/schema_migrations/20240109031624
new file mode 100644
index 00000000000..8210a8dc762
--- /dev/null
+++ b/db/schema_migrations/20240109031624
@@ -0,0 +1 @@
+eaa86ad90ae101a641f30090941d0a993fa0bd92709ed429d8f23ea5b91c04c7 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 7d07dfd2287..4102ce44c63 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -35471,6 +35471,8 @@ CREATE UNIQUE INDEX index_system_note_metadata_on_description_version_id ON syst
CREATE UNIQUE INDEX index_system_note_metadata_on_note_id ON system_note_metadata USING btree (note_id);
+CREATE UNIQUE INDEX index_system_note_metadata_pkey_on_id_convert_to_bigint ON system_note_metadata USING btree (id_convert_to_bigint);
+
CREATE INDEX index_taggings_on_tag_id ON taggings USING btree (tag_id);
CREATE INDEX index_taggings_on_taggable_id_and_taggable_type_and_context ON taggings USING btree (taggable_id, taggable_type, context);
diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md
index 112c441164d..53ec384ad0c 100644
--- a/doc/administration/gitaly/praefect.md
+++ b/doc/administration/gitaly/praefect.md
@@ -376,7 +376,7 @@ You should use PgBouncer with `session` pool mode. You can use the
The following example uses the bundled PgBouncer and sets up two separate connection pools on PostgreSQL host,
one in `session` pool mode and the other in `transaction` pool mode. For this example to work,
-you need to prepare PostgreSQL server as documented in [in the setup instructions](#manual-database-setup):
+you need to prepare PostgreSQL server as documented in [the setup instructions](#manual-database-setup):
```ruby
pgbouncer['databases'] = {
diff --git a/doc/administration/postgresql/replication_and_failover.md b/doc/administration/postgresql/replication_and_failover.md
index e93dfc8336e..573110d7f57 100644
--- a/doc/administration/postgresql/replication_and_failover.md
+++ b/doc/administration/postgresql/replication_and_failover.md
@@ -428,8 +428,8 @@ authentication mode (`patroni['tls_client_mode']`), must each have the same valu
# START user configuration
# Set the real values as explained in Required Information section
- # Replace CONSUL_PASSWORD_HASH with with a generated md5 value
- # Replace PGBOUNCER_PASSWORD_HASH with with a generated md5 value
+ # Replace CONSUL_PASSWORD_HASH with a generated md5 value
+ # Replace PGBOUNCER_PASSWORD_HASH with a generated md5 value
pgbouncer['users'] = {
'gitlab-consul': {
password: 'CONSUL_PASSWORD_HASH'
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index a8a29e2f01c..426d0b87bbb 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1384,7 +1384,6 @@ Input type: `AiActionInput`
| <a id="mutationaiactionfillinmergerequesttemplate"></a>`fillInMergeRequestTemplate` | [`AiFillInMergeRequestTemplateInput`](#aifillinmergerequesttemplateinput) | Input for fill_in_merge_request_template AI action. |
| <a id="mutationaiactiongeneratecommitmessage"></a>`generateCommitMessage` | [`AiGenerateCommitMessageInput`](#aigeneratecommitmessageinput) | Input for generate_commit_message AI action. |
| <a id="mutationaiactiongeneratedescription"></a>`generateDescription` | [`AiGenerateDescriptionInput`](#aigeneratedescriptioninput) | Input for generate_description AI action. |
-| <a id="mutationaiactiongeneratetestfile"></a>`generateTestFile` | [`GenerateTestFileInput`](#generatetestfileinput) | Input for generate_test_file AI action. |
| <a id="mutationaiactionresolvevulnerability"></a>`resolveVulnerability` | [`AiResolveVulnerabilityInput`](#airesolvevulnerabilityinput) | Input for resolve_vulnerability AI action. |
| <a id="mutationaiactionsummarizecomments"></a>`summarizeComments` | [`AiSummarizeCommentsInput`](#aisummarizecommentsinput) | Input for summarize_comments AI action. |
| <a id="mutationaiactionsummarizereview"></a>`summarizeReview` | [`AiSummarizeReviewInput`](#aisummarizereviewinput) | Input for summarize_review AI action. |
@@ -34334,15 +34333,6 @@ Represents an escalation rule.
| <a id="escalationruleinputstatus"></a>`status` | [`EscalationRuleStatus!`](#escalationrulestatus) | Status required to prevent the rule from activating. |
| <a id="escalationruleinputusername"></a>`username` | [`String`](#string) | Username of the user to notify. |
-### `GenerateTestFileInput`
-
-#### Arguments
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| <a id="generatetestfileinputfilepath"></a>`filePath` | [`String!`](#string) | File path to generate test files for. |
-| <a id="generatetestfileinputresourceid"></a>`resourceId` | [`AiModelID!`](#aimodelid) | Global ID of the resource to mutate. |
-
### `JiraUsersMappingInputType`
#### Arguments
diff --git a/doc/api/packages.md b/doc/api/packages.md
index 0d69078d1a1..3d2e8e5bfbf 100644
--- a/doc/api/packages.md
+++ b/doc/api/packages.md
@@ -32,7 +32,7 @@ GET /projects/:id/packages
| `package_name` | string | no | Filter the project packages with a fuzzy search by name. |
| `package_version` | string | no | Filter the project packages by version. If used in combination with `include_versionless`, then no versionless packages are returned. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/349065) in GitLab 16.6. |
| `include_versionless` | boolean | no | When set to true, versionless packages are included in the response. |
-| `status` | string | no | Filter the returned packages by status. One of `default` (default), `hidden`, `processing`, `error`, or `pending_destruction`. |
+| `status` | string | no | Filter the returned packages by status. One of `default`, `hidden`, `processing`, `error`, or `pending_destruction`. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/packages"
@@ -98,7 +98,7 @@ GET /groups/:id/packages
| `package_name` | string | no | Filter the project packages with a fuzzy search by name. |
| `package_version` | string | no | Filter the returned packages by version. If used in combination with `include_versionless`, then no versionless packages are returned. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/349065) in GitLab 16.6. |
| `include_versionless` | boolean | no | When set to true, versionless packages are included in the response. |
-| `status` | string | no | Filter the returned packages by status. One of `default` (default), `hidden`, `processing`, `error`, or `pending_destruction`. |
+| `status` | string | no | Filter the returned packages by status. One of `default`, `hidden`, `processing`, `error`, or `pending_destruction`. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/packages?exclude_subgroups=false"
diff --git a/doc/ci/runners/runners_scope.md b/doc/ci/runners/runners_scope.md
index d6a556ffd9b..230c5510111 100644
--- a/doc/ci/runners/runners_scope.md
+++ b/doc/ci/runners/runners_scope.md
@@ -641,7 +641,7 @@ To determine the IP address of a shared runner:
### Determine the IP address of a project runner
-To can find the IP address of a runner for a project project,
+To can find the IP address of a runner for a project,
you must have the Owner role for the
project.
diff --git a/doc/ci/secrets/index.md b/doc/ci/secrets/index.md
index 96b9709bdef..96a35703f1f 100644
--- a/doc/ci/secrets/index.md
+++ b/doc/ci/secrets/index.md
@@ -33,7 +33,7 @@ The [Authenticating and Reading Secrets With HashiCorp Vault](../examples/authen
tutorial has more details about authenticating with ID tokens.
You must [configure your Vault server](#configure-your-vault-server) before you
-can use [use Vault secrets in a CI job](#use-vault-secrets-in-a-ci-job).
+can [use Vault secrets in a CI job](#use-vault-secrets-in-a-ci-job).
The flow for using GitLab with HashiCorp Vault
is summarized by this diagram:
diff --git a/doc/development/sidekiq/limited_capacity_worker.md b/doc/development/sidekiq/limited_capacity_worker.md
index e8561cb936d..ff8728f3751 100644
--- a/doc/development/sidekiq/limited_capacity_worker.md
+++ b/doc/development/sidekiq/limited_capacity_worker.md
@@ -94,3 +94,8 @@ name as label:
- `limited_capacity_worker_running_jobs`
- `limited_capacity_worker_max_running_jobs`
- `limited_capacity_worker_remaining_work_count`
+
+## Alternatives
+
+If limited capacity worker doesn't fit your architecture, there's also a [concurrency limit](worker_attributes.md#concurrency-limit)
+attribute that can be used to restrict concurrency of a Sidekiq worker.
diff --git a/doc/development/sidekiq/worker_attributes.md b/doc/development/sidekiq/worker_attributes.md
index 016bf0b6634..072f34f0c96 100644
--- a/doc/development/sidekiq/worker_attributes.md
+++ b/doc/development/sidekiq/worker_attributes.md
@@ -365,3 +365,44 @@ class PausedWorker
# ...
end
```
+
+## Concurrency limit
+
+With the `concurrency_limit` property, you can limit the worker's concurrency. It will put the jobs that are over this limit in
+a separate `LIST` and re-enqueued when it falls under the limit. `ConcurrencyLimit::ResumeWorker` is a cron
+worker that checks if any throttled jobs should be re-enqueued.
+
+The first job that crosses the defined concurency limit initiates the throttling process for all other jobs of this class.
+Until this happens, jobs are scheduled and executed as usual.
+
+When the throttling starts, newly scheduled and executed jobs will be added to the end of the `LIST` to ensure that
+the execution order is preserved. As soon as the `LIST` is empty again, the throttling process ends.
+
+WARNING:
+If there is a sustained workload over the limit, the `LIST` is going to grow until the limit is disabled or
+the workload drops under the limit.
+
+FLAG:
+The feature is currently behind a default-disabled feature flag `sidekiq_concurrency_limit_middleware`.
+
+You should use a lambda to define the limit. If it returns `nil`, `0`, or a negative value, the limit won't be applied.
+
+```ruby
+class LimitedWorker
+ include ApplicationWorker
+
+ concurrency_limit -> { 60 }
+
+ # ...
+end
+```
+
+```ruby
+class LimitedWorker
+ include ApplicationWorker
+
+ concurrency_limit -> { ApplicationSetting.current.elasticsearch_concurrent_sidekiq_jobs }
+
+ # ...
+end
+```
diff --git a/doc/user/ai_features.md b/doc/user/ai_features.md
index 931876682e0..0659f39564c 100644
--- a/doc/user/ai_features.md
+++ b/doc/user/ai_features.md
@@ -18,7 +18,7 @@ GitLab is creating AI-assisted features across our DevSecOps platform. These fea
| Assists with quickly getting everyone up to speed on lengthy conversations to help ensure you are all on the same page. | [Discussion summary](#summarize-issue-discussions-with-discussion-summary) | **(ULTIMATE SAAS EXPERIMENT)** |
| Generates issue descriptions. | [Issue description generation](#summarize-an-issue-with-issue-description-generation) | **(ULTIMATE SAAS EXPERIMENT)** |
| Helps you write code more efficiently by viewing code suggestions as you type. <br><br><i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=hCAyCTacdAQ) | [Code Suggestions](project/repository/code_suggestions/index.md) | For SaaS: **(FREE)**<br><br> For self-managed: **(PREMIUM)** |
-| Automates repetitive tasks and helps catch bugs early. | [Test generation](project/merge_requests/ai_in_merge_requests.md#generate-suggested-tests-in-merge-requests) | **(ULTIMATE SAAS EXPERIMENT)** |
+| Automates repetitive tasks and helps catch bugs early. | [Test generation](gitlab_duo_chat.md#write-tests-in-the-ide) | **(ULTIMATE SAAS EXPERIMENT)** |
| Generates a description for the merge request based on the contents of the template. | [Merge request template population](project/merge_requests/ai_in_merge_requests.md#fill-in-merge-request-templates) | **(ULTIMATE SAAS EXPERIMENT)** |
| Assists in creating faster and higher-quality reviews by automatically suggesting reviewers for your merge request. <br><br><i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=ivwZQgh4Rxw) | [Suggested Reviewers](project/merge_requests/reviews/index.md#gitlab-duo-suggested-reviewers) | **(ULTIMATE SAAS)** |
| Efficiently communicates the impact of your merge request changes. | [Merge request summary](project/merge_requests/ai_in_merge_requests.md#summarize-merge-request-changes) | **(ULTIMATE SAAS EXPERIMENT)** |
@@ -194,7 +194,7 @@ For details about this Beta feature, see [GitLab Duo Chat](gitlab_duo_chat.md).
| [Discussion summary](#summarize-issue-discussions-with-discussion-summary) | Vertex AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) |
| [Issue description generation](#summarize-an-issue-with-issue-description-generation) | Anthropic [`Claude-2`](https://docs.anthropic.com/claude/reference/selecting-a-model) |
| [Code Suggestions](project/repository/code_suggestions/index.md) | For Code Completion: Vertex AI Codey [`code-gecko`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/code-completion) For Code Generation: Anthropic [`Claude-2`](https://docs.anthropic.com/claude/reference/selecting-a-model) |
-| [Test generation](project/merge_requests/ai_in_merge_requests.md#generate-suggested-tests-in-merge-requests) | Vertex AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) |
+| [Test generation](gitlab_duo_chat.md#write-tests-in-the-ide) | Vertex AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) |
| [Merge request template population](project/merge_requests/ai_in_merge_requests.md#fill-in-merge-request-templates) | Vertex AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) |
| [Suggested Reviewers](project/merge_requests/reviews/index.md#gitlab-duo-suggested-reviewers) | GitLab creates a machine learning model for each project, which is used to generate reviewers [View the issue](https://gitlab.com/gitlab-org/modelops/applied-ml/applied-ml-updates/-/issues/10) |
| [Merge request summary](project/merge_requests/ai_in_merge_requests.md#summarize-merge-request-changes) | Vertex AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) |
diff --git a/doc/user/group/settings/group_access_tokens.md b/doc/user/group/settings/group_access_tokens.md
index 5b3f962061e..a8633c0cbbe 100644
--- a/doc/user/group/settings/group_access_tokens.md
+++ b/doc/user/group/settings/group_access_tokens.md
@@ -82,7 +82,7 @@ or API. However, administrators can use a workaround:
# Set the GitLab administration user to use. If user ID 1 is not available or is not an administrator, use 'admin = User.admins.first' instead to select an administrator.
admin = User.find(1)
- # Set the group group you want to create a token for. For example, group with ID 109.
+ # Set the group you want to create a token for. For example, group with ID 109.
group = Group.find(109)
# Create the group bot user. For further group access tokens, the username should be `group_{group_id}_bot_{random_string}` and email address `group_{group_id}_bot_{random_string}@noreply.{Gitlab.config.gitlab.host}`.
diff --git a/doc/user/packages/generic_packages/index.md b/doc/user/packages/generic_packages/index.md
index 188b634af0a..6348a8b9da7 100644
--- a/doc/user/packages/generic_packages/index.md
+++ b/doc/user/packages/generic_packages/index.md
@@ -50,7 +50,7 @@ PUT /projects/:id/packages/generic/:package_name/:package_version/:file_name?sta
| `package_name` | string | yes | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`).
| `package_version` | string | yes | The package version. The following regex validates this: `\A(\.?[\w\+-]+\.?)+\z`. You can test your version strings on [Rubular](https://rubular.com/r/aNCV0wG5K14uq8).
| `file_name` | string | yes | The filename. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`).
-| `status` | string | no | The package status. It can be `default` (default) or `hidden`. Hidden packages do not appear in the UI or [package API list endpoints](../../../api/packages.md).
+| `status` | string | no | The package status. It can be `default` or `hidden`. Hidden packages do not appear in the UI or [package API list endpoints](../../../api/packages.md).
| `select` | string | no | The response payload. By default, the response is empty. Valid values are: `package_file`. `package_file` returns details of the package file record created by this request.
Provide the file context in the request body.
diff --git a/doc/user/project/merge_requests/ai_in_merge_requests.md b/doc/user/project/merge_requests/ai_in_merge_requests.md
index daddf5477ca..8be2d45f06b 100644
--- a/doc/user/project/merge_requests/ai_in_merge_requests.md
+++ b/doc/user/project/merge_requests/ai_in_merge_requests.md
@@ -94,27 +94,15 @@ Provide feedback on this experimental feature in [issue 408994](https://gitlab.c
- Contents of the file
- The file name
-## Generate suggested tests in merge requests
-
-> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10366) in GitLab 16.0 as an [Experiment](../../../policy/experiment-beta-support.md#experiment).
-
-This feature is an [Experiment](../../../policy/experiment-beta-support.md) on GitLab.com.
-
-Use GitLab Duo Test generation in a merge request to see a list of suggested tests for the file you are reviewing. This functionality can help determine if appropriate test coverage has been provided, or if you need more coverage for your project.
-
-View a [click-through demo](https://go.gitlab.com/Xfp0l4).
+<!--- start_remove The following content will be removed on remove_date: '2024-04-12' -->
-To generate a test suggestion:
-
-1. In a merge request, select the **Changes** tab.
-1. On the header for the file, in the upper-right corner, select **Options** (**{ellipsis_v}**).
-1. Select **Suggest test cases**.
-
-The test suggestion is generated in a sidebar. You can copy the suggestion to your editor and use it as the start of your tests.
+## Generate suggested tests in merge requests
-Feedback on this experimental feature can be provided in [issue 408995](https://gitlab.com/gitlab-org/gitlab/-/issues/408995).
+> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10366) in GitLab 16.0 as an [Experiment](../../../policy/experiment-beta-support.md#experiment).
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141086) to GitLab Duo Chat in GitLab 16.8.
-**Data usage**: When you use this feature, the following data is sent to the large language model referenced above:
+This feature was [moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141086)
+into GitLab Duo Chat in GitLab 16.8. Find more information in
+[Write tests in the IDE](../../gitlab_duo_chat.md#write-tests-in-the-ide).
-- Contents of the file
-- The file name
+<!--- end_remove -->
diff --git a/lib/gitlab/redis/multi_store.rb b/lib/gitlab/redis/multi_store.rb
index f5deff96eae..6d6dde1471b 100644
--- a/lib/gitlab/redis/multi_store.rb
+++ b/lib/gitlab/redis/multi_store.rb
@@ -99,6 +99,7 @@ module Gitlab
hset
incr
incrby
+ ltrim
mapped_hmset
pfadd
pfmerge
diff --git a/lib/gitlab/sidekiq_logging/concurrency_limit_logger.rb b/lib/gitlab/sidekiq_logging/concurrency_limit_logger.rb
new file mode 100644
index 00000000000..666cf7bbd90
--- /dev/null
+++ b/lib/gitlab/sidekiq_logging/concurrency_limit_logger.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqLogging
+ class ConcurrencyLimitLogger
+ include Singleton
+ include LogsJobs
+
+ def deferred_log(job)
+ payload = parse_job(job)
+ payload['job_status'] = 'concurrency_limit'
+ payload['message'] = "#{base_message(payload)}: concurrency_limit: paused"
+
+ Sidekiq.logger.info payload
+ end
+
+ def resumed_log(worker_name, args)
+ job = {
+ 'class' => worker_name,
+ 'args' => args
+ }
+ payload = parse_job(job)
+ payload['job_status'] = 'resumed'
+ payload['message'] = "#{base_message(payload)}: concurrency_limit: resumed"
+
+ Sidekiq.logger.info payload
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware.rb b/lib/gitlab/sidekiq_middleware.rb
index 96bda86ab08..b0cf165350b 100644
--- a/lib/gitlab/sidekiq_middleware.rb
+++ b/lib/gitlab/sidekiq_middleware.rb
@@ -38,6 +38,7 @@ module Gitlab
chain.add ::Gitlab::SidekiqMiddleware::WorkerContext::Server
chain.add ::Gitlab::SidekiqMiddleware::PauseControl::Server
chain.add ::ClickHouse::MigrationSupport::SidekiqMiddleware
+ chain.add ::Gitlab::SidekiqMiddleware::ConcurrencyLimit::Server
# DuplicateJobs::Server should be placed at the bottom, but before the SidekiqServerMiddleware,
# so we can compare the latest WAL location against replica
chain.add ::Gitlab::SidekiqMiddleware::DuplicateJobs::Server
@@ -57,6 +58,7 @@ module Gitlab
# so we can store WAL location before we deduplicate the job.
chain.add ::Gitlab::Database::LoadBalancing::SidekiqClientMiddleware
chain.add ::Gitlab::SidekiqMiddleware::PauseControl::Client
+ chain.add ::Gitlab::SidekiqMiddleware::ConcurrencyLimit::Client
chain.add ::Gitlab::SidekiqMiddleware::DuplicateJobs::Client
chain.add ::Gitlab::SidekiqStatus::ClientMiddleware
chain.add ::Gitlab::SidekiqMiddleware::AdminMode::Client
diff --git a/lib/gitlab/sidekiq_middleware/concurrency_limit/client.rb b/lib/gitlab/sidekiq_middleware/concurrency_limit/client.rb
new file mode 100644
index 00000000000..3784330ce23
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/concurrency_limit/client.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module ConcurrencyLimit
+ class Client
+ def call(worker_class, job, _queue, _redis_pool, &block)
+ ::Gitlab::SidekiqMiddleware::ConcurrencyLimit::Middleware.new(worker_class, job).schedule(&block)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/concurrency_limit/concurrency_limit_service.rb b/lib/gitlab/sidekiq_middleware/concurrency_limit/concurrency_limit_service.rb
new file mode 100644
index 00000000000..5a01f212f11
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/concurrency_limit/concurrency_limit_service.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module ConcurrencyLimit
+ class ConcurrencyLimitService
+ # Class for managing queues for deferred workers
+
+ def initialize(worker_name)
+ @worker_name = worker_name
+ @redis_key = "sidekiq:concurrency_limit:throttled_jobs:{#{worker_name.underscore}}"
+ end
+
+ class << self
+ def add_to_queue!(worker_name, args, context)
+ new(worker_name).add_to_queue!(args, context)
+ end
+
+ def has_jobs_in_queue?(worker_name)
+ new(worker_name).has_jobs_in_queue?
+ end
+
+ def resume_processing!(worker_name, limit:)
+ new(worker_name).resume_processing!(limit: limit)
+ end
+
+ def queue_size(worker_name)
+ new(worker_name).queue_size
+ end
+ end
+
+ def add_to_queue!(args, context)
+ with_redis do |redis|
+ redis.rpush(redis_key, serialize(args, context))
+ end
+ end
+
+ def queue_size
+ with_redis { |redis| redis.llen(redis_key) }
+ end
+
+ def has_jobs_in_queue?
+ queue_size != 0
+ end
+
+ def resume_processing!(limit:)
+ with_redis do |redis|
+ jobs = next_batch_from_queue(redis, limit: limit)
+ break if jobs.empty?
+
+ jobs.each { |j| send_to_processing_queue(deserialize(j)) }
+
+ remove_processed_jobs(redis, limit: jobs.length)
+
+ jobs.length
+ end
+ end
+
+ private
+
+ attr_reader :worker_name, :redis_key
+
+ def with_redis(&blk)
+ Gitlab::Redis::SharedState.with(&blk) # rubocop:disable CodeReuse/ActiveRecord -- Not active record
+ end
+
+ def serialize(args, context)
+ {
+ args: args,
+ context: context
+ }.to_json
+ end
+
+ def deserialize(json)
+ Gitlab::Json.parse(json)
+ end
+
+ def send_to_processing_queue(job)
+ context = (job['context'] || {}).merge(related_class: self.class.name)
+
+ Gitlab::ApplicationContext.with_raw_context(context) do
+ args = job['args']
+
+ Gitlab::SidekiqLogging::ConcurrencyLimitLogger.instance.resumed_log(worker_name, args)
+
+ worker_name.safe_constantize&.perform_async(*args)
+ end
+ end
+
+ def next_batch_from_queue(redis, limit:)
+ return [] unless limit > 0
+
+ redis.lrange(redis_key, 0, limit - 1)
+ end
+
+ def remove_processed_jobs(redis, limit:)
+ redis.ltrim(redis_key, limit, -1)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/concurrency_limit/middleware.rb b/lib/gitlab/sidekiq_middleware/concurrency_limit/middleware.rb
new file mode 100644
index 00000000000..c15eba47879
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/concurrency_limit/middleware.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module ConcurrencyLimit
+ class Middleware
+ def initialize(worker, job)
+ @worker = worker
+ @job = job
+ end
+
+ # This will continue the middleware chain if the job should be scheduled
+ # It will return false if the job needs to be cancelled
+ def schedule
+ if should_defer_schedule?
+ defer_job!
+ return
+ end
+
+ yield
+ end
+
+ # This will continue the server middleware chain if the job should be
+ # executed.
+ # It will return false if the job should not be executed.
+ def perform
+ if should_defer_perform?
+ defer_job!
+ return
+ end
+
+ yield
+ end
+
+ private
+
+ attr_reader :job, :worker
+
+ def should_defer_schedule?
+ return false if Feature.disabled?(:sidekiq_concurrency_limit_middleware, Feature.current_request, type: :ops)
+ return false if resumed?
+ return false unless ::Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersMap.limit_for(worker: worker)
+
+ has_jobs_in_queue?
+ end
+
+ def should_defer_perform?
+ return false if Feature.disabled?(:sidekiq_concurrency_limit_middleware, Feature.current_request, type: :ops)
+
+ return false if resumed?
+ return true if has_jobs_in_queue?
+
+ ::Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersMap.over_the_limit?(worker: worker)
+ end
+
+ def concurrency_service
+ ::Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService
+ end
+
+ def resumed?
+ current_context['meta.related_class'] == concurrency_service.name
+ end
+
+ def has_jobs_in_queue?
+ worker_class = worker.is_a?(Class) ? worker : worker.class
+ concurrency_service.has_jobs_in_queue?(worker_class.name)
+ end
+
+ def defer_job!
+ ::Gitlab::SidekiqLogging::ConcurrencyLimitLogger.instance.deferred_log(job)
+
+ concurrency_service.add_to_queue!(
+ job['class'],
+ job['args'],
+ current_context
+ )
+ end
+
+ def current_context
+ ::Gitlab::ApplicationContext.current
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/concurrency_limit/server.rb b/lib/gitlab/sidekiq_middleware/concurrency_limit/server.rb
new file mode 100644
index 00000000000..8e6ed856d23
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/concurrency_limit/server.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module ConcurrencyLimit
+ class Server
+ def call(worker, job, _queue, &block)
+ ::Gitlab::SidekiqMiddleware::ConcurrencyLimit::Middleware.new(worker, job).perform(&block)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_concurrency.rb b/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_concurrency.rb
new file mode 100644
index 00000000000..fe2deb867ca
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_concurrency.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module ConcurrencyLimit
+ class WorkersConcurrency
+ class << self
+ CACHE_EXPIRES_IN = 5.seconds
+
+ def current_for(worker:, skip_cache: false)
+ worker_class = worker.is_a?(Class) ? worker : worker.class
+ worker_name = worker_class.name
+
+ workers(skip_cache: skip_cache)[worker_name].to_i
+ end
+
+ def workers(skip_cache: false)
+ return workers_uncached if skip_cache
+
+ Rails.cache.fetch(self.class.name, expires_in: CACHE_EXPIRES_IN) do
+ workers_uncached
+ end
+ end
+
+ private
+
+ def workers_uncached
+ sidekiq_workers.map { |_process_id, _thread_id, work| ::Gitlab::Json.parse(work['payload'])['class'] }.tally
+ end
+
+ def sidekiq_workers
+ Sidekiq::Workers.new.each
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_map.rb b/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_map.rb
new file mode 100644
index 00000000000..360fabadd31
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_map.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module ConcurrencyLimit
+ class WorkersMap
+ class << self
+ def set_limit_for(worker:, max_jobs:)
+ raise ArgumentError, 'max_jobs must be a Proc instance' if max_jobs && !max_jobs.is_a?(Proc)
+
+ @data ||= {}
+ @data[worker] = max_jobs
+ end
+
+ def limit_for(worker:)
+ return unless data
+ return if Feature.disabled?(:sidekiq_concurrency_limit_middleware, Feature.current_request, type: :ops)
+
+ worker_class = worker.is_a?(Class) ? worker : worker.class
+ data[worker_class]
+ end
+
+ def over_the_limit?(worker:)
+ limit_proc = limit_for(worker: worker)
+
+ limit = limit_proc&.call
+ return false if limit.to_i <= 0
+
+ current = ::Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersConcurrency.current_for(worker: worker)
+
+ current >= limit
+ end
+
+ def workers
+ return [] unless data
+
+ data.keys
+ end
+
+ private
+
+ attr_reader :data
+ end
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 8d3626ea0cb..5c8b24e888f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -22538,7 +22538,7 @@ msgstr ""
msgid "GithubImporter|Issue %{issue_iid} attachment"
msgstr ""
-msgid "GithubImporter|Issue links"
+msgid "GithubImporter|Issue attachments"
msgstr ""
msgid "GithubImporter|Merge request %{merge_request_iid} attachment"
@@ -22547,7 +22547,7 @@ msgstr ""
msgid "GithubImporter|Note attachment"
msgstr ""
-msgid "GithubImporter|Note links"
+msgid "GithubImporter|Note attachments"
msgstr ""
msgid "GithubImporter|PR attachments"
@@ -22586,7 +22586,7 @@ msgstr ""
msgid "GithubImporter|Release %{tag} attachment"
msgstr ""
-msgid "GithubImporter|Release links"
+msgid "GithubImporter|Release attachments"
msgstr ""
msgid "GithubImporter|Your import of GitHub gists into GitLab snippets is complete."
@@ -41491,9 +41491,6 @@ msgstr ""
msgid "Response didn't include `service_desk_address`"
msgstr ""
-msgid "Response generated by AI"
-msgstr ""
-
msgid "Response initiated"
msgstr ""
@@ -47889,9 +47886,6 @@ msgstr ""
msgid "Suggest code changes which can be immediately applied in one click. Try it out!"
msgstr ""
-msgid "Suggest test cases"
-msgstr ""
-
msgid "Suggested change"
msgstr ""
@@ -48828,9 +48822,6 @@ msgid_plural "Test coverage: %d hits"
msgstr[0] ""
msgstr[1] ""
-msgid "Test generated by AI"
-msgstr ""
-
msgid "Test settings"
msgstr ""
@@ -52167,9 +52158,6 @@ msgstr ""
msgid "Unable to generate new instance ID"
msgstr ""
-msgid "Unable to generate tests for specified file."
-msgstr ""
-
msgid "Unable to load commits. Try again later."
msgstr ""
diff --git a/qa/gdk/entrypoint b/qa/gdk/entrypoint
index 62403648885..934aff35873 100755
--- a/qa/gdk/entrypoint
+++ b/qa/gdk/entrypoint
@@ -2,12 +2,25 @@
set -e
-# host file can't be modified during docker build because it is created on container start
-# this makes for a more portable solution as other alternative is to use `--add-host` option for 'docker run' command
-# We want a non-loopback ip otherwise some services (maybe just workhorse?) bind to localhost and can't be accessed
-# from outside the container
-echo "$(hostname -i) gdk.test" | sudo tee -a /etc/hosts > /dev/null
+unset BUNDLE_PATH
+
+if [[ "${WITH_LOAD_BALANCER}" == "true" ]]; then
+ gdk config set postgresql.replica.enabled true
+ gdk config set postgresql.replica_2.enabled true
+ gdk config set load_balancing.enabled true
+ gdk reconfigure
+fi
gdk start
-exec "$@" | tee -a ${HOME}/gitlab-development-kit/gitlab/log/gdk.log
+# Split logs in to multiple files when running in CI
+if [ -z "$CI" ]; then
+ exec "$@" | tee -a ${HOME}/gitlab-development-kit/gitlab/log/gdk-container.log
+else
+ logs=("gitaly" "gitlab-workhorse" "postgresql" "rails-background-jobs" "redis" "sshd" "vite" "rails-web" "webpack")
+ for log in "${logs[@]}"; do
+ gdk tail $log &>${CI_BUILDS_DIR}/gdk.${log}.log &
+ done
+
+ exec "$@"
+fi
diff --git a/qa/gdk/gdk.yml b/qa/gdk/gdk.yml
index 36bf901033c..1ea170a867a 100644
--- a/qa/gdk/gdk.yml
+++ b/qa/gdk/gdk.yml
@@ -3,10 +3,10 @@ hostname: gdk.test
sshd:
additional_config: 'AcceptEnv GIT_PROTOCOL'
webpack:
- live_reload: false
- sourcemaps: false
- incremental: false
- static: true
+ enabled: false
+vite:
+ enabled: true
+ hot_module_reloading: false
gdk:
ask_to_restart_after_update: false
auto_reconfigure: false
diff --git a/qa/qa/flow/saml.rb b/qa/qa/flow/saml.rb
index 1cf29e75c67..42fd150a1bf 100644
--- a/qa/qa/flow/saml.rb
+++ b/qa/qa/flow/saml.rb
@@ -61,6 +61,7 @@ module QA
end
def remove_saml_idp_service(saml_idp_service)
+ saml_idp_service.logs # print log output before removal
saml_idp_service.remove!
end
diff --git a/qa/qa/page/settings/common.rb b/qa/qa/page/settings/common.rb
index 2fb5f17458b..229218ffaf4 100644
--- a/qa/qa/page/settings/common.rb
+++ b/qa/qa/page/settings/common.rb
@@ -10,7 +10,7 @@ module QA
def expand_content(element_name)
within_element(element_name) do
# Because it is possible to click the button before the JS toggle code is bound
- wait_until(reload: false) do
+ wait_until(reload: false, message: "Waiting until content is expanded") do
click_button 'Expand' unless has_css?('button', text: 'Collapse', wait: 1)
has_content?('Collapse')
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 231909b1a5b..c62df7a8cde 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -692,8 +692,11 @@ module QA
ENV['QA_1P_GITHUB_UUID']
end
- def gdk_url
- ENV['GDK_URL']
+ # Docker network to use when starting sidecar containers
+ #
+ # @return [String]
+ def docker_network
+ ENV["QA_DOCKER_NETWORK"]
end
private
diff --git a/qa/qa/service/docker_run/base.rb b/qa/qa/service/docker_run/base.rb
index bcddfc4b3a9..1796ea0b5f8 100644
--- a/qa/qa/service/docker_run/base.rb
+++ b/qa/qa/service/docker_run/base.rb
@@ -5,6 +5,9 @@ require 'socket'
module QA
module Service
module DockerRun
+ # TODO: There are a lot of methods that reference @name yet it is not part of initializer
+ # Refactor all child implementations to remove assumption that @name will exist
+ #
class Base
include Service::Shellout
@@ -13,7 +16,7 @@ module QA
end
def initialize
- @network = gdk_network || Runtime::Scenario.attributes[:network] || 'test'
+ @network = Runtime::Scenario.attributes[:network] || Runtime::Env.docker_network || 'test'
end
# Authenticate against a container registry
@@ -40,7 +43,9 @@ module QA
end
def network
- network_exists?(@network) ? @network : 'bridge'
+ return @network_cache if @network_cache
+
+ @network_cache = network_exists?(@network) ? @network : 'bridge'
end
def inspect_network(name)
@@ -58,8 +63,17 @@ module QA
end
end
+ # Host name of the container
+ #
+ # If host or default bridge network is used, container can only be reached using ip address
+ #
+ # @return [String]
def host_name
- "#{@name}.#{network}"
+ @host_name ||= if network == "host" || network == "bridge"
+ host_ip
+ else
+ "#{@name}.#{network}"
+ end
end
def register!
@@ -88,24 +102,6 @@ module QA
shell("docker inspect --format='{{json .State.Health.Status}}' #{@name}").delete('"')
end
- # The network to use when testing against GDK in docker
- #
- # @return [String]
- def gdk_network
- return unless Runtime::Env.gdk_url
-
- 'host'
- end
-
- # The IP address of the docker host when testing against GDK in docker
- #
- # @return [String]
- def gdk_host_ip
- return unless Runtime::Env.gdk_url
-
- Addrinfo.tcp(URI(Runtime::Env.gdk_url).host, nil).ip_address
- end
-
# Returns the IP address of the docker host
#
# @return [String]
diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb
index a67a8163daf..ecfc77272c1 100644
--- a/qa/qa/service/docker_run/gitlab_runner.rb
+++ b/qa/qa/service/docker_run/gitlab_runner.rb
@@ -41,7 +41,6 @@ module QA
docker run -d --rm --network #{network} --name #{@name} #{'--user=root' if Runtime::Env.fips?}
#{'-v /var/run/docker.sock:/var/run/docker.sock' if @executor == :docker}
--privileged
- #{"--add-host gdk.test:#{gdk_host_ip}" if gdk_network}
#{@image} #{add_gitlab_tls_cert if @address.include? 'https'}
&& docker exec --detach #{@name} sh -c "#{register_command}"
CMD
@@ -91,7 +90,6 @@ module QA
args << '--docker-privileged=true'
args << "--docker-network-mode=#{network}"
args << "--docker-volumes=/certs/client"
- args << "--docker-extra-hosts=gdk.test:#{gdk_host_ip}" if gdk_network
end
<<~CMD.strip
diff --git a/qa/qa/service/docker_run/saml_idp.rb b/qa/qa/service/docker_run/saml_idp.rb
index a0638bbcc2e..28396cda951 100644
--- a/qa/qa/service/docker_run/saml_idp.rb
+++ b/qa/qa/service/docker_run/saml_idp.rb
@@ -4,6 +4,8 @@ module QA
module Service
module DockerRun
class SamlIdp < Base
+ include Support::API
+
def initialize(gitlab_host, group)
@image = 'jamedjo/test-saml-idp'
@name = 'saml-idp-server'
@@ -12,6 +14,8 @@ module QA
super()
end
+ delegate :logger, to: Runtime::Logger
+
def idp_base_url
"https://#{host_name}:8443/simplesaml"
end
@@ -40,17 +44,10 @@ module QA
QA::Runtime::Env.simple_saml_fingerprint || '119b9e027959cdb7c662cfd075d9e2ef384e445f'
end
- def host_name
- return 'localhost' unless QA::Runtime::Env.running_in_ci?
-
- super
- end
-
def register!
command = <<~CMD.tr("\n", ' ')
docker run -d --rm
--network #{network}
- --hostname #{host_name}
--name #{@name}
--env SIMPLESAMLPHP_SP_ENTITY_ID=#{@gitlab_host}/groups/#{@group}
--env SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE=#{@gitlab_host}/groups/#{@group}/-/saml/callback
@@ -59,9 +56,23 @@ module QA
#{@image}
CMD
- command.gsub!("--network #{network} ", '') unless QA::Runtime::Env.running_in_ci?
-
shell command
+
+ logger.debug("Waiting for SAML IDP to start...")
+ Support::Retrier.retry_until(
+ max_attempts: 3,
+ sleep_interval: 1,
+ retry_on_exception: true,
+ message: "Waiting for SAML IDP to start"
+ ) do
+ logger.debug("Pinging SAML IDP service")
+ # Endpoint will return 403 for unauthenticated request once it is up
+ get("http://#{host_name}:8080").code == 403
+ end
+ rescue StandardError => e
+ # Remove container on failure because it isn't using a unique name
+ remove!
+ raise e
end
end
end
diff --git a/qa/qa/service/docker_run/smocker.rb b/qa/qa/service/docker_run/smocker.rb
index cd69d8771e1..d1905d5664d 100644
--- a/qa/qa/service/docker_run/smocker.rb
+++ b/qa/qa/service/docker_run/smocker.rb
@@ -5,12 +5,12 @@ module QA
module DockerRun
class Smocker < Base
def initialize(name: 'smocker-server')
- @image = 'thiht/smocker:0.17.1'
+ @image = 'thiht/smocker:0.18.5'
@name = name
@public_port = 8080
@admin_port = 8081
+
super()
- @network_cache = network
end
# @param wait [Integer] seconds to wait for server
@@ -40,14 +40,6 @@ module QA
attr_reader :public_port, :admin_port
- def host_name
- @host_name ||= if qa_environment? && !gdk_network && @network_cache != 'bridge'
- "#{@name}.#{@network_cache}"
- else
- host_ip
- end
- end
-
def wait_for_running
Support::Waiter.wait_until(raise_on_failure: false, reload_page: false) do
running?
@@ -57,24 +49,19 @@ module QA
def register!
command = <<~CMD.tr("\n", ' ')
docker run -d --rm
- --network #{@network_cache}
- --hostname #{host_name}
- --name #{@name}
- --publish #{@public_port}:8080
- --publish #{@admin_port}:8081
- #{@image}
+ --network #{network}
+ --name #{name}
+ --publish #{public_port}:8080
+ --publish #{admin_port}:8081
+ #{image}
CMD
- command.gsub!("--network #{@network_cache} ", '') unless qa_environment?
-
shell command
end
private
- def qa_environment?
- QA::Runtime::Env.running_in_ci? || QA::Runtime::Env.qa_hostname
- end
+ attr_reader :name, :image
end
end
end
diff --git a/qa/qa/support/knapsack_report.rb b/qa/qa/support/knapsack_report.rb
index 836ff33f543..405afdd849b 100644
--- a/qa/qa/support/knapsack_report.rb
+++ b/qa/qa/support/knapsack_report.rb
@@ -8,6 +8,8 @@ module QA
PROJECT = "gitlab-qa-resources"
BUCKET = "knapsack-reports"
FALLBACK_REPORT = "knapsack/master_report.json"
+ PATTERN_VAR_NAME = "KNAPSACK_TEST_FILE_PATTERN"
+ DEFAULT_TEST_PATTERN = "qa/specs/features/**/*_spec.rb"
class << self
delegate :configure!, :move_regenerated_report, :download_report, :upload_report, to: :new
@@ -117,9 +119,9 @@ module QA
#
# @return [void]
def setup_environment!
- ENV["KNAPSACK_TEST_FILE_PATTERN"] ||= "qa/specs/features/**/*_spec.rb"
ENV["KNAPSACK_TEST_DIR"] = "qa/specs"
ENV["KNAPSACK_REPORT_PATH"] = report_path
+ ENV[PATTERN_VAR_NAME] = ENV[PATTERN_VAR_NAME].presence || DEFAULT_TEST_PATTERN
end
# Logger instance
diff --git a/qa/spec/service/docker_run/smocker_spec.rb b/qa/spec/service/docker_run/smocker_spec.rb
index 0017ee48e95..85da88a4027 100644
--- a/qa/spec/service/docker_run/smocker_spec.rb
+++ b/qa/spec/service/docker_run/smocker_spec.rb
@@ -29,38 +29,20 @@ module QA
end
end
- context 'when running in CI' do
- before do
- allow(Runtime::Env).to receive(:running_in_ci?).and_return(true)
- end
-
- context 'when network is not bridge' do
- it_behaves_like 'returns name.network'
- end
-
- context 'when network is bridge' do
- let(:network) { 'bridge' }
-
- it_behaves_like 'returns host ip'
- end
+ context 'when network is not bridge or host' do
+ it_behaves_like 'returns name.network'
end
- context 'when running not in CI' do
- before do
- allow(Runtime::Env).to receive(:running_in_ci?).and_return(false)
- end
+ context 'when network is bridge' do
+ let(:network) { 'bridge' }
- context 'when QA hostname is not set' do
- it_behaves_like 'returns host ip'
- end
+ it_behaves_like 'returns host ip'
+ end
- context 'when QA hostname is set' do
- before do
- allow(Runtime::Env).to receive(:qa_hostname).and_return('qa-hostname')
- end
+ context 'when network is host' do
+ let(:network) { 'host' }
- it_behaves_like 'returns name.network'
- end
+ it_behaves_like 'returns host ip'
end
end
end
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js
index 813db12e83f..4676f56c47e 100644
--- a/spec/frontend/diffs/components/app_spec.js
+++ b/spec/frontend/diffs/components/app_spec.js
@@ -47,8 +47,6 @@ const ENDPOINT_METADATA_URL = `${TEST_HOST}/diff/endpointMetadata`;
Vue.use(Vuex);
Vue.use(VueApollo);
-Vue.config.ignoredElements = ['copy-code'];
-
function getCollapsedFilesWarning(wrapper) {
return wrapper.findComponent(CollapsedFilesWarning);
}
diff --git a/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/client_spec.rb
new file mode 100644
index 00000000000..53bb3bda0ce
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/client_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::ConcurrencyLimit::Client, :clean_gitlab_redis_queues, feature_category: :global_search do
+ let(:worker_class) do
+ Class.new do
+ def self.name
+ 'TestConcurrencyLimitWorker'
+ end
+
+ include ApplicationWorker
+
+ concurrency_limit -> { 5 }
+
+ def perform(*)
+ self.class.work
+ end
+
+ def self.work; end
+ end
+ end
+
+ before do
+ stub_const('TestConcurrencyLimitWorker', worker_class)
+ end
+
+ describe '#call' do
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(sidekiq_concurrency_limit_middleware: false)
+ end
+
+ it 'schedules the job' do
+ expect(Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService).not_to receive(:add_to_queue!)
+
+ TestConcurrencyLimitWorker.perform_async('foo')
+
+ expect(TestConcurrencyLimitWorker.jobs.size).to eq(1)
+ end
+ end
+
+ context 'when there are jobs in the queue' do
+ before do
+ allow(::Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService).to receive(:has_jobs_in_queue?)
+ .and_return(true)
+ end
+
+ it 'defers the job' do
+ expect(Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService).to receive(:add_to_queue!).once
+
+ TestConcurrencyLimitWorker.perform_async('foo')
+
+ expect(TestConcurrencyLimitWorker.jobs.size).to eq(0)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/concurrency_limit_service_spec.rb b/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/concurrency_limit_service_spec.rb
new file mode 100644
index 00000000000..a3922aed3a5
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/concurrency_limit_service_spec.rb
@@ -0,0 +1,190 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService, :clean_gitlab_redis_shared_state, feature_category: :global_search do
+ let(:worker_class) do
+ Class.new do
+ def self.name
+ 'DummyWorker'
+ end
+
+ include ApplicationWorker
+ end
+ end
+
+ let(:worker_class_name) { worker_class.name }
+
+ let(:worker_context) do
+ { 'correlation_id' => 'context_correlation_id',
+ 'meta.project' => 'gitlab-org/gitlab' }
+ end
+
+ let(:stored_context) do
+ {
+ "#{Gitlab::ApplicationContext::LOG_KEY}.project" => 'gitlab-org/gitlab',
+ "correlation_id" => 'context_correlation_id'
+ }
+ end
+
+ let(:worker_args) { [1, 2] }
+
+ subject(:service) { described_class.new(worker_class_name) }
+
+ before do
+ stub_const(worker_class_name, worker_class)
+ end
+
+ describe '.add_to_queue!' do
+ subject(:add_to_queue!) { described_class.add_to_queue!(worker_class_name, worker_args, worker_context) }
+
+ it 'calls an instance method' do
+ expect_next_instance_of(described_class) do |instance|
+ expect(instance).to receive(:add_to_queue!).with(worker_args, worker_context)
+ end
+
+ add_to_queue!
+ end
+ end
+
+ describe '.has_jobs_in_queue?' do
+ it 'calls an instance method' do
+ expect_next_instance_of(described_class) do |instance|
+ expect(instance).to receive(:has_jobs_in_queue?)
+ end
+
+ described_class.has_jobs_in_queue?(worker_class_name)
+ end
+ end
+
+ describe '.resume_processing!' do
+ subject(:resume_processing!) { described_class.resume_processing!(worker_class_name, limit: 10) }
+
+ it 'calls an instance method' do
+ expect_next_instance_of(described_class) do |instance|
+ expect(instance).to receive(:resume_processing!)
+ end
+
+ resume_processing!
+ end
+ end
+
+ describe '.queue_size' do
+ it 'reports the queue size' do
+ expect(described_class.queue_size(worker_class_name)).to eq(0)
+
+ service.add_to_queue!(worker_args, worker_context)
+
+ expect(described_class.queue_size(worker_class_name)).to eq(1)
+
+ expect { service.resume_processing!(limit: 1) }.to change { described_class.queue_size(worker_class_name) }.by(-1)
+ end
+ end
+
+ describe '#add_to_queue!' do
+ subject(:add_to_queue!) { service.add_to_queue!(worker_args, worker_context) }
+
+ it 'adds a job to the set' do
+ expect { add_to_queue! }
+ .to change { service.queue_size }
+ .from(0).to(1)
+ end
+
+ it 'adds only one unique job to the set' do
+ expect do
+ 2.times { add_to_queue! }
+ end.to change { service.queue_size }.from(0).to(1)
+ end
+
+ it 'stores context information' do
+ add_to_queue!
+
+ service.send(:with_redis) do |r|
+ set_key = service.send(:redis_key)
+ stored_job = service.send(:deserialize, r.lrange(set_key, 0, -1).first)
+
+ expect(stored_job['context']).to eq(stored_context)
+ end
+ end
+ end
+
+ describe '#has_jobs_in_queue?' do
+ it 'uses queue_size' do
+ expect { service.add_to_queue!(worker_args, worker_context) }
+ .to change { service.has_jobs_in_queue? }
+ .from(false).to(true)
+ end
+ end
+
+ describe '#resume_processing!' do
+ let(:jobs) { [[1], [2], [3]] }
+ let(:expected_context) { stored_context.merge(related_class: described_class.name) }
+
+ it 'puts jobs back into the queue and respects order' do
+ jobs.each do |j|
+ service.add_to_queue!(j, worker_context)
+ end
+
+ expect(worker_class).to receive(:perform_async).with(1).ordered
+ expect(worker_class).to receive(:perform_async).with(2).ordered
+ expect(worker_class).not_to receive(:perform_async).with(3).ordered
+
+ expect(Gitlab::SidekiqLogging::ConcurrencyLimitLogger.instance)
+ .to receive(:resumed_log)
+ .with(worker_class_name, [1])
+ expect(Gitlab::SidekiqLogging::ConcurrencyLimitLogger.instance)
+ .to receive(:resumed_log)
+ .with(worker_class_name, [2])
+
+ service.resume_processing!(limit: 2)
+ end
+
+ it 'drops a set after execution' do
+ jobs.each do |j|
+ service.add_to_queue!(j, worker_context)
+ end
+
+ expect(Gitlab::ApplicationContext).to receive(:with_raw_context)
+ .with(expected_context)
+ .exactly(jobs.count).times.and_call_original
+ expect(worker_class).to receive(:perform_async).exactly(jobs.count).times
+
+ expect { service.resume_processing!(limit: jobs.count) }
+ .to change { service.has_jobs_in_queue? }.from(true).to(false)
+ end
+ end
+
+ context 'with concurrent changes to different queues' do
+ let(:second_worker_class) do
+ Class.new do
+ def self.name
+ 'SecondDummyIndexingWorker'
+ end
+
+ include ApplicationWorker
+ end
+ end
+
+ let(:other_subject) { described_class.new(second_worker_class.name) }
+
+ before do
+ stub_const(second_worker_class.name, second_worker_class)
+ end
+
+ it 'allows to use queues independently of each other' do
+ expect { service.add_to_queue!(worker_args, worker_context) }
+ .to change { service.queue_size }
+ .from(0).to(1)
+
+ expect { other_subject.add_to_queue!(worker_args, worker_context) }
+ .to change { other_subject.queue_size }
+ .from(0).to(1)
+
+ expect { service.resume_processing!(limit: 1) }.to change { service.has_jobs_in_queue? }
+ .from(true).to(false)
+
+ expect { other_subject.resume_processing!(limit: 1) }.to change { other_subject.has_jobs_in_queue? }
+ .from(true).to(false)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/server_spec.rb
new file mode 100644
index 00000000000..d2c1f345b1a
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/server_spec.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::ConcurrencyLimit::Server, feature_category: :global_search do
+ let(:worker_class) do
+ Class.new do
+ def self.name
+ 'TestConcurrencyLimitWorker'
+ end
+
+ include ApplicationWorker
+
+ concurrency_limit -> { 5 }
+
+ def perform(*)
+ self.class.work
+ end
+
+ def self.work; end
+ end
+ end
+
+ before do
+ stub_const('TestConcurrencyLimitWorker', worker_class)
+ end
+
+ around do |example|
+ with_sidekiq_server_middleware do |chain|
+ chain.add described_class
+ Sidekiq::Testing.inline! { example.run }
+ end
+ end
+
+ describe '#call' do
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(sidekiq_concurrency_limit_middleware: false)
+ end
+
+ it 'executes the job' do
+ expect(TestConcurrencyLimitWorker).to receive(:work)
+ expect(Gitlab::SidekiqLogging::ConcurrencyLimitLogger.instance).not_to receive(:deferred_log)
+ expect(Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService).not_to receive(:add_to_queue!)
+
+ TestConcurrencyLimitWorker.perform_async('foo')
+ end
+ end
+
+ context 'when there are jobs in the queue' do
+ before do
+ allow(::Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService).to receive(:has_jobs_in_queue?)
+ .and_return(true)
+ end
+
+ it 'defers the job' do
+ expect(TestConcurrencyLimitWorker).not_to receive(:work)
+ expect(Gitlab::SidekiqLogging::ConcurrencyLimitLogger.instance).to receive(:deferred_log).and_call_original
+ expect(Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService).to receive(:add_to_queue!)
+
+ TestConcurrencyLimitWorker.perform_async('foo')
+ end
+
+ it 'executes the job if resumed' do
+ expect(TestConcurrencyLimitWorker).to receive(:work)
+ expect(Gitlab::SidekiqLogging::ConcurrencyLimitLogger.instance).not_to receive(:deferred_log)
+ expect(Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService).not_to receive(:add_to_queue!)
+
+ related_class = 'Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService'
+ Gitlab::ApplicationContext.with_raw_context(related_class: related_class) do
+ TestConcurrencyLimitWorker.perform_async('foo')
+ end
+ end
+ end
+
+ context 'when sidekiq_workers are stubbed' do
+ before do
+ allow(::Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersMap).to receive(:over_the_limit?)
+ .and_return(over_the_limit)
+ end
+
+ context 'when under the limit' do
+ let(:over_the_limit) { false }
+
+ it 'executes the job' do
+ expect(TestConcurrencyLimitWorker).to receive(:work)
+ expect(Gitlab::SidekiqLogging::ConcurrencyLimitLogger.instance).not_to receive(:deferred_log)
+ expect(Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService).not_to receive(:add_to_queue!)
+
+ TestConcurrencyLimitWorker.perform_async('foo')
+ end
+ end
+
+ context 'when over the limit' do
+ let(:over_the_limit) { true }
+
+ it 'defers the job' do
+ expect(TestConcurrencyLimitWorker).not_to receive(:work)
+ expect(Gitlab::SidekiqLogging::ConcurrencyLimitLogger.instance).to receive(:deferred_log).and_call_original
+ expect(Gitlab::SidekiqMiddleware::ConcurrencyLimit::ConcurrencyLimitService).to receive(:add_to_queue!)
+
+ TestConcurrencyLimitWorker.perform_async('foo')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_concurrency_spec.rb b/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_concurrency_spec.rb
new file mode 100644
index 00000000000..69747dfceeb
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_concurrency_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersConcurrency, feature_category: :global_search do
+ let(:worker_class) do
+ Class.new do
+ def self.name
+ 'TestConcurrencyLimitWorker'
+ end
+
+ include ApplicationWorker
+
+ concurrency_limit -> { 60 }
+
+ def perform(*); end
+ end
+ end
+
+ let(:current_concurrency) { 10 }
+ let(:sidekiq_worker) do
+ [
+ 'process_id',
+ 'thread_id',
+ {
+ 'queue' => 'default',
+ 'payload' => {
+ 'class' => 'TestConcurrencyLimitWorker'
+ }.to_json
+ }
+ ]
+ end
+
+ before do
+ stub_const('TestConcurrencyLimitWorker', worker_class)
+ allow(described_class).to receive(:sidekiq_workers).and_return([sidekiq_worker] * current_concurrency)
+ end
+
+ describe '.current_for' do
+ subject(:current_for) { described_class.current_for(worker: TestConcurrencyLimitWorker, skip_cache: skip_cache) }
+
+ context 'without cache' do
+ let(:skip_cache) { true }
+
+ it 'returns the current concurrency' do
+ expect(described_class).to receive(:workers_uncached).and_call_original
+ expect(current_for).to eq(current_concurrency)
+ end
+ end
+
+ context 'with cache' do
+ let(:skip_cache) { false }
+ let(:cached_value) { { "TestConcurrencyLimitWorker" => 20 } }
+
+ before do
+ allow(Rails.cache).to receive(:fetch).and_return(cached_value)
+ end
+
+ it 'returns cached current_for' do
+ expect(described_class).not_to receive(:workers_uncached)
+
+ expect(current_for).to eq(20)
+ end
+ end
+ end
+
+ describe '.workers' do
+ subject(:workers) { described_class.workers(skip_cache: skip_cache) }
+
+ context 'without cache' do
+ let(:skip_cache) { true }
+
+ it 'returns current_workers' do
+ expect(workers).to eq('TestConcurrencyLimitWorker' => 10)
+ end
+ end
+
+ context 'with cache' do
+ let(:skip_cache) { false }
+ let(:cached_value) { { "TestConcurrencyLimitWorker" => 20 } }
+
+ before do
+ allow(Rails.cache).to receive(:fetch).and_return(cached_value)
+ end
+
+ it 'returns cached workers' do
+ expect(described_class).not_to receive(:workers_uncached)
+
+ expect(workers).to eq(cached_value)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_map_spec.rb b/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_map_spec.rb
new file mode 100644
index 00000000000..3836b7f1690
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/concurrency_limit/workers_map_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersMap, feature_category: :global_search do
+ let(:worker_class) do
+ Class.new do
+ def self.name
+ 'TestConcurrencyLimitWorker'
+ end
+
+ include ApplicationWorker
+
+ concurrency_limit -> { 60 }
+
+ def perform(*); end
+ end
+ end
+
+ before do
+ stub_const('TestConcurrencyLimitWorker', worker_class)
+ end
+
+ describe '.limit_for' do
+ let(:expected_limit) { 60 }
+
+ it 'accepts worker instance' do
+ expect(described_class.limit_for(worker: worker_class.new).call).to eq(expected_limit)
+ end
+
+ it 'accepts worker class' do
+ expect(described_class.limit_for(worker: worker_class).call).to eq(expected_limit)
+ end
+
+ it 'returns nil for unknown worker' do
+ expect(described_class.limit_for(worker: described_class)).to be_nil
+ end
+
+ it 'returns nil if the feature flag is disabled' do
+ stub_feature_flags(sidekiq_concurrency_limit_middleware: false)
+
+ expect(described_class.limit_for(worker: worker_class)).to be_nil
+ end
+ end
+
+ describe '.over_the_limit?' do
+ subject(:over_the_limit?) { described_class.over_the_limit?(worker: worker_class) }
+
+ it 'returns false if no limit is set' do
+ expect(described_class).to receive(:limit_for).and_return(nil)
+
+ expect(over_the_limit?).to be_falsey
+ end
+
+ it 'returns false if under the limit' do
+ allow(::Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersConcurrency).to receive(:current_for).and_return(50)
+
+ expect(over_the_limit?).to be_falsey
+ end
+
+ it 'returns true if over the limit' do
+ allow(::Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersConcurrency).to receive(:current_for).and_return(100)
+
+ expect(over_the_limit?).to be_truthy
+ end
+ end
+
+ describe '.workers' do
+ subject(:workers) { described_class.workers }
+
+ it 'includes the worker' do
+ expect(workers).to include(worker_class)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracking/event_definition_spec.rb b/spec/lib/gitlab/tracking/event_definition_spec.rb
index 7c5047dc0c6..2bdf7e17c0d 100644
--- a/spec/lib/gitlab/tracking/event_definition_spec.rb
+++ b/spec/lib/gitlab/tracking/event_definition_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Tracking::EventDefinition do
+RSpec.describe Gitlab::Tracking::EventDefinition, feature_category: :service_ping do
let(:attributes) do
{
description: 'Created issues',
@@ -15,8 +15,10 @@ RSpec.describe Gitlab::Tracking::EventDefinition do
product_stage: 'growth',
product_section: 'dev',
product_group: 'group::product analytics',
- distribution: %w[ee ce],
- tier: %w[free premium ultimate]
+ distributions: %w[ee ce],
+ tiers: %w[free premium ultimate],
+ introduced_by_url: "https://gitlab.com/example/-/merge_requests/123",
+ milestone: '1.6'
}
end
diff --git a/vite.config.js b/vite.config.js
index 5597a6087d1..007706a1955 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -143,21 +143,24 @@ export default defineConfig({
}
: viteGDKConfig.hmr,
https: false,
- watch: {
- ignored: [
- '**/*.stories.js',
- function ignoreRootFolder(x) {
- /*
- `vite` watches the root folder of gitlab and all of its sub folders
- This is not what we want, because we have temp files, and all kind
- of other stuff. As vite starts its watchers recursively, we just
- ignore if the path matches exactly the root folder
+ watch:
+ viteGDKConfig.hmr === null
+ ? null
+ : {
+ ignored: [
+ '**/*.stories.js',
+ function ignoreRootFolder(x) {
+ /*
+ `vite` watches the root folder of gitlab and all of its sub folders
+ This is not what we want, because we have temp files, and all kind
+ of other stuff. As vite starts its watchers recursively, we just
+ ignore if the path matches exactly the root folder
- Additional folders like `ee/app/assets` are defined in
- */
- return x === __dirname;
- },
- ],
- },
+ Additional folders like `ee/app/assets` are defined in
+ */
+ return x === __dirname;
+ },
+ ],
+ },
},
});