From 9a66377d7f1e7bfe1235608f471aab08307d02b2 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 27 May 2024 18:19:32 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/rails.gitlab-ci.yml | 53 ++++ .gitlab/ci/rails/shared.gitlab-ci.yml | 11 + .gitlab/ci/rules.gitlab-ci.yml | 5 + .rubocop_todo/layout/argument_alignment.yml | 26 -- .rubocop_todo/lint/symbol_conversion.yml | 25 -- app/assets/javascripts/api.js | 11 + .../list/components/issues_list_app.vue | 2 +- .../model_registry/apps/index_ml_models.vue | 17 +- .../components/model_create.vue | 234 ++++++++++++++ .../branch_rules/components/view/index.vue | 7 +- .../components/view/protection.vue | 17 +- .../components/view/protection_row.vue | 15 +- .../components/view/rule_drawer.vue | 25 +- .../branch_rules_details.query.graphql | 3 + .../javascripts/projects/settings/utils.js | 2 +- .../components/list_selector/group_item.vue | 2 +- .../components/list_selector/index.vue | 25 ++ .../components/work_item_detail.vue | 34 +- .../stylesheets/page_bundles/work_items.scss | 19 +- app/controllers/admin/groups_controller.rb | 6 +- app/controllers/admin/projects_controller.rb | 2 +- app/controllers/groups_controller.rb | 4 +- app/controllers/projects_controller.rb | 2 +- app/helpers/breadcrumbs_helper.rb | 2 +- app/presenters/project_presenter.rb | 24 +- .../create_cloudsql_instance_service.rb | 12 +- app/services/cohorts_service.rb | 2 +- .../validates_classification_label.rb | 2 +- .../groups/group_links/destroy_service.rb | 2 +- app/services/issuable_links/create_service.rb | 4 +- .../relative_position_rebalancing_service.rb | 2 +- app/services/jira/requests/base.rb | 4 +- config/initializers/1_settings.rb | 2 +- ...carrierwave_s3_encryption_headers_patch.rb | 2 +- ...tive_record_relation_methods_with_limit.rb | 2 +- data/whats_new/202205220001_15_0.yml | 2 +- data/whats_new/202206220001_15_1.yml | 2 +- data/whats_new/202305220001_16_0.yml | 2 +- ...fill_related_epic_links_to_issue_links.yml | 9 + ...kfill_related_epic_links_to_issue_links.rb | 27 ++ db/schema_migrations/20240517131715 | 1 + .../pipeline_execution_policy/index.md | 32 +- doc/development/fe_guide/style/scss.md | 39 ++- ...kfill_related_epic_links_to_issue_links.rb | 46 +++ lib/gitlab/usage_data.rb | 4 +- .../merge_request_activity_unique_counter.rb | 2 +- lib/peek/views/detailed_view.rb | 2 +- lib/peek/views/redis_detailed.rb | 2 +- lib/tasks/gems.rake | 12 +- lib/tasks/gitlab/feature_categories.rake | 6 +- lib/tasks/gitlab/seed.rake | 2 +- lib/tasks/gitlab/seed/runner_fleet.rake | 2 +- lib/tasks/gitlab/uploads/sanitize.rake | 8 +- locale/gitlab.pot | 54 +++- rubocop/cop/gitlab/finder_with_find_by.rb | 2 +- rubocop/cop/qa/ambiguous_page_object_name.rb | 2 +- .../admin/groups_controller_spec.rb | 1 + .../admin/projects_controller_spec.rb | 13 +- spec/controllers/groups_controller_spec.rb | 1 + spec/controllers/projects_controller_spec.rb | 1 + spec/features/groups_spec.rb | 2 +- spec/features/projects_spec.rb | 2 +- spec/frontend/api_spec.js | 13 + .../apps/index_ml_models_spec.js | 7 +- .../components/model_create_spec.js | 298 ++++++++++++++++++ .../branch_rules/components/view/mock_data.js | 9 + .../components/view/protection_row_spec.js | 5 +- .../components/view/protection_spec.js | 19 +- .../components/list_selector/index_spec.js | 6 + .../snippets_repository_pipeline_spec.rb | 4 +- .../constraints/group_url_constrainer_spec.rb | 2 +- .../project_url_constrainer_spec.rb | 2 +- .../constraints/user_url_constrainer_spec.rb | 2 +- .../gitlab_api_client_spec.rb | 66 ++-- .../redis_hll_generator_spec.rb | 2 +- .../usage_metric_definition_generator_spec.rb | 2 +- ..._related_epic_links_to_issue_links_spec.rb | 120 +++++++ spec/lib/gitlab/ci/config/entry/job_spec.rb | 2 +- spec/lib/gitlab/ci/config/entry/jobs_spec.rb | 6 +- .../ci/config/entry/processable_spec.rb | 2 +- .../ci/parsers/accessibility/pa11y_spec.rb | 50 +-- .../parsers/codequality/code_climate_spec.rb | 138 ++++---- .../ci/reports/accessibility_reports_spec.rb | 32 +- .../ci/reports/codequality_reports_spec.rb | 10 +- spec/lib/gitlab/ci/yaml_processor_spec.rb | 6 +- ..._related_epic_links_to_issue_links_spec.rb | 26 ++ spec/workers/ci/retry_pipeline_worker_spec.rb | 18 +- 87 files changed, 1342 insertions(+), 390 deletions(-) create mode 100644 app/assets/javascripts/ml/model_registry/components/model_create.vue create mode 100644 db/docs/batched_background_migrations/backfill_related_epic_links_to_issue_links.yml create mode 100644 db/post_migrate/20240517131715_queue_backfill_related_epic_links_to_issue_links.rb create mode 100644 db/schema_migrations/20240517131715 create mode 100644 lib/gitlab/background_migration/backfill_related_epic_links_to_issue_links.rb create mode 100644 spec/frontend/ml/model_registry/components/model_create_spec.js create mode 100644 spec/lib/gitlab/background_migration/backfill_related_epic_links_to_issue_links_spec.rb create mode 100644 spec/migrations/20240517131715_queue_backfill_related_epic_links_to_issue_links_spec.rb diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 25874b0dc71..1d0adfa2ade 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -306,6 +306,59 @@ rspec system pg14 praefect: - .rspec-system-parallel - .rails:rules:praefect-with-db +# Test jobs that run without Gitaly's transactions enabled. These will be removed once +# transactions are always in use in Gitaly. +rspec migration pg14 no_gitaly_transactions: + extends: + - rspec migration pg14 + - .gitaly-without-transactions + +rspec background_migration pg14 no_gitaly_transactions: + extends: + - rspec background_migration pg14 + - .gitaly-without-transactions + +rspec unit pg14 no_gitaly_transactions: + extends: + - rspec unit pg14 + - .gitaly-without-transactions + +rspec integration pg14 no_gitaly_transactions: + extends: + - rspec integration pg14 + - .gitaly-without-transactions + +rspec system pg14 no_gitaly_transactions: + extends: + - rspec system pg14 + - .gitaly-without-transactions + +rspec-ee migration pg14 no_gitaly_transactions: + extends: + - rspec-ee migration pg14 + - .gitaly-without-transactions + +rspec-ee background_migration pg14 no_gitaly_transactions: + extends: + - rspec-ee background_migration pg14 + - .gitaly-without-transactions + +rspec-ee unit pg14 no_gitaly_transactions: + extends: + - rspec-ee unit pg14 + - .gitaly-without-transactions + +rspec-ee integration pg14 no_gitaly_transactions: + extends: + - rspec-ee integration pg14 + - .gitaly-without-transactions + +rspec-ee system pg14 no_gitaly_transactions: + extends: + - rspec-ee system pg14 + - .gitaly-without-transactions + + # Dedicated job to test DB library code against PG13. # Note that these are already tested against PG13 in the `rspec unit pg13` / `rspec-ee unit pg13` jobs. rspec db-library-code pg13: diff --git a/.gitlab/ci/rails/shared.gitlab-ci.yml b/.gitlab/ci/rails/shared.gitlab-ci.yml index e230b0d3190..e9c976db7fc 100644 --- a/.gitlab/ci/rails/shared.gitlab-ci.yml +++ b/.gitlab/ci/rails/shared.gitlab-ci.yml @@ -56,6 +56,16 @@ include: variables: GITALY_PRAEFECT_WITH_DB: '1' +.gitaly-with-transactions: + variables: + GITALY_TRANSACTIONS_ENABLED: "true" + +.gitaly-without-transactions: + extends: + - .rails:rules:gitaly-without-transactions + variables: + GITALY_TRANSACTIONS_ENABLED: "false" + .rspec-base-needs: needs: - job: "clone-gitlab-repo" @@ -68,6 +78,7 @@ include: - .rails-job-base - .base-artifacts - .repo-from-artifacts + - .gitaly-with-transactions stage: test variables: RUBY_GC_MALLOC_LIMIT: 67108864 diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 9872ffd76e8..9835ad088ba 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -2039,6 +2039,11 @@ - if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-praefect-with-db/' allow_failure: true +.rails:rules:gitaly-without-transactions: + rules: + - <<: *if-schedule-maintenance + - if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-without-gitaly-transactions/' + .rails:rules:ee-and-foss-migration: rules: - <<: *if-fork-merge-request diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml index 6d7d5554748..e4cbcd0d97a 100644 --- a/.rubocop_todo/layout/argument_alignment.yml +++ b/.rubocop_todo/layout/argument_alignment.yml @@ -73,19 +73,6 @@ Layout/ArgumentAlignment: - 'ee/spec/requests/api/deployments_spec.rb' - 'ee/spec/requests/api/dora/metrics_spec.rb' - 'ee/spec/requests/api/epics_spec.rb' - - 'ee/spec/requests/api/graphql/ci/runner_spec.rb' - - 'ee/spec/requests/api/graphql/group/dast_profile_schedule_spec.rb' - - 'ee/spec/requests/api/graphql/group/epic/epic_children_spec.rb' - - 'ee/spec/requests/api/graphql/group/epic/epic_issues_spec.rb' - - 'ee/spec/requests/api/graphql/group/epics_spec.rb' - - 'ee/spec/requests/api/graphql/merge_request_reviewer_spec.rb' - - 'ee/spec/requests/api/graphql/merge_requests/approval_state_spec.rb' - - 'ee/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb' - - 'ee/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb' - - 'ee/spec/requests/api/graphql/mutations/boards/lists/update_limit_metrics_spec.rb' - - 'ee/spec/requests/api/graphql/mutations/boards/update_epic_user_preferences_spec.rb' - - 'ee/spec/requests/api/graphql/mutations/compliance_management/frameworks/destroy_spec.rb' - - 'ee/spec/requests/api/graphql/mutations/compliance_management/frameworks/update_spec.rb' - 'ee/spec/requests/api/graphql/mutations/epic_tree/reorder_spec.rb' - 'ee/spec/requests/api/graphql/mutations/issues/set_weight_spec.rb' - 'ee/spec/requests/api/graphql/mutations/iterations/cadences/create_spec.rb' @@ -307,19 +294,6 @@ Layout/ArgumentAlignment: - 'lib/gitlab/sidekiq_config/worker.rb' - 'lib/gitlab/spamcheck/client.rb' - 'lib/gitlab/usage/metrics/instrumentations/database_metric.rb' - - 'lib/gitlab/usage_data.rb' - - 'lib/gitlab/usage_data_counters/ci_template_unique_counter.rb' - - 'lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb' - - 'lib/peek/views/detailed_view.rb' - - 'lib/peek/views/redis_detailed.rb' - - 'lib/tasks/gems.rake' - - 'lib/tasks/gitlab/feature_categories.rake' - - 'lib/tasks/gitlab/seed.rake' - - 'lib/tasks/gitlab/seed/runner_fleet.rake' - - 'lib/tasks/gitlab/shell.rake' - - 'lib/tasks/gitlab/uploads/sanitize.rake' - - 'rubocop/cop/gitlab/finder_with_find_by.rb' - - 'rubocop/cop/qa/ambiguous_page_object_name.rb' - 'rubocop/cop/rspec/modify_sidekiq_middleware.rb' - 'scripts/packages/automated_cleanup.rb' - 'scripts/rubocop-parse' diff --git a/.rubocop_todo/lint/symbol_conversion.yml b/.rubocop_todo/lint/symbol_conversion.yml index c79852103d5..c400ccf8aaa 100644 --- a/.rubocop_todo/lint/symbol_conversion.yml +++ b/.rubocop_todo/lint/symbol_conversion.yml @@ -13,7 +13,6 @@ Lint/SymbolConversion: - 'app/graphql/resolvers/environments/last_deployment_resolver.rb' - 'app/graphql/resolvers/user_discussions_count_resolver.rb' - 'app/graphql/resolvers/user_notes_count_resolver.rb' - - 'app/helpers/breadcrumbs_helper.rb' - 'app/helpers/notifications_helper.rb' - 'app/helpers/projects_helper.rb' - 'app/models/application_record.rb' @@ -28,7 +27,6 @@ Lint/SymbolConversion: - 'app/models/work_items/widgets/assignees.rb' - 'app/models/work_items/widgets/base.rb' - 'app/presenters/snippet_presenter.rb' - - 'app/services/cloud_seed/google_cloud/create_cloudsql_instance_service.rb' - 'app/services/concerns/deploy_token_methods.rb' - 'app/services/concerns/rate_limited_service.rb' - 'app/services/container_expiration_policies/cleanup_service.rb' @@ -37,30 +35,22 @@ Lint/SymbolConversion: - 'app/services/notification_recipients/builder/default.rb' - 'app/services/notification_service.rb' - 'app/workers/project_export_worker.rb' - - 'ee/app/components/billing/plan_component.rb' - - 'ee/app/controllers/projects/security/scanned_resources_controller.rb' - 'ee/app/graphql/ee/resolvers/projects/branch_rules_resolver.rb' - 'ee/app/graphql/types/ci/jobs_duration_statistics_type.rb' - 'ee/app/models/concerns/ee/protected_branch.rb' - 'ee/app/models/geo_node_status.rb' - 'ee/app/policies/ee/group_policy.rb' - 'ee/app/policies/ee/project_policy.rb' - - 'ee/app/serializers/integrations/zentao_serializers/issue_entity.rb' - 'ee/app/serializers/report_list_entity.rb' - 'ee/app/services/security/security_orchestration_policies/ci_action/base.rb' - 'ee/app/services/vulnerabilities/manually_create_service.rb' - 'ee/app/workers/security/store_scans_worker.rb' - - 'ee/db/fixtures/development/35_merge_request_predictions.rb' - 'ee/lib/api/concerns/dependency_proxy/packages_helpers.rb' - 'ee/lib/ee/api/helpers.rb' - - 'ee/lib/elastic/latest/note_class_proxy.rb' - - 'ee/lib/gitlab/applied_ml/suggested_reviewers/client.rb' - 'ee/lib/gitlab/elastic/search_results.rb' - 'ee/lib/gitlab/geo/replicator.rb' - 'ee/lib/gitlab/graphql/aggregations/epics/epic_node.rb' - 'ee/lib/search/zoekt/search_results.rb' - - 'ee/spec/controllers/admin/audit_logs_controller_spec.rb' - - 'ee/spec/controllers/groups/audit_events_controller_spec.rb' - 'ee/spec/controllers/projects/audit_events_controller_spec.rb' - 'ee/spec/factories/ci/builds.rb' - 'ee/spec/factories/ci/pipelines.rb' @@ -141,21 +131,6 @@ Lint/SymbolConversion: - 'spec/lib/api/entities/nuget/search_result_spec.rb' - 'spec/lib/api/helpers/rate_limiter_spec.rb' - 'spec/lib/bulk_imports/projects/pipelines/project_feature_pipeline_spec.rb' - - 'spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb' - - 'spec/lib/constraints/group_url_constrainer_spec.rb' - - 'spec/lib/constraints/project_url_constrainer_spec.rb' - - 'spec/lib/constraints/user_url_constrainer_spec.rb' - - 'spec/lib/container_registry/gitlab_api_client_spec.rb' - - 'spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb' - - 'spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb' - - 'spec/lib/gitlab/ci/config/entry/job_spec.rb' - - 'spec/lib/gitlab/ci/config/entry/jobs_spec.rb' - - 'spec/lib/gitlab/ci/config/entry/processable_spec.rb' - - 'spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb' - - 'spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb' - - 'spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb' - - 'spec/lib/gitlab/ci/reports/codequality_reports_spec.rb' - - 'spec/lib/gitlab/ci/yaml_processor_spec.rb' - 'spec/lib/google_api/cloud_platform/client_spec.rb' - 'spec/lib/service_ping/devops_report_spec.rb' - 'spec/models/appearance_spec.rb' diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 7ecdd38ca83..50f75bddc08 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -176,6 +176,17 @@ const Api = { }); }, + groupSubgroups(id, options) { + const url = Api.buildUrl(this.subgroupsPath).replace(':id', encodeURIComponent(id)); + + return axios.get(url, { + params: { + per_page: DEFAULT_PER_PAGE, + ...options, + }, + }); + }, + inviteGroupMembers(id, data) { const url = Api.buildUrl(this.groupInvitationsPath).replace(':id', encodeURIComponent(id)); diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue index 4cf515e1c0c..3063e69f63e 100644 --- a/app/assets/javascripts/issues/list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue @@ -922,7 +922,7 @@ export default { :key="activeIssuable.iid" :work-item-iid="activeIssuable.iid" is-drawer - class="gl-pt-0!" + class="gl-pt-0! work-item-drawer" @work-item-updated="updateIssuablesCache" @work-item-emoji-updated="updateIssuableEmojis" @addChild="refetchIssuables" diff --git a/app/assets/javascripts/ml/model_registry/apps/index_ml_models.vue b/app/assets/javascripts/ml/model_registry/apps/index_ml_models.vue index 4894b8b69e4..fb39bca7630 100644 --- a/app/assets/javascripts/ml/model_registry/apps/index_ml_models.vue +++ b/app/assets/javascripts/ml/model_registry/apps/index_ml_models.vue @@ -1,5 +1,5 @@ + + diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue index 6371318e4d6..7093908feab 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue @@ -10,6 +10,7 @@ import { GlModal, GlModalDirective, } from '@gitlab/ui'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { sprintf, n__, s__ } from '~/locale'; import { getParameterByName, @@ -92,13 +93,13 @@ export default { projectPath: this.projectPath, }; }, - update({ project: { branchRules } }) { + update({ project: { branchRules, group } }) { const branchRule = branchRules.nodes.find((rule) => rule.name === this.branch); this.branchRule = branchRule; this.branchProtection = branchRule?.branchProtection; this.statusChecks = branchRule?.externalStatusChecks?.nodes || []; this.matchingBranchesCount = branchRule?.matchingBranchesCount; - + this.groupId = getIdFromGraphQLId(group?.id) || ''; if (!this.showApprovers) return; // The approval rules app uses a separate endpoint to fetch the list of approval rules. // In future, we will update the GraphQL request to include the approval rules data. @@ -118,6 +119,7 @@ export default { branchProtection: {}, statusChecks: [], branchRule: {}, + groupId: null, matchingBranchesCount: null, isAllowedToMergeDrawerOpen: false, isRuleUpdating: false, @@ -355,6 +357,7 @@ export default { :users="mergeAccessLevels.users" :groups="mergeAccessLevels.groups" :is-loading="isRuleUpdating" + :group-id="groupId" :title="s__('BranchRules|Edit allowed to merge')" @editRule=" editBranchRule({ diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue index f9b1fffb63f..367f074e381 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue @@ -6,7 +6,7 @@ import ProtectionRow from './protection_row.vue'; export const i18n = { rolesTitle: s__('BranchRules|Roles'), - usersTitle: s__('BranchRules|Users'), + usersAndGroupsTitle: s__('BranchRules|Users & groups'), groupsTitle: s__('BranchRules|Groups'), }; @@ -117,20 +117,13 @@ export default { - + - - - diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue index 88ded6c3512..15c1e3fec94 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue @@ -36,6 +36,11 @@ export default { required: false, default: () => [], }, + groups: { + type: Array, + required: false, + default: () => [], + }, statusCheckUrl: { type: String, required: false, @@ -50,6 +55,9 @@ export default { this.users.length - this.$options.MAX_VISIBLE_AVATARS, ); }, + usersAndGroups() { + return [...this.users, ...this.groups]; + }, }, }; @@ -65,9 +73,9 @@ export default {
{{ statusCheckUrl }}
diff --git a/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql b/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql index 343f8828ed1..f2f7ed2794e 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql +++ b/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql @@ -1,6 +1,9 @@ query getBranchRulesDetails($projectPath: ID!) { project(fullPath: $projectPath) { id + group { + id + } branchRules { nodes { id diff --git a/app/assets/javascripts/projects/settings/utils.js b/app/assets/javascripts/projects/settings/utils.js index 9c19657bb39..150d034bcf9 100644 --- a/app/assets/javascripts/projects/settings/utils.js +++ b/app/assets/javascripts/projects/settings/utils.js @@ -28,7 +28,7 @@ export const getAccessLevels = (accessLevels = {}) => { const src = node.user.avatarUrl; accessLevelTypes.users.push({ src, ...node.user }); } else if (node.group) { - accessLevelTypes.groups.push(node); + accessLevelTypes.groups.push(node.group); } else { accessLevelTypes.roles.push({ accessLevelDescription: node.accessLevelDescription }); } diff --git a/app/assets/javascripts/vue_shared/components/list_selector/group_item.vue b/app/assets/javascripts/vue_shared/components/list_selector/group_item.vue index 1fce74ceaea..c9615b2943c 100644 --- a/app/assets/javascripts/vue_shared/components/list_selector/group_item.vue +++ b/app/assets/javascripts/vue_shared/components/list_selector/group_item.vue @@ -25,7 +25,7 @@ export default { return sprintf(__('Delete %{name}'), { name: this.name }); }, fullName() { - return this.data.fullName; + return this.data.fullName || this.data.name; }, name() { return this.data.name; diff --git a/app/assets/javascripts/vue_shared/components/list_selector/index.vue b/app/assets/javascripts/vue_shared/components/list_selector/index.vue index 1cbcd1a876a..34fb05f7494 100644 --- a/app/assets/javascripts/vue_shared/components/list_selector/index.vue +++ b/app/assets/javascripts/vue_shared/components/list_selector/index.vue @@ -40,6 +40,16 @@ export default { required: false, default: null, }, + groupPath: { + type: String, + required: false, + default: null, + }, + groupId: { + type: Number, + required: false, + default: null, + }, autofocus: { type: Boolean, required: false, @@ -128,6 +138,10 @@ export default { }, async fetchGroupsBySearchTerm(search) { let groups = []; + if (parseBoolean(this.groupId)) { + groups = await this.fetchSubgroupsBySearchTerm(search); + return groups; + } if (parseBoolean(this.isProjectNamespace)) { groups = await this.fetchProjectGroups(search); } else { @@ -164,6 +178,17 @@ export default { })), ); }, + async fetchSubgroupsBySearchTerm(search) { + let groups = []; + const subgroups = await Api.groupSubgroups(this.groupId, search); + groups = subgroups?.data || []; + return groups?.map((group) => ({ + text: group.fullName, + value: group.name, + type: 'group', + ...group, + })); + }, fetchDeployKeysBySearchTerm() { // TODO - implement API request (follow-up) // https://gitlab.com/gitlab-org/gitlab/-/issues/432494 diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index c546fbfdc01..0085f9bacb7 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -650,24 +650,24 @@ export default { /> - - + + diff --git a/app/assets/stylesheets/page_bundles/work_items.scss b/app/assets/stylesheets/page_bundles/work_items.scss index 726948374a6..b44cc39f995 100644 --- a/app/assets/stylesheets/page_bundles/work_items.scss +++ b/app/assets/stylesheets/page_bundles/work_items.scss @@ -143,12 +143,23 @@ $work-item-overview-gap-width: 2rem; } } -.work-item-view { +.gl-modal .work-item-view, +.work-item-drawer .work-item-view { container-name: work-item-view; container-type: inline-size; } -@container work-item-view (max-width: #{calc($breakpoint-md - 1px)}) { +@mixin container-and-media($query) { + @container work-item-view (#{$query}) { + @content; + } + + @media ($query) { + @content; + } +} + +@include container-and-media("max-width: #{calc($breakpoint-md - 0.02px)}") { .work-item-overview { display: block !important; } @@ -176,7 +187,7 @@ $work-item-overview-gap-width: 2rem; margin-top: $gl-spacing-scale-5; } -@container work-item-view (min-width: #{$breakpoint-md}) { +@include container-and-media("min-width: #{calc($breakpoint-md)}") { .work-item-attributes-wrapper { top: calc(#{$calc-application-header-height} + #{$work-item-sticky-header-height}); height: calc(#{$calc-application-viewport-height} - #{$work-item-sticky-header-height}); @@ -190,9 +201,7 @@ $work-item-overview-gap-width: 2rem; .work-item-date-picker { max-width: 175px; } -} -@container work-item-view (min-width: #{$breakpoint-md}) { .work-item-overview-right-sidebar { margin-top: 0; grid-row-start: 1; diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index ef89ff725ae..74baecbaf51 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -65,9 +65,9 @@ class Admin::GroupsController < Admin::ApplicationController def destroy Groups::DestroyService.new(@group, current_user).async_execute - redirect_to admin_groups_path, - status: :found, - alert: format(_('Group %{group_name} was scheduled for deletion.'), group_name: @group.name) + flash[:toast] = format(_("Group '%{group_name}' is being deleted."), group_name: @group.full_name) + + redirect_to admin_groups_path, status: :found end private diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index e79a899cee7..38d3de04d34 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -43,7 +43,7 @@ class Admin::ProjectsController < Admin::ApplicationController def destroy ::Projects::DestroyService.new(@project, current_user, {}).async_execute - flash[:notice] = format(_("Project '%{project_name}' is in the process of being deleted."), project_name: @project.full_name) + flash[:toast] = format(_("Project '%{project_name}' is being deleted."), project_name: @project.full_name) redirect_to admin_projects_path, status: :found rescue Projects::DestroyService::DestroyError => e diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index e172a2a5c8c..fbb27eb2523 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -173,7 +173,9 @@ class GroupsController < Groups::ApplicationController def destroy Groups::DestroyService.new(@group, current_user).async_execute - redirect_to root_path, status: :found, alert: "Group '#{@group.name}' was scheduled for deletion." + flash[:toast] = format(_("Group '%{group_name}' is being deleted."), group_name: @group.full_name) + + redirect_to root_path, status: :found end # rubocop: disable CodeReuse/ActiveRecord diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 46a3711ab2f..9e8f3567bb4 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -194,7 +194,7 @@ class ProjectsController < Projects::ApplicationController return access_denied! unless can?(current_user, :remove_project, @project) ::Projects::DestroyService.new(@project, current_user, {}).async_execute - flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.full_name } + flash[:toast] = format(_("Project '%{project_name}' is being deleted."), project_name: @project.full_name) redirect_to dashboard_projects_path, status: :found rescue Projects::DestroyService::DestroyError => e diff --git a/app/helpers/breadcrumbs_helper.rb b/app/helpers/breadcrumbs_helper.rb index 7026063df44..8aefc715156 100644 --- a/app/helpers/breadcrumbs_helper.rb +++ b/app/helpers/breadcrumbs_helper.rb @@ -39,7 +39,7 @@ module BreadcrumbsHelper { '@context': 'https://schema.org', '@type': 'BreadcrumbList', - 'itemListElement': build_item_list_elements&.map&.with_index do |item, index| + itemListElement: build_item_list_elements&.map&.with_index do |item, index| { '@type' => 'ListItem', 'position' => index + 1, diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb index d3e2d41782f..4a72a2a248a 100644 --- a/app/presenters/project_presenter.rb +++ b/app/presenters/project_presenter.rb @@ -187,11 +187,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated AnchorData.new( true, statistic_icon('rocket-launch') + - n_('%{strong_start}%{release_count}%{strong_end} Release', '%{strong_start}%{release_count}%{strong_end} Releases', releases_count).html_safe % { + (n_('%{strong_start}%{release_count}%{strong_end} Release', '%{strong_start}%{release_count}%{strong_end} Releases', releases_count).html_safe % { release_count: number_with_delimiter(releases_count), strong_start: ''.html_safe, strong_end: ''.html_safe - }, + }), project_releases_path(project) ) end @@ -205,11 +205,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated AnchorData.new( true, statistic_icon('environment') + - n_('%{strong_start}%{count}%{strong_end} Environment', '%{strong_start}%{count}%{strong_end} Environments', environments_count).html_safe % { + (n_('%{strong_start}%{count}%{strong_end} Environment', '%{strong_start}%{count}%{strong_end} Environments', environments_count).html_safe % { count: number_with_delimiter(environments_count), strong_start: ''.html_safe, strong_end: ''.html_safe - }, + }), project_environments_path(project) ) end @@ -218,11 +218,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated AnchorData.new( true, statistic_icon('commit') + - n_('%{strong_start}%{commit_count}%{strong_end} Commit', '%{strong_start}%{commit_count}%{strong_end} Commits', statistics.commit_count).html_safe % { + (n_('%{strong_start}%{commit_count}%{strong_end} Commit', '%{strong_start}%{commit_count}%{strong_end} Commits', statistics.commit_count).html_safe % { commit_count: number_with_delimiter(statistics.commit_count), strong_start: ''.html_safe, strong_end: ''.html_safe - }, + }), empty_repo? ? nil : project_commits_path(project, default_branch_or_main) ) end @@ -231,11 +231,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated AnchorData.new( true, statistic_icon('branch') + - n_('%{strong_start}%{branch_count}%{strong_end} Branch', '%{strong_start}%{branch_count}%{strong_end} Branches', repository.branch_count).html_safe % { + (n_('%{strong_start}%{branch_count}%{strong_end} Branch', '%{strong_start}%{branch_count}%{strong_end} Branches', repository.branch_count).html_safe % { branch_count: number_with_delimiter(repository.branch_count), strong_start: ''.html_safe, strong_end: ''.html_safe - }, + }), empty_repo? ? nil : project_branches_path(project) ) end @@ -252,11 +252,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated AnchorData.new( true, statistic_icon('terraform') + - n_('%{strong_start}%{terraform_states_count}%{strong_end} Terraform State', '%{strong_start}%{terraform_states_count}%{strong_end} Terraform States', project.terraform_states.count).html_safe % { + (n_('%{strong_start}%{terraform_states_count}%{strong_end} Terraform State', '%{strong_start}%{terraform_states_count}%{strong_end} Terraform States', project.terraform_states.count).html_safe % { terraform_states_count: number_with_delimiter(project.terraform_states.count), strong_start: ''.html_safe, strong_end: ''.html_safe - } + terraform_warn_icon, + }) + terraform_warn_icon, project_terraform_index_path(project) ) end @@ -266,11 +266,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated AnchorData.new( true, statistic_icon('label') + - n_('%{strong_start}%{tag_count}%{strong_end} Tag', '%{strong_start}%{tag_count}%{strong_end} Tags', repository.tag_count).html_safe % { + (n_('%{strong_start}%{tag_count}%{strong_end} Tag', '%{strong_start}%{tag_count}%{strong_end} Tags', repository.tag_count).html_safe % { tag_count: number_with_delimiter(repository.tag_count), strong_start: ''.html_safe, strong_end: ''.html_safe - }, + }), empty_repo? ? nil : project_tags_path(project) ) end diff --git a/app/services/cloud_seed/google_cloud/create_cloudsql_instance_service.rb b/app/services/cloud_seed/google_cloud/create_cloudsql_instance_service.rb index 8b967a2d551..62fb5ccbc30 100644 --- a/app/services/cloud_seed/google_cloud/create_cloudsql_instance_service.rb +++ b/app/services/cloud_seed/google_cloud/create_cloudsql_instance_service.rb @@ -34,12 +34,12 @@ module CloudSeed current_user.id, project.id, { - 'google_oauth2_token': google_oauth2_token, - 'gcp_project_id': gcp_project_id, - 'instance_name': instance_name, - 'database_version': database_version, - 'environment_name': environment_name, - 'is_protected': protected? + google_oauth2_token: google_oauth2_token, + gcp_project_id: gcp_project_id, + instance_name: instance_name, + database_version: database_version, + environment_name: environment_name, + is_protected: protected? } ) end diff --git a/app/services/cohorts_service.rb b/app/services/cohorts_service.rb index 1b1598b301c..8adc8d3913e 100644 --- a/app/services/cohorts_service.rb +++ b/app/services/cohorts_service.rb @@ -57,7 +57,7 @@ class CohortsService month_totals = all_months .map { |activity_month| counts_by_month[[registration_month, activity_month]] } - .reduce([]) { |result, total| result << result.last.to_i + total.to_i } + .reduce([]) { |result, total| result << (result.last.to_i + total.to_i) } .reverse overall_total = month_totals.first diff --git a/app/services/concerns/validates_classification_label.rb b/app/services/concerns/validates_classification_label.rb index ebcf5c24ff8..35bbfe7fdbe 100644 --- a/app/services/concerns/validates_classification_label.rb +++ b/app/services/concerns/validates_classification_label.rb @@ -18,7 +18,7 @@ module ValidatesClassificationLabel def rejection_reason_for_label(label) reason_from_service = ::Gitlab::ExternalAuthorization.rejection_reason(current_user, label).presence - reason_from_service || _("Access to '%{classification_label}' not allowed") % { classification_label: label } + reason_from_service || (_("Access to '%{classification_label}' not allowed") % { classification_label: label }) end def classification_label_change?(record, attribute_name) diff --git a/app/services/groups/group_links/destroy_service.rb b/app/services/groups/group_links/destroy_service.rb index 8eed46b28ca..b2de99c86c0 100644 --- a/app/services/groups/group_links/destroy_service.rb +++ b/app/services/groups/group_links/destroy_service.rb @@ -4,7 +4,7 @@ module Groups module GroupLinks class DestroyService < ::Groups::BaseService def execute(one_or_more_links, skip_authorization: false) - unless skip_authorization || group && can?(current_user, :admin_group_member, group) + unless skip_authorization || (group && can?(current_user, :admin_group_member, group)) return error('Not Found', 404) end diff --git a/app/services/issuable_links/create_service.rb b/app/services/issuable_links/create_service.rb index c855a58522c..77492cc850e 100644 --- a/app/services/issuable_links/create_service.rb +++ b/app/services/issuable_links/create_service.rb @@ -79,10 +79,10 @@ module IssuableLinks link = relate_issuables(referenced_object) if link.errors.any? - @errors << _("%{ref} cannot be added: %{error}") % { + @errors << (_("%{ref} cannot be added: %{error}") % { ref: referenced_object.to_reference, error: link.errors.messages.values.flatten.to_sentence - } + }) else after_create_for(link) end diff --git a/app/services/issues/relative_position_rebalancing_service.rb b/app/services/issues/relative_position_rebalancing_service.rb index e165cb36634..dad20fb66c0 100644 --- a/app/services/issues/relative_position_rebalancing_service.rb +++ b/app/services/issues/relative_position_rebalancing_service.rb @@ -161,7 +161,7 @@ module Issues end def start_position - @start_position ||= (RelativePositioning::START_POSITION - (gaps / 2) * gap_size).to_i + @start_position ||= (RelativePositioning::START_POSITION - ((gaps / 2) * gap_size)).to_i end def with_retry(initial_batch_size, exit_batch_size) diff --git a/app/services/jira/requests/base.rb b/app/services/jira/requests/base.rb index 5197e6d47c3..a0a3fecb61b 100644 --- a/app/services/jira/requests/base.rb +++ b/app/services/jira/requests/base.rb @@ -83,7 +83,7 @@ module Jira def error_message(error) reportable_error_message(error) || - s_('JiraRequest|An error occurred while requesting data from Jira. Check your %{docs_link_start}Jira integration configuration%{docs_link_end} and try again.').html_safe % { docs_link_start: config_docs_link_start, docs_link_end: ''.html_safe } + (s_('JiraRequest|An error occurred while requesting data from Jira. Check your %{docs_link_start}Jira integration configuration%{docs_link_end} and try again.').html_safe % { docs_link_start: config_docs_link_start, docs_link_end: ''.html_safe }) end # Returns a user-facing error message if possible, otherwise `nil`. @@ -113,7 +113,7 @@ module Jira when 'Forbidden' s_('JiraRequest|The credentials for accessing Jira are not allowed to access the data. Check your %{docs_link_start}Jira integration credentials%{docs_link_end} and try again.').html_safe % { docs_link_start: auth_docs_link_start, docs_link_end: ''.html_safe } when 'Bad Request' - jira_ruby_json_error_message(error.response.body) || s_('JiraRequest|An error occurred while requesting data from Jira. Check your %{docs_link_start}Jira integration configuration%{docs_link_end} and try again.').html_safe % { docs_link_start: config_docs_link_start, docs_link_end: ''.html_safe } + jira_ruby_json_error_message(error.response.body) || (s_('JiraRequest|An error occurred while requesting data from Jira. Check your %{docs_link_start}Jira integration configuration%{docs_link_end} and try again.').html_safe % { docs_link_start: config_docs_link_start, docs_link_end: ''.html_safe }) end end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 738cf920b2b..e0c445b2110 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -938,7 +938,7 @@ Settings['sidekiq']['routing_rules'] = Settings.build_sidekiq_routing_rules(Sett # GitLab Shell # Settings['gitlab_shell'] ||= {} -Settings.gitlab_shell['path'] = Settings.absolute(Settings.gitlab_shell['path'] || Settings.gitlab['user_home'] + '/gitlab-shell/') +Settings.gitlab_shell['path'] = Settings.absolute(Settings.gitlab_shell['path'] || (Settings.gitlab['user_home'] + '/gitlab-shell/')) Settings.gitlab_shell['hooks_path'] = :deprecated_use_gitlab_shell_path_instead Settings.gitlab_shell['authorized_keys_file'] ||= File.join(Dir.home, '.ssh', 'authorized_keys') Settings.gitlab_shell['secret_file'] ||= Rails.root.join('.gitlab_shell_secret') diff --git a/config/initializers/carrierwave_s3_encryption_headers_patch.rb b/config/initializers/carrierwave_s3_encryption_headers_patch.rb index 449f4b4a607..69be80bcdf7 100644 --- a/config/initializers/carrierwave_s3_encryption_headers_patch.rb +++ b/config/initializers/carrierwave_s3_encryption_headers_patch.rb @@ -47,7 +47,7 @@ module CarrierWave # avoid a get by using local references local_directory = connection.directories.new(key: @uploader.fog_directory) local_file = local_directory.files.new(key: path) - expire_at = options[:expire_at] || ::Fog::Time.now + @uploader.fog_authenticated_url_expiration + expire_at = options[:expire_at] || (::Fog::Time.now + @uploader.fog_authenticated_url_expiration) case @uploader.fog_credentials[:provider] when 'AWS', 'Google', 'AzureRM' local_file.url(expire_at, options) diff --git a/config/initializers/kaminari_active_record_relation_methods_with_limit.rb b/config/initializers/kaminari_active_record_relation_methods_with_limit.rb index 3b7fc46c2c0..741e4c3f6aa 100644 --- a/config/initializers/kaminari_active_record_relation_methods_with_limit.rb +++ b/config/initializers/kaminari_active_record_relation_methods_with_limit.rb @@ -18,7 +18,7 @@ module Kaminari # Total count has to be 0 if loaded records are 0 return @total_count = 0 if (current_page == 1) && @records.empty? # Total count is calculable at the last page - return @total_count = (current_page - 1) * limit_value + @records.length if @records.any? && (@records.length < limit_value) + return @total_count = ((current_page - 1) * limit_value) + @records.length if @records.any? && (@records.length < limit_value) end limit = options.fetch(:limit, MAX_COUNT_LIMIT).to_i diff --git a/data/whats_new/202205220001_15_0.yml b/data/whats_new/202205220001_15_0.yml index 112154d9352..019644c8f43 100644 --- a/data/whats_new/202205220001_15_0.yml +++ b/data/whats_new/202205220001_15_0.yml @@ -90,7 +90,7 @@ release: 15.0 # XX.Y - name: Limited Availability GitLab SaaS runners on macOS (x86-64) # Match the release post entry description: | # Do not modify this line, instead modify the lines below. - GitLab SaaS runners on macOS are now in [Limited Availability](https://about.gitlab.com/handbook/product/gitlab-the-product/#limited-availability-la). If you use GitLab SaaS and have a Premium or Ultimate subscription, you can build applications that require macOS in a secure, on-demand GitLab Runner build environment that's fully integrated with GitLab CI/CD. As part of the Limited Availability release, CI jobs that run on the macOS runners will count toward your CI/CD minutes quota at a [cost factor](https://docs.gitlab.com/ee/ci/pipelines/cicd_minutes.html#cost-factor) of 6. + GitLab SaaS runners on macOS are now in [Limited Availability](https://about.gitlab.com/handbook/product/gitlab-the-product/#limited-availability-la). If you use GitLab SaaS and have a Premium or Ultimate subscription, you can build applications that require macOS in a secure, on-demand GitLab Runner build environment that's fully integrated with GitLab CI/CD. As part of the Limited Availability release, CI jobs that run on the macOS runners will count toward your CI/CD minutes quota at a [cost factor](https://docs.gitlab.com/ee/ci/pipelines/compute_minutes.html#cost-factor) of 6. stage: verify # String value of the stage that the feature was created in. e.g., Growth self-managed: false # Boolean value (true or false) gitlab-com: true # Boolean value (true or false) diff --git a/data/whats_new/202206220001_15_1.yml b/data/whats_new/202206220001_15_1.yml index eae52721e69..9dbc027a28a 100644 --- a/data/whats_new/202206220001_15_1.yml +++ b/data/whats_new/202206220001_15_1.yml @@ -36,7 +36,7 @@ [Supply-chain Levels for Software Artifacts (SLSA)](https://github.com/slsa-framework/slsa) is a security framework that helps ensure the security and integrity of your software supply chain. By default, GitLab Runner is now capable of generating and producing SLSA-2 compliant attestation metadata for build artifacts. If the artifact is stored in a registry, then the attestation metadata is stored alongside the artifact in that registry. Otherwise, the metadata is in rendered in a plain text `.json` file that's stored with the artifact. This new attestation information can help you more easily verify that your build artifacts have not been tampered with. To enable this feature, simply set `RUNNER_GENERATE_ARTIFACTS_METADATA = "true"` in your `.gitlab-ci.yml` file. - As part of the Limited Availability release, CI jobs that run on the macOS runners will count toward your CI/CD minutes quota at a [cost factor](https://docs.gitlab.com/ee/ci/pipelines/cicd_minutes.html#cost-factor) of 6. + As part of the Limited Availability release, CI jobs that run on the macOS runners will count toward your CI/CD minutes quota at a [cost factor](https://docs.gitlab.com/ee/ci/pipelines/compute_minutes.html#cost-factor) of 6. stage: verify self-managed: true gitlab-com: true diff --git a/data/whats_new/202305220001_16_0.yml b/data/whats_new/202305220001_16_0.yml index c911d9ef356..f2645cb1c36 100644 --- a/data/whats_new/202305220001_16_0.yml +++ b/data/whats_new/202305220001_16_0.yml @@ -16,7 +16,7 @@ - name: Upsizing GitLab SaaS runners on Linux description: | # Do not modify this line, instead modify the lines below. - You asked, we listened! In our efforts to be best-in-class for CI/CD build speeds, we're doubling the vCPU & RAM for all GitLab SaaS runners on Linux, with no increase in the [cost factor](https://docs.gitlab.com/ee/ci/pipelines/cicd_minutes.html#cost-factor). + You asked, we listened! In our efforts to be best-in-class for CI/CD build speeds, we're doubling the vCPU & RAM for all GitLab SaaS runners on Linux, with no increase in the [cost factor](https://docs.gitlab.com/ee/ci/pipelines/compute_minutes.html#cost-factor). We're excited to see pipelines run faster and boost productivity. stage: Verify diff --git a/db/docs/batched_background_migrations/backfill_related_epic_links_to_issue_links.yml b/db/docs/batched_background_migrations/backfill_related_epic_links_to_issue_links.yml new file mode 100644 index 00000000000..f704e7f9896 --- /dev/null +++ b/db/docs/batched_background_migrations/backfill_related_epic_links_to_issue_links.yml @@ -0,0 +1,9 @@ +--- +migration_job_name: BackfillRelatedEpicLinksToIssueLinks +description: Creates a record on issue_links table for every related_epic_links record +feature_category: team_planning +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/152104 +milestone: '17.1' +queued_migration_version: 20240517131715 +finalize_after: '2024-06-13' +finalized_by: # version of the migration that finalized this BBM diff --git a/db/post_migrate/20240517131715_queue_backfill_related_epic_links_to_issue_links.rb b/db/post_migrate/20240517131715_queue_backfill_related_epic_links_to_issue_links.rb new file mode 100644 index 00000000000..573b5809b13 --- /dev/null +++ b/db/post_migrate/20240517131715_queue_backfill_related_epic_links_to_issue_links.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class QueueBackfillRelatedEpicLinksToIssueLinks < Gitlab::Database::Migration[2.2] + milestone '17.0' + + restrict_gitlab_migration gitlab_schema: :gitlab_main + + MIGRATION = "BackfillRelatedEpicLinksToIssueLinks" + DELAY_INTERVAL = 2.minutes + BATCH_SIZE = 1000 + SUB_BATCH_SIZE = 100 + + def up + queue_batched_background_migration( + MIGRATION, + :related_epic_links, + :id, + job_interval: DELAY_INTERVAL, + batch_size: BATCH_SIZE, + sub_batch_size: SUB_BATCH_SIZE + ) + end + + def down + delete_batched_background_migration(MIGRATION, :related_epic_links, :id, []) + end +end diff --git a/db/schema_migrations/20240517131715 b/db/schema_migrations/20240517131715 new file mode 100644 index 00000000000..d4ab28dec99 --- /dev/null +++ b/db/schema_migrations/20240517131715 @@ -0,0 +1 @@ +672eaf2d05154e166aef1d7d9b13100bb33d599172926eb37d07b69933bc33bc \ No newline at end of file diff --git a/doc/architecture/blueprints/pipeline_execution_policy/index.md b/doc/architecture/blueprints/pipeline_execution_policy/index.md index 9044e2d4983..a95f8c0b647 100644 --- a/doc/architecture/blueprints/pipeline_execution_policy/index.md +++ b/doc/architecture/blueprints/pipeline_execution_policy/index.md @@ -1,7 +1,7 @@ --- status: ongoing creation-date: "2023-11-23" -authors: [ "@Andysoiron", "@g.hickman" ] +authors: [ "@Andysoiron", "@g.hickman", "@mcavoj" ] coach: "@fabiopitino" approvers: [ "@g.hickman" ] owning-stage: "~devops::govern" @@ -53,11 +53,14 @@ Known compliance pipelines issues are: ## Proposal Currently, security policies can include multiple scan actions. Each scan action will result in a CI job that will be injected in the project CI pipeline. -The new feature allows you to define custom CI jobs that will be injected into the project CI pipeline as well. We want to generalize the security policy +The new policy type Pipeline Execution Policy allows users to define custom CI jobs that will be injected into the project CI pipeline as well. We want to generalize the security policy approach to provide the same flexibility that [compliance framework](../../../user/group/compliance_frameworks.md) needs. The combination of the 2 features means that security policies can be scope to compliance frameworks and enforce the presence of custom CI jobs. -Like scan execution policies, custom CI jobs can be scoped to certain branch names, branch types or compliance frameworks applied to the project. +Like Scan Execution Policies, Pipeline Execution Policy jobs can be +[scoped](../../../user/application_security/policies/scan-execution-policies.md#policy_scope-scope-type) +to certain compliance frameworks applied to the project. +It should be possible to control when the policy jobs are enforced by using the existing [workflow rules](../../../ci/yaml/workflow.md). Users can leverage one of the predefined security-policy stages to position jobs in the pipeline according to their needs. Transitioning from compliance pipelines to the new feature should be as smooth as possible. @@ -67,25 +70,25 @@ Transitioning from compliance pipelines to the new feature should be as smooth a The Pipeline Execution Policy MVC will allow the transition from compliance pipelines. -- It should be possible to add custom CI YAML to a security policy. The CI YAML should follow the same schema as `.gitlab-ci.yml` files. The custom CI will be merged with the project CI when a pipeline starts. +- It should be possible to add custom CI YAML to a security policy. For simplicity, the custom CI YAML should support the same configuration and follow the same schema as `.gitlab-ci.yml` files. The custom CI will be merged with the project CI when a pipeline starts. - The security policy schema should allow the custom CI to be defined in an external file by allowing a project and file path to be added. -- Scan execution policies should execute custom CI YAML similar to existing policies, by injecting the job into the GitLab CI YAML of the target projects. -- At minimum, pipeline execution policy jobs should align with [existing CI variable precedence](../../../ci/variables/index.md#cicd-variable-precedence). Ideally, all CI variables defined in a scan execution policy job should execute as highest precedence. Specifically, scan execution job variables should take precedence over project, group, and instance variables compliance project variables, among others. -- Jobs should be executed in a way that is visible to users within the pipeline and that will not allow project jobs to override the SEP jobs. In scan execution policies today, we utilize the index pattern (-0,-1,-2,...) to increment the name of the job if a job of the same name exists. This also gives some minor indication of which jobs are executed by a security policy. For custom YAML jobs, the same pattern should be utilized. -- Users should be able to define the stage in which their job will run, and scan execution policies will have a method to handle precedence. For example, a security/compliance team may define want to enforce jobs that run commonly after a build stage. They would be able to use build_after (for example) and scan execution policies would inject the build_after stage after the build stage and enforce the custom CI YAML defined in the pipeline execution policy within this stage. The stage and job cannot be interfered with by development teams once enforced by a scan execution policy. We should define the rules that allow for injecting stages cleanly into all enforced projects but be minimal invasive as to the project CI. -- Pipeline execution policies should execute custom CI YAML in projects that do not contain an existing CI configuration, the same as standard scan execution policies work today. +- Pipeline Execution Policies should execute custom CI YAML by creating jobs in isolated pipelines which are merged into the pipeline of the target projects. +- At minimum, Pipeline Execution Policy jobs should align with [existing CI variable precedence](../../../ci/variables/index.md#cicd-variable-precedence). + Ideally, Pipeline Execution Policy jobs should not get any user-defined variables except those defined in the group or project where the policy belongs. +- Jobs should be executed in a way that is visible to users within the pipeline and that will not allow project jobs to override the policy jobs. In Scan Execution Policies today, we utilize the index pattern (-0,-1,-2,...) to increment the name of the job if a job of the same name exists. This also gives some minor indication of which jobs are executed by a security policy. For Pipeline Execution Policy jobs, the same pattern should be utilized. +- Jobs coming from the policies should be marked as such in the database so that they can be distinguished, for example by using build metadata. This allows for different handling of jobs and the corresponding variables by the runner. +- Users should be able to define the stage in which their job will run, and Pipeline Execution Policies will have a method to handle precedence. For example, a security/compliance team may want to enforce jobs that run commonly after a build stage. The stages and jobs must not interfere with those defined by development teams once enforced by a Pipeline Execution Policy. +- Pipeline Execution Policies should allow for jobs to be enforced in projects that do not contain an existing CI configuration. ### Stages management strategy We want users to be able to place jobs to run before or after certain CI stages of the project pipeline. -To achieve this, we want to introduce 3 reserved stages that can be used only by pipeline execution policies and injected into the project pipeline: +To achieve this, we want to introduce 2 reserved stages that can be used only by pipeline execution policies and injected into the project pipeline: 1. `.pipeline-policy-pre` stage will run at the very beginning of the pipeline, before the `.pre` stage. -1. `.pipeline-policy` stage will be injected after the `test` stage. If the `test` stage does not exist, it will be injected after the `build` stage. If the `build` stage does not exist, it will be injected at the beginning of the pipeline after the `.pre` stage. 1. `.pipeline-policy-post` stage will run at the very end of the pipeline, after the `.post` stage. -Injecting jobs in any of these 3 stages is guaranteed to always work. Execution policy jobs can also be assigned to any stage that exists in the project pipeline. In this case, however, it's not guaranteed that the injection always works as it depends whether the project pipeline has declared such stage. -It will not be possible to create custom stages in a pipeline execution policy. +Injecting jobs in any of these stages is guaranteed to always work. Execution policy jobs can also be assigned to any standard (`build`, `test`, `deploy`) or user-declared stages. However, in this case, the jobs may be ignored depending on the project pipeline configuration. We will try this approach as part of the experiment phase. We also discussed the following strategies that we might want to try: @@ -133,4 +136,5 @@ If the `test` stage doesn't exist, it will be injected after the `build` stage. ## Links -- [Pipeline execution policy MVC epic](https://gitlab.com/groups/gitlab-org/-/epics/7312) +- [Pipeline Execution Policy Type](https://gitlab.com/groups/gitlab-org/-/epics/13266#top) +- [Pipeline Execution Action (Custom CI YAML Support) for Scan Execution Policy Type](https://gitlab.com/groups/gitlab-org/-/epics/7312) diff --git a/doc/development/fe_guide/style/scss.md b/doc/development/fe_guide/style/scss.md index 47b6fd002ae..ebb6e83fe4b 100644 --- a/doc/development/fe_guide/style/scss.md +++ b/doc/development/fe_guide/style/scss.md @@ -35,6 +35,43 @@ We are in the process of migrating our CSS utility class setup to [Tailwind CSS] See the [Tailwind CSS blueprint](../../../architecture/blueprints/tailwindcss/index.md) for motivation, proposal, and implementation details. +#### Tailwind CSS basics + +Below are some Tailwind CSS basics and information about how it has been configured to use the [Pajamas design system](https://design.gitlab.com/). For a more in-depth guide see the [official Tailwind CSS documentation](https://tailwindcss.com/docs/utility-first). + +##### Prefix + +We have configured Tailwind CSS to use a [prefix](https://tailwindcss.com/docs/configuration#prefix) so all utility classes are prefixed with `gl-`. +When using responsive utilities or state modifiers the prefix goes after the colon. +**Examples:** `gl-mt-5`, `lg:gl-mt-5`. + +##### Responsive CSS utility classes + +[Responsive CSS utility classes](https://tailwindcss.com/docs/responsive-design) are prefixed with the breakpoint name, followed by the `:` character. +The available breakpoints are configured in [tailwind.defaults.js#L44](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/6612eaee37cdb4dd0258468c9f415be28c1053f0/tailwind.defaults.js#L44) +**Example:** `lg:gl-mt-5` + +##### Hover, focus, and other state modifiers + +[State modifiers](https://tailwindcss.com/docs/hover-focus-and-other-states) can be used to conditionally apply any Tailwind CSS class. Prefix the CSS utility class with the name of the modifier, followed by the `:` character. +**Example:** `hover:gl-underline` + +##### `!important` modifier + +You can use the [important modifier](https://tailwindcss.com/docs/configuration#important-modifier) by adding `!` to the beginning of the CSS utility class. When using in conjunction with responsive utility classes or state modifiers the `!` goes after the `:` character. +**Examples:** `!gl-mt-5`, `lg:!gl-mt-5`, `hover:!gl-underline` + +##### Spacing and sizing CSS utility classes + +Spacing and sizing CSS utility classes (e.g. `margin`, `padding`, `width`, `height`) use our spacing scale defined in +[tailwind.defaults.js#L4](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/6612eaee37cdb4dd0258468c9f415be28c1053f0/tailwind.defaults.js#L4). They will use the naming conventions documented in the [official Tailwind CSS documentation](https://tailwindcss.com/docs/installation) but the scale will not match. When using the [Tailwind CSS autocomplete](#tailwind-css-autocomplete) our configured spacing scale will be shown. +**Example:** `gl-mt-5` will be `margin-top: 1rem;` + +##### Color CSS utility classes + +Color CSS utility classes (e.g. `color` and `background-color`) use colors defined in [src/tokens/build/tailwind/tokens.cjs](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/24a08b50da6bd3d34fb3f8d24f84436d90d165f6/src/tokens/build/tailwind/tokens.cjs). They will use the naming conventions documented in the [official Tailwind CSS documentation](https://tailwindcss.com/docs/installation) but the color names will not match. When using the [Tailwind CSS autocomplete](#tailwind-css-autocomplete) our configured colors will be shown. +**Example:** `gl-text-red-500` will be `color: var(--red-500, #dd2b0e);` + #### Building the Tailwind CSS bundle When using Vite or Webpack with the GitLab Development Kit, Tailwind CSS watches for file changes to @@ -92,7 +129,7 @@ For full HAML and custom `*-class` prop support these are the recommended update #### Official Tailwind CSS documentation -GitLab defines its own Tailwind CSS config in [https://gitlab.com/gitlab-org/gitlab-ui/-/blob/main/tailwind.defaults.js](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/main/tailwind.defaults.js) to match the Pajamas design system and to prefix CSS utility classes with `gl-`. This means that in the [official Tailwind CSS documentation](https://tailwindcss.com/docs/installation) the spacing, sizing, and color CSS utility classes may not match. Also, the `gl-` prefix will not be shown. Here is our [spacing scale](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/main/tailwind.defaults.js#L51) and [colors](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/main/tailwind.defaults.js#L8). In the future we plan to utilize [Tailwind config viewer](https://github.com/rogden/tailwind-config-viewer) to have a Tailwind CSS documentation site specific to GitLab. +GitLab defines its own Tailwind CSS config in [tailwind.defaults.js](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/6612eaee37cdb4dd0258468c9f415be28c1053f0/tailwind.defaults.js) to match the Pajamas design system and to prefix CSS utility classes with `gl-`. This means that in the [official Tailwind CSS documentation](https://tailwindcss.com/docs/installation) the spacing, sizing, and color CSS utility classes may not match. Also, the `gl-` prefix will not be shown. Here is our [spacing scale](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/6612eaee37cdb4dd0258468c9f415be28c1053f0/tailwind.defaults.js#L4) and [colors](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/24a08b50da6bd3d34fb3f8d24f84436d90d165f6/src/tokens/build/tailwind/tokens.cjs). In the future we plan to utilize [Tailwind config viewer](https://github.com/rogden/tailwind-config-viewer) to have a Tailwind CSS documentation site specific to GitLab. ### Where should you put new utility classes? diff --git a/lib/gitlab/background_migration/backfill_related_epic_links_to_issue_links.rb b/lib/gitlab/background_migration/backfill_related_epic_links_to_issue_links.rb new file mode 100644 index 00000000000..45680e48604 --- /dev/null +++ b/lib/gitlab/background_migration/backfill_related_epic_links_to_issue_links.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + class BackfillRelatedEpicLinksToIssueLinks < BatchedMigrationJob + operation_name :backfill_issue_links_with_related_epic_links + feature_category :team_planning + + def perform + each_sub_batch do |sub_batch| + values_subquery = sub_batch.select(select_fields_to_insert_sql) + values_subquery = values_subquery.joins(joins_target_and_source_epic_sql) + + connection.execute(<<~SQL) + INSERT INTO issue_links (source_id, target_id, link_type, created_at, updated_at) + #{values_subquery.to_sql} + ON CONFLICT (source_id, target_id) + DO UPDATE SET + link_type = EXCLUDED.link_type, + created_at = EXCLUDED.created_at, + updated_at = EXCLUDED.updated_at + SQL + end + end + + private + + def select_fields_to_insert_sql + <<~SQL + source_epics.issue_id AS source_id, + target_epics.issue_id AS target_id, + related_epic_links.link_type, + related_epic_links.created_at AT TIME ZONE '#{Time.zone.tzinfo.name}' AS created_at, + related_epic_links.updated_at AT TIME ZONE '#{Time.zone.tzinfo.name}' AS updated_at + SQL + end + + def joins_target_and_source_epic_sql + <<~SQL + INNER JOIN epics source_epics ON related_epic_links.source_id = source_epics.id + INNER JOIN epics target_epics ON related_epic_links.target_id = target_epics.id + SQL + end + end + end +end diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index ae45269e08c..8a58b6f8d92 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -361,8 +361,8 @@ module Gitlab clusters: distinct_count(::Clusters::Cluster.where(time_period), :user_id), clusters_integrations_prometheus: cluster_integrations_user_distinct_count(::Clusters::Integrations::Prometheus, time_period), operations_dashboard_default_dashboard: count(::User.active.with_dashboard('operations').where(time_period), - start: minimum_id(User), - finish: maximum_id(User)), + start: minimum_id(User), + finish: maximum_id(User)), projects_with_error_tracking_enabled: distinct_count(::Project.with_enabled_error_tracking.where(time_period), :creator_id), projects_with_incidents: distinct_count(::Issue.with_issue_type(:incident).where(time_period), :project_id), # We are making an assumption here that all alert_management_alerts are associated with an issue of type diff --git a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb index db48095ab74..7277e016b07 100644 --- a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb +++ b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb @@ -95,7 +95,7 @@ module Gitlab property: MR_APPROVE_ACTION, label: 'redis_hll_counters.code_review.i_code_review_user_approve_mr_monthly', context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, - event: MR_APPROVE_ACTION).to_context] + event: MR_APPROVE_ACTION).to_context] ) end diff --git a/lib/peek/views/detailed_view.rb b/lib/peek/views/detailed_view.rb index c37c6bb8561..feaeed8d87a 100644 --- a/lib/peek/views/detailed_view.rb +++ b/lib/peek/views/detailed_view.rb @@ -51,7 +51,7 @@ module Peek duration = (call[:duration] * 1000).round(3) call.merge(duration: duration, - warnings: warning_for(duration, self.class.thresholds[:individual_call])) + warnings: warning_for(duration, self.class.thresholds[:individual_call])) end def warning_for(actual, threshold, label: nil) diff --git a/lib/peek/views/redis_detailed.rb b/lib/peek/views/redis_detailed.rb index 6b5c4b37c9d..cc9673f3032 100644 --- a/lib/peek/views/redis_detailed.rb +++ b/lib/peek/views/redis_detailed.rb @@ -20,7 +20,7 @@ module Peek cmd = call[:commands].map { |command| command.join(' ') }.join(', ') super.merge(cmd: cmd, - instance: call[:storage]) + instance: call[:storage]) end def format_command(cmd) diff --git a/lib/tasks/gems.rake b/lib/tasks/gems.rake index 867adc0c449..4f1df168594 100644 --- a/lib/tasks/gems.rake +++ b/lib/tasks/gems.rake @@ -36,12 +36,12 @@ namespace :gems do user_id = File.stat(vendor_gem_dir).uid Kernel.system('docker', 'run', - "--user=#{user_id}", '--rm', "--volume=#{vendor_gem_dir}:/code", docker_image, - 'generate', - '--input-spec', api_url, - '--generator-name', 'ruby', - '--output', "/code/#{gem_name}", - "--additional-properties=moduleName=#{module_name}" + "--user=#{user_id}", '--rm', "--volume=#{vendor_gem_dir}:/code", docker_image, + 'generate', + '--input-spec', api_url, + '--generator-name', 'ruby', + '--output', "/code/#{gem_name}", + "--additional-properties=moduleName=#{module_name}" ) end diff --git a/lib/tasks/gitlab/feature_categories.rake b/lib/tasks/gitlab/feature_categories.rake index db496012158..b1772cc14f4 100644 --- a/lib/tasks/gitlab/feature_categories.rake +++ b/lib/tasks/gitlab/feature_categories.rake @@ -55,9 +55,9 @@ namespace :gitlab do end puts YAML.dump('controller_actions' => controller_actions, - 'api_endpoints' => endpoints, - 'sidekiq_workers' => workers, - 'database_tables' => database_tables) + 'api_endpoints' => endpoints, + 'sidekiq_workers' => workers, + 'database_tables' => database_tables) end private diff --git a/lib/tasks/gitlab/seed.rake b/lib/tasks/gitlab/seed.rake index 9cc6b906aba..67de6b0e793 100644 --- a/lib/tasks/gitlab/seed.rake +++ b/lib/tasks/gitlab/seed.rake @@ -37,7 +37,7 @@ namespace :gitlab do puts "\nSeeding issues for the '#{project.full_path}' project" seeder = Quality::Seeders::Issues.new(project: project) issues_created = seeder.seed(backfill_weeks: args.backfill_weeks.to_i, - average_issues_per_week: args.average_issues_per_week.to_i) + average_issues_per_week: args.average_issues_per_week.to_i) puts "\n#{issues_created} issues created!" end end diff --git a/lib/tasks/gitlab/seed/runner_fleet.rake b/lib/tasks/gitlab/seed/runner_fleet.rake index 784451226b7..9ae88a30bf5 100644 --- a/lib/tasks/gitlab/seed/runner_fleet.rake +++ b/lib/tasks/gitlab/seed/runner_fleet.rake @@ -19,7 +19,7 @@ namespace :gitlab do namespace :seed do desc 'Seed groups with sub-groups/projects/runners/jobs for Runner Fleet testing' task :runner_fleet, - [:username, :registration_prefix, :runner_count, :job_count] => :gitlab_environment do |_t, args| + [:username, :registration_prefix, :runner_count, :job_count] => :gitlab_environment do |_t, args| timings = Benchmark.measure do projects_to_runners = Gitlab::Seeders::Ci::Runner::RunnerFleetSeeder.new( Gitlab::AppLogger, diff --git a/lib/tasks/gitlab/uploads/sanitize.rake b/lib/tasks/gitlab/uploads/sanitize.rake index 40f6a7bb67d..37d468d9391 100644 --- a/lib/tasks/gitlab/uploads/sanitize.rake +++ b/lib/tasks/gitlab/uploads/sanitize.rake @@ -12,10 +12,10 @@ namespace :gitlab do sanitizer = Gitlab::Sanitizers::Exif.new(logger: logger) sanitizer.batch_clean(start_id: args.start_id, stop_id: args.stop_id, - dry_run: args.dry_run != 'false', - sleep_time: args.sleep_time.to_f, - uploader: args.uploader, - since: args.since) + dry_run: args.dry_run != 'false', + sleep_time: args.sleep_time.to_f, + uploader: args.uploader, + since: args.since) end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5e928c1bb2d..0dd41e89d1f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9212,7 +9212,7 @@ msgstr "" msgid "BranchRules|Update target branch" msgstr "" -msgid "BranchRules|Users" +msgid "BranchRules|Users & groups" msgstr "" msgid "BranchRules|View details" @@ -24685,9 +24685,6 @@ msgstr "" msgid "Group %{group_name} was exported successfully." msgstr "" -msgid "Group %{group_name} was scheduled for deletion." -msgstr "" - msgid "Group %{group_name} was successfully created." msgstr "" @@ -24697,6 +24694,9 @@ msgstr "" msgid "Group '%{group_name}' has been successfully restored." msgstr "" +msgid "Group '%{group_name}' is being deleted." +msgstr "" + msgid "Group '%{group_name}' was successfully updated." msgstr "" @@ -33148,6 +33148,9 @@ msgstr "" msgid "MlModelRegistry|Create model version & import artifacts" msgstr "" +msgid "MlModelRegistry|Create model, version & import artifacts" +msgstr "" + msgid "MlModelRegistry|Creating models is also possible through the MLflow client. %{linkStart}Follow the documentation to learn more.%{linkEnd}" msgstr "" @@ -33178,9 +33181,15 @@ msgstr "" msgid "MlModelRegistry|Enter some description" msgstr "" +msgid "MlModelRegistry|Enter some model description" +msgstr "" + msgid "MlModelRegistry|Error creating model version and uploading artifacts. Please try again." msgstr "" +msgid "MlModelRegistry|Error creating model, version and uploading artifacts. Please try again." +msgstr "" + msgid "MlModelRegistry|Error importing artifact. Please try again." msgstr "" @@ -33202,18 +33211,27 @@ msgstr "" msgid "MlModelRegistry|For example 1.0.0" msgstr "" +msgid "MlModelRegistry|For example my-model" +msgstr "" + msgid "MlModelRegistry|ID" msgstr "" msgid "MlModelRegistry|Info" msgstr "" +msgid "MlModelRegistry|Initial version name. Must be a semantic version." +msgstr "" + msgid "MlModelRegistry|Latest version" msgstr "" msgid "MlModelRegistry|Leave empty to auto increment." msgstr "" +msgid "MlModelRegistry|Leave empty to skip version creation." +msgstr "" + msgid "MlModelRegistry|MLflow run ID" msgstr "" @@ -33226,6 +33244,15 @@ msgstr "" msgid "MlModelRegistry|Model deleted successfully" msgstr "" +msgid "MlModelRegistry|Model has been created but version or artifacts could not be uploaded. Try creating model version." +msgstr "" + +msgid "MlModelRegistry|Model name" +msgstr "" + +msgid "MlModelRegistry|Model name must not contain spaces or upper case letter." +msgstr "" + msgid "MlModelRegistry|Model performance" msgstr "" @@ -33277,6 +33304,9 @@ msgstr "" msgid "MlModelRegistry|Use versions to track performance, parameters, and metadata" msgstr "" +msgid "MlModelRegistry|Version Description" +msgstr "" + msgid "MlModelRegistry|Version candidates" msgstr "" @@ -40264,10 +40294,10 @@ msgstr "" msgid "Project '%{project_name}' has been successfully restored." msgstr "" -msgid "Project '%{project_name}' is being imported." +msgid "Project '%{project_name}' is being deleted." msgstr "" -msgid "Project '%{project_name}' is in the process of being deleted." +msgid "Project '%{project_name}' is being imported." msgstr "" msgid "Project '%{project_name}' queued for deletion." @@ -44433,12 +44463,6 @@ msgid_plural "Runners|%{highlightStart}%{duration}%{highlightEnd} seconds" msgstr[0] "" msgstr[1] "" -msgid "Runners|%{installLinkStart}Upgrade GitLab Runner%{installLinkEnd} to match your GitLab version. %{versionLinkStart}Major and minor versions%{versionLinkEnd} must match." -msgstr "" - -msgid "Runners|%{installLinkStart}Upgrade GitLab Runner%{installLinkEnd} to match your GitLab version. This upgrade is highly recommended for this runner and may contain security or compatibilty fixes. %{versionLinkStart}Major and minor versions%{versionLinkEnd} must match." -msgstr "" - msgid "Runners|%{linkStart}Create a new runner%{linkEnd} to get started." msgstr "" @@ -44464,6 +44488,12 @@ msgid_plural "Runners|%{strongStart}%{count}%{strongEnd} runners will be permane msgstr[0] "" msgstr[1] "" +msgid "Runners|%{upgradeLinkStart}Upgrade GitLab Runner%{upgradeLinkEnd} to match your GitLab version. %{versionLinkStart}Major and minor versions%{versionLinkEnd} must match." +msgstr "" + +msgid "Runners|%{upgradeLinkStart}Upgrade GitLab Runner%{upgradeLinkEnd} to match your GitLab version. This upgrade is highly recommended for this runner and may contain security or compatibilty fixes. %{versionLinkStart}Major and minor versions%{versionLinkEnd} must match." +msgstr "" + msgid "Runners|1. Configure your Google Cloud project" msgstr "" diff --git a/rubocop/cop/gitlab/finder_with_find_by.rb b/rubocop/cop/gitlab/finder_with_find_by.rb index e6d190e4476..9bfba70dd10 100644 --- a/rubocop/cop/gitlab/finder_with_find_by.rb +++ b/rubocop/cop/gitlab/finder_with_find_by.rb @@ -27,7 +27,7 @@ module RuboCop before_execute = node.descendants[1].source_range range_to_remove = node.source_range .with(begin_pos: before_execute.end_pos, - end_pos: upto_including_execute.end_pos) + end_pos: upto_including_execute.end_pos) corrector.remove(range_to_remove) end diff --git a/rubocop/cop/qa/ambiguous_page_object_name.rb b/rubocop/cop/qa/ambiguous_page_object_name.rb index a331851bcc5..887352507bf 100644 --- a/rubocop/cop/qa/ambiguous_page_object_name.rb +++ b/rubocop/cop/qa/ambiguous_page_object_name.rb @@ -34,7 +34,7 @@ module RuboCop return unless ambiguous_page?(node) add_offense(node.arguments.each_node(:arg).first, - message: MESSAGE % page_object_name(node)) + message: MESSAGE % page_object_name(node)) end private diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index 41198d2e1d6..bb34299996d 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -111,6 +111,7 @@ RSpec.describe Admin::GroupsController, feature_category: :groups_and_projects d it 'redirects to the admin group path' do delete :destroy, params: { id: project.group.path } + expect(flash[:toast]).to eq(format(_("Group '%{group_name}' is being deleted."), group_name: group.full_name)) expect(response).to redirect_to(admin_groups_path) end end diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb index 95986b5c034..fd27b87436f 100644 --- a/spec/controllers/admin/projects_controller_spec.rb +++ b/spec/controllers/admin/projects_controller_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' -RSpec.describe Admin::ProjectsController do - let!(:project) { create(:project, :public) } +RSpec.describe Admin::ProjectsController, feature_category: :groups_and_projects do + let_it_be(:project) { create(:project, :public) } before do sign_in(create(:admin)) @@ -107,4 +107,13 @@ RSpec.describe Admin::ProjectsController do end end end + + describe 'DELETE #destroy' do + it 'redirects to the admin projects path and displays the flash toast' do + delete :destroy, params: { namespace_id: project.namespace, id: project } + + expect(flash[:toast]).to eq(format(_("Project '%{project_name}' is being deleted."), project_name: project.full_name)) + expect(response).to redirect_to(admin_projects_path) + end + end end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 224631baf53..4e83b348743 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -698,6 +698,7 @@ RSpec.describe GroupsController, factory_default: :keep, feature_category: :code it 'redirects to the root path' do delete :destroy, params: { id: group.to_param } + expect(flash[:toast]).to eq(format(_("Group '%{group_name}' is being deleted."), group_name: group.full_name)) expect(response).to redirect_to(root_path) end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index e19e1bebc66..da08d1dddf8 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -1083,6 +1083,7 @@ RSpec.describe ProjectsController, feature_category: :groups_and_projects do expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound) expect(response).to have_gitlab_http_status(:found) + expect(flash[:toast]).to eq(format(_("Project '%{project_name}' is being deleted."), project_name: project.full_name)) expect(response).to redirect_to(dashboard_projects_path) end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index d89f852b97e..a3fa05242ef 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -416,7 +416,7 @@ RSpec.describe 'Group', feature_category: :groups_and_projects do it 'removes group', :sidekiq_might_not_need_inline do expect { remove_with_confirm('Delete group', group.path) }.to change { Group.count }.by(-1) expect(group.members.all.count).to be_zero - expect(page).to have_content "scheduled for deletion" + expect(page).to have_content "is being deleted" end end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index cc3cbc64cb3..0da46359fc9 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -286,7 +286,7 @@ RSpec.describe 'Project', feature_category: :source_code_management do it 'deletes a project', :sidekiq_inline do expect { remove_with_confirm('Delete project', project.path_with_namespace, 'Yes, delete project') }.to change { Project.count }.by(-1) - expect(page).to have_content "Project '#{project.full_name}' is in the process of being deleted." + expect(page).to have_content "Project '#{project.full_name}' is being deleted." expect(Project.all.count).to be_zero expect(project.issues).to be_empty expect(project.merge_requests).to be_empty diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js index 07475ea99e9..c37b649e847 100644 --- a/spec/frontend/api_spec.js +++ b/spec/frontend/api_spec.js @@ -205,6 +205,19 @@ describe('Api', () => { }); }); + describe('groupSubgroups', () => { + it('fetches group subgroups', () => { + const groupId = '54321'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/subgroups`; + const expectedData = [{ id: 7 }]; + mock.onGet(expectedUrl).reply(HTTP_STATUS_OK, expectedData); + + return Api.groupSubgroups(groupId).then(({ data }) => { + expect(data).toEqual(expectedData); + }); + }); + }); + describe('inviteGroupMembers', () => { it('invites a new email address to create a new User and become a Group Member', () => { const groupId = 1; diff --git a/spec/frontend/ml/model_registry/apps/index_ml_models_spec.js b/spec/frontend/ml/model_registry/apps/index_ml_models_spec.js index 632316d5066..52d5b2e019d 100644 --- a/spec/frontend/ml/model_registry/apps/index_ml_models_spec.js +++ b/spec/frontend/ml/model_registry/apps/index_ml_models_spec.js @@ -4,6 +4,7 @@ import VueApollo from 'vue-apollo'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import { IndexMlModels } from '~/ml/model_registry/apps'; import ModelRow from '~/ml/model_registry/components/model_row.vue'; +import ModelCreate from '~/ml/model_registry/components/model_create.vue'; import { MODEL_ENTITIES } from '~/ml/model_registry/constants'; import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue'; @@ -58,7 +59,7 @@ describe('ml/model_registry/apps/index_ml_models', () => { const findTitleArea = () => wrapper.findComponent(TitleArea); const findModelCountMetadataItem = () => findTitleArea().findComponent(MetadataItem); const findBadge = () => wrapper.findComponent(GlExperimentBadge); - const findCreateButton = () => wrapper.findByTestId('create-model-button'); + const findModelCreate = () => wrapper.findComponent(ModelCreate); const findActionsDropdown = () => wrapper.findComponent(ActionsDropdown); const findSearchableList = () => wrapper.findComponent(SearchableList); @@ -99,7 +100,7 @@ describe('ml/model_registry/apps/index_ml_models', () => { await waitForPromises(); - expect(findCreateButton().exists()).toBe(false); + expect(findModelCreate().exists()).toBe(false); }); }); @@ -112,7 +113,7 @@ describe('ml/model_registry/apps/index_ml_models', () => { await waitForPromises(); - expect(findCreateButton().attributes().href).toBe('path/to/create'); + expect(findModelCreate().exists()).toBe(true); }); }); }); diff --git a/spec/frontend/ml/model_registry/components/model_create_spec.js b/spec/frontend/ml/model_registry/components/model_create_spec.js new file mode 100644 index 00000000000..73bee31cf62 --- /dev/null +++ b/spec/frontend/ml/model_registry/components/model_create_spec.js @@ -0,0 +1,298 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { GlAlert, GlModal } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import * as Sentry from '~/sentry/sentry_browser_wrapper'; +import { visitUrl } from '~/lib/utils/url_utility'; +import ModelCreate from '~/ml/model_registry/components/model_create.vue'; +import ImportArtifactZone from '~/ml/model_registry/components/import_artifact_zone.vue'; +import { uploadModel } from '~/ml/model_registry/services/upload_model'; +import createModelMutation from '~/ml/model_registry/graphql/mutations/create_model.mutation.graphql'; +import createModelVersionMutation from '~/ml/model_registry/graphql/mutations/create_model_version.mutation.graphql'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; + +import { createModelResponses, createModelVersionResponses } from '../graphql_mock_data'; + +Vue.use(VueApollo); + +jest.mock('~/lib/utils/url_utility', () => ({ + ...jest.requireActual('~/lib/utils/url_utility'), + visitUrl: jest.fn(), +})); + +jest.mock('~/ml/model_registry/services/upload_model', () => ({ + uploadModel: jest.fn(), +})); + +describe('ModelCreate', () => { + let wrapper; + let apolloProvider; + + beforeEach(() => { + jest.spyOn(Sentry, 'captureException').mockImplementation(); + }); + + afterEach(() => { + apolloProvider = null; + }); + + const createWrapper = ( + createModelResolver = jest.fn().mockResolvedValue(createModelResponses.success), + createModelVersionResolver = jest.fn().mockResolvedValue(createModelVersionResponses.success), + ) => { + const requestHandlers = [ + [createModelMutation, createModelResolver], + [createModelVersionMutation, createModelVersionResolver], + ]; + apolloProvider = createMockApollo(requestHandlers); + + wrapper = shallowMountExtended(ModelCreate, { + provide: { + projectPath: 'some/project', + }, + apolloProvider, + }); + }; + + const findModalButton = () => wrapper.findByText('Create model'); + const findNameInput = () => wrapper.findByTestId('nameId'); + const findVersionInput = () => wrapper.findByTestId('versionId'); + const findDescriptionInput = () => wrapper.findByTestId('descriptionId'); + const findVersionDescriptionInput = () => wrapper.findByTestId('versionDescriptionId'); + const findImportArtifactZone = () => wrapper.findComponent(ImportArtifactZone); + const findGlModal = () => wrapper.findComponent(GlModal); + const findGlAlert = () => wrapper.findComponent(GlAlert); + const submitForm = async () => { + findGlModal().vm.$emit('primary', new Event('primary')); + await waitForPromises(); + }; + + describe('Initial state', () => { + beforeEach(() => { + createWrapper(); + }); + + it('renders the modal button', () => { + expect(findModalButton().text()).toBe('Create model'); + }); + + describe('Modal open', () => { + beforeEach(() => { + findModalButton().trigger('click'); + }); + + it('renders the name input', () => { + expect(findNameInput().exists()).toBe(true); + }); + + it('renders the version input', () => { + expect(findVersionInput().exists()).toBe(true); + }); + + it('renders the description input', () => { + expect(findDescriptionInput().exists()).toBe(true); + }); + + it('renders the version description input', () => { + expect(findVersionDescriptionInput().exists()).toBe(true); + }); + + it('renders the import artifact zone input', () => { + expect(findImportArtifactZone().exists()).toBe(false); + }); + + it('renders the import artifact zone input with version entered', async () => { + findNameInput().vm.$emit('input', 'gpt-alice-1'); + findVersionInput().vm.$emit('input', '1.0.0'); + await waitForPromises(); + expect(findImportArtifactZone().props()).toEqual({ + path: null, + submitOnSelect: false, + value: null, + }); + }); + + it('renders the import modal', () => { + expect(findGlModal().props()).toMatchObject({ + modalId: 'create-model-modal', + title: 'Create model, version & import artifacts', + size: 'sm', + }); + }); + + it('renders the cancel button in the modal', () => { + expect(findGlModal().props('actionCancel')).toEqual({ text: 'Cancel' }); + }); + + it('renders the create button in the modal', () => { + expect(findGlModal().props('actionPrimary')).toEqual({ + attributes: { variant: 'confirm' }, + text: 'Create', + }); + }); + + it('does not render the alert by default', () => { + expect(findGlAlert().exists()).toBe(false); + }); + }); + }); + + describe('Successful flow with version', () => { + beforeEach(async () => { + createWrapper(); + findNameInput().vm.$emit('input', 'gpt-alice-1'); + findVersionInput().vm.$emit('input', '1.0.0'); + findDescriptionInput().vm.$emit('input', 'My model description'); + findVersionDescriptionInput().vm.$emit('input', 'My version description'); + jest.spyOn(apolloProvider.defaultClient, 'mutate'); + + await submitForm(); + }); + + it('Makes a create model mutation upon confirm', () => { + expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith( + expect.objectContaining({ + mutation: createModelMutation, + variables: { + projectPath: 'some/project', + name: 'gpt-alice-1', + description: 'My model description', + }, + }), + ); + }); + + it('Makes a create model version mutation upon confirm', () => { + expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith( + expect.objectContaining({ + mutation: createModelVersionMutation, + variables: { + modelId: 'gid://gitlab/Ml::Model/1', + projectPath: 'some/project', + version: '1.0.0', + description: 'My version description', + }, + }), + ); + }); + + it('Uploads a file mutation upon confirm', () => { + expect(uploadModel).toHaveBeenCalledWith({ + file: null, + importPath: '/api/v4/projects/1/packages/ml_models/1/files/', + }); + }); + + it('Visits the model versions page upon successful create mutation', async () => { + createWrapper(); + await submitForm(); + expect(visitUrl).toHaveBeenCalledWith('/some/project/-/ml/models/1/versions/1'); + }); + }); + + describe('Successful flow without version', () => { + beforeEach(async () => { + createWrapper(); + findNameInput().vm.$emit('input', 'gpt-alice-1'); + findDescriptionInput().vm.$emit('input', 'My model description'); + jest.spyOn(apolloProvider.defaultClient, 'mutate'); + + await submitForm(); + }); + + it('Visits the model page upon successful create mutation without a version', async () => { + createWrapper(); + await submitForm(); + expect(visitUrl).toHaveBeenCalledWith('/some/project/-/ml/models/1'); + }); + }); + + describe('Failed flow with version', () => { + beforeEach(async () => { + const failedCreateModelVersionResolver = jest + .fn() + .mockResolvedValue(createModelVersionResponses.failure); + createWrapper(undefined, failedCreateModelVersionResolver); + jest.spyOn(apolloProvider.defaultClient, 'mutate'); + + findNameInput().vm.$emit('input', 'gpt-alice-1'); + findVersionInput().vm.$emit('input', '1.0.0'); + findVersionDescriptionInput().vm.$emit('input', 'My version description'); + await submitForm(); + }); + + it('Displays an alert upon failed model create mutation', () => { + expect(findGlAlert().text()).toBe('Version is invalid'); + }); + }); + + describe('Failed flow with version retried', () => { + beforeEach(async () => { + const failedCreateModelVersionResolver = jest + .fn() + .mockResolvedValueOnce(createModelVersionResponses.failure); + createWrapper(undefined, failedCreateModelVersionResolver); + jest.spyOn(apolloProvider.defaultClient, 'mutate'); + + findNameInput().vm.$emit('input', 'gpt-alice-1'); + findVersionInput().vm.$emit('input', '1.0.0'); + findVersionDescriptionInput().vm.$emit('input', 'My retried version description'); + await submitForm(); + }); + + it('Displays an alert upon failed model create mutation', async () => { + expect(findGlAlert().text()).toBe('Version is invalid'); + + await submitForm(); + + expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith( + expect.objectContaining({ + mutation: createModelVersionMutation, + variables: { + modelId: 'gid://gitlab/Ml::Model/1', + projectPath: 'some/project', + version: '1.0.0', + description: 'My retried version description', + }, + }), + ); + }); + }); + + describe('Failed flow without version', () => { + describe('Mutation errors', () => { + beforeEach(async () => { + const failedCreateModelResolver = jest + .fn() + .mockResolvedValue(createModelResponses.validationFailure); + createWrapper(failedCreateModelResolver); + jest.spyOn(apolloProvider.defaultClient, 'mutate'); + + findNameInput().vm.$emit('input', 'gpt-alice-1'); + await submitForm(); + }); + + it('Displays an alert upon failed model create mutation', () => { + expect(findGlAlert().text()).toBe("Name is invalid, Name can't be blank"); + }); + + it('Displays an alert upon an exception', () => { + expect(findGlAlert().text()).toBe("Name is invalid, Name can't be blank"); + }); + }); + + it('Logs to sentry upon an exception', async () => { + const error = new Error('Runtime error'); + createWrapper(); + jest.spyOn(apolloProvider.defaultClient, 'mutate').mockImplementation(() => { + throw error; + }); + + findNameInput().vm.$emit('input', 'gpt-alice-1'); + await submitForm(); + + expect(Sentry.captureException).toHaveBeenCalledWith(error); + }); + }); +}); diff --git a/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js b/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js index 8723b6587c6..d4ce2b7857a 100644 --- a/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js +++ b/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js @@ -69,6 +69,7 @@ export const protectionEmptyStatePropsMock = { export const protectionRowPropsMock = { title: 'Test title', users: usersMock, + groups: groupsMock, accessLevels: accessLevelsMock, approvalsRequired, statusCheckUrl: statusChecksRulesMock[0].externalUrl, @@ -106,6 +107,10 @@ export const branchProtectionsMockResponse = { project: { id: 'gid://gitlab/Project/1', __typename: 'Project', + group: { + id: 'gid://gitlab/Group/1', + __typename: 'Group', + }, branchRules: { __typename: 'BranchRuleConnection', nodes: [ @@ -158,6 +163,10 @@ export const predefinedBranchRulesMockResponse = { project: { id: 'gid://gitlab/Project/1', __typename: 'Project', + group: { + id: 'gid://gitlab/Group/1', + __typename: 'Group', + }, branchRules: { __typename: 'BranchRuleConnection', nodes: [ diff --git a/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js index 87caba47683..33069a4ef90 100644 --- a/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js +++ b/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js @@ -30,7 +30,10 @@ describe('Branch rule protection row', () => { }); it('renders an avatars-inline component', () => { - expect(findAvatarsInline().props('avatars')).toMatchObject(protectionRowPropsMock.users); + expect(findAvatarsInline().props('avatars')).toMatchObject([ + ...protectionRowPropsMock.users, + ...protectionRowPropsMock.groups, + ]); expect(findAvatarsInline().props('badgeSrOnlyText')).toBe('1 additional user'); }); diff --git a/spec/frontend/projects/settings/branch_rules/components/view/protection_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/protection_spec.js index 6427ffeb802..91e4eb158df 100644 --- a/spec/frontend/projects/settings/branch_rules/components/view/protection_spec.js +++ b/spec/frontend/projects/settings/branch_rules/components/view/protection_spec.js @@ -58,31 +58,24 @@ describe('Branch rule protection', () => { }); }); - it('renders a protection row for users', () => { + it('renders a protection row for users and groups', () => { expect(findProtectionRows().at(1).props()).toMatchObject({ + showDivider: true, + groups: protectionPropsMock.groups, users: protectionPropsMock.users, - showDivider: true, - title: i18n.usersTitle, - }); - }); - - it('renders a protection row for groups', () => { - expect(findProtectionRows().at(2).props()).toMatchObject({ - accessLevels: protectionPropsMock.groups, - showDivider: true, - title: i18n.groupsTitle, + title: i18n.usersAndGroupsTitle, }); }); it('renders a protection row for status checks', () => { const statusCheck = protectionPropsMock.statusChecks[0]; - expect(findProtectionRows().at(3).props()).toMatchObject({ + expect(findProtectionRows().at(2).props()).toMatchObject({ title: statusCheck.name, showDivider: false, statusCheckUrl: statusCheck.externalUrl, }); - expect(findProtectionRows().at(4).props('showDivider')).toBe(true); + expect(findProtectionRows().at(3).props('showDivider')).toBe(true); }); describe('When `isEditAvailable` prop is set to true', () => { diff --git a/spec/frontend/vue_shared/components/list_selector/index_spec.js b/spec/frontend/vue_shared/components/list_selector/index_spec.js index 4024a21ba39..260d7f3c691 100644 --- a/spec/frontend/vue_shared/components/list_selector/index_spec.js +++ b/spec/frontend/vue_shared/components/list_selector/index_spec.js @@ -25,6 +25,12 @@ jest.mock('~/rest_api', () => ({ { name: 'Project 2', id: '2' }, ], }), + getSubgroups: jest.fn().mockResolvedValue({ + data: [ + { name: 'Subgroup 1', id: '1' }, + { name: 'Subgroup 2', id: '2' }, + ], + }), })); Vue.use(VueApollo); diff --git a/spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb index 28e2e3bdc26..8ecea8a10dc 100644 --- a/spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb +++ b/spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb @@ -60,7 +60,7 @@ RSpec.describe BulkImports::Projects::Pipelines::SnippetsRepositoryPipeline, fea end describe '#run', :clean_gitlab_redis_shared_state do - let(:validation_response) { double(Hash, 'error?': false) } + let(:validation_response) { double(Hash, error?: false) } before do allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor| @@ -172,7 +172,7 @@ RSpec.describe BulkImports::Projects::Pipelines::SnippetsRepositoryPipeline, fea end context 'when snippet is invalid' do - let(:validation_response) { double(Hash, 'error?': true) } + let(:validation_response) { double(Hash, error?: true) } before do allow_next_instance_of(Repository) do |repository| diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb index 45a5b3afd81..5dae9ed6bf4 100644 --- a/spec/lib/constraints/group_url_constrainer_spec.rb +++ b/spec/lib/constraints/group_url_constrainer_spec.rb @@ -61,7 +61,7 @@ RSpec.describe Constraints::GroupUrlConstrainer do def build_request(path, method = 'GET') double(:request, - 'get?': (method == 'GET'), + get?: (method == 'GET'), params: { id: path }) end end diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb index 1e8aac8479d..21ed46ec179 100644 --- a/spec/lib/constraints/project_url_constrainer_spec.rb +++ b/spec/lib/constraints/project_url_constrainer_spec.rb @@ -51,7 +51,7 @@ RSpec.describe Constraints::ProjectUrlConstrainer do def build_request(namespace, project, method = 'GET') double(:request, - 'get?': (method == 'GET'), + get?: (method == 'GET'), params: { namespace_id: namespace, id: project }) end end diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb index 1b68e966585..9f04a865edb 100644 --- a/spec/lib/constraints/user_url_constrainer_spec.rb +++ b/spec/lib/constraints/user_url_constrainer_spec.rb @@ -38,7 +38,7 @@ RSpec.describe Constraints::UserUrlConstrainer do def build_request(username, method = 'GET') double(:request, - 'get?': (method == 'GET'), + get?: (method == 'GET'), params: { username: username }) end end diff --git a/spec/lib/container_registry/gitlab_api_client_spec.rb b/spec/lib/container_registry/gitlab_api_client_spec.rb index 5ee74a03df3..83acccd0c1d 100644 --- a/spec/lib/container_registry/gitlab_api_client_spec.rb +++ b/spec/lib/container_registry/gitlab_api_client_spec.rb @@ -273,16 +273,16 @@ RSpec.describe ContainerRegistry::GitlabApiClient, feature_category: :container_ let(:response) do [ { - "name": "docker-alpine", - "path": "gitlab-org/build/cng/docker-alpine", - "created_at": "2022-06-07T12:11:13.633+00:00", - "updated_at": "2022-06-07T14:37:49.251+00:00" + name: "docker-alpine", + path: "gitlab-org/build/cng/docker-alpine", + created_at: "2022-06-07T12:11:13.633+00:00", + updated_at: "2022-06-07T14:37:49.251+00:00" }, { - "name": "git-base", - "path": "gitlab-org/build/cng/git-base", - "created_at": "2022-06-07T12:11:13.633+00:00", - "updated_at": "2022-06-07T14:37:49.251+00:00" + name: "git-base", + path: "gitlab-org/build/cng/git-base", + created_at: "2022-06-07T12:11:13.633+00:00", + updated_at: "2022-06-07T14:37:49.251+00:00" } ] end @@ -468,7 +468,7 @@ RSpec.describe ContainerRegistry::GitlabApiClient, feature_category: :container_ describe '.deduplicated_size' do let(:path) { 'foo/bar' } - let(:response) { { 'size_bytes': 555 } } + let(:response) { { size_bytes: 555 } } let(:registry_enabled) { true } subject { described_class.deduplicated_size(path) } @@ -685,16 +685,16 @@ RSpec.describe ContainerRegistry::GitlabApiClient, feature_category: :container_ let(:client_response_repositories) do [ { - "name": "docker-alpine", - "path": "gitlab-org/build/cng/docker-alpine", - "created_at": "2022-06-07T12:11:13.633+00:00", - "updated_at": "2022-06-07T14:37:49.251+00:00" + name: "docker-alpine", + path: "gitlab-org/build/cng/docker-alpine", + created_at: "2022-06-07T12:11:13.633+00:00", + updated_at: "2022-06-07T14:37:49.251+00:00" }, { - "name": "git-base", - "path": "gitlab-org/build/cng/git-base", - "created_at": "2022-06-07T12:11:13.633+00:00", - "updated_at": "2022-06-07T14:37:49.251+00:00" + name: "git-base", + path: "gitlab-org/build/cng/git-base", + created_at: "2022-06-07T12:11:13.633+00:00", + updated_at: "2022-06-07T14:37:49.251+00:00" } ] end @@ -707,16 +707,16 @@ RSpec.describe ContainerRegistry::GitlabApiClient, feature_category: :container_ let(:client_response_repositories1) do [ { - "name": "docker-alpine", - "path": "gitlab-org/build/cng/docker-alpine", - "created_at": "2022-06-07T12:11:13.633+00:00", - "updated_at": "2022-06-07T14:37:49.251+00:00" + name: "docker-alpine", + path: "gitlab-org/build/cng/docker-alpine", + created_at: "2022-06-07T12:11:13.633+00:00", + updated_at: "2022-06-07T14:37:49.251+00:00" }, { - "name": "git-base", - "path": "gitlab-org/build/cng/git-base", - "created_at": "2022-06-07T12:11:13.633+00:00", - "updated_at": "2022-06-07T14:37:49.251+00:00" + name: "git-base", + path: "gitlab-org/build/cng/git-base", + created_at: "2022-06-07T12:11:13.633+00:00", + updated_at: "2022-06-07T14:37:49.251+00:00" } ] end @@ -725,16 +725,16 @@ RSpec.describe ContainerRegistry::GitlabApiClient, feature_category: :container_ let(:client_response_repositories2) do [ { - "name": "docker-alpine1", - "path": "gitlab-org/build/cng/docker-alpine", - "created_at": "2022-06-07T12:11:13.633+00:00", - "updated_at": "2022-06-07T14:37:49.251+00:00" + name: "docker-alpine1", + path: "gitlab-org/build/cng/docker-alpine", + created_at: "2022-06-07T12:11:13.633+00:00", + updated_at: "2022-06-07T14:37:49.251+00:00" }, { - "name": "git-base1", - "path": "gitlab-org/build/cng/git-base", - "created_at": "2022-06-07T12:11:13.633+00:00", - "updated_at": "2022-06-07T14:37:49.251+00:00" + name: "git-base1", + path: "gitlab-org/build/cng/git-base", + created_at: "2022-06-07T12:11:13.633+00:00", + updated_at: "2022-06-07T14:37:49.251+00:00" } ] end diff --git a/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb b/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb index 5265b608ab4..5650d96ef86 100644 --- a/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb +++ b/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb @@ -78,7 +78,7 @@ RSpec.describe Gitlab::UsageMetricDefinition::RedisHllGenerator, :silence_stdout end it 'creates metric definition files' do - described_class.new(args, { 'ee': true }).invoke_all + described_class.new(args, { ee: true }).invoke_all expect(weekly_metric_definition).to include("key_path" => "redis_hll_counters.test_category.i_test_event_weekly") expect(weekly_metric_definition["distribution"]).to include('ee') diff --git a/spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb b/spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb index e0cb74d8559..5827d0ff5c3 100644 --- a/spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb +++ b/spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb @@ -52,7 +52,7 @@ RSpec.describe Gitlab::UsageMetricDefinitionGenerator, :silence_stdout, feature_ end it 'creates a metric definition file using the template' do - described_class.new([key_path], { 'dir' => dir, 'class_name' => class_name, 'ee': true }).invoke_all + described_class.new([key_path], { 'dir' => dir, 'class_name' => class_name, ee: true }).invoke_all expect(YAML.safe_load(File.read(metric_definition_path))).to eq(sample_metric) end end diff --git a/spec/lib/gitlab/background_migration/backfill_related_epic_links_to_issue_links_spec.rb b/spec/lib/gitlab/background_migration/backfill_related_epic_links_to_issue_links_spec.rb new file mode 100644 index 00000000000..5160aaaf34a --- /dev/null +++ b/spec/lib/gitlab/background_migration/backfill_related_epic_links_to_issue_links_spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::BackfillRelatedEpicLinksToIssueLinks, feature_category: :team_planning do + let!(:epic_type_id) { table(:work_item_types).find_by(base_type: 7).id } + let!(:author) { table(:users).create!(username: 'tester', projects_limit: 100) } + let!(:namespace) { table(:namespaces).create!(name: 'my test group1', path: 'my-test-group1') } + + let(:epics) { table(:epics) } + let(:issues) { table(:issues) } + let(:related_epic_links) { table(:related_epic_links) } + let(:issue_links) { table(:issue_links) } + let(:start_id) { related_epic_links.minimum(:id) } + let(:end_id) { related_epic_links.maximum(:id) } + + # Source epics + let(:source_epic_1) { create_epic_with_work_item(title: 'Epic 1', iid: 1) } + let(:source_epic_2) { create_epic_with_work_item(title: 'Epic 2', iid: 2) } + let(:source_epic_3) { create_epic_with_work_item(title: 'Epic 3', iid: 3) } + let(:source_epic_4) { create_epic_with_work_item(title: 'Epic 4', iid: 4) } + # Target epics + let(:target_epic_1) { create_epic_with_work_item(title: 'Epic 5', iid: 5) } + let(:target_epic_2) { create_epic_with_work_item(title: 'Epic 6', iid: 6) } + let(:target_epic_3) { create_epic_with_work_item(title: 'Epic 7', iid: 7) } + let(:target_epic_4) { create_epic_with_work_item(title: 'Epic 8', iid: 8) } + + # Epic links not in sync(without a corresponding issue links record) + let!(:related_epic_link_1) { create_related_epic_link(source: source_epic_1, target: target_epic_1, link_type: 0) } + let!(:related_epic_link_2) { create_related_epic_link(source: source_epic_2, target: target_epic_2, link_type: 1) } + # Epic link in sync + let!(:related_epic_link_3) { create_related_epic_link(source: source_epic_3, target: target_epic_3, link_type: 0) } + let!(:synced_issue_link_1) do + issue_links.create!( + source_id: source_epic_3.issue_id, + target_id: target_epic_3.issue_id, + link_type: 0 + ) + end + + # Epic link in sync but with outdated value + let!(:related_epic_link_4) { create_related_epic_link(source: source_epic_4, target: target_epic_4, link_type: 0) } + let!(:synced_issue_link_2) do + issue_links.create!( + source_id: source_epic_4.issue_id, + target_id: target_epic_4.issue_id, + link_type: 1, + created_at: 1.day.ago, + updated_at: 1.hour.ago + ) + end + + subject(:migration) do + described_class.new( + start_id: start_id, + end_id: end_id, + batch_table: :related_epic_links, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ApplicationRecord.connection + ) + end + + RSpec::Matchers.define :have_synced_issue_link do + match do |epic_link| + source_work_item_id = epics.find(epic_link.source_id).issue_id + target_work_item_id = epics.find(epic_link.target_id).issue_id + + issue_links.find_by( + source_id: source_work_item_id, + target_id: target_work_item_id, + link_type: epic_link.link_type + ).present? + end + end + + it 'backfills data correctly' do + expect do + migration.perform + end.to change { issue_links.count }.from(2).to(4) + .and not_change { synced_issue_link_1.reload } + .and change { synced_issue_link_2.reload.link_type }.from(1).to(0) + .and change { synced_issue_link_2.reload.created_at }.to(related_epic_link_4.reload.created_at) + .and change { synced_issue_link_2.reload.updated_at }.to(related_epic_link_4.reload.updated_at) + + expect(related_epic_link_1).to have_synced_issue_link + expect(related_epic_link_2).to have_synced_issue_link + expect(related_epic_link_3).to have_synced_issue_link + expect(related_epic_link_4).to have_synced_issue_link + end + + def create_epic_with_work_item(iid:, title:) + wi = issues.create!( + iid: iid, + author_id: author.id, + work_item_type_id: epic_type_id, + namespace_id: namespace.id, + lock_version: 1, + title: title + ) + + epics.create!( + iid: iid, + title: title, + title_html: title, + group_id: namespace.id, + author_id: author.id, + issue_id: wi.id + ) + end + + def create_related_epic_link(source:, target:, link_type:) + related_epic_links.create!( + source_id: source.id, + target_id: target.id, + link_type: link_type + ) + end +end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 81888801d5a..6da8b43f5c0 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -113,7 +113,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_compo end context 'when job name is empty' do - let(:entry) { described_class.new(config, name: ''.to_sym) } + let(:entry) { described_class.new(config, name: :"") } it 'reports error' do expect(entry.errors).to include "job name can't be blank" diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb index b03175cd80f..9af54419f07 100644 --- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb @@ -7,8 +7,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Jobs do let(:config) do { - '.hidden_job'.to_sym => { script: 'something' }, - '.hidden_bridge'.to_sym => { trigger: 'my/project' }, + ".hidden_job": { script: 'something' }, + ".hidden_bridge": { trigger: 'my/project' }, regular_job: { script: 'something' }, my_trigger: { trigger: 'my/project' } } @@ -81,7 +81,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Jobs do end context 'when no visible jobs present' do - let(:config) { { '.hidden'.to_sym => { script: [] } } } + let(:config) { { ".hidden": { script: [] } } } it 'returns error about no visible jobs defined' do expect(entry.errors) diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb index 4139d5f60a1..3b250686a49 100644 --- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb @@ -56,7 +56,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable, feature_category: :pipeli end context 'when job name is empty' do - let(:entry) { node_class.new(config, name: ''.to_sym) } + let(:entry) { node_class.new(config, name: :"") } it 'reports error' do expect(entry.errors).to include "job name can't be blank" diff --git a/spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb b/spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb index b3edf452f36..e82e920d87f 100644 --- a/spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb +++ b/spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb @@ -12,13 +12,13 @@ RSpec.describe Gitlab::Ci::Parsers::Accessibility::Pa11y do context "when there are no URLs provided" do let(:pa11y) do { - "total": 1, - "passes": 0, - "errors": 0, - "results": { + total: 1, + passes: 0, + errors: 0, + results: { "": [ { - "message": "Protocol error (Page.navigate): Cannot navigate to invalid URL" + message: "Protocol error (Page.navigate): Cannot navigate to invalid URL" } ] } @@ -39,10 +39,10 @@ RSpec.describe Gitlab::Ci::Parsers::Accessibility::Pa11y do context "when there are no errors" do let(:pa11y) do { - "total": 1, - "passes": 1, - "errors": 0, - "results": { + total: 1, + passes: 1, + errors: 0, + results: { "http://pa11y.org/": [] } }.to_json @@ -61,20 +61,20 @@ RSpec.describe Gitlab::Ci::Parsers::Accessibility::Pa11y do context "when there are errors" do let(:pa11y) do { - "total": 1, - "passes": 0, - "errors": 1, - "results": { + total: 1, + passes: 0, + errors: 1, + results: { "https://about.gitlab.com/": [ { - "code": "WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent", - "type": "error", - "typeCode": 1, - "message": "Anchor element found with a valid href attribute, but no link content has been supplied.", - "context": "", - "selector": "#main-nav > div:nth-child(1) > a", - "runner": "htmlcs", - "runnerExtras": {} + code: "WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent", + type: "error", + typeCode: 1, + message: "Anchor element found with a valid href attribute, but no link content has been supplied.", + context: "", + selector: "#main-nav > div:nth-child(1) > a", + runner: "htmlcs", + runnerExtras: {} } ] } @@ -96,10 +96,10 @@ RSpec.describe Gitlab::Ci::Parsers::Accessibility::Pa11y do context "when data is not a valid JSON string" do let(:pa11y) do { - "total": 1, - "passes": 1, - "errors": 0, - "results": { + total: 1, + passes: 1, + errors: 0, + results: { "http://pa11y.org/": [] } } diff --git a/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb b/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb index 94757d4f7d1..f12f6b12999 100644 --- a/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb +++ b/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb @@ -10,27 +10,27 @@ RSpec.describe Gitlab::Ci::Parsers::Codequality::CodeClimate do let(:code_climate) do [ { - "categories": [ + categories: [ "Complexity" ], - "check_name": "argument_count", - "content": { - "body": "" + check_name: "argument_count", + content: { + body: "" }, - "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", - "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547", - "location": { - "path": "foo.rb", - "lines": { - "begin": 10, - "end": 10 + description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", + fingerprint: "15cdb5c53afd42bc22f8ca366a08d547", + location: { + path: "foo.rb", + lines: { + begin: 10, + end: 10 } }, - "other_locations": [], - "remediation_points": 900000, - "severity": "major", - "type": "issue", - "engine_name": "structure" + other_locations: [], + remediation_points: 900000, + severity: "major", + type: "issue", + engine_name: "structure" } ].to_json end @@ -68,27 +68,27 @@ RSpec.describe Gitlab::Ci::Parsers::Codequality::CodeClimate do let(:code_climate) do [ { - "categories": [ + categories: [ "Complexity" ], - "check_name": "argument_count", - "content": { - "body": "" + check_name: "argument_count", + content: { + body: "" }, - "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", - "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547", - "location": { - "path": "foo.rb", - "lines": { - "begin": 10, - "end": 10 + description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", + fingerprint: "15cdb5c53afd42bc22f8ca366a08d547", + location: { + path: "foo.rb", + lines: { + begin: 10, + end: 10 } }, - "other_locations": [], - "remediation_points": 900000, - "severity": "major", - "type": "issue", - "engine_name": "structure" + other_locations: [], + remediation_points: 900000, + severity: "major", + type: "issue", + engine_name: "structure" } ] end @@ -104,34 +104,34 @@ RSpec.describe Gitlab::Ci::Parsers::Codequality::CodeClimate do let(:code_climate) do [ { - "type": "Issue", - "check_name": "Rubocop/Metrics/ParameterLists", - "description": "Avoid parameter lists longer than 5 parameters. [12/5]", - "fingerprint": "ab5f8b935886b942d621399aefkaehfiaehf", - "severity": "minor" + type: "Issue", + check_name: "Rubocop/Metrics/ParameterLists", + description: "Avoid parameter lists longer than 5 parameters. [12/5]", + fingerprint: "ab5f8b935886b942d621399aefkaehfiaehf", + severity: "minor" }, { - "categories": [ + categories: [ "Complexity" ], - "check_name": "argument_count", - "content": { - "body": "" + check_name: "argument_count", + content: { + body: "" }, - "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", - "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547", - "location": { - "path": "foo.rb", - "lines": { - "begin": 10, - "end": 10 + description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", + fingerprint: "15cdb5c53afd42bc22f8ca366a08d547", + location: { + path: "foo.rb", + lines: { + begin: 10, + end: 10 } }, - "other_locations": [], - "remediation_points": 900000, - "severity": "major", - "type": "issue", - "engine_name": "structure" + other_locations: [], + remediation_points: 900000, + severity: "major", + type: "issue", + engine_name: "structure" } ].to_json end @@ -147,27 +147,27 @@ RSpec.describe Gitlab::Ci::Parsers::Codequality::CodeClimate do let(:code_climate) do [ { - "categories": [ + categories: [ "Complexity" ], - "check_name": "argument_count", - "content": { - "body": "" + check_name: "argument_count", + content: { + body: "" }, - "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", - "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547", - "location": { - "path": "foo.rb", - "lines": { - "begin": 10, - "end": 10 + description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", + fingerprint: "15cdb5c53afd42bc22f8ca366a08d547", + location: { + path: "foo.rb", + lines: { + begin: 10, + end: 10 } }, - "other_locations": [], - "remediation_points": 900000, - "severity": "major", - "type": "issue", - "engine_name": "structure" + other_locations: [], + remediation_points: 900000, + severity: "major", + type: "issue", + engine_name: "structure" } ].to_json end diff --git a/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb b/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb index dff59474746..15dc16e7375 100644 --- a/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb +++ b/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb @@ -8,24 +8,24 @@ RSpec.describe Gitlab::Ci::Reports::AccessibilityReports do let(:data) do [ { - "code": "WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent", - "type": "error", - "typeCode": 1, - "message": "Anchor element found with a valid href attribute, but no link content has been supplied.", - "context": %(), - "selector": "html > body > div:nth-child(9) > div:nth-child(2) > a:nth-child(17)", - "runner": "htmlcs", - "runnerExtras": {} + code: "WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent", + type: "error", + typeCode: 1, + message: "Anchor element found with a valid href attribute, but no link content has been supplied.", + context: %(), + selector: "html > body > div:nth-child(9) > div:nth-child(2) > a:nth-child(17)", + runner: "htmlcs", + runnerExtras: {} }, { - "code": "WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent", - "type": "error", - "typeCode": 1, - "message": "Anchor element found with a valid href attribute, but no link content has been supplied.", - "context": %( body > div:nth-child(9) > div:nth-child(2) > a:nth-child(18)", - "runner": "htmlcs", - "runnerExtras": {} + code: "WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent", + type: "error", + typeCode: 1, + message: "Anchor element found with a valid href attribute, but no link content has been supplied.", + context: %( body > div:nth-child(9) > div:nth-child(2) > a:nth-child(18)", + runner: "htmlcs", + runnerExtras: {} } ] end diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb index 93644aa1497..3ed7c7b1cfd 100644 --- a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb +++ b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb @@ -30,11 +30,11 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do context 'when a required property is missing in the degradation' do let(:invalid_degradation) do { - "type": "Issue", - "check_name": "Rubocop/Metrics/ParameterLists", - "description": "Avoid parameter lists longer than 5 parameters. [12/5]", - "fingerprint": "ab5f8b935886b942d621399aefkaehfiaehf", - "severity": "minor" + type: "Issue", + check_name: "Rubocop/Metrics/ParameterLists", + description: "Avoid parameter lists longer than 5 parameters. [12/5]", + fingerprint: "ab5f8b935886b942d621399aefkaehfiaehf", + severity: "minor" }.with_indifferent_access end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 7e074472b4d..a03fa65596b 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -2815,12 +2815,12 @@ module Gitlab build1: { stage: 'build', script: 'build', - parallel: { matrix: [{ 'PROVIDER': ['aws'], 'STACK': %w[monitoring app1 app2] }] } + parallel: { matrix: [{ PROVIDER: ['aws'], STACK: %w[monitoring app1 app2] }] } }, test1: { stage: 'test', script: 'test', - needs: [{ job: 'build1', parallel: { matrix: [{ 'PROVIDER': ['aws'], 'STACK': ['app1'] }] } }] + needs: [{ job: 'build1', parallel: { matrix: [{ PROVIDER: ['aws'], STACK: ['app1'] }] } }] } } end @@ -3255,7 +3255,7 @@ module Gitlab end context 'returns errors if there are no visible jobs defined' do - let(:config) { YAML.dump({ before_script: ["bundle update"], '.hidden'.to_sym => { script: 'ls' } }) } + let(:config) { YAML.dump({ before_script: ["bundle update"], ".hidden": { script: 'ls' } }) } it_behaves_like 'returns errors', 'jobs config should contain at least one visible job' end diff --git a/spec/migrations/20240517131715_queue_backfill_related_epic_links_to_issue_links_spec.rb b/spec/migrations/20240517131715_queue_backfill_related_epic_links_to_issue_links_spec.rb new file mode 100644 index 00000000000..823f4c2de80 --- /dev/null +++ b/spec/migrations/20240517131715_queue_backfill_related_epic_links_to_issue_links_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe QueueBackfillRelatedEpicLinksToIssueLinks, feature_category: :team_planning do + let!(:batched_migration) { described_class::MIGRATION } + + it 'schedules a new batched migration' do + reversible_migration do |migration| + migration.before -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + + migration.after -> { + expect(batched_migration).to have_scheduled_batched_migration( + table_name: :related_epic_links, + column_name: :id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + } + end + end +end diff --git a/spec/workers/ci/retry_pipeline_worker_spec.rb b/spec/workers/ci/retry_pipeline_worker_spec.rb index f41b6b88c6f..4d2d003a472 100644 --- a/spec/workers/ci/retry_pipeline_worker_spec.rb +++ b/spec/workers/ci/retry_pipeline_worker_spec.rb @@ -6,19 +6,19 @@ RSpec.describe Ci::RetryPipelineWorker, feature_category: :continuous_integratio describe '#perform' do subject(:perform) { described_class.new.perform(pipeline_id, user_id) } - let(:pipeline) { create(:ci_pipeline) } + let_it_be(:pipeline) { create(:ci_pipeline) } + let_it_be(:user) { create(:user) } + + before_all do + pipeline.project.add_maintainer(user) + end context 'when pipeline exists' do let(:pipeline_id) { pipeline.id } context 'when user exists' do - let(:user) { create(:user) } let(:user_id) { user.id } - before do - pipeline.project.add_maintainer(user) - end - it 'retries the pipeline' do expect(::Ci::Pipeline).to receive(:find_by_id).with(pipeline.id).and_return(pipeline) expect(pipeline).to receive(:retry_failed).with(having_attributes(id: user_id)) @@ -28,7 +28,7 @@ RSpec.describe Ci::RetryPipelineWorker, feature_category: :continuous_integratio end context 'when user does not exist' do - let(:user_id) { 1234 } + let(:user_id) { non_existing_record_id } it 'does not retry the pipeline' do expect(::Ci::Pipeline).to receive(:find_by_id).with(pipeline_id).and_return(pipeline) @@ -40,8 +40,8 @@ RSpec.describe Ci::RetryPipelineWorker, feature_category: :continuous_integratio end context 'when pipeline does not exist' do - let(:pipeline_id) { 1234 } - let(:user_id) { 1234 } + let(:pipeline_id) { non_existing_record_id } + let(:user_id) { user.id } it 'returns nil' do expect(perform).to be_nil