summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-05-18 18:08:05 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-18 18:08:05 +0000
commit6b5d8b17e28741bccf31029633cc5af7ceab1486 (patch)
tree38732dffc21a5084b5c3d49b56b81aa11b4e657e
parenta84995f457d775bb73598d4393c3bc99805d9b58 (diff)
downloadgitlab-ce-6b5d8b17e28741bccf31029633cc5af7ceab1486.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--app/assets/javascripts/editor/schema/ci.json86
-rw-r--r--app/assets/javascripts/issuable/issuable_form.js3
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue19
-rw-r--r--app/assets/javascripts/issues/list/constants.js2
-rw-r--r--app/assets/javascripts/issues/list/utils.js11
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue4
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue4
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js2
-rw-r--r--app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue8
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue4
-rw-r--r--app/assets/stylesheets/framework/header.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss21
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss21
-rw-r--r--app/assets/stylesheets/startup/startup-signin.scss4
-rw-r--r--app/controllers/mailgun/webhooks_controller.rb55
-rw-r--r--app/controllers/members/mailgun/permanent_failures_controller.rb65
-rw-r--r--app/controllers/projects/ci/pipeline_editor_controller.rb1
-rw-r--r--app/graphql/resolvers/concerns/resolves_merge_requests.rb1
-rw-r--r--app/graphql/resolvers/merge_request_pipelines_resolver.rb7
-rw-r--r--app/models/merge_request.rb7
-rw-r--r--app/services/members/mailgun/process_webhook_service.rb4
-rw-r--r--app/services/projects/update_pages_service.rb5
-rw-r--r--app/views/layouts/header/_default.html.haml5
-rw-r--r--app/views/projects/releases/edit.html.haml2
-rw-r--r--app/workers/database/batched_background_migration/single_database_worker.rb20
-rw-r--r--app/workers/namespaces/process_sync_events_worker.rb2
-rw-r--r--app/workers/projects/process_sync_events_worker.rb2
-rw-r--r--config/database.yml.decomposed-postgresql22
-rw-r--r--config/database.yml.postgresql28
-rw-r--r--config/database_geo.yml.postgresql43
-rw-r--r--config/feature_flags/development/ci_show_all_projects_with_usage_sorted_descending.yml (renamed from config/feature_flags/development/pipeline_editor_file_tree.yml)8
-rw-r--r--config/initializers/1_settings.rb2
-rw-r--r--config/initializers/database_config.rb8
-rw-r--r--config/routes.rb7
-rw-r--r--config/routes/members.rb7
-rw-r--r--db/migrate/20220503035221_add_gitlab_schema_to_batched_background_migrations.rb11
-rw-r--r--db/migrate/20220503035437_add_text_limit_to_batched_background_migrations_gitlab_schema.rb13
-rw-r--r--db/migrate/20220512020500_index_batched_migrations_on_gitlab_schema_and_configuration.rb19
-rw-r--r--db/post_migrate/20210727113447_backfill_integrations_type_new.rb4
-rw-r--r--db/post_migrate/20210930211936_backfill_user_namespace.rb4
-rw-r--r--db/post_migrate/20211026070408_backfill_issue_search_data.rb4
-rw-r--r--db/post_migrate/20211209103048_backfill_project_namespaces_for_group.rb3
-rw-r--r--db/post_migrate/20220120123800_backfill_namespace_id_for_namespace_routes.rb4
-rw-r--r--db/post_migrate/20220120211832_backfill_member_namespace_id_for_group_members.rb4
-rw-r--r--db/post_migrate/20220223112304_schedule_nullify_orphan_runner_id_on_ci_builds.rb4
-rw-r--r--db/post_migrate/20220301093434_backfill_all_project_namespaces.rb3
-rw-r--r--db/post_migrate/20220302114046_backfill_group_features.rb4
-rw-r--r--db/post_migrate/20220323023800_backfill_namespace_id_for_project_routes.rb4
-rw-r--r--db/post_migrate/20220324165436_schedule_backfill_project_settings.rb4
-rw-r--r--db/post_migrate/20220510121338_remove_threat_monitoring_alerts.rb15
-rw-r--r--db/post_migrate/20220512190659_remove_web_hooks_web_hook_logs_web_hook_id_fk.rb50
-rw-r--r--db/post_migrate/20220513043344_reschedule_expire_o_auth_tokens.rb8
-rw-r--r--db/post_migrate/20220517101119_create_confidential_notes_index_synchronously.rb15
-rw-r--r--db/post_migrate/20220517133753_migrate_free_user_cap_remediation.rb11
-rw-r--r--db/schema_migrations/202205030352211
-rw-r--r--db/schema_migrations/202205030354371
-rw-r--r--db/schema_migrations/202205101213381
-rw-r--r--db/schema_migrations/202205120205001
-rw-r--r--db/schema_migrations/202205121906591
-rw-r--r--db/schema_migrations/202205171011191
-rw-r--r--db/schema_migrations/202205171337531
-rw-r--r--db/structure.sql9
-rw-r--r--doc/administration/geo/replication/datatypes.md2
-rw-r--r--doc/administration/integration/mailgun.md27
-rw-r--r--doc/administration/server_hooks.md2
-rw-r--r--doc/api/merge_requests.md217
-rw-r--r--doc/ci/pipeline_editor/index.md8
-rw-r--r--doc/ci/pipelines/cicd_minutes.md9
-rw-r--r--doc/ci/runners/saas/macos/codesigning.md121
-rw-r--r--doc/ci/runners/saas/macos/environment.md2
-rw-r--r--doc/ci/runners/saas/macos_saas_runner.md32
-rw-r--r--doc/development/database/batched_background_migrations.md7
-rw-r--r--doc/development/documentation/styleguide/index.md6
-rw-r--r--doc/development/feature_flags/controls.md1
-rw-r--r--doc/development/geo.md2
-rw-r--r--doc/push_rules/push_rules.md11
-rw-r--r--doc/topics/autodevops/customize.md1
-rw-r--r--lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml6
-rw-r--r--lib/gitlab/database/background_migration/batched_migration.rb27
-rw-r--r--lib/gitlab/database/background_migration/batched_migration_runner.rb5
-rw-r--r--lib/gitlab/database/migration_helpers.rb9
-rw-r--r--lib/gitlab/database/migrations/batched_background_migration_helpers.rb53
-rw-r--r--lib/gitlab/patch/database_config.rb66
-rw-r--r--lib/tasks/rubocop.rake2
-rw-r--r--locale/gitlab.pot12
-rw-r--r--rubocop/formatter/todo_formatter.rb46
-rw-r--r--rubocop/todo_dir.rb63
-rw-r--r--scripts/lib/glfm/update_specification.rb24
-rw-r--r--scripts/prepare_build.sh12
-rw-r--r--spec/db/schema_spec.rb5
-rw-r--r--spec/factories/alert_management/alerts.rb4
-rw-r--r--spec/factories/gitlab/database/background_migration/batched_migrations.rb1
-rw-r--r--spec/features/projects/ci/editor_spec.rb10
-rw-r--r--spec/features/projects/releases/user_views_edit_release_spec.rb4
-rw-r--r--spec/frontend/editor/schema/ci/ci_schema_spec.js1
-rw-r--r--spec/frontend/issues/list/components/issues_list_app_spec.js26
-rw-r--r--spec/frontend/issues/list/utils_spec.js24
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js3
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js3
-rw-r--r--spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js40
-rw-r--r--spec/frontend/pipeline_editor/pipeline_editor_home_spec.js92
-rw-r--r--spec/initializers/validate_database_config_spec.rb3
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb7
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_spec.rb85
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb20
-rw-r--r--spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb105
-rw-r--r--spec/lib/gitlab/patch/database_config_spec.rb3
-rw-r--r--spec/lib/gitlab/subscription_portal_spec.rb46
-rw-r--r--spec/migrations/20220503035221_add_gitlab_schema_to_batched_background_migrations_spec.rb34
-rw-r--r--spec/migrations/20220505044348_fix_automatic_iterations_cadences_start_date_spec.rb3
-rw-r--r--spec/migrations/20220512190659_remove_web_hooks_web_hook_logs_web_hook_id_fk_spec.rb33
-rw-r--r--spec/migrations/remove_not_null_contraint_on_title_from_sprints_spec.rb2
-rw-r--r--spec/models/hooks/web_hook_spec.rb4
-rw-r--r--spec/models/merge_request_spec.rb53
-rw-r--r--spec/requests/api/graphql/project/merge_request/pipelines_spec.rb69
-rw-r--r--spec/requests/api/usage_data_spec.rb21
-rw-r--r--spec/requests/mailgun/webhooks_controller_spec.rb149
-rw-r--r--spec/requests/members/mailgun/permanent_failure_spec.rb128
-rw-r--r--spec/rubocop/formatter/todo_formatter_spec.rb9
-rw-r--r--spec/rubocop/todo_dir_spec.rb6
-rw-r--r--spec/scripts/lib/glfm/update_specification_spec.rb75
-rw-r--r--spec/services/members/mailgun/process_webhook_service_spec.rb30
-rw-r--r--spec/services/projects/update_pages_service_spec.rb12
-rw-r--r--spec/support/helpers/graphql_helpers.rb4
-rw-r--r--spec/support/helpers/subscription_portal_helper.rb13
-rw-r--r--spec/support/matchers/background_migrations_matchers.rb6
-rw-r--r--spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb138
-rw-r--r--spec/tasks/rubocop_rake_spec.rb11
132 files changed, 1818 insertions, 942 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 46ababbbca8..8aa8a81ede5 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-93762b621c011fe570339c1c247d5197c2cfefcc
+2106629e3af3e8949b23f20825d6bfee62c10992
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 32f02f10ebe..6dfe8b1298c 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-14.3.0
+14.3.1
diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json
index 1352211b927..2f8719ee6af 100644
--- a/app/assets/javascripts/editor/schema/ci.json
+++ b/app/assets/javascripts/editor/schema/ci.json
@@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://gitlab.com/.gitlab-ci.yml",
"title": "Gitlab CI configuration",
- "description": "Gitlab has a built-in solution for doing CI called Gitlab CI. It is configured by supplying a file called `.gitlab-ci.yml`, which will list all the jobs that are going to run for the project. A full list of all options can be found at https://docs.gitlab.com/ee/ci/yaml/. You can read more about Gitlab CI at https://docs.gitlab.com/ee/ci/README.html.",
+ "markdownDescription": "Gitlab has a built-in solution for doing CI called Gitlab CI. It is configured by supplying a file called `.gitlab-ci.yml`, which will list all the jobs that are going to run for the project. A full list of all options can be found [here](https://docs.gitlab.com/ee/ci/yaml). [Learn More](https://docs.gitlab.com/ee/ci/index.html).",
"type": "object",
"properties": {
"$schema": {
@@ -33,7 +33,7 @@
},
"stages": {
"type": "array",
- "description": "Groups jobs into stages. All jobs in one stage must complete before next stage is executed. Defaults to ['build', 'test', 'deploy'].",
+ "markdownDescription": "Groups jobs into stages. All jobs in one stage must complete before next stage is executed. Defaults to ['build', 'test', 'deploy']. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#stages).",
"default": ["build", "test", "deploy"],
"items": {
"type": "string"
@@ -42,7 +42,7 @@
"minItems": 1
},
"include": {
- "description": "Can be `IncludeItem` or `IncludeItem[]`. Each `IncludeItem` will be a string, or an object with properties for the method if including external YAML file. The external content will be fetched, included and evaluated along the `.gitlab-ci.yml`.",
+ "markdownDescription": "Can be `IncludeItem` or `IncludeItem[]`. Each `IncludeItem` will be a string, or an object with properties for the method if including external YAML file. The external content will be fetched, included and evaluated along the `.gitlab-ci.yml`. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#include).",
"oneOf": [
{ "$ref": "#/definitions/include_item" },
{
@@ -53,7 +53,7 @@
},
"pages": {
"$ref": "#/definitions/job",
- "description": "A special job used to upload static sites to Gitlab pages. Requires a `public/` directory with `artifacts.path` pointing to it."
+ "markdownDescription": "A special job used to upload static sites to Gitlab pages. Requires a `public/` directory with `artifacts.path` pointing to it. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#pages)."
},
"workflow": {
"type": "object",
@@ -93,12 +93,12 @@
"definitions": {
"artifacts": {
"type": "object",
- "description": "Used to specify a list of files and directories that should be attached to the job if it succeeds. Artifacts are sent to Gitlab where they can be downloaded.",
+ "markdownDescription": "Used to specify a list of files and directories that should be attached to the job if it succeeds. Artifacts are sent to Gitlab where they can be downloaded. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifacts).",
"additionalProperties": false,
"properties": {
"paths": {
"type": "array",
- "description": "A list of paths to files/folders that should be included in the artifact.",
+ "markdownDescription": "A list of paths to files/folders that should be included in the artifact. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactspaths).",
"items": {
"type": "string"
},
@@ -106,7 +106,7 @@
},
"exclude": {
"type": "array",
- "description": "A list of paths to files/folders that should be excluded in the artifact.",
+ "markdownDescription": "A list of paths to files/folders that should be excluded in the artifact. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactsexclude).",
"items": {
"type": "string"
},
@@ -114,19 +114,19 @@
},
"expose_as": {
"type": "string",
- "description": "Can be used to expose job artifacts in the merge request UI. GitLab will add a link <expose_as> to the relevant merge request that points to the artifact."
+ "markdownDescription": "Can be used to expose job artifacts in the merge request UI. GitLab will add a link <expose_as> to the relevant merge request that points to the artifact. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactsexpose_as)."
},
"name": {
"type": "string",
- "description": "Name for the archive created on job success. Can use variables in the name, e.g. '$CI_JOB_NAME'"
+ "markdownDescription": "Name for the archive created on job success. Can use variables in the name, e.g. '$CI_JOB_NAME' [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactsname)."
},
"untracked": {
"type": "boolean",
- "description": "Whether to add all untracked files (along with 'artifacts.paths') to the artifact.",
+ "markdownDescription": "Whether to add all untracked files (along with 'artifacts.paths') to the artifact. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactsuntracked).",
"default": false
},
"when": {
- "description": "Configure when artifacts are uploaded depended on job status.",
+ "markdownDescription": "Configure when artifacts are uploaded depended on job status. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactswhen).",
"default": "on_success",
"oneOf": [
{
@@ -145,12 +145,12 @@
},
"expire_in": {
"type": "string",
- "description": "How long artifacts should be kept. They are saved 30 days by default. Artifacts that have expired are removed periodically via cron job. Supports a wide variety of formats, e.g. '1 week', '3 mins 4 sec', '2 hrs 20 min', '2h20min', '6 mos 1 day', '47 yrs 6 mos and 4d', '3 weeks and 2 days'.",
+ "markdownDescription": "How long artifacts should be kept. They are saved 30 days by default. Artifacts that have expired are removed periodically via cron job. Supports a wide variety of formats, e.g. '1 week', '3 mins 4 sec', '2 hrs 20 min', '2h20min', '6 mos 1 day', '47 yrs 6 mos and 4d', '3 weeks and 2 days'. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactsexpire_in).",
"default": "30 days"
},
"reports": {
"type": "object",
- "description": "Reports will be uploaded as artifacts, and often displayed in the Gitlab UI, such as in Merge Requests.",
+ "markdownDescription": "Reports will be uploaded as artifacts, and often displayed in the Gitlab UI, such as in Merge Requests. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactsreports).",
"additionalProperties": false,
"properties": {
"junit": {
@@ -367,11 +367,11 @@
"required": ["name"]
}
],
- "description": "Specifies the docker image to use for the job or globally for all jobs. Job configuration takes precedence over global setting. Requires a certain kind of Gitlab runner executor."
+ "markdownDescription": "Specifies the docker image to use for the job or globally for all jobs. Job configuration takes precedence over global setting. Requires a certain kind of Gitlab runner executor. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#image)."
},
"services": {
"type": "array",
- "description": "Similar to `image` property, but will link the specified services to the `image` container.",
+ "markdownDescription": "Similar to `image` property, but will link the specified services to the `image` container. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#services).",
"items": {
"oneOf": [
{
@@ -418,7 +418,7 @@
},
"secrets": {
"type": "object",
- "description": "Defines secrets to be injected as environment variables",
+ "markdownDescription": "Defines secrets to be injected as environment variables. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#secrets).",
"additionalProperties": {
"type": "object",
"description": "Environment variable name",
@@ -453,7 +453,7 @@
},
"before_script": {
"type": "array",
- "description": "Defines scripts that should run *before* the job. Can be set globally or per job.",
+ "markdownDescription": "Defines scripts that should run *before* the job. Can be set globally or per job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#before_script).",
"items": {
"anyOf": [
{
@@ -470,7 +470,7 @@
},
"after_script": {
"type": "array",
- "description": "Defines scripts that should run *after* the job. Can be set globally or per job.",
+ "markdownDescription": "Defines scripts that should run *after* the job. Can be set globally or per job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#after_script).",
"items": {
"anyOf": [
{
@@ -487,7 +487,7 @@
},
"rules": {
"type": "array",
- "description": "Rules allows for an array of individual rule objects to be evaluated in order, until one matches and dynamically provides attributes to the job.",
+ "markdownDescription": "Rules allows for an array of individual rule objects to be evaluated in order, until one matches and dynamically provides attributes to the job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#rules).",
"items": {
"type": "object",
"additionalProperties": false,
@@ -503,7 +503,7 @@
}
},
"globalVariables": {
- "description": "Defines environment variables globally. Job level property overrides global variables. If a job sets `variables: {}`, all global variables are turned off. You can use the value and description keywords to define variables that are prefilled when running a pipeline manually.",
+ "markdownDescription": "Defines environment variables globally. Job level property overrides global variables. If a job sets `variables: {}`, all global variables are turned off. You can use the value and description keywords to define variables that are prefilled when running a pipeline manually. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#variables).",
"type": "object",
"additionalProperties": {
"anyOf": [
@@ -523,41 +523,41 @@
},
"if": {
"type": "string",
- "description": "Expression to evaluate whether additional attributes should be provided to the job"
+ "markdownDescription": "Expression to evaluate whether additional attributes should be provided to the job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#rulesif)."
},
"changes": {
"type": "array",
- "description": "Additional attributes will be provided to job if any of the provided paths matches a modified file",
+ "markdownDescription": "Additional attributes will be provided to job if any of the provided paths matches a modified file. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#ruleschanges).",
"items": {
"type": "string"
}
},
"exists": {
"type": "array",
- "description": "Additional attributes will be provided to job if any of the provided paths matches an existing file in the repository",
+ "markdownDescription": "Additional attributes will be provided to job if any of the provided paths matches an existing file in the repository. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#rulesexists).",
"items": {
"type": "string"
}
},
"variables": {
"type": "object",
- "description": "Defines environment variables for specific jobs. Job level property overrides global variables. If a job sets `variables: {}`, all global variables are turned off.",
+ "markdownDescription": "Defines environment variables for specific jobs. Job level property overrides global variables. If a job sets `variables: {}`, all global variables are turned off. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#rulesvariables).",
"additionalProperties": {
"type": ["string", "integer"]
}
},
"timeout": {
"type": "string",
- "description": "Allows you to configure a timeout for a specific job (e.g. `1 minute`, `1h 30m 12s`). Read more: https://docs.gitlab.com/ee/ci/yaml/README.html#timeout",
+ "markdownDescription": "Allows you to configure a timeout for a specific job (e.g. `1 minute`, `1h 30m 12s`). [Learn More](https://docs.gitlab.com/ee/ci/yaml/index.html#timeout).",
"minLength": 1
},
"start_in": {
"type": "string",
- "description": "Used in conjunction with 'when: delayed' to set how long to delay before starting a job. e.g. '5', 5 seconds, 30 minutes, 1 week, etc. Read more: https://docs.gitlab.com/ee/ci/jobs/job_control.html#run-a-job-after-a-delay",
+ "markdownDescription": "Used in conjunction with 'when: delayed' to set how long to delay before starting a job. e.g. '5', 5 seconds, 30 minutes, 1 week, etc. [Learn More](https://docs.gitlab.com/ee/ci/jobs/job_control.html#run-a-job-after-a-delay).",
"minLength": 1
},
"allow_failure": {
- "description": "Allow job to fail. A failed job does not cause the pipeline to fail.",
+ "markdownDescription": "Allow job to fail. A failed job does not cause the pipeline to fail. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#allow_failure).",
"oneOf": [
{
"description": "Setting this option to true will allow the job to fail while still letting the pipeline pass.",
@@ -594,7 +594,7 @@
]
},
"when": {
- "description": "Describes the conditions for when to run the job. Defaults to 'on_success'.",
+ "markdownDescription": "Describes the conditions for when to run the job. Defaults to 'on_success'.",
"default": "on_success",
"oneOf": [
{
@@ -611,11 +611,11 @@
},
{
"enum": ["manual"],
- "description": "Execute the job manually from Gitlab UI or API. Read more: https://docs.gitlab.com/ee/ci/yaml/#when-manual"
+ "markdownDescription": "Execute the job manually from Gitlab UI or API. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#when)."
},
{
"enum": ["delayed"],
- "description": "Execute a job after the time limit in 'start_in' expires. Read more: https://docs.gitlab.com/ee/ci/yaml/#when-delayed"
+ "markdownDescription": "Execute a job after the time limit in 'start_in' expires. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#when)."
},
{
"enum": ["never"],
@@ -626,7 +626,7 @@
"cache": {
"properties": {
"when": {
- "description": "Defines when to save the cache, based on the status of the job.",
+ "markdownDescription": "Defines when to save the cache, based on the status of the job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#cachewhen).",
"default": "on_success",
"oneOf": [
{
@@ -778,7 +778,7 @@
},
"variables": {
"type": "array",
- "description": "Filter job by checking comparing values of environment variables. Read more about variable expressions: https://docs.gitlab.com/ee/ci/variables/README.html#variables-expressions",
+ "markdownDescription": "Filter job by checking comparing values of CI/CD variables. [Learn More](https://docs.gitlab.com/ee/ci/jobs/job_control.html#cicd-variable-expressions).",
"items": {
"type": "string"
}
@@ -795,7 +795,7 @@
]
},
"retry": {
- "description": "Retry a job if it fails. Can be a simple integer or object definition.",
+ "markdownDescription": "Retry a job if it fails. Can be a simple integer or object definition. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#retry).",
"oneOf": [
{ "$ref": "#/definitions/retry_max" },
{
@@ -804,7 +804,7 @@
"properties": {
"max": { "$ref": "#/definitions/retry_max" },
"when": {
- "description": "Either a single or array of error types to trigger job retry.",
+ "markdownDescription": "Either a single or array of error types to trigger job retry. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#retrywhen).",
"oneOf": [
{ "$ref": "#/definitions/retry_errors" },
{
@@ -884,7 +884,7 @@
},
"interruptible": {
"type": "boolean",
- "description": "Interruptible is used to indicate that a job should be canceled if made redundant by a newer pipeline run.",
+ "markdownDescription": "Interruptible is used to indicate that a job should be canceled if made redundant by a newer pipeline run. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#interruptible).",
"default": false
},
"job": {
@@ -1241,11 +1241,11 @@
"description": "Limit job concurrency. Can be used to ensure that the Runner will not run certain jobs simultaneously."
},
"trigger": {
- "description": "Trigger allows you to define downstream pipeline trigger. When a job created from trigger definition is started by GitLab, a downstream pipeline gets created. Read more: https://docs.gitlab.com/ee/ci/yaml/README.html#trigger",
+ "markdownDescription": "Trigger allows you to define downstream pipeline trigger. When a job created from trigger definition is started by GitLab, a downstream pipeline gets created. [Learn More](https://docs.gitlab.com/ee/ci/yaml/index.html#trigger).",
"oneOf": [
{
"type": "object",
- "description": "Trigger a multi-project pipeline. Read more: https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html#specify-a-downstream-pipeline-branch",
+ "markdownDescription": "Trigger a multi-project pipeline. [Learn More](https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html#specify-a-downstream-pipeline-branch).",
"additionalProperties": false,
"properties": {
"project": {
@@ -1287,7 +1287,7 @@
},
{
"type": "object",
- "description": "Trigger a child pipeline. Read more: https://docs.gitlab.com/ee/ci/pipelines/parent_child_pipelines.html",
+ "description": "Trigger a child pipeline. [Learn More](https://docs.gitlab.com/ee/ci/pipelines/parent_child_pipelines.html).",
"additionalProperties": false,
"properties": {
"include": {
@@ -1398,7 +1398,7 @@
}
},
{
- "description": "Path to the project, e.g. `group/project`, or `group/sub-group/project`. Read more: https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html#define-multi-project-pipelines-in-your-gitlab-ciyml-file",
+ "markdownDescription": "Path to the project, e.g. `group/project`, or `group/sub-group/project`. [Learn More](https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html#define-multi-project-pipelines-in-your-gitlab-ciyml-file).",
"type": "string",
"pattern": "\\S/\\S"
}
@@ -1406,10 +1406,10 @@
},
"inherit": {
"type": "object",
- "description": "Controls inheritance of globally-defined defaults and variables. Boolean values control inheritance of all default: or variables: keywords. To inherit only a subset of default: or variables: keywords, specify what you wish to inherit. Anything not listed is not inherited.",
+ "markdownDescription": "Controls inheritance of globally-defined defaults and variables. Boolean values control inheritance of all default: or variables: keywords. To inherit only a subset of default: or variables: keywords, specify what you wish to inherit. Anything not listed is not inherited. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#inherit).",
"properties": {
"default": {
- "description": "Whether to inherit all globally-defined defaults or not. Or subset of inherited defaults",
+ "markdownDescription": "Whether to inherit all globally-defined defaults or not. Or subset of inherited defaults. [Learn more](https://docs.gitlab.com/ee/ci/yaml/#inheritdefault).",
"oneOf": [
{
"type": "boolean"
@@ -1435,7 +1435,7 @@
]
},
"variables": {
- "description": "Whether to inherit all globally-defined variables or not. Or subset of inherited variables",
+ "markdownDescription": "Whether to inherit all globally-defined variables or not. Or subset of inherited variables. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#inheritvariables).",
"oneOf": [
{ "type": "boolean" },
{
@@ -1470,7 +1470,7 @@
},
"tags": {
"type": "array",
- "description": "Used to select runners from the list of available runners. A runner must have all tags listed here to run the job.",
+ "markdownDescription": "Used to select runners from the list of available runners. A runner must have all tags listed here to run the job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#tags).",
"items": {
"type": "string"
}
diff --git a/app/assets/javascripts/issuable/issuable_form.js b/app/assets/javascripts/issuable/issuable_form.js
index 8e76a33c7dd..38453072af8 100644
--- a/app/assets/javascripts/issuable/issuable_form.js
+++ b/app/assets/javascripts/issuable/issuable_form.js
@@ -46,6 +46,9 @@ function getFallbackKey() {
export default class IssuableForm {
constructor(form) {
+ if (form.length === 0) {
+ return;
+ }
this.form = form;
this.toggleWip = this.toggleWip.bind(this);
this.renderWipExplanation = this.renderWipExplanation.bind(this);
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 b81ab103271..9d8b339e813 100644
--- a/app/assets/javascripts/issues/list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue
@@ -23,6 +23,7 @@ import CsvImportExportButtons from '~/issuable/components/csv_import_export_butt
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
import { IssuableStatus } from '~/issues/constants';
import axios from '~/lib/utils/axios_utils';
+import { isPositiveInteger } from '~/lib/utils/number_utils';
import { scrollUp } from '~/lib/utils/scroll_utils';
import { getParameterByName, joinPaths } from '~/lib/utils/url_utility';
import {
@@ -45,6 +46,8 @@ import {
ISSUE_REFERENCE,
MAX_LIST_SIZE,
PAGE_SIZE,
+ PARAM_FIRST_PAGE_SIZE,
+ PARAM_LAST_PAGE_SIZE,
PARAM_PAGE_AFTER,
PARAM_PAGE_BEFORE,
PARAM_SORT,
@@ -390,12 +393,14 @@ export default {
},
urlParams() {
return {
- page_after: this.pageParams.afterCursor,
- page_before: this.pageParams.beforeCursor,
search: this.searchQuery,
sort: urlSortParams[this.sortKey],
state: this.state,
...this.urlFilterParams,
+ first_page_size: this.pageParams.firstPageSize,
+ last_page_size: this.pageParams.lastPageSize,
+ page_after: this.pageParams.afterCursor,
+ page_before: this.pageParams.beforeCursor,
};
},
hasCrmParameter() {
@@ -632,6 +637,8 @@ export default {
this.showBulkEditSidebar = showBulkEditSidebar;
},
updateData(sortValue) {
+ const firstPageSize = getParameterByName(PARAM_FIRST_PAGE_SIZE);
+ const lastPageSize = getParameterByName(PARAM_LAST_PAGE_SIZE);
const pageAfter = getParameterByName(PARAM_PAGE_AFTER);
const pageBefore = getParameterByName(PARAM_PAGE_BEFORE);
const state = getParameterByName(PARAM_STATE);
@@ -660,7 +667,13 @@ export default {
this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery();
this.filterTokens = isSearchDisabled ? [] : getFilterTokens(window.location.search);
- this.pageParams = getInitialPageParams(sortKey, pageAfter, pageBefore);
+ this.pageParams = getInitialPageParams(
+ sortKey,
+ isPositiveInteger(firstPageSize) ? parseInt(firstPageSize, 10) : undefined,
+ isPositiveInteger(lastPageSize) ? parseInt(lastPageSize, 10) : undefined,
+ pageAfter,
+ pageBefore,
+ );
this.sortKey = sortKey;
this.state = state || IssuableStates.Opened;
},
diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js
index 0795df10a7c..c7098d048ad 100644
--- a/app/assets/javascripts/issues/list/constants.js
+++ b/app/assets/javascripts/issues/list/constants.js
@@ -57,6 +57,8 @@ export const MAX_LIST_SIZE = 10;
export const PAGE_SIZE = 20;
export const PAGE_SIZE_MANUAL = 100;
export const PARAM_ASSIGNEE_ID = 'assignee_id';
+export const PARAM_FIRST_PAGE_SIZE = 'first_page_size';
+export const PARAM_LAST_PAGE_SIZE = 'last_page_size';
export const PARAM_PAGE_AFTER = 'page_after';
export const PARAM_PAGE_BEFORE = 'page_before';
export const PARAM_SORT = 'sort';
diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js
index 3ca93069628..dfdc6e27f0d 100644
--- a/app/assets/javascripts/issues/list/utils.js
+++ b/app/assets/javascripts/issues/list/utils.js
@@ -46,8 +46,15 @@ import {
WEIGHT_DESC,
} from './constants';
-export const getInitialPageParams = (sortKey, afterCursor, beforeCursor) => ({
- firstPageSize: sortKey === RELATIVE_POSITION_ASC ? PAGE_SIZE_MANUAL : PAGE_SIZE,
+export const getInitialPageParams = (
+ sortKey,
+ firstPageSize = sortKey === RELATIVE_POSITION_ASC ? PAGE_SIZE_MANUAL : PAGE_SIZE,
+ lastPageSize,
+ afterCursor,
+ beforeCursor,
+) => ({
+ firstPageSize: lastPageSize ? undefined : firstPageSize,
+ lastPageSize,
afterCursor,
beforeCursor,
});
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
index 15d92ab0ef7..9176210eb22 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
@@ -25,6 +25,7 @@ import {
NOT_AVAILABLE_TEXT,
NOT_AVAILABLE_SIZE,
MORE_ACTIONS_TEXT,
+ COPY_IMAGE_PATH_TITLE,
} from '../../constants/index';
export default {
@@ -72,6 +73,7 @@ export default {
CONFIGURATION_DETAILS_ROW_TEST,
MISSING_MANIFEST_WARNING_TOOLTIP,
MORE_ACTIONS_TEXT,
+ COPY_IMAGE_PATH_TITLE,
},
computed: {
formattedSize() {
@@ -138,7 +140,7 @@ export default {
<clipboard-button
v-if="tag.location"
- :title="tag.location"
+ :title="$options.i18n.COPY_IMAGE_PATH_TITLE"
:text="tag.location"
category="tertiary"
:disabled="disabled"
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
index d76a8245b63..e67d77210bb 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
@@ -14,6 +14,7 @@ import {
IMAGE_FAILED_DELETED_STATUS,
IMAGE_MIGRATING_STATE,
ROOT_IMAGE_TEXT,
+ COPY_IMAGE_PATH_TITLE,
} from '../../constants/index';
import DeleteButton from '../delete_button.vue';
import CleanupStatus from './cleanup_status.vue';
@@ -52,6 +53,7 @@ export default {
i18n: {
REMOVE_REPOSITORY_LABEL,
ROW_SCHEDULED_FOR_DELETION,
+ COPY_IMAGE_PATH_TITLE,
},
computed: {
disabledDelete() {
@@ -115,7 +117,7 @@ export default {
v-if="item.location"
:disabled="deleting"
:text="item.location"
- :title="item.location"
+ :title="$options.i18n.COPY_IMAGE_PATH_TITLE"
category="tertiary"
/>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
index ceaf8a65a10..c6a7591e0d9 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
@@ -41,6 +41,8 @@ export const EMPTY_RESULT_MESSAGE = s__(
'ContainerRegistry|To widen your search, change or remove the filters above.',
);
+export const COPY_IMAGE_PATH_TITLE = s__('ContainerRegistry|Copy image path');
+
// Parameters
export const IMAGE_DELETE_SCHEDULED_STATUS = 'DELETE_SCHEDULED';
diff --git a/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue b/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue
index 58df98d0fb7..8e95fad1e48 100644
--- a/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue
+++ b/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue
@@ -1,7 +1,6 @@
<script>
import { GlButton } from '@gitlab/ui';
import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.query.graphql';
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { EDITOR_APP_STATUS_EMPTY, EDITOR_APP_STATUS_LOADING } from '../../constants';
import FileTreePopover from '../popovers/file_tree_popover.vue';
import BranchSwitcher from './branch_switcher.vue';
@@ -12,7 +11,6 @@ export default {
FileTreePopover,
GlButton,
},
- mixins: [glFeatureFlagMixin()],
props: {
hasUnsavedChanges: {
type: Boolean,
@@ -43,11 +41,7 @@ export default {
return this.appStatus === EDITOR_APP_STATUS_LOADING;
},
showFileTreeToggle() {
- return (
- this.glFeatures.pipelineEditorFileTree &&
- !this.isNewCiConfigFile &&
- this.appStatus !== EDITOR_APP_STATUS_EMPTY
- );
+ return !this.isNewCiConfigFile && this.appStatus !== EDITOR_APP_STATUS_EMPTY;
},
},
methods: {
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
index 59022a91322..f26cdd8b017 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
@@ -1,7 +1,6 @@
<script>
import { GlModal } from '@gitlab/ui';
import { __ } from '~/locale';
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import CommitSection from './components/commit/commit_section.vue';
import PipelineEditorDrawer from './components/drawer/pipeline_editor_drawer.vue';
import PipelineEditorFileNav from './components/file_nav/pipeline_editor_file_nav.vue';
@@ -34,7 +33,6 @@ export default {
PipelineEditorHeader,
PipelineEditorTabs,
},
- mixins: [glFeatureFlagMixin()],
props: {
ciConfigData: {
type: Object,
@@ -76,9 +74,6 @@ export default {
includesFiles() {
return this.ciConfigData?.includes || [];
},
- isFileTreeVisible() {
- return this.showFileTree && this.glFeatures.pipelineEditorFileTree;
- },
},
mounted() {
this.showFileTree = JSON.parse(localStorage.getItem(FILE_TREE_DISPLAY_KEY)) || false;
@@ -140,7 +135,7 @@ export default {
/>
<div class="gl-display-flex gl-w-full gl-sm-flex-direction-column">
<pipeline-editor-file-tree
- v-if="isFileTreeVisible"
+ v-if="showFileTree"
class="gl-flex-shrink-0"
:includes="includesFiles"
/>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue
index 0e31f97b9db..b1c4f7c5a7c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue
@@ -98,10 +98,10 @@ export default {
<template>
<div data-qa-selector="approvals_summary_content">
- <strong>{{ approvalLeftMessage }}</strong>
+ <span class="gl-font-weight-bold">{{ approvalLeftMessage }}</span>
<template v-if="hasApprovers">
<span v-if="approvalLeftMessage">{{ message }}</span>
- <strong v-else>{{ message }}</strong>
+ <span v-else class="gl-font-weight-bold">{{ message }}</span>
<user-avatar-list
class="gl-display-inline-block gl-vertical-align-middle"
:img-size="24"
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index f76a0cbbae8..7c03a2bb049 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -234,6 +234,8 @@
.navbar-sub-nav {
display: flex;
+ align-items: center;
+ height: 100%;
margin: 0 0 0 6px;
.dropdown-chevron {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index bc649b6407d..194ccb6b73e 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -439,7 +439,7 @@ $browser-scrollbar-size: 10px;
/*
* Misc
*/
-$header-height: var(--header-height, 40px);
+$header-height: var(--header-height, 48px);
$header-zindex: 1000;
$zindex-dropdown-menu: 300;
$suggestion-header-height: 46px;
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index 001431e517b..b2a5906bb01 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -755,7 +755,7 @@ input {
padding: 0 16px;
z-index: 1000;
margin-bottom: 0;
- min-height: var(--header-height, 40px);
+ min-height: var(--header-height, 48px);
border: 0;
position: fixed;
top: 0;
@@ -771,7 +771,7 @@ input {
display: flex;
justify-content: space-between;
position: relative;
- min-height: var(--header-height, 40px);
+ min-height: var(--header-height, 48px);
padding-left: 0;
}
.navbar-gitlab .header-content .title {
@@ -787,9 +787,6 @@ input {
.navbar-gitlab .header-content .title img {
height: 24px;
}
-.navbar-gitlab .header-content .title img + .logo-text {
- margin-left: 8px;
-}
.navbar-gitlab .header-content .title a {
display: flex;
align-items: center;
@@ -915,6 +912,8 @@ input {
}
.navbar-sub-nav {
display: flex;
+ align-items: center;
+ height: 100%;
margin: 0 0 0 6px;
}
.caret-down,
@@ -1034,7 +1033,7 @@ input {
left: 0;
z-index: 600;
width: 220px;
- top: var(--header-height, 40px);
+ top: var(--header-height, 48px);
background-color: #303030;
transform: translate3d(0, 0, 0);
}
@@ -2044,19 +2043,9 @@ body.gl-dark {
.gl-display-none {
display: none;
}
-@media (min-width: 992px) {
- .gl-lg-display-none\! {
- display: none !important;
- }
-}
.gl-display-flex {
display: flex;
}
-@media (min-width: 992px) {
- .gl-lg-display-flex {
- display: flex;
- }
-}
@media (min-width: 576px) {
.gl-sm-display-block {
display: block;
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index c42b5554d8d..37d0c624eea 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -740,7 +740,7 @@ input {
padding: 0 16px;
z-index: 1000;
margin-bottom: 0;
- min-height: var(--header-height, 40px);
+ min-height: var(--header-height, 48px);
border: 0;
position: fixed;
top: 0;
@@ -756,7 +756,7 @@ input {
display: flex;
justify-content: space-between;
position: relative;
- min-height: var(--header-height, 40px);
+ min-height: var(--header-height, 48px);
padding-left: 0;
}
.navbar-gitlab .header-content .title {
@@ -772,9 +772,6 @@ input {
.navbar-gitlab .header-content .title img {
height: 24px;
}
-.navbar-gitlab .header-content .title img + .logo-text {
- margin-left: 8px;
-}
.navbar-gitlab .header-content .title a {
display: flex;
align-items: center;
@@ -900,6 +897,8 @@ input {
}
.navbar-sub-nav {
display: flex;
+ align-items: center;
+ height: 100%;
margin: 0 0 0 6px;
}
.caret-down,
@@ -1019,7 +1018,7 @@ input {
left: 0;
z-index: 600;
width: 220px;
- top: var(--header-height, 40px);
+ top: var(--header-height, 48px);
background-color: #f0f0f0;
transform: translate3d(0, 0, 0);
}
@@ -1704,19 +1703,9 @@ svg.s16 {
.gl-display-none {
display: none;
}
-@media (min-width: 992px) {
- .gl-lg-display-none\! {
- display: none !important;
- }
-}
.gl-display-flex {
display: flex;
}
-@media (min-width: 992px) {
- .gl-lg-display-flex {
- display: flex;
- }
-}
@media (min-width: 576px) {
.gl-sm-display-block {
display: block;
diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss
index 020ed9c040b..7ac1a187bb8 100644
--- a/app/assets/stylesheets/startup/startup-signin.scss
+++ b/app/assets/stylesheets/startup/startup-signin.scss
@@ -419,7 +419,7 @@ body.navless {
}
}
.navless-container {
- margin-top: var(--header-height, 40px);
+ margin-top: var(--header-height, 48px);
padding-top: 32px;
}
.btn {
@@ -506,7 +506,7 @@ label.label-bold {
}
.navbar-empty {
justify-content: center;
- height: var(--header-height, 40px);
+ height: var(--header-height, 48px);
background: #fff;
border-bottom: 1px solid #dbdbdb;
}
diff --git a/app/controllers/mailgun/webhooks_controller.rb b/app/controllers/mailgun/webhooks_controller.rb
new file mode 100644
index 00000000000..faeda99769f
--- /dev/null
+++ b/app/controllers/mailgun/webhooks_controller.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+module Mailgun
+ class WebhooksController < ApplicationController
+ respond_to :json
+
+ skip_before_action :authenticate_user!
+ skip_before_action :verify_authenticity_token
+
+ before_action :ensure_feature_enabled!
+ before_action :authenticate_signature!
+
+ feature_category :team_planning
+
+ WEBHOOK_PROCESSORS = [
+ ::Members::Mailgun::ProcessWebhookService
+ ].freeze
+
+ def process_webhook
+ WEBHOOK_PROCESSORS.each do |processor_class|
+ processor = processor_class.new(params['event-data'] || {})
+ processor.execute if processor.should_process?
+ end
+
+ head :ok
+ end
+
+ private
+
+ def ensure_feature_enabled!
+ render_406 unless Gitlab::CurrentSettings.mailgun_events_enabled?
+ end
+
+ def authenticate_signature!
+ access_denied! unless valid_signature?
+ end
+
+ def valid_signature?
+ return false if Gitlab::CurrentSettings.mailgun_signing_key.blank?
+
+ # per this guide: https://documentation.mailgun.com/en/latest/user_manual.html#webhooks
+ digest = OpenSSL::Digest.new('SHA256')
+ data = [params.dig(:signature, :timestamp), params.dig(:signature, :token)].join
+
+ hmac_digest = OpenSSL::HMAC.hexdigest(digest, Gitlab::CurrentSettings.mailgun_signing_key, data)
+
+ ActiveSupport::SecurityUtils.secure_compare(params.dig(:signature, :signature), hmac_digest)
+ end
+
+ def render_406
+ # failure to stop retries per https://documentation.mailgun.com/en/latest/user_manual.html#webhooks
+ head :not_acceptable
+ end
+ end
+end
diff --git a/app/controllers/members/mailgun/permanent_failures_controller.rb b/app/controllers/members/mailgun/permanent_failures_controller.rb
deleted file mode 100644
index 685faa34694..00000000000
--- a/app/controllers/members/mailgun/permanent_failures_controller.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-# frozen_string_literal: true
-
-module Members
- module Mailgun
- class PermanentFailuresController < ApplicationController
- respond_to :json
-
- skip_before_action :authenticate_user!
- skip_before_action :verify_authenticity_token
-
- before_action :ensure_feature_enabled!
- before_action :authenticate_signature!
- before_action :validate_invite_email!
-
- feature_category :authentication_and_authorization
-
- def create
- webhook_processor.execute
-
- head :ok
- end
-
- private
-
- def ensure_feature_enabled!
- render_406 unless Gitlab::CurrentSettings.mailgun_events_enabled?
- end
-
- def authenticate_signature!
- access_denied! unless valid_signature?
- end
-
- def valid_signature?
- return false if Gitlab::CurrentSettings.mailgun_signing_key.blank?
-
- # per this guide: https://documentation.mailgun.com/en/latest/user_manual.html#webhooks
- digest = OpenSSL::Digest.new('SHA256')
- data = [params.dig(:signature, :timestamp), params.dig(:signature, :token)].join
-
- hmac_digest = OpenSSL::HMAC.hexdigest(digest, Gitlab::CurrentSettings.mailgun_signing_key, data)
-
- ActiveSupport::SecurityUtils.secure_compare(params.dig(:signature, :signature), hmac_digest)
- end
-
- def validate_invite_email!
- # permanent_failures webhook does not provide a way to filter failures, so we'll get them all on this endpoint
- # and we only care about our invite_emails
- render_406 unless payload[:tags]&.include?(::Members::Mailgun::INVITE_EMAIL_TAG)
- end
-
- def webhook_processor
- ::Members::Mailgun::ProcessWebhookService.new(payload)
- end
-
- def payload
- @payload ||= params.permit!['event-data']
- end
-
- def render_406
- # failure to stop retries per https://documentation.mailgun.com/en/latest/user_manual.html#webhooks
- head :not_acceptable
- end
- end
- end
-end
diff --git a/app/controllers/projects/ci/pipeline_editor_controller.rb b/app/controllers/projects/ci/pipeline_editor_controller.rb
index dbf3b2051fb..84e5d59a2c3 100644
--- a/app/controllers/projects/ci/pipeline_editor_controller.rb
+++ b/app/controllers/projects/ci/pipeline_editor_controller.rb
@@ -4,7 +4,6 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController
before_action :check_can_collaborate!
before_action do
push_frontend_feature_flag(:schema_linting, @project)
- push_frontend_feature_flag(:pipeline_editor_file_tree, @project)
end
feature_category :pipeline_authoring
diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
index a72b9a09118..697cc6f5b03 100644
--- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb
+++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
@@ -52,6 +52,7 @@ module ResolvesMergeRequests
security_auto_fix: [:author],
head_pipeline: [:merge_request_diff, { head_pipeline: [:merge_request] }],
timelogs: [:timelogs],
+ pipelines: [:merge_request_diffs], # used by `recent_diff_head_shas` to load pipelines
committers: [merge_request_diff: [:merge_request_diff_commits]]
}
end
diff --git a/app/graphql/resolvers/merge_request_pipelines_resolver.rb b/app/graphql/resolvers/merge_request_pipelines_resolver.rb
index f84eedb4c3b..deb698c63e1 100644
--- a/app/graphql/resolvers/merge_request_pipelines_resolver.rb
+++ b/app/graphql/resolvers/merge_request_pipelines_resolver.rb
@@ -21,8 +21,9 @@ module Resolvers
super
end
- def query_for(args)
- resolve_pipelines(project, args).merge(merge_request.all_pipelines)
+ def query_for(input)
+ mr, args = input
+ resolve_pipelines(mr.source_project, args).merge(mr.all_pipelines)
end
def model_class
@@ -30,7 +31,7 @@ module Resolvers
end
def query_input(**args)
- args
+ [merge_request, args]
end
def project
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 39b5949ea7a..57eaf27069d 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1696,7 +1696,12 @@ class MergeRequest < ApplicationRecord
service_class.new(project, current_user, id: id, report_type: report_type).execute(comparison_base_pipeline(identifier), actual_head_pipeline)
end
- def recent_diff_head_shas(limit = 100)
+ MAX_RECENT_DIFF_HEAD_SHAS = 100
+
+ def recent_diff_head_shas(limit = MAX_RECENT_DIFF_HEAD_SHAS)
+ # see MergeRequestDiff.recent
+ return merge_request_diffs.to_a.sort_by(&:id).reverse.first(limit).pluck(:head_commit_sha) if merge_request_diffs.loaded?
+
merge_request_diffs.recent(limit).pluck(:head_commit_sha)
end
diff --git a/app/services/members/mailgun/process_webhook_service.rb b/app/services/members/mailgun/process_webhook_service.rb
index e359a83ad42..c0a9c2d5290 100644
--- a/app/services/members/mailgun/process_webhook_service.rb
+++ b/app/services/members/mailgun/process_webhook_service.rb
@@ -16,6 +16,10 @@ module Members
Gitlab::ErrorTracking.track_exception(e)
end
+ def should_process?
+ payload['event'] == 'failed' && payload['severity'] == 'permanent' && payload['tags']&.include?(::Members::Mailgun::INVITE_EMAIL_TAG)
+ end
+
private
attr_reader :payload, :member
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 2ec965fe2f4..c6ea364320f 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -30,6 +30,7 @@ module Projects
validate_state!
validate_max_size!
+ validate_public_folder!
validate_max_entries!
build.artifacts_file.use_file do |artifacts_path|
@@ -180,6 +181,10 @@ module Projects
end
end
+ def validate_public_folder!
+ raise InvalidStateError, 'Error: The `public/` folder is missing, or not declared in `.gitlab-ci.yml`.' unless total_size > 0
+ end
+
def entries_count
# we're using the full archive and pages daemon needs to read it
# so we want the total count from entries, not only "public/" directory
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 3cae8186750..4ba2a2d4fe4 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -9,10 +9,7 @@
%h1.title
%span.gl-sr-only GitLab
= link_to root_path, title: _('Dashboard'), id: 'logo', **tracking_attrs('main_navigation', 'click_gitlab_logo_link', 'navigation') do
- %span{ :class => "gl-display-none gl-lg-display-flex" }
- = brand_header_logo({add_gitlab_white_text: true})
- %span{ :class => "gl-lg-display-none! gl-display-flex" }
- = brand_header_logo
+ = brand_header_logo
- if Gitlab.com_and_canary?
= link_to Gitlab::Saas.canary_toggle_com_url, class: 'canary-badge bg-transparent', data: { qa_selector: 'canary_badge_link' }, target: :_blank, rel: 'noopener noreferrer' do
= gl_badge_tag({ variant: :success, size: :sm }) do
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index 88ca64f2af0..1d53726e25c 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -1,3 +1,5 @@
- page_title _('Edit Release')
+- add_to_breadcrumbs _('Releases'), project_releases_path(@project)
+- add_to_breadcrumbs @release.name, project_release_path(@project, @release)
#js-edit-release-page{ data: data_for_edit_release_page }
diff --git a/app/workers/database/batched_background_migration/single_database_worker.rb b/app/workers/database/batched_background_migration/single_database_worker.rb
index aeadda4b8e1..8a0de268048 100644
--- a/app/workers/database/batched_background_migration/single_database_worker.rb
+++ b/app/workers/database/batched_background_migration/single_database_worker.rb
@@ -7,6 +7,7 @@ module Database
include ApplicationWorker
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+ include Gitlab::Utils::StrongMemoize
LEASE_TIMEOUT_MULTIPLIER = 3
MINIMUM_LEASE_TIMEOUT = 10.minutes.freeze
@@ -44,6 +45,15 @@ module Database
return
end
+ if shares_db_config?
+ Sidekiq.logger.info(
+ class: self.class.name,
+ database: self.class.tracking_database,
+ message: 'skipping migration execution for database that shares database configuration with another database')
+
+ return
+ end
+
Gitlab::Database::SharedModel.using_connection(base_model.connection) do
break unless self.class.enabled? && active_migration
@@ -63,7 +73,7 @@ module Database
private
def active_migration
- @active_migration ||= Gitlab::Database::BackgroundMigration::BatchedMigration.active_migration
+ @active_migration ||= Gitlab::Database::BackgroundMigration::BatchedMigration.active_migration(connection: base_model.connection)
end
def run_active_migration
@@ -71,7 +81,13 @@ module Database
end
def base_model
- @base_model ||= Gitlab::Database.database_base_models[self.class.tracking_database]
+ strong_memoize(:base_model) do
+ Gitlab::Database.database_base_models[self.class.tracking_database]
+ end
+ end
+
+ def shares_db_config?
+ base_model && Gitlab::Database.db_config_share_with(base_model.connection_db_config).present?
end
def with_exclusive_lease(interval)
diff --git a/app/workers/namespaces/process_sync_events_worker.rb b/app/workers/namespaces/process_sync_events_worker.rb
index 269710dd804..ea28fcd8720 100644
--- a/app/workers/namespaces/process_sync_events_worker.rb
+++ b/app/workers/namespaces/process_sync_events_worker.rb
@@ -13,7 +13,7 @@ module Namespaces
urgency :high
idempotent!
- deduplicate :until_executing
+ deduplicate :until_executed
def perform
results = ::Ci::ProcessSyncEventsService.new(
diff --git a/app/workers/projects/process_sync_events_worker.rb b/app/workers/projects/process_sync_events_worker.rb
index 1330ae47a68..6b6b4e3db7f 100644
--- a/app/workers/projects/process_sync_events_worker.rb
+++ b/app/workers/projects/process_sync_events_worker.rb
@@ -13,7 +13,7 @@ module Projects
urgency :high
idempotent!
- deduplicate :until_executing
+ deduplicate :until_executed
def perform
results = ::Ci::ProcessSyncEventsService.new(
diff --git a/config/database.yml.decomposed-postgresql b/config/database.yml.decomposed-postgresql
index 23c7f052f5a..04add959ad4 100644
--- a/config/database.yml.decomposed-postgresql
+++ b/config/database.yml.decomposed-postgresql
@@ -16,6 +16,14 @@ production:
username: git
password: "secure password"
host: localhost
+ geo:
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_production
+ username: git
+ password: "secure password"
+ host: localhost
+
#
# Development specific
#
@@ -38,6 +46,13 @@ development:
host: localhost
variables:
statement_timeout: 15s
+ geo:
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_development
+ username: postgres
+ password: "secure password"
+ host: localhost
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
@@ -63,3 +78,10 @@ test: &test
prepared_statements: false
variables:
statement_timeout: 15s
+ geo:
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_test
+ username: postgres
+ password:
+ host: localhost
diff --git a/config/database.yml.postgresql b/config/database.yml.postgresql
index a4daab1fd0c..5329a8e9fd7 100644
--- a/config/database.yml.postgresql
+++ b/config/database.yml.postgresql
@@ -18,6 +18,13 @@ production:
# port: 8600
# record: secondary.postgresql.service.consul
# interval: 300
+ geo:
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_production
+ username: git
+ password: "secure password"
+ host: localhost
#
# Development specific
@@ -32,6 +39,13 @@ development:
host: localhost
variables:
statement_timeout: 15s
+ geo:
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_development
+ username: postgres
+ password: "secure password"
+ host: localhost
#
# Staging specific
@@ -44,6 +58,13 @@ staging:
username: git
password: "secure password"
host: localhost
+ geo:
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_staging
+ username: git
+ password: "secure password"
+ host: localhost
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
@@ -59,3 +80,10 @@ test: &test
prepared_statements: false
variables:
statement_timeout: 15s
+ geo:
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_test
+ username: postgres
+ password:
+ host: localhost
diff --git a/config/database_geo.yml.postgresql b/config/database_geo.yml.postgresql
deleted file mode 100644
index 25b9c6d5eb1..00000000000
--- a/config/database_geo.yml.postgresql
+++ /dev/null
@@ -1,43 +0,0 @@
-#
-# PRODUCTION
-#
-production:
- adapter: postgresql
- encoding: unicode
- database: gitlabhq_geo_production
- username: git
- password: "secure password"
- host: localhost
-
-#
-# Development specific
-#
-development:
- adapter: postgresql
- encoding: unicode
- database: gitlabhq_geo_development
- username: postgres
- password: "secure password"
- host: localhost
-
-#
-# Staging specific
-#
-staging:
- adapter: postgresql
- encoding: unicode
- database: gitlabhq_geo_staging
- username: git
- password: "secure password"
- host: localhost
-
-# Warning: The database defined as "test" will be erased and
-# re-generated from your development database when you run "rake".
-# Do not set this db to the same as development or production.
-test: &test
- adapter: postgresql
- encoding: unicode
- database: gitlabhq_geo_test
- username: postgres
- password:
- host: localhost
diff --git a/config/feature_flags/development/pipeline_editor_file_tree.yml b/config/feature_flags/development/ci_show_all_projects_with_usage_sorted_descending.yml
index db31133a530..871cb26b316 100644
--- a/config/feature_flags/development/pipeline_editor_file_tree.yml
+++ b/config/feature_flags/development/ci_show_all_projects_with_usage_sorted_descending.yml
@@ -1,8 +1,8 @@
---
-name: pipeline_editor_file_tree
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83910
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/357219
+name: ci_show_all_projects_with_usage_sorted_descending
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83680
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/362331
milestone: '15.0'
type: development
-group: group::pipeline authoring
+group: group::pipeline execution
default_enabled: false
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 05d480f267e..2a9ce327d9d 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -711,7 +711,7 @@ Gitlab.ee do
Settings.cron_jobs['ldap_sync_worker']['job_class'] = 'LdapSyncWorker'
Settings.cron_jobs['free_user_cap_data_remediation'] ||= Settingslogic.new({})
Settings.cron_jobs['free_user_cap_data_remediation']['cron'] ||= '17 6,10,14,18 * * *'
- Settings.cron_jobs['free_user_cap_data_remediation']['job_class'] = 'Namespaces::FreeUserCapWorker'
+ Settings.cron_jobs['free_user_cap_data_remediation']['job_class'] = 'Namespaces::FreeUserCap::RemediationWorker'
Settings.cron_jobs['update_max_seats_used_for_gitlab_com_subscriptions_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['update_max_seats_used_for_gitlab_com_subscriptions_worker']['cron'] ||= '0 12 * * *'
Settings.cron_jobs['update_max_seats_used_for_gitlab_com_subscriptions_worker']['job_class'] = 'UpdateMaxSeatsUsedForGitlabComSubscriptionsWorker'
diff --git a/config/initializers/database_config.rb b/config/initializers/database_config.rb
index 050ab1d9b3e..84ef0bc9f16 100644
--- a/config/initializers/database_config.rb
+++ b/config/initializers/database_config.rb
@@ -1,14 +1,6 @@
# frozen_string_literal: true
Gitlab.ee do
- # We need to initialize the Geo database before
- # setting the Geo DB connection pool size.
- if File.exist?(Rails.root.join('config/database_geo.yml'))
- Rails.application.configure do
- config.geo_database = config_for(:database_geo)
- end
- end
-
if Gitlab::Runtime.sidekiq? && Gitlab::Geo.geo_database_configured?
# The Geo::TrackingBase model does not yet use connects_to. So,
# this will not properly support geo: from config/databse.yml
diff --git a/config/routes.rb b/config/routes.rb
index b2b4bece68a..20098c422ec 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -228,7 +228,12 @@ Rails.application.routes.draw do
draw :snippets
draw :profile
- draw :members
+
+ post '/mailgun/webhooks' => 'mailgun/webhooks#process_webhook'
+
+ # Deprecated route for permanent failures
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/362606
+ post '/members/mailgun/permanent_failures' => 'mailgun/webhooks#process_webhook'
# Product analytics collector
match '/collector/i', to: ProductAnalytics::CollectorApp.new, via: :all
diff --git a/config/routes/members.rb b/config/routes/members.rb
deleted file mode 100644
index e84f0987171..00000000000
--- a/config/routes/members.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-namespace :members do
- namespace :mailgun do
- resources :permanent_failures, only: [:create]
- end
-end
diff --git a/db/migrate/20220503035221_add_gitlab_schema_to_batched_background_migrations.rb b/db/migrate/20220503035221_add_gitlab_schema_to_batched_background_migrations.rb
new file mode 100644
index 00000000000..eaebe979ff3
--- /dev/null
+++ b/db/migrate/20220503035221_add_gitlab_schema_to_batched_background_migrations.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddGitlabSchemaToBatchedBackgroundMigrations < Gitlab::Database::Migration[2.0]
+ # rubocop:disable Migration/AddLimitToTextColumns
+ # limit is added in 20220503035437_add_text_limit_to_batched_background_migrations_gitlab_schema
+ def change
+ add_column :batched_background_migrations, :gitlab_schema, :text, null: false, default: :gitlab_main
+ change_column_default(:batched_background_migrations, :gitlab_schema, from: :gitlab_main, to: nil)
+ end
+ # rubocop:enable Migration/AddLimitToTextColumns
+end
diff --git a/db/migrate/20220503035437_add_text_limit_to_batched_background_migrations_gitlab_schema.rb b/db/migrate/20220503035437_add_text_limit_to_batched_background_migrations_gitlab_schema.rb
new file mode 100644
index 00000000000..d5629cd1b8d
--- /dev/null
+++ b/db/migrate/20220503035437_add_text_limit_to_batched_background_migrations_gitlab_schema.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddTextLimitToBatchedBackgroundMigrationsGitlabSchema < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :batched_background_migrations, :gitlab_schema, 255
+ end
+
+ def down
+ remove_text_limit :batched_background_migrations, :gitlab_schema
+ end
+end
diff --git a/db/migrate/20220512020500_index_batched_migrations_on_gitlab_schema_and_configuration.rb b/db/migrate/20220512020500_index_batched_migrations_on_gitlab_schema_and_configuration.rb
new file mode 100644
index 00000000000..077a60856cd
--- /dev/null
+++ b/db/migrate/20220512020500_index_batched_migrations_on_gitlab_schema_and_configuration.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class IndexBatchedMigrationsOnGitlabSchemaAndConfiguration < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ TABLE_NAME = :batched_background_migrations
+ INDEX_NAME = 'index_batched_migrations_on_gl_schema_and_unique_configuration'
+
+ def up
+ add_concurrent_index TABLE_NAME,
+ %i[gitlab_schema job_class_name table_name column_name job_arguments],
+ unique: true,
+ name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name TABLE_NAME, INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20210727113447_backfill_integrations_type_new.rb b/db/post_migrate/20210727113447_backfill_integrations_type_new.rb
index 8544c236fd7..eb6095abdb5 100644
--- a/db/post_migrate/20210727113447_backfill_integrations_type_new.rb
+++ b/db/post_migrate/20210727113447_backfill_integrations_type_new.rb
@@ -16,8 +16,6 @@ class BackfillIntegrationsTypeNew < ActiveRecord::Migration[6.1]
end
def down
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :integrations, :id, [])
- .delete_all
+ delete_batched_background_migration(MIGRATION, :integrations, :id, [])
end
end
diff --git a/db/post_migrate/20210930211936_backfill_user_namespace.rb b/db/post_migrate/20210930211936_backfill_user_namespace.rb
index c5dd170f18e..3d9228f21f1 100644
--- a/db/post_migrate/20210930211936_backfill_user_namespace.rb
+++ b/db/post_migrate/20210930211936_backfill_user_namespace.rb
@@ -19,8 +19,6 @@ class BackfillUserNamespace < Gitlab::Database::Migration[1.0]
end
def down
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :namespaces, :id, [])
- .delete_all
+ delete_batched_background_migration(MIGRATION, :namespaces, :id, [])
end
end
diff --git a/db/post_migrate/20211026070408_backfill_issue_search_data.rb b/db/post_migrate/20211026070408_backfill_issue_search_data.rb
index a840adcb991..c0073036944 100644
--- a/db/post_migrate/20211026070408_backfill_issue_search_data.rb
+++ b/db/post_migrate/20211026070408_backfill_issue_search_data.rb
@@ -15,8 +15,6 @@ class BackfillIssueSearchData < Gitlab::Database::Migration[1.0]
end
def down
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :issues, :id, [])
- .delete_all
+ delete_batched_background_migration(MIGRATION, :issues, :id, [])
end
end
diff --git a/db/post_migrate/20211209103048_backfill_project_namespaces_for_group.rb b/db/post_migrate/20211209103048_backfill_project_namespaces_for_group.rb
index 0b65db7aab4..44dffc798d3 100644
--- a/db/post_migrate/20211209103048_backfill_project_namespaces_for_group.rb
+++ b/db/post_migrate/20211209103048_backfill_project_namespaces_for_group.rb
@@ -36,7 +36,6 @@ class BackfillProjectNamespacesForGroup < Gitlab::Database::Migration[1.0]
def down
return unless Gitlab.com? || Gitlab.staging?
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :projects, :id, [GROUP_ID, 'up']).delete_all
+ delete_batched_background_migration(MIGRATION, :projects, :id, [GROUP_ID, 'up'])
end
end
diff --git a/db/post_migrate/20220120123800_backfill_namespace_id_for_namespace_routes.rb b/db/post_migrate/20220120123800_backfill_namespace_id_for_namespace_routes.rb
index 4aac0903502..57ac4fe825e 100644
--- a/db/post_migrate/20220120123800_backfill_namespace_id_for_namespace_routes.rb
+++ b/db/post_migrate/20220120123800_backfill_namespace_id_for_namespace_routes.rb
@@ -20,8 +20,6 @@ class BackfillNamespaceIdForNamespaceRoutes < Gitlab::Database::Migration[1.0]
end
def down
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :routes, :id, [])
- .delete_all
+ delete_batched_background_migration(MIGRATION, :routes, :id, [])
end
end
diff --git a/db/post_migrate/20220120211832_backfill_member_namespace_id_for_group_members.rb b/db/post_migrate/20220120211832_backfill_member_namespace_id_for_group_members.rb
index 947c0a1edd0..f77c40a0b4e 100644
--- a/db/post_migrate/20220120211832_backfill_member_namespace_id_for_group_members.rb
+++ b/db/post_migrate/20220120211832_backfill_member_namespace_id_for_group_members.rb
@@ -20,8 +20,6 @@ class BackfillMemberNamespaceIdForGroupMembers < Gitlab::Database::Migration[1.0
end
def down
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :members, :id, [])
- .delete_all
+ delete_batched_background_migration(MIGRATION, :members, :id, [])
end
end
diff --git a/db/post_migrate/20220223112304_schedule_nullify_orphan_runner_id_on_ci_builds.rb b/db/post_migrate/20220223112304_schedule_nullify_orphan_runner_id_on_ci_builds.rb
index e4005885c3b..d361aff5738 100644
--- a/db/post_migrate/20220223112304_schedule_nullify_orphan_runner_id_on_ci_builds.rb
+++ b/db/post_migrate/20220223112304_schedule_nullify_orphan_runner_id_on_ci_builds.rb
@@ -20,8 +20,6 @@ class ScheduleNullifyOrphanRunnerIdOnCiBuilds < Gitlab::Database::Migration[1.0]
end
def down
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :ci_builds, :id, [])
- .delete_all
+ delete_batched_background_migration(MIGRATION, :ci_builds, :id, [])
end
end
diff --git a/db/post_migrate/20220301093434_backfill_all_project_namespaces.rb b/db/post_migrate/20220301093434_backfill_all_project_namespaces.rb
index 7071e6241ce..607161004cf 100644
--- a/db/post_migrate/20220301093434_backfill_all_project_namespaces.rb
+++ b/db/post_migrate/20220301093434_backfill_all_project_namespaces.rb
@@ -24,7 +24,6 @@ class BackfillAllProjectNamespaces < Gitlab::Database::Migration[1.0]
end
def down
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :projects, :id, [nil, 'up']).delete_all
+ delete_batched_background_migration(MIGRATION, :projects, :id, [nil, 'up'])
end
end
diff --git a/db/post_migrate/20220302114046_backfill_group_features.rb b/db/post_migrate/20220302114046_backfill_group_features.rb
index dd8961b8bee..5e0ff9095a3 100644
--- a/db/post_migrate/20220302114046_backfill_group_features.rb
+++ b/db/post_migrate/20220302114046_backfill_group_features.rb
@@ -22,8 +22,6 @@ class BackfillGroupFeatures < Gitlab::Database::Migration[1.0]
end
def down
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :namespaces, :id, [BATCH_SIZE])
- .delete_all
+ delete_batched_background_migration(MIGRATION, :namespaces, :id, [BATCH_SIZE])
end
end
diff --git a/db/post_migrate/20220323023800_backfill_namespace_id_for_project_routes.rb b/db/post_migrate/20220323023800_backfill_namespace_id_for_project_routes.rb
index b938688be2c..0b2c7318887 100644
--- a/db/post_migrate/20220323023800_backfill_namespace_id_for_project_routes.rb
+++ b/db/post_migrate/20220323023800_backfill_namespace_id_for_project_routes.rb
@@ -22,8 +22,6 @@ class BackfillNamespaceIdForProjectRoutes < Gitlab::Database::Migration[1.0]
end
def down
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :routes, :id, [])
- .delete_all
+ delete_batched_background_migration(MIGRATION, :routes, :id, [])
end
end
diff --git a/db/post_migrate/20220324165436_schedule_backfill_project_settings.rb b/db/post_migrate/20220324165436_schedule_backfill_project_settings.rb
index c1aaea44bfd..1ca1f29a2da 100644
--- a/db/post_migrate/20220324165436_schedule_backfill_project_settings.rb
+++ b/db/post_migrate/20220324165436_schedule_backfill_project_settings.rb
@@ -18,8 +18,6 @@ class ScheduleBackfillProjectSettings < Gitlab::Database::Migration[1.0]
end
def down
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :projects, :id, [])
- .delete_all
+ delete_batched_background_migration(MIGRATION, :projects, :id, [])
end
end
diff --git a/db/post_migrate/20220510121338_remove_threat_monitoring_alerts.rb b/db/post_migrate/20220510121338_remove_threat_monitoring_alerts.rb
new file mode 100644
index 00000000000..ff171ee516c
--- /dev/null
+++ b/db/post_migrate/20220510121338_remove_threat_monitoring_alerts.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class RemoveThreatMonitoringAlerts < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ execute("DELETE FROM alert_management_alerts WHERE domain = 1")
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20220512190659_remove_web_hooks_web_hook_logs_web_hook_id_fk.rb b/db/post_migrate/20220512190659_remove_web_hooks_web_hook_logs_web_hook_id_fk.rb
new file mode 100644
index 00000000000..b73d3a7f102
--- /dev/null
+++ b/db/post_migrate/20220512190659_remove_web_hooks_web_hook_logs_web_hook_id_fk.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+class RemoveWebHooksWebHookLogsWebHookIdFk < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ PARENT_TABLE_NAME = :web_hook_logs
+ FK_NAME = "fk_rails_bb3355782d"
+
+ def up
+ with_lock_retries do
+ execute('LOCK web_hooks, web_hook_logs IN ACCESS EXCLUSIVE MODE') if transaction_open?
+
+ remove_foreign_key_if_exists(:web_hook_logs, :web_hooks, name: FK_NAME)
+ end
+ end
+
+ def down
+ fk_attrs = {
+ name: FK_NAME, # Note we need the same name for every partition
+ column: :web_hook_id,
+ target_column: :id,
+ on_delete: :cascade
+ }
+
+ # Must add child FK's first, then to the partitioned table.
+ child_tables.each do |tbl|
+ add_concurrent_foreign_key(
+ tbl, :web_hooks,
+ # This embeds the lock table statement in the with_lock_retries inside add_concurrent_foreign_key
+ reverse_lock_order: true,
+ **fk_attrs)
+ end
+
+ with_lock_retries do
+ execute("LOCK web_hooks, #{PARENT_TABLE_NAME} IN ACCESS EXCLUSIVE MODE") if transaction_open?
+ add_foreign_key(:web_hook_logs, :web_hooks, **fk_attrs)
+ end
+ end
+
+ # This table is partitioned: we need to apply the index changes to each
+ # partition separately.
+ def child_tables
+ @child_tables ||= execute(<<~SQL.squish).pluck("child")
+ SELECT inhrelid::regclass AS child
+ FROM pg_catalog.pg_inherits
+ WHERE inhparent = '#{PARENT_TABLE_NAME}'::regclass
+ ORDER BY inhrelid ASC
+ SQL
+ end
+end
diff --git a/db/post_migrate/20220513043344_reschedule_expire_o_auth_tokens.rb b/db/post_migrate/20220513043344_reschedule_expire_o_auth_tokens.rb
index 0c8a2386583..4b24b1cf65a 100644
--- a/db/post_migrate/20220513043344_reschedule_expire_o_auth_tokens.rb
+++ b/db/post_migrate/20220513043344_reschedule_expire_o_auth_tokens.rb
@@ -10,9 +10,7 @@ class RescheduleExpireOAuthTokens < Gitlab::Database::Migration[2.0]
def up
# remove the original migration from db/post_migrate/20220428133724_schedule_expire_o_auth_tokens.rb
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :oauth_access_tokens, :id, [])
- .delete_all
+ delete_batched_background_migration(MIGRATION, :oauth_access_tokens, :id, [])
# reschedule
queue_batched_background_migration(
@@ -24,8 +22,6 @@ class RescheduleExpireOAuthTokens < Gitlab::Database::Migration[2.0]
end
def down
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :oauth_access_tokens, :id, [])
- .delete_all
+ delete_batched_background_migration(MIGRATION, :oauth_access_tokens, :id, [])
end
end
diff --git a/db/post_migrate/20220517101119_create_confidential_notes_index_synchronously.rb b/db/post_migrate/20220517101119_create_confidential_notes_index_synchronously.rb
new file mode 100644
index 00000000000..25b7fe7f7bb
--- /dev/null
+++ b/db/post_migrate/20220517101119_create_confidential_notes_index_synchronously.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class CreateConfidentialNotesIndexSynchronously < Gitlab::Database::Migration[2.0]
+ INDEX_NAME = 'index_notes_on_confidential'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :notes, :confidential, where: 'confidential = true', name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :notes, name: INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20220517133753_migrate_free_user_cap_remediation.rb b/db/post_migrate/20220517133753_migrate_free_user_cap_remediation.rb
new file mode 100644
index 00000000000..44f30134c94
--- /dev/null
+++ b/db/post_migrate/20220517133753_migrate_free_user_cap_remediation.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class MigrateFreeUserCapRemediation < Gitlab::Database::Migration[2.0]
+ def up
+ sidekiq_queue_migrate 'cronjob:namespaces_free_user_cap', to: 'cronjob:namespaces_free_user_cap_remediation'
+ end
+
+ def down
+ sidekiq_queue_migrate 'cronjob:namespaces_free_user_cap_remediation', to: 'cronjob:namespaces_free_user_cap'
+ end
+end
diff --git a/db/schema_migrations/20220503035221 b/db/schema_migrations/20220503035221
new file mode 100644
index 00000000000..40b89d95db8
--- /dev/null
+++ b/db/schema_migrations/20220503035221
@@ -0,0 +1 @@
+0eeb96f26d8f8372c70f9aef59e976c4d403ec6aec01ad8784d31a7594bbaee2 \ No newline at end of file
diff --git a/db/schema_migrations/20220503035437 b/db/schema_migrations/20220503035437
new file mode 100644
index 00000000000..964d0bb4944
--- /dev/null
+++ b/db/schema_migrations/20220503035437
@@ -0,0 +1 @@
+78306ca94e5f8e595d496efff983ed4749ee841ca8e2633afdb0cc5cb077e96a \ No newline at end of file
diff --git a/db/schema_migrations/20220510121338 b/db/schema_migrations/20220510121338
new file mode 100644
index 00000000000..8d41973afd1
--- /dev/null
+++ b/db/schema_migrations/20220510121338
@@ -0,0 +1 @@
+6f77ac586d48274a8f88384808b56ecad33a8ce00488595c0f82a074b45d5634 \ No newline at end of file
diff --git a/db/schema_migrations/20220512020500 b/db/schema_migrations/20220512020500
new file mode 100644
index 00000000000..86830de0c9d
--- /dev/null
+++ b/db/schema_migrations/20220512020500
@@ -0,0 +1 @@
+16071cd85b772d86242ced867ba2f8c5e317222f6c3297e15dd456de13b4926e \ No newline at end of file
diff --git a/db/schema_migrations/20220512190659 b/db/schema_migrations/20220512190659
new file mode 100644
index 00000000000..a3ea4c1b46e
--- /dev/null
+++ b/db/schema_migrations/20220512190659
@@ -0,0 +1 @@
+bbd6be6772f4fc5aecf393d6c043c1fdc38fdde6efd1586ba4be08ddbb5886dd \ No newline at end of file
diff --git a/db/schema_migrations/20220517101119 b/db/schema_migrations/20220517101119
new file mode 100644
index 00000000000..18c25e858dc
--- /dev/null
+++ b/db/schema_migrations/20220517101119
@@ -0,0 +1 @@
+74585dbb76b6744533a1df5dc995b461798fe8d0e0cd37fb6f9d52958f87a403 \ No newline at end of file
diff --git a/db/schema_migrations/20220517133753 b/db/schema_migrations/20220517133753
new file mode 100644
index 00000000000..295658d34a2
--- /dev/null
+++ b/db/schema_migrations/20220517133753
@@ -0,0 +1 @@
+eadee7b8b85cce48f48dbdab2f5e0b8cd633ef4ef8363960b7de1eb73a6fe1aa \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index fa9d9e8f778..396cf191475 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -11767,6 +11767,8 @@ CREATE TABLE batched_background_migrations (
max_batch_size integer,
started_at timestamp with time zone,
on_hold_until timestamp with time zone,
+ gitlab_schema text NOT NULL,
+ CONSTRAINT check_0406d9776f CHECK ((char_length(gitlab_schema) <= 255)),
CONSTRAINT check_5bb0382d6f CHECK ((char_length(column_name) <= 63)),
CONSTRAINT check_6b6a06254a CHECK ((char_length(table_name) <= 63)),
CONSTRAINT check_batch_size_in_range CHECK ((batch_size >= sub_batch_size)),
@@ -26911,6 +26913,8 @@ CREATE INDEX index_batched_jobs_by_batched_migration_id_and_id ON batched_backgr
CREATE INDEX index_batched_jobs_on_batched_migration_id_and_status ON batched_background_migration_jobs USING btree (batched_background_migration_id, status);
+CREATE UNIQUE INDEX index_batched_migrations_on_gl_schema_and_unique_configuration ON batched_background_migrations USING btree (gitlab_schema, job_class_name, table_name, column_name, job_arguments);
+
CREATE INDEX index_board_assignees_on_assignee_id ON board_assignees USING btree (assignee_id);
CREATE UNIQUE INDEX index_board_assignees_on_board_id_and_assignee_id ON board_assignees USING btree (board_id, assignee_id);
@@ -28425,6 +28429,8 @@ CREATE INDEX index_notes_on_author_id_and_created_at_and_id ON notes USING btree
CREATE INDEX index_notes_on_commit_id ON notes USING btree (commit_id);
+CREATE INDEX index_notes_on_confidential ON notes USING btree (confidential) WHERE (confidential = true);
+
CREATE INDEX index_notes_on_created_at ON notes USING btree (created_at);
CREATE INDEX index_notes_on_discussion_id ON notes USING btree (discussion_id);
@@ -33118,9 +33124,6 @@ ALTER TABLE ONLY approval_project_rules_users
ALTER TABLE ONLY lists
ADD CONSTRAINT fk_rails_baed5f39b7 FOREIGN KEY (milestone_id) REFERENCES milestones(id) ON DELETE CASCADE;
-ALTER TABLE web_hook_logs
- ADD CONSTRAINT fk_rails_bb3355782d FOREIGN KEY (web_hook_id) REFERENCES web_hooks(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY security_findings
ADD CONSTRAINT fk_rails_bb63863cf1 FOREIGN KEY (scan_id) REFERENCES security_scans(id) ON DELETE CASCADE;
diff --git a/doc/administration/geo/replication/datatypes.md b/doc/administration/geo/replication/datatypes.md
index 83e207e7a8f..8aff9226203 100644
--- a/doc/administration/geo/replication/datatypes.md
+++ b/doc/administration/geo/replication/datatypes.md
@@ -203,6 +203,8 @@ successfully, you must replicate their data using some other means.
|[External merge request diffs](../../merge_request_diffs.md) | **Yes** (13.5) | **Yes** (14.6) | Via Object Storage provider if supported. Native Geo support (Beta). | Replication is behind the feature flag `geo_merge_request_diff_replication`, enabled by default. Verification was behind the feature flag `geo_merge_request_diff_verification`, removed in 14.7.|
|[Versioned snippets](../../../user/snippets.md#versioned-snippets) | [**Yes** (13.7)](https://gitlab.com/groups/gitlab-org/-/epics/2809) | [**Yes** (14.2)](https://gitlab.com/groups/gitlab-org/-/epics/2810) | No | Verification was implemented behind the feature flag `geo_snippet_repository_verification` in 13.11, and the feature flag was removed in 14.2. |
|[GitLab Pages](../../pages/index.md) | [**Yes** (14.3)](https://gitlab.com/groups/gitlab-org/-/epics/589) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (14.6) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_pages_deployment_replication`, enabled by default. Verification was behind the feature flag `geo_pages_deployment_verification`, removed in 14.7. |
+|[Incident Metric Images](../../../operations/incident_management/incidents.md#metrics) | [Planned](https://gitlab.com/gitlab-org/gitlab/-/issues/352326) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/362561) | No | |
+|[Alert Metric Images](../../../operations/incident_management/alerts.md#metrics-tab) | [Planned](https://gitlab.com/gitlab-org/gitlab/-/issues/352326) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/362561) | No | |
|[Server-side Git hooks](../../server_hooks.md) | [Not planned](https://gitlab.com/groups/gitlab-org/-/epics/1867) | No | No | Not planned because of current implementation complexity, low customer interest, and availability of alternatives to hooks. |
|[Elasticsearch integration](../../../integration/elasticsearch.md) | [Not planned](https://gitlab.com/gitlab-org/gitlab/-/issues/1186) | No | No | Not planned because further product discovery is required and Elasticsearch (ES) clusters can be rebuilt. Secondaries use the same ES cluster as the primary. |
|[Dependency proxy images](../../../user/packages/dependency_proxy/index.md) | [Not planned](https://gitlab.com/gitlab-org/gitlab/-/issues/259694) | No | No | Blocked by [Geo: Secondary Mimicry](https://gitlab.com/groups/gitlab-org/-/epics/1528). Replication of this cache is not needed for disaster recovery purposes because it can be recreated from external sources. |
diff --git a/doc/administration/integration/mailgun.md b/doc/administration/integration/mailgun.md
index 5a56aed4427..fbad201be7d 100644
--- a/doc/administration/integration/mailgun.md
+++ b/doc/administration/integration/mailgun.md
@@ -1,6 +1,6 @@
---
-stage: Growth
-group: Expansion
+stage: Plan
+group: Project Management
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
type: reference, howto
---
@@ -9,26 +9,37 @@ type: reference, howto
When you use Mailgun to send emails for your GitLab instance and [Mailgun](https://www.mailgun.com/)
integration is enabled and configured in GitLab, you can receive their webhook for
-permanent invite email failures. To set up the integration, you must:
+tracking delivery failures. To set up the integration, you must:
1. [Configure your Mailgun domain](#configure-your-mailgun-domain).
1. [Enable Mailgun integration](#enable-mailgun-integration).
-After completing the integration, Mailgun `permanent_failure` webhooks are sent to your GitLab instance.
+After completing the integration, Mailgun `temporary_failure` and `permanent_failure` webhooks are sent to your GitLab instance.
## Configure your Mailgun domain
-Before you can enable Mailgun in GitLab, set up your own Mailgun permanent failure endpoint to receive the webhooks.
+> [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/359113) the `/-/members/mailgun/permanent_failures` URL in GitLab 15.0.
+> [Added](https://gitlab.com/gitlab-org/gitlab/-/issues/359113) the URL to handle both temporary and permanent failures in GitLab 15.0.
+
+Before you can enable Mailgun in GitLab, set up your own Mailgun endpoints to receive the webhooks.
Using the [Mailgun webhook guide](https://www.mailgun.com/blog/a-guide-to-using-mailguns-webhooks/):
1. Add a webhook with the **Event type** set to **Permanent Failure**.
-1. Fill in the URL of your instance and include the `/-/members/mailgun/permanent_failures` path.
- - Example: `https://myinstance.gitlab.com/-/members/mailgun/permanent_failures`
+1. Enter the URL of your instance and include the `/-/mailgun/webhooks` path.
+
+ For example:
+
+ ```plaintext
+ https://myinstance.gitlab.com/-/mailgun/webhooks
+ ```
+
+1. Add another webhook with the **Event type** set to **Temporary Failure**.
+1. Enter the URL of your instance and use the same `/-/mailgun/webhooks` path.
## Enable Mailgun integration
-After configuring your Mailgun domain for the permanent failures endpoint,
+After configuring your Mailgun domain for the webhook endpoints,
you're ready to enable the Mailgun integration:
1. Sign in to GitLab as an [Administrator](../../user/permissions.md) user.
diff --git a/doc/administration/server_hooks.md b/doc/administration/server_hooks.md
index 9d8bc03e5e9..a83a17d6d52 100644
--- a/doc/administration/server_hooks.md
+++ b/doc/administration/server_hooks.md
@@ -23,7 +23,7 @@ alternatives to server hooks include:
- [Webhooks](../user/project/integrations/webhooks.md).
- [GitLab CI/CD](../ci/index.md).
-- [Push rules](../push_rules/push_rules.md), for a user-configurable Git hook interface.
+- [Push rules](../user/project/repository/push_rules.md), for a user-configurable Git hook interface.
[Geo](geo/index.md) doesn't replicate server hooks to secondary nodes.
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index abe9cb65f95..124bbd3d5f4 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -619,92 +619,56 @@ Parameters:
```json
{
- "id": 1,
- "iid": 1,
- "project_id": 3,
- "title": "test1",
- "description": "fixed login page css paddings",
- "state": "merged",
- "created_at": "2017-04-29T08:46:00Z",
- "updated_at": "2017-04-29T08:46:00Z",
+ "id": 155016530,
+ "iid": 133,
+ "project_id": 15513260,
+ "title": "Manual job rules",
+ "description": "",
+ "state": "opened",
+ "created_at": "2022-05-13T07:26:38.402Z",
+ "updated_at": "2022-05-14T03:38:31.354Z",
+ "merged_by": null, // Deprecated and will be removed in API v5, use `merge_user` instead
+ "merge_user": null,
+ "merged_at": null,
+ "closed_by": null,
+ "closed_at": null,
"target_branch": "master",
- "source_branch": "test1",
+ "source_branch": "manual-job-rules",
+ "user_notes_count": 0,
"upvotes": 0,
"downvotes": 0,
"author": {
- "id": 1,
- "name": "Administrator",
- "username": "admin",
- "state": "active",
- "avatar_url": null,
- "web_url" : "https://gitlab.example.com/admin"
- },
- "user" : {
- "can_merge" : false
- },
- "assignee": {
- "id": 1,
- "name": "Administrator",
- "username": "admin",
- "state": "active",
- "avatar_url": null,
- "web_url" : "https://gitlab.example.com/admin"
- },
- "assignees": [{
- "name": "Miss Monserrate Beier",
- "username": "axel.block",
- "id": 12,
- "state": "active",
- "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
- "web_url": "https://gitlab.example.com/axel.block"
- }],
- "reviewers": [{
- "id": 2,
- "name": "Sam Bauch",
- "username": "kenyatta_oconnell",
+ "id": 4155490,
+ "username": "marcel.amirault",
+ "name": "Marcel Amirault",
"state": "active",
- "avatar_url": "https://www.gravatar.com/avatar/956c92487c6f6f7616b536927e22c9a0?s=80&d=identicon",
- "web_url": "http://gitlab.example.com//kenyatta_oconnell"
- }],
- "source_project_id": 2,
- "target_project_id": 3,
- "labels": [
- "Community contribution",
- "Manage"
- ],
+ "avatar_url": "https://gitlab.com/uploads/-/system/user/avatar/4155490/avatar.png",
+ "web_url": "https://gitlab.com/marcel.amirault"
+ },
+ "assignees": [],
+ "assignee": null,
+ "reviewers": [],
+ "source_project_id": 15513260,
+ "target_project_id": 15513260,
+ "labels": [],
"draft": false,
"work_in_progress": false,
- "milestone": {
- "id": 5,
- "iid": 1,
- "project_id": 3,
- "title": "v2.0",
- "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
- "state": "closed",
- "created_at": "2015-02-02T19:49:26.013Z",
- "updated_at": "2015-02-02T19:49:26.013Z",
- "due_date": "2018-09-22",
- "start_date": "2018-08-08",
- "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1"
- },
- "merge_when_pipeline_succeeds": true,
+ "milestone": null,
+ "merge_when_pipeline_succeeds": false,
"merge_status": "can_be_merged",
- "merge_error": null,
- "sha": "8888888888888888888888888888888888888888",
+ "sha": "e82eb4a098e32c796079ca3915e07487fc4db24c",
"merge_commit_sha": null,
"squash_commit_sha": null,
- "user_notes_count": 1,
"discussion_locked": null,
- "should_remove_source_branch": true,
- "force_remove_source_branch": false,
- "allow_collaboration": false,
- "allow_maintainer_to_push": false,
- "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1",
+ "should_remove_source_branch": null,
+ "force_remove_source_branch": true,
+ "reference": "!133",
"references": {
- "short": "!1",
- "relative": "!1",
- "full": "my-group/my-project!1"
+ "short": "!133",
+ "relative": "!133",
+ "full": "marcel.amirault/test-project!133"
},
+ "web_url": "https://gitlab.com/marcel.amirault/test-project/-/merge_requests/133",
"time_stats": {
"time_estimate": 0,
"total_time_spent": 0,
@@ -712,51 +676,80 @@ Parameters:
"human_total_time_spent": null
},
"squash": false,
- "subscribed": false,
- "changes_count": "1",
- "merged_by": { // Deprecated and will be removed in API v5, use `merge_user` instead
- "id": 87854,
- "name": "Douwe Maan",
- "username": "DouweM",
- "state": "active",
- "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png",
- "web_url": "https://gitlab.com/DouweM"
- },
- "merge_user": {
- "id": 87854,
- "name": "Douwe Maan",
- "username": "DouweM",
- "state": "active",
- "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png",
- "web_url": "https://gitlab.com/DouweM"
+ "task_completion_status": {
+ "count": 0,
+ "completed_count": 0
},
- "merged_at": "2018-09-07T11:16:17.520Z",
- "closed_by": null,
- "closed_at": null,
- "latest_build_started_at": "2018-09-07T07:27:38.472Z",
- "latest_build_finished_at": "2018-09-07T08:07:06.012Z",
+ "has_conflicts": false,
+ "blocking_discussions_resolved": true,
+ "approvals_before_merge": null,
+ "subscribed": true,
+ "changes_count": "1",
+ "latest_build_started_at": "2022-05-13T09:46:50.032Z",
+ "latest_build_finished_at": null,
"first_deployed_to_production_at": null,
- "pipeline": {
- "id": 29626725,
- "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
- "ref": "patch-28",
- "status": "success",
- "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725"
+ "pipeline": { // Old parameter, use `head_pipeline` instead.
+ "id": 538317940,
+ "iid": 1877,
+ "project_id": 15513260,
+ "sha": "1604b0c46c395822e4e9478777f8e54ac99fe5b9",
+ "ref": "refs/merge-requests/133/merge",
+ "status": "failed",
+ "source": "merge_request_event",
+ "created_at": "2022-05-13T09:46:39.560Z",
+ "updated_at": "2022-05-13T09:47:20.706Z",
+ "web_url": "https://gitlab.com/marcel.amirault/test-project/-/pipelines/538317940"
+ },
+ "head_pipeline": {
+ "id": 538317940,
+ "iid": 1877,
+ "project_id": 15513260,
+ "sha": "1604b0c46c395822e4e9478777f8e54ac99fe5b9",
+ "ref": "refs/merge-requests/133/merge",
+ "status": "failed",
+ "source": "merge_request_event",
+ "created_at": "2022-05-13T09:46:39.560Z",
+ "updated_at": "2022-05-13T09:47:20.706Z",
+ "web_url": "https://gitlab.com/marcel.amirault/test-project/-/pipelines/538317940",
+ "before_sha": "1604b0c46c395822e4e9478777f8e54ac99fe5b9",
+ "tag": false,
+ "yaml_errors": null,
+ "user": {
+ "id": 4155490,
+ "username": "marcel.amirault",
+ "name": "Marcel Amirault",
+ "state": "active",
+ "avatar_url": "https://gitlab.com/uploads/-/system/user/avatar/4155490/avatar.png",
+ "web_url": "https://gitlab.com/marcel.amirault"
+ },
+ "started_at": "2022-05-13T09:46:50.032Z",
+ "finished_at": "2022-05-13T09:47:20.697Z",
+ "committed_at": null,
+ "duration": 30,
+ "queued_duration": 10,
+ "coverage": null,
+ "detailed_status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/marcel.amirault/test-project/-/pipelines/538317940",
+ "illustration": null,
+ "favicon": "/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ }
},
"diff_refs": {
- "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00",
- "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
- "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
+ "base_sha": "1162f719d711319a2efb2a35566f3bfdadee8bab",
+ "head_sha": "e82eb4a098e32c796079ca3915e07487fc4db24c",
+ "start_sha": "1162f719d711319a2efb2a35566f3bfdadee8bab"
},
- "diverged_commits_count": 2,
- "rebase_in_progress": false,
+ "merge_error": null,
"first_contribution": false,
- "task_completion_status":{
- "count":0,
- "completed_count":0
- },
- "has_conflicts": false,
- "blocking_discussions_resolved": true
+ "user": {
+ "can_merge": true
+ }
}
```
diff --git a/doc/ci/pipeline_editor/index.md b/doc/ci/pipeline_editor/index.md
index 57a9d7a9358..d87b336224c 100644
--- a/doc/ci/pipeline_editor/index.md
+++ b/doc/ci/pipeline_editor/index.md
@@ -53,12 +53,8 @@ reflected in the CI lint. It displays the same results as the existing [CI Lint
## View included CI/CD configuration
-> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/7064) in GitLab 15.0 [with a flag](../../administration/feature_flags.md) named `pipeline_editor_file_tree`. Disabled by default.
-
-FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available,
-ask an administrator to [enable the feature flag](../../administration/feature_flags.md)
-named `pipeline_editor_file_tree`.
+> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/7064) in GitLab 15.0 [with a flag](../../administration/feature_flags.md) named `pipeline_editor_file_tree`. Disabled by default.
+> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/357219) in GitLab 15.1.
You can review configuration added with the [`include`](../yaml/index.md#include)
keyword in the pipeline editor. In the top right, select the file tree (**{file-tree}**)
diff --git a/doc/ci/pipelines/cicd_minutes.md b/doc/ci/pipelines/cicd_minutes.md
index 1051b176f06..2b18b1d353b 100644
--- a/doc/ci/pipelines/cicd_minutes.md
+++ b/doc/ci/pipelines/cicd_minutes.md
@@ -193,9 +193,12 @@ The cost factor for a job running on a shared runner is:
### Additional costs on GitLab SaaS
-On GitLab SaaS, shared runners can have different cost factors depending on the cost involved
-in executing the runner. For example, a high spec shared runner could be set to have a cost factor of `2`.
-Conversely, a shared runner that executes jobs for public projects could have a low cost factor, like `0.008`.
+GitLab SaaS shared runners have different cost factors, depending on the runner type (Linux, Windows, macOS) and the virtual machine configuration.
+
+| GitLab SaaS runner type | Virtual machine configuration | CI/CD minutes cost factor |
+| :--------- | :------------------- | :--------- |
+| Linux OS + Docker executor| 1 vCPU, 3.75 GB RAM |1|
+| macOS + shell executor | 4 vCPU, 10 GB RAM| 6 |
### Monthly reset of CI/CD minutes
diff --git a/doc/ci/runners/saas/macos/codesigning.md b/doc/ci/runners/saas/macos/codesigning.md
new file mode 100644
index 00000000000..4f8316faf17
--- /dev/null
+++ b/doc/ci/runners/saas/macos/codesigning.md
@@ -0,0 +1,121 @@
+---
+stage: Verify
+group: Runner
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Code signing for SaaS runners on macOS
+
+> Introduced in GitLab 15.0.
+
+Before you can integrate GitLab with Apple services, install to a device, or deploy to the Apple App Store, you must [code sign](https://developer.apple.com/support/code-signing/) your application.
+
+To code sign an iOS project, you need the following files:
+
+- A certifcate issued by Apple.
+- A provisioning profile.
+
+## Code signing iOS Projects with fastlane
+
+When you use SaaS runners on macOS, each job runs on a VM. Included in each VM is [fastlane](https://fastlane.tools/),
+an open-source solution aimed at simplifying mobile app deployment.
+
+These steps outline the minimal setup required to use fastlane to code sign your application. Refer to the fastlane [getting started guide](https://docs.fastlane.tools/), [best practices for integrating with GitLab CI](https://docs.fastlane.tools/best-practices/continuous-integration/gitlab/) and the [fastlane code signing getting started guide](https://docs.fastlane.tools/codesigning/getting-started/) for installation instructions, and an overview of how to use fastlane to handle code signing.
+
+To use fastlane to code sign your application:
+
+1. At the root of your project repository, on your local development system, run this command:
+
+ ```plaintext
+ fastlane match init
+ ```
+
+ This command creates the `fastlane` directory and adds two files: `Fastfile` and `Appfile`.
+
+1. Open `Appfile` and edit it to include your Apple ID and app ID.
+
+ ```plaintext
+ app_identifier("APP IDENTIFIER") # The bundle identifier of your app
+
+ apple_id("APPLE ID") # Your Apple email address
+ ```
+
+1. Open `Fastfile`, which includes the fastlane build steps.
+ In the following snippet, the steps `get_certificates`, `get_provisioning_profile,match`, `gym`, and
+ `upload_to_testflight` are fastlane [actions](https://docs.fastlane.tools/actions/).
+
+ ```plaintext
+ # This file contains the fastlane.tools configuration
+ # You can find the documentation at https://docs.fastlane.tools
+
+ default_platform(:ios)
+
+ platform :ios do
+ desc "Build the application"
+ lane :beta do
+ increment_build_number(
+ build_number: latest_testflight_build_number + 1,
+ xcodeproj: "${PROJECT_NAME}.xcodeproj"
+ )
+ get_certificates
+ get_provisioning_profile
+ # match(type: "appstore",read_only: true)
+ gym
+ upload_to_testflight
+ end
+ end
+ ```
+
+The example configuration also includes an optional `Gymfile`. This file stores configuration
+parameters and is used by the fastlane [`gym`](https://docs.fastlane.tools/actions/gym/) action.
+
+## Using fastlane match
+
+To simplify the code signing process and implement the
+[Code Signing Best Practices Guide](https://codesigning.guide/) recommendations,
+use [fastlane match](https://docs.fastlane.tools/actions/match/).
+
+- Use one code signing identity shared across your team.
+- Store the required certificates and provisioning profiles in a separate GitLab project repository.
+
+Match automatically syncs iOS and macOS keys and provisioning profiles across all team members with access to the GitLab project. Each team member with access to the project can use the credentials for code signing.
+
+To use fastlane match:
+
+1. Initialize match in the project repository:
+
+ ```shell
+ bundle exec fastlane match init
+ ```
+
+1. Select `git` as your storage node.
+1. Enter the URL of the GitLab project you plan to use to store your code signing identities.
+1. Optional. To create a new certificate and provisioning profile, run:
+
+ ```shell
+ bundle exec fastlane match development
+ ```
+
+For different code signing identities' storage options, and for a complete step-by-step guide for using match,
+refer to the [match documentation](https://docs.fastlane.tools/actions/match/#usage).
+
+### Environment variables and authentication
+
+To complete the setup, you must configure environment variables to use with fastlane. The required variables are outlined in the [fastlane documentation](https://docs.fastlane.tools/best-practices/continuous-integration/#environment-variables-to-set).
+
+To support Apple's two factor authentication requirement, configure these variables:
+
+- `FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD` and
+- `FASTLANE_SESSION`
+
+To authenticate fastlane with the App Store for the TestFlight upload, configure these variables:
+
+- `FASTLANE_USER` and
+- `FASTLANE_PASSWORD`
+
+View the [fastlane authentication with Apple Services guide](https://docs.fastlane.tools/getting-started/ios/authentication/) for an overview of authentication options.
+
+## Related topics
+
+- [Apple Developer Support - Code Signing](https://developer.apple.com/support/code-signing/)
+- [Code Signing Best Practice Guide](https://codesigning.guide/)
diff --git a/doc/ci/runners/saas/macos/environment.md b/doc/ci/runners/saas/macos/environment.md
index 9da35758754..edd897e4c23 100644
--- a/doc/ci/runners/saas/macos/environment.md
+++ b/doc/ci/runners/saas/macos/environment.md
@@ -20,7 +20,7 @@ Each time you run a job that requires tooling or dependencies not available in t
GitLab SaaS provides macOS build machines on Apple servers with Intel x86-64 processors.
The expectation is that virtual machines running on the Apple M1 chip will be available in the second half of 2022.
-For the [Beta](../../../../policy/alpha-beta-support.md#beta-features), there is only one available machine type, `gbc-macos-large`.
+At this time there is only one available machine type offered, `gbc-macos-large`.
| Instance type | vCPUS | Memory (GB) |
| --------- | --- | ------- |
diff --git a/doc/ci/runners/saas/macos_saas_runner.md b/doc/ci/runners/saas/macos_saas_runner.md
index bad9da960b2..0ab2de36f10 100644
--- a/doc/ci/runners/saas/macos_saas_runner.md
+++ b/doc/ci/runners/saas/macos_saas_runner.md
@@ -4,10 +4,9 @@ group: Runner
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# SaaS runners on macOS (beta) **(PREMIUM SAAS)**
+# SaaS runners on macOS (Limited Availability) **(PREMIUM SAAS)**
-SaaS runners on macOS are in [Beta](../../../policy/alpha-beta-support.md#beta-features)
-and shouldn't be relied upon for mission-critical production jobs.
+SaaS runners on macOS are now in [Limited Availability](../../../policy/alpha-beta-support.md#beta-features) for approved open source programs and customers in Premium and Ultimate plans.
SaaS runners on macOS provide an on-demand macOS build environment integrated with
GitLab SaaS [CI/CD](../../../ci/index.md).
@@ -15,11 +14,17 @@ Use these runners to build, test, and deploy apps for the Apple ecosystem (macOS
of all the capabilities of the GitLab single DevOps platform and not have to manage or operate a
build environment.
+CI/CD minutes used on GitLab SaaS macOS runners are included in your CI/CD minute consumption totals. CI jobs that run on macOS **will** consume CI minutes at a faster rate than CI jobs on the GitLab SaaS runners on Linux.
+
+Refer to the CI/CD minutes [cost factor](../../../ci/pipelines/cicd_minutes.md#cost-factor) for the cost factor applied to the GitLab SaaS macOS runners.
+
## Quickstart
-To start using SaaS runners on macOS, you must submit an access request [issue](https://gitlab.com/gitlab-com/macos-buildcloud-runners-beta/-/issues/new?issuable_template=beta_access_request). After your
-access has been granted and your build environment configured, you must configure your
-`.gitlab-ci.yml` pipeline file:
+To start using SaaS runners on macOS, you must be an active GitLab SaaS Premium or Ultimate customer. Participants in the GitLab Open Source program are also eligible to use the service.
+
+### Configuring your pipeline
+
+To start using the SaaS runners on macOS to run your CI jobs, you must configure your `.gitlab-ci.yml` file:
1. Add a `.gitlab-ci.yml` file to your project repository.
1. Specify the [image](macos/environment.md#vm-images) you want to use.
@@ -27,7 +32,7 @@ access has been granted and your build environment configured, you must configur
The runners automatically run your build.
-## Example `.gitlab-ci.yml` file
+### Example `.gitlab-ci.yml` file
The following sample `.gitlab-ci.yml` file shows how to start using the SaaS runners on macOS:
@@ -42,7 +47,7 @@ stages:
- test
before_script:
- - echo "started by ${GITLAB_USER_NAME}"
+ - echo "started by ${GITLAB_USER_NAME}"
build:
extends:
@@ -60,4 +65,13 @@ test:
```
NOTE:
-During the Beta period, the architecture of this solution will change. Rather than the jobs running on a specific VM instance, they will run on an ephemeral VM instance that is created by an autoscaling instance, known as the Runner Manager. We will notify all Beta participants of any downtime required to do this work.
+You can specify a different Xcode image to run a job. To do so, replace the value for the `image` keyword with the value of the [virtual machine image name](macos/environment.md#vm-images) from the list of available images.
+
+## SaaS runners on macOS service level objective
+
+In SaaS runners on macOS, the objective is to make 90% of CI jobs start executing in 120 seconds or less. The error rate should be less than 0.5%.
+
+## Known Limitations and Usage Constraints
+
+- If the VM image does not include the specific software version you need for your job, then the job execution time will increase as the required software needs to be fetched and installed.
+- At this time, it is not possible to bring your own OS image.
diff --git a/doc/development/database/batched_background_migrations.md b/doc/development/database/batched_background_migrations.md
index 3a0fa77eff9..008584cf803 100644
--- a/doc/development/database/batched_background_migrations.md
+++ b/doc/development/database/batched_background_migrations.md
@@ -152,9 +152,7 @@ When you start the second post-deployment migration, delete the
previously batched migration with the provided code:
```ruby
-Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION_NAME, TABLE_NAME, COLUMN, JOB_ARGUMENTS)
- .delete_all
+delete_batched_background_migration(MIGRATION_NAME, TABLE_NAME, COLUMN, JOB_ARGUMENTS)
```
## Cleaning up
@@ -261,8 +259,7 @@ background migration.
end
def down
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :routes, :id, []).delete_all
+ delete_batched_background_migration(MIGRATION_NAME, :routes, :id, [])
end
end
```
diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md
index c11d1422167..b532826bcda 100644
--- a/doc/development/documentation/styleguide/index.md
+++ b/doc/development/documentation/styleguide/index.md
@@ -191,6 +191,8 @@ GitLab documentation should be clear and easy to understand.
### Capitalization
+As a company, we tend toward lowercase.
+
#### Headings
Use sentence case. For example:
@@ -589,6 +591,10 @@ Consider installing a plugin or extension in your editor for formatting tables:
- [Markdown Table Formatter](https://packagecontrol.io/packages/Markdown%20Table%20Formatter) for Sublime Text
- [Markdown Table Formatter](https://atom.io/packages/markdown-table-formatter) for Atom
+### Table headings
+
+Use sentence case for table headings. For example, `Keyword value` or `Project name`.
+
### Feature tables
When creating tables of lists of features (such the features
diff --git a/doc/development/feature_flags/controls.md b/doc/development/feature_flags/controls.md
index 68c14c1b0c9..7d801c00714 100644
--- a/doc/development/feature_flags/controls.md
+++ b/doc/development/feature_flags/controls.md
@@ -95,6 +95,7 @@ Guidelines:
- Consider notifying `#support_gitlab-com` beforehand. So in case if the feature has any side effects on user experience, they can mitigate and disable the feature flag to reduce some impact.
- If the feature meets the requirements for creating a [Change Management](https://about.gitlab.com/handbook/engineering/infrastructure/change-management/#feature-flags-and-the-change-management-process) issue, create a Change Management issue per [criticality guidelines](https://about.gitlab.com/handbook/engineering/infrastructure/change-management/#change-request-workflows).
- For simple, low-risk, easily reverted features, proceed and [enable the feature in `#production`](#process).
+- For support requests to toggle feature flags for specific groups or projects, please follow the process outlined in the [support workflows](https://about.gitlab.com/handbook/support/workflows/saas_feature_flags.html).
#### Process
diff --git a/doc/development/geo.md b/doc/development/geo.md
index f62b2de30db..5505d41ccc9 100644
--- a/doc/development/geo.md
+++ b/doc/development/geo.md
@@ -188,7 +188,7 @@ needs to be applied to the tracking database on each **secondary** site.
### Configuration
-The database configuration is set in [`config/database_geo.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/database_geo.yml.postgresql).
+The database configuration is set in [`config/database.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/database.yml.postgresql).
The directory [`ee/db/geo`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/ee/db/geo)
contains the schema and migrations for this database.
diff --git a/doc/push_rules/push_rules.md b/doc/push_rules/push_rules.md
deleted file mode 100644
index ab87cba9da3..00000000000
--- a/doc/push_rules/push_rules.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../user/project/repository/push_rules.md'
-remove_date: '2022-05-10'
----
-
-This document was moved to [another location](../user/project/repository/push_rules.md).
-
-<!-- This redirect file can be deleted after <2022-05-10>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/topics/autodevops/customize.md b/doc/topics/autodevops/customize.md
index e2d984dbbff..e278e95378c 100644
--- a/doc/topics/autodevops/customize.md
+++ b/doc/topics/autodevops/customize.md
@@ -409,6 +409,7 @@ applications.
| `AUTO_DEVOPS_BUILD_IMAGE_CNB_BUILDER` | The builder used when building with Cloud Native Buildpacks. The default builder is `heroku/buildpacks:18`. [More details](stages.md#auto-build-using-cloud-native-buildpacks). |
| `AUTO_DEVOPS_BUILD_IMAGE_EXTRA_ARGS` | Extra arguments to be passed to the `docker build` command. Note that using quotes doesn't prevent word splitting. [More details](#passing-arguments-to-docker-build). |
| `AUTO_DEVOPS_BUILD_IMAGE_FORWARDED_CI_VARIABLES` | A [comma-separated list of CI/CD variable names](#forward-cicd-variables-to-the-build-environment) to be forwarded to the build environment (the buildpack builder or `docker build`). |
+| `AUTO_DEVOPS_BUILD_IMAGE_CNB_PORT` | In GitLab 15.0 and later, port exposed by the generated Docker image. Set to `false` to prevent exposing any ports. Defaults to `5000`. |
| `AUTO_DEVOPS_CHART` | Helm Chart used to deploy your apps. Defaults to the one [provided by GitLab](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image/-/tree/master/assets/auto-deploy-app). |
| `AUTO_DEVOPS_CHART_REPOSITORY` | Helm Chart repository used to search for charts. Defaults to `https://charts.gitlab.io`. |
| `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | Used to set the name of the Helm repository. Defaults to `gitlab`. |
diff --git a/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
index 5c56594da78..b95b36fd555 100644
--- a/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
@@ -156,6 +156,9 @@ gemnasium-python-dependency_scanning:
bundler-audit-dependency_scanning:
extends: .ds-analyzer
+ variables:
+ DS_ANALYZER_NAME: "bundler-audit"
+ DS_MAJOR_VERSION: 2
script:
- echo "This job was deprecated in GitLab 14.8 and removed in GitLab 15.0"
- echo "For more information see https://gitlab.com/gitlab-org/gitlab/-/issues/347491"
@@ -165,6 +168,9 @@ bundler-audit-dependency_scanning:
retire-js-dependency_scanning:
extends: .ds-analyzer
+ variables:
+ DS_ANALYZER_NAME: "retire.js"
+ DS_MAJOR_VERSION: 2
script:
- echo "This job was deprecated in GitLab 14.8 and removed in GitLab 15.0"
- echo "For more information see https://gitlab.com/gitlab-org/gitlab/-/issues/289830"
diff --git a/lib/gitlab/database/background_migration/batched_migration.rb b/lib/gitlab/database/background_migration/batched_migration.rb
index a90cae7aea2..d052d5adc4c 100644
--- a/lib/gitlab/database/background_migration/batched_migration.rb
+++ b/lib/gitlab/database/background_migration/batched_migration.rb
@@ -30,9 +30,23 @@ module Gitlab
scope :created_after, ->(time) { where('created_at > ?', time) }
- scope :for_configuration, ->(job_class_name, table_name, column_name, job_arguments) do
- where(job_class_name: job_class_name, table_name: table_name, column_name: column_name)
+ scope :for_configuration, ->(gitlab_schema, job_class_name, table_name, column_name, job_arguments) do
+ relation = where(job_class_name: job_class_name, table_name: table_name, column_name: column_name)
.where("job_arguments = ?", job_arguments.to_json) # rubocop:disable Rails/WhereEquals
+
+ # This method is called from migrations older than the gitlab_schema column,
+ # check and add this filter only if the column exists.
+ relation = relation.for_gitlab_schema(gitlab_schema) if gitlab_schema_column_exists?
+
+ relation
+ end
+
+ def self.gitlab_schema_column_exists?
+ column_names.include?('gitlab_schema')
+ end
+
+ scope :for_gitlab_schema, ->(gitlab_schema) do
+ where(gitlab_schema: gitlab_schema)
end
state_machine :status, initial: :paused do
@@ -73,12 +87,13 @@ module Gitlab
state_machine.states.map(&:name)
end
- def self.find_for_configuration(job_class_name, table_name, column_name, job_arguments)
- for_configuration(job_class_name, table_name, column_name, job_arguments).first
+ def self.find_for_configuration(gitlab_schema, job_class_name, table_name, column_name, job_arguments)
+ for_configuration(gitlab_schema, job_class_name, table_name, column_name, job_arguments).first
end
- def self.active_migration
- executable.queue_order.first
+ def self.active_migration(connection:)
+ for_gitlab_schema(Gitlab::Database.gitlab_schemas_for_connection(connection))
+ .executable.queue_order.first
end
def self.successful_rows_counts(migrations)
diff --git a/lib/gitlab/database/background_migration/batched_migration_runner.rb b/lib/gitlab/database/background_migration/batched_migration_runner.rb
index 59ff9a9744f..388eb596ce2 100644
--- a/lib/gitlab/database/background_migration/batched_migration_runner.rb
+++ b/lib/gitlab/database/background_migration/batched_migration_runner.rb
@@ -54,7 +54,10 @@ module Gitlab
# in order to prevent it being picked up by the background worker. Perform all pending jobs,
# then keep running until migration is finished.
def finalize(job_class_name, table_name, column_name, job_arguments)
- migration = BatchedMigration.find_for_configuration(job_class_name, table_name, column_name, job_arguments)
+ migration = BatchedMigration.find_for_configuration(
+ Gitlab::Database.gitlab_schemas_for_connection(connection),
+ job_class_name, table_name, column_name, job_arguments
+ )
configuration = {
job_class_name: job_class_name,
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 0453b81d67d..17ada9395d5 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -945,8 +945,13 @@ module Gitlab
end
def ensure_batched_background_migration_is_finished(job_class_name:, table_name:, column_name:, job_arguments:, finalize: true)
- migration = Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(job_class_name, table_name, column_name, job_arguments).first
+ Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_dml_mode!
+
+ Gitlab::Database::BackgroundMigration::BatchedMigration.reset_column_information
+ migration = Gitlab::Database::BackgroundMigration::BatchedMigration.find_for_configuration(
+ Gitlab::Database.gitlab_schemas_for_connection(connection),
+ job_class_name, table_name, column_name, job_arguments
+ )
configuration = {
job_class_name: job_class_name,
diff --git a/lib/gitlab/database/migrations/batched_background_migration_helpers.rb b/lib/gitlab/database/migrations/batched_background_migration_helpers.rb
index 7113c3686f1..72b94964c5b 100644
--- a/lib/gitlab/database/migrations/batched_background_migration_helpers.rb
+++ b/lib/gitlab/database/migrations/batched_background_migration_helpers.rb
@@ -67,10 +67,16 @@ module Gitlab
batch_class_name: BATCH_CLASS_NAME,
batch_size: BATCH_SIZE,
max_batch_size: nil,
- sub_batch_size: SUB_BATCH_SIZE
+ sub_batch_size: SUB_BATCH_SIZE,
+ gitlab_schema: nil
)
+ Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_dml_mode!
- if Gitlab::Database::BackgroundMigration::BatchedMigration.for_configuration(job_class_name, batch_table_name, batch_column_name, job_arguments).exists?
+ gitlab_schema ||= gitlab_schema_from_context
+
+ Gitlab::Database::BackgroundMigration::BatchedMigration.reset_column_information
+
+ if Gitlab::Database::BackgroundMigration::BatchedMigration.for_configuration(gitlab_schema, job_class_name, batch_table_name, batch_column_name, job_arguments).exists?
Gitlab::AppLogger.warn "Batched background migration not enqueued because it already exists: " \
"job_class_name: #{job_class_name}, table_name: #{batch_table_name}, column_name: #{batch_column_name}, " \
"job_arguments: #{job_arguments.inspect}"
@@ -119,11 +125,17 @@ module Gitlab
end
end
+ if migration.respond_to?(:gitlab_schema)
+ migration.gitlab_schema = gitlab_schema
+ end
+
migration.save!
migration
end
def finalize_batched_background_migration(job_class_name:, table_name:, column_name:, job_arguments:)
+ Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_dml_mode!
+
database_name = Gitlab::Database.db_config_name(connection)
unless ActiveRecord::Base.configurations.primary?(database_name)
@@ -132,12 +144,47 @@ module Gitlab
'https://docs.gitlab.com/ee/development/database/migrations_for_multiple_databases.html'
end
- migration = Gitlab::Database::BackgroundMigration::BatchedMigration.find_for_configuration(job_class_name, table_name, column_name, job_arguments)
+ Gitlab::Database::BackgroundMigration::BatchedMigration.reset_column_information
+
+ migration = Gitlab::Database::BackgroundMigration::BatchedMigration.find_for_configuration(
+ gitlab_schema_from_context, job_class_name, table_name, column_name, job_arguments)
raise 'Could not find batched background migration' if migration.nil?
Gitlab::Database::BackgroundMigration::BatchedMigrationRunner.finalize(job_class_name, table_name, column_name, job_arguments, connection: connection)
end
+
+ # Deletes batched background migration for the given configuration.
+ #
+ # job_class_name - The background migration job class as a string
+ # table_name - The name of the table the migration iterates over
+ # column_name - The name of the column the migration will batch over
+ # job_arguments - Migration arguments
+ #
+ # Example:
+ #
+ # delete_batched_background_migration(
+ # 'CopyColumnUsingBackgroundMigrationJob',
+ # :events,
+ # :id,
+ # ['column1', 'column2'])
+ def delete_batched_background_migration(job_class_name, table_name, column_name, job_arguments)
+ Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_dml_mode!
+ Gitlab::Database::BackgroundMigration::BatchedMigration.reset_column_information
+
+ Gitlab::Database::BackgroundMigration::BatchedMigration
+ .for_configuration(
+ gitlab_schema_from_context, job_class_name, table_name, column_name, job_arguments
+ ).delete_all
+ end
+
+ def gitlab_schema_from_context
+ if respond_to?(:allowed_gitlab_schemas) # Gitlab::Database::Migration::V2_0
+ Array(allowed_gitlab_schemas).first
+ else # Gitlab::Database::Migration::V1_0
+ :gitlab_main
+ end
+ end
end
end
end
diff --git a/lib/gitlab/patch/database_config.rb b/lib/gitlab/patch/database_config.rb
index c5c73d50518..20d8f7be8fd 100644
--- a/lib/gitlab/patch/database_config.rb
+++ b/lib/gitlab/patch/database_config.rb
@@ -1,77 +1,15 @@
# frozen_string_literal: true
-# The purpose of this code is to transform legacy `database.yml`
-# into a `database.yml` containing `main:` as a name of a first database
-#
-# This should be removed once all places using legacy `database.yml`
-# are fixed. The likely moment to remove this check is the %14.0.
-#
-# This converts the following syntax:
-#
-# production:
-# adapter: postgresql
-# database: gitlabhq_production
-# username: git
-# password: "secure password"
-# host: localhost
-#
-# Into:
-#
-# production:
-# main:
-# adapter: postgresql
-# database: gitlabhq_production
-# username: git
-# password: "secure password"
-# host: localhost
-#
-
+# The purpose of this code is to set the migrations path
+# for the Geo tracking database.
module Gitlab
module Patch
module DatabaseConfig
extend ActiveSupport::Concern
- def load_database_yaml
- return super unless Gitlab.ee?
-
- super.deep_merge(load_geo_database_yaml)
- end
-
- # This method is taken from Rails to load a database YAML file without
- # evaluating ERB. This allows us to create the rake tasks for the Geo
- # tracking database without filling in the configuration values or
- # loading the environment. To be removed when we start configure Geo
- # tracking database in database.yml instead of custom database_geo.yml
- #
- # https://github.com/rails/rails/blob/v6.1.4/railties/lib/rails/application/configuration.rb#L255
- def load_geo_database_yaml
- path = Rails.root.join("config/database_geo.yml")
- return {} unless File.exist?(path)
-
- require "rails/application/dummy_erb_compiler"
-
- yaml = DummyERB.new(Pathname.new(path).read).result
- config = YAML.load(yaml) || {} # rubocop:disable Security/YAMLLoad
-
- config.to_h do |env, configs|
- # This check is taken from Rails where the transformation
- # of a flat database.yml is done into `primary:`
- # https://github.com/rails/rails/blob/v6.1.4/activerecord/lib/active_record/database_configurations.rb#L169
- if configs.is_a?(Hash) && !configs.all? { |_, v| v.is_a?(Hash) }
- configs = { "geo" => configs }
- end
-
- [env, configs]
- end
- end
-
def database_configuration
super.to_h do |env, configs|
if Gitlab.ee?
- if !configs.key?("geo") && File.exist?(Rails.root.join("config/database_geo.yml"))
- configs["geo"] = Rails.application.config_for(:database_geo).stringify_keys
- end
-
if configs.key?("geo")
migrations_paths = Array(configs["geo"]["migrations_paths"])
migrations_paths << "ee/db/geo/migrate" if migrations_paths.empty?
diff --git a/lib/tasks/rubocop.rake b/lib/tasks/rubocop.rake
index 6eabdf51dcd..28c370e5ca6 100644
--- a/lib/tasks/rubocop.rake
+++ b/lib/tasks/rubocop.rake
@@ -36,7 +36,7 @@ unless Rails.env.production?
# expected.
cop_names = args.to_a
- todo_dir = RuboCop::TodoDir.new(RuboCop::TodoDir::DEFAULT_TODO_DIR)
+ todo_dir = RuboCop::TodoDir.new(RuboCop::Formatter::TodoFormatter.base_directory)
if cop_names.any?
# We are sorting the cop names to benefit from RuboCop cache which
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 96137675f03..af61511a366 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -9671,6 +9671,9 @@ msgstr ""
msgid "ContainerRegistry|Copy build command"
msgstr ""
+msgid "ContainerRegistry|Copy image path"
+msgstr ""
+
msgid "ContainerRegistry|Copy login command"
msgstr ""
@@ -16850,6 +16853,9 @@ msgstr ""
msgid "Geo|Shards to synchronize"
msgstr ""
+msgid "Geo|Show more"
+msgstr ""
+
msgid "Geo|Site name can't be blank"
msgstr ""
@@ -40792,6 +40798,12 @@ msgstr ""
msgid "UsageQuota|This namespace has no projects which use shared runners"
msgstr ""
+msgid "UsageQuota|This namespace has no projects which used shared runners in the current period"
+msgstr ""
+
+msgid "UsageQuota|This table omits projects that used 0 CI/CD minutes or 0 shared runners duration"
+msgstr ""
+
msgid "UsageQuota|Total excess storage used"
msgstr ""
diff --git a/rubocop/formatter/todo_formatter.rb b/rubocop/formatter/todo_formatter.rb
index 14b4242063e..662cc1551ff 100644
--- a/rubocop/formatter/todo_formatter.rb
+++ b/rubocop/formatter/todo_formatter.rb
@@ -14,13 +14,6 @@ module RuboCop
# For example, this formatter stores offenses for `RSpec/VariableName`
# in `.rubocop_todo/rspec/variable_name.yml`.
class TodoFormatter < BaseFormatter
- # Disable a cop which exceeds this limit. This way we ensure that we
- # don't enable a cop by accident when moving it from
- # .rubocop_todo.yml to .rubocop_todo/.
- # We keep the cop disabled if it has been disabled previously explicitly
- # via `Enabled: false` in .rubocop_todo.yml or .rubocop_todo/.
- MAX_OFFENSE_COUNT = 15
-
class Todo
attr_reader :cop_name, :files, :offense_count
@@ -41,12 +34,20 @@ module RuboCop
end
end
- def initialize(output, options = {})
- directory = options.delete(:rubocop_todo_dir) || TodoDir::DEFAULT_TODO_DIR
+ DEFAULT_BASE_DIRECTORY = File.expand_path('../../.rubocop_todo', __dir__)
+
+ class << self
+ attr_accessor :base_directory
+ end
+
+ self.base_directory = DEFAULT_BASE_DIRECTORY
+
+ def initialize(output, _options = {})
+ @directory = self.class.base_directory
@todos = Hash.new { |hash, cop_name| hash[cop_name] = Todo.new(cop_name) }
@todo_dir = TodoDir.new(directory)
- @config_inspect_todo_dir = load_config_inspect_todo_dir(directory)
- @config_old_todo_yml = load_config_old_todo_yml(directory)
+ @config_inspect_todo_dir = load_config_inspect_todo_dir
+ @config_old_todo_yml = load_config_old_todo_yml
check_multiple_configurations!
super
@@ -71,10 +72,21 @@ module RuboCop
end
end
+ def self.with_base_directory(directory)
+ old = base_directory
+ self.base_directory = directory
+
+ yield
+ ensure
+ self.base_directory = old
+ end
+
private
+ attr_reader :directory
+
def relative_path(path)
- parent = File.expand_path('..', @todo_dir.directory)
+ parent = File.expand_path('..', directory)
path.delete_prefix("#{parent}/")
end
@@ -84,7 +96,7 @@ module RuboCop
yaml << '# Cop supports --auto-correct.' if todo.autocorrectable?
yaml << "#{todo.cop_name}:"
- if previously_disabled?(todo) && offense_count_exceeded?(todo)
+ if previously_disabled?(todo)
yaml << " # Offense count: #{todo.offense_count}"
yaml << ' # Temporarily disabled due to too many offenses'
yaml << ' Enabled: false'
@@ -99,10 +111,6 @@ module RuboCop
yaml.join("\n")
end
- def offense_count_exceeded?(todo)
- todo.offense_count > MAX_OFFENSE_COUNT
- end
-
def check_multiple_configurations!
cop_names = @config_inspect_todo_dir.keys & @config_old_todo_yml.keys
return if cop_names.empty?
@@ -121,7 +129,7 @@ module RuboCop
config['Enabled'] == false
end
- def load_config_inspect_todo_dir(directory)
+ def load_config_inspect_todo_dir
@todo_dir.list_inspect.each_with_object({}) do |path, combined|
config = YAML.load_file(path)
combined.update(config) if Hash === config
@@ -130,7 +138,7 @@ module RuboCop
# Load YAML configuration from `.rubocop_todo.yml`.
# We consider this file already old, obsolete, and to be removed soon.
- def load_config_old_todo_yml(directory)
+ def load_config_old_todo_yml
path = File.expand_path(File.join(directory, '../.rubocop_todo.yml'))
config = YAML.load_file(path) if File.exist?(path)
diff --git a/rubocop/todo_dir.rb b/rubocop/todo_dir.rb
index 4aca4454a06..57b9895a925 100644
--- a/rubocop/todo_dir.rb
+++ b/rubocop/todo_dir.rb
@@ -6,22 +6,36 @@ require 'active_support/inflector/inflections'
module RuboCop
# Helper class to manage file access to RuboCop TODOs in .rubocop_todo directory.
class TodoDir
- DEFAULT_TODO_DIR = File.expand_path('../.rubocop_todo', __dir__)
+ # Suffix a TODO file.
+ SUFFIX_YAML = '.yml'
# Suffix to indicate TODOs being inspected right now.
SUFFIX_INSPECT = '.inspect'
- attr_reader :directory
-
+ # Instantiates a TodoDir.
+ #
+ # @param directory [String] base directory where all TODO YAML files are written to.
+ # @param inflector [ActiveSupport::Inflector, #underscore] an object which supports
+ # converting a string to its underscored version.
def initialize(directory, inflector: ActiveSupport::Inflector)
@directory = directory
@inflector = inflector
end
- def read(cop_name, suffix = nil)
- read_suffixed(cop_name)
+ # Reads content of TODO YAML for given +cop_name+.
+ #
+ # @param cop_name [String] name of the cop rule
+ #
+ # @return [String, nil] content of the TODO YAML file if it exists
+ def read(cop_name)
+ path = path_for(cop_name)
+
+ File.read(path) if File.exist?(path)
end
+ # Saves +content+ for given +cop_name+ to TODO YAML file.
+ #
+ # @return [String] path of the written TODO YAML file
def write(cop_name, content)
path = path_for(cop_name)
@@ -31,6 +45,10 @@ module RuboCop
path
end
+ # Marks a TODO YAML file for inspection by renaming the original TODO YAML
+ # and appending the suffix +.inspect+ to it.
+ #
+ # @return [Boolean] +true+ a file was marked for inspection successfully.
def inspect(cop_name)
path = path_for(cop_name)
@@ -42,38 +60,47 @@ module RuboCop
end
end
+ # Marks all TODO YAML files for inspection.
+ #
+ # @return [Integer] number of renamed YAML TODO files.
+ #
+ # @see inspect
def inspect_all
- pattern = File.join(@directory, '**/*.yml')
+ pattern = File.join(@directory, "**/*#{SUFFIX_YAML}")
Dir.glob(pattern).count do |path|
FileUtils.mv(path, "#{path}#{SUFFIX_INSPECT}")
end
end
+ # Returns a list of TODO YAML files which are marked for inspection.
+ #
+ # @return [Array<String>] list of paths
+ #
+ # @see inspect
+ # @see inspect_all
def list_inspect
- pattern = File.join(@directory, "**/*.yml.inspect")
+ pattern = File.join(@directory, "**/*#{SUFFIX_YAML}#{SUFFIX_INSPECT}")
Dir.glob(pattern)
end
+ # Deletes a list of TODO yaml files which were marked for inspection.
+ #
+ # @return [Integer] number of deleted YAML TODO files.
+ #
+ # @see #inspect
+ # @see #inspect_all
def delete_inspected
- pattern = File.join(@directory, '**/*.yml.inspect')
-
- Dir.glob(pattern).count do |path|
+ list_inspect.count do |path|
File.delete(path)
end
end
private
- def read_suffixed(cop_name, suffix = nil)
- path = path_for(cop_name, suffix)
-
- File.read(path) if File.exist?(path)
- end
-
- def path_for(cop_name, suffix = nil)
- todo_path = "#{@inflector.underscore(cop_name)}.yml#{suffix}"
+ def path_for(cop_name)
+ todo_path = "#{@inflector.underscore(cop_name)}#{SUFFIX_YAML}"
File.join(@directory, todo_path)
end
diff --git a/scripts/lib/glfm/update_specification.rb b/scripts/lib/glfm/update_specification.rb
index df648f4115b..73c23d40de5 100644
--- a/scripts/lib/glfm/update_specification.rb
+++ b/scripts/lib/glfm/update_specification.rb
@@ -12,13 +12,35 @@ module Glfm
def process
output('Updating specification...')
- ghfm_spec_txt_lines = download_and_write_ghfm_spec_txt
+ ghfm_spec_txt_lines = load_ghfm_spec_txt
glfm_spec_txt_string = build_glfm_spec_txt(ghfm_spec_txt_lines)
write_glfm_spec_txt(glfm_spec_txt_string)
end
private
+ def load_ghfm_spec_txt
+ # We only re-download the GitHub Flavored Markdown specification if the
+ # UPDATE_GHFM_SPEC_TXT environment variable is set to true, which should only
+ # ever be done manually and locally, never in CI. This provides some security
+ # protection against a possible injection attack vector, if the GitHub-hosted
+ # version of the spec is ever temporarily compromised with an injection attack.
+ #
+ # This also avoids doing external network access to download the file
+ # in CI jobs, which can avoid potentially flaky builds if the GitHub-hosted
+ # version of the file is temporarily unavailable.
+ if ENV['UPDATE_GHFM_SPEC_TXT'] == 'true'
+ download_and_write_ghfm_spec_txt
+ else
+ read_existing_ghfm_spec_txt
+ end
+ end
+
+ def read_existing_ghfm_spec_txt
+ output("Reading existing #{GHFM_SPEC_TXT_PATH}...")
+ File.open(GHFM_SPEC_TXT_PATH).readlines
+ end
+
def download_and_write_ghfm_spec_txt
output("Downloading #{GHFM_SPEC_TXT_URI}...")
ghfm_spec_txt_uri_io = URI.open(GHFM_SPEC_TXT_URI)
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 4b528696322..d68432d9ec0 100644
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -17,8 +17,11 @@ else
cp config/database.yml.postgresql config/database.yml
fi
-if [ -f config/database_geo.yml.postgresql ]; then
- cp config/database_geo.yml.postgresql config/database_geo.yml
+# Remove Geo database setting if `ee/` directory does not exist. When it does
+# not exist, it runs the GitLab test suite "as if FOSS", meaning the jobs run
+# in the context of gitlab-org/gitlab-foss where the Geo is not available.
+if [ ! -d "ee/" ] ; then
+ sed -i '/geo:/,/^$/d' config/database.yml
fi
# Set user to a non-superuser to ensure we test permissions
@@ -27,11 +30,6 @@ sed -i 's/username: root/username: gitlab/g' config/database.yml
sed -i 's/localhost/postgres/g' config/database.yml
sed -i 's/username: git/username: postgres/g' config/database.yml
-if [ -f config/database_geo.yml ]; then
- sed -i 's/localhost/postgres/g' config/database_geo.yml
- sed -i 's/username: git/username: postgres/g' config/database_geo.yml
-fi
-
cp config/cable.yml.example config/cable.yml
sed -i 's|url:.*$|url: redis://redis:6379|g' config/cable.yml
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index e21c73976a8..0895e13c3c3 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -94,7 +94,10 @@ RSpec.describe 'Database schema' do
vulnerability_identifiers: %w[external_id],
vulnerability_scanners: %w[external_id],
security_scans: %w[pipeline_id], # foreign key is not added as ci_pipeline table will be moved into different db soon
- vulnerability_reads: %w[cluster_agent_id]
+ vulnerability_reads: %w[cluster_agent_id],
+ # See: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87584
+ # Fixes performance issues with the deletion of web-hooks with many log entries
+ web_hook_logs: %w[web_hook_id]
}.with_indifferent_access.freeze
context 'for table' do
diff --git a/spec/factories/alert_management/alerts.rb b/spec/factories/alert_management/alerts.rb
index 7e9e58edc1e..443e43d5fd1 100644
--- a/spec/factories/alert_management/alerts.rb
+++ b/spec/factories/alert_management/alerts.rb
@@ -95,10 +95,6 @@ FactoryBot.define do
severity { 'unknown' }
end
- trait :threat_monitoring do
- domain { :threat_monitoring }
- end
-
trait :prometheus do
monitoring_tool { Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus] }
payload do
diff --git a/spec/factories/gitlab/database/background_migration/batched_migrations.rb b/spec/factories/gitlab/database/background_migration/batched_migrations.rb
index 5ff90ff44b9..ea0fb571cc4 100644
--- a/spec/factories/gitlab/database/background_migration/batched_migrations.rb
+++ b/spec/factories/gitlab/database/background_migration/batched_migrations.rb
@@ -12,6 +12,7 @@ FactoryBot.define do
sequence(:job_arguments) { |n| [["column_#{n}"], ["column_#{n}_convert_to_bigint"]] }
total_tuple_count { 10_000 }
pause_ms { 100 }
+ gitlab_schema { :gitlab_main }
trait(:paused) do
status { 0 }
diff --git a/spec/features/projects/ci/editor_spec.rb b/spec/features/projects/ci/editor_spec.rb
index 2f960c09936..8197fe46c7b 100644
--- a/spec/features/projects/ci/editor_spec.rb
+++ b/spec/features/projects/ci/editor_spec.rb
@@ -12,8 +12,6 @@ RSpec.describe 'Pipeline Editor', :js do
let(:other_branch) { 'test' }
before do
- stub_feature_flags(pipeline_editor_file_tree: false)
-
sign_in(user)
project.add_developer(user)
@@ -70,14 +68,8 @@ RSpec.describe 'Pipeline Editor', :js do
expect(page).to have_content('Pipeline Editor')
end
- describe 'Branch Switcher (pipeline_editor_file_tree disabled)' do
- it_behaves_like 'default branch switcher behavior'
- end
-
- describe 'Branch Switcher (pipeline_editor_file_tree enabled)' do
+ describe 'Branch Switcher' do
before do
- stub_feature_flags(pipeline_editor_file_tree: true)
-
visit project_ci_pipeline_editor_path(project)
wait_for_requests
diff --git a/spec/features/projects/releases/user_views_edit_release_spec.rb b/spec/features/projects/releases/user_views_edit_release_spec.rb
index f08f5529472..6551b254643 100644
--- a/spec/features/projects/releases/user_views_edit_release_spec.rb
+++ b/spec/features/projects/releases/user_views_edit_release_spec.rb
@@ -30,10 +30,12 @@ RSpec.describe 'User edits Release', :js do
it 'renders the breadcrumbs' do
within('.breadcrumbs') do
- expect(page).to have_content("#{project.creator.name} #{project.name} Edit Release")
+ expect(page).to have_content("#{project.creator.name} #{project.name} Releases #{release.name} Edit Release")
expect(page).to have_link(project.creator.name, href: user_path(project.creator))
expect(page).to have_link(project.name, href: project_path(project))
+ expect(page).to have_link(_('Releases'), href: project_releases_path(project))
+ expect(page).to have_link(release.name, href: project_release_path(project, release))
expect(page).to have_link('Edit Release', href: edit_project_release_path(project, release))
end
end
diff --git a/spec/frontend/editor/schema/ci/ci_schema_spec.js b/spec/frontend/editor/schema/ci/ci_schema_spec.js
index 628c34a27c1..c59806a5d60 100644
--- a/spec/frontend/editor/schema/ci/ci_schema_spec.js
+++ b/spec/frontend/editor/schema/ci/ci_schema_spec.js
@@ -38,6 +38,7 @@ const ajv = new Ajv({
strictTuples: false,
allowMatchingProperties: true,
});
+ajv.addKeyword('markdownDescription');
AjvFormats(ajv);
const schema = ajv.compile(CiSchema);
diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js
index d92ba527b5c..7de653e3069 100644
--- a/spec/frontend/issues/list/components/issues_list_app_spec.js
+++ b/spec/frontend/issues/list/components/issues_list_app_spec.js
@@ -301,17 +301,23 @@ describe('CE IssuesListApp component', () => {
describe('initial url params', () => {
describe('page', () => {
it('page_after is set from the url params', () => {
- setWindowLocation('?page_after=randomCursorString');
+ setWindowLocation('?page_after=randomCursorString&first_page_size=20');
wrapper = mountComponent();
- expect(wrapper.vm.$route.query).toMatchObject({ page_after: 'randomCursorString' });
+ expect(wrapper.vm.$route.query).toMatchObject({
+ page_after: 'randomCursorString',
+ first_page_size: '20',
+ });
});
it('page_before is set from the url params', () => {
- setWindowLocation('?page_before=anotherRandomCursorString');
+ setWindowLocation('?page_before=anotherRandomCursorString&last_page_size=20');
wrapper = mountComponent();
- expect(wrapper.vm.$route.query).toMatchObject({ page_before: 'anotherRandomCursorString' });
+ expect(wrapper.vm.$route.query).toMatchObject({
+ page_before: 'anotherRandomCursorString',
+ last_page_size: '20',
+ });
});
});
@@ -675,10 +681,10 @@ describe('CE IssuesListApp component', () => {
});
describe.each`
- event | paramName | paramValue
- ${'next-page'} | ${'page_after'} | ${'endCursor'}
- ${'previous-page'} | ${'page_before'} | ${'startCursor'}
- `('when "$event" event is emitted by IssuableList', ({ event, paramName, paramValue }) => {
+ event | params
+ ${'next-page'} | ${{ page_after: 'endCursor', page_before: undefined, first_page_size: 20, last_page_size: undefined }}
+ ${'previous-page'} | ${{ page_after: undefined, page_before: 'startCursor', first_page_size: undefined, last_page_size: 20 }}
+ `('when "$event" event is emitted by IssuableList', ({ event, params }) => {
beforeEach(() => {
wrapper = mountComponent({
data: {
@@ -697,9 +703,9 @@ describe('CE IssuesListApp component', () => {
expect(scrollUp).toHaveBeenCalled();
});
- it(`updates url with "${paramName}" param`, () => {
+ it(`updates url`, () => {
expect(wrapper.vm.$router.push).toHaveBeenCalledWith({
- query: expect.objectContaining({ [paramName]: paramValue }),
+ query: expect.objectContaining(params),
});
});
});
diff --git a/spec/frontend/issues/list/utils_spec.js b/spec/frontend/issues/list/utils_spec.js
index ce0477883d7..e8ffba9bc80 100644
--- a/spec/frontend/issues/list/utils_spec.js
+++ b/spec/frontend/issues/list/utils_spec.js
@@ -42,27 +42,37 @@ describe('getInitialPageParams', () => {
'returns the correct page params for sort key %s with afterCursor',
(sortKey) => {
const firstPageSize = sortKey === RELATIVE_POSITION_ASC ? PAGE_SIZE_MANUAL : PAGE_SIZE;
+ const lastPageSize = undefined;
const afterCursor = 'randomCursorString';
const beforeCursor = undefined;
-
- expect(getInitialPageParams(sortKey, afterCursor, beforeCursor)).toEqual({
+ const pageParams = getInitialPageParams(
+ sortKey,
firstPageSize,
+ lastPageSize,
afterCursor,
- });
+ beforeCursor,
+ );
+
+ expect(pageParams).toEqual({ firstPageSize, afterCursor });
},
);
it.each(Object.keys(urlSortParams))(
'returns the correct page params for sort key %s with beforeCursor',
(sortKey) => {
- const firstPageSize = sortKey === RELATIVE_POSITION_ASC ? PAGE_SIZE_MANUAL : PAGE_SIZE;
+ const firstPageSize = undefined;
+ const lastPageSize = PAGE_SIZE;
const afterCursor = undefined;
const beforeCursor = 'anotherRandomCursorString';
-
- expect(getInitialPageParams(sortKey, afterCursor, beforeCursor)).toEqual({
+ const pageParams = getInitialPageParams(
+ sortKey,
firstPageSize,
+ lastPageSize,
+ afterCursor,
beforeCursor,
- });
+ );
+
+ expect(pageParams).toEqual({ lastPageSize, beforeCursor });
},
);
});
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js
index 057312828ff..84f01f10f21 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js
@@ -10,6 +10,7 @@ import {
MISSING_MANIFEST_WARNING_TOOLTIP,
NOT_AVAILABLE_TEXT,
NOT_AVAILABLE_SIZE,
+ COPY_IMAGE_PATH_TITLE,
} from '~/packages_and_registries/container_registry/explorer/constants/index';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
@@ -150,7 +151,7 @@ describe('tags list row', () => {
expect(findClipboardButton().attributes()).toMatchObject({
text: tag.location,
- title: tag.location,
+ title: COPY_IMAGE_PATH_TITLE,
});
});
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
index 690d827ec67..979e1500d7d 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
@@ -13,6 +13,7 @@ import {
IMAGE_MIGRATING_STATE,
SCHEDULED_STATUS,
ROOT_IMAGE_TEXT,
+ COPY_IMAGE_PATH_TITLE,
} from '~/packages_and_registries/container_registry/explorer/constants';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
@@ -106,7 +107,7 @@ describe('Image List Row', () => {
const button = findClipboardButton();
expect(button.exists()).toBe(true);
expect(button.props('text')).toBe(item.location);
- expect(button.props('title')).toBe(item.location);
+ expect(button.props('title')).toBe(COPY_IMAGE_PATH_TITLE);
});
describe('cleanup status component', () => {
diff --git a/spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js b/spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js
index a61796dbed2..d503aff40b8 100644
--- a/spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js
+++ b/spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js
@@ -23,7 +23,6 @@ describe('Pipeline editor file nav', () => {
const createComponent = ({
appStatus = EDITOR_APP_STATUS_VALID,
isNewCiConfigFile = false,
- pipelineEditorFileTree = false,
} = {}) => {
mockApollo.clients.defaultClient.cache.writeQuery({
query: getAppStatus,
@@ -38,11 +37,6 @@ describe('Pipeline editor file nav', () => {
wrapper = extendedWrapper(
shallowMount(PipelineEditorFileNav, {
apolloProvider: mockApollo,
- provide: {
- glFeatures: {
- pipelineEditorFileTree,
- },
- },
propsData: {
isNewCiConfigFile,
},
@@ -66,24 +60,12 @@ describe('Pipeline editor file nav', () => {
it('renders the branch switcher', () => {
expect(findBranchSwitcher().exists()).toBe(true);
});
-
- it('does not render the file tree button', () => {
- expect(findFileTreeBtn().exists()).toBe(false);
- });
-
- it('does not render the file tree popover', () => {
- expect(findPopoverContainer().exists()).toBe(false);
- });
});
- describe('with pipelineEditorFileTree feature flag ON', () => {
+ describe('file tree', () => {
describe('when editor is in the empty state', () => {
beforeEach(() => {
- createComponent({
- appStatus: EDITOR_APP_STATUS_EMPTY,
- isNewCiConfigFile: false,
- pipelineEditorFileTree: true,
- });
+ createComponent({ appStatus: EDITOR_APP_STATUS_EMPTY, isNewCiConfigFile: false });
});
it('does not render the file tree button', () => {
@@ -97,11 +79,7 @@ describe('Pipeline editor file nav', () => {
describe('when user is about to create their config file for the first time', () => {
beforeEach(() => {
- createComponent({
- appStatus: EDITOR_APP_STATUS_VALID,
- isNewCiConfigFile: true,
- pipelineEditorFileTree: true,
- });
+ createComponent({ appStatus: EDITOR_APP_STATUS_VALID, isNewCiConfigFile: true });
});
it('does not render the file tree button', () => {
@@ -115,11 +93,7 @@ describe('Pipeline editor file nav', () => {
describe('when app is in a global loading state', () => {
it('renders the file tree button with a loading icon', () => {
- createComponent({
- appStatus: EDITOR_APP_STATUS_LOADING,
- isNewCiConfigFile: false,
- pipelineEditorFileTree: true,
- });
+ createComponent({ appStatus: EDITOR_APP_STATUS_LOADING, isNewCiConfigFile: false });
expect(findFileTreeBtn().exists()).toBe(true);
expect(findFileTreeBtn().attributes('loading')).toBe('true');
@@ -128,11 +102,7 @@ describe('Pipeline editor file nav', () => {
describe('when editor has a non-empty config file open', () => {
beforeEach(() => {
- createComponent({
- appStatus: EDITOR_APP_STATUS_VALID,
- isNewCiConfigFile: false,
- pipelineEditorFileTree: true,
- });
+ createComponent({ appStatus: EDITOR_APP_STATUS_VALID, isNewCiConfigFile: false });
});
it('renders the file tree button', () => {
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
index bf0f7fd8c9f..6307a5aefe7 100644
--- a/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
+++ b/spec/frontend/pipeline_editor/pipeline_editor_home_spec.js
@@ -247,81 +247,63 @@ describe('Pipeline editor home wrapper', () => {
await nextTick();
};
- describe('with pipelineEditorFileTree feature flag OFF', () => {
+ describe('button toggle', () => {
beforeEach(() => {
- createComponent();
+ createComponent({
+ stubs: {
+ GlButton,
+ PipelineEditorFileNav,
+ },
+ });
});
- it('hides the file tree', () => {
- expect(findFileTreeBtn().exists()).toBe(false);
- expect(findPipelineEditorFileTree().exists()).toBe(false);
+ it('shows button toggle', () => {
+ expect(findFileTreeBtn().exists()).toBe(true);
});
- });
-
- describe('with pipelineEditorFileTree feature flag ON', () => {
- describe('button toggle', () => {
- beforeEach(() => {
- createComponent({
- glFeatures: {
- pipelineEditorFileTree: true,
- },
- stubs: {
- GlButton,
- PipelineEditorFileNav,
- },
- });
- });
-
- it('shows button toggle', () => {
- expect(findFileTreeBtn().exists()).toBe(true);
- });
- it('toggles the drawer on button click', async () => {
- await toggleFileTree();
+ it('toggles the drawer on button click', async () => {
+ await toggleFileTree();
- expect(findPipelineEditorFileTree().exists()).toBe(true);
+ expect(findPipelineEditorFileTree().exists()).toBe(true);
- await toggleFileTree();
+ await toggleFileTree();
- expect(findPipelineEditorFileTree().exists()).toBe(false);
- });
+ expect(findPipelineEditorFileTree().exists()).toBe(false);
+ });
- it('sets the display state in local storage', async () => {
- await toggleFileTree();
+ it('sets the display state in local storage', async () => {
+ await toggleFileTree();
- expect(localStorage.getItem(FILE_TREE_DISPLAY_KEY)).toBe('true');
+ expect(localStorage.getItem(FILE_TREE_DISPLAY_KEY)).toBe('true');
- await toggleFileTree();
+ await toggleFileTree();
- expect(localStorage.getItem(FILE_TREE_DISPLAY_KEY)).toBe('false');
- });
+ expect(localStorage.getItem(FILE_TREE_DISPLAY_KEY)).toBe('false');
});
+ });
- describe('when file tree display state is saved in local storage', () => {
- beforeEach(() => {
- localStorage.setItem(FILE_TREE_DISPLAY_KEY, 'true');
- createComponent({
- glFeatures: { pipelineEditorFileTree: true },
- stubs: { PipelineEditorFileNav },
- });
+ describe('when file tree display state is saved in local storage', () => {
+ beforeEach(() => {
+ localStorage.setItem(FILE_TREE_DISPLAY_KEY, 'true');
+ createComponent({
+ stubs: { PipelineEditorFileNav },
});
+ });
- it('shows the file tree by default', () => {
- expect(findPipelineEditorFileTree().exists()).toBe(true);
- });
+ it('shows the file tree by default', () => {
+ expect(findPipelineEditorFileTree().exists()).toBe(true);
});
+ });
- describe('when file tree display state is not saved in local storage', () => {
- beforeEach(() => {
- createComponent({
- glFeatures: { pipelineEditorFileTree: true },
- stubs: { PipelineEditorFileNav },
- });
+ describe('when file tree display state is not saved in local storage', () => {
+ beforeEach(() => {
+ createComponent({
+ stubs: { PipelineEditorFileNav },
});
+ });
- it('hides the file tree by default', () => {
- expect(findPipelineEditorFileTree().exists()).toBe(false);
- });
+ it('hides the file tree by default', () => {
+ expect(findPipelineEditorFileTree().exists()).toBe(false);
});
});
});
diff --git a/spec/initializers/validate_database_config_spec.rb b/spec/initializers/validate_database_config_spec.rb
index 5f3f950a852..23a3d9a2950 100644
--- a/spec/initializers/validate_database_config_spec.rb
+++ b/spec/initializers/validate_database_config_spec.rb
@@ -14,9 +14,6 @@ RSpec.describe 'validate database config' do
end
before do
- allow(File).to receive(:exist?).and_call_original
- allow(File).to receive(:exist?).with(Rails.root.join("config/database_geo.yml")).and_return(false)
-
# The `AS::ConfigurationFile` calls `read` in `def initialize`
# thus we cannot use `expect_next_instance_of`
# rubocop:disable RSpec/AnyInstanceOf
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
index f147e8204e6..97459d4a7be 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
@@ -311,6 +311,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
let(:table_name) { :_test_batched_migrations_test_table }
let(:column_name) { :some_id }
let(:job_arguments) { [:some_id, :some_id_convert_to_bigint] }
+ let(:gitlab_schemas) { Gitlab::Database.gitlab_schemas_for_connection(connection) }
let(:migration_status) { :active }
@@ -358,7 +359,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
it 'completes the migration' do
expect(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:find_for_configuration)
- .with('CopyColumnUsingBackgroundMigrationJob', table_name, column_name, job_arguments)
+ .with(gitlab_schemas, 'CopyColumnUsingBackgroundMigrationJob', table_name, column_name, job_arguments)
.and_return(batched_migration)
expect(batched_migration).to receive(:finalize!).and_call_original
@@ -399,7 +400,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
it 'is a no-op' do
expect(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:find_for_configuration)
- .with('CopyColumnUsingBackgroundMigrationJob', table_name, column_name, job_arguments)
+ .with(gitlab_schemas, 'CopyColumnUsingBackgroundMigrationJob', table_name, column_name, job_arguments)
.and_return(batched_migration)
configuration = {
@@ -426,7 +427,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
context 'when the migration does not exist' do
it 'is a no-op' do
expect(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:find_for_configuration)
- .with('CopyColumnUsingBackgroundMigrationJob', table_name, column_name, [:some, :other, :arguments])
+ .with(gitlab_schemas, 'CopyColumnUsingBackgroundMigrationJob', table_name, column_name, [:some, :other, :arguments])
.and_return(nil)
configuration = {
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
index a1c979bba50..8819171cfd0 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
@@ -78,23 +78,41 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end
describe '.active_migration' do
+ let(:connection) { Gitlab::Database.database_base_models[:main].connection }
let!(:migration1) { create(:batched_background_migration, :finished) }
- context 'without migrations on hold' do
+ subject(:active_migration) { described_class.active_migration(connection: connection) }
+
+ around do |example|
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ example.run
+ end
+ end
+
+ context 'when there are no migrations on hold' do
let!(:migration2) { create(:batched_background_migration, :active) }
let!(:migration3) { create(:batched_background_migration, :active) }
it 'returns the first active migration according to queue order' do
- expect(described_class.active_migration).to eq(migration2)
+ expect(active_migration).to eq(migration2)
end
end
- context 'with migrations are on hold' do
+ context 'when there are migrations on hold' do
let!(:migration2) { create(:batched_background_migration, :active, on_hold_until: 10.minutes.from_now) }
let!(:migration3) { create(:batched_background_migration, :active, on_hold_until: 2.minutes.ago) }
it 'returns the first active migration that is not on hold according to queue order' do
- expect(described_class.active_migration).to eq(migration3)
+ expect(active_migration).to eq(migration3)
+ end
+ end
+
+ context 'when there are migrations not available for the current connection' do
+ let!(:migration2) { create(:batched_background_migration, :active, gitlab_schema: :gitlab_not_existing) }
+ let!(:migration3) { create(:batched_background_migration, :active, gitlab_schema: :gitlab_main) }
+
+ it 'returns the first active migration that is available for the current connection' do
+ expect(active_migration).to eq(migration3)
end
end
end
@@ -553,25 +571,43 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end
describe '.for_configuration' do
- let!(:migration) do
- create(
- :batched_background_migration,
+ let!(:attributes) do
+ {
job_class_name: 'MyJobClass',
table_name: :projects,
column_name: :id,
- job_arguments: [[:id], [:id_convert_to_bigint]]
- )
+ job_arguments: [[:id], [:id_convert_to_bigint]],
+ gitlab_schema: :gitlab_main
+ }
end
+ let!(:migration) { create(:batched_background_migration, attributes) }
+
before do
- create(:batched_background_migration, job_class_name: 'OtherClass')
- create(:batched_background_migration, table_name: 'other_table')
- create(:batched_background_migration, column_name: 'other_column')
- create(:batched_background_migration, job_arguments: %w[other arguments])
+ create(:batched_background_migration, attributes.merge(job_class_name: 'OtherClass'))
+ create(:batched_background_migration, attributes.merge(table_name: 'other_table'))
+ create(:batched_background_migration, attributes.merge(column_name: 'other_column'))
+ create(:batched_background_migration, attributes.merge(job_arguments: %w[other arguments]))
end
it 'finds the migration matching the given configuration parameters' do
- actual = described_class.for_configuration('MyJobClass', :projects, :id, [[:id], [:id_convert_to_bigint]])
+ actual = described_class.for_configuration(:gitlab_main, 'MyJobClass', :projects, :id, [[:id], [:id_convert_to_bigint]])
+
+ expect(actual).to contain_exactly(migration)
+ end
+
+ it 'filters by gitlab schemas available for the connection' do
+ actual = described_class.for_configuration(:gitlab_ci, 'MyJobClass', :projects, :id, [[:id], [:id_convert_to_bigint]])
+
+ expect(actual).to be_empty
+ end
+
+ it 'doesn not filter by gitlab schemas available for the connection if the column is nor present' do
+ skip_if_multiple_databases_not_setup
+
+ expect(described_class).to receive(:gitlab_schema_column_exists?).and_return(false)
+
+ actual = described_class.for_configuration(:gitlab_main, 'MyJobClass', :projects, :id, [[:id], [:id_convert_to_bigint]])
expect(actual).to contain_exactly(migration)
end
@@ -579,7 +615,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
describe '.find_for_configuration' do
it 'returns nill if such migration does not exists' do
- expect(described_class.find_for_configuration('MyJobClass', :projects, :id, [[:id], [:id_convert_to_bigint]])).to be_nil
+ expect(described_class.find_for_configuration(:gitlab_main, 'MyJobClass', :projects, :id, [[:id], [:id_convert_to_bigint]])).to be_nil
end
it 'returns the migration when it exists' do
@@ -588,10 +624,25 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
job_class_name: 'MyJobClass',
table_name: :projects,
column_name: :id,
- job_arguments: [[:id], [:id_convert_to_bigint]]
+ job_arguments: [[:id], [:id_convert_to_bigint]],
+ gitlab_schema: :gitlab_main
)
- expect(described_class.find_for_configuration('MyJobClass', :projects, :id, [[:id], [:id_convert_to_bigint]])).to eq(migration)
+ expect(described_class.find_for_configuration(:gitlab_main, 'MyJobClass', :projects, :id, [[:id], [:id_convert_to_bigint]])).to eq(migration)
+ end
+ end
+
+ describe '.for_gitlab_schema' do
+ let!(:migration) { create(:batched_background_migration, gitlab_schema: :gitlab_main) }
+
+ before do
+ create(:batched_background_migration, gitlab_schema: :gitlab_not_existing)
+ end
+
+ it 'finds the migrations matching the given gitlab schema' do
+ actual = described_class.for_gitlab_schema(:gitlab_main)
+
+ expect(actual).to contain_exactly(migration)
end
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 04fe1fad10e..5d9963bf3ea 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -2242,10 +2242,16 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
}
end
+ let(:migration_attributes) do
+ configuration.merge(gitlab_schema: Gitlab::Database.gitlab_schemas_for_connection(model.connection).first)
+ end
+
subject(:ensure_batched_background_migration_is_finished) { model.ensure_batched_background_migration_is_finished(**configuration) }
it 'raises an error when migration exists and is not marked as finished' do
- create(:batched_background_migration, :active, configuration)
+ expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_dml_mode!).twice
+
+ create(:batched_background_migration, :active, migration_attributes)
allow_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |runner|
allow(runner).to receive(:finalize).with(job_class_name, table, column_name, job_arguments).and_return(false)
@@ -2265,13 +2271,19 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
it 'does not raise error when migration exists and is marked as finished' do
- create(:batched_background_migration, :finished, configuration)
+ expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_dml_mode!)
+
+ create(:batched_background_migration, :finished, migration_attributes)
expect { ensure_batched_background_migration_is_finished }
.not_to raise_error
end
it 'logs a warning when migration does not exist' do
+ expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_dml_mode!)
+
+ create(:batched_background_migration, :active, migration_attributes.merge(gitlab_schema: :gitlab_something_else))
+
expect(Gitlab::AppLogger).to receive(:warn)
.with("Could not find batched background migration for the given configuration: #{configuration}")
@@ -2280,6 +2292,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
it 'finalizes the migration' do
+ expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_dml_mode!).twice
+
migration = create(:batched_background_migration, :active, configuration)
allow_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |runner|
@@ -2291,6 +2305,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
context 'when the flag finalize is false' do
it 'does not finalize the migration' do
+ expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_dml_mode!)
+
create(:batched_background_migration, :active, configuration)
allow_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |runner|
diff --git a/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb
index d1a66036149..87b6c65bf7e 100644
--- a/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
before do
allow(Gitlab::Database::PgClass).to receive(:for_table).and_call_original
+ expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_dml_mode!)
end
context 'when such migration already exists' do
@@ -27,7 +28,8 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
batch_class_name: 'MyBatchClass',
batch_size: 200,
sub_batch_size: 20,
- job_arguments: [[:id], [:id_convert_to_bigint]]
+ job_arguments: [[:id], [:id_convert_to_bigint]],
+ gitlab_schema: :gitlab_ci
)
expect do
@@ -41,7 +43,8 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
batch_max_value: 1000,
batch_class_name: 'MyBatchClass',
batch_size: 100,
- sub_batch_size: 10)
+ sub_batch_size: 10,
+ gitlab_schema: :gitlab_ci)
end.not_to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }
end
end
@@ -60,7 +63,8 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
batch_class_name: 'MyBatchClass',
batch_size: 100,
max_batch_size: 10000,
- sub_batch_size: 10)
+ sub_batch_size: 10,
+ gitlab_schema: :gitlab_ci)
end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1)
expect(Gitlab::Database::BackgroundMigration::BatchedMigration.last).to have_attributes(
@@ -76,7 +80,8 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
sub_batch_size: 10,
job_arguments: %w[],
status_name: :active,
- total_tuple_count: pgclass_info.cardinality_estimate)
+ total_tuple_count: pgclass_info.cardinality_estimate,
+ gitlab_schema: 'gitlab_ci')
end
context 'when the job interval is lower than the minimum' do
@@ -162,11 +167,29 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
end
end
end
+
+ context 'when gitlab_schema is not given' do
+ it 'fetches gitlab_schema from the migration context' do
+ expect(migration).to receive(:gitlab_schema_from_context).and_return(:gitlab_ci)
+
+ expect do
+ migration.queue_batched_background_migration('MyJobClass', :events, :id, job_interval: 5.minutes)
+ end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1)
+
+ created_migration = Gitlab::Database::BackgroundMigration::BatchedMigration.last
+
+ expect(created_migration.gitlab_schema).to eq('gitlab_ci')
+ end
+ end
end
describe '#finalize_batched_background_migration' do
let!(:batched_migration) { create(:batched_background_migration, job_class_name: 'MyClass', table_name: :projects, column_name: :id, job_arguments: []) }
+ before do
+ expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_dml_mode!)
+ end
+
it 'finalizes the migration' do
allow_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |runner|
expect(runner).to receive(:finalize).with('MyClass', :projects, :id, [])
@@ -204,4 +227,78 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
end
end
end
+
+ describe '#delete_batched_background_migration' do
+ before do
+ expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_dml_mode!)
+ end
+
+ context 'when migration exists' do
+ it 'deletes it' do
+ create(
+ :batched_background_migration,
+ job_class_name: 'MyJobClass',
+ table_name: :projects,
+ column_name: :id,
+ interval: 10.minutes,
+ min_value: 5,
+ max_value: 1005,
+ batch_class_name: 'MyBatchClass',
+ batch_size: 200,
+ sub_batch_size: 20,
+ job_arguments: [[:id], [:id_convert_to_bigint]]
+ )
+
+ expect do
+ migration.delete_batched_background_migration(
+ 'MyJobClass',
+ :projects,
+ :id,
+ [[:id], [:id_convert_to_bigint]])
+ end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.from(1).to(0)
+ end
+ end
+
+ context 'when migration does not exist' do
+ it 'does nothing' do
+ create(
+ :batched_background_migration,
+ job_class_name: 'SomeOtherJobClass',
+ table_name: :projects,
+ column_name: :id,
+ interval: 10.minutes,
+ min_value: 5,
+ max_value: 1005,
+ batch_class_name: 'MyBatchClass',
+ batch_size: 200,
+ sub_batch_size: 20,
+ job_arguments: [[:id], [:id_convert_to_bigint]]
+ )
+
+ expect do
+ migration.delete_batched_background_migration(
+ 'MyJobClass',
+ :projects,
+ :id,
+ [[:id], [:id_convert_to_bigint]])
+ end.not_to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }
+ end
+ end
+ end
+
+ describe '#gitlab_schema_from_context' do
+ context 'when allowed_gitlab_schemas is not available' do
+ it 'defaults to :gitlab_main' do
+ expect(migration.gitlab_schema_from_context).to eq(:gitlab_main)
+ end
+ end
+
+ context 'when allowed_gitlab_schemas is available' do
+ it 'uses schema from allowed_gitlab_schema' do
+ expect(migration).to receive(:allowed_gitlab_schemas).and_return([:gitlab_ci])
+
+ expect(migration.gitlab_schema_from_context).to eq(:gitlab_ci)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/patch/database_config_spec.rb b/spec/lib/gitlab/patch/database_config_spec.rb
index 73dc84bb2ef..b06d28dbcd5 100644
--- a/spec/lib/gitlab/patch/database_config_spec.rb
+++ b/spec/lib/gitlab/patch/database_config_spec.rb
@@ -11,9 +11,6 @@ RSpec.describe Gitlab::Patch::DatabaseConfig do
let(:configuration) { Rails::Application::Configuration.new(Rails.root) }
before do
- allow(File).to receive(:exist?).and_call_original
- allow(File).to receive(:exist?).with(Rails.root.join("config/database_geo.yml")).and_return(false)
-
# The `AS::ConfigurationFile` calls `read` in `def initialize`
# thus we cannot use `expect_next_instance_of`
# rubocop:disable RSpec/AnyInstanceOf
diff --git a/spec/lib/gitlab/subscription_portal_spec.rb b/spec/lib/gitlab/subscription_portal_spec.rb
index 8d5a39baf77..098a58bff83 100644
--- a/spec/lib/gitlab/subscription_portal_spec.rb
+++ b/spec/lib/gitlab/subscription_portal_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe ::Gitlab::SubscriptionPortal do
using RSpec::Parameterized::TableSyntax
+ include SubscriptionPortalHelper
let(:env_value) { nil }
@@ -13,9 +14,9 @@ RSpec.describe ::Gitlab::SubscriptionPortal do
describe '.default_subscriptions_url' do
where(:test, :development, :result) do
- false | false | 'https://customers.gitlab.com'
- false | true | 'https://customers.staging.gitlab.com'
- true | false | 'https://customers.staging.gitlab.com'
+ false | false | prod_customers_url
+ false | true | staging_customers_url
+ true | false | staging_customers_url
end
before do
@@ -34,7 +35,7 @@ RSpec.describe ::Gitlab::SubscriptionPortal do
subject { described_class.subscriptions_url }
context 'when CUSTOMER_PORTAL_URL ENV is unset' do
- it { is_expected.to eq('https://customers.staging.gitlab.com') }
+ it { is_expected.to eq(staging_customers_url) }
end
context 'when CUSTOMER_PORTAL_URL ENV is set' do
@@ -54,17 +55,17 @@ RSpec.describe ::Gitlab::SubscriptionPortal do
context 'url methods' do
where(:method_name, :result) do
- :default_subscriptions_url | 'https://customers.staging.gitlab.com'
- :payment_form_url | 'https://customers.staging.gitlab.com/payment_forms/cc_validation'
- :payment_validation_form_id | 'payment_method_validation'
- :registration_validation_form_url | 'https://customers.staging.gitlab.com/payment_forms/cc_registration_validation'
- :subscriptions_graphql_url | 'https://customers.staging.gitlab.com/graphql'
- :subscriptions_more_minutes_url | 'https://customers.staging.gitlab.com/buy_pipeline_minutes'
- :subscriptions_more_storage_url | 'https://customers.staging.gitlab.com/buy_storage'
- :subscriptions_manage_url | 'https://customers.staging.gitlab.com/subscriptions'
- :subscriptions_instance_review_url | 'https://customers.staging.gitlab.com/instance_review'
- :subscriptions_gitlab_plans_url | 'https://customers.staging.gitlab.com/gitlab_plans'
- :edit_account_url | 'https://customers.staging.gitlab.com/customers/edit'
+ :default_subscriptions_url | staging_customers_url
+ :payment_form_url | "#{staging_customers_url}/payment_forms/cc_validation"
+ :payment_validation_form_id | 'payment_method_validation'
+ :registration_validation_form_url | "#{staging_customers_url}/payment_forms/cc_registration_validation"
+ :subscriptions_graphql_url | "#{staging_customers_url}/graphql"
+ :subscriptions_more_minutes_url | "#{staging_customers_url}/buy_pipeline_minutes"
+ :subscriptions_more_storage_url | "#{staging_customers_url}/buy_storage"
+ :subscriptions_manage_url | "#{staging_customers_url}/subscriptions"
+ :subscriptions_instance_review_url | "#{staging_customers_url}/instance_review"
+ :subscriptions_gitlab_plans_url | "#{staging_customers_url}/gitlab_plans"
+ :edit_account_url | "#{staging_customers_url}/customers/edit"
end
with_them do
@@ -79,7 +80,10 @@ RSpec.describe ::Gitlab::SubscriptionPortal do
let(:group_id) { 153 }
- it { is_expected.to eq("https://customers.staging.gitlab.com/gitlab/namespaces/#{group_id}/extra_seats") }
+ it do
+ url = "#{staging_customers_url}/gitlab/namespaces/#{group_id}/extra_seats"
+ is_expected.to eq(url)
+ end
end
describe '.upgrade_subscription_url' do
@@ -88,7 +92,10 @@ RSpec.describe ::Gitlab::SubscriptionPortal do
let(:group_id) { 153 }
let(:plan_id) { 5 }
- it { is_expected.to eq("https://customers.staging.gitlab.com/gitlab/namespaces/#{group_id}/upgrade/#{plan_id}") }
+ it do
+ url = "#{staging_customers_url}/gitlab/namespaces/#{group_id}/upgrade/#{plan_id}"
+ is_expected.to eq(url)
+ end
end
describe '.renew_subscription_url' do
@@ -96,6 +103,9 @@ RSpec.describe ::Gitlab::SubscriptionPortal do
let(:group_id) { 153 }
- it { is_expected.to eq("https://customers.staging.gitlab.com/gitlab/namespaces/#{group_id}/renew") }
+ it do
+ url = "#{staging_customers_url}/gitlab/namespaces/#{group_id}/renew"
+ is_expected.to eq(url)
+ end
end
end
diff --git a/spec/migrations/20220503035221_add_gitlab_schema_to_batched_background_migrations_spec.rb b/spec/migrations/20220503035221_add_gitlab_schema_to_batched_background_migrations_spec.rb
new file mode 100644
index 00000000000..5002c665c79
--- /dev/null
+++ b/spec/migrations/20220503035221_add_gitlab_schema_to_batched_background_migrations_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe AddGitlabSchemaToBatchedBackgroundMigrations do
+ it 'sets gitlab_schema for existing methods to "gitlab_main" and default to NULL' do
+ batched_migrations = table(:batched_background_migrations)
+ batched_migration = batched_migrations.create!(
+ id: 1, created_at: Time.now, updated_at: Time.now,
+ max_value: 100, batch_size: 100, sub_batch_size: 10, interval: 120,
+ job_class_name: 'TestJob', table_name: '_test', column_name: 'id'
+ )
+
+ reversible_migration do |migration|
+ migration.before -> {
+ batched_migrations.reset_column_information
+ column = batched_migrations.columns.find { |column| column.name == 'gitlab_schema' }
+
+ expect(column).to be_nil
+ }
+
+ migration.after -> {
+ expect(batched_migration.reload.gitlab_schema).to eq('gitlab_main')
+
+ batched_migrations.reset_column_information
+ column = batched_migrations.columns.find { |column| column.name == 'gitlab_schema' }
+
+ expect(column).to be
+ expect(column.default).to be_nil
+ }
+ end
+ end
+end
diff --git a/spec/migrations/20220505044348_fix_automatic_iterations_cadences_start_date_spec.rb b/spec/migrations/20220505044348_fix_automatic_iterations_cadences_start_date_spec.rb
index 8bc336a6b26..575157f8331 100644
--- a/spec/migrations/20220505044348_fix_automatic_iterations_cadences_start_date_spec.rb
+++ b/spec/migrations/20220505044348_fix_automatic_iterations_cadences_start_date_spec.rb
@@ -4,8 +4,7 @@ require 'spec_helper'
require_migration!
-RSpec.describe FixAutomaticIterationsCadencesStartDate,
- quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/362446' do
+RSpec.describe FixAutomaticIterationsCadencesStartDate do
let(:migration) { described_class.new }
let(:namespaces) { table(:namespaces) }
let(:sprints) { table(:sprints) }
diff --git a/spec/migrations/20220512190659_remove_web_hooks_web_hook_logs_web_hook_id_fk_spec.rb b/spec/migrations/20220512190659_remove_web_hooks_web_hook_logs_web_hook_id_fk_spec.rb
new file mode 100644
index 00000000000..fa94a73582d
--- /dev/null
+++ b/spec/migrations/20220512190659_remove_web_hooks_web_hook_logs_web_hook_id_fk_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe RemoveWebHooksWebHookLogsWebHookIdFk do
+ let(:web_hooks) { table(:web_hooks) }
+ let(:logs) { table(:web_hook_logs) }
+
+ let!(:hook) { web_hooks.create! }
+
+ let!(:log_a) { logs.create!(web_hook_id: hook.id, response_body: 'msg-a') }
+ let!(:log_b) { logs.create!(web_hook_id: hook.id, response_body: 'msg-b') }
+
+ describe '#up' do
+ it 'allows us to delete web-hooks and leave web-hook logs intact' do
+ migrate!
+
+ expect { hook.delete }.not_to change(logs, :count)
+
+ expect(logs.pluck(:response_body)).to match_array %w[msg-a msg-b]
+ end
+ end
+
+ describe '#down' do
+ it 'ensures referential integrity of hook logs' do
+ migrate!
+ schema_migrate_down!
+
+ expect { hook.delete }.to change(logs, :count).by(-2)
+ end
+ end
+end
diff --git a/spec/migrations/remove_not_null_contraint_on_title_from_sprints_spec.rb b/spec/migrations/remove_not_null_contraint_on_title_from_sprints_spec.rb
index fdafc4a5a89..198644fe183 100644
--- a/spec/migrations/remove_not_null_contraint_on_title_from_sprints_spec.rb
+++ b/spec/migrations/remove_not_null_contraint_on_title_from_sprints_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require_migration!
-RSpec.describe RemoveNotNullContraintOnTitleFromSprints, :migration, schema: 20220304052335 do
+RSpec.describe RemoveNotNullContraintOnTitleFromSprints, :migration do
let(:migration) { described_class.new }
let(:namespaces) { table(:namespaces) }
let(:sprints) { table(:sprints) }
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index dd954e08156..290cf500852 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -130,11 +130,11 @@ RSpec.describe WebHook do
end
describe '#destroy' do
- it 'cascades to web_hook_logs' do
+ it 'does not cascade to web_hook_logs' do
web_hook = create(:project_hook)
create_list(:web_hook_log, 3, web_hook: web_hook)
- expect { web_hook.destroy! }.to change(web_hook.web_hook_logs, :count).by(-3)
+ expect { web_hook.destroy! }.not_to change(web_hook.web_hook_logs, :count)
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index d40c78b5b60..32639b3e7d2 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -5077,4 +5077,57 @@ RSpec.describe MergeRequest, factory_default: :keep do
expect(assignees).to match_array([subject.merge_request_assignees[0]])
end
end
+
+ describe '#recent_diff_head_shas' do
+ let_it_be(:merge_request_with_diffs) do
+ params = {
+ target_project: project,
+ source_project: project,
+ target_branch: 'master',
+ source_branch: 'feature'
+ }
+
+ create(:merge_request, params).tap do |mr|
+ 4.times { mr.merge_request_diffs.create! }
+ mr.create_merge_head_diff
+ end
+ end
+
+ let(:shas) do
+ # re-find to avoid caching the association
+ described_class.find(merge_request_with_diffs.id).merge_request_diffs.order(id: :desc).pluck(:head_commit_sha)
+ end
+
+ shared_examples 'correctly sorted and limited diff_head_shas' do
+ it 'has up to MAX_RECENT_DIFF_HEAD_SHAS, ordered most recent first' do
+ stub_const('MergeRequest::MAX_RECENT_DIFF_HEAD_SHAS', 3)
+
+ expect(subject.recent_diff_head_shas).to eq(shas.first(3))
+ end
+
+ it 'supports limits' do
+ expect(subject.recent_diff_head_shas(2)).to eq(shas.first(2))
+ end
+ end
+
+ context 'when the association is not loaded' do
+ subject(:mr) { merge_request_with_diffs }
+
+ include_examples 'correctly sorted and limited diff_head_shas'
+ end
+
+ context 'when the association is loaded' do
+ subject(:mr) do
+ described_class.where(id: merge_request_with_diffs.id).preload(:merge_request_diffs).first
+ end
+
+ include_examples 'correctly sorted and limited diff_head_shas'
+
+ it 'does not issue any queries' do
+ expect(subject).to be_a(described_class) # preload here
+
+ expect { subject.recent_diff_head_shas }.not_to exceed_query_limit(0)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb b/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb
index 820a5d818c7..4dc272b5c2e 100644
--- a/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe 'Query.project.mergeRequests.pipelines' do
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:author) { create(:user) }
+ let_it_be(:mr_nodes_path) { [:data, :project, :merge_requests, :nodes] }
let_it_be(:merge_requests) do
[
create(:merge_request, author: author, source_project: project),
@@ -33,8 +34,49 @@ RSpec.describe 'Query.project.mergeRequests.pipelines' do
GQL
end
- def run_query(first = nil)
- post_graphql(query, current_user: author, variables: { path: project.full_path, first: first })
+ before do
+ merge_requests.first(2).each do |mr|
+ shas = mr.recent_diff_head_shas
+
+ shas.each do |sha|
+ create(:ci_pipeline, :success, project: project, ref: mr.source_branch, sha: sha)
+ end
+ end
+ end
+
+ it 'produces correct results' do
+ r = run_query(3)
+
+ nodes = graphql_dig_at(r, *mr_nodes_path)
+
+ expect(nodes).to all(match('iid' => be_present, 'pipelines' => include('count' => be_a(Integer))))
+ expect(graphql_dig_at(r, *mr_nodes_path, :pipelines, :count)).to contain_exactly(1, 1, 0)
+ end
+
+ it 'is scalable', :request_store, :use_clean_rails_memory_store_caching do
+ baseline = ActiveRecord::QueryRecorder.new { run_query(1) }
+
+ expect { run_query(2) }.not_to exceed_query_limit(baseline)
+ end
+ end
+
+ describe '.nodes' do
+ let(:query) do
+ <<~GQL
+ query($path: ID!, $first: Int) {
+ project(fullPath: $path) {
+ mergeRequests(first: $first) {
+ nodes {
+ iid
+ pipelines {
+ count
+ nodes { id }
+ }
+ }
+ }
+ }
+ }
+ GQL
end
before do
@@ -48,18 +90,27 @@ RSpec.describe 'Query.project.mergeRequests.pipelines' do
end
it 'produces correct results' do
- run_query(2)
-
- p_nodes = graphql_data_at(:project, :merge_requests, :nodes)
+ r = run_query
- expect(p_nodes).to all(match('iid' => be_present, 'pipelines' => match('count' => 1)))
+ expect(graphql_dig_at(r, *mr_nodes_path, :pipelines, :nodes, :id).uniq.size).to eq 3
end
it 'is scalable', :request_store, :use_clean_rails_memory_store_caching do
- # warm up
- run_query
+ baseline = ActiveRecord::QueryRecorder.new { run_query(1) }
- expect { run_query(2) }.to(issue_same_number_of_queries_as { run_query(1) }.ignoring_cached_queries)
+ expect { run_query(2) }.not_to exceed_query_limit(baseline)
end
+
+ it 'requests merge_request_diffs at most once' do
+ r = ActiveRecord::QueryRecorder.new { run_query(2) }
+
+ expect(r.log.grep(/merge_request_diffs/)).to contain_exactly(a_string_including('SELECT'))
+ end
+ end
+
+ def run_query(first = nil)
+ run_with_clean_state(query,
+ context: { current_user: author },
+ variables: { path: project.full_path, first: first })
end
end
diff --git a/spec/requests/api/usage_data_spec.rb b/spec/requests/api/usage_data_spec.rb
index 84f16446bc4..ea50c404d92 100644
--- a/spec/requests/api/usage_data_spec.rb
+++ b/spec/requests/api/usage_data_spec.rb
@@ -7,9 +7,7 @@ RSpec.describe API::UsageData do
describe 'POST /usage_data/increment_counter' do
let(:endpoint) { '/usage_data/increment_counter' }
- let(:known_event) { "#{known_event_prefix}_#{known_event_postfix}" }
- let(:known_event_prefix) { "static_site_editor" }
- let(:known_event_postfix) { 'commits' }
+ let(:known_event) { "diff_searches" }
let(:unknown_event) { 'unknown' }
context 'without CSRF token' do
@@ -57,27 +55,18 @@ RSpec.describe API::UsageData do
end
context 'with correct params' do
- using RSpec::Parameterized::TableSyntax
-
- where(:prefix, :event) do
- 'static_site_editor' | 'merge_requests'
- 'static_site_editor' | 'commits'
- end
-
before do
stub_application_setting(usage_ping_enabled: true)
stub_feature_flags(usage_data_api: true)
allow(Gitlab::RequestForgeryProtection).to receive(:verified?).and_return(true)
end
- with_them do
- it 'returns status :ok' do
- expect(Gitlab::UsageDataCounters::BaseCounter).to receive(:count).with(event)
+ it 'returns status :ok' do
+ expect(Gitlab::UsageDataCounters::BaseCounter).to receive(:count).with("searches")
- post api(endpoint, user), params: { event: "#{prefix}_#{event}" }
+ post api(endpoint, user), params: { event: known_event }
- expect(response).to have_gitlab_http_status(:ok)
- end
+ expect(response).to have_gitlab_http_status(:ok)
end
end
diff --git a/spec/requests/mailgun/webhooks_controller_spec.rb b/spec/requests/mailgun/webhooks_controller_spec.rb
new file mode 100644
index 00000000000..ae6dc89d003
--- /dev/null
+++ b/spec/requests/mailgun/webhooks_controller_spec.rb
@@ -0,0 +1,149 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mailgun::WebhooksController do
+ let(:mailgun_signing_key) { 'abc123' }
+ let(:valid_signature) do
+ {
+ timestamp: "1625056677",
+ token: "eb944d0ace7227667a1b97d2d07276ae51d2b849ed2cfa68f3",
+ signature: "9790cc6686eb70f0b1f869180d906870cdfd496d27fee81da0aa86b9e539e790"
+ }
+ end
+
+ let(:event_data) { {} }
+
+ before do
+ stub_application_setting(mailgun_events_enabled: true, mailgun_signing_key: mailgun_signing_key)
+ end
+
+ def post_request(override_params = {})
+ post mailgun_webhooks_path, params: standard_params.merge(override_params)
+ end
+
+ describe '#process_webhook' do
+ it 'returns 406 when integration is not enabled' do
+ stub_application_setting(mailgun_events_enabled: false)
+
+ post_request
+
+ expect(response).to have_gitlab_http_status(:not_acceptable)
+ end
+
+ it 'returns 404 when signing key is not configured' do
+ stub_application_setting(mailgun_signing_key: nil)
+
+ post_request
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 when signature invalid' do
+ post_request(
+ 'signature' => valid_signature.merge('signature' => 'xxx')
+ )
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 200 when signature is valid' do
+ post_request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'invite email failures' do
+ let_it_be(:member) { create(:project_member, :invited) }
+
+ let(:event_data) do
+ {
+ 'event': 'failed',
+ 'severity': 'permanent',
+ 'tags': [Members::Mailgun::INVITE_EMAIL_TAG],
+ 'user-variables': {
+ Members::Mailgun::INVITE_EMAIL_TOKEN_KEY => member.raw_invite_token
+ }
+ }
+ end
+
+ it 'marks the member invite email success as false' do
+ expect { post_request }.to change { member.reload.invite_email_success }.from(true).to(false)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'supports legacy URL' do
+ expect do
+ post members_mailgun_permanent_failures_path, params: {
+ 'signature' => valid_signature,
+ 'event-data' => event_data
+ }
+ end.to change { member.reload.invite_email_success }.from(true).to(false)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'does not change the invite status if failure is temporary' do
+ expect do
+ post_request({ 'event-data' => event_data.merge(severity: 'temporary') })
+ end.not_to change { member.reload.invite_email_success }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ def standard_params
+ {
+ "signature": valid_signature,
+ "event-data": {
+ "severity": "permanent",
+ "tags": ["invite_email"],
+ "timestamp": 1521233195.375624,
+ "storage": {
+ "url": "_anything_",
+ "key": "_anything_"
+ },
+ "log-level": "error",
+ "id": "_anything_",
+ "campaigns": [],
+ "reason": "suppress-bounce",
+ "user-variables": {
+ "invite_token": '12345'
+ },
+ "flags": {
+ "is-routed": false,
+ "is-authenticated": true,
+ "is-system-test": false,
+ "is-test-mode": false
+ },
+ "recipient-domain": "example.com",
+ "envelope": {
+ "sender": "bob@mg.gitlab.com",
+ "transport": "smtp",
+ "targets": "alice@example.com"
+ },
+ "message": {
+ "headers": {
+ "to": "Alice <alice@example.com>",
+ "message-id": "20130503192659.13651.20287@mg.gitlab.com",
+ "from": "Bob <bob@mg.gitlab.com>",
+ "subject": "Test permanent_fail webhook"
+ },
+ "attachments": [],
+ "size": 111
+ },
+ "recipient": "alice@example.com",
+ "event": "failed",
+ "delivery-status": {
+ "attempt-no": 1,
+ "message": "",
+ "code": 605,
+ "description": "Not delivering to previously bounced address",
+ "session-seconds": 0
+ }
+ }.merge(event_data)
+ }
+ end
+end
diff --git a/spec/requests/members/mailgun/permanent_failure_spec.rb b/spec/requests/members/mailgun/permanent_failure_spec.rb
deleted file mode 100644
index e47aedf8e94..00000000000
--- a/spec/requests/members/mailgun/permanent_failure_spec.rb
+++ /dev/null
@@ -1,128 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'receive a permanent failure' do
- describe 'POST /members/mailgun/permanent_failures', :aggregate_failures do
- let_it_be(:member) { create(:project_member, :invited) }
-
- let(:raw_invite_token) { member.raw_invite_token }
- let(:mailgun_events) { true }
- let(:mailgun_signing_key) { 'abc123' }
-
- subject(:post_request) { post members_mailgun_permanent_failures_path(standard_params) }
-
- before do
- stub_application_setting(mailgun_events_enabled: mailgun_events, mailgun_signing_key: mailgun_signing_key)
- end
-
- it 'marks the member invite email success as false' do
- expect { post_request }.to change { member.reload.invite_email_success }.from(true).to(false)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
-
- context 'when the change to a member is not made' do
- context 'with incorrect signing key' do
- context 'with incorrect signing key' do
- let(:mailgun_signing_key) { '_foobar_' }
-
- it 'does not change member status and responds as not_found' do
- expect { post_request }.not_to change { member.reload.invite_email_success }
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'with nil signing key' do
- let(:mailgun_signing_key) { nil }
-
- it 'does not change member status and responds as not_found' do
- expect { post_request }.not_to change { member.reload.invite_email_success }
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- context 'when the feature is not enabled' do
- let(:mailgun_events) { false }
-
- it 'does not change member status and responds as expected' do
- expect { post_request }.not_to change { member.reload.invite_email_success }
-
- expect(response).to have_gitlab_http_status(:not_acceptable)
- end
- end
-
- context 'when it is not an invite email' do
- before do
- stub_const('::Members::Mailgun::INVITE_EMAIL_TAG', '_foobar_')
- end
-
- it 'does not change member status and responds as expected' do
- expect { post_request }.not_to change { member.reload.invite_email_success }
-
- expect(response).to have_gitlab_http_status(:not_acceptable)
- end
- end
- end
-
- def standard_params
- {
- "signature": {
- "timestamp": "1625056677",
- "token": "eb944d0ace7227667a1b97d2d07276ae51d2b849ed2cfa68f3",
- "signature": "9790cc6686eb70f0b1f869180d906870cdfd496d27fee81da0aa86b9e539e790"
- },
- "event-data": {
- "severity": "permanent",
- "tags": ["invite_email"],
- "timestamp": 1521233195.375624,
- "storage": {
- "url": "_anything_",
- "key": "_anything_"
- },
- "log-level": "error",
- "id": "_anything_",
- "campaigns": [],
- "reason": "suppress-bounce",
- "user-variables": {
- "invite_token": raw_invite_token
- },
- "flags": {
- "is-routed": false,
- "is-authenticated": true,
- "is-system-test": false,
- "is-test-mode": false
- },
- "recipient-domain": "example.com",
- "envelope": {
- "sender": "bob@mg.gitlab.com",
- "transport": "smtp",
- "targets": "alice@example.com"
- },
- "message": {
- "headers": {
- "to": "Alice <alice@example.com>",
- "message-id": "20130503192659.13651.20287@mg.gitlab.com",
- "from": "Bob <bob@mg.gitlab.com>",
- "subject": "Test permanent_fail webhook"
- },
- "attachments": [],
- "size": 111
- },
- "recipient": "alice@example.com",
- "event": "failed",
- "delivery-status": {
- "attempt-no": 1,
- "message": "",
- "code": 605,
- "description": "Not delivering to previously bounced address",
- "session-seconds": 0
- }
- }
- }
- end
- end
-end
diff --git a/spec/rubocop/formatter/todo_formatter_spec.rb b/spec/rubocop/formatter/todo_formatter_spec.rb
index e1b1de33bfe..fcff028f07d 100644
--- a/spec/rubocop/formatter/todo_formatter_spec.rb
+++ b/spec/rubocop/formatter/todo_formatter_spec.rb
@@ -14,17 +14,18 @@ RSpec.describe RuboCop::Formatter::TodoFormatter do
let(:real_tmp_dir) { File.join(tmp_dir, 'real') }
let(:symlink_tmp_dir) { File.join(tmp_dir, 'symlink') }
let(:rubocop_todo_dir) { "#{symlink_tmp_dir}/.rubocop_todo" }
- let(:options) { { rubocop_todo_dir: rubocop_todo_dir } }
let(:todo_dir) { RuboCop::TodoDir.new(rubocop_todo_dir) }
- subject(:formatter) { described_class.new(stdout, options) }
+ subject(:formatter) { described_class.new(stdout) }
around do |example|
FileUtils.mkdir(real_tmp_dir)
FileUtils.symlink(real_tmp_dir, symlink_tmp_dir)
Dir.chdir(symlink_tmp_dir) do
- example.run
+ described_class.with_base_directory(rubocop_todo_dir) do
+ example.run
+ end
end
end
@@ -38,8 +39,6 @@ RSpec.describe RuboCop::Formatter::TodoFormatter do
let(:offense_autocorrect) { fake_offense('B/AutoCorrect') }
before do
- stub_const("#{described_class}::MAX_OFFENSE_COUNT", 1)
-
stub_rubocop_registry(
'A/Offense' => { autocorrectable: false },
'B/AutoCorrect' => { autocorrectable: true }
diff --git a/spec/rubocop/todo_dir_spec.rb b/spec/rubocop/todo_dir_spec.rb
index ae59def885d..a5c12e23896 100644
--- a/spec/rubocop/todo_dir_spec.rb
+++ b/spec/rubocop/todo_dir_spec.rb
@@ -42,12 +42,6 @@ RSpec.describe RuboCop::TodoDir do
end
end
- describe '#directory' do
- subject { todo_dir.directory }
-
- it { is_expected.to eq(directory) }
- end
-
describe '#read' do
let(:content) { 'a' }
diff --git a/spec/scripts/lib/glfm/update_specification_spec.rb b/spec/scripts/lib/glfm/update_specification_spec.rb
index 941883cd1e0..e8d34b13efa 100644
--- a/spec/scripts/lib/glfm/update_specification_spec.rb
+++ b/spec/scripts/lib/glfm/update_specification_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do
let(:ghfm_spec_txt_uri) { described_class::GHFM_SPEC_TXT_URI }
let(:ghfm_spec_txt_uri_io) { StringIO.new(ghfm_spec_txt_contents) }
let(:ghfm_spec_txt_path) { described_class::GHFM_SPEC_TXT_PATH }
- let(:ghfm_spec_txt_local_io) { StringIO.new }
+ let(:ghfm_spec_txt_local_io) { StringIO.new(ghfm_spec_txt_contents) }
let(:glfm_intro_txt_path) { described_class::GLFM_INTRO_TXT_PATH }
let(:glfm_intro_txt_io) { StringIO.new(glfm_intro_txt_contents) }
@@ -71,11 +71,15 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do
end
before do
+ # Mock default ENV var values
+ allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_TXT').and_return(nil)
+ allow(ENV).to receive(:[]).and_call_original
+
# We mock out the URI and local file IO objects with real StringIO, instead of just mock
# objects. This gives better and more realistic coverage, while still avoiding
# actual network and filesystem I/O during the spec run.
allow(URI).to receive(:open).with(ghfm_spec_txt_uri) { ghfm_spec_txt_uri_io }
- allow(File).to receive(:open).with(ghfm_spec_txt_path, 'w') { ghfm_spec_txt_local_io }
+ allow(File).to receive(:open).with(ghfm_spec_txt_path) { ghfm_spec_txt_local_io }
allow(File).to receive(:open).with(glfm_intro_txt_path) { glfm_intro_txt_io }
allow(File).to receive(:open).with(glfm_examples_txt_path) { glfm_examples_txt_io }
allow(File).to receive(:open).with(glfm_spec_txt_path, 'w') { glfm_spec_txt_io }
@@ -85,45 +89,64 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do
end
describe 'retrieving latest GHFM spec.txt' do
- context 'with success' do
- it 'downloads and saves' do
+ context 'when UPDATE_GHFM_SPEC_TXT is not true (default)' do
+ it 'does not download' do
+ expect(URI).not_to receive(:open).with(ghfm_spec_txt_uri)
+
subject.process
expect(reread_io(ghfm_spec_txt_local_io)).to eq(ghfm_spec_txt_contents)
end
end
- context 'with error handling' do
- context 'with a version mismatch' do
- let(:ghfm_spec_txt_contents) do
- <<~GHFM_SPEC_TXT_CONTENTS
- ---
- title: GitHub Flavored Markdown Spec
- version: 0.30
- ...
- GHFM_SPEC_TXT_CONTENTS
- end
+ context 'when UPDATE_GHFM_SPEC_TXT is true' do
+ let(:ghfm_spec_txt_local_io) { StringIO.new }
- it 'raises an error' do
- expect { subject.process }.to raise_error /version mismatch.*expected.*29.*got.*30/i
- end
+ before do
+ allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_TXT').and_return('true')
+ allow(File).to receive(:open).with(ghfm_spec_txt_path, 'w') { ghfm_spec_txt_local_io }
end
- context 'with a failed read of file lines' do
- let(:ghfm_spec_txt_contents) { '' }
+ context 'with success' do
+ it 'downloads and saves' do
+ subject.process
- it 'raises an error if lines cannot be read' do
- expect { subject.process }.to raise_error /unable to read lines/i
+ expect(reread_io(ghfm_spec_txt_local_io)).to eq(ghfm_spec_txt_contents)
end
end
- context 'with a failed re-read of file string' do
- before do
- allow(ghfm_spec_txt_uri_io).to receive(:read).and_return(nil)
+ context 'with error handling' do
+ context 'with a version mismatch' do
+ let(:ghfm_spec_txt_contents) do
+ <<~GHFM_SPEC_TXT_CONTENTS
+ ---
+ title: GitHub Flavored Markdown Spec
+ version: 0.30
+ ...
+ GHFM_SPEC_TXT_CONTENTS
+ end
+
+ it 'raises an error' do
+ expect { subject.process }.to raise_error /version mismatch.*expected.*29.*got.*30/i
+ end
end
- it 'raises an error if file is blank' do
- expect { subject.process }.to raise_error /unable to read string/i
+ context 'with a failed read of file lines' do
+ let(:ghfm_spec_txt_contents) { '' }
+
+ it 'raises an error if lines cannot be read' do
+ expect { subject.process }.to raise_error /unable to read lines/i
+ end
+ end
+
+ context 'with a failed re-read of file string' do
+ before do
+ allow(ghfm_spec_txt_uri_io).to receive(:read).and_return(nil)
+ end
+
+ it 'raises an error if file is blank' do
+ expect { subject.process }.to raise_error /unable to read string/i
+ end
end
end
end
diff --git a/spec/services/members/mailgun/process_webhook_service_spec.rb b/spec/services/members/mailgun/process_webhook_service_spec.rb
index d6a21183395..3b657c05bd8 100644
--- a/spec/services/members/mailgun/process_webhook_service_spec.rb
+++ b/spec/services/members/mailgun/process_webhook_service_spec.rb
@@ -39,4 +39,34 @@ RSpec.describe Members::Mailgun::ProcessWebhookService do
end
end
end
+
+ describe '#should_process?' do
+ it 'processes permanent failures for member invite emails' do
+ payload = { 'event' => 'failed', 'severity' => 'permanent', 'tags' => [Members::Mailgun::INVITE_EMAIL_TAG] }
+ service = described_class.new(payload)
+
+ expect(service.should_process?).to eq(true)
+ end
+
+ it 'does not process temporary failures' do
+ payload = { 'event' => 'failed', 'severity' => 'temporary', 'tags' => [Members::Mailgun::INVITE_EMAIL_TAG] }
+ service = described_class.new(payload)
+
+ expect(service.should_process?).to eq(false)
+ end
+
+ it 'does not process non member invite emails' do
+ payload = { 'event' => 'failed', 'severity' => 'permanent', 'tags' => [] }
+ service = described_class.new(payload)
+
+ expect(service.should_process?).to eq(false)
+ end
+
+ it 'does not process other types of events' do
+ payload = { 'event' => 'delivered', 'tags' => [Members::Mailgun::INVITE_EMAIL_TAG] }
+ service = described_class.new(payload)
+
+ expect(service.should_process?).to eq(false)
+ end
+ end
end
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index 6407b8d3940..777162b6196 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -13,6 +13,7 @@ RSpec.describe Projects::UpdatePagesService do
let(:file) { fixture_file_upload("spec/fixtures/pages.zip") }
let(:empty_file) { fixture_file_upload("spec/fixtures/pages_empty.zip") }
+ let(:empty_metadata_filename) { "spec/fixtures/pages_empty.zip.meta" }
let(:metadata_filename) { "spec/fixtures/pages.zip.meta" }
let(:metadata) { fixture_file_upload(metadata_filename) if File.exist?(metadata_filename) }
@@ -91,6 +92,17 @@ RSpec.describe Projects::UpdatePagesService do
end
end
+ context 'when archive does not have pages directory' do
+ let(:file) { empty_file }
+ let(:metadata_filename) { empty_metadata_filename }
+
+ it 'returns an error' do
+ expect(execute).not_to eq(:success)
+
+ expect(GenericCommitStatus.last.description).to eq("Error: The `public/` folder is missing, or not declared in `.gitlab-ci.yml`.")
+ end
+ end
+
it 'limits pages size' do
stub_application_setting(max_pages_size: 1)
expect(execute).not_to eq(:success)
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index db8d45f61ea..7aaf11498cf 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -699,13 +699,13 @@ module GraphqlHelpers
end
# assumes query_string and user to be let-bound in the current context
- def execute_query(query_type, schema: empty_schema, graphql: query_string, raise_on_error: false)
+ def execute_query(query_type = Types::QueryType, schema: empty_schema, graphql: query_string, raise_on_error: false, variables: {})
schema.query(query_type)
r = schema.execute(
graphql,
context: { current_user: user },
- variables: {}
+ variables: variables
)
if raise_on_error && r.to_h['errors'].present?
diff --git a/spec/support/helpers/subscription_portal_helper.rb b/spec/support/helpers/subscription_portal_helper.rb
new file mode 100644
index 00000000000..53e8f78371d
--- /dev/null
+++ b/spec/support/helpers/subscription_portal_helper.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module SubscriptionPortalHelper
+ def staging_customers_url
+ 'https://customers.staging.gitlab.com'
+ end
+
+ def prod_customers_url
+ 'https://customers.gitlab.com'
+ end
+end
+
+SubscriptionPortalHelper.prepend_mod
diff --git a/spec/support/matchers/background_migrations_matchers.rb b/spec/support/matchers/background_migrations_matchers.rb
index b471323dd72..c5b3e140585 100644
--- a/spec/support/matchers/background_migrations_matchers.rb
+++ b/spec/support/matchers/background_migrations_matchers.rb
@@ -65,11 +65,13 @@ RSpec::Matchers.define :be_scheduled_migration_with_multiple_args do |*expected|
end
end
-RSpec::Matchers.define :have_scheduled_batched_migration do |table_name: nil, column_name: nil, job_arguments: [], **attributes|
+RSpec::Matchers.define :have_scheduled_batched_migration do |gitlab_schema: :gitlab_main, table_name: nil, column_name: nil, job_arguments: [], **attributes|
define_method :matches? do |migration|
+ reset_column_information(Gitlab::Database::BackgroundMigration::BatchedMigration)
+
batched_migrations =
Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(migration, table_name, column_name, job_arguments)
+ .for_configuration(gitlab_schema, migration, table_name, column_name, job_arguments)
expect(batched_migrations.count).to be(1)
expect(batched_migrations).to all(have_attributes(attributes)) if attributes.present?
diff --git a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
index 26731f34ed6..ee0c2dbfff4 100644
--- a/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
+++ b/spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb
@@ -98,10 +98,26 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
end
context 'when the feature flag is enabled' do
+ let(:base_model) { Gitlab::Database.database_base_models[tracking_database] }
+
before do
stub_feature_flags(feature_flag => true)
- allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration).and_return(nil)
+ allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration)
+ .with(connection: base_model.connection)
+ .and_return(nil)
+ end
+
+ context 'when database config is shared' do
+ it 'does nothing' do
+ expect(Gitlab::Database).to receive(:db_config_share_with)
+ .with(base_model.connection_db_config).and_return('main')
+
+ expect(worker).not_to receive(:active_migration)
+ expect(worker).not_to receive(:run_active_migration)
+
+ worker.perform
+ end
end
context 'when no active migrations exist' do
@@ -121,6 +137,7 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
before do
allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration)
+ .with(connection: base_model.connection)
.and_return(migration)
allow(migration).to receive(:interval_elapsed?).with(variance: interval_variance).and_return(true)
@@ -205,4 +222,123 @@ RSpec.shared_examples 'it runs batched background migration jobs' do |tracking_d
end
end
end
+
+ describe 'executing an entire migration', :freeze_time, if: Gitlab::Database.has_config?(tracking_database) do
+ include Gitlab::Database::DynamicModelHelpers
+
+ let(:migration_class) do
+ Class.new(Gitlab::BackgroundMigration::BatchedMigrationJob) do
+ def perform(matching_status)
+ each_sub_batch(
+ operation_name: :update_all,
+ batching_scope: -> (relation) { relation.where(status: matching_status) }
+ ) do |sub_batch|
+ sub_batch.update_all(some_column: 0)
+ end
+ end
+ end
+ end
+
+ let!(:migration) do
+ create(
+ :batched_background_migration,
+ :active,
+ table_name: table_name,
+ column_name: :id,
+ max_value: migration_records,
+ batch_size: batch_size,
+ sub_batch_size: sub_batch_size,
+ job_class_name: 'ExampleDataMigration',
+ job_arguments: [1]
+ )
+ end
+
+ let(:table_name) { 'example_data' }
+ let(:batch_size) { 5 }
+ let(:sub_batch_size) { 2 }
+ let(:number_of_batches) { 10 }
+ let(:migration_records) { batch_size * number_of_batches }
+
+ let(:connection) { Gitlab::Database.database_base_models[tracking_database].connection }
+ let(:example_data) { define_batchable_model(table_name, connection: connection) }
+
+ around do |example|
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ example.run
+ end
+ end
+
+ before do
+ # Create example table populated with test data to migrate.
+ #
+ # Test data should have two records that won't be updated:
+ # - one record beyond the migration's range
+ # - one record that doesn't match the migration job's batch condition
+ connection.execute(<<~SQL)
+ CREATE TABLE #{table_name} (
+ id integer primary key,
+ some_column integer,
+ status smallint);
+
+ INSERT INTO #{table_name} (id, some_column, status)
+ SELECT generate_series, generate_series, 1
+ FROM generate_series(1, #{migration_records + 1});
+
+ UPDATE #{table_name}
+ SET status = 0
+ WHERE some_column = #{migration_records - 5};
+ SQL
+
+ stub_feature_flags(execute_batched_migrations_on_schedule: true)
+
+ stub_const('Gitlab::BackgroundMigration::ExampleDataMigration', migration_class)
+ end
+
+ subject(:full_migration_run) do
+ # process all batches, then do an extra execution to mark the job as finished
+ (number_of_batches + 1).times do
+ described_class.new.perform
+
+ travel_to((migration.interval + described_class::INTERVAL_VARIANCE).seconds.from_now)
+ end
+ end
+
+ it 'marks the migration record as finished' do
+ expect { full_migration_run }.to change { migration.reload.status }.from(1).to(3) # active -> finished
+ end
+
+ it 'creates job records for each processed batch', :aggregate_failures do
+ expect { full_migration_run }.to change { migration.reload.batched_jobs.count }.from(0)
+
+ final_min_value = migration.batched_jobs.reduce(1) do |next_min_value, batched_job|
+ expect(batched_job.min_value).to eq(next_min_value)
+
+ batched_job.max_value + 1
+ end
+
+ final_max_value = final_min_value - 1
+ expect(final_max_value).to eq(migration_records)
+ end
+
+ it 'marks all job records as succeeded', :aggregate_failures do
+ expect { full_migration_run }.to change { migration.reload.batched_jobs.count }.from(0)
+
+ expect(migration.batched_jobs).to all(be_succeeded)
+ end
+
+ it 'updates matching records in the range', :aggregate_failures do
+ expect { full_migration_run }
+ .to change { example_data.where('status = 1 AND some_column <> 0').count }
+ .from(migration_records).to(1)
+
+ record_outside_range = example_data.last
+
+ expect(record_outside_range.status).to eq(1)
+ expect(record_outside_range.some_column).not_to eq(0)
+ end
+
+ it 'does not update non-matching records in the range' do
+ expect { full_migration_run }.not_to change { example_data.where('status <> 1 AND some_column <> 0').count }
+ end
+ end
end
diff --git a/spec/tasks/rubocop_rake_spec.rb b/spec/tasks/rubocop_rake_spec.rb
index cf7e45aae28..a92d7dc2e52 100644
--- a/spec/tasks/rubocop_rake_spec.rb
+++ b/spec/tasks/rubocop_rake_spec.rb
@@ -8,6 +8,7 @@ require 'fileutils'
require_relative '../support/silence_stdout'
require_relative '../support/helpers/next_instance_of'
require_relative '../support/helpers/rake_helpers'
+require_relative '../../rubocop/formatter/todo_formatter'
require_relative '../../rubocop/todo_dir'
RSpec.describe 'rubocop rake tasks', :silence_stdout do
@@ -29,22 +30,22 @@ RSpec.describe 'rubocop rake tasks', :silence_stdout do
around do |example|
Dir.chdir(tmp_dir) do
- with_inflections do
- example.run
+ ::RuboCop::Formatter::TodoFormatter.with_base_directory(rubocop_todo_dir) do
+ with_inflections do
+ example.run
+ end
end
end
end
before do
- allow(RuboCop::TodoDir).to receive(:new).and_return(todo_dir)
-
# This Ruby file will trigger the following 3 offenses.
File.write('a.rb', <<~RUBY)
a+b
RUBY
- # Mimic GitLab's .rubocop_todo.yml avoids relying on RuboCop's
+ # Mimicking GitLab's .rubocop_todo.yml avoids relying on RuboCop's
# default.yml configuration.
File.write('.rubocop.yml', <<~YAML)
<% unless ENV['REVEAL_RUBOCOP_TODO'] == '1' %>