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>2023-11-17 15:10:53 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-11-17 15:10:53 +0300
commit2724004cd7b8fa9d179c926c62f2fd7be63c2d81 (patch)
tree9915474bdea07e9748e3b940de07e899fb5f641c
parentf665874e9ee6c28d5098248852f07ae7469d8b2b (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/package-and-test/main.gitlab-ci.yml35
-rw-r--r--.gitlab/ci/qa-common/main.gitlab-ci.yml1
-rw-r--r--.rubocop_todo/rspec/feature_category.yml1
-rw-r--r--.rubocop_todo/rspec/named_subject.yml1
-rw-r--r--.rubocop_todo/style/inline_disable_annotation.yml1
-rw-r--r--app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue2
-rw-r--r--app/assets/javascripts/commons/index.js3
-rw-r--r--app/assets/javascripts/commons/nav/user_merge_requests.js93
-rw-r--r--app/assets/javascripts/graphql_shared/possible_types.json4
-rw-r--r--app/assets/javascripts/invite_members/components/invite_group_trigger.vue7
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_trigger.vue9
-rw-r--r--app/assets/javascripts/invite_members/components/invite_modal_base.vue2
-rw-r--r--app/assets/javascripts/invite_members/components/members_token_select.vue1
-rw-r--r--app/assets/javascripts/invite_members/constants.js2
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue8
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue3
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue4
-rw-r--r--app/assets/javascripts/super_sidebar/user_counts_fetch.js10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue4
-rw-r--r--app/assets/javascripts/work_items/components/work_item_parent.vue1
-rw-r--r--app/assets/stylesheets/application_utilities.scss2
-rw-r--r--app/assets/stylesheets/tmp_utilities.scss44
-rw-r--r--app/controllers/projects/merge_requests_controller.rb12
-rw-r--r--app/graphql/resolvers/work_items/types_resolver.rb16
-rw-r--r--app/graphql/types/work_item_type.rb6
-rw-r--r--app/graphql/types/work_items/type_type.rb20
-rw-r--r--app/graphql/types/work_items/widget_definition_interface.rb30
-rw-r--r--app/graphql/types/work_items/widget_definitions/assignees_type.rb30
-rw-r--r--app/graphql/types/work_items/widget_definitions/generic_type.rb16
-rw-r--r--app/models/ability.rb9
-rw-r--r--app/models/bulk_import.rb2
-rw-r--r--app/models/bulk_imports/entity.rb2
-rw-r--r--app/models/bulk_imports/tracker.rb2
-rw-r--r--app/models/deployment.rb12
-rw-r--r--app/models/project.rb3
-rw-r--r--app/models/project_feature_usage.rb3
-rw-r--r--app/models/repository.rb9
-rw-r--r--app/models/user.rb6
-rw-r--r--app/models/vulnerability.rb2
-rw-r--r--app/models/work_items/widgets/assignees.rb4
-rw-r--r--app/policies/ci/runner_policy.rb16
-rw-r--r--app/workers/bulk_imports/pipeline_worker.rb6
-rw-r--r--app/workers/concerns/gitlab/github_import/stage_methods.rb2
-rw-r--r--config/feature_flags/development/enforce_acceptance_of_changed_terms.yml (renamed from config/feature_flags/development/github_importer_raise_max_interruptions.yml)10
-rw-r--r--config/feature_flags/development/log_read_namespace_usages.yml (renamed from config/feature_flags/development/only_highlight_discussions_requested.yml)10
-rw-r--r--config/initializers/sidekiq.rb7
-rw-r--r--config/metrics/counts_28d/20210216181152_projects_jira_dvcs_cloud_active.yml4
-rw-r--r--config/metrics/counts_28d/20220825232557_count_user_auth.yml2
-rw-r--r--config/metrics/counts_7d/20220615103711_incident_management_timeline_event_total_unique_counts_weekly.yml4
-rw-r--r--config/metrics/counts_all/20210216181128_projects_jira_dvcs_cloud_active.yml4
-rw-r--r--doc/.vale/gitlab/Prerequisites.yml14
-rw-r--r--doc/administration/settings/usage_statistics.md1
-rw-r--r--doc/api/graphql/reference/index.md35
-rw-r--r--doc/api/project_vulnerabilities.md5
-rw-r--r--doc/api/vulnerabilities.md8
-rw-r--r--doc/architecture/blueprints/google_cloud_platform_integration/index.md34
-rw-r--r--doc/development/internal_analytics/index.md1
-rw-r--r--lib/backup/repositories.rb19
-rw-r--r--lib/bulk_imports/common/pipelines/entity_finisher.rb4
-rw-r--r--lib/gitlab/patch/sidekiq_scheduled_enq.rb33
-rw-r--r--lib/gitlab/usage_data.rb1
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/component/members/invite_members_modal.rb20
-rw-r--r--qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb9
-rw-r--r--spec/controllers/application_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb133
-rw-r--r--spec/factories/project_feature_usage.rb4
-rw-r--r--spec/factories/projects.rb6
-rw-r--r--spec/features/admin/admin_settings_spec.rb7
-rw-r--r--spec/features/projects/members/manage_groups_spec.rb2
-rw-r--r--spec/frontend/commons/nav/user_merge_requests_spec.js154
-rw-r--r--spec/frontend/invite_members/components/invite_members_trigger_spec.js12
-rw-r--r--spec/frontend/invite_members/components/invite_modal_base_spec.js2
-rw-r--r--spec/frontend/notes/components/comment_form_spec.js6
-rw-r--r--spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js18
-rw-r--r--spec/frontend/sidebar/sidebar_mediator_spec.js1
-rw-r--r--spec/frontend/super_sidebar/user_counts_manager_spec.js30
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js23
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js3
-rw-r--r--spec/graphql/types/work_items/widget_definition_interface_spec.rb29
-rw-r--r--spec/graphql/types/work_items/widget_definitions/assignees_type_spec.rb11
-rw-r--r--spec/graphql/types/work_items/widget_definitions/generic_type_spec.rb11
-rw-r--r--spec/lib/backup/repositories_spec.rb30
-rw-r--r--spec/lib/gitlab/patch/sidekiq_scheduled_enq_spec.rb73
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb3
-rw-r--r--spec/models/ability_spec.rb34
-rw-r--r--spec/models/bulk_imports/tracker_spec.rb26
-rw-r--r--spec/models/deployment_spec.rb13
-rw-r--r--spec/models/project_spec.rb10
-rw-r--r--spec/models/repository_spec.rb55
-rw-r--r--spec/models/tree_spec.rb79
-rw-r--r--spec/models/user_spec.rb43
-rw-r--r--spec/models/work_items/widgets/assignees_spec.rb26
-rw-r--r--spec/policies/ci/runner_manager_policy_spec.rb24
-rw-r--r--spec/policies/ci/runner_policy_spec.rb24
-rw-r--r--spec/requests/api/graphql/group/work_item_types_spec.rb55
-rw-r--r--spec/requests/api/graphql/project/work_item_types_spec.rb55
-rw-r--r--spec/requests/api/graphql/work_item_spec.rb12
-rw-r--r--spec/support/shared_contexts/requests/api/graphql/work_items/work_item_types_shared_context.rb48
-rw-r--r--spec/support/shared_examples/requests/api/graphql/work_item_type_list_shared_examples.rb49
-rw-r--r--spec/workers/bulk_imports/pipeline_worker_spec.rb27
-rw-r--r--spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb12
-rw-r--r--vendor/gems/sidekiq-reliable-fetch/lib/sidekiq/semi_reliable_fetch.rb25
-rw-r--r--vendor/gems/sidekiq-reliable-fetch/spec/semi_reliable_fetch_spec.rb34
-rw-r--r--yarn.lock8
105 files changed, 948 insertions, 944 deletions
diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
index 21dd8f957d4..1f7ed5aa2e6 100644
--- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml
+++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
@@ -76,8 +76,7 @@ instance-ff-inverse:
- .parallel
variables:
QA_SCENARIO: Test::Instance::Image
- QA_KNAPSACK_REPORT_NAME: ee-instance
- GITLAB_QA_OPTS: --set-feature-flags $QA_FEATURE_FLAGS
+ QA_KNAPSACK_REPORT_NAME: instance
rules:
- !reference [.rules:test:feature-flags-set, rules]
@@ -167,7 +166,7 @@ decomposition-single-db:
- .parallel
variables:
QA_SCENARIO: Test::Instance::Image
- GITLAB_QA_OPTS: --omnibus-config decomposition_single_db $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config decomposition_single_db
rules:
- !reference [.rules:test:smoke-for-omnibus-mr, rules]
- !reference [.rules:test:qa-parallel, rules]
@@ -177,7 +176,7 @@ decomposition-single-db-selective:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::Image
- GITLAB_QA_OPTS: --omnibus-config decomposition_single_db $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config decomposition_single_db
rules:
- !reference [.rules:test:qa-selective, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
@@ -188,7 +187,7 @@ decomposition-single-db-selective-parallel:
- .parallel
variables:
QA_SCENARIO: Test::Instance::Image
- GITLAB_QA_OPTS: --omnibus-config decomposition_single_db $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config decomposition_single_db
rules:
- !reference [.rules:test:qa-selective-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
@@ -204,7 +203,7 @@ decomposition-multiple-db:
variables:
QA_SCENARIO: Test::Instance::Image
GITLAB_ALLOW_SEPARATE_CI_DATABASE: "true"
- GITLAB_QA_OPTS: --omnibus-config decomposition_multiple_db $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config decomposition_multiple_db
rules:
- !reference [.rules:test:smoke-for-omnibus-mr, rules]
- !reference [.rules:test:qa-parallel, rules]
@@ -215,7 +214,7 @@ decomposition-multiple-db-selective:
variables:
QA_SCENARIO: Test::Instance::Image
GITLAB_ALLOW_SEPARATE_CI_DATABASE: "true"
- GITLAB_QA_OPTS: --omnibus-config decomposition_multiple_db $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config decomposition_multiple_db
rules:
- !reference [.rules:test:qa-selective, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
@@ -227,7 +226,7 @@ decomposition-multiple-db-selective-parallel:
variables:
QA_SCENARIO: Test::Instance::Image
GITLAB_ALLOW_SEPARATE_CI_DATABASE: "true"
- GITLAB_QA_OPTS: --omnibus-config decomposition_multiple_db $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config decomposition_multiple_db
rules:
- !reference [.rules:test:qa-selective-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
@@ -244,7 +243,7 @@ object-storage:
variables:
QA_SCENARIO: Test::Instance::Image
QA_RSPEC_TAGS: --tag object_storage
- GITLAB_QA_OPTS: --omnibus-config object_storage $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config object_storage
rules:
- !reference [.rules:test:qa-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::ObjectStorage/
@@ -254,7 +253,7 @@ object-storage-selective:
variables:
QA_SCENARIO: Test::Instance::Image
QA_RSPEC_TAGS: --tag object_storage
- GITLAB_QA_OPTS: --omnibus-config object_storage $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config object_storage
rules:
- !reference [.rules:test:qa-selective, rules]
- if: $QA_SUITES =~ /Test::Instance::ObjectStorage/
@@ -265,7 +264,7 @@ object-storage-selective-parallel:
variables:
QA_SCENARIO: Test::Instance::Image
QA_RSPEC_TAGS: --tag object_storage
- GITLAB_QA_OPTS: --omnibus-config object_storage $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config object_storage
rules:
- !reference [.rules:test:qa-selective-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::ObjectStorage/
@@ -283,7 +282,7 @@ object-storage-aws:
AWS_S3_BUCKET_NAME: $QA_AWS_S3_BUCKET_NAME
AWS_S3_KEY_ID: $QA_AWS_S3_KEY_ID
AWS_S3_REGION: $QA_AWS_S3_REGION
- GITLAB_QA_OPTS: --omnibus-config object_storage_aws $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config object_storage_aws
object-storage-aws-selective:
extends: object-storage-selective
@@ -292,7 +291,7 @@ object-storage-aws-selective:
AWS_S3_BUCKET_NAME: $QA_AWS_S3_BUCKET_NAME
AWS_S3_KEY_ID: $QA_AWS_S3_KEY_ID
AWS_S3_REGION: $QA_AWS_S3_REGION
- GITLAB_QA_OPTS: --omnibus-config object_storage_aws $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config object_storage_aws
object-storage-aws-selective-parallel:
extends: object-storage-selective-parallel
@@ -301,7 +300,7 @@ object-storage-aws-selective-parallel:
AWS_S3_BUCKET_NAME: $QA_AWS_S3_BUCKET_NAME
AWS_S3_KEY_ID: $QA_AWS_S3_KEY_ID
AWS_S3_REGION: $QA_AWS_S3_REGION
- GITLAB_QA_OPTS: --omnibus-config object_storage_aws $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config object_storage_aws
# ========== object-storage-gcs ===========
@@ -315,7 +314,7 @@ object-storage-gcs:
GOOGLE_PROJECT: $QA_GOOGLE_PROJECT
GOOGLE_JSON_KEY: $QA_GOOGLE_JSON_KEY
GOOGLE_CLIENT_EMAIL: $QA_GOOGLE_CLIENT_EMAIL
- GITLAB_QA_OPTS: --omnibus-config object_storage_gcs $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config object_storage_gcs
object-storage-gcs-selective:
extends: object-storage-selective
@@ -324,7 +323,7 @@ object-storage-gcs-selective:
GOOGLE_PROJECT: $QA_GOOGLE_PROJECT
GOOGLE_JSON_KEY: $QA_GOOGLE_JSON_KEY
GOOGLE_CLIENT_EMAIL: $QA_GOOGLE_CLIENT_EMAIL
- GITLAB_QA_OPTS: --omnibus-config object_storage_gcs $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config object_storage_gcs
object-storage-gcs-selective-parallel:
extends: object-storage-selective-parallel
@@ -333,7 +332,7 @@ object-storage-gcs-selective-parallel:
GOOGLE_PROJECT: $QA_GOOGLE_PROJECT
GOOGLE_JSON_KEY: $QA_GOOGLE_JSON_KEY
GOOGLE_CLIENT_EMAIL: $QA_GOOGLE_CLIENT_EMAIL
- GITLAB_QA_OPTS: --omnibus-config object_storage_gcs $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config object_storage_gcs
# ------------------------------------------
# Non parallel jobs
@@ -567,7 +566,7 @@ registry-object-storage-tls:
QA_SCENARIO: Test::Integration::RegistryTLS
QA_RSPEC_TAGS: ""
GITLAB_TLS_CERTIFICATE: $QA_GITLAB_TLS_CERTIFICATE
- GITLAB_QA_OPTS: --omnibus-config registry_object_storage $EXTRA_GITLAB_QA_OPTS
+ GITLAB_QA_OPTS: --omnibus-config registry_object_storage
importers:
extends:
diff --git a/.gitlab/ci/qa-common/main.gitlab-ci.yml b/.gitlab/ci/qa-common/main.gitlab-ci.yml
index 94236730f6a..c44aa327762 100644
--- a/.gitlab/ci/qa-common/main.gitlab-ci.yml
+++ b/.gitlab/ci/qa-common/main.gitlab-ci.yml
@@ -68,7 +68,6 @@ stages:
QA_INTERCEPT_REQUESTS: "true"
GITLAB_LICENSE_MODE: test
GITLAB_QA_ADMIN_ACCESS_TOKEN: $QA_ADMIN_ACCESS_TOKEN
- GITLAB_QA_OPTS: $EXTRA_GITLAB_QA_OPTS
before_script:
- !reference [.qa-base, before_script]
# Prepend the file paths with the absolute path from inside the container since the files will be read from there
diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml
index eb740c099c3..1aee8934702 100644
--- a/.rubocop_todo/rspec/feature_category.yml
+++ b/.rubocop_todo/rspec/feature_category.yml
@@ -4797,7 +4797,6 @@ RSpec/FeatureCategory:
- 'spec/models/time_tracking/timelog_category_spec.rb'
- 'spec/models/todo_spec.rb'
- 'spec/models/token_with_iv_spec.rb'
- - 'spec/models/tree_spec.rb'
- 'spec/models/trending_project_spec.rb'
- 'spec/models/upload_spec.rb'
- 'spec/models/uploads/fog_spec.rb'
diff --git a/.rubocop_todo/rspec/named_subject.yml b/.rubocop_todo/rspec/named_subject.yml
index c99d6784b73..5099162c5a8 100644
--- a/.rubocop_todo/rspec/named_subject.yml
+++ b/.rubocop_todo/rspec/named_subject.yml
@@ -2356,7 +2356,6 @@ RSpec/NamedSubject:
- 'spec/lib/gitlab/pagination/offset_pagination_spec.rb'
- 'spec/lib/gitlab/patch/draw_route_spec.rb'
- 'spec/lib/gitlab/patch/prependable_spec.rb'
- - 'spec/lib/gitlab/patch/sidekiq_scheduled_enq_spec.rb'
- 'spec/lib/gitlab/path_regex_spec.rb'
- 'spec/lib/gitlab/performance_bar/stats_spec.rb'
- 'spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb'
diff --git a/.rubocop_todo/style/inline_disable_annotation.yml b/.rubocop_todo/style/inline_disable_annotation.yml
index 9fcd52993bc..faa79f6aa3a 100644
--- a/.rubocop_todo/style/inline_disable_annotation.yml
+++ b/.rubocop_todo/style/inline_disable_annotation.yml
@@ -2677,7 +2677,6 @@ Style/InlineDisableAnnotation:
- 'lib/gitlab/patch/prependable.rb'
- 'lib/gitlab/patch/redis_cache_store.rb'
- 'lib/gitlab/patch/sidekiq_cron_poller.rb'
- - 'lib/gitlab/patch/sidekiq_scheduled_enq.rb'
- 'lib/gitlab/performance_bar.rb'
- 'lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb'
- 'lib/gitlab/popen/runner.rb'
diff --git a/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue b/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue
index 3544ff448b7..9540e1ed3ea 100644
--- a/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue
+++ b/app/assets/javascripts/ci/catalog/components/list/ci_resources_list_item.vue
@@ -112,7 +112,7 @@ export default {
</div>
</div>
<div
- class="gl-display-flex gl-sm-flex-direction-column gl-justify-content-space-between gl-font-sm"
+ class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-justify-content-space-between gl-font-sm"
>
<span class="gl-display-flex gl-flex-basis-two-thirds">{{ resource.description }}</span>
<div class="gl-display-flex gl-justify-content-end">
diff --git a/app/assets/javascripts/commons/index.js b/app/assets/javascripts/commons/index.js
index d2a5ef83faf..6c9283a22cf 100644
--- a/app/assets/javascripts/commons/index.js
+++ b/app/assets/javascripts/commons/index.js
@@ -3,6 +3,3 @@ import './bootstrap';
import './vue';
import './gitlab_ui';
import '../lib/utils/axios_utils';
-import { openUserCountsBroadcast } from './nav/user_merge_requests';
-
-openUserCountsBroadcast();
diff --git a/app/assets/javascripts/commons/nav/user_merge_requests.js b/app/assets/javascripts/commons/nav/user_merge_requests.js
deleted file mode 100644
index 90dca0310f3..00000000000
--- a/app/assets/javascripts/commons/nav/user_merge_requests.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import { getUserCounts } from '~/rest_api';
-
-let channel;
-
-function broadcastCount(newCount) {
- if (!channel) {
- return;
- }
-
- channel.postMessage(newCount);
-}
-
-function updateUserMergeRequestCounts(newCount) {
- const mergeRequestsCountEl = document.querySelector('.js-assigned-mr-count');
- mergeRequestsCountEl.textContent = newCount.toLocaleString();
-}
-
-function updateReviewerMergeRequestCounts(newCount) {
- const mergeRequestsCountEl = document.querySelector('.js-reviewer-mr-count');
- mergeRequestsCountEl.textContent = newCount.toLocaleString();
-}
-
-function updateMergeRequestCounts(newCount) {
- const mergeRequestsCountEl = document.querySelector('.js-merge-requests-count');
- mergeRequestsCountEl.textContent = newCount.toLocaleString();
- mergeRequestsCountEl.classList.toggle('gl-display-none', Number(newCount) === 0);
-}
-
-/**
- * Refresh user counts (and broadcast if open)
- */
-export function refreshUserMergeRequestCounts() {
- if (gon?.use_new_navigation) {
- // The new sidebar manages _all_ the counts in
- // ~/super_sidebar/user_counts_manager.js
- document.dispatchEvent(new CustomEvent('userCounts:fetch'));
- return Promise.resolve();
- }
- return getUserCounts()
- .then(({ data }) => {
- const assignedMergeRequests = data.assigned_merge_requests;
- const reviewerMergeRequests = data.review_requested_merge_requests;
- const fullCount = assignedMergeRequests + reviewerMergeRequests;
-
- updateUserMergeRequestCounts(assignedMergeRequests);
- updateReviewerMergeRequestCounts(reviewerMergeRequests);
- updateMergeRequestCounts(fullCount);
- broadcastCount(fullCount);
- })
- .catch((ex) => {
- console.error(ex); // eslint-disable-line no-console
- });
-}
-
-/**
- * Close the broadcast channel for user counts
- */
-export function closeUserCountsBroadcast() {
- if (!channel) {
- return;
- }
-
- channel.close();
- channel = null;
-}
-
-/**
- * Open the broadcast channel for user counts, adds user id so we only update
- *
- * **Please note:**
- * Not supported in all browsers, but not polyfilling for now
- * to keep bundle size small and
- * no special functionality lost except cross tab notifications
- */
-export function openUserCountsBroadcast() {
- if (gon?.use_new_navigation) {
- // The new sidebar broadcasts _all counts_ and updates
- // them accordingly. Therefore we do not need this manager
- // ~/super_sidebar/user_counts_manager.js
- return;
- }
- closeUserCountsBroadcast();
-
- if (window.BroadcastChannel) {
- const currentUserId = typeof gon !== 'undefined' && gon && gon.current_user_id;
- if (currentUserId) {
- channel = new BroadcastChannel(`mr_count_channel_${currentUserId}`);
- channel.onmessage = (ev) => {
- updateMergeRequestCounts(ev.data);
- };
- }
- }
-}
diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json
index 1439a3181b0..ee5f8c2ac2f 100644
--- a/app/assets/javascripts/graphql_shared/possible_types.json
+++ b/app/assets/javascripts/graphql_shared/possible_types.json
@@ -201,5 +201,9 @@
"WorkItemWidgetStatus",
"WorkItemWidgetTestReports",
"WorkItemWidgetWeight"
+ ],
+ "WorkItemWidgetDefinition": [
+ "WorkItemWidgetDefinitionAssignees",
+ "WorkItemWidgetDefinitionGeneric"
]
}
diff --git a/app/assets/javascripts/invite_members/components/invite_group_trigger.vue b/app/assets/javascripts/invite_members/components/invite_group_trigger.vue
index 424a9d3fabd..b0cfe670edc 100644
--- a/app/assets/javascripts/invite_members/components/invite_group_trigger.vue
+++ b/app/assets/javascripts/invite_members/components/invite_group_trigger.vue
@@ -28,12 +28,7 @@ export default {
</script>
<template>
- <gl-button
- :class="classes"
- data-qa-selector="invite_a_group_button"
- data-test-id="invite-group-button"
- @click="openModal"
- >
+ <gl-button :class="classes" data-testid="invite-group-button" @click="openModal">
{{ displayText }}
</gl-button>
</template>
diff --git a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
index 6efb7a6cdf1..b37333d923c 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
@@ -4,7 +4,7 @@ import { s__ } from '~/locale';
import eventHub from '../event_hub';
import {
TRIGGER_ELEMENT_BUTTON,
- TRIGGER_DEFAULT_QA_SELECTOR,
+ TRIGGER_DEFAULT_TESTID,
TRIGGER_ELEMENT_WITH_EMOJI,
TRIGGER_ELEMENT_DROPDOWN_WITH_EMOJI,
TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN,
@@ -42,18 +42,17 @@ export default {
required: false,
default: 'button',
},
- qaSelector: {
+ testId: {
type: String,
required: false,
- default: TRIGGER_DEFAULT_QA_SELECTOR,
+ default: TRIGGER_DEFAULT_TESTID,
},
},
computed: {
componentAttributes() {
return {
class: this.classes,
- 'data-qa-selector': this.qaSelector,
- 'data-test-id': 'invite-members-button',
+ 'data-testid': this.testId,
};
},
item() {
diff --git a/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
index 81aea12f311..86e3e56a525 100644
--- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue
+++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
@@ -168,7 +168,6 @@ export default {
variant: 'confirm',
disabled: this.submitDisabled,
loading: this.isLoading,
- 'data-qa-selector': 'invite_button',
},
};
},
@@ -298,7 +297,6 @@ export default {
<gl-collapsible-listbox
:id="dropdownId"
v-model="selectedAccessLevel"
- data-qa-selector="access_level_dropdown"
data-testid="access-level-dropdown"
:items="accessLevelsOptions"
block
diff --git a/app/assets/javascripts/invite_members/components/members_token_select.vue b/app/assets/javascripts/invite_members/components/members_token_select.vue
index 0be04b7af35..015cadc9993 100644
--- a/app/assets/javascripts/invite_members/components/members_token_select.vue
+++ b/app/assets/javascripts/invite_members/components/members_token_select.vue
@@ -102,7 +102,6 @@ export default {
textInputAttrs() {
return {
'data-testid': 'members-token-select-input',
- 'data-qa-selector': 'members_token_select_input',
id: this.inputId,
};
},
diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js
index 93386e5504b..f44313ff41f 100644
--- a/app/assets/javascripts/invite_members/constants.js
+++ b/app/assets/javascripts/invite_members/constants.js
@@ -20,7 +20,7 @@ export const TRIGGER_ELEMENT_WITH_EMOJI = 'text-emoji';
export const TRIGGER_ELEMENT_DROPDOWN_WITH_EMOJI = 'dropdown-text-emoji';
export const TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN = 'dropdown-text';
export const INVITE_MEMBER_MODAL_TRACKING_CATEGORY = 'invite_members_modal';
-export const TRIGGER_DEFAULT_QA_SELECTOR = 'invite_members_button';
+export const TRIGGER_DEFAULT_TESTID = 'invite-members-button';
export const IMPORT_PROJECT_MEMBERS_MODAL_TRACKING_CATEGORY = 'invite_project_members_modal';
export const IMPORT_PROJECT_MEMBERS_MODAL_TRACKING_LABEL = 'project-members-page';
export const MEMBERS_MODAL_DEFAULT_TITLE = s__('InviteMembersModal|Invite members');
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 329d6cfec00..87b55b19c08 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -3,7 +3,6 @@ import { GlAlert, GlButton, GlIcon, GlFormCheckbox, GlTooltipDirective } from '@
import $ from 'jquery';
// eslint-disable-next-line no-restricted-imports
import { mapActions, mapGetters, mapState } from 'vuex';
-import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import { createAlert } from '~/alert';
import { STATUS_CLOSED, STATUS_MERGED, STATUS_OPEN, STATUS_REOPENED } from '~/issues/constants';
import { containsSensitiveToken, confirmSensitiveAction } from '~/lib/utils/secret_detection';
@@ -17,6 +16,7 @@ import { badgeState } from '~/merge_requests/components/merge_request_header.vue
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import { trackSavedUsingEditor } from '~/vue_shared/components/markdown/tracking';
+import { fetchUserCounts } from '~/super_sidebar/user_counts_fetch';
import * as constants from '../constants';
import eventHub from '../event_hub';
@@ -297,8 +297,10 @@ export default {
const toggleState = this.isOpen ? this.closeIssuable : this.reopenIssuable;
toggleState()
- .then(() => badgeState.updateStatus && badgeState.updateStatus())
- .then(refreshUserMergeRequestCounts)
+ .then(() => {
+ fetchUserCounts();
+ return badgeState?.updateStatus();
+ })
.catch(() =>
createAlert({
message: constants.toggleStateErrorMessage[this.noteableType][this.openState],
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
index 0563ed8394c..897cd3583c8 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
@@ -1,5 +1,4 @@
<script>
-import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import { createAlert } from '~/alert';
import { TYPE_ISSUE } from '~/issues/constants';
import { __ } from '~/locale';
@@ -104,8 +103,6 @@ export default {
.then(() => {
this.loading = false;
this.store.resetChanging();
-
- refreshUserMergeRequestCounts();
})
.catch(() => {
this.loading = false;
diff --git a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
index 440d0344ae0..1d1dbd51756 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
@@ -2,12 +2,12 @@
// NOTE! For the first iteration, we are simply copying the implementation of Assignees
// It will soon be overhauled in Issue https://gitlab.com/gitlab-org/gitlab/-/issues/233736
import Vue from 'vue';
-import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import { createAlert } from '~/alert';
import { TYPE_ISSUE } from '~/issues/constants';
import { __ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { fetchUserCounts } from '~/super_sidebar/user_counts_fetch';
import eventHub from '../../event_hub';
import getMergeRequestReviewersQuery from '../../queries/get_merge_request_reviewers.query.graphql';
import mergeRequestReviewersUpdatedSubscription from '../../queries/merge_request_reviewers.subscription.graphql';
@@ -157,7 +157,7 @@ export default {
.saveReviewers(this.field)
.then(() => {
this.loading = false;
- refreshUserMergeRequestCounts();
+ fetchUserCounts();
this.$apollo.queries.issuable.refetch();
})
.catch(() => {
diff --git a/app/assets/javascripts/super_sidebar/user_counts_fetch.js b/app/assets/javascripts/super_sidebar/user_counts_fetch.js
new file mode 100644
index 00000000000..779cb2609c2
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/user_counts_fetch.js
@@ -0,0 +1,10 @@
+/**
+ * This triggers a re-fetch of the user counts
+ *
+ * It is separate from the user_counts_manager, so that
+ * this function is side-effect free and can be used in
+ * anywhere in the app without bloating bundle size
+ */
+export function fetchUserCounts() {
+ document.dispatchEvent(new CustomEvent('userCounts:fetch'));
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue
index 9258bc39bcb..2c5f6b9a3ec 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue
@@ -1,9 +1,9 @@
<script>
-import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import { STATUS_MERGED } from '~/issues/constants';
import simplePoll from '~/lib/utils/simple_poll';
import MergeRequest from '~/merge_request';
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
+import { fetchUserCounts } from '~/super_sidebar/user_counts_fetch';
import eventHub from '../../event_hub';
import { MERGE_ACTIVE_STATUS_PHRASES, STATE_MACHINE } from '../../constants';
import StatusIcon from '../mr_widget_status_icon.vue';
@@ -58,7 +58,7 @@ export default {
MergeRequest.decreaseCounter();
stopPolling();
- refreshUserMergeRequestCounts();
+ fetchUserCounts();
// If user checked remove source branch and we didn't remove the branch yet
// we should start another polling for source branch remove process
diff --git a/app/assets/javascripts/work_items/components/work_item_parent.vue b/app/assets/javascripts/work_items/components/work_item_parent.vue
index ce30f7985cf..0c0842a3e05 100644
--- a/app/assets/javascripts/work_items/components/work_item_parent.vue
+++ b/app/assets/javascripts/work_items/components/work_item_parent.vue
@@ -211,6 +211,7 @@ export default {
id="work-item-parent-listbox-value"
class="gl-max-w-max-content"
data-testid="work-item-parent-listbox"
+ block
searchable
is-check-centered
category="tertiary"
diff --git a/app/assets/stylesheets/application_utilities.scss b/app/assets/stylesheets/application_utilities.scss
index 8bec12784ed..817e983a0ec 100644
--- a/app/assets/stylesheets/application_utilities.scss
+++ b/app/assets/stylesheets/application_utilities.scss
@@ -10,5 +10,3 @@
// Gitlab UI util classes
@import '@gitlab/ui/src/scss/utilities';
-
-@import 'tmp_utilities'; \ No newline at end of file
diff --git a/app/assets/stylesheets/tmp_utilities.scss b/app/assets/stylesheets/tmp_utilities.scss
deleted file mode 100644
index c8144e97693..00000000000
--- a/app/assets/stylesheets/tmp_utilities.scss
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * DISCLAIMER
- * This is a temporary stylesheet meant to assist in migrating away from desktop-first responsive
- * CSS utilities.
- * DO NOT add utils in here unless you are actively taking part in in the migration.
- * We needed this new file for temporary utils to be defined _after_ the main, non-responsive
- * GitLab UI util.
- * This file is scheduled to be removed by the end of 2023.
- */
- .gl-sm-w-25p {
- @include gl-media-breakpoint-up(sm) {
- width: 25%;
- }
-}
-
-.gl-sm-w-30p {
- @include gl-media-breakpoint-up(sm) {
- width: 30%;
- }
-}
-
-.gl-sm-w-40p {
- @include gl-media-breakpoint-up(sm) {
- width: 40%;
- }
-}
-
-.gl-sm-w-75p {
- @include gl-media-breakpoint-up(sm) {
- width: 75%;
- }
-}
-
-.gl-sm-flex-direction-row-reverse {
- @include gl-media-breakpoint-up(sm) {
- flex-direction: row-reverse;
- }
-}
-
-.gl-sm-mb-0 {
- @include gl-media-breakpoint-up(sm) {
- margin-bottom: 0;
- }
-}
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index ce0c8291eda..6368ae914dc 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -344,15 +344,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def discussions
- if Feature.enabled?(:only_highlight_discussions_requested, project)
- super do |discussion_notes|
- note_ids = discussion_notes.flat_map { |x| x.notes.collect(&:id) }
- merge_request.discussions_diffs.load_highlight(diff_note_ids: note_ids)
- end
- else
- merge_request.discussions_diffs.load_highlight
-
- super
+ super do |discussion_notes|
+ note_ids = discussion_notes.flat_map { |x| x.notes.collect(&:id) }
+ merge_request.discussions_diffs.load_highlight(diff_note_ids: note_ids)
end
end
diff --git a/app/graphql/resolvers/work_items/types_resolver.rb b/app/graphql/resolvers/work_items/types_resolver.rb
index 2508125d392..d560c2bf612 100644
--- a/app/graphql/resolvers/work_items/types_resolver.rb
+++ b/app/graphql/resolvers/work_items/types_resolver.rb
@@ -3,6 +3,8 @@
module Resolvers
module WorkItems
class TypesResolver < BaseResolver
+ include LooksAhead
+
type Types::WorkItems::TypeType.connection_type, null: true
argument :taskable, ::GraphQL::Types::Boolean,
@@ -10,13 +12,23 @@ module Resolvers
description: 'If `true`, only taskable work item types will be returned.' \
' Argument is experimental and can be removed in the future without notice.'
- def resolve(taskable: nil)
+ def resolve_with_lookahead(taskable: nil)
+ context.scoped_set!(:resource_parent, object)
+
# This will require a finder in the future when groups/projects get their work item types
# All groups/projects use the default types for now
base_scope = ::WorkItems::Type.default
base_scope = base_scope.by_type(:task) if taskable
- base_scope.order_by_name_asc
+ apply_lookahead(base_scope.order_by_name_asc)
+ end
+
+ private
+
+ def preloads
+ {
+ widget_definitions: :enabled_widget_definitions
+ }
end
end
end
diff --git a/app/graphql/types/work_item_type.rb b/app/graphql/types/work_item_type.rb
index 103a1c0ec9b..b42684e650b 100644
--- a/app/graphql/types/work_item_type.rb
+++ b/app/graphql/types/work_item_type.rb
@@ -67,6 +67,12 @@ module Types
expose_permissions Types::PermissionTypes::WorkItem
+ def work_item_type
+ context.scoped_set!(:resource_parent, object.resource_parent)
+
+ object.work_item_type
+ end
+
def web_url
Gitlab::UrlBuilder.build(object)
end
diff --git a/app/graphql/types/work_items/type_type.rb b/app/graphql/types/work_items/type_type.rb
index 4d008a21b9c..b42d73544dc 100644
--- a/app/graphql/types/work_items/type_type.rb
+++ b/app/graphql/types/work_items/type_type.rb
@@ -7,12 +7,20 @@ module Types
authorize :read_work_item_type
- field :icon_name, GraphQL::Types::String, null: true,
- description: 'Icon name of the work item type.'
- field :id, Types::GlobalIDType[::WorkItems::Type], null: false,
- description: 'Global ID of the work item type.'
- field :name, GraphQL::Types::String, null: false,
- description: 'Name of the work item type.'
+ field :icon_name, GraphQL::Types::String,
+ null: true,
+ description: 'Icon name of the work item type.'
+ field :id, Types::GlobalIDType[::WorkItems::Type],
+ null: false,
+ description: 'Global ID of the work item type.'
+ field :name, GraphQL::Types::String,
+ null: false,
+ description: 'Name of the work item type.'
+ field :widget_definitions, [Types::WorkItems::WidgetDefinitionInterface],
+ null: true,
+ description: 'Available widgets for the work item type.',
+ method: :widgets,
+ alpha: { milestone: '16.7' }
end
end
end
diff --git a/app/graphql/types/work_items/widget_definition_interface.rb b/app/graphql/types/work_items/widget_definition_interface.rb
new file mode 100644
index 00000000000..0cfa6c978fc
--- /dev/null
+++ b/app/graphql/types/work_items/widget_definition_interface.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module WidgetDefinitionInterface
+ include Types::BaseInterface
+
+ graphql_name 'WorkItemWidgetDefinition'
+
+ field :type, ::Types::WorkItems::WidgetTypeEnum,
+ null: false,
+ description: 'Widget type.'
+
+ ORPHAN_TYPES = [
+ ::Types::WorkItems::WidgetDefinitions::AssigneesType,
+ ::Types::WorkItems::WidgetDefinitions::GenericType
+ ].freeze
+
+ def self.resolve_type(object, _context)
+ if object == ::WorkItems::Widgets::Assignees
+ ::Types::WorkItems::WidgetDefinitions::AssigneesType
+ else
+ ::Types::WorkItems::WidgetDefinitions::GenericType
+ end
+ end
+
+ orphan_types(*ORPHAN_TYPES)
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widget_definitions/assignees_type.rb b/app/graphql/types/work_items/widget_definitions/assignees_type.rb
new file mode 100644
index 00000000000..3113543ec1b
--- /dev/null
+++ b/app/graphql/types/work_items/widget_definitions/assignees_type.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module WidgetDefinitions
+ # rubocop:disable Graphql/AuthorizeTypes -- Authorization too granular, parent type is authorized
+ class AssigneesType < BaseObject
+ graphql_name 'WorkItemWidgetDefinitionAssignees'
+ description 'Represents an assignees widget definition'
+
+ implements Types::WorkItems::WidgetDefinitionInterface
+
+ field :can_invite_members, GraphQL::Types::Boolean,
+ null: false,
+ description: 'Indicates whether the current user can invite members to the work item\'s parent.'
+
+ def can_invite_members
+ object.can_invite_members?(current_user, resource_parent)
+ end
+
+ private
+
+ def resource_parent
+ context[:resource_parent]
+ end
+ end
+ # rubocop:enable Graphql/AuthorizeTypes
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widget_definitions/generic_type.rb b/app/graphql/types/work_items/widget_definitions/generic_type.rb
new file mode 100644
index 00000000000..f3817ade654
--- /dev/null
+++ b/app/graphql/types/work_items/widget_definitions/generic_type.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module WidgetDefinitions
+ # rubocop:disable Graphql/AuthorizeTypes -- Authorization too granular, parent type is authorized
+ class GenericType < BaseObject
+ graphql_name 'WorkItemWidgetDefinitionGeneric'
+ description 'Represents a generic widget definition'
+
+ implements Types::WorkItems::WidgetDefinitionInterface
+ end
+ # rubocop:enable Graphql/AuthorizeTypes
+ end
+ end
+end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index b8433191d84..f1db4be8eb4 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -78,6 +78,15 @@ class Ability
policy = policy_for(user, subject)
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/421150#note_1638311666
+ if ability == :read_namespace && Feature.enabled?(:log_read_namespace_usages, Feature.current_request)
+ Gitlab::AppLogger.info(
+ message: 'Ability is in use',
+ ability: ability,
+ caller_locations: caller_locations(1, 5).map(&:to_s)
+ )
+ end
+
before_check(policy, ability.to_sym, user, subject, opts)
case opts[:scope]
diff --git a/app/models/bulk_import.rb b/app/models/bulk_import.rb
index a7ace7429d7..80e0412ad19 100644
--- a/app/models/bulk_import.rb
+++ b/app/models/bulk_import.rb
@@ -16,7 +16,7 @@ class BulkImport < ApplicationRecord
enum source_type: { gitlab: 0 }
- scope :stale, -> { where('created_at < ?', 8.hours.ago).where(status: [0, 1]) }
+ scope :stale, -> { where('created_at < ?', 24.hours.ago).where(status: [0, 1]) }
scope :order_by_created_at, ->(direction) { order(created_at: direction) }
state_machine :status, initial: :created do
diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb
index a075c2f7e4f..d7ccd33d19d 100644
--- a/app/models/bulk_imports/entity.rb
+++ b/app/models/bulk_imports/entity.rb
@@ -54,7 +54,7 @@ class BulkImports::Entity < ApplicationRecord
enum source_type: { group_entity: 0, project_entity: 1 }
scope :by_user_id, ->(user_id) { joins(:bulk_import).where(bulk_imports: { user_id: user_id }) }
- scope :stale, -> { where('created_at < ?', 8.hours.ago).where(status: [0, 1]) }
+ scope :stale, -> { where('created_at < ?', 24.hours.ago).where(status: [0, 1]) }
scope :by_bulk_import_id, ->(bulk_import_id) { where(bulk_import_id: bulk_import_id) }
scope :order_by_created_at, ->(direction) { order(created_at: direction) }
diff --git a/app/models/bulk_imports/tracker.rb b/app/models/bulk_imports/tracker.rb
index d9efd489af5..b06583c8e06 100644
--- a/app/models/bulk_imports/tracker.rb
+++ b/app/models/bulk_imports/tracker.rb
@@ -21,7 +21,7 @@ class BulkImports::Tracker < ApplicationRecord
validates :stage, presence: true
- delegate :file_extraction_pipeline?, to: :pipeline_class
+ delegate :file_extraction_pipeline?, :abort_on_failure?, to: :pipeline_class
DEFAULT_PAGE_SIZE = 500
STALE_AFTER = 4.hours
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index f0093445ba8..36f4a0ef426 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -395,9 +395,9 @@ class Deployment < ApplicationRecord
def update_status(status)
update_status!(status)
rescue StandardError => e
- Gitlab::ErrorTracking.track_exception(
- StatusUpdateError.new(e.message), deployment_id: self.id)
-
+ error = StatusUpdateError.new(e.message)
+ error.set_backtrace(caller)
+ Gitlab::ErrorTracking.track_exception(error, deployment_id: self.id)
false
end
@@ -410,9 +410,9 @@ class Deployment < ApplicationRecord
update_status!(job_status)
rescue StandardError => e
- Gitlab::ErrorTracking.track_exception(
- StatusSyncError.new(e.message), deployment_id: self.id, job_id: job.id)
-
+ error = StatusSyncError.new(e.message)
+ error.set_backtrace(caller)
+ Gitlab::ErrorTracking.track_exception(error, deployment_id: self.id, job_id: job.id)
false
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 37e86df9b7c..7ff32706566 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -506,7 +506,7 @@ class Project < ApplicationRecord
delegate :merge_requests_access_level, :forking_access_level, :issues_access_level, :wiki_access_level, :snippets_access_level, :builds_access_level, :repository_access_level, :package_registry_access_level, :pages_access_level, :metrics_dashboard_access_level, :analytics_access_level, :operations_access_level, :security_and_compliance_access_level, :container_registry_access_level, :environments_access_level, :feature_flags_access_level, :monitor_access_level, :releases_access_level, :infrastructure_access_level, :model_experiments_access_level, to: :project_feature, allow_nil: true
delegate :name, to: :owner, allow_nil: true, prefix: true
- delegate :jira_dvcs_server_last_sync_at, :jira_dvcs_cloud_last_sync_at, to: :feature_usage
+ delegate :jira_dvcs_server_last_sync_at, to: :feature_usage
delegate :last_pipeline, to: :commit, allow_nil: true
with_options to: :team do
@@ -677,7 +677,6 @@ class Project < ApplicationRecord
scope :non_archived, -> { where(archived: false) }
scope :with_push, -> { joins(:events).merge(Event.pushed_action) }
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
- scope :with_jira_dvcs_cloud, -> { joins(:feature_usage).merge(ProjectFeatureUsage.with_jira_dvcs_integration_enabled(cloud: true)) }
scope :with_jira_dvcs_server, -> { joins(:feature_usage).merge(ProjectFeatureUsage.with_jira_dvcs_integration_enabled(cloud: false)) }
scope :inc_routes, -> { includes(:route, namespace: :route) }
scope :with_statistics, -> { includes(:statistics) }
diff --git a/app/models/project_feature_usage.rb b/app/models/project_feature_usage.rb
index 5e47ec6310d..1f371156873 100644
--- a/app/models/project_feature_usage.rb
+++ b/app/models/project_feature_usage.rb
@@ -1,6 +1,9 @@
# frozen_string_literal: true
class ProjectFeatureUsage < ApplicationRecord
+ include IgnorableColumns
+ ignore_column :jira_dvcs_cloud_last_sync_at, remove_with: '16.9', remove_after: '2024-01-21'
+
self.primary_key = :project_id
JIRA_DVCS_CLOUD_FIELD = 'jira_dvcs_cloud_last_sync_at'
diff --git a/app/models/repository.rb b/app/models/repository.rb
index e639a389e0a..e4dba8a1911 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1271,6 +1271,15 @@ class Repository
.map(&:to_h)
end
+ def filter_generated_files(revision, paths)
+ # NOTE: We should still support linguist-generated override for GitHub compatibility,
+ # but `gitlab-*` prefixed overrides would give us a better control moving forward.
+ generated_files = get_file_attributes(revision, paths, %w[gitlab-generated linguist-generated])
+ .pluck(:path)
+
+ paths - generated_files
+ end
+
private
def ancestor_cache_key(ancestor_id, descendant_id)
diff --git a/app/models/user.rb b/app/models/user.rb
index 109b0506648..f6a455c280c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2064,7 +2064,11 @@ class User < MainClusterwide::ApplicationRecord
def terms_accepted?
return true if project_bot? || service_account? || security_policy_bot?
- accepted_term_id.present?
+ if Feature.enabled?(:enforce_acceptance_of_changed_terms)
+ !!ApplicationSetting::Term.latest&.accepted_by_user?(self)
+ else
+ accepted_term_id.present?
+ end
end
def required_terms_not_accepted?
diff --git a/app/models/vulnerability.rb b/app/models/vulnerability.rb
index 1dff78354db..a9e1b2dbc5b 100644
--- a/app/models/vulnerability.rb
+++ b/app/models/vulnerability.rb
@@ -5,7 +5,7 @@ class Vulnerability < ApplicationRecord
include EachBatch
include IgnorableColumns
- ignore_column %i[epic_id milestone_id], remove_with: '16.9', remove_after: '2023-01-13'
+ ignore_column %i[epic_id milestone_id last_edited_at], remove_with: '16.9', remove_after: '2023-01-13'
alias_attribute :vulnerability_id, :id
diff --git a/app/models/work_items/widgets/assignees.rb b/app/models/work_items/widgets/assignees.rb
index 0707b03e647..839c82f8003 100644
--- a/app/models/work_items/widgets/assignees.rb
+++ b/app/models/work_items/widgets/assignees.rb
@@ -13,6 +13,10 @@ module WorkItems
def self.quick_action_params
[:assignee_ids]
end
+
+ def self.can_invite_members?(user, resource_parent)
+ user.can?("admin_#{resource_parent.to_ability_name}_member".to_sym, resource_parent)
+ end
end
end
end
diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb
index 7b01dccff87..0a9d9fdeae8 100644
--- a/app/policies/ci/runner_policy.rb
+++ b/app/policies/ci/runner_policy.rb
@@ -5,6 +5,7 @@ module Ci
with_options scope: :subject, score: 0
condition(:locked, scope: :subject) { @subject.locked? }
+ with_options scope: :subject, score: 20
condition(:owned_runner) do
@user.owns_runner?(@subject)
end
@@ -23,6 +24,11 @@ module Ci
@subject.group_type?
end
+ with_options scope: :subject, score: 0
+ condition(:is_project_runner) do
+ @subject.project_type?
+ end
+
with_options scope: :user, score: 5
condition(:any_developer_maintainer_owned_groups_inheriting_shared_runners) do
@user.developer_maintainer_owned_groups.with_shared_runners_enabled.any?
@@ -44,6 +50,12 @@ module Ci
end
end
+ with_options score: 6
+ condition(:developer_in_any_associated_projects) do
+ # Check if runner is associated to any projects where user is a developer+
+ @subject.projects.visible_to_user_and_access_level(@user, Gitlab::Access::DEVELOPER).exists?
+ end
+
condition(:belongs_to_multiple_projects, scope: :subject) do
@subject.belongs_to_more_than_one_project?
end
@@ -63,6 +75,10 @@ module Ci
enable :read_runner
end
+ rule { is_project_runner & developer_in_any_associated_projects }.policy do
+ enable :read_runner
+ end
+
rule { is_group_runner & any_associated_projects_in_group_runner_inheriting_group_runners }.policy do
enable :read_runner
end
diff --git a/app/workers/bulk_imports/pipeline_worker.rb b/app/workers/bulk_imports/pipeline_worker.rb
index 4bf6da73fdf..ee12096b835 100644
--- a/app/workers/bulk_imports/pipeline_worker.rb
+++ b/app/workers/bulk_imports/pipeline_worker.rb
@@ -62,7 +62,7 @@ module BulkImports
@entity = ::BulkImports::Entity.find(entity_id)
@pipeline_tracker = ::BulkImports::Tracker.find(pipeline_tracker_id)
- fail_tracker(exception)
+ fail_pipeline(exception)
end
private
@@ -100,9 +100,11 @@ module BulkImports
entity.bulk_import.source_version_info.to_s
end
- def fail_tracker(exception)
+ def fail_pipeline(exception)
pipeline_tracker.update!(status_event: 'fail_op', jid: jid)
+ entity.fail_op! if pipeline_tracker.abort_on_failure?
+
log_exception(exception, log_attributes(message: 'Pipeline failed'))
Gitlab::ErrorTracking.track_exception(exception, log_attributes)
diff --git a/app/workers/concerns/gitlab/github_import/stage_methods.rb b/app/workers/concerns/gitlab/github_import/stage_methods.rb
index 5c63c667a03..14a2d859c14 100644
--- a/app/workers/concerns/gitlab/github_import/stage_methods.rb
+++ b/app/workers/concerns/gitlab/github_import/stage_methods.rb
@@ -37,8 +37,6 @@ module Gitlab
# - Continue their loop from where it left off:
# https://gitlab.com/gitlab-org/gitlab/-/blob/024235ec/lib/gitlab/github_import/importer/pull_requests/review_requests_importer.rb#L15
def resumes_work_when_interrupted!
- return unless Feature.enabled?(:github_importer_raise_max_interruptions)
-
sidekiq_options max_retries_after_interruption: MAX_RETRIES_AFTER_INTERRUPTION
end
end
diff --git a/config/feature_flags/development/github_importer_raise_max_interruptions.yml b/config/feature_flags/development/enforce_acceptance_of_changed_terms.yml
index 3cbcc10865f..5864d25bc89 100644
--- a/config/feature_flags/development/github_importer_raise_max_interruptions.yml
+++ b/config/feature_flags/development/enforce_acceptance_of_changed_terms.yml
@@ -1,8 +1,8 @@
---
-name: github_importer_raise_max_interruptions
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134949
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/429306
-milestone: '16.6'
+name: enforce_acceptance_of_changed_terms
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136039
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/430799
+milestone: '16.7'
type: development
-group: group::import and integrate
+group: group::authentication
default_enabled: false
diff --git a/config/feature_flags/development/only_highlight_discussions_requested.yml b/config/feature_flags/development/log_read_namespace_usages.yml
index 8dfb93c33e0..fd844a9c9f5 100644
--- a/config/feature_flags/development/only_highlight_discussions_requested.yml
+++ b/config/feature_flags/development/log_read_namespace_usages.yml
@@ -1,8 +1,8 @@
---
-name: only_highlight_discussions_requested
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135096
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/429489
-milestone: '16.6'
+name: log_read_namespace_usages
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136617
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/421150
+milestone: '16.7'
type: development
-group: group::code review
+group: group::tenant scale
default_enabled: false
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 9b6a9b17935..8df12671f26 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -85,12 +85,6 @@ Sidekiq.configure_server do |config|
end
if enable_reliable_fetch?
- if Gitlab::Utils.to_boolean(ENV['SIDEKIQ_ENABLE_DUAL_NAMESPACE_POLLING'], default: true)
- # set non-namespaced store for fetcher to poll both namespaced and non-namespaced queues
- config[:alternative_store] = ::Gitlab::Redis::Queues
- config[:namespace] = Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE
- end
-
config[:semi_reliable_fetch] = enable_semi_reliable_fetch_mode?
Sidekiq::ReliableFetch.setup_reliable_fetch!(config)
end
@@ -119,7 +113,6 @@ Sidekiq.configure_client do |config|
config.client_middleware(&Gitlab::SidekiqMiddleware.client_configurator)
end
-Sidekiq::Scheduled::Enq.prepend Gitlab::Patch::SidekiqScheduledEnq
Sidekiq::Scheduled::Poller.prepend Gitlab::Patch::SidekiqPoller
Sidekiq::Cron::Poller.prepend Gitlab::Patch::SidekiqPoller
Sidekiq::Cron::Poller.prepend Gitlab::Patch::SidekiqCronPoller
diff --git a/config/metrics/counts_28d/20210216181152_projects_jira_dvcs_cloud_active.yml b/config/metrics/counts_28d/20210216181152_projects_jira_dvcs_cloud_active.yml
index 3169c02624a..650e84cca79 100644
--- a/config/metrics/counts_28d/20210216181152_projects_jira_dvcs_cloud_active.yml
+++ b/config/metrics/counts_28d/20210216181152_projects_jira_dvcs_cloud_active.yml
@@ -7,7 +7,7 @@ product_section: dev
product_stage: manage
product_group: integration
value_type: number
-status: active
+status: removed
time_frame: 28d
data_source: database
distribution:
@@ -20,3 +20,5 @@ tier:
performance_indicator_type:
- customer_health_score
milestone: "<13.9"
+removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136484
+milestone_removed: "16.7"
diff --git a/config/metrics/counts_28d/20220825232557_count_user_auth.yml b/config/metrics/counts_28d/20220825232557_count_user_auth.yml
index 7fd9a845130..625973f9851 100644
--- a/config/metrics/counts_28d/20220825232557_count_user_auth.yml
+++ b/config/metrics/counts_28d/20220825232557_count_user_auth.yml
@@ -12,8 +12,6 @@ time_frame: 28d
data_source: database
instrumentation_class: CountUserAuthMetric
data_category: optional
-performance_indicator_type:
-- smau
distribution:
- ce
- ee
diff --git a/config/metrics/counts_7d/20220615103711_incident_management_timeline_event_total_unique_counts_weekly.yml b/config/metrics/counts_7d/20220615103711_incident_management_timeline_event_total_unique_counts_weekly.yml
index 20c36d459c3..da73a2b5412 100644
--- a/config/metrics/counts_7d/20220615103711_incident_management_timeline_event_total_unique_counts_weekly.yml
+++ b/config/metrics/counts_7d/20220615103711_incident_management_timeline_event_total_unique_counts_weekly.yml
@@ -20,9 +20,7 @@ options:
- incident_management_timeline_event_created
- incident_management_timeline_event_edited
- incident_management_timeline_event_deleted
-performance_indicator_type:
- - gmau
- - paid_gmau
+performance_indicator_type: []
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216181128_projects_jira_dvcs_cloud_active.yml b/config/metrics/counts_all/20210216181128_projects_jira_dvcs_cloud_active.yml
index 015a7c2bbbe..7b89554c2b2 100644
--- a/config/metrics/counts_all/20210216181128_projects_jira_dvcs_cloud_active.yml
+++ b/config/metrics/counts_all/20210216181128_projects_jira_dvcs_cloud_active.yml
@@ -7,7 +7,7 @@ product_section: dev
product_stage: manage
product_group: integration
value_type: number
-status: active
+status: removed
time_frame: all
data_source: database
distribution:
@@ -18,3 +18,5 @@ tier:
- premium
- ultimate
milestone: "<13.9"
+removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136484
+milestone_removed: "16.7"
diff --git a/doc/.vale/gitlab/Prerequisites.yml b/doc/.vale/gitlab/Prerequisites.yml
new file mode 100644
index 00000000000..239f9277c4d
--- /dev/null
+++ b/doc/.vale/gitlab/Prerequisites.yml
@@ -0,0 +1,14 @@
+---
+# Error: gitlab.Prerequisites
+#
+# The "Prerequisites:" line should always be plural.
+#
+# For a list of all options, see https://vale.sh/docs/topics/styles/
+extends: existence
+message: "Pluralize 'Prerequisites', even if it includes only one item."
+link: https://docs.gitlab.com/ee/development/documentation/topic_types/task.html#task-prerequisites
+level: warning
+nonword: true
+scope: text
+raw:
+ - '^Prerequisite:'
diff --git a/doc/administration/settings/usage_statistics.md b/doc/administration/settings/usage_statistics.md
index 05133d2d5f0..b8e8d96b88e 100644
--- a/doc/administration/settings/usage_statistics.md
+++ b/doc/administration/settings/usage_statistics.md
@@ -72,6 +72,7 @@ In the following table, you can see:
| [Advanced search](../../user/search/advanced_search.md) | GitLab 16.6 and later |
| [DevOps Adoption](../../user/group/devops_adoption/index.md) | GitLab 16.6 and later |
| [Сross-project pipelines with artifacts dependencies](../../ci/yaml/index.md#needsproject) | GitLab 16.7 and later |
+| [Feature flag related issues](../../operations/feature_flags.md#feature-flag-related-issues) | GitLab 16.7 and later |
### Enable registration features
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index ad97272efff..4c64e6521c4 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -27752,6 +27752,7 @@ Check permissions for the current user on a work item.
| <a id="workitemtypeiconname"></a>`iconName` | [`String`](#string) | Icon name of the work item type. |
| <a id="workitemtypeid"></a>`id` | [`WorkItemsTypeID!`](#workitemstypeid) | Global ID of the work item type. |
| <a id="workitemtypename"></a>`name` | [`String!`](#string) | Name of the work item type. |
+| <a id="workitemtypewidgetdefinitions"></a>`widgetDefinitions` **{warning-solid}** | [`[WorkItemWidgetDefinition!]`](#workitemwidgetdefinition) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Available widgets for the work item type. |
### `WorkItemWidgetAssignees`
@@ -27807,6 +27808,27 @@ four standard [pagination arguments](#connection-pagination-arguments):
| ---- | ---- | ----------- |
| <a id="workitemwidgetcurrentusertodoscurrentusertodosstate"></a>`state` | [`TodoStateEnum`](#todostateenum) | State of the to-do items. |
+### `WorkItemWidgetDefinitionAssignees`
+
+Represents an assignees widget definition.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="workitemwidgetdefinitionassigneescaninvitemembers"></a>`canInviteMembers` | [`Boolean!`](#boolean) | Indicates whether the current user can invite members to the work item's parent. |
+| <a id="workitemwidgetdefinitionassigneestype"></a>`type` | [`WorkItemWidgetType!`](#workitemwidgettype) | Widget type. |
+
+### `WorkItemWidgetDefinitionGeneric`
+
+Represents a generic widget definition.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="workitemwidgetdefinitiongenerictype"></a>`type` | [`WorkItemWidgetType!`](#workitemwidgettype) | Widget type. |
+
### `WorkItemWidgetDescription`
Represents a description widget.
@@ -32487,6 +32509,19 @@ Implementations:
| ---- | ---- | ----------- |
| <a id="workitemwidgettype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
+#### `WorkItemWidgetDefinition`
+
+Implementations:
+
+- [`WorkItemWidgetDefinitionAssignees`](#workitemwidgetdefinitionassignees)
+- [`WorkItemWidgetDefinitionGeneric`](#workitemwidgetdefinitiongeneric)
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="workitemwidgetdefinitiontype"></a>`type` | [`WorkItemWidgetType!`](#workitemwidgettype) | Widget type. |
+
## Input types
Types that may be used as arguments (all scalar types may also
diff --git a/doc/api/project_vulnerabilities.md b/doc/api/project_vulnerabilities.md
index 2b4d3ec50df..1fbea66de7f 100644
--- a/doc/api/project_vulnerabilities.md
+++ b/doc/api/project_vulnerabilities.md
@@ -7,7 +7,8 @@ type: reference, api
# Project Vulnerabilities API **(ULTIMATE ALL)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10242) in GitLab 12.6.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10242) in GitLab 12.6.
+> - `last_edited_at` [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/268154) in GitLab 16.7.
WARNING:
This API is in the process of being deprecated and considered unstable.
@@ -79,7 +80,6 @@ Example response:
"vulnerability_id": 103
},
"id": 103,
- "last_edited_at": null,
"last_edited_by_id": null,
"project": {
"created_at": "2020-04-07T13:54:25.634Z",
@@ -167,7 +167,6 @@ Example response:
"vulnerability_id": 103
},
"id": 103,
- "last_edited_at": null,
"last_edited_by_id": null,
"project": {
"created_at": "2020-04-07T13:54:25.634Z",
diff --git a/doc/api/vulnerabilities.md b/doc/api/vulnerabilities.md
index dc5e5c5f509..ff45c5d6cfa 100644
--- a/doc/api/vulnerabilities.md
+++ b/doc/api/vulnerabilities.md
@@ -6,7 +6,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Vulnerabilities API **(ULTIMATE ALL)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10242) in GitLab 12.6.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10242) in GitLab 12.6.
+> - `last_edited_at` [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/268154) in GitLab 16.7.
NOTE:
The former Vulnerabilities API was renamed to Vulnerability Findings API
@@ -67,7 +68,6 @@ Example response:
"due_date": null,
"created_at": "2019-10-13T15:08:40.219Z",
"updated_at": "2019-10-13T15:09:40.382Z",
- "last_edited_at": null,
"closed_at": null
}
```
@@ -117,7 +117,6 @@ Example response:
"due_date": null,
"created_at": "2019-10-13T15:08:40.219Z",
"updated_at": "2019-10-13T15:09:40.382Z",
- "last_edited_at": null,
"closed_at": null
}
```
@@ -167,7 +166,6 @@ Example response:
"due_date": null,
"created_at": "2019-10-13T15:08:40.219Z",
"updated_at": "2019-10-13T15:09:40.382Z",
- "last_edited_at": null,
"closed_at": null
}
```
@@ -217,7 +215,6 @@ Example response:
"due_date": null,
"created_at": "2019-10-13T15:08:40.219Z",
"updated_at": "2019-10-13T15:09:40.382Z",
- "last_edited_at": null,
"closed_at": null
}
```
@@ -267,7 +264,6 @@ Example response:
"due_date": null,
"created_at": "2019-10-13T15:08:40.219Z",
"updated_at": "2019-10-13T15:09:40.382Z",
- "last_edited_at": null,
"closed_at": null
}
```
diff --git a/doc/architecture/blueprints/google_cloud_platform_integration/index.md b/doc/architecture/blueprints/google_cloud_platform_integration/index.md
new file mode 100644
index 00000000000..7fe2c1f655a
--- /dev/null
+++ b/doc/architecture/blueprints/google_cloud_platform_integration/index.md
@@ -0,0 +1,34 @@
+---
+status: ongoing
+creation-date: "2023-10-26"
+authors: [ "@sgoldstein" ]
+coaches: ["@jessieay", "@grzesiek"]
+approvers: ["@sgoldstein", "@jreporter"]
+owning-stage: "~section::ops"
+participating-stages: ["~devops::verify", "~devops::package", "~devops::govern"]
+---
+
+# Google Cloud Platform Integration
+
+GitLab and Google Cloud Platform (GCP) provide complementary tooling which we
+are integrating via our [partnership](https://about.gitlab.com/blog/2023/08/29/gitlab-google-partnership-s3c/).
+
+This design doc is not public at that time. The whole content is
+available in [GitLab-internal project](https://gitlab.com/gitlab-org/architecture/gitlab-gcp-integration/design-doc).
+
+## Who
+
+<!-- vale gitlab.Spelling = NO -->
+
+| Who | Role
+|------------------------|--------------------------------------------------|
+| Sam Goldstein | Director of Engineering, Engineering DRI |
+| Grzegorz Bizon | Distinguished Engineer - Technical Lead |
+| Jessie Young | Principal Engineer |
+| David Fernandez | Staff Engineer |
+| Imre Farkas | Staff Engineer |
+| João Pereira | Staff Engineer |
+| Joe Burnett | Staff Engineer |
+| Tomasz Maczukin | Senior Engineer |
+
+<!-- vale gitlab.Spelling = YES -->
diff --git a/doc/development/internal_analytics/index.md b/doc/development/internal_analytics/index.md
index 4a719cf43b9..b5403f56600 100644
--- a/doc/development/internal_analytics/index.md
+++ b/doc/development/internal_analytics/index.md
@@ -97,6 +97,7 @@ FROM common_mart.mart_behavior_structured_event
WHERE event_action = 'feature_used'
AND event_category = 'InternalEventTracking'
AND behavior_date > '2023-08-01' --restricted minimum date for performance
+AND app_id='gitlab' -- use gitlab for production events and gitlab-staging for events from staging
GROUP BY 1 ORDER BY 1 desc
```
diff --git a/lib/backup/repositories.rb b/lib/backup/repositories.rb
index 46825dbd203..c3154ccfbb5 100644
--- a/lib/backup/repositories.rb
+++ b/lib/backup/repositories.rb
@@ -38,7 +38,6 @@ module Backup
ensure
strategy.finish!
- cleanup_snippets_without_repositories
restore_object_pools
end
@@ -133,24 +132,6 @@ module Backup
pool.schedule
end
end
-
- # Snippets without a repository should be removed because they failed to import
- # due to having invalid repositories
- def cleanup_snippets_without_repositories
- invalid_snippets = []
-
- snippet_relation.find_each(batch_size: 1000).each do |snippet|
- response = Snippets::RepositoryValidationService.new(nil, snippet).execute
- next if response.success?
-
- snippet.repository.remove
- progress.puts("Snippet #{snippet.full_path} can't be restored: #{response.message}")
-
- invalid_snippets << snippet.id
- end
-
- Snippet.id_in(invalid_snippets).delete_all
- end
end
end
diff --git a/lib/bulk_imports/common/pipelines/entity_finisher.rb b/lib/bulk_imports/common/pipelines/entity_finisher.rb
index 723359aa438..a8bbd122445 100644
--- a/lib/bulk_imports/common/pipelines/entity_finisher.rb
+++ b/lib/bulk_imports/common/pipelines/entity_finisher.rb
@@ -8,6 +8,10 @@ module BulkImports
false
end
+ def self.abort_on_failure?
+ false
+ end
+
def initialize(context)
@context = context
@entity = @context.entity
diff --git a/lib/gitlab/patch/sidekiq_scheduled_enq.rb b/lib/gitlab/patch/sidekiq_scheduled_enq.rb
deleted file mode 100644
index b5a40c19923..00000000000
--- a/lib/gitlab/patch/sidekiq_scheduled_enq.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-# Patch to address https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/2286
-# Using a dual-namespace poller eliminates the need for script based migration of
-# schedule-related sets in Sidekiq.
-module Gitlab
- module Patch
- module SidekiqScheduledEnq
- # The patched enqueue_jobs will poll non-namespaced scheduled sets before doing the same for
- # namespaced sets via super and vice-versa depending on how Sidekiq.redis was configured
- def enqueue_jobs(sorted_sets = Sidekiq::Scheduled::SETS)
- # checks the other namespace
- if Gitlab::Utils.to_boolean(ENV['SIDEKIQ_ENABLE_DUAL_NAMESPACE_POLLING'], default: true)
- # Refer to https://github.com/sidekiq/sidekiq/blob/v6.5.7/lib/sidekiq/scheduled.rb#L25
- # this portion swaps out Sidekiq.redis for Gitlab::Redis::Queues
- Gitlab::Redis::Queues.with do |conn| # rubocop:disable Cop/RedisQueueUsage
- sorted_sets.each do |sorted_set|
- # adds namespace since `super` polls with a non-namespaced Sidekiq.redis
- sorted_set = "#{Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE}:#{sorted_set}" # rubocop:disable Cop/RedisQueueUsage
-
- while !@done && (job = zpopbyscore(conn, keys: [sorted_set], argv: [Time.now.to_f.to_s])) # rubocop:disable Gitlab/ModuleWithInstanceVariables, Lint/AssignmentInCondition
- Sidekiq::Client.push(Sidekiq.load_json(job)) # rubocop:disable Cop/SidekiqApiUsage
- Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
- end
- end
- end
- end
-
- super
- end
- end
- end
-end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 2b8b7aedeb2..41fb946c62d 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -406,7 +406,6 @@ module Gitlab
service_desk_enabled_projects: distinct_count_service_desk_enabled_projects(time_period),
service_desk_issues: count(::Issue.service_desk.where(time_period)),
projects_jira_active: distinct_count(::Project.with_active_integration(::Integrations::Jira).where(time_period), :creator_id),
- projects_jira_dvcs_cloud_active: distinct_count(::Project.with_active_integration(::Integrations::Jira).with_jira_dvcs_cloud.where(time_period), :creator_id),
projects_jira_dvcs_server_active: distinct_count(::Project.with_active_integration(::Integrations::Jira).with_jira_dvcs_server.where(time_period), :creator_id)
}
end
diff --git a/package.json b/package.json
index ec0af3047d7..bd1541ee58c 100644
--- a/package.json
+++ b/package.json
@@ -59,7 +59,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.3.0",
"@gitlab/svgs": "3.71.0",
- "@gitlab/ui": "^68.5.0",
+ "@gitlab/ui": "^69.0.0",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "^0.0.1-dev-20231116214726",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
diff --git a/qa/qa/page/component/members/invite_members_modal.rb b/qa/qa/page/component/members/invite_members_modal.rb
index e3d681cf403..36bff71aa38 100644
--- a/qa/qa/page/component/members/invite_members_modal.rb
+++ b/qa/qa/page/component/members/invite_members_modal.rb
@@ -12,37 +12,37 @@ module QA
super
base.view 'app/assets/javascripts/invite_members/components/invite_modal_base.vue' do
- element :invite_button
- element :access_level_dropdown
+ element 'invite-modal-submit'
+ element 'access-level-dropdown'
element 'invite-modal'
end
base.view 'app/assets/javascripts/invite_members/components/members_token_select.vue' do
- element :members_token_select_input
+ element 'members-token-select-input'
end
base.view 'app/assets/javascripts/invite_members/components/invite_group_trigger.vue' do
- element :invite_a_group_button
+ element 'invite-group-button'
end
base.view 'app/assets/javascripts/invite_members/constants.js' do
- element :invite_members_button
+ element 'invite-members-button'
end
end
def open_invite_members_modal
- click_element :invite_members_button
+ click_element 'invite-members-button'
end
def open_invite_group_modal
- click_element :invite_a_group_button
+ click_element 'invite-group-button'
end
def add_member(username, access_level = 'Developer', refresh_page: true)
open_invite_members_modal
within_element('invite-modal') do
- fill_element(:members_token_select_input, username)
+ fill_element('members-token-select-input', username)
Support::WaitForRequests.wait_for_requests
click_button(username, match: :prefer_exact)
set_access_level(access_level)
@@ -68,7 +68,7 @@ module QA
end
def send_invite(refresh = false)
- click_element :invite_button
+ click_element 'invite-modal-submit'
Support::WaitForRequests.wait_for_requests
page.refresh if refresh
end
@@ -76,7 +76,7 @@ module QA
private
def set_access_level(access_level)
- within_element(:access_level_dropdown) do
+ within_element('access-level-dropdown') do
expand_select_list
select_item access_level
end
diff --git a/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb
index 72569d1ac73..e140b37e137 100644
--- a/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb
@@ -367,7 +367,14 @@ module QA
logger.info("== Verifying repository import ==")
expect(imported_project.description).to eq(gh_repo.description)
expect(gl_branches).to include(*gh_branches)
- expect(gl_commits).to include(*gh_commits)
+
+ # When testing with very large repositories, comparing with include will raise 'stack level too deep' error
+ # Compare just the size in this case
+ if gh_commits.size > 10000
+ expect(gl_commits.size).to be >= gh_commits.size
+ else
+ expect(gl_commits).to include(*gh_commits)
+ end
end
# Verify imported labels
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index b7ee01ce6b3..047dd12912b 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -470,7 +470,7 @@ RSpec.describe ApplicationController, feature_category: :shared do
enforce_terms
- expect { get :index }.not_to exceed_query_limit(control)
+ expect { get :index }.not_to exceed_query_limit(control.count).with_threshold(1)
end
context 'when terms are enforced' do
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 1e879458f48..55741a82862 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -2282,121 +2282,58 @@ RSpec.describe Projects::MergeRequestsController, feature_category: :code_review
end
context 'highlight preloading' do
- context 'when only_highlight_discussions_requested is false' do
- before do
- stub_feature_flags(only_highlight_discussions_requested: false)
+ context 'with commit diff notes' do
+ let!(:first_commit_diff_note) do
+ create(:diff_note_on_commit, project: merge_request.project)
end
- context 'with commit diff notes' do
- let!(:first_commit_diff_note) do
- create(:diff_note_on_commit, project: merge_request.project)
- end
-
- let!(:second_commit_diff_note) do
- create(:diff_note_on_commit, project: merge_request.project)
- end
-
- it 'preloads all of the notes diffs highlights' do
- expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
- first_note_diff_file = first_commit_diff_note.note_diff_file
- second_note_diff_file = second_commit_diff_note.note_diff_file
+ let!(:second_commit_diff_note) do
+ create(:diff_note_on_commit, project: merge_request.project)
+ end
- expect(collection).to receive(:load_highlight).and_call_original
- expect(collection).to receive(:find_by_id).with(first_note_diff_file.id).and_call_original
- expect(collection).to receive(:find_by_id).with(second_note_diff_file.id).and_call_original
- end
+ it 'preloads all of the notes diffs highlights' do
+ expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
+ first_note_diff_file = first_commit_diff_note.note_diff_file
+ second_note_diff_file = second_commit_diff_note.note_diff_file
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid,
- per_page: 2 }
+ expect(collection).to receive(:load_highlight).with(diff_note_ids: [first_commit_diff_note.id, second_commit_diff_note.id]).and_call_original
+ expect(collection).to receive(:find_by_id).with(first_note_diff_file.id).and_call_original
+ expect(collection).to receive(:find_by_id).with(second_note_diff_file.id).and_call_original
end
- it 'preloads all of the notes diffs highlights when per_page is 1' do
- expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
- first_note_diff_file = first_commit_diff_note.note_diff_file
- second_note_diff_file = second_commit_diff_note.note_diff_file
-
- expect(collection).to receive(:load_highlight).and_call_original
- expect(collection).to receive(:find_by_id).with(first_note_diff_file.id).and_call_original
- expect(collection).not_to receive(:find_by_id).with(second_note_diff_file.id)
- end
-
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid,
- per_page: 1 }
- end
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid,
+ per_page: 2 }
end
- context 'with diff notes' do
- let!(:diff_note) do
- create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.project)
- end
-
- it 'preloads notes diffs highlights' do
- expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
- note_diff_file = diff_note.note_diff_file
+ it 'preloads all of the notes diffs highlights when per_page is 1' do
+ expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
+ first_note_diff_file = first_commit_diff_note.note_diff_file
+ second_note_diff_file = second_commit_diff_note.note_diff_file
- expect(collection).to receive(:load_highlight).and_call_original
- expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original
- end
-
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
+ expect(collection).to receive(:load_highlight).with(diff_note_ids: [first_commit_diff_note.id]).and_call_original
+ expect(collection).to receive(:find_by_id).with(first_note_diff_file.id).and_call_original
+ expect(collection).not_to receive(:find_by_id).with(second_note_diff_file.id)
end
+
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid,
+ per_page: 1 }
end
end
- context 'when only_highlight_discussions_requested is true' do
- context 'with commit diff notes' do
- let!(:first_commit_diff_note) do
- create(:diff_note_on_commit, project: merge_request.project)
- end
-
- let!(:second_commit_diff_note) do
- create(:diff_note_on_commit, project: merge_request.project)
- end
-
- it 'preloads all of the notes diffs highlights' do
- expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
- first_note_diff_file = first_commit_diff_note.note_diff_file
- second_note_diff_file = second_commit_diff_note.note_diff_file
-
- expect(collection).to receive(:load_highlight).with(diff_note_ids: [first_commit_diff_note.id, second_commit_diff_note.id]).and_call_original
- expect(collection).to receive(:find_by_id).with(first_note_diff_file.id).and_call_original
- expect(collection).to receive(:find_by_id).with(second_note_diff_file.id).and_call_original
- end
-
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid,
- per_page: 2 }
- end
-
- it 'preloads all of the notes diffs highlights when per_page is 1' do
- expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
- first_note_diff_file = first_commit_diff_note.note_diff_file
- second_note_diff_file = second_commit_diff_note.note_diff_file
-
- expect(collection).to receive(:load_highlight).with(diff_note_ids: [first_commit_diff_note.id]).and_call_original
- expect(collection).to receive(:find_by_id).with(first_note_diff_file.id).and_call_original
- expect(collection).not_to receive(:find_by_id).with(second_note_diff_file.id)
- end
-
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid,
- per_page: 1 }
- end
+ context 'with diff notes' do
+ let!(:diff_note) do
+ create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.project)
end
- context 'with diff notes' do
- let!(:diff_note) do
- create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.project)
- end
+ it 'preloads notes diffs highlights' do
+ expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
+ note_diff_file = diff_note.note_diff_file
- it 'preloads notes diffs highlights' do
- expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
- note_diff_file = diff_note.note_diff_file
-
- expect(collection).to receive(:load_highlight).with(diff_note_ids: [diff_note.id]).and_call_original
- expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original
- end
-
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
+ expect(collection).to receive(:load_highlight).with(diff_note_ids: [diff_note.id]).and_call_original
+ expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original
end
+
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
end
end
end
diff --git a/spec/factories/project_feature_usage.rb b/spec/factories/project_feature_usage.rb
index 8265ea04392..eb9605d08d6 100644
--- a/spec/factories/project_feature_usage.rb
+++ b/spec/factories/project_feature_usage.rb
@@ -4,10 +4,6 @@ FactoryBot.define do
factory :project_feature_usage do
project
- trait :dvcs_cloud do
- jira_dvcs_cloud_last_sync_at { Time.current }
- end
-
trait :dvcs_server do
jira_dvcs_server_last_sync_at { Time.current }
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 1e3ade779af..a9c3285356a 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -177,12 +177,6 @@ FactoryBot.define do
import_status { :canceled }
end
- trait :jira_dvcs_cloud do
- before(:create) do |project|
- create(:project_feature_usage, :dvcs_cloud, project: project)
- end
- end
-
trait :jira_dvcs_server do
before(:create) do |project|
create(:project_feature_usage, :dvcs_server, project: project)
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 4e0198b1f2b..a443ba62abb 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -261,7 +261,7 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do
expect(page).to have_content "Application settings saved successfully"
end
- it 'terms of Service' do
+ it 'terms of Service', :js do
# Already have the admin accept terms, so they don't need to accept in this spec.
_existing_terms = create(:term)
accept_terms(admin)
@@ -274,7 +274,10 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do
expect(current_settings.enforce_terms).to be(true)
expect(current_settings.terms).to eq 'Be nice!'
- expect(page).to have_content 'Application settings saved successfully'
+
+ click_button 'Accept terms'
+
+ expect(page).to have_current_path(general_admin_application_settings_path, ignore_query: true)
end
it 'modify oauth providers' do
diff --git a/spec/features/projects/members/manage_groups_spec.rb b/spec/features/projects/members/manage_groups_spec.rb
index 63ff1ba8455..7bd7c822a5d 100644
--- a/spec/features/projects/members/manage_groups_spec.rb
+++ b/spec/features/projects/members/manage_groups_spec.rb
@@ -228,6 +228,6 @@ RSpec.describe 'Project > Members > Manage groups', :js, feature_category: :grou
end
def invite_group_selector
- 'button[data-test-id="invite-group-button"]'
+ 'button[data-testid="invite-group-button"]'
end
end
diff --git a/spec/frontend/commons/nav/user_merge_requests_spec.js b/spec/frontend/commons/nav/user_merge_requests_spec.js
deleted file mode 100644
index 114cbbf812c..00000000000
--- a/spec/frontend/commons/nav/user_merge_requests_spec.js
+++ /dev/null
@@ -1,154 +0,0 @@
-import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import * as UserApi from '~/api/user_api';
-import {
- openUserCountsBroadcast,
- closeUserCountsBroadcast,
- refreshUserMergeRequestCounts,
-} from '~/commons/nav/user_merge_requests';
-
-jest.mock('~/api');
-
-const TEST_COUNT = 1000;
-const MR_COUNT_CLASS = 'js-merge-requests-count';
-
-describe('User Merge Requests', () => {
- let channelMock;
- let newBroadcastChannelMock;
-
- beforeEach(() => {
- jest.spyOn(document, 'dispatchEvent').mockReturnValue(false);
-
- global.gon.current_user_id = 123;
- global.gon.use_new_navigation = false;
-
- channelMock = {
- postMessage: jest.fn(),
- close: jest.fn(),
- };
- newBroadcastChannelMock = jest.fn().mockImplementation(() => channelMock);
-
- global.BroadcastChannel = newBroadcastChannelMock;
- setHTMLFixture(
- `<div><div class="${MR_COUNT_CLASS}">0</div><div class="js-assigned-mr-count"></div><div class="js-reviewer-mr-count"></div></div>`,
- );
- });
-
- afterEach(() => {
- resetHTMLFixture();
- });
-
- const findMRCountText = () => document.body.querySelector(`.${MR_COUNT_CLASS}`).textContent;
-
- describe('refreshUserMergeRequestCounts', () => {
- beforeEach(() => {
- jest.spyOn(UserApi, 'getUserCounts').mockResolvedValue({
- data: {
- assigned_merge_requests: TEST_COUNT,
- review_requested_merge_requests: TEST_COUNT,
- },
- });
- });
-
- describe('with open broadcast channel', () => {
- beforeEach(() => {
- openUserCountsBroadcast();
-
- return refreshUserMergeRequestCounts();
- });
-
- it('updates the top count of merge requests', () => {
- expect(findMRCountText()).toEqual(Number(TEST_COUNT + TEST_COUNT).toLocaleString());
- });
-
- it('calls the API', () => {
- expect(UserApi.getUserCounts).toHaveBeenCalled();
- });
-
- it('posts count to BroadcastChannel', () => {
- expect(channelMock.postMessage).toHaveBeenCalledWith(TEST_COUNT + TEST_COUNT);
- });
- });
-
- describe('without open broadcast channel', () => {
- beforeEach(() => refreshUserMergeRequestCounts());
-
- it('does not post anything', () => {
- expect(channelMock.postMessage).not.toHaveBeenCalled();
- });
- });
-
- it('does not emit event to refetch counts', () => {
- expect(document.dispatchEvent).not.toHaveBeenCalled();
- });
- });
-
- describe('openUserCountsBroadcast', () => {
- beforeEach(() => {
- openUserCountsBroadcast();
- });
-
- it('creates BroadcastChannel that updates DOM on message received', () => {
- expect(findMRCountText()).toEqual('0');
-
- channelMock.onmessage({ data: TEST_COUNT });
-
- expect(newBroadcastChannelMock).toHaveBeenCalled();
- expect(findMRCountText()).toEqual(TEST_COUNT.toLocaleString());
- });
-
- it('closes if called while already open', () => {
- expect(channelMock.close).not.toHaveBeenCalled();
-
- openUserCountsBroadcast();
-
- expect(newBroadcastChannelMock).toHaveBeenCalled();
- expect(channelMock.close).toHaveBeenCalled();
- });
- });
-
- describe('closeUserCountsBroadcast', () => {
- describe('when not opened', () => {
- it('does nothing', () => {
- expect(channelMock.close).not.toHaveBeenCalled();
- });
- });
-
- describe('when opened', () => {
- beforeEach(() => {
- openUserCountsBroadcast();
- });
-
- it('closes', () => {
- expect(channelMock.close).not.toHaveBeenCalled();
-
- closeUserCountsBroadcast();
-
- expect(channelMock.close).toHaveBeenCalled();
- });
- });
- });
-
- describe('if new navigation is enabled', () => {
- beforeEach(() => {
- global.gon.use_new_navigation = true;
- jest.spyOn(UserApi, 'getUserCounts');
- });
-
- it('openUserCountsBroadcast is a noop', () => {
- openUserCountsBroadcast();
- expect(newBroadcastChannelMock).not.toHaveBeenCalled();
- });
-
- describe('refreshUserMergeRequestCounts', () => {
- it('does not call api', async () => {
- await refreshUserMergeRequestCounts();
- expect(UserApi.getUserCounts).not.toHaveBeenCalled();
- });
-
- it('emits event to refetch counts', async () => {
- await refreshUserMergeRequestCounts();
- expect(document.dispatchEvent).toHaveBeenCalledWith(new CustomEvent('todo:toggle'));
- });
- });
- });
-});
diff --git a/spec/frontend/invite_members/components/invite_members_trigger_spec.js b/spec/frontend/invite_members/components/invite_members_trigger_spec.js
index 58c40a49b3c..c8a06a66a3f 100644
--- a/spec/frontend/invite_members/components/invite_members_trigger_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_trigger_spec.js
@@ -4,7 +4,7 @@ import InviteMembersTrigger from '~/invite_members/components/invite_members_tri
import eventHub from '~/invite_members/event_hub';
import {
TRIGGER_ELEMENT_BUTTON,
- TRIGGER_DEFAULT_QA_SELECTOR,
+ TRIGGER_DEFAULT_TESTID,
TRIGGER_ELEMENT_WITH_EMOJI,
TRIGGER_ELEMENT_DROPDOWN_WITH_EMOJI,
TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN,
@@ -67,16 +67,16 @@ describe.each(triggerItems)('with triggerElement as %s', (triggerItem) => {
expect(findButton().text()).toBe(displayText);
});
- it('uses the default qa selector value', () => {
+ it('uses the default testid value', () => {
createComponent();
- expect(findButton().attributes('data-qa-selector')).toBe(TRIGGER_DEFAULT_QA_SELECTOR);
+ expect(findButton().attributes('data-testid')).toBe(TRIGGER_DEFAULT_TESTID);
});
- it('sets the qa selector value', () => {
- createComponent({ qaSelector: '_qaSelector_' });
+ it('sets the testid value', () => {
+ createComponent({ testId: '_testId_' });
- expect(findButton().attributes('data-qa-selector')).toBe('_qaSelector_');
+ expect(findButton().attributes('data-testid')).toBe('_testId_');
});
});
diff --git a/spec/frontend/invite_members/components/invite_modal_base_spec.js b/spec/frontend/invite_members/components/invite_modal_base_spec.js
index 26e7d98b8f4..a858f964c18 100644
--- a/spec/frontend/invite_members/components/invite_modal_base_spec.js
+++ b/spec/frontend/invite_members/components/invite_modal_base_spec.js
@@ -95,7 +95,7 @@ describe('InviteModalBase', () => {
const actionButton = findActionButton();
expect(actionButton.text()).toBe(INVITE_BUTTON_TEXT);
- expect(actionButton.attributes('data-qa-selector')).toBe('invite_button');
+ expect(actionButton.attributes('data-testid')).toBe('invite-modal-submit');
expect(actionButton.props()).toMatchObject({
variant: 'confirm',
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js
index 1309fd79c14..8f761476c7c 100644
--- a/spec/frontend/notes/components/comment_form_spec.js
+++ b/spec/frontend/notes/components/comment_form_spec.js
@@ -9,7 +9,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import batchComments from '~/batch_comments/stores/modules/batch_comments';
-import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
+import { fetchUserCounts } from '~/super_sidebar/user_counts_fetch';
import { createAlert } from '~/alert';
import { STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants';
import axios from '~/lib/utils/axios_utils';
@@ -26,7 +26,7 @@ import { mockTracking } from 'helpers/tracking_helper';
import { loggedOutnoteableData, notesDataMock, userDataMock, noteableDataMock } from '../mock_data';
jest.mock('autosize');
-jest.mock('~/commons/nav/user_merge_requests');
+jest.mock('~/super_sidebar/user_counts_fetch');
jest.mock('~/alert');
Vue.use(Vuex);
@@ -586,7 +586,7 @@ describe('issue_comment_form component', () => {
await nextTick();
- expect(refreshUserMergeRequestCounts).toHaveBeenCalled();
+ expect(fetchUserCounts).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js b/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js
index a221d28704b..ae31e60254f 100644
--- a/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js
+++ b/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';
import VueApollo from 'vue-apollo';
@@ -8,8 +8,11 @@ import SidebarReviewers from '~/sidebar/components/reviewers/sidebar_reviewers.v
import SidebarService from '~/sidebar/services/sidebar_service';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarStore from '~/sidebar/stores/sidebar_store';
+import { fetchUserCounts } from '~/super_sidebar/user_counts_fetch';
import Mock from '../../mock_data';
+jest.mock('~/super_sidebar/user_counts_fetch');
+
Vue.use(VueApollo);
describe('sidebar reviewers', () => {
@@ -39,7 +42,7 @@ describe('sidebar reviewers', () => {
axiosMock = new AxiosMockAdapter(axios);
mediator = new SidebarMediator(Mock.mediator);
- jest.spyOn(mediator, 'saveReviewers');
+ jest.spyOn(mediator, 'saveReviewers').mockResolvedValue({});
jest.spyOn(mediator, 'addSelfReview');
});
@@ -60,6 +63,17 @@ describe('sidebar reviewers', () => {
expect(mediator.saveReviewers).toHaveBeenCalled();
});
+ it('re-fetches user counts after saving reviewers', async () => {
+ createComponent();
+
+ expect(fetchUserCounts).not.toHaveBeenCalled();
+
+ wrapper.vm.saveReviewers();
+ await nextTick();
+
+ expect(fetchUserCounts).toHaveBeenCalled();
+ });
+
it('calls the mediator when "reviewBySelf" method is called', () => {
createComponent();
diff --git a/spec/frontend/sidebar/sidebar_mediator_spec.js b/spec/frontend/sidebar/sidebar_mediator_spec.js
index 9c12088216b..4dc285fc3c8 100644
--- a/spec/frontend/sidebar/sidebar_mediator_spec.js
+++ b/spec/frontend/sidebar/sidebar_mediator_spec.js
@@ -9,7 +9,6 @@ import Mock from './mock_data';
jest.mock('~/alert');
jest.mock('~/vue_shared/plugins/global_toast');
-jest.mock('~/commons/nav/user_merge_requests');
describe('Sidebar mediator', () => {
const { mediator: mediatorMockData } = Mock;
diff --git a/spec/frontend/super_sidebar/user_counts_manager_spec.js b/spec/frontend/super_sidebar/user_counts_manager_spec.js
index b5074620195..3b2ee5b0991 100644
--- a/spec/frontend/super_sidebar/user_counts_manager_spec.js
+++ b/spec/frontend/super_sidebar/user_counts_manager_spec.js
@@ -6,6 +6,7 @@ import {
userCounts,
destroyUserCountsManager,
} from '~/super_sidebar/user_counts_manager';
+import { fetchUserCounts } from '~/super_sidebar/user_counts_fetch';
jest.mock('~/api');
@@ -118,15 +119,30 @@ describe('User Merge Requests', () => {
createUserCountsManager();
});
- it('fetches counts from API, stores and rebroadcasts them', async () => {
- expect(userCounts).toMatchObject(userCountDefaults);
+ describe('manually created event', () => {
+ it('fetches counts from API, stores and rebroadcasts them', async () => {
+ expect(userCounts).toMatchObject(userCountDefaults);
+
+ document.dispatchEvent(new CustomEvent('userCounts:fetch'));
+ await waitForPromises();
+
+ expect(UserApi.getUserCounts).toHaveBeenCalled();
+ expect(userCounts).toMatchObject(userCountUpdate);
+ expect(channelMock.postMessage).toHaveBeenLastCalledWith(userCounts);
+ });
+ });
+
+ describe('fetchUserCounts helper', () => {
+ it('fetches counts from API, stores and rebroadcasts them', async () => {
+ expect(userCounts).toMatchObject(userCountDefaults);
- document.dispatchEvent(new CustomEvent('userCounts:fetch'));
- await waitForPromises();
+ fetchUserCounts();
+ await waitForPromises();
- expect(UserApi.getUserCounts).toHaveBeenCalled();
- expect(userCounts).toMatchObject(userCountUpdate);
- expect(channelMock.postMessage).toHaveBeenLastCalledWith(userCounts);
+ expect(UserApi.getUserCounts).toHaveBeenCalled();
+ expect(userCounts).toMatchObject(userCountUpdate);
+ expect(channelMock.postMessage).toHaveBeenLastCalledWith(userCounts);
+ });
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js
index 85acd5f9a9e..328c0134368 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js
@@ -1,8 +1,12 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import simplePoll from '~/lib/utils/simple_poll';
import MrWidgetMerging from '~/vue_merge_request_widget/components/states/mr_widget_merging.vue';
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
+import { STATUS_MERGED } from '~/issues/constants';
+import { fetchUserCounts } from '~/super_sidebar/user_counts_fetch';
+jest.mock('~/super_sidebar/user_counts_fetch');
jest.mock('~/lib/utils/simple_poll', () =>
jest.fn().mockImplementation(jest.requireActual('~/lib/utils/simple_poll').default),
);
@@ -13,7 +17,7 @@ describe('MRWidgetMerging', () => {
const pollMock = jest.fn().mockResolvedValue();
const GlEmoji = { template: '<img />' };
- beforeEach(() => {
+ const createComponent = () => {
wrapper = shallowMount(MrWidgetMerging, {
propsData: {
mr: {
@@ -29,14 +33,18 @@ describe('MRWidgetMerging', () => {
GlEmoji,
},
});
- });
+ };
it('renders information about merge request being merged', () => {
+ createComponent();
+
const message = wrapper.findComponent(BoldText).props('message');
expect(message).toContain('Merging!');
});
describe('initiateMergePolling', () => {
+ beforeEach(createComponent);
+
it('should call simplePoll', () => {
expect(simplePoll).toHaveBeenCalledWith(expect.any(Function), { timeout: 0 });
});
@@ -45,4 +53,15 @@ describe('MRWidgetMerging', () => {
expect(pollMock).toHaveBeenCalled();
});
});
+
+ describe('on successful merge', () => {
+ it('should re-fetch user counts', async () => {
+ pollMock.mockResolvedValueOnce({ data: { state: STATUS_MERGED } });
+ createComponent();
+
+ await nextTick();
+
+ expect(fetchUserCounts).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
index 9239807ae71..dbfd3cec76c 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -19,9 +19,6 @@ import eventHub from '~/vue_merge_request_widget/event_hub';
jest.mock('~/lib/utils/simple_poll', () =>
jest.fn().mockImplementation(jest.requireActual('~/lib/utils/simple_poll').default),
);
-jest.mock('~/commons/nav/user_merge_requests', () => ({
- refreshUserMergeRequestCounts: jest.fn(),
-}));
const commitMessage = readyToMergeResponse.data.project.mergeRequest.defaultMergeCommitMessage;
const squashCommitMessage =
diff --git a/spec/graphql/types/work_items/widget_definition_interface_spec.rb b/spec/graphql/types/work_items/widget_definition_interface_spec.rb
new file mode 100644
index 00000000000..6f1fd0cc572
--- /dev/null
+++ b/spec/graphql/types/work_items/widget_definition_interface_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::WorkItems::WidgetDefinitionInterface, feature_category: :team_planning do
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ type
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+
+ describe '.resolve_type' do
+ subject { described_class.resolve_type(object, {}) }
+
+ context 'for assignees widget' do
+ let(:object) { WorkItems::Widgets::Assignees }
+
+ it { is_expected.to eq(Types::WorkItems::WidgetDefinitions::AssigneesType) }
+ end
+
+ context 'for other widgets' do
+ let(:object) { WorkItems::Widgets::Description }
+
+ it { is_expected.to eq(Types::WorkItems::WidgetDefinitions::GenericType) }
+ end
+ end
+end
diff --git a/spec/graphql/types/work_items/widget_definitions/assignees_type_spec.rb b/spec/graphql/types/work_items/widget_definitions/assignees_type_spec.rb
new file mode 100644
index 00000000000..d6179e92c02
--- /dev/null
+++ b/spec/graphql/types/work_items/widget_definitions/assignees_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::WorkItems::WidgetDefinitions::AssigneesType, feature_category: :team_planning do
+ it 'exposes the expected fields' do
+ expected_fields = %i[type can_invite_members]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/work_items/widget_definitions/generic_type_spec.rb b/spec/graphql/types/work_items/widget_definitions/generic_type_spec.rb
new file mode 100644
index 00000000000..19e962e71fd
--- /dev/null
+++ b/spec/graphql/types/work_items/widget_definitions/generic_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::WorkItems::WidgetDefinitions::GenericType, feature_category: :team_planning do
+ it 'exposes the expected fields' do
+ expected_fields = %i[type]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/lib/backup/repositories_spec.rb b/spec/lib/backup/repositories_spec.rb
index ad5fb8ea84e..139c178afde 100644
--- a/spec/lib/backup/repositories_spec.rb
+++ b/spec/lib/backup/repositories_spec.rb
@@ -249,36 +249,6 @@ RSpec.describe Backup::Repositories, feature_category: :backup_restore do
end
end
- context 'cleanup snippets' do
- before do
- error_response = ServiceResponse.error(message: "Repository has more than one branch")
- allow(Snippets::RepositoryValidationService).to receive_message_chain(:new, :execute).and_return(error_response)
- end
-
- it 'shows the appropriate error' do
- subject.restore(destination, backup_id)
-
- expect(progress).to have_received(:puts).with("Snippet #{personal_snippet.full_path} can't be restored: Repository has more than one branch")
- expect(progress).to have_received(:puts).with("Snippet #{project_snippet.full_path} can't be restored: Repository has more than one branch")
- end
-
- it 'removes the snippets from the DB' do
- expect { subject.restore(destination, backup_id) }.to change(PersonalSnippet, :count).by(-1)
- .and change(ProjectSnippet, :count).by(-1)
- .and change(SnippetRepository, :count).by(-2)
- end
-
- it 'removes the repository from disk' do
- gitlab_shell = Gitlab::Shell.new
- shard_name = personal_snippet.repository.shard
- path = personal_snippet.disk_path + '.git'
-
- subject.restore(destination, backup_id)
-
- expect(gitlab_shell.repository_exists?(shard_name, path)).to eq false
- end
- end
-
context 'storages' do
let(:storages) { %w[default] }
diff --git a/spec/lib/gitlab/patch/sidekiq_scheduled_enq_spec.rb b/spec/lib/gitlab/patch/sidekiq_scheduled_enq_spec.rb
deleted file mode 100644
index cd3718f5dcc..00000000000
--- a/spec/lib/gitlab/patch/sidekiq_scheduled_enq_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Patch::SidekiqScheduledEnq, :clean_gitlab_redis_queues, feature_category: :scalability do
- describe '#enqueue_jobs' do
- let_it_be(:payload) { {} }
-
- before do
- allow(Sidekiq).to receive(:load_json).and_return(payload)
-
- # stub data in both namespaces
- Gitlab::Redis::Queues.with { |c| c.zadd('resque:gitlab:schedule', 100, 'dummy') }
- Gitlab::Redis::Queues.with { |c| c.zadd('schedule', 100, 'dummy') }
- end
-
- subject { Sidekiq::Scheduled::Enq.new.enqueue_jobs }
-
- it 'polls both namespaces by default' do
- expect(Sidekiq::Client).to receive(:push).with(payload).twice
-
- subject
-
- Sidekiq.redis do |conn|
- expect(conn.zcard('schedule')).to eq(0)
- end
-
- Gitlab::Redis::Queues.with do |conn|
- expect(conn.zcard('resque:gitlab:schedule')).to eq(0)
- end
- end
-
- context 'when SIDEKIQ_ENABLE_DUAL_NAMESPACE_POLLING is disabled' do
- before do
- stub_env('SIDEKIQ_ENABLE_DUAL_NAMESPACE_POLLING', 'false')
- end
-
- it 'polls via Sidekiq.redis only' do
- expect(Sidekiq::Client).to receive(:push).with(payload).once
-
- subject
-
- Sidekiq.redis do |conn|
- expect(conn.zcard('schedule')).to eq(0)
- end
-
- Gitlab::Redis::Queues.with do |conn|
- expect(conn.zcard('resque:gitlab:schedule')).to eq(1)
- end
- end
- end
-
- context 'when SIDEKIQ_ENABLE_DUAL_NAMESPACE_POLLING is enabled' do
- before do
- stub_env('SIDEKIQ_ENABLE_DUAL_NAMESPACE_POLLING', 'true')
- end
-
- it 'polls both sets' do
- expect(Sidekiq::Client).to receive(:push).with(payload).twice
-
- subject
-
- Sidekiq.redis do |conn|
- expect(conn.zcard('schedule')).to eq(0)
- end
-
- Gitlab::Redis::Queues.with do |conn|
- expect(conn.zcard('resque:gitlab:schedule')).to eq(0)
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 06492056da2..72c68c83014 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -343,7 +343,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
create(:issue, project: project, author: Users::Internal.support_bot)
create(:note, project: project, noteable: issue, author: user)
create(:todo, project: project, target: issue, author: user)
- create(:jira_integration, :jira_cloud_service, active: true, project: create(:project, :jira_dvcs_cloud, creator: user))
create(:jira_integration, active: true, project: create(:project, :jira_dvcs_server, creator: user))
end
@@ -354,7 +353,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
service_desk_enabled_projects: 2,
service_desk_issues: 2,
projects_jira_active: 2,
- projects_jira_dvcs_cloud_active: 2,
projects_jira_dvcs_server_active: 2
)
expect(described_class.usage_activity_by_stage_plan(described_class.monthly_time_range_db_params)).to include(
@@ -364,7 +362,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
service_desk_enabled_projects: 1,
service_desk_issues: 1,
projects_jira_active: 1,
- projects_jira_dvcs_cloud_active: 1,
projects_jira_dvcs_server_active: 1
)
end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index a808cb1c823..35ce678e3a6 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -483,4 +483,38 @@ RSpec.describe Ability do
end
end
end
+
+ describe '.allowed?' do
+ context 'when used with :read_namespace' do
+ subject(:allowed?) { described_class.allowed?(nil, :read_namespace) }
+
+ before do
+ allow(Gitlab::AppLogger).to receive(:info)
+ end
+
+ it 'logs the usage', :aggregate_failures do
+ allowed?
+
+ expect(Gitlab::AppLogger).to have_received(:info) do |args|
+ expect(args[:message]).to eq('Ability is in use')
+ expect(args[:ability]).to eq(:read_namespace)
+ expect(args[:caller_locations].first)
+ .to include('/spec/models/ability_spec.rb:489:in `block (4 levels) in <top (required)>')
+ expect(args[:caller_locations].length).to eq(5)
+ end
+ end
+
+ context 'when :log_read_namespace_usages feature flag is disabled' do
+ before do
+ stub_feature_flags(log_read_namespace_usages: false)
+ end
+
+ it 'does not log the usage' do
+ allowed?
+
+ expect(Gitlab::AppLogger).not_to have_received(:info)
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/bulk_imports/tracker_spec.rb b/spec/models/bulk_imports/tracker_spec.rb
index edd9adfa5f6..25cd5489a9f 100644
--- a/spec/models/bulk_imports/tracker_spec.rb
+++ b/spec/models/bulk_imports/tracker_spec.rb
@@ -83,5 +83,31 @@ RSpec.describe BulkImports::Tracker, type: :model, feature_category: :importers
"'InexistingPipeline' is not a valid BulkImport Pipeline"
)
end
+
+ context 'when using delegation methods' do
+ context 'with group pipelines' do
+ let(:entity) { create(:bulk_import_entity) }
+
+ it 'does not raise' do
+ entity.pipelines.each do |pipeline|
+ tracker = create(:bulk_import_tracker, entity: entity, pipeline_name: pipeline[:pipeline])
+ expect { tracker.abort_on_failure? }.not_to raise_error
+ expect { tracker.file_extraction_pipeline? }.not_to raise_error
+ end
+ end
+ end
+
+ context 'with project pipelines' do
+ let(:entity) { create(:bulk_import_entity, :project_entity) }
+
+ it 'does not raise' do
+ entity.pipelines.each do |pipeline|
+ tracker = create(:bulk_import_tracker, entity: entity, pipeline_name: pipeline[:pipeline])
+ expect { tracker.abort_on_failure? }.not_to raise_error
+ expect { tracker.file_extraction_pipeline? }.not_to raise_error
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index ee48e8cac6c..cb2c38c15e0 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -1270,10 +1270,15 @@ RSpec.describe Deployment, feature_category: :continuous_delivery do
shared_examples_for 'gracefully handling error' do
it 'tracks an exception' do
- expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
- instance_of(described_class::StatusSyncError),
- deployment_id: deployment.id,
- job_id: job.id)
+ expect(Gitlab::ErrorTracking).to(
+ receive(:track_exception).with(
+ instance_of(described_class::StatusSyncError),
+ deployment_id: deployment.id,
+ job_id: job.id
+ ) do |error|
+ expect(error.backtrace).to be_present
+ end
+ )
is_expected.to eq(false)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 3ea5f6ea0ae..52c341a754e 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2176,19 +2176,9 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
end
end
- describe '.with_jira_dvcs_cloud' do
- it 'returns the correct project' do
- jira_dvcs_cloud_project = create(:project, :jira_dvcs_cloud)
- create(:project, :jira_dvcs_server)
-
- expect(described_class.with_jira_dvcs_cloud).to contain_exactly(jira_dvcs_cloud_project)
- end
- end
-
describe '.with_jira_dvcs_server' do
it 'returns the correct project' do
jira_dvcs_server_project = create(:project, :jira_dvcs_server)
- create(:project, :jira_dvcs_cloud)
expect(described_class.with_jira_dvcs_server).to contain_exactly(jira_dvcs_server_project)
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 606c4ea05b9..974848b3967 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -4011,4 +4011,59 @@ RSpec.describe Repository, feature_category: :source_code_management do
it { expect { file_attributes }.to raise_error(ArgumentError) }
end
end
+
+ describe '#filter_generated_files' do
+ let(:project) do
+ create(:project, :custom_repo, files: {
+ '.gitattributes' => gitattr_content,
+ 'file1.txt' => 'first file',
+ 'file2.txt' => 'second file'
+ })
+ end
+
+ let(:rev) { 'master' }
+ let(:paths) { ['file1.txt', 'file2.txt'] }
+
+ subject(:generated_files) { repository.filter_generated_files(rev, paths) }
+
+ context 'when the linguist-generated attribute is used' do
+ let(:gitattr_content) { "*.txt text\nfile1.txt linguist-generated\n" }
+
+ it 'returns generated files only' do
+ expect(generated_files).to contain_exactly('file2.txt')
+ end
+ end
+
+ context 'when the gitlab-generated attribute is used' do
+ let(:gitattr_content) { "*.txt text\nfile1.txt gitlab-generated\n" }
+
+ it 'returns generated files only' do
+ expect(generated_files).to contain_exactly('file2.txt')
+ end
+ end
+
+ context 'when both linguist-generated and gitlab-generated attribute are used' do
+ let(:gitattr_content) { "*.txt text\nfile1.txt linguist-generated gitlab-generated\n" }
+
+ it 'returns generated files only' do
+ expect(generated_files).to contain_exactly('file2.txt')
+ end
+ end
+
+ context 'when no generated overrides are used' do
+ let(:gitattr_content) { "*.txt text\n" }
+
+ it 'returns the original paths' do
+ expect(generated_files).to eq paths
+ end
+ end
+
+ context 'when the given files are generated' do
+ let(:gitattr_content) { "*.txt gitlab-generated\n" }
+
+ it 'returns an empty array' do
+ expect(generated_files).to eq []
+ end
+ end
+ end
end
diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb
index 20d786f311f..302cd9e9f10 100644
--- a/spec/models/tree_spec.rb
+++ b/spec/models/tree_spec.rb
@@ -2,69 +2,58 @@
require 'spec_helper'
-RSpec.describe Tree do
- let_it_be(:repository) { create(:project, :repository).repository }
-
- let(:sha) { repository.root_ref }
-
+RSpec.describe Tree, feature_category: :source_code_management do
subject(:tree) { described_class.new(repository, '54fcc214') }
- describe '#readme' do
- before do
- stub_const('FakeBlob', Class.new)
- FakeBlob.class_eval do
- attr_reader :name
+ let_it_be(:repository) { create(:project, :repository).repository }
- def initialize(name)
- @name = name
- end
+ describe '#readme' do
+ subject { tree.readme }
- def readme?
- name =~ /^readme/i
- end
- end
+ before do
+ allow(tree).to receive(:blobs).and_return(files)
end
- it 'returns nil when repository does not contains a README file' do
- files = [FakeBlob.new('file'), FakeBlob.new('license'), FakeBlob.new('copying')]
- expect(subject).to receive(:blobs).and_return(files)
+ context 'when repository does not contains a README file' do
+ let(:files) { [fake_blob('file'), fake_blob('license'), fake_blob('copying')] }
- expect(subject.readme).to eq nil
+ it { is_expected.to be_nil }
end
- it 'returns nil when repository does not contains a previewable README file' do
- files = [FakeBlob.new('file'), FakeBlob.new('README.pages'), FakeBlob.new('README.png')]
- expect(subject).to receive(:blobs).and_return(files)
+ context 'when repository does not contains a previewable README file' do
+ let(:files) { [fake_blob('file'), fake_blob('README.pages'), fake_blob('README.png')] }
- expect(subject.readme).to eq nil
+ it { is_expected.to be_nil }
end
- it 'returns README when repository contains a previewable README file' do
- files = [FakeBlob.new('README.png'), FakeBlob.new('README'), FakeBlob.new('file')]
- expect(subject).to receive(:blobs).and_return(files)
+ context 'when repository contains a previewable README file' do
+ let(:files) { [fake_blob('README.png'), fake_blob('README'), fake_blob('file')] }
- expect(subject.readme.name).to eq 'README'
+ it { is_expected.to have_attributes(name: 'README') }
end
- it 'returns first previewable README when repository contains more than one' do
- files = [FakeBlob.new('file'), FakeBlob.new('README.md'), FakeBlob.new('README.asciidoc')]
- expect(subject).to receive(:blobs).and_return(files)
+ context 'when repository contains more than one README file' do
+ let(:files) { [fake_blob('file'), fake_blob('README.md'), fake_blob('README.asciidoc')] }
- expect(subject.readme.name).to eq 'README.md'
- end
+ it 'returns first previewable README' do
+ is_expected.to have_attributes(name: 'README.md')
+ end
- it 'returns first plain text README when repository contains more than one' do
- files = [FakeBlob.new('file'), FakeBlob.new('README'), FakeBlob.new('README.txt')]
- expect(subject).to receive(:blobs).and_return(files)
+ context 'when only plain-text READMEs' do
+ let(:files) { [fake_blob('file'), fake_blob('README'), fake_blob('README.txt')] }
- expect(subject.readme.name).to eq 'README'
+ it 'returns first plain text README' do
+ is_expected.to have_attributes(name: 'README')
+ end
+ end
end
- it 'prioritizes previewable README file over one in plain text' do
- files = [FakeBlob.new('file'), FakeBlob.new('README'), FakeBlob.new('README.md')]
- expect(subject).to receive(:blobs).and_return(files)
+ context 'when the repository has a previewable and plain text READMEs' do
+ let(:files) { [fake_blob('file'), fake_blob('README'), fake_blob('README.md')] }
- expect(subject.readme.name).to eq 'README.md'
+ it 'prefers previewable README file' do
+ is_expected.to have_attributes(name: 'README.md')
+ end
end
end
@@ -73,4 +62,10 @@ RSpec.describe Tree do
it { is_expected.to be_an_instance_of(Gitaly::PaginationCursor) }
end
+
+ private
+
+ def fake_blob(name)
+ instance_double(Gitlab::Git::Blob, name: name)
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index fe229ce836f..5a73be94f67 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -6381,6 +6381,49 @@ RSpec.describe User, feature_category: :user_profile do
end
end
end
+
+ context 'with multiple versions of terms' do
+ shared_examples 'terms acceptance' do
+ let(:another_term) { create :term }
+ let(:required_terms_are_accepted) { !required_terms_not_accepted }
+
+ context 'when the latest term is not accepted' do
+ before do
+ accept_terms(user)
+ another_term
+ end
+
+ it { expect(required_terms_are_accepted).to be result_for_latest_not_accepted }
+ end
+
+ context 'when the latest term is accepted' do
+ before do
+ another_term
+ accept_terms(user)
+ end
+
+ it { expect(required_terms_are_accepted).to be result_for_latest_accepted }
+ end
+ end
+
+ context 'when enforce_acceptance_of_changed_terms is enabled' do
+ let(:result_for_latest_not_accepted) { false }
+ let(:result_for_latest_accepted) { true }
+
+ include_examples 'terms acceptance'
+ end
+
+ context 'when enforce_acceptance_of_changed_terms is disabled' do
+ let(:result_for_latest_not_accepted) { true }
+ let(:result_for_latest_accepted) { true }
+
+ before do
+ stub_feature_flags(enforce_acceptance_of_changed_terms: false)
+ end
+
+ include_examples 'terms acceptance'
+ end
+ end
end
end
diff --git a/spec/models/work_items/widgets/assignees_spec.rb b/spec/models/work_items/widgets/assignees_spec.rb
index 19c17658ce4..b44246855ce 100644
--- a/spec/models/work_items/widgets/assignees_spec.rb
+++ b/spec/models/work_items/widgets/assignees_spec.rb
@@ -17,6 +17,32 @@ RSpec.describe WorkItems::Widgets::Assignees do
it { is_expected.to include(:assignee_ids) }
end
+ describe '.can_invite_members?' do
+ let(:user) { build_stubbed(:user) }
+
+ subject(:execute) { described_class.can_invite_members?(user, resource_parent) }
+
+ context 'when resource_parent is a project' do
+ let(:resource_parent) { build_stubbed(:project) }
+
+ it 'checks the ability with the correct permission' do
+ expect(user).to receive(:can?).with(:admin_project_member, resource_parent)
+
+ execute
+ end
+ end
+
+ context 'when resource_parent is a group' do
+ let(:resource_parent) { build_stubbed(:group) }
+
+ it 'checks the ability with the correct permission' do
+ expect(user).to receive(:can?).with(:admin_group_member, resource_parent)
+
+ execute
+ end
+ end
+ end
+
describe '#type' do
subject { described_class.new(work_item).type }
diff --git a/spec/policies/ci/runner_manager_policy_spec.rb b/spec/policies/ci/runner_manager_policy_spec.rb
index d7004033ceb..d4f52879714 100644
--- a/spec/policies/ci/runner_manager_policy_spec.rb
+++ b/spec/policies/ci/runner_manager_policy_spec.rb
@@ -129,7 +129,29 @@ RSpec.describe Ci::RunnerManagerPolicy, feature_category: :runner_fleet do
context 'with project runner' do
let(:runner) { project_runner }
- it { expect_disallowed :read_runner_manager }
+ it { expect_allowed :read_runner_manager }
+
+ context 'when user is not developer in parent group' do
+ let_it_be(:developers_group_developer) { create(:user) }
+ let_it_be_with_reload(:developers_group) { create(:group, name: 'developers', path: 'developers') }
+
+ let(:user) { developers_group_developer }
+
+ before_all do
+ create(:project_group_link, :developer, group: developers_group, project: project)
+ developers_group.add_reporter(developers_group_developer)
+ end
+
+ it { expect_disallowed :read_runner_manager }
+
+ context 'when user is developer in a group invited to project as developer' do
+ before_all do
+ developers_group.add_developer(developers_group_developer)
+ end
+
+ it { expect_allowed :read_runner_manager }
+ end
+ end
end
end
diff --git a/spec/policies/ci/runner_policy_spec.rb b/spec/policies/ci/runner_policy_spec.rb
index e0a9e3c2870..007669b8f52 100644
--- a/spec/policies/ci/runner_policy_spec.rb
+++ b/spec/policies/ci/runner_policy_spec.rb
@@ -127,7 +127,29 @@ RSpec.describe Ci::RunnerPolicy, feature_category: :runner do
context 'with project runner' do
let(:runner) { project_runner }
- it { expect_disallowed :read_runner }
+ it { expect_allowed :read_runner }
+
+ context 'when user is not developer in parent group' do
+ let_it_be(:developers_group_developer) { create(:user) }
+ let_it_be_with_reload(:developers_group) { create(:group, name: 'developers', path: 'developers') }
+
+ let(:user) { developers_group_developer }
+
+ before_all do
+ create(:project_group_link, :developer, group: developers_group, project: project)
+ developers_group.add_reporter(developers_group_developer)
+ end
+
+ it { expect_disallowed :read_runner }
+
+ context 'when user is developer in a group invited to project as developer' do
+ before_all do
+ developers_group.add_developer(developers_group_developer)
+ end
+
+ it { expect_allowed :read_runner }
+ end
+ end
end
end
diff --git a/spec/requests/api/graphql/group/work_item_types_spec.rb b/spec/requests/api/graphql/group/work_item_types_spec.rb
index 791c0fb9524..fbebcdad389 100644
--- a/spec/requests/api/graphql/group/work_item_types_spec.rb
+++ b/spec/requests/api/graphql/group/work_item_types_spec.rb
@@ -5,56 +5,19 @@ require 'spec_helper'
RSpec.describe 'getting a list of work item types for a group', feature_category: :team_planning do
include GraphqlHelpers
- let_it_be(:developer) { create(:user) }
let_it_be(:group) { create(:group, :private) }
+ let_it_be(:developer) { create(:user).tap { |u| group.add_developer(u) } }
- before_all do
- group.add_developer(developer)
- end
-
- let(:current_user) { developer }
-
- let(:fields) do
- <<~GRAPHQL
- workItemTypes{
- nodes { id name iconName }
- }
- GRAPHQL
- end
-
- let(:query) do
- graphql_query_for(
- 'group',
- { 'fullPath' => group.full_path },
- fields
- )
- end
-
- context 'when user has access to the group' do
- before do
- post_graphql(query, current_user: current_user)
- end
+ it_behaves_like 'graphql work item type list request spec' do
+ let(:current_user) { developer }
+ let(:parent_key) { :group }
- it_behaves_like 'a working graphql query'
-
- it 'returns all default work item types' do
- expect(graphql_data.dig('group', 'workItemTypes', 'nodes')).to match_array(
- WorkItems::Type.default.map do |type|
- hash_including('id' => type.to_global_id.to_s, 'name' => type.name, 'iconName' => type.icon_name)
- end
+ let(:query) do
+ graphql_query_for(
+ 'group',
+ { 'fullPath' => group.full_path },
+ query_nodes('WorkItemTypes', work_item_type_fields)
)
end
end
-
- context "when user doesn't have access to the group" do
- let(:current_user) { create(:user) }
-
- before do
- post_graphql(query, current_user: current_user)
- end
-
- it 'does not return the group' do
- expect(graphql_data).to eq('group' => nil)
- end
- end
end
diff --git a/spec/requests/api/graphql/project/work_item_types_spec.rb b/spec/requests/api/graphql/project/work_item_types_spec.rb
index c31a260c4b8..086db983760 100644
--- a/spec/requests/api/graphql/project/work_item_types_spec.rb
+++ b/spec/requests/api/graphql/project/work_item_types_spec.rb
@@ -5,56 +5,19 @@ require 'spec_helper'
RSpec.describe 'getting a list of work item types for a project', feature_category: :team_planning do
include GraphqlHelpers
- let_it_be(:developer) { create(:user) }
let_it_be(:project) { create(:project) }
+ let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
- before_all do
- project.add_developer(developer)
- end
-
- let(:current_user) { developer }
-
- let(:fields) do
- <<~GRAPHQL
- workItemTypes{
- nodes { id name iconName }
- }
- GRAPHQL
- end
-
- let(:query) do
- graphql_query_for(
- 'project',
- { 'fullPath' => project.full_path },
- fields
- )
- end
-
- context 'when user has access to the project' do
- before do
- post_graphql(query, current_user: current_user)
- end
+ it_behaves_like 'graphql work item type list request spec' do
+ let(:current_user) { developer }
+ let(:parent_key) { :project }
- it_behaves_like 'a working graphql query'
-
- it 'returns all default work item types' do
- expect(graphql_data.dig('project', 'workItemTypes', 'nodes')).to match_array(
- WorkItems::Type.default.map do |type|
- hash_including('id' => type.to_global_id.to_s, 'name' => type.name, 'iconName' => type.icon_name)
- end
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_nodes('WorkItemTypes', work_item_type_fields)
)
end
end
-
- context "when user doesn't have access to the project" do
- let(:current_user) { create(:user) }
-
- before do
- post_graphql(query, current_user: current_user)
- end
-
- it 'does not return the project' do
- expect(graphql_data).to eq('project' => nil)
- end
- end
end
diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb
index 36a27abd982..fe77b7ae736 100644
--- a/spec/requests/api/graphql/work_item_spec.rb
+++ b/spec/requests/api/graphql/work_item_spec.rb
@@ -104,6 +104,18 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
end
end
+ context 'when querying work item type information' do
+ include_context 'with work item types request context'
+
+ let(:work_item_fields) { "workItemType { #{work_item_type_fields} }" }
+
+ it 'returns work item type information' do
+ expect(work_item_data['workItemType']).to match(
+ expected_work_item_type_response(work_item.work_item_type).first
+ )
+ end
+ end
+
context 'when querying widgets' do
describe 'description widget' do
let(:work_item_fields) do
diff --git a/spec/support/shared_contexts/requests/api/graphql/work_items/work_item_types_shared_context.rb b/spec/support/shared_contexts/requests/api/graphql/work_items/work_item_types_shared_context.rb
new file mode 100644
index 00000000000..c3c2ddcea27
--- /dev/null
+++ b/spec/support/shared_contexts/requests/api/graphql/work_items/work_item_types_shared_context.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'with work item types request context' do
+ let(:work_item_type_fields) do
+ <<~GRAPHQL
+ id
+ name
+ iconName
+ widgetDefinitions {
+ type
+ ... on WorkItemWidgetDefinitionAssignees {
+ canInviteMembers
+ }
+ }
+ GRAPHQL
+ end
+
+ let(:widget_attributes) do
+ {
+ assignees: {
+ 'canInviteMembers' => false
+ }
+ }
+ end
+
+ def expected_work_item_type_response(type = nil)
+ base_scope = WorkItems::Type.default
+ base_scope = base_scope.id_in(type.id) if type
+
+ base_scope.map do |type|
+ hash_including(
+ 'id' => type.to_global_id.to_s,
+ 'name' => type.name,
+ 'iconName' => type.icon_name,
+ 'widgetDefinitions' => match_array(widgets_for(type))
+ )
+ end
+ end
+
+ def widgets_for(type)
+ type.widgets.map do |widget|
+ base_attributes = { 'type' => widget.type.to_s.upcase }
+ next base_attributes unless widget_attributes[widget.type]
+
+ base_attributes.merge(widget_attributes[widget.type])
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/graphql/work_item_type_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/work_item_type_list_shared_examples.rb
new file mode 100644
index 00000000000..a5315aa47a0
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/work_item_type_list_shared_examples.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'graphql work item type list request spec' do
+ include_context 'with work item types request context'
+
+ context 'when user has access to the group' do
+ it_behaves_like 'a working graphql query that returns data' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+ end
+
+ it 'returns all default work item types' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data_at(parent_key, :workItemTypes, :nodes)).to match_array(expected_work_item_type_response)
+ end
+
+ it 'prevents N+1 queries' do
+ # Destroy 2 existing types
+ WorkItems::Type.by_type([:issue, :task]).delete_all
+
+ post_graphql(query, current_user: current_user) # warm-up
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) { post_graphql(query, current_user: current_user) }
+ expect(graphql_errors).to be_blank
+
+ # Add back the 2 deleted types
+ expect do
+ Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.upsert_types
+ end.to change { WorkItems::Type.count }.by(2)
+
+ expect { post_graphql(query, current_user: current_user) }.to issue_same_number_of_queries_as(control)
+ expect(graphql_errors).to be_blank
+ end
+ end
+
+ context "when user doesn't have access to the parent" do
+ let(:current_user) { create(:user) }
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it 'does not return the parent' do
+ expect(graphql_data).to eq(parent_key.to_s => nil)
+ end
+ end
+end
diff --git a/spec/workers/bulk_imports/pipeline_worker_spec.rb b/spec/workers/bulk_imports/pipeline_worker_spec.rb
index d99b3e9de73..39f4d6c4f6d 100644
--- a/spec/workers/bulk_imports/pipeline_worker_spec.rb
+++ b/spec/workers/bulk_imports/pipeline_worker_spec.rb
@@ -16,12 +16,16 @@ RSpec.describe BulkImports::PipelineWorker, feature_category: :importers do
def self.file_extraction_pipeline?
false
end
+
+ def self.abort_on_failure?
+ false
+ end
end
end
let_it_be(:bulk_import) { create(:bulk_import) }
let_it_be(:config) { create(:bulk_import_configuration, bulk_import: bulk_import) }
- let_it_be(:entity) { create(:bulk_import_entity, bulk_import: bulk_import) }
+ let_it_be_with_reload(:entity) { create(:bulk_import_entity, bulk_import: bulk_import) }
let(:pipeline_tracker) do
create(
@@ -156,6 +160,21 @@ RSpec.describe BulkImports::PipelineWorker, feature_category: :importers do
expect(pipeline_tracker.status_name).to eq(:failed)
expect(pipeline_tracker.jid).to eq('jid')
+ expect(entity.reload.status_name).to eq(:created)
+ end
+
+ context 'when pipeline has abort_on_failure' do
+ before do
+ allow(pipeline_class).to receive(:abort_on_failure?).and_return(true)
+ end
+
+ it 'marks entity as failed' do
+ job = { 'args' => [pipeline_tracker.id, pipeline_tracker.stage, entity.id] }
+
+ described_class.sidekiq_retries_exhausted_block.call(job, StandardError.new('Error!'))
+
+ expect(entity.reload.status_name).to eq(:failed)
+ end
end
end
@@ -266,6 +285,10 @@ RSpec.describe BulkImports::PipelineWorker, feature_category: :importers do
describe '#perform' do
context 'when entity is failed' do
+ before do
+ entity.update!(status: -1)
+ end
+
it 'marks tracker as skipped and logs the skip' do
pipeline_tracker = create(
:bulk_import_tracker,
@@ -274,8 +297,6 @@ RSpec.describe BulkImports::PipelineWorker, feature_category: :importers do
status_event: 'enqueue'
)
- entity.update!(status: -1)
-
expect_next_instance_of(BulkImports::Logger) do |logger|
allow(logger).to receive(:info)
diff --git a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
index fa782967441..07f35c9e5b7 100644
--- a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
@@ -198,17 +198,5 @@ RSpec.describe Gitlab::GithubImport::StageMethods, feature_category: :importers
is_expected.to include('max_retries_after_interruption' => 20)
end
-
- context 'when the flag is disabled' do
- before do
- stub_feature_flags(github_importer_raise_max_interruptions: false)
- end
-
- it 'does not set `max_retries_after_interruption`' do
- worker.class.resumes_work_when_interrupted!
-
- is_expected.not_to have_key('max_retries_after_interruption')
- end
- end
end
end
diff --git a/vendor/gems/sidekiq-reliable-fetch/lib/sidekiq/semi_reliable_fetch.rb b/vendor/gems/sidekiq-reliable-fetch/lib/sidekiq/semi_reliable_fetch.rb
index d33b5049300..91b41501374 100644
--- a/vendor/gems/sidekiq-reliable-fetch/lib/sidekiq/semi_reliable_fetch.rb
+++ b/vendor/gems/sidekiq-reliable-fetch/lib/sidekiq/semi_reliable_fetch.rb
@@ -10,25 +10,16 @@ module Sidekiq
def initialize(options)
super
- @alternative_store = options[:alternative_store]
- @namespace = options[:namespace]
-
- @queues = @queues + @queues.map { |q| "#{@namespace}:#{q}" } if @namespace
-
- if strictly_ordered_queues
- @queues = @queues.uniq
- @queues << { timeout: semi_reliable_fetch_timeout }
- end
+ @queues = @queues.uniq
end
private
def retrieve_unit_of_work
- work = with_redis { |conn| conn.brpop(*queues_cmd) }
+ work = Sidekiq.redis { |conn| conn.brpop(*queues_cmd, timeout: semi_reliable_fetch_timeout) }
return unless work
queue, job = work
- queue = queue.to_s.sub(/\A#{@namespace}:/, '') if @namespace
unit_of_work = UnitOfWork.new(queue, job)
Sidekiq.redis do |conn|
@@ -38,21 +29,11 @@ module Sidekiq
unit_of_work
end
- def with_redis(&blk)
- if @alternative_store
- @alternative_store.with(&blk)
- else
- Sidekiq.redis(&blk)
- end
- end
-
def queues_cmd
if strictly_ordered_queues
@queues
else
- queues = @queues.shuffle.uniq
- queues << { timeout: semi_reliable_fetch_timeout }
- queues
+ @queues.shuffle
end
end
diff --git a/vendor/gems/sidekiq-reliable-fetch/spec/semi_reliable_fetch_spec.rb b/vendor/gems/sidekiq-reliable-fetch/spec/semi_reliable_fetch_spec.rb
index d6701c7c7e1..60cd81ba913 100644
--- a/vendor/gems/sidekiq-reliable-fetch/spec/semi_reliable_fetch_spec.rb
+++ b/vendor/gems/sidekiq-reliable-fetch/spec/semi_reliable_fetch_spec.rb
@@ -11,40 +11,6 @@ describe Sidekiq::SemiReliableFetch do
let(:options) { { queues: queues } }
let(:fetcher) { described_class.new(options) }
- context 'namespace config' do
- let(:options) { { queues: queues, namespace: 'namespaced' } }
-
- before do
- Sidekiq.redis do |conn|
- conn.rpush('queue:stuff_to_do', 'msg1')
- conn.rpush('namespaced:queue:stuff_to_do', 'msg2')
- end
- end
-
- it 'runs brpop on both namespaced and non-namespaced' do
- jobs = (1..2).map { fetcher.retrieve_work&.job }
-
- expect(jobs).to match_array(['msg1', 'msg2'])
- end
- end
-
- context 'alternative_store config' do
- let(:store) { Sidekiq::RedisConnection.create(url: REDIS_URL, size: 10) }
- let(:options) { { queues: queues, alternative_store: store } }
-
- it 'connects using alternative store' do
- Sidekiq.redis do |connection|
- expect(connection).not_to receive(:brpop)
- end
-
- store.with do |connection|
- expect(connection).to receive(:brpop).with("queue:stuff_to_do", { timeout: 2 }).once.and_call_original
- end
-
- fetcher.retrieve_work
- end
- end
-
context 'timeout config' do
before do
stub_env('SIDEKIQ_SEMI_RELIABLE_FETCH_TIMEOUT', timeout)
diff --git a/yarn.lock b/yarn.lock
index 298d39fc322..210bde0d8a7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1274,10 +1274,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.71.0.tgz#ff4a3cf22cd12b3c861ef2065583cc49923cf5f8"
integrity sha512-aYjC9uef5Q3CDg4Zu9fh0mce4jO2LANaEgRLutoAYRXG4ymWwRmgP8SZmZyQY0B4hcZjBfUsyVykIhVnlNcRLw==
-"@gitlab/ui@^68.5.0":
- version "68.5.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-68.5.0.tgz#4850c4c87d924b1ae40539bf4de22a555f8e9b17"
- integrity sha512-4Cr0+TTCjr7MApZOm0n9GJuWHVkFCkbldLZjsoCVVGBc6qpv2I0h+nMmbFFMrMjuVicfa9/UPXm4XIdqbw3QqA==
+"@gitlab/ui@^69.0.0":
+ version "69.0.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-69.0.0.tgz#981979e1f4a639ef21f266c9e96b8aa6a625db87"
+ integrity sha512-HCWLX0/1NwQILeRQ1c0yVCcjGftX7L8Put4mmEaoCPGcX1r8aE/mdkqTzOp9vTzi3OxzaJ8FDA/GV7RaxdGP0A==
dependencies:
"@floating-ui/dom" "1.2.9"
bootstrap-vue "2.23.1"