summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-07-14 15:09:57 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-07-14 15:09:57 +0000
commitb689f371350fbf1b71f266764ee018befc9b91f7 (patch)
tree7de1d3ab26d3cae0ac2a7a8ccd8302fcdaac5534
parent0b194c4854f312e36616fccf7c610cb2b0ec6957 (diff)
downloadgitlab-ce-b689f371350fbf1b71f266764ee018befc9b91f7.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/setup.gitlab-ci.yml2
-rw-r--r--.rubocop.yml16
-rw-r--r--.rubocop_manual_todo.yml171
-rw-r--r--app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue36
-rw-r--r--app/assets/javascripts/boards/stores/mutations.js18
-rw-r--r--app/assets/javascripts/branches/components/delete_branch_button.vue8
-rw-r--r--app/assets/javascripts/content_editor/components/toolbar_image_button.vue110
-rw-r--r--app/assets/javascripts/content_editor/components/toolbar_link_button.vue4
-rw-r--r--app/assets/javascripts/content_editor/components/top_toolbar.vue13
-rw-r--r--app/assets/javascripts/content_editor/extensions/link.js6
-rw-r--r--app/assets/javascripts/import_entities/components/group_dropdown.vue40
-rw-r--r--app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue26
-rw-r--r--app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue14
-rw-r--r--app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue62
-rw-r--r--app/assets/javascripts/import_entities/import_projects/index.js2
-rw-r--r--app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue68
-rw-r--r--app/assets/javascripts/repository/components/tree_content.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/select2_select.vue48
-rw-r--r--app/assets/stylesheets/snippets.scss15
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/models/integrations/datadog.rb34
-rw-r--r--app/services/ci/register_job_service.rb2
-rw-r--r--app/services/service_ping/submit_service.rb42
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml4
-rw-r--r--config/feature_flags/development/escalation_policies_mvc.yml8
-rw-r--r--config/feature_flags/development/increase_page_size_exponentially.yml8
-rw-r--r--config/feature_flags/development/multiple_oncall_schedules.yml8
-rw-r--r--config/feature_flags/development/pipeline_editor_branch_switcher.yml2
-rw-r--r--config/feature_flags/development/security_configuration_redesign_ee.yml4
-rw-r--r--danger/datateam/Dangerfile8
-rw-r--r--doc/administration/geo/glossary.md4
-rw-r--r--doc/ci/pipeline_editor/index.md5
-rw-r--r--doc/ci/pipelines/settings.md96
-rw-r--r--doc/ci/runners/configure_runners.md4
-rw-r--r--doc/ci/yaml/index.md2
-rw-r--r--doc/development/code_review.md2
-rw-r--r--doc/development/database/transaction_guidelines.md117
-rw-r--r--doc/subscriptions/gitlab_com/index.md17
-rw-r--r--doc/user/compliance/license_compliance/img/policies_maintainer_edit_v13_2.pngbin20327 -> 0 bytes
-rw-r--r--doc/user/compliance/license_compliance/img/policies_maintainer_edit_v14_2.pngbin0 -> 49070 bytes
-rw-r--r--doc/user/compliance/license_compliance/index.md5
-rw-r--r--doc/user/group/repositories_analytics/index.md2
-rw-r--r--lib/api/ci/runner.rb8
-rw-r--r--lib/api/ci/runners.rb32
-rw-r--r--lib/api/entities/ci/job.rb2
-rw-r--r--lib/api/entities/ci/job_request/artifacts.rb20
-rw-r--r--lib/api/entities/ci/job_request/cache.rb13
-rw-r--r--lib/api/entities/ci/job_request/credentials.rb13
-rw-r--r--lib/api/entities/ci/job_request/dependency.rb14
-rw-r--r--lib/api/entities/ci/job_request/git_info.rb16
-rw-r--r--lib/api/entities/ci/job_request/image.rb14
-rw-r--r--lib/api/entities/ci/job_request/job_info.rb14
-rw-r--r--lib/api/entities/ci/job_request/port.rb13
-rw-r--r--lib/api/entities/ci/job_request/response.rb39
-rw-r--r--lib/api/entities/ci/job_request/runner_info.rb14
-rw-r--r--lib/api/entities/ci/job_request/service.rb13
-rw-r--r--lib/api/entities/ci/job_request/step.rb13
-rw-r--r--lib/api/entities/ci/runner.rb19
-rw-r--r--lib/api/entities/ci/runner_details.rb36
-rw-r--r--lib/api/entities/ci/runner_registration_details.rb11
-rw-r--r--lib/api/entities/job_request/artifacts.rb18
-rw-r--r--lib/api/entities/job_request/cache.rb11
-rw-r--r--lib/api/entities/job_request/credentials.rb11
-rw-r--r--lib/api/entities/job_request/dependency.rb12
-rw-r--r--lib/api/entities/job_request/git_info.rb14
-rw-r--r--lib/api/entities/job_request/image.rb12
-rw-r--r--lib/api/entities/job_request/job_info.rb12
-rw-r--r--lib/api/entities/job_request/port.rb11
-rw-r--r--lib/api/entities/job_request/response.rb37
-rw-r--r--lib/api/entities/job_request/runner_info.rb12
-rw-r--r--lib/api/entities/job_request/service.rb11
-rw-r--r--lib/api/entities/job_request/step.rb11
-rw-r--r--lib/api/entities/runner.rb17
-rw-r--r--lib/api/entities/runner_details.rb34
-rw-r--r--lib/api/entities/runner_registration_details.rb9
-rw-r--r--locale/gitlab.pot35
-rw-r--r--qa/qa/page/group/bulk_import.rb5
-rw-r--r--qa/qa/page/project/import/github.rb10
-rw-r--r--rubocop/cop/database/multiple_databases.rb32
-rw-r--r--spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js9
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js3
-rw-r--r--spec/frontend/branches/components/delete_branch_button_spec.js6
-rw-r--r--spec/frontend/content_editor/components/toolbar_image_button_spec.js78
-rw-r--r--spec/frontend/content_editor/components/toolbar_link_button_spec.js10
-rw-r--r--spec/frontend/content_editor/components/top_toolbar_spec.js1
-rw-r--r--spec/frontend/import_entities/components/group_dropdown_spec.js44
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_table_row_spec.js6
-rw-r--r--spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js4
-rw-r--r--spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js19
-rw-r--r--spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js31
-rw-r--r--spec/frontend/repository/components/tree_content_spec.js5
-rw-r--r--spec/lib/api/entities/ci/job_request/image_spec.rb (renamed from spec/lib/api/entities/job_request/image_spec.rb)2
-rw-r--r--spec/lib/api/entities/ci/job_request/port_spec.rb (renamed from spec/lib/api/entities/job_request/port_spec.rb)2
-rw-r--r--spec/models/integrations/datadog_spec.rb12
-rw-r--r--spec/rubocop/cop/database/multiple_databases_spec.rb15
-rw-r--r--spec/services/service_ping/submit_service_ping_service_spec.rb56
-rw-r--r--spec/tooling/danger/project_helper_spec.rb2
-rw-r--r--tooling/danger/project_helper.rb2
99 files changed, 1412 insertions, 556 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index b7a4ef45240..454f5b6cbf2 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -25,7 +25,7 @@ review-build-cng:
extends:
- .default-retry
- .review:rules:review-build-cng
- image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7
+ image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine3.13
stage: review-prepare
before_script:
- source ./scripts/utils.sh
diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml
index 7b1c25d3af9..f2d5d872d64 100644
--- a/.gitlab/ci/setup.gitlab-ci.yml
+++ b/.gitlab/ci/setup.gitlab-ci.yml
@@ -52,7 +52,7 @@ no_ee_check:
verify-tests-yml:
extends:
- .setup:rules:verify-tests-yml
- image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7
+ image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine3.13
stage: test
needs: []
script:
diff --git a/.rubocop.yml b/.rubocop.yml
index 981b71e582f..31bb810aab2 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -280,6 +280,22 @@ GitlabSecurity/PublicSend:
- 'ee/lib/**/*.rake'
- 'ee/spec/**/*'
+Database/MultipleDatabases:
+ Enabled: true
+ Include:
+ - 'app/**/*.rb'
+ - 'ee/app/**/*.rb'
+ - 'lib/**/*.rb'
+ - 'ee/lib/**/*.rb'
+ - 'spec/**/*.rb'
+ - 'ee/spec/**/*.rb'
+ Exclude:
+ - 'ee/db/**/*.rb'
+ - 'spec/migrations/**/*.rb'
+ - 'lib/gitlab/background_migration/**/*.rb'
+ - 'spec/lib/gitlab/background_migration/**/*.rb'
+ - 'spec/lib/gitlab/database/**/*.rb'
+
Gitlab/DuplicateSpecLocation:
Enabled: true
diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml
index 5b99d0768ec..75bace1fa22 100644
--- a/.rubocop_manual_todo.yml
+++ b/.rubocop_manual_todo.yml
@@ -2201,8 +2201,8 @@ Cop/UserAdmin:
- 'ee/lib/ee/gitlab/git_access.rb'
- 'lib/api/award_emoji.rb'
- 'lib/api/ci/runners.rb'
- - 'lib/api/entities/runner_details.rb'
- - 'lib/api/entities/user_safe.rb'
+ - 'lib/api/entities/ci/runner_details.rb'
+ - 'lib/api/entities/ci/user_safe.rb'
- 'lib/api/groups.rb'
- 'lib/api/helpers.rb'
- 'lib/api/personal_access_tokens.rb'
@@ -2465,3 +2465,170 @@ Style/RegexpLiteralMixedPreserve:
- 'lib/gitlab/regex.rb'
- 'lib/gitlab/utils.rb'
- 'lib/product_analytics/tracker.rb'
+ - 'qa/qa/page/project/settings/advanced.rb'
+ - 'qa/spec/service/docker_run/gitlab_runner_spec.rb'
+ - 'rubocop/cop/gitlab/duplicate_spec_location.rb'
+ - 'spec/features/clusters/cluster_health_dashboard_spec.rb'
+ - 'spec/features/markdown/metrics_spec.rb'
+ - 'spec/features/search/user_searches_for_code_spec.rb'
+ - 'spec/features/snippets/embedded_snippet_spec.rb'
+ - 'spec/helpers/diff_helper_spec.rb'
+ - 'spec/helpers/releases_helper_spec.rb'
+ - 'spec/lib/gitlab/ci/reports/test_case_spec.rb'
+ - 'spec/lib/gitlab/consul/internal_spec.rb'
+ - 'spec/lib/gitlab/import_export/shared_spec.rb'
+ - 'spec/lib/gitlab/utils/usage_data_spec.rb'
+ - 'spec/presenters/ci/build_runner_presenter_spec.rb'
+ - 'spec/requests/api/projects_spec.rb'
+ - 'spec/services/jira/requests/projects/list_service_spec.rb'
+ - 'spec/support/capybara.rb'
+ - 'spec/support/helpers/grafana_api_helpers.rb'
+ - 'spec/support/helpers/query_recorder.rb'
+ - 'spec/support/helpers/require_migration.rb'
+ - 'spec/views/layouts/_head.html.haml_spec.rb'
+
+Database/MultipleDatabases:
+ Exclude:
+ - 'app/mailers/previews/notify_preview.rb'
+ - 'app/models/application_setting.rb'
+ - 'app/models/internal_id.rb'
+ - 'app/services/auto_merge/base_service.rb'
+ - 'app/services/ci/delete_unit_tests_service.rb'
+ - 'app/services/ci/unlock_artifacts_service.rb'
+ - 'app/services/deployments/update_environment_service.rb'
+ - 'app/services/design_management/copy_design_collection/copy_service.rb'
+ - 'app/services/feature_flags/create_service.rb'
+ - 'app/services/feature_flags/destroy_service.rb'
+ - 'app/services/feature_flags/update_service.rb'
+ - 'app/services/issuable/clone/base_service.rb'
+ - 'app/services/issuable/common_system_notes_service.rb'
+ - 'app/services/issuable/destroy_label_links_service.rb'
+ - 'app/services/packages/create_dependency_service.rb'
+ - 'app/services/packages/go/create_package_service.rb'
+ - 'app/services/packages/npm/create_package_service.rb'
+ - 'app/services/packages/terraform_module/create_package_service.rb'
+ - 'app/services/projects/cleanup_service.rb'
+ - 'app/services/projects/fetch_statistics_increment_service.rb'
+ - 'app/services/releases/update_service.rb'
+ - 'app/services/todos/destroy/destroyed_issuable_service.rb'
+ - 'ee/app/models/dora/daily_metrics.rb'
+ - 'ee/app/services/analytics/devops_adoption/enabled_namespaces/bulk_delete_service.rb'
+ - 'ee/app/services/approval_rules/finalize_service.rb'
+ - 'ee/app/services/approval_rules/project_rule_destroy_service.rb'
+ - 'ee/app/services/app_sec/dast/site_profiles/create_service.rb'
+ - 'ee/app/services/app_sec/dast/site_profiles/update_service.rb'
+ - 'ee/app/services/ci/minutes/update_build_minutes_service.rb'
+ - 'ee/app/services/ee/issuable/common_system_notes_service.rb'
+ - 'ee/app/services/group_saml/group_managed_accounts/transfer_membership_service.rb'
+ - 'ee/app/services/group_saml/sign_up_service.rb'
+ - 'ee/app/services/iterations/roll_over_issues_service.rb'
+ - 'ee/app/services/security/store_scan_service.rb'
+ - 'ee/app/services/timebox_report_service.rb'
+ - 'ee/app/services/vulnerability_feedback/create_service.rb'
+ - 'ee/lib/ee/gitlab/checks/push_rule_check.rb'
+ - 'ee/lib/ee/gitlab/database.rb'
+ - 'ee/lib/gitlab/geo/database_tasks.rb'
+ - 'ee/lib/gitlab/geo/geo_tasks.rb'
+ - 'ee/lib/gitlab/geo/health_check.rb'
+ - 'ee/lib/gitlab/geo/log_cursor/daemon.rb'
+ - 'ee/lib/pseudonymizer/dumper.rb'
+ - 'ee/lib/pseudonymizer/pager.rb'
+ - 'ee/lib/system_check/geo/geo_database_configured_check.rb'
+ - 'ee/spec/lib/pseudonymizer/dumper_spec.rb'
+ - 'ee/spec/models/pg_replication_slot_spec.rb'
+ - 'ee/spec/services/ee/merge_requests/update_service_spec.rb'
+ - 'lib/backup/database.rb'
+ - 'lib/after_commit_queue.rb'
+ - 'lib/api/rubygem_packages.rb'
+ - 'lib/backup/manager.rb'
+ - 'lib/gitlab/analytics/cycle_analytics/stage_query_helpers.rb'
+ - 'lib/gitlab/chaos.rb'
+ - 'lib/gitlab/current_settings.rb'
+ - 'lib/gitlab/database/batch_count.rb'
+ - 'lib/gitlab/database/batch_counter.rb'
+ - 'lib/gitlab/database/count/reltuples_count_strategy.rb'
+ - 'lib/gitlab/database/count/tablesample_count_strategy.rb'
+ - 'lib/gitlab/database/grant.rb'
+ - 'lib/gitlab/database/load_balancing/load_balancer.rb'
+ - 'lib/gitlab/database/load_balancing.rb'
+ - 'lib/gitlab/database/load_balancing/sticking.rb'
+ - 'lib/gitlab/database/migrations/observers/migration_observer.rb'
+ - 'lib/gitlab/database/migrations/observers/query_log.rb'
+ - 'lib/gitlab/database/multi_threaded_migration.rb'
+ - 'lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table.rb'
+ - 'lib/gitlab/database/partitioning/monthly_strategy.rb'
+ - 'lib/gitlab/database/partitioning/partition_manager.rb'
+ - 'lib/gitlab/database/partitioning/partition_creator.rb'
+ - 'lib/gitlab/database/partitioning/replace_table.rb'
+ - 'lib/gitlab/database/partitioning/time_partition.rb'
+ - 'lib/gitlab/database/postgres_hll/batch_distinct_counter.rb'
+ - 'lib/gitlab/database/postgresql_adapter/dump_schema_versions_mixin.rb'
+ - 'lib/gitlab/database/postgresql_database_tasks/load_schema_versions_mixin.rb'
+ - 'lib/gitlab/database.rb'
+ - 'lib/gitlab/database/reindexing/concurrent_reindex.rb'
+ - 'lib/gitlab/database/reindexing/reindex_concurrently.rb'
+ - 'lib/gitlab/database/schema_cache_with_renamed_table.rb'
+ - 'lib/gitlab/database/schema_migrations/context.rb'
+ - 'lib/gitlab/database/schema_version_files.rb'
+ - 'lib/gitlab/database/similarity_score.rb'
+ - 'lib/gitlab/database/unidirectional_copy_trigger.rb'
+ - 'lib/gitlab/database/with_lock_retries.rb'
+ - 'lib/gitlab/gitlab_import/importer.rb'
+ - 'lib/gitlab/health_checks/db_check.rb'
+ - 'lib/gitlab/import_export/base/relation_factory.rb'
+ - 'lib/gitlab/import_export/relation_tree_restorer.rb'
+ - 'lib/gitlab/legacy_github_import/importer.rb'
+ - 'lib/gitlab/metrics/samplers/database_sampler.rb'
+ - 'lib/gitlab/optimistic_locking.rb'
+ - 'lib/gitlab/otp_key_rotator.rb'
+ - 'lib/gitlab/profiler.rb'
+ - 'lib/gitlab/seeder.rb'
+ - 'lib/gitlab/sherlock/query.rb'
+ - 'lib/gitlab/sql/glob.rb'
+ - 'lib/gitlab/sql/set_operator.rb'
+ - 'lib/system_check/orphans/repository_check.rb'
+ - 'spec/db/schema_spec.rb'
+ - 'spec/features/admin/dashboard_spec.rb'
+ - 'spec/initializers/database_config_spec.rb'
+ - 'spec/initializers/lograge_spec.rb'
+ - 'spec/lib/backup/manager_spec.rb'
+ - 'spec/lib/gitlab/current_settings_spec.rb'
+ - 'spec/lib/gitlab/database_spec.rb'
+ - 'spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb'
+ - 'spec/lib/gitlab/import_export/project/tree_saver_spec.rb'
+ - 'spec/lib/gitlab/metrics/subscribers/active_record_spec.rb'
+ - 'spec/lib/gitlab/pagination/keyset/order_spec.rb'
+ - 'spec/lib/gitlab/profiler_spec.rb'
+ - 'spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb'
+ - 'spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb'
+ - 'spec/lib/gitlab/sql/cte_spec.rb'
+ - 'spec/lib/gitlab/sql/glob_spec.rb'
+ - 'spec/lib/gitlab/sql/recursive_cte_spec.rb'
+ - 'spec/lib/gitlab/usage_data_metrics_spec.rb'
+ - 'spec/lib/gitlab/usage_data_queries_spec.rb'
+ - 'spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/constraints_spec.rb'
+ - 'spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins_spec.rb'
+ - 'spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb'
+ - 'spec/lib/gitlab/utils/usage_data_spec.rb'
+ - 'spec/models/application_setting_spec.rb'
+ - 'spec/models/concerns/case_sensitivity_spec.rb'
+ - 'spec/models/concerns/sortable_spec.rb'
+ - 'spec/models/concerns/where_composite_spec.rb'
+ - 'spec/models/experiment_spec.rb'
+ - 'spec/models/internal_id_spec.rb'
+ - 'spec/models/project_feature_usage_spec.rb'
+ - 'spec/models/users_statistics_spec.rb'
+ - 'spec/requests/api/statistics_spec.rb'
+ - 'spec/services/users/activity_service_spec.rb'
+ - 'spec/support/caching.rb'
+ - 'spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb'
+ - 'spec/support/helpers/database_connection_helpers.rb'
+ - 'spec/support/helpers/database/database_helpers.rb'
+ - 'spec/support/helpers/database/table_schema_helpers.rb'
+ - 'spec/support/helpers/migrations_helpers.rb'
+ - 'spec/support/helpers/query_recorder.rb'
+ - 'spec/support/helpers/usage_data_helpers.rb'
+ - 'spec/tasks/gitlab/backup_rake_spec.rb'
+ - 'spec/tasks/gitlab/db_rake_spec.rb'
+ - 'spec/workers/analytics/usage_trends/counter_job_worker_spec.rb'
+ - 'spec/workers/users/create_statistics_worker_spec.rb'
diff --git a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
index d58033b36c7..a490111e13b 100644
--- a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
+++ b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
@@ -62,6 +62,7 @@ export default {
projects: [],
selectedProjects: this.defaultProjects || [],
searchTerm: '',
+ isDirty: false,
};
},
computed: {
@@ -124,6 +125,24 @@ export default {
this.setSelectedProjects(project, !isSelected);
this.$emit('selected', this.selectedProjects);
},
+ onMultiSelectClick({ project, isSelected }) {
+ this.setSelectedProjects(project, !isSelected);
+ this.isDirty = true;
+ },
+ onSelected(ev) {
+ if (this.multiSelect) {
+ this.onMultiSelectClick(ev);
+ } else {
+ this.onClick(ev);
+ }
+ },
+ onHide() {
+ if (this.multiSelect && this.isDirty) {
+ this.$emit('selected', this.selectedProjects);
+ }
+ this.searchTerm = '';
+ this.isDirty = false;
+ },
fetchData() {
this.loading = true;
@@ -158,12 +177,12 @@ export default {
},
};
</script>
-
<template>
<gl-dropdown
ref="projectsDropdown"
class="dropdown dropdown-projects"
toggle-class="gl-shadow-none"
+ @hide="onHide"
>
<template #button-content>
<div class="gl-display-flex gl-flex-grow-1">
@@ -181,15 +200,18 @@ export default {
</div>
<gl-icon class="gl-ml-2" name="chevron-down" />
</template>
- <gl-dropdown-section-header>{{ __('Projects') }}</gl-dropdown-section-header>
- <gl-search-box-by-type v-model.trim="searchTerm" />
-
+ <template #header>
+ <gl-dropdown-section-header>{{ __('Projects') }}</gl-dropdown-section-header>
+ <gl-search-box-by-type v-model.trim="searchTerm" />
+ </template>
<gl-dropdown-item
v-for="project in availableProjects"
:key="project.id"
:is-check-item="true"
:is-checked="isProjectSelected(project.id)"
- @click.prevent="onClick({ project, isSelected: isProjectSelected(project.id) })"
+ @click.native.capture.stop="
+ onSelected({ project, isSelected: isProjectSelected(project.id) })
+ "
>
<div class="gl-display-flex">
<gl-avatar
@@ -203,7 +225,9 @@ export default {
/>
<div>
<div data-testid="project-name">{{ project.name }}</div>
- <div class="gl-text-gray-500" data-testid="project-full-path">{{ project.fullPath }}</div>
+ <div class="gl-text-gray-500" data-testid="project-full-path">
+ {{ project.fullPath }}
+ </div>
</div>
</div>
</gl-dropdown-item>
diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js
index 6cd0a62657e..a32a100fa11 100644
--- a/app/assets/javascripts/boards/stores/mutations.js
+++ b/app/assets/javascripts/boards/stores/mutations.js
@@ -35,13 +35,23 @@ export const addItemToList = ({ state, listId, itemId, moveBeforeId, moveAfterId
export default {
[mutationTypes.SET_INITIAL_BOARD_DATA](state, data) {
- const { boardType, disabled, boardId, fullBoardId, fullPath, boardConfig, issuableType } = data;
+ const {
+ allowSubEpics,
+ boardConfig,
+ boardId,
+ boardType,
+ disabled,
+ fullBoardId,
+ fullPath,
+ issuableType,
+ } = data;
+ state.allowSubEpics = allowSubEpics;
+ state.boardConfig = boardConfig;
state.boardId = boardId;
- state.fullBoardId = fullBoardId;
- state.fullPath = fullPath;
state.boardType = boardType;
state.disabled = disabled;
- state.boardConfig = boardConfig;
+ state.fullBoardId = fullBoardId;
+ state.fullPath = fullPath;
state.issuableType = issuableType;
},
diff --git a/app/assets/javascripts/branches/components/delete_branch_button.vue b/app/assets/javascripts/branches/components/delete_branch_button.vue
index 5a5f49e25e7..6a6d4d48c52 100644
--- a/app/assets/javascripts/branches/components/delete_branch_button.vue
+++ b/app/assets/javascripts/branches/components/delete_branch_button.vue
@@ -47,12 +47,6 @@ export default {
},
},
computed: {
- variant() {
- if (this.disabled) {
- return 'default';
- }
- return 'danger';
- },
title() {
if (this.isProtectedBranch && this.disabled) {
return s__('Branches|Only a project maintainer or owner can delete a protected branch');
@@ -83,7 +77,7 @@ export default {
class="js-delete-branch-button"
data-qa-selector="delete_branch_button"
:disabled="disabled"
- :variant="variant"
+ variant="default"
:title="title"
:aria-label="title"
@click="openModal"
diff --git a/app/assets/javascripts/content_editor/components/toolbar_image_button.vue b/app/assets/javascripts/content_editor/components/toolbar_image_button.vue
new file mode 100644
index 00000000000..ebeee16dbec
--- /dev/null
+++ b/app/assets/javascripts/content_editor/components/toolbar_image_button.vue
@@ -0,0 +1,110 @@
+<script>
+import {
+ GlDropdown,
+ GlDropdownForm,
+ GlButton,
+ GlFormInputGroup,
+ GlDropdownDivider,
+ GlDropdownItem,
+ GlTooltipDirective as GlTooltip,
+} from '@gitlab/ui';
+import { Editor as TiptapEditor } from '@tiptap/vue-2';
+import { acceptedMimes } from '../extensions/image';
+import { getImageAlt } from '../services/utils';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownForm,
+ GlFormInputGroup,
+ GlDropdownDivider,
+ GlDropdownItem,
+ GlButton,
+ },
+ directives: {
+ GlTooltip,
+ },
+ props: {
+ tiptapEditor: {
+ type: TiptapEditor,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ imgSrc: '',
+ };
+ },
+ methods: {
+ resetFields() {
+ this.imgSrc = '';
+ this.$refs.fileSelector.value = '';
+ },
+ insertImage() {
+ this.tiptapEditor
+ .chain()
+ .focus()
+ .setImage({
+ src: this.imgSrc,
+ canonicalSrc: this.imgSrc,
+ alt: getImageAlt(this.imgSrc),
+ })
+ .run();
+
+ this.resetFields();
+ this.emitExecute();
+ },
+ emitExecute(source = 'url') {
+ this.$emit('execute', { contentType: 'image', value: source });
+ },
+ openFileUpload() {
+ this.$refs.fileSelector.click();
+ },
+ onFileSelect(e) {
+ this.tiptapEditor
+ .chain()
+ .focus()
+ .uploadImage({
+ file: e.target.files[0],
+ })
+ .run();
+
+ this.resetFields();
+ this.emitExecute('upload');
+ },
+ },
+ acceptedMimes,
+};
+</script>
+<template>
+ <gl-dropdown
+ v-gl-tooltip
+ :aria-label="__('Insert image')"
+ :title="__('Insert image')"
+ size="small"
+ category="tertiary"
+ icon="media"
+ @hidden="resetFields()"
+ >
+ <gl-dropdown-form class="gl-px-3!">
+ <gl-form-input-group v-model="imgSrc" :placeholder="__('Image URL')">
+ <template #append>
+ <gl-button variant="confirm" @click="insertImage">{{ __('Insert') }}</gl-button>
+ </template>
+ </gl-form-input-group>
+ </gl-dropdown-form>
+ <gl-dropdown-divider />
+ <gl-dropdown-item @click="openFileUpload">
+ {{ __('Upload image') }}
+ </gl-dropdown-item>
+
+ <input
+ ref="fileSelector"
+ type="file"
+ name="content_editor_image"
+ :accept="$options.acceptedMimes"
+ class="gl-display-none"
+ @change="onFileSelect"
+ />
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/content_editor/components/toolbar_link_button.vue b/app/assets/javascripts/content_editor/components/toolbar_link_button.vue
index c0ced2d2228..8f57959a73f 100644
--- a/app/assets/javascripts/content_editor/components/toolbar_link_button.vue
+++ b/app/assets/javascripts/content_editor/components/toolbar_link_button.vue
@@ -43,7 +43,7 @@ export default {
},
mounted() {
this.tiptapEditor.on('selectionUpdate', ({ editor }) => {
- const { 'data-canonical-src': canonicalSrc, href } = editor.getAttributes(linkContentType);
+ const { canonicalSrc, href } = editor.getAttributes(linkContentType);
this.linkHref = canonicalSrc || href;
});
@@ -56,7 +56,7 @@ export default {
.unsetLink()
.setLink({
href: this.linkHref,
- 'data-canonical-src': this.linkHref,
+ canonicalSrc: this.linkHref,
})
.run();
diff --git a/app/assets/javascripts/content_editor/components/top_toolbar.vue b/app/assets/javascripts/content_editor/components/top_toolbar.vue
index e1bc26d50fc..fafc7a660e7 100644
--- a/app/assets/javascripts/content_editor/components/top_toolbar.vue
+++ b/app/assets/javascripts/content_editor/components/top_toolbar.vue
@@ -4,6 +4,7 @@ import { CONTENT_EDITOR_TRACKING_LABEL, TOOLBAR_CONTROL_TRACKING_ACTION } from '
import { ContentEditor } from '../services/content_editor';
import Divider from './divider.vue';
import ToolbarButton from './toolbar_button.vue';
+import ToolbarImageButton from './toolbar_image_button.vue';
import ToolbarLinkButton from './toolbar_link_button.vue';
import ToolbarTableButton from './toolbar_table_button.vue';
import ToolbarTextStyleDropdown from './toolbar_text_style_dropdown.vue';
@@ -18,6 +19,7 @@ export default {
ToolbarTextStyleDropdown,
ToolbarLinkButton,
ToolbarTableButton,
+ ToolbarImageButton,
Divider,
},
mixins: [trackingMixin],
@@ -89,6 +91,12 @@ export default {
@execute="trackToolbarControlExecution"
/>
<divider />
+ <toolbar-image-button
+ ref="imageButton"
+ data-testid="image"
+ :tiptap-editor="contentEditor.tiptapEditor"
+ @execute="trackToolbarControlExecution"
+ />
<toolbar-button
data-testid="blockquote"
content-type="blockquote"
@@ -140,3 +148,8 @@ export default {
/>
</div>
</template>
+<style>
+.gl-spinner-container {
+ text-align: left;
+}
+</style>
diff --git a/app/assets/javascripts/content_editor/extensions/link.js b/app/assets/javascripts/content_editor/extensions/link.js
index 89076e0210c..12019ab4636 100644
--- a/app/assets/javascripts/content_editor/extensions/link.js
+++ b/app/assets/javascripts/content_editor/extensions/link.js
@@ -38,11 +38,11 @@ export const tiptapExtension = Link.extend({
};
},
},
- 'data-canonical-src': {
+ canonicalSrc: {
default: null,
parseHTML: (element) => {
return {
- href: element.dataset.canonicalSrc,
+ canonicalSrc: element.dataset.canonicalSrc,
};
},
},
@@ -57,7 +57,7 @@ export const serializer = {
return '[';
},
close(state, mark) {
- const href = mark.attrs['data-canonical-src'] || mark.attrs.href;
+ const href = mark.attrs.canonicalSrc || mark.attrs.href;
return `](${state.esc(href)}${mark.attrs.title ? ` ${state.quote(mark.attrs.title)}` : ''})`;
},
};
diff --git a/app/assets/javascripts/import_entities/components/group_dropdown.vue b/app/assets/javascripts/import_entities/components/group_dropdown.vue
new file mode 100644
index 00000000000..44d6d17232f
--- /dev/null
+++ b/app/assets/javascripts/import_entities/components/group_dropdown.vue
@@ -0,0 +1,40 @@
+<script>
+import { GlDropdown, GlSearchBoxByType } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlDropdown,
+ GlSearchBoxByType,
+ },
+ inheritAttrs: false,
+ props: {
+ namespaces: {
+ type: Array,
+ required: true,
+ },
+ },
+ data() {
+ return { searchTerm: '' };
+ },
+ computed: {
+ filteredNamespaces() {
+ return this.namespaces.filter((ns) =>
+ ns.toLowerCase().includes(this.searchTerm.toLowerCase()),
+ );
+ },
+ },
+};
+</script>
+<template>
+ <gl-dropdown
+ toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!"
+ class="import-entities-namespace-dropdown gl-h-7 gl-flex-fill-1"
+ data-qa-selector="target_namespace_selector_dropdown"
+ v-bind="$attrs"
+ >
+ <template #header>
+ <gl-search-box-by-type v-model.trim="searchTerm" />
+ </template>
+ <slot :namespaces="filteredNamespaces"></slot>
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue b/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue
index 63c18f4d78e..1c3ede769e0 100644
--- a/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue
+++ b/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue
@@ -1,7 +1,6 @@
<script>
import {
GlButton,
- GlDropdown,
GlDropdownDivider,
GlDropdownItem,
GlDropdownSectionHeader,
@@ -11,6 +10,7 @@ import {
} from '@gitlab/ui';
import { joinPaths } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
+import ImportGroupDropdown from '../../components/group_dropdown.vue';
import ImportStatus from '../../components/import_status.vue';
import { STATUSES } from '../../constants';
import addValidationErrorMutation from '../graphql/mutations/add_validation_error.mutation.graphql';
@@ -22,8 +22,8 @@ const DEBOUNCE_INTERVAL = 300;
export default {
components: {
ImportStatus,
+ ImportGroupDropdown,
GlButton,
- GlDropdown,
GlDropdownDivider,
GlDropdownItem,
GlDropdownSectionHeader,
@@ -83,6 +83,10 @@ export default {
},
computed: {
+ availableNamespaceNames() {
+ return this.availableNamespaces.map((ns) => ns.full_path);
+ },
+
importTarget() {
return this.group.import_target;
},
@@ -153,9 +157,11 @@ export default {
disabled: isAlreadyImported,
}"
>
- <gl-dropdown
+ <import-group-dropdown
+ #default="{ namespaces }"
:text="importTarget.target_namespace"
:disabled="isAlreadyImported"
+ :namespaces="availableNamespaceNames"
toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!"
class="import-entities-namespace-dropdown gl-h-7 gl-flex-grow-1"
data-qa-selector="target_namespace_selector_dropdown"
@@ -163,22 +169,22 @@ export default {
<gl-dropdown-item @click="$emit('update-target-namespace', '')">{{
s__('BulkImport|No parent')
}}</gl-dropdown-item>
- <template v-if="availableNamespaces.length">
+ <template v-if="namespaces.length">
<gl-dropdown-divider />
<gl-dropdown-section-header>
{{ s__('BulkImport|Existing groups') }}
</gl-dropdown-section-header>
<gl-dropdown-item
- v-for="ns in availableNamespaces"
- :key="ns.full_path"
+ v-for="ns in namespaces"
+ :key="ns"
data-qa-selector="target_group_dropdown_item"
- :data-qa-group-name="ns.full_path"
- @click="$emit('update-target-namespace', ns.full_path)"
+ :data-qa-group-name="ns"
+ @click="$emit('update-target-namespace', ns)"
>
- {{ ns.full_path }}
+ {{ ns }}
</gl-dropdown-item>
</template>
- </gl-dropdown>
+ </import-group-dropdown>
<div
class="import-entities-target-select-separator gl-h-7 gl-px-3 gl-display-flex gl-align-items-center gl-border-solid gl-border-0 gl-border-t-1 gl-border-b-1"
>
diff --git a/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue b/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue
index be09052fb7e..14d08caef34 100644
--- a/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue
+++ b/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue
@@ -47,18 +47,7 @@ export default {
},
availableNamespaces() {
- const serializedNamespaces = this.namespaces.map(({ fullPath }) => ({
- id: fullPath,
- text: fullPath,
- }));
-
- return [
- { text: __('Groups'), children: serializedNamespaces },
- {
- text: __('Users'),
- children: [{ id: this.defaultTargetNamespace, text: this.defaultTargetNamespace }],
- },
- ];
+ return this.namespaces.map(({ fullPath }) => fullPath);
},
importAllButtonText() {
@@ -179,6 +168,7 @@ export default {
:key="repo.importSource.providerLink"
:repo="repo"
:available-namespaces="availableNamespaces"
+ :user-namespace="defaultTargetNamespace"
/>
</template>
</tbody>
diff --git a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue
index a803afeb901..e2fd608d9db 100644
--- a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue
+++ b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue
@@ -1,8 +1,17 @@
<script>
-import { GlIcon, GlBadge, GlFormInput, GlButton, GlLink } from '@gitlab/ui';
+import {
+ GlIcon,
+ GlBadge,
+ GlFormInput,
+ GlButton,
+ GlLink,
+ GlDropdownItem,
+ GlDropdownDivider,
+ GlDropdownSectionHeader,
+} from '@gitlab/ui';
import { mapState, mapGetters, mapActions } from 'vuex';
import { __ } from '~/locale';
-import Select2Select from '~/vue_shared/components/select2_select.vue';
+import ImportGroupDropdown from '../../components/group_dropdown.vue';
import ImportStatus from '../../components/import_status.vue';
import { STATUSES } from '../../constants';
import { isProjectImportable, isIncompatible, getImportStatus } from '../utils';
@@ -10,10 +19,13 @@ import { isProjectImportable, isIncompatible, getImportStatus } from '../utils';
export default {
name: 'ProviderRepoTableRow',
components: {
- Select2Select,
+ ImportGroupDropdown,
ImportStatus,
GlFormInput,
GlButton,
+ GlDropdownItem,
+ GlDropdownDivider,
+ GlDropdownSectionHeader,
GlIcon,
GlBadge,
GlLink,
@@ -23,6 +35,10 @@ export default {
type: Object,
required: true,
},
+ userNamespace: {
+ type: String,
+ required: true,
+ },
availableNamespaces: {
type: Array,
required: true,
@@ -61,22 +77,6 @@ export default {
return this.ciCdOnly ? __('Connect') : __('Import');
},
- select2Options() {
- return {
- data: this.availableNamespaces,
- containerCssClass: 'import-namespace-select qa-project-namespace-select gl-w-auto',
- };
- },
-
- targetNamespaceSelect: {
- get() {
- return this.importTarget.targetNamespace;
- },
- set(value) {
- this.updateImportTarget({ targetNamespace: value });
- },
- },
-
newNameInput: {
get() {
return this.importTarget.newName;
@@ -118,7 +118,29 @@ export default {
<template v-if="repo.importSource.target">{{ repo.importSource.target }}</template>
<template v-else-if="isImportNotStarted">
<div class="import-entities-target-select gl-display-flex gl-align-items-stretch gl-w-full">
- <select2-select v-model="targetNamespaceSelect" :options="select2Options" />
+ <import-group-dropdown
+ #default="{ namespaces }"
+ :text="importTarget.targetNamespace"
+ :namespaces="availableNamespaces"
+ >
+ <template v-if="namespaces.length">
+ <gl-dropdown-section-header>{{ __('Groups') }}</gl-dropdown-section-header>
+ <gl-dropdown-item
+ v-for="ns in namespaces"
+ :key="ns"
+ data-qa-selector="target_group_dropdown_item"
+ :data-qa-group-name="ns"
+ @click="updateImportTarget({ targetNamespace: ns })"
+ >
+ {{ ns }}
+ </gl-dropdown-item>
+ <gl-dropdown-divider />
+ </template>
+ <gl-dropdown-section-header>{{ __('Users') }}</gl-dropdown-section-header>
+ <gl-dropdown-item @click="updateImportTarget({ targetNamespace: ns })">{{
+ userNamespace
+ }}</gl-dropdown-item>
+ </import-group-dropdown>
<div
class="import-entities-target-select-separator gl-px-3 gl-display-flex gl-align-items-center gl-border-solid gl-border-0 gl-border-t-1 gl-border-b-1"
>
diff --git a/app/assets/javascripts/import_entities/import_projects/index.js b/app/assets/javascripts/import_entities/import_projects/index.js
index 6b7fe23ed60..110cc77b20d 100644
--- a/app/assets/javascripts/import_entities/import_projects/index.js
+++ b/app/assets/javascripts/import_entities/import_projects/index.js
@@ -38,7 +38,7 @@ export function initStoreFromElement(element) {
export function initPropsFromElement(element) {
return {
- providerTitle: element.dataset.providerTitle,
+ providerTitle: element.dataset.provider,
filterable: parseBoolean(element.dataset.filterable),
paginatable: parseBoolean(element.dataset.paginatable),
};
diff --git a/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue b/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue
index 853e839a7ab..73c91839f04 100644
--- a/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue
+++ b/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue
@@ -1,18 +1,27 @@
<script>
-import { GlIcon } from '@gitlab/ui';
+import { GlAlert, GlLink, GlSprintf, GlIcon } from '@gitlab/ui';
import { uniqueId } from 'lodash';
+import { helpPagePath } from '~/helpers/help_page_helper';
import { s__ } from '~/locale';
import SourceEditor from '~/vue_shared/components/source_editor.vue';
+import getCurrentBranch from '../../graphql/queries/client/current_branch.graphql';
export default {
i18n: {
viewOnlyMessage: s__('Pipelines|Merged YAML is view only'),
+ unavailableDefaultTitle: s__('Pipelines|Merged YAML unavailable'),
+ unavailableDefaultText: s__(
+ 'Pipelines|The merged YAML view is only available for the default branch. %{linkStart}Learn more.%{linkEnd}',
+ ),
},
components: {
SourceEditor,
+ GlAlert,
GlIcon,
+ GlLink,
+ GlSprintf,
},
- inject: ['ciConfigPath'],
+ inject: ['ciConfigPath', 'defaultBranch'],
props: {
ciConfigData: {
type: Object,
@@ -24,6 +33,15 @@ export default {
failureType: null,
};
},
+ // This is not the best practice, don't copy me (@samdbeckham)
+ // This is a temporary workaround to unblock a release.
+ // See this comment for more information on this approach
+ // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65972#note_626095648
+ apollo: {
+ currentBranch: {
+ query: getCurrentBranch,
+ },
+ },
computed: {
fileGlobalId() {
return `${this.ciConfigPath}-${uniqueId()}`;
@@ -31,24 +49,44 @@ export default {
mergedYaml() {
return this.ciConfigData.mergedYaml;
},
+ isOnDefaultBranch() {
+ return this.currentBranch === this.defaultBranch;
+ },
+ expandedConfigHelpPath() {
+ return helpPagePath('ci/pipeline_editor/index', { anchor: 'view-expanded-configuration' });
+ },
},
};
</script>
<template>
<div>
- <div class="gl-display-flex gl-align-items-center">
- <gl-icon :size="16" name="lock" class="gl-text-gray-500 gl-mr-3" />
- {{ $options.i18n.viewOnlyMessage }}
- </div>
- <div class="gl-mt-3 gl-border-solid gl-border-gray-100 gl-border-1">
- <source-editor
- ref="editor"
- :value="mergedYaml"
- :file-name="ciConfigPath"
- :file-global-id="fileGlobalId"
- :editor-options="{ readOnly: true }"
- v-on="$listeners"
- />
+ <div v-if="isOnDefaultBranch">
+ <div class="gl-display-flex gl-align-items-center">
+ <gl-icon :size="16" name="lock" class="gl-text-gray-500 gl-mr-3" />
+ {{ $options.i18n.viewOnlyMessage }}
+ </div>
+ <div class="gl-mt-3 gl-border-solid gl-border-gray-100 gl-border-1">
+ <source-editor
+ ref="editor"
+ :value="mergedYaml"
+ :file-name="ciConfigPath"
+ :file-global-id="fileGlobalId"
+ :editor-options="{ readOnly: true }"
+ v-on="$listeners"
+ />
+ </div>
</div>
+ <gl-alert
+ v-else
+ variant="info"
+ :dismissible="false"
+ :title="$options.i18n.unavailableDefaultTitle"
+ >
+ <gl-sprintf :message="$options.i18n.unavailableDefaultText">
+ <template #link="{ content }">
+ <gl-link :href="expandedConfigHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </gl-alert>
</div>
</template>
diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue
index cf1cff9023e..c861fb8dd06 100644
--- a/app/assets/javascripts/repository/components/tree_content.vue
+++ b/app/assets/javascripts/repository/components/tree_content.vue
@@ -1,6 +1,7 @@
<script>
import filesQuery from 'shared_queries/repository/files.query.graphql';
import createFlash from '~/flash';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { __ } from '../../locale';
import { TREE_PAGE_SIZE, TREE_INITIAL_FETCH_COUNT, TREE_PAGE_LIMIT } from '../constants';
import getRefMixin from '../mixins/get_ref';
@@ -14,7 +15,7 @@ export default {
FileTable,
FilePreview,
},
- mixins: [getRefMixin],
+ mixins: [getRefMixin, glFeatureFlagMixin()],
apollo: {
projectPath: {
query: projectPathQuery,
@@ -52,7 +53,9 @@ export default {
pageSize() {
// we want to exponentially increase the page size to reduce the load on the frontend
const exponentialSize = (TREE_PAGE_SIZE / TREE_INITIAL_FETCH_COUNT) * (this.fetchCounter + 1);
- return exponentialSize < TREE_PAGE_SIZE ? exponentialSize : TREE_PAGE_SIZE;
+ return exponentialSize < TREE_PAGE_SIZE && this.glFeatures.increasePageSizeExponentially
+ ? exponentialSize
+ : TREE_PAGE_SIZE;
},
totalEntries() {
return Object.values(this.entries).flat().length;
diff --git a/app/assets/javascripts/vue_shared/components/select2_select.vue b/app/assets/javascripts/vue_shared/components/select2_select.vue
deleted file mode 100644
index bb1a8fae7b0..00000000000
--- a/app/assets/javascripts/vue_shared/components/select2_select.vue
+++ /dev/null
@@ -1,48 +0,0 @@
-<script>
-import $ from 'jquery';
-import 'select2';
-import { loadCSSFile } from '~/lib/utils/css_utils';
-
-export default {
- // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
- // eslint-disable-next-line @gitlab/require-i18n-strings
- name: 'Select2Select',
- props: {
- options: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- value: {
- type: String,
- required: false,
- default: '',
- },
- },
-
- watch: {
- value() {
- $(this.$refs.dropdownInput).val(this.value).trigger('change');
- },
- },
-
- mounted() {
- loadCSSFile(gon.select2_css_path)
- .then(() => {
- $(this.$refs.dropdownInput)
- .val(this.value)
- .select2(this.options)
- .on('change', (event) => this.$emit('input', event.target.value));
- })
- .catch(() => {});
- },
-
- beforeDestroy() {
- $(this.$refs.dropdownInput).select2('destroy');
- },
-};
-</script>
-
-<template>
- <input ref="dropdownInput" type="hidden" />
-</template>
diff --git a/app/assets/stylesheets/snippets.scss b/app/assets/stylesheets/snippets.scss
index ad040f65f3c..d38c1818f53 100644
--- a/app/assets/stylesheets/snippets.scss
+++ b/app/assets/stylesheets/snippets.scss
@@ -54,6 +54,8 @@
white-space: pre;
word-wrap: normal;
border-left: $border-style;
+ text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%; /* stylelint-disable-line property-no-vendor-prefix */
}
code {
@@ -65,7 +67,7 @@
}
.line-numbers {
- padding: 10px;
+ padding: 10px 10px 10px 0;
text-align: right;
float: left;
@@ -86,18 +88,24 @@
}
}
+ .file-actions {
+ flex-shrink: 0;
+ }
+
.file-title-flex-parent {
display: flex;
- align-items: center;
+ align-items: flex-start;
justify-content: space-between;
background-color: $gray-light;
border: $border-style;
border-bottom: 0;
- padding: $gl-padding-top $gl-padding;
+ padding: $gl-padding;
margin: 0;
border-radius: $border-radius-default $border-radius-default 0 0;
.file-header-content {
+ max-width: 75%;
+
.file-title-name {
font-weight: $gl-font-weight-bold;
}
@@ -105,6 +113,7 @@
.gitlab-embedded-snippets-title {
text-decoration: none;
color: $gl-text-color;
+ word-break: break-word;
&:hover {
text-decoration: underline;
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 48d943587d4..0dbf7d40f87 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -37,6 +37,7 @@ class ProjectsController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:refactor_blob_viewer, @project, default_enabled: :yaml)
+ push_frontend_feature_flag(:increase_page_size_exponentially, @project, default_enabled: :yaml)
end
layout :determine_layout
diff --git a/app/models/integrations/datadog.rb b/app/models/integrations/datadog.rb
index 211c80f8e33..d7d472f1d89 100644
--- a/app/models/integrations/datadog.rb
+++ b/app/models/integrations/datadog.rb
@@ -2,10 +2,10 @@
module Integrations
class Datadog < Integration
- DEFAULT_SITE = 'datadoghq.com'
- URL_TEMPLATE = 'https://webhooks-http-intake.logs.%{datadog_site}/v1/input/'
- URL_TEMPLATE_API_KEYS = 'https://app.%{datadog_site}/account/settings#api'
- URL_API_KEYS_DOCS = "https://docs.#{DEFAULT_SITE}/account_management/api-app-keys/"
+ DEFAULT_DOMAIN = 'datadoghq.com'
+ URL_TEMPLATE = 'https://webhooks-http-intake.logs.%{datadog_domain}/api/v2/webhook'
+ URL_TEMPLATE_API_KEYS = 'https://app.%{datadog_domain}/account/settings#api'
+ URL_API_KEYS_DOCS = "https://docs.#{DEFAULT_DOMAIN}/account_management/api-app-keys/"
SUPPORTED_EVENTS = %w[
pipeline job
@@ -26,7 +26,7 @@ module Integrations
def initialize_properties
super
- self.datadog_site ||= DEFAULT_SITE
+ self.datadog_site ||= DEFAULT_DOMAIN
end
def self.supported_events
@@ -62,7 +62,7 @@ module Integrations
{
type: 'text',
name: 'datadog_site',
- placeholder: DEFAULT_SITE,
+ placeholder: DEFAULT_DOMAIN,
help: 'Choose the Datadog site to send data to. Set to "datadoghq.eu" to send data to the EU site',
required: false
},
@@ -105,18 +105,21 @@ module Integrations
end
def hook_url
- url = api_url.presence || sprintf(URL_TEMPLATE, datadog_site: datadog_site)
+ url = api_url.presence || sprintf(URL_TEMPLATE, datadog_domain: datadog_domain)
url = URI.parse(url)
- url.path = File.join(url.path || '/', api_key)
- query = { service: datadog_service.presence, env: datadog_env.presence }.compact
- url.query = query.to_query unless query.empty?
+ query = {
+ "dd-api-key" => api_key,
+ service: datadog_service.presence,
+ env: datadog_env.presence
+ }.compact
+ url.query = query.to_query
url.to_s
end
def api_keys_url
return URL_API_KEYS_DOCS unless datadog_site.presence
- sprintf(URL_TEMPLATE_API_KEYS, datadog_site: datadog_site)
+ sprintf(URL_TEMPLATE_API_KEYS, datadog_domain: datadog_domain)
end
def execute(data)
@@ -137,5 +140,14 @@ module Integrations
{ success: true, result: result[:message] }
end
+
+ private
+
+ def datadog_domain
+ # Transparently ignore "app" prefix from datadog_site as the official docs table in
+ # https://docs.datadoghq.com/getting_started/site/ is confusing for internal URLs.
+ # US3 needs to keep a prefix but other datacenters cannot have the listed "app" prefix
+ datadog_site.delete_prefix("app.")
+ end
end
end
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index ec50312c6d4..dc046e1d164 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -209,7 +209,7 @@ module Ci
# We need to use the presenter here because Gitaly calls in the presenter
# may fail, and we need to ensure the response has been generated.
presented_build = ::Ci::BuildRunnerPresenter.new(build) # rubocop:disable CodeReuse/Presenter
- build_json = ::API::Entities::JobRequest::Response.new(presented_build).to_json
+ build_json = ::API::Entities::Ci::JobRequest::Response.new(presented_build).to_json
Result.new(build, build_json, true)
end
diff --git a/app/services/service_ping/submit_service.rb b/app/services/service_ping/submit_service.rb
index 06e4fcbaf32..5c03aa46e18 100644
--- a/app/services/service_ping/submit_service.rb
+++ b/app/services/service_ping/submit_service.rb
@@ -20,34 +20,50 @@ module ServicePing
def execute
return unless ServicePing::PermitDataCategoriesService.new.product_intelligence_enabled?
- usage_data = Gitlab::UsageData.data(force_refresh: true)
+ begin
+ usage_data = BuildPayloadService.new.execute
+ raw_usage_data, response = submit_usage_data_payload(usage_data)
+ rescue StandardError
+ return unless Gitlab::CurrentSettings.usage_ping_enabled?
+
+ usage_data = Gitlab::UsageData.data(force_refresh: true)
+ raw_usage_data, response = submit_usage_data_payload(usage_data)
+ end
- raise SubmissionError, 'Usage data is blank' if usage_data.blank?
+ version_usage_data_id = response.dig('conv_index', 'usage_data_id') || response.dig('dev_ops_score', 'usage_data_id')
- raw_usage_data = save_raw_usage_data(usage_data)
+ unless version_usage_data_id.is_a?(Integer) && version_usage_data_id > 0
+ raise SubmissionError, "Invalid usage_data_id in response: #{version_usage_data_id}"
+ end
+
+ raw_usage_data.update_version_metadata!(usage_data_id: version_usage_data_id)
+
+ store_metrics(response)
+ end
+
+ private
- response = Gitlab::HTTP.post(
+ def submit_payload(usage_data)
+ Gitlab::HTTP.post(
url,
body: usage_data.to_json,
allow_local_requests: true,
headers: { 'Content-type' => 'application/json' }
)
+ end
- raise SubmissionError, "Unsuccessful response code: #{response.code}" unless response.success?
+ def submit_usage_data_payload(usage_data)
+ raise SubmissionError, 'Usage data is blank' if usage_data.blank?
- version_usage_data_id = response.dig('conv_index', 'usage_data_id') || response.dig('dev_ops_score', 'usage_data_id')
+ raw_usage_data = save_raw_usage_data(usage_data)
- unless version_usage_data_id.is_a?(Integer) && version_usage_data_id > 0
- raise SubmissionError, "Invalid usage_data_id in response: #{version_usage_data_id}"
- end
+ response = submit_payload(usage_data)
- raw_usage_data.update_version_metadata!(usage_data_id: version_usage_data_id)
+ raise SubmissionError, "Unsuccessful response code: #{response.code}" unless response.success?
- store_metrics(response)
+ [raw_usage_data, response]
end
- private
-
def save_raw_usage_data(usage_data)
RawUsageData.safe_find_or_create_by(recorded_at: usage_data[:recorded_at]) do |record|
record.payload = usage_data
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index 3942cfa4643..a91c12d01ad 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -74,7 +74,7 @@
= f.text_field :build_timeout_human_readable, class: 'form-control gl-form-input'
%p.form-text.text-muted
= html_escape(_('Jobs fail if they run longer than the timeout time. Input value is in seconds by default. Human readable input is also accepted, for example %{code_open}1 hour%{code_close}.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'timeout'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'set-a-limit-for-how-long-jobs-can-run'), target: '_blank'
- if can?(current_user, :update_max_artifacts_size, @project)
.form-group
@@ -94,7 +94,7 @@
.input-group-text /
%p.form-text.text-muted
= html_escape(_('The regular expression used to find test coverage output in the job log. For example, use %{regex} for Simplecov (Ruby). Leave blank to disable.')) % { regex: '<code>\(\d+.\d+%\)</code>'.html_safe }
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'test-coverage-parsing'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'add-test-coverage-results-to-a-merge-request'), target: '_blank'
= f.submit _('Save changes'), class: "btn gl-button btn-confirm", data: { qa_selector: 'save_general_pipelines_changes_button' }
diff --git a/config/feature_flags/development/escalation_policies_mvc.yml b/config/feature_flags/development/escalation_policies_mvc.yml
deleted file mode 100644
index 21766f9485e..00000000000
--- a/config/feature_flags/development/escalation_policies_mvc.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: escalation_policies_mvc
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60524
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/329347
-milestone: '13.12'
-type: development
-group: group::monitor
-default_enabled: false
diff --git a/config/feature_flags/development/increase_page_size_exponentially.yml b/config/feature_flags/development/increase_page_size_exponentially.yml
new file mode 100644
index 00000000000..88f05d1e850
--- /dev/null
+++ b/config/feature_flags/development/increase_page_size_exponentially.yml
@@ -0,0 +1,8 @@
+---
+name: increase_page_size_exponentially
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66174
+rollout_issue_url:
+milestone: '14.1'
+type: development
+group: group::source code
+default_enabled: false
diff --git a/config/feature_flags/development/multiple_oncall_schedules.yml b/config/feature_flags/development/multiple_oncall_schedules.yml
deleted file mode 100644
index c1640c30e8e..00000000000
--- a/config/feature_flags/development/multiple_oncall_schedules.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: multiple_oncall_schedules
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59829
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/328474
-milestone: '13.11'
-type: development
-group: group::monitor
-default_enabled: false
diff --git a/config/feature_flags/development/pipeline_editor_branch_switcher.yml b/config/feature_flags/development/pipeline_editor_branch_switcher.yml
index 4ba6c8f94fd..49cf2a07a2c 100644
--- a/config/feature_flags/development/pipeline_editor_branch_switcher.yml
+++ b/config/feature_flags/development/pipeline_editor_branch_switcher.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326189
milestone: '13.11'
type: development
group: group::pipeline authoring
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/development/security_configuration_redesign_ee.yml b/config/feature_flags/development/security_configuration_redesign_ee.yml
index a1018ec153d..bf981b65c7f 100644
--- a/config/feature_flags/development/security_configuration_redesign_ee.yml
+++ b/config/feature_flags/development/security_configuration_redesign_ee.yml
@@ -1,7 +1,7 @@
---
name: security_configuration_redesign_ee
-introduced_by_url:
-rollout_issue_url:
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65171
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/336077
milestone: '14.1'
type: development
group: group::analyzer frontend
diff --git a/danger/datateam/Dangerfile b/danger/datateam/Dangerfile
index e054cddce06..263fd4aa8e3 100644
--- a/danger/datateam/Dangerfile
+++ b/danger/datateam/Dangerfile
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-# rubocop:disable Style/SignalException
DATA_WAREHOUSE_LABELS = [
"Data Warehouse::Impact Check",
@@ -15,11 +14,6 @@ Notification to the Data Team about changes to the db/structure.sql file, add la
MSG
db_schema_updated = !git.modified_files.grep(%r{\Adb/structure\.sql}).empty?
-
no_data_warehouse_labels = (gitlab.mr_labels & DATA_WAREHOUSE_LABELS).empty?
-if db_schema_updated && no_data_warehouse_labels
-
- markdown(CHANGED_SCHEMA_MESSAGE)
-
-end
+markdown(CHANGED_SCHEMA_MESSAGE) if db_schema_updated && no_data_warehouse_labels
diff --git a/doc/administration/geo/glossary.md b/doc/administration/geo/glossary.md
index 1ec552326aa..f8769d31ec7 100644
--- a/doc/administration/geo/glossary.md
+++ b/doc/administration/geo/glossary.md
@@ -21,7 +21,7 @@ these definitions yet.
| Term | Definition | Scope | Discouraged synonyms |
|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|-------------------------------------------------|
-| Node | An individual server that runs GitLab either with a specific role or as a whole (e.g. a Rails application node). In a cloud context this can be a specific machine type. | GitLab | instance, server |
+| Node | An individual server that runs GitLab either with a specific role or as a whole (for example a Rails application node). In a cloud context this can be a specific machine type. | GitLab | instance, server |
| Site | One or a collection of nodes running a single GitLab application. A site can be single-node or multi-node. | GitLab | deployment, installation instance |
| Single-node site | A specific configuration of GitLab that uses exactly one node. | GitLab | single-server, single-instance
| Multi-node site | A specific configuration of GitLab that uses more than one node. | GitLab | multi-server, multi-instance, high availability |
@@ -31,7 +31,7 @@ these definitions yet.
| Reference architecture(s) | A [specified configuration of GitLab for a number of users](../reference_architectures/index.md), possibly including multiple nodes and multiple sites. | GitLab | |
| Promoting | Changing the role of a site from secondary to primary. | Geo-specific | |
| Demoting | Changing the role of a site from primary to secondary. | Geo-specific | |
-| Failover | The entire process that shifts users from a primary Site to a secondary site. This includes promoting a secondary, but contains other parts as well e.g. scheduling maintenance. | Geo-specific | |
+| Failover | The entire process that shifts users from a primary Site to a secondary site. This includes promoting a secondary, but contains other parts as well. For example, scheduling maintenance. | Geo-specific | |
## Examples
diff --git a/doc/ci/pipeline_editor/index.md b/doc/ci/pipeline_editor/index.md
index 6e0ba8fa33f..96df93848dc 100644
--- a/doc/ci/pipeline_editor/index.md
+++ b/doc/ci/pipeline_editor/index.md
@@ -15,7 +15,7 @@ the `.gitlab-ci.yml` file in the root of your repository. To access the editor,
From the pipeline editor page you can:
-- Select the branch to work from. [Introduced in GitLab 13.12](https://gitlab.com/gitlab-org/gitlab/-/issues/326189), disabled by default.
+- Select the branch to work from.
- [Validate](#validate-ci-configuration) your configuration syntax while editing the file.
- Do a deeper [lint](#lint-ci-configuration) of your configuration, that verifies it with any configuration
added with the [`include`](../yaml/index.md#include) keyword.
@@ -85,6 +85,9 @@ where:
[extended configuration merged into the job](../yaml/index.md#merge-details).
- YAML anchors are [replaced with the linked configuration](../yaml/index.md#anchors).
+NOTE:
+You can only see the expanded view when editing the [default branch](../../user/project/repository/branches/default.md).
+
## Commit changes to CI configuration
The commit form appears at the bottom of each tab in the editor so you can commit
diff --git a/doc/ci/pipelines/settings.md b/doc/ci/pipelines/settings.md
index 8aab7db04f8..db6fa7f4d23 100644
--- a/doc/ci/pipelines/settings.md
+++ b/doc/ci/pipelines/settings.md
@@ -104,10 +104,10 @@ To customize the path:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings > CI/CD**.
1. Expand **General pipelines**.
-1. In the **CI/CD configuration file** field, enter the file name, and if:
- - The file is not in the root directory, include the path.
- - The file is in a different project, include the group and project name.
- - The file is on an external site, enter the full URL.
+1. In the **CI/CD configuration file** field, enter the filename. If the file:
+ - Is not in the root directory, include the path.
+ - Is in a different project, include the group and project name.
+ - Is on an external site, enter the full URL.
1. Select **Save changes**.
### Custom CI/CD configuration file examples
@@ -122,7 +122,7 @@ If the CI/CD configuration file is on an external site, the URL must end with `.
- `http://example.com/generate/ci/config.yml`
-If the CI/CD configuration file is in a different project in GitLab, the path must be relative
+If the CI/CD configuration file is in a different project, the path must be relative
to the root directory in the other project. Include the group and project name at the end:
- `.gitlab-ci.yml@mygroup/another-project`
@@ -173,31 +173,32 @@ In GitLab 12.0 and later, newly created projects automatically have a default
This value can be overridden by the [`GIT_DEPTH` variable](../large_repositories/index.md#shallow-cloning)
in the `.gitlab-ci.yml` file.
-## Timeout
+## Set a limit for how long jobs can run
-Timeout defines the maximum amount of time in minutes that a job is able run.
-This is configurable under your project's **Settings > CI/CD > General pipelines settings**.
-The default value is 60 minutes. Decrease the time limit if you want to impose
-a hard limit on your jobs' running time or increase it otherwise. In any case,
-if the job surpasses the threshold, it is marked as failed.
+You can define how long a job can run before it times out.
-### Timeout overriding for runners
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > CI/CD**.
+1. Expand **General pipelines**.
+1. In the **Timeout** field, enter the number of minutes, or a human-readable value like `2 hours`.
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17221) in GitLab 10.7.
+Jobs that exceed the timeout are marked as failed.
-Project defined timeout (either specific timeout set by user or the default
-60 minutes timeout) may be [overridden for runners](../runners/configure_runners.md#set-maximum-job-timeout-for-a-runner).
+You can override this value [for individual runners](../runners/configure_runners.md#set-maximum-job-timeout-for-a-runner).
-## Test coverage parsing
+## Add test coverage results to a merge request
-If you use test coverage in your code, GitLab can capture its output in the
-job log using a regular expression.
+If you use test coverage in your code, you can use a regular expression to
+find coverage results in the job log. You can then include these results
+in the merge request in GitLab.
-In your project, go to **Settings > CI/CD** and expand the **General pipelines**
-section. Enter the regular expression in the **Test coverage parsing** field.
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > CI/CD**.
+1. Expand **General pipelines**.
+1. In the **Test coverage parsing** field, enter a regular expression.
+ Leave blank to disable this feature.
-Leave blank if you want to disable it or enter a Ruby regular expression. You
-can use <https://rubular.com> to test your regex. The regex returns the **last**
+You can use <https://rubular.com> to test your regex. The regex returns the **last**
match found in the output.
If the pipeline succeeds, the coverage is shown in the merge request widget and
@@ -208,6 +209,10 @@ averaged.
![Build status coverage](img/pipelines_test_coverage_build.png)
+### Test coverage examples
+
+Use this regex for commonly used test tools.
+
<!-- vale gitlab.Spelling = NO -->
- Simplecov (Ruby). Example: `\(\d+.\d+\%\) covered`.
@@ -221,21 +226,25 @@ averaged.
- `mix test --cover` (Elixir). Example: `\d+.\d+\%\s+\|\s+Total`.
- JaCoCo (Java/Kotlin). Example: `Total.*?([0-9]{1,3})%`.
- `go test -cover` (Go). Example: `coverage: \d+.\d+% of statements`.
-- .Net (OpenCover). Example: `(Visited Points).*\((.*)\)`.
-- .Net (`dotnet test` line coverage). Example: `Total\s*\|\s*(\d+(?:\.\d+)?)`.
+- .NET (OpenCover). Example: `(Visited Points).*\((.*)\)`.
+- .NET (`dotnet test` line coverage). Example: `Total\s*\|\s*(\d+(?:\.\d+)?)`.
<!-- vale gitlab.Spelling = YES -->
-### Code coverage history
+### View code coverage history
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/209121) the ability to download a `.csv` in GitLab 12.10.
> - [Graph introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33743) in GitLab 13.1.
To see the evolution of your project code coverage over time,
-you can view a graph or download a CSV file with this data. From your project:
+you can view a graph or download a CSV file with this data.
-1. Go to **Project Analytics > Repository** to see the historic data for each job listed in the dropdown above the graph.
-1. If you want a CSV file of that data, click **Download raw data (`.csv`)**
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Analytics > Repository**.
+
+The historic data for each job is listed in the dropdown above the graph.
+
+To view a CSV file of the data, select **Download raw data (`.csv`)**.
![Code coverage graph of a project over time](img/code_coverage_graph_v13_1.png)
@@ -261,7 +270,7 @@ Follow these steps to enable the `Coverage-Check` MR approval rule:
![Coverage-Check approval rule](img/coverage_check_approval_rule_14_1.png)
-### Removing color codes
+### Remove color codes from code coverage
Some test coverage tools output with ANSI color codes that aren't
parsed correctly by the regular expression. This causes coverage
@@ -279,13 +288,18 @@ lein cloverage | perl -pe 's/\e\[?.*?[\@-~]//g'
## Pipeline badges
-In the pipelines settings page you can find pipeline status and test coverage
-badges for your project. The latest successful pipeline is used to read
-the pipeline status and test coverage values.
+Pipeline badges indicate the pipeline status and a test coverage value
+for your project. These badges are determined by the latest successful pipeline.
+
+### View the code for the pipeline status and coverage reports badges
-Visit the pipelines settings page in your project to see the exact link to
-your badges. You can also see ways to embed the badge image in your HTML or Markdown
-pages.
+You can view the exact link for your badges. Then you can embed the badge in your HTML
+or Markdown pages.
+
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > CI/CD**.
+1. Expand **General pipelines**.
+1. In the **Pipeline status** or **Coverage report** sections, view the URLs for the images.
![Pipelines badges](img/pipelines_settings_badges.png)
@@ -301,7 +315,7 @@ Depending on the status of your pipeline, a badge can have the following values:
- `canceled`
- `unknown`
-You can access a pipeline status badge image using the following link:
+You can access a pipeline status badge image by using the following link:
```plaintext
https://gitlab.example.com/<namespace>/<project>/badges/<branch>/pipeline.svg
@@ -309,7 +323,7 @@ https://gitlab.example.com/<namespace>/<project>/badges/<branch>/pipeline.svg
#### Display only non-skipped status
-If you want the pipeline status badge to only display the last non-skipped status, you can use the `?ignore_skipped=true` query parameter:
+To make the pipeline status badge display only the last non-skipped status, use the `?ignore_skipped=true` query parameter:
```plaintext
https://gitlab.example.com/<namespace>/<project>/badges/<branch>/pipeline.svg?ignore_skipped=true
@@ -317,20 +331,20 @@ https://gitlab.example.com/<namespace>/<project>/badges/<branch>/pipeline.svg?ig
### Test coverage report badge
-GitLab makes it possible to define the regular expression for the [coverage report](#test-coverage-parsing),
+You can define the regular expression for the [coverage report](#add-test-coverage-results-to-a-merge-request)
that each job log is matched against. This means that each job in the
pipeline can have the test coverage percentage value defined.
-The test coverage badge can be accessed using following link:
+To access the test coverage badge, use the following link:
```plaintext
https://gitlab.example.com/<namespace>/<project>/badges/<branch>/coverage.svg
```
-If you would like to get the coverage report from a specific job, you can add
+To get the coverage report from a specific job, add
the `job=coverage_job_name` parameter to the URL. For example, the following
Markdown code embeds the test coverage report badge of the `coverage` job
-into your `README.md`:
+in your `README.md`:
```markdown
![coverage](https://gitlab.com/gitlab-org/gitlab/badges/main/coverage.svg?job=coverage)
diff --git a/doc/ci/runners/configure_runners.md b/doc/ci/runners/configure_runners.md
index 3f52627740f..2f845a05a4e 100644
--- a/doc/ci/runners/configure_runners.md
+++ b/doc/ci/runners/configure_runners.md
@@ -19,14 +19,14 @@ Read [clearing the cache](../caching/index.md#clearing-the-cache).
## Set maximum job timeout for a runner
For each runner, you can specify a *maximum job timeout*. This timeout,
-if smaller than the [project defined timeout](../pipelines/settings.md#timeout), takes precedence.
+if smaller than the [project defined timeout](../pipelines/settings.md#set-a-limit-for-how-long-jobs-can-run), takes precedence.
This feature can be used to prevent your shared runner from being overwhelmed
by a project that has jobs with a long timeout (for example, one week).
When not configured, runners do not override the project timeout.
-On GitLab.com, you cannot override the job timeout for shared runners and must use the [project defined timeout](../pipelines/settings.md#timeout).
+On GitLab.com, you cannot override the job timeout for shared runners and must use the [project defined timeout](../pipelines/settings.md#set-a-limit-for-how-long-jobs-can-run).
To set the maximum job timeout:
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index 8cdcdb2d61d..26974eb4abd 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -3418,7 +3418,7 @@ test:
```
The job-level timeout can exceed the
-[project-level timeout](../pipelines/settings.md#timeout) but can't
+[project-level timeout](../pipelines/settings.md#set-a-limit-for-how-long-jobs-can-run) but can't
exceed the runner-specific timeout.
### `parallel`
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 20530d805ca..a24a4ecc266 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -605,7 +605,7 @@ A merge request may benefit from being considered a customer critical priority b
Properties of customer critical merge requests:
-- The [Senior Director of Development](https://about.gitlab.com/job-families/engineering/engineering-management/#senior-director-engineering) ([@clefelhocz1](https://gitlab.com/clefelhocz1)) is the DRI for deciding if a merge request is customer critical.
+- The [VP of Development](https://about.gitlab.com/job-families/engineering/development/management/vp) ([@clefelhocz1](https://gitlab.com/clefelhocz1)) is the DRI for deciding if a merge request qualifies as customer critical.
- The DRI applies the `customer-critical-merge-request` label to the merge request.
- It is required that the reviewer(s) and maintainer(s) involved with a customer critical merge request are engaged as soon as this decision is made.
- It is required to prioritize work for those involved on a customer critical merge request so that they have the time available necessary to focus on it.
diff --git a/doc/development/database/transaction_guidelines.md b/doc/development/database/transaction_guidelines.md
new file mode 100644
index 00000000000..1c25496b153
--- /dev/null
+++ b/doc/development/database/transaction_guidelines.md
@@ -0,0 +1,117 @@
+---
+stage: Enablement
+group: Database
+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
+---
+
+# Transaction guidelines
+
+This document gives a few examples of the usage of database transactions in application code.
+
+For further reference please check PostgreSQL documentation about [transactions](https://www.postgresql.org/docs/current/tutorial-transactions.html).
+
+## Database decomposition and sharding
+
+The [sharding group](https://about.gitlab.com/handbook/engineering/development/enablement/sharding) plans to split the main GitLab database and move some of the database tables to other database servers.
+
+The group will start decomposing the `ci_*` related database tables first. To maintain the current application development experience, tooling and static analyzers will be added to the codebase to ensure correct data access and data modification methods. By using the correct form for defining database transactions, we can save significant refactoring work in the future.
+
+## The transaction block
+
+The `ActiveRecord` library provides a convenient way to group database statements into a transaction.
+
+```ruby
+issue = Issue.find(10)
+project = issue.project
+
+ApplicationRecord.transaction do
+ issue.update!(title: 'updated title')
+ project.update!(last_update_at: Time.now)
+end
+```
+
+This transaction involves two database tables, in case of an error, each `UPDATE` statement will be rolled back to the previous, consistent state.
+
+NOTE:
+Avoid referencing the `ActiveRecord::Base` class and use `ApplicationRecord` instead.
+
+## Transaction and database locks
+
+When a transaction block is opened, the database will try to acquire the necessary locks on the resources. The type of locks will depend on the actual database statements.
+
+Consider a concurrent update scenario where the following code is executed at the same time from two different processes:
+
+```ruby
+issue = Issue.find(10)
+project = issue.project
+
+ApplicationRecord.transaction do
+ issue.update!(title: 'updated title')
+ project.update!(last_update_at: Time.now)
+end
+```
+
+The database will try to acquire the `FOR UPDATE` lock for the referenced `issue` and `project` records. In our case, we have two competing transactions for these locks, one of them will successfully acquire them. The other transaction will have to wait in the lock queue until the first transaction finishes. The execution of the second transaction is blocked at this point.
+
+## Transaction speed
+
+To prevent lock contention and maintain stable application performance, the transaction block should finish as fast as possible. When a transaction acquires locks, it will hold on to them until the transaction finishes.
+
+Apart from application performance, long-running transactions can also affect the application upgrade processes by blocking database migrations.
+
+### Dangerous example: 3rd party API calls
+
+Consider the following example:
+
+```ruby
+member = Member.find(5)
+
+Member.transaction do
+ member.update!(notification_email_sent: true)
+
+ member.send_notification_email
+end
+```
+
+Here, we ensure that the `notification_email_sent` column is updated only when the `send_notification_email` method succeeds. The `send_notification_email` method executes a network request to an email sending service. If the underlying infrastructure does not specify timeouts or the network call takes too long time, the database transaction will stay open.
+
+Ideally, a transaction should only contain database statements.
+
+Avoid doing in a `transaction` block:
+
+- External network requests such as: triggering Sidekiq jobs, sending emails, HTTP API calls and running database statements using a different connection.
+- File system operations.
+- Long, CPU intensive computation.
+- Calling `sleep(n)`.
+
+## Explicit model referencing
+
+If a transaction modifies records from the same database table, it's advised to use the `Model.transaction` block:
+
+```ruby
+build_1 = Ci::Build.find(1)
+build_2 = Ci::Build.find(2)
+
+Ci::Build.transaction do
+ build_1.touch
+ build_2.touch
+end
+```
+
+The transaction above will use the same database connection for the transaction as the models in the `transaction` block. In a multi-database environment the following example would be dangerous:
+
+```ruby
+# `ci_builds` table is located on another database
+class Ci::Build < CiDatabase
+end
+
+build_1 = Ci::Build.find(1)
+build_2 = Ci::Build.find(2)
+
+ActiveRecord::Base.transaction do
+ build_1.touch
+ build_2.touch
+end
+```
+
+The `ActiveRecord::Base` class uses a different database connection than the `Ci::Build` records. The two statements in the transaction block will not be part of the transaction and will not be rolled back in case something goes wrong. They act as 3rd part calls.
diff --git a/doc/subscriptions/gitlab_com/index.md b/doc/subscriptions/gitlab_com/index.md
index 38347172958..32f587d900e 100644
--- a/doc/subscriptions/gitlab_com/index.md
+++ b/doc/subscriptions/gitlab_com/index.md
@@ -272,14 +272,15 @@ If you add a member to a group by using the [share a group with another group](.
## CI pipeline minutes
-CI pipeline minutes are the execution time for your
-[pipelines](../../ci/pipelines/index.md) on GitLab shared runners. Each
-[GitLab SaaS tier](https://about.gitlab.com/pricing/) includes a monthly quota
-of CI pipeline minutes:
-
-- Free: 400 minutes
-- Premium: 10,000 minutes
-- Ultimate: 50,000 minutes
+CI pipeline minutes are the execution time for your [pipelines](../../ci/pipelines/index.md)
+on GitLab shared runners. Each [GitLab SaaS tier](https://about.gitlab.com/pricing/)
+includes a monthly quota of CI pipeline minutes for private and public projects:
+
+| Plan | Private projects | Public projects |
+|----------|------------------|-----------------|
+| Free | 400 | 50,000 |
+| Premium | 10,000 | 1,250,000 |
+| Ultimate | 50,000 | 6,250,000 |
Quotas apply to:
diff --git a/doc/user/compliance/license_compliance/img/policies_maintainer_edit_v13_2.png b/doc/user/compliance/license_compliance/img/policies_maintainer_edit_v13_2.png
deleted file mode 100644
index ee3bb310c1a..00000000000
--- a/doc/user/compliance/license_compliance/img/policies_maintainer_edit_v13_2.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/compliance/license_compliance/img/policies_maintainer_edit_v14_2.png b/doc/user/compliance/license_compliance/img/policies_maintainer_edit_v14_2.png
new file mode 100644
index 00000000000..c188c6cd834
--- /dev/null
+++ b/doc/user/compliance/license_compliance/img/policies_maintainer_edit_v14_2.png
Binary files differ
diff --git a/doc/user/compliance/license_compliance/index.md b/doc/user/compliance/license_compliance/index.md
index e67a0d9c3cb..1a43c5ae96f 100644
--- a/doc/user/compliance/license_compliance/index.md
+++ b/doc/user/compliance/license_compliance/index.md
@@ -47,8 +47,7 @@ When GitLab detects a **Denied** license, you can view it in the [license list](
![License List](img/license_list_v13_0.png)
You can view and modify existing policies from the [policies](#policies) tab.
-
-![Edit Policy](img/policies_maintainer_edit_v13_2.png)
+![Edit Policy](img/policies_maintainer_edit_v14_2.png)
## Supported languages and package managers
@@ -722,7 +721,7 @@ which enables a designated approver that can approve and then merge a merge requ
The **Policies** tab in the project's license compliance section displays your project's license
policies. Project maintainers can specify policies in this section.
-![Edit Policy](img/policies_maintainer_edit_v13_2.png)
+![Edit Policy](img/policies_maintainer_edit_v14_2.png)
![Add Policy](img/policies_maintainer_add_v13_2.png)
Developers of the project can view the policies configured in a project.
diff --git a/doc/user/group/repositories_analytics/index.md b/doc/user/group/repositories_analytics/index.md
index b9f94d96b48..054c6ab7a6b 100644
--- a/doc/user/group/repositories_analytics/index.md
+++ b/doc/user/group/repositories_analytics/index.md
@@ -41,7 +41,7 @@ To see the latest code coverage for each project in your group:
1. In the **Latest test coverage results** section, use the **Select projects** dropdown to choose the projects you want to check.
You can download code coverage data for specific projects using
-[code coverage history](../../../ci/pipelines/settings.md#code-coverage-history).
+[code coverage history](../../../ci/pipelines/settings.md#view-code-coverage-history).
## Download historic test coverage data
diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb
index 63923b02146..0bac6fe2054 100644
--- a/lib/api/ci/runner.rb
+++ b/lib/api/ci/runner.rb
@@ -11,7 +11,7 @@ module API
resource :runners do
desc 'Registers a new Runner' do
- success Entities::RunnerRegistrationDetails
+ success Entities::Ci::RunnerRegistrationDetails
http_codes [[201, 'Runner was created'], [403, 'Forbidden']]
end
params do
@@ -47,7 +47,7 @@ module API
@runner = ::Ci::Runner.create(attributes)
if @runner.persisted?
- present @runner, with: Entities::RunnerRegistrationDetails
+ present @runner, with: Entities::Ci::RunnerRegistrationDetails
else
render_validation_error!(@runner)
end
@@ -82,7 +82,7 @@ module API
before { set_application_context }
desc 'Request a job' do
- success Entities::JobRequest::Response
+ success Entities::Ci::JobRequest::Response
http_codes [[201, 'Job was scheduled'],
[204, 'No job for Runner'],
[403, 'Forbidden']]
@@ -267,7 +267,7 @@ module API
end
desc 'Upload artifacts for job' do
- success Entities::JobRequest::Response
+ success Entities::Ci::JobRequest::Response
http_codes [[201, 'Artifact uploaded'],
[400, 'Bad request'],
[403, 'Forbidden'],
diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb
index 44ffc941cfa..d46b3a27e72 100644
--- a/lib/api/ci/runners.rb
+++ b/lib/api/ci/runners.rb
@@ -11,7 +11,7 @@ module API
resource :runners do
desc 'Get runners available for user' do
- success Entities::Runner
+ success Entities::Ci::Runner
end
params do
optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
@@ -30,11 +30,11 @@ module API
runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
- present paginate(runners), with: Entities::Runner
+ present paginate(runners), with: Entities::Ci::Runner
end
desc 'Get all runners - shared and specific' do
- success Entities::Runner
+ success Entities::Ci::Runner
end
params do
optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES,
@@ -55,11 +55,11 @@ module API
runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
- present paginate(runners), with: Entities::Runner
+ present paginate(runners), with: Entities::Ci::Runner
end
desc "Get runner's details" do
- success Entities::RunnerDetails
+ success Entities::Ci::RunnerDetails
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
@@ -68,11 +68,11 @@ module API
runner = get_runner(params[:id])
authenticate_show_runner!(runner)
- present runner, with: Entities::RunnerDetails, current_user: current_user
+ present runner, with: Entities::Ci::RunnerDetails, current_user: current_user
end
desc "Update runner's details" do
- success Entities::RunnerDetails
+ success Entities::Ci::RunnerDetails
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
@@ -92,14 +92,14 @@ module API
update_service = ::Ci::UpdateRunnerService.new(runner)
if update_service.update(declared_params(include_missing: false))
- present runner, with: Entities::RunnerDetails, current_user: current_user
+ present runner, with: Entities::Ci::RunnerDetails, current_user: current_user
else
render_validation_error!(runner)
end
end
desc 'Remove a runner' do
- success Entities::Runner
+ success Entities::Ci::Runner
end
params do
requires :id, type: Integer, desc: 'The ID of the runner'
@@ -139,7 +139,7 @@ module API
before { authorize_admin_project }
desc 'Get runners available for project' do
- success Entities::Runner
+ success Entities::Ci::Runner
end
params do
optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES,
@@ -158,11 +158,11 @@ module API
runners = filter_runners(runners, params[:scope])
runners = apply_filter(runners, params)
- present paginate(runners), with: Entities::Runner
+ present paginate(runners), with: Entities::Ci::Runner
end
desc 'Enable a runner for a project' do
- success Entities::Runner
+ success Entities::Ci::Runner
end
params do
requires :runner_id, type: Integer, desc: 'The ID of the runner'
@@ -172,14 +172,14 @@ module API
authenticate_enable_runner!(runner)
if runner.assign_to(user_project)
- present runner, with: Entities::Runner
+ present runner, with: Entities::Ci::Runner
else
render_validation_error!(runner)
end
end
desc "Disable project's runner" do
- success Entities::Runner
+ success Entities::Ci::Runner
end
params do
requires :runner_id, type: Integer, desc: 'The ID of the runner'
@@ -204,7 +204,7 @@ module API
before { authorize_admin_group }
desc 'Get runners available for group' do
- success Entities::Runner
+ success Entities::Ci::Runner
end
params do
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
@@ -218,7 +218,7 @@ module API
runners = ::Ci::Runner.belonging_to_group(user_group.id, include_ancestors: true)
runners = apply_filter(runners, params)
- present paginate(runners), with: Entities::Runner
+ present paginate(runners), with: Entities::Ci::Runner
end
end
diff --git a/lib/api/entities/ci/job.rb b/lib/api/entities/ci/job.rb
index 76487ed01dc..cf87684ce55 100644
--- a/lib/api/entities/ci/job.rb
+++ b/lib/api/entities/ci/job.rb
@@ -7,7 +7,7 @@ module API
# artifacts_file is included in job_artifacts, but kept for backward compatibility (remove in api/v5)
expose :artifacts_file, using: ::API::Entities::Ci::JobArtifactFile, if: -> (job, opts) { job.artifacts? }
expose :job_artifacts, as: :artifacts, using: ::API::Entities::Ci::JobArtifact
- expose :runner, with: ::API::Entities::Runner
+ expose :runner, with: ::API::Entities::Ci::Runner
expose :artifacts_expire_at
expose :tag_list do |job|
job.tags.map(&:name).sort
diff --git a/lib/api/entities/ci/job_request/artifacts.rb b/lib/api/entities/ci/job_request/artifacts.rb
new file mode 100644
index 00000000000..4b09db40504
--- /dev/null
+++ b/lib/api/entities/ci/job_request/artifacts.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ module JobRequest
+ class Artifacts < Grape::Entity
+ expose :name
+ expose :untracked
+ expose :paths
+ expose :exclude, expose_nil: false
+ expose :when
+ expose :expire_in
+ expose :artifact_type
+ expose :artifact_format
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/job_request/cache.rb b/lib/api/entities/ci/job_request/cache.rb
new file mode 100644
index 00000000000..9820719b4f0
--- /dev/null
+++ b/lib/api/entities/ci/job_request/cache.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ module JobRequest
+ class Cache < Grape::Entity
+ expose :key, :untracked, :paths, :policy, :when
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/job_request/credentials.rb b/lib/api/entities/ci/job_request/credentials.rb
new file mode 100644
index 00000000000..57cdd9c9b19
--- /dev/null
+++ b/lib/api/entities/ci/job_request/credentials.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ module JobRequest
+ class Credentials < Grape::Entity
+ expose :type, :url, :username, :password
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/job_request/dependency.rb b/lib/api/entities/ci/job_request/dependency.rb
new file mode 100644
index 00000000000..2c6ed417714
--- /dev/null
+++ b/lib/api/entities/ci/job_request/dependency.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ module JobRequest
+ class Dependency < Grape::Entity
+ expose :id, :name, :token
+ expose :artifacts_file, using: Entities::Ci::JobArtifactFile, if: ->(job, _) { job.artifacts? }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/job_request/git_info.rb b/lib/api/entities/ci/job_request/git_info.rb
new file mode 100644
index 00000000000..872c896b870
--- /dev/null
+++ b/lib/api/entities/ci/job_request/git_info.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ module JobRequest
+ class GitInfo < Grape::Entity
+ expose :repo_url, :ref, :sha, :before_sha
+ expose :ref_type
+ expose :refspecs
+ expose :git_depth, as: :depth
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/job_request/image.rb b/lib/api/entities/ci/job_request/image.rb
new file mode 100644
index 00000000000..8e404a8fa02
--- /dev/null
+++ b/lib/api/entities/ci/job_request/image.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ module JobRequest
+ class Image < Grape::Entity
+ expose :name, :entrypoint
+ expose :ports, using: Entities::Ci::JobRequest::Port
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/job_request/job_info.rb b/lib/api/entities/ci/job_request/job_info.rb
new file mode 100644
index 00000000000..5c3f4b08af2
--- /dev/null
+++ b/lib/api/entities/ci/job_request/job_info.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ module JobRequest
+ class JobInfo < Grape::Entity
+ expose :id, :name, :stage
+ expose :project_id, :project_name
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/job_request/port.rb b/lib/api/entities/ci/job_request/port.rb
new file mode 100644
index 00000000000..31aa06ff843
--- /dev/null
+++ b/lib/api/entities/ci/job_request/port.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ module JobRequest
+ class Port < Grape::Entity
+ expose :number, :protocol, :name
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/job_request/response.rb b/lib/api/entities/ci/job_request/response.rb
new file mode 100644
index 00000000000..86c945cb236
--- /dev/null
+++ b/lib/api/entities/ci/job_request/response.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ module JobRequest
+ class Response < Grape::Entity
+ expose :id
+ expose :token
+ expose :allow_git_fetch
+
+ expose :job_info, using: Entities::Ci::JobRequest::JobInfo do |model|
+ model
+ end
+
+ expose :git_info, using: Entities::Ci::JobRequest::GitInfo do |model|
+ model
+ end
+
+ expose :runner_info, using: Entities::Ci::JobRequest::RunnerInfo do |model|
+ model
+ end
+
+ expose :runner_variables, as: :variables
+ expose :steps, using: Entities::Ci::JobRequest::Step
+ expose :image, using: Entities::Ci::JobRequest::Image
+ expose :services, using: Entities::Ci::JobRequest::Service
+ expose :artifacts, using: Entities::Ci::JobRequest::Artifacts
+ expose :cache, using: Entities::Ci::JobRequest::Cache
+ expose :credentials, using: Entities::Ci::JobRequest::Credentials
+ expose :all_dependencies, as: :dependencies, using: Entities::Ci::JobRequest::Dependency
+ expose :features
+ end
+ end
+ end
+ end
+end
+
+API::Entities::Ci::JobRequest::Response.prepend_mod_with('API::Entities::Ci::JobRequest::Response')
diff --git a/lib/api/entities/ci/job_request/runner_info.rb b/lib/api/entities/ci/job_request/runner_info.rb
new file mode 100644
index 00000000000..96336a1080e
--- /dev/null
+++ b/lib/api/entities/ci/job_request/runner_info.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ module JobRequest
+ class RunnerInfo < Grape::Entity
+ expose :metadata_timeout, as: :timeout
+ expose :runner_session_url
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/job_request/service.rb b/lib/api/entities/ci/job_request/service.rb
new file mode 100644
index 00000000000..f89b95c1d5c
--- /dev/null
+++ b/lib/api/entities/ci/job_request/service.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ module JobRequest
+ class Service < Entities::Ci::JobRequest::Image
+ expose :alias, :command
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/job_request/step.rb b/lib/api/entities/ci/job_request/step.rb
new file mode 100644
index 00000000000..2a0c4cd032e
--- /dev/null
+++ b/lib/api/entities/ci/job_request/step.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ module JobRequest
+ class Step < Grape::Entity
+ expose :name, :script, :timeout, :when, :allow_failure
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/runner.rb b/lib/api/entities/ci/runner.rb
new file mode 100644
index 00000000000..ede698696de
--- /dev/null
+++ b/lib/api/entities/ci/runner.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ class Runner < Grape::Entity
+ expose :id
+ expose :description
+ expose :ip_address
+ expose :active
+ expose :instance_type?, as: :is_shared
+ expose :runner_type
+ expose :name
+ expose :online?, as: :online
+ expose :status
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/runner_details.rb b/lib/api/entities/ci/runner_details.rb
new file mode 100644
index 00000000000..9d44da7e5b3
--- /dev/null
+++ b/lib/api/entities/ci/runner_details.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ class RunnerDetails < Runner
+ expose :tag_list
+ expose :run_untagged
+ expose :locked
+ expose :maximum_timeout
+ expose :access_level
+ expose :version, :revision, :platform, :architecture
+ expose :contacted_at
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ expose :projects, with: Entities::BasicProjectDetails do |runner, options|
+ if options[:current_user].admin? # rubocop: disable Cop/UserAdmin
+ runner.projects
+ else
+ options[:current_user].authorized_projects.where(id: runner.projects)
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
+ expose :groups, with: Entities::BasicGroupDetails do |runner, options|
+ if options[:current_user].admin? # rubocop: disable Cop/UserAdmin
+ runner.groups
+ else
+ options[:current_user].authorized_groups.where(id: runner.groups)
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ci/runner_registration_details.rb b/lib/api/entities/ci/runner_registration_details.rb
new file mode 100644
index 00000000000..fa7e44c9e40
--- /dev/null
+++ b/lib/api/entities/ci/runner_registration_details.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ class RunnerRegistrationDetails < Grape::Entity
+ expose :id, :token
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/job_request/artifacts.rb b/lib/api/entities/job_request/artifacts.rb
deleted file mode 100644
index 0d27f5a9189..00000000000
--- a/lib/api/entities/job_request/artifacts.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module JobRequest
- class Artifacts < Grape::Entity
- expose :name
- expose :untracked
- expose :paths
- expose :exclude, expose_nil: false
- expose :when
- expose :expire_in
- expose :artifact_type
- expose :artifact_format
- end
- end
- end
-end
diff --git a/lib/api/entities/job_request/cache.rb b/lib/api/entities/job_request/cache.rb
deleted file mode 100644
index cd533d7e5b3..00000000000
--- a/lib/api/entities/job_request/cache.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module JobRequest
- class Cache < Grape::Entity
- expose :key, :untracked, :paths, :policy, :when
- end
- end
- end
-end
diff --git a/lib/api/entities/job_request/credentials.rb b/lib/api/entities/job_request/credentials.rb
deleted file mode 100644
index cdac5566cbd..00000000000
--- a/lib/api/entities/job_request/credentials.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module JobRequest
- class Credentials < Grape::Entity
- expose :type, :url, :username, :password
- end
- end
- end
-end
diff --git a/lib/api/entities/job_request/dependency.rb b/lib/api/entities/job_request/dependency.rb
deleted file mode 100644
index 7d6ec832ba1..00000000000
--- a/lib/api/entities/job_request/dependency.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module JobRequest
- class Dependency < Grape::Entity
- expose :id, :name, :token
- expose :artifacts_file, using: Entities::Ci::JobArtifactFile, if: ->(job, _) { job.artifacts? }
- end
- end
- end
-end
diff --git a/lib/api/entities/job_request/git_info.rb b/lib/api/entities/job_request/git_info.rb
deleted file mode 100644
index e07099263b5..00000000000
--- a/lib/api/entities/job_request/git_info.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module JobRequest
- class GitInfo < Grape::Entity
- expose :repo_url, :ref, :sha, :before_sha
- expose :ref_type
- expose :refspecs
- expose :git_depth, as: :depth
- end
- end
- end
-end
diff --git a/lib/api/entities/job_request/image.rb b/lib/api/entities/job_request/image.rb
deleted file mode 100644
index 47f4542d2d5..00000000000
--- a/lib/api/entities/job_request/image.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module JobRequest
- class Image < Grape::Entity
- expose :name, :entrypoint
- expose :ports, using: Entities::JobRequest::Port
- end
- end
- end
-end
diff --git a/lib/api/entities/job_request/job_info.rb b/lib/api/entities/job_request/job_info.rb
deleted file mode 100644
index a4bcc9726d0..00000000000
--- a/lib/api/entities/job_request/job_info.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module JobRequest
- class JobInfo < Grape::Entity
- expose :id, :name, :stage
- expose :project_id, :project_name
- end
- end
- end
-end
diff --git a/lib/api/entities/job_request/port.rb b/lib/api/entities/job_request/port.rb
deleted file mode 100644
index ee427da8657..00000000000
--- a/lib/api/entities/job_request/port.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module JobRequest
- class Port < Grape::Entity
- expose :number, :protocol, :name
- end
- end
- end
-end
diff --git a/lib/api/entities/job_request/response.rb b/lib/api/entities/job_request/response.rb
deleted file mode 100644
index 2e8dfc5bde0..00000000000
--- a/lib/api/entities/job_request/response.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module JobRequest
- class Response < Grape::Entity
- expose :id
- expose :token
- expose :allow_git_fetch
-
- expose :job_info, using: Entities::JobRequest::JobInfo do |model|
- model
- end
-
- expose :git_info, using: Entities::JobRequest::GitInfo do |model|
- model
- end
-
- expose :runner_info, using: Entities::JobRequest::RunnerInfo do |model|
- model
- end
-
- expose :runner_variables, as: :variables
- expose :steps, using: Entities::JobRequest::Step
- expose :image, using: Entities::JobRequest::Image
- expose :services, using: Entities::JobRequest::Service
- expose :artifacts, using: Entities::JobRequest::Artifacts
- expose :cache, using: Entities::JobRequest::Cache
- expose :credentials, using: Entities::JobRequest::Credentials
- expose :all_dependencies, as: :dependencies, using: Entities::JobRequest::Dependency
- expose :features
- end
- end
- end
-end
-
-API::Entities::JobRequest::Response.prepend_mod_with('API::Entities::JobRequest::Response')
diff --git a/lib/api/entities/job_request/runner_info.rb b/lib/api/entities/job_request/runner_info.rb
deleted file mode 100644
index e6d2e8d9e85..00000000000
--- a/lib/api/entities/job_request/runner_info.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module JobRequest
- class RunnerInfo < Grape::Entity
- expose :metadata_timeout, as: :timeout
- expose :runner_session_url
- end
- end
- end
-end
diff --git a/lib/api/entities/job_request/service.rb b/lib/api/entities/job_request/service.rb
deleted file mode 100644
index 9ad5abf4e9e..00000000000
--- a/lib/api/entities/job_request/service.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module JobRequest
- class Service < Entities::JobRequest::Image
- expose :alias, :command
- end
- end
- end
-end
diff --git a/lib/api/entities/job_request/step.rb b/lib/api/entities/job_request/step.rb
deleted file mode 100644
index 498dd017fb4..00000000000
--- a/lib/api/entities/job_request/step.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- module JobRequest
- class Step < Grape::Entity
- expose :name, :script, :timeout, :when, :allow_failure
- end
- end
- end
-end
diff --git a/lib/api/entities/runner.rb b/lib/api/entities/runner.rb
deleted file mode 100644
index e78f14cf920..00000000000
--- a/lib/api/entities/runner.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- class Runner < Grape::Entity
- expose :id
- expose :description
- expose :ip_address
- expose :active
- expose :instance_type?, as: :is_shared
- expose :runner_type
- expose :name
- expose :online?, as: :online
- expose :status
- end
- end
-end
diff --git a/lib/api/entities/runner_details.rb b/lib/api/entities/runner_details.rb
deleted file mode 100644
index 0afe298ef64..00000000000
--- a/lib/api/entities/runner_details.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- class RunnerDetails < Runner
- expose :tag_list
- expose :run_untagged
- expose :locked
- expose :maximum_timeout
- expose :access_level
- expose :version, :revision, :platform, :architecture
- expose :contacted_at
-
- # rubocop: disable CodeReuse/ActiveRecord
- expose :projects, with: Entities::BasicProjectDetails do |runner, options|
- if options[:current_user].admin?
- runner.projects
- else
- options[:current_user].authorized_projects.where(id: runner.projects)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
- expose :groups, with: Entities::BasicGroupDetails do |runner, options|
- if options[:current_user].admin?
- runner.groups
- else
- options[:current_user].authorized_groups.where(id: runner.groups)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
- end
-end
diff --git a/lib/api/entities/runner_registration_details.rb b/lib/api/entities/runner_registration_details.rb
deleted file mode 100644
index c8ed88ba10a..00000000000
--- a/lib/api/entities/runner_registration_details.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- class RunnerRegistrationDetails < Grape::Entity
- expose :id, :token
- end
- end
-end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 6ad72e44ebe..eee9af5f627 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -17489,6 +17489,9 @@ msgstr ""
msgid "Input the remote repository URL"
msgstr ""
+msgid "Insert"
+msgstr ""
+
msgid "Insert a %{rows}x%{cols} table."
msgstr ""
@@ -19173,6 +19176,9 @@ msgstr ""
msgid "Last item before this page loaded in your browser:"
msgstr ""
+msgid "Last modified"
+msgstr ""
+
msgid "Last month"
msgstr ""
@@ -21749,15 +21755,9 @@ msgstr ""
msgid "NetworkPolicies|Kubernetes error: %{error}"
msgstr ""
-msgid "NetworkPolicies|Last modified"
-msgstr ""
-
msgid "NetworkPolicies|Name"
msgstr ""
-msgid "NetworkPolicies|Namespace"
-msgstr ""
-
msgid "NetworkPolicies|Network"
msgstr ""
@@ -22905,12 +22905,6 @@ msgstr ""
msgid "OnCallSchedules|The schedule could not be updated. Please try again."
msgstr ""
-msgid "OnCallSchedules|To create an escalation policy that defines which schedule is used when, visit the %{linkStart}escalation policy%{linkEnd} page."
-msgstr ""
-
-msgid "OnCallSchedules|To create an escalation policy using this schedule, visit the %{linkStart}escalation policy%{linkEnd} page."
-msgstr ""
-
msgid "OnCallSchedules|Try adding a rotation"
msgstr ""
@@ -22926,7 +22920,7 @@ msgstr ""
msgid "OnCallSchedules|You are currently a part of:"
msgstr ""
-msgid "OnCallSchedules|Your schedule has been successfully created. To add individual users to this schedule, use the add a rotation button."
+msgid "OnCallSchedules|Your schedule has been successfully created. To add individual users to this schedule, use the add a rotation button. To create an escalation policy that defines which schedule is used when, visit the %{linkStart}escalation policy%{linkEnd} page."
msgstr ""
msgid "OnDemandScans|Could not fetch scanner profiles. Please refresh the page, or try again later."
@@ -24227,6 +24221,9 @@ msgstr ""
msgid "Pipelines|Merged YAML is view only"
msgstr ""
+msgid "Pipelines|Merged YAML unavailable"
+msgstr ""
+
msgid "Pipelines|More Information"
msgstr ""
@@ -24263,6 +24260,9 @@ msgstr ""
msgid "Pipelines|The GitLab CI configuration could not be updated."
msgstr ""
+msgid "Pipelines|The merged YAML view is only available for the default branch. %{linkStart}Learn more.%{linkEnd}"
+msgstr ""
+
msgid "Pipelines|There are currently no finished pipelines."
msgstr ""
@@ -29111,6 +29111,9 @@ msgstr ""
msgid "SecurityOrchestration|Security policy project"
msgstr ""
+msgid "SecurityPolicies|+%{count} more"
+msgstr ""
+
msgid "SecurityPolicies|All policies"
msgstr ""
@@ -29120,6 +29123,9 @@ msgstr ""
msgid "SecurityPolicies|Enforcement status"
msgstr ""
+msgid "SecurityPolicies|Environment(s)"
+msgstr ""
+
msgid "SecurityPolicies|Latest scan"
msgstr ""
@@ -35334,6 +35340,9 @@ msgstr ""
msgid "Upload file"
msgstr ""
+msgid "Upload image"
+msgstr ""
+
msgid "Upload license"
msgstr ""
diff --git a/qa/qa/page/group/bulk_import.rb b/qa/qa/page/group/bulk_import.rb
index 11741bdf4cb..a0511c9a16c 100644
--- a/qa/qa/page/group/bulk_import.rb
+++ b/qa/qa/page/group/bulk_import.rb
@@ -10,12 +10,15 @@ module QA
view "app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue" do
element :import_item
- element :target_namespace_selector_dropdown
element :target_group_dropdown_item
element :import_status_indicator
element :import_group_button
end
+ view "app/assets/javascripts/import_entities/components/group_dropdown.vue" do
+ element :target_namespace_selector_dropdown
+ end
+
# Import source group in to target group
#
# @param [String] source_group_name
diff --git a/qa/qa/page/project/import/github.rb b/qa/qa/page/project/import/github.rb
index b1c15dfa8e6..74bc4cec467 100644
--- a/qa/qa/page/project/import/github.rb
+++ b/qa/qa/page/project/import/github.rb
@@ -14,13 +14,16 @@ module QA
view 'app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue' do
element :project_import_row
- element :project_namespace_select
element :project_path_field
element :import_button
element :project_path_content
element :go_to_project_button
end
+ view "app/assets/javascripts/import_entities/components/group_dropdown.vue" do
+ element :target_namespace_selector_dropdown
+ end
+
def add_personal_access_token(personal_access_token)
# If for some reasons this process is retried, user cannot re-enter github token in the same group
# In this case skip this step and proceed to import project row
@@ -59,10 +62,9 @@ module QA
def choose_test_namespace(full_path)
within_repo_path(full_path) do
- click_element :project_namespace_select
+ within_element(:target_namespace_selector_dropdown) { click_button(class: 'dropdown-toggle') }
+ click_element(:target_group_dropdown_item, group_name: Runtime::Namespace.path)
end
-
- search_and_select(Runtime::Namespace.path)
end
def set_path(full_path, name)
diff --git a/rubocop/cop/database/multiple_databases.rb b/rubocop/cop/database/multiple_databases.rb
new file mode 100644
index 00000000000..fb6e81f9845
--- /dev/null
+++ b/rubocop/cop/database/multiple_databases.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ module Database
+ # @example
+ # # bad
+ # ActiveRecord::Base.connection
+ #
+ # # good
+ # ApplicationRecord.connection
+ #
+ class MultipleDatabases < RuboCop::Cop::Cop
+ AR_BASE_MESSAGE = <<~EOF
+ Do not use methods from ActiveRecord::Base, use the ApplicationRecord class instead
+ For fixing offenses related to the ActiveRecord::Base.transaction method, see our guidelines:
+ https://docs.gitlab.com/ee/development/database/transaction_guidelines.html
+ EOF
+
+ def_node_matcher :active_record_base_method_is_used?, <<~PATTERN
+ (send (const (const nil? :ActiveRecord) :Base) $_)
+ PATTERN
+
+ def on_send(node)
+ return unless active_record_base_method_is_used?(node)
+
+ add_offense(node, location: :expression, message: AR_BASE_MESSAGE)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js b/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
index 4bb244df8ac..2537b8fb816 100644
--- a/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
+++ b/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
@@ -78,6 +78,8 @@ describe('ProjectsDropdownFilter component', () => {
const selectDropdownItemAtIndex = (index) =>
findDropdownAtIndex(index).find('button').trigger('click');
+ const selectedIds = () => wrapper.vm.selectedProjects.map(({ id }) => id);
+
describe('queryParams are applied when fetching data', () => {
beforeEach(() => {
createComponent({
@@ -238,14 +240,15 @@ describe('ProjectsDropdownFilter component', () => {
selectDropdownItemAtIndex(0);
selectDropdownItemAtIndex(1);
- expect(wrapper.emitted().selected).toEqual([[[projects[0]]], [[projects[0], projects[1]]]]);
+ expect(selectedIds()).toEqual([projects[0].id, projects[1].id]);
});
it('should remove from selection when clicked again', () => {
selectDropdownItemAtIndex(0);
- selectDropdownItemAtIndex(0);
+ expect(selectedIds()).toEqual([projects[0].id]);
- expect(wrapper.emitted().selected).toEqual([[[projects[0]]], [[]]]);
+ selectDropdownItemAtIndex(0);
+ expect(selectedIds()).toEqual([]);
});
it('renders the correct placeholder text when multiple projects are selected', async () => {
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
index 5b38f04e77b..37f0969a39a 100644
--- a/spec/frontend/boards/stores/mutations_spec.js
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -35,6 +35,7 @@ describe('Board Store Mutations', () => {
describe('SET_INITIAL_BOARD_DATA', () => {
it('Should set initial Boards data to state', () => {
+ const allowSubEpics = true;
const boardId = 1;
const fullPath = 'gitlab-org';
const boardType = 'group';
@@ -45,6 +46,7 @@ describe('Board Store Mutations', () => {
const issuableType = issuableTypes.issue;
mutations[types.SET_INITIAL_BOARD_DATA](state, {
+ allowSubEpics,
boardId,
fullPath,
boardType,
@@ -53,6 +55,7 @@ describe('Board Store Mutations', () => {
issuableType,
});
+ expect(state.allowSubEpics).toBe(allowSubEpics);
expect(state.boardId).toEqual(boardId);
expect(state.fullPath).toEqual(fullPath);
expect(state.boardType).toEqual(boardType);
diff --git a/spec/frontend/branches/components/delete_branch_button_spec.js b/spec/frontend/branches/components/delete_branch_button_spec.js
index acbc83a9bdc..b029f34c3d7 100644
--- a/spec/frontend/branches/components/delete_branch_button_spec.js
+++ b/spec/frontend/branches/components/delete_branch_button_spec.js
@@ -34,7 +34,7 @@ describe('Delete branch button', () => {
expect(findDeleteButton().attributes()).toMatchObject({
title: 'Delete branch',
- variant: 'danger',
+ variant: 'default',
icon: 'remove',
});
});
@@ -44,7 +44,7 @@ describe('Delete branch button', () => {
expect(findDeleteButton().attributes()).toMatchObject({
title: 'Delete protected branch',
- variant: 'danger',
+ variant: 'default',
icon: 'remove',
});
});
@@ -78,7 +78,7 @@ describe('Delete branch button', () => {
expect(findDeleteButton().attributes()).toMatchObject({
title: 'Delete branch',
- variant: 'danger',
+ variant: 'default',
});
});
diff --git a/spec/frontend/content_editor/components/toolbar_image_button_spec.js b/spec/frontend/content_editor/components/toolbar_image_button_spec.js
new file mode 100644
index 00000000000..701dcf83476
--- /dev/null
+++ b/spec/frontend/content_editor/components/toolbar_image_button_spec.js
@@ -0,0 +1,78 @@
+import { GlButton, GlFormInputGroup } from '@gitlab/ui';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import ToolbarImageButton from '~/content_editor/components/toolbar_image_button.vue';
+import { configure as configureImageExtension } from '~/content_editor/extensions/image';
+import { createTestEditor, mockChainedCommands } from '../test_utils';
+
+describe('content_editor/components/toolbar_image_button', () => {
+ let wrapper;
+ let editor;
+
+ const buildWrapper = () => {
+ wrapper = mountExtended(ToolbarImageButton, {
+ propsData: {
+ tiptapEditor: editor,
+ },
+ });
+ };
+
+ const findImageURLInput = () =>
+ wrapper.findComponent(GlFormInputGroup).find('input[type="text"]');
+ const findApplyImageButton = () => wrapper.findComponent(GlButton);
+
+ const selectFile = async (file) => {
+ const input = wrapper.find({ ref: 'fileSelector' });
+
+ // override the property definition because `input.files` isn't directly modifyable
+ Object.defineProperty(input.element, 'files', { value: [file], writable: true });
+ await input.trigger('change');
+ };
+
+ beforeEach(() => {
+ const { tiptapExtension: Image } = configureImageExtension({
+ renderMarkdown: jest.fn(),
+ uploadsPath: '/uploads/',
+ });
+
+ editor = createTestEditor({
+ extensions: [Image],
+ });
+
+ buildWrapper();
+ });
+
+ afterEach(() => {
+ editor.destroy();
+ wrapper.destroy();
+ });
+
+ it('sets the image to the value in the URL input when "Insert" button is clicked', async () => {
+ const commands = mockChainedCommands(editor, ['focus', 'setImage', 'run']);
+
+ await findImageURLInput().setValue('https://example.com/img.jpg');
+ await findApplyImageButton().trigger('click');
+
+ expect(commands.focus).toHaveBeenCalled();
+ expect(commands.setImage).toHaveBeenCalledWith({
+ alt: 'img',
+ src: 'https://example.com/img.jpg',
+ canonicalSrc: 'https://example.com/img.jpg',
+ });
+ expect(commands.run).toHaveBeenCalled();
+
+ expect(wrapper.emitted().execute[0]).toEqual([{ contentType: 'image', value: 'url' }]);
+ });
+
+ it('uploads the selected image when file input changes', async () => {
+ const commands = mockChainedCommands(editor, ['focus', 'uploadImage', 'run']);
+ const file = new File(['foo'], 'foo.png', { type: 'image/png' });
+
+ await selectFile(file);
+
+ expect(commands.focus).toHaveBeenCalled();
+ expect(commands.uploadImage).toHaveBeenCalledWith({ file });
+ expect(commands.run).toHaveBeenCalled();
+
+ expect(wrapper.emitted().execute[0]).toEqual([{ contentType: 'image', value: 'upload' }]);
+ });
+});
diff --git a/spec/frontend/content_editor/components/toolbar_link_button_spec.js b/spec/frontend/content_editor/components/toolbar_link_button_spec.js
index 7dcf3ac659b..576a2912f72 100644
--- a/spec/frontend/content_editor/components/toolbar_link_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_link_button_spec.js
@@ -76,15 +76,17 @@ describe('content_editor/components/toolbar_link_button', () => {
expect(commands.unsetLink).toHaveBeenCalled();
expect(commands.setLink).toHaveBeenCalledWith({
href: 'https://example',
- 'data-canonical-src': 'https://example',
+ canonicalSrc: 'https://example',
});
expect(commands.run).toHaveBeenCalled();
+
+ expect(wrapper.emitted().execute[0]).toEqual([{ contentType: 'link' }]);
});
describe('on selection update', () => {
it('updates link input box with canonical-src if present', async () => {
jest.spyOn(editor, 'getAttributes').mockReturnValueOnce({
- 'data-canonical-src': 'uploads/my-file.zip',
+ canonicalSrc: 'uploads/my-file.zip',
href: '/username/my-project/uploads/abcdefgh133535/my-file.zip',
});
@@ -130,9 +132,11 @@ describe('content_editor/components/toolbar_link_button', () => {
expect(commands.focus).toHaveBeenCalled();
expect(commands.setLink).toHaveBeenCalledWith({
href: 'https://example',
- 'data-canonical-src': 'https://example',
+ canonicalSrc: 'https://example',
});
expect(commands.run).toHaveBeenCalled();
+
+ expect(wrapper.emitted().execute[0]).toEqual([{ contentType: 'link' }]);
});
});
diff --git a/spec/frontend/content_editor/components/top_toolbar_spec.js b/spec/frontend/content_editor/components/top_toolbar_spec.js
index a5d9a626a04..5411793cd5e 100644
--- a/spec/frontend/content_editor/components/top_toolbar_spec.js
+++ b/spec/frontend/content_editor/components/top_toolbar_spec.js
@@ -51,6 +51,7 @@ describe('content_editor/components/top_toolbar', () => {
${'code-block'} | ${{ contentType: 'codeBlock', iconName: 'doc-code', label: 'Insert a code block', editorCommand: 'toggleCodeBlock' }}
${'text-styles'} | ${{}}
${'link'} | ${{}}
+ ${'image'} | ${{}}
`('given a $testId toolbar control', ({ testId, controlProps }) => {
beforeEach(() => {
buildWrapper();
diff --git a/spec/frontend/import_entities/components/group_dropdown_spec.js b/spec/frontend/import_entities/components/group_dropdown_spec.js
new file mode 100644
index 00000000000..f7aa0e889ea
--- /dev/null
+++ b/spec/frontend/import_entities/components/group_dropdown_spec.js
@@ -0,0 +1,44 @@
+import { GlSearchBoxByType, GlDropdown } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import GroupDropdown from '~/import_entities/components/group_dropdown.vue';
+
+describe('Import entities group dropdown component', () => {
+ let wrapper;
+ let namespacesTracker;
+
+ const createComponent = (propsData) => {
+ namespacesTracker = jest.fn();
+
+ wrapper = shallowMount(GroupDropdown, {
+ scopedSlots: {
+ default: namespacesTracker,
+ },
+ stubs: { GlDropdown },
+ propsData,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('passes namespaces from props to default slot', () => {
+ const namespaces = ['ns1', 'ns2'];
+ createComponent({ namespaces });
+
+ expect(namespacesTracker).toHaveBeenCalledWith({ namespaces });
+ });
+
+ it('filters namespaces based on user input', async () => {
+ const namespaces = ['match1', 'some unrelated', 'match2'];
+ createComponent({ namespaces });
+
+ namespacesTracker.mockReset();
+ wrapper.find(GlSearchBoxByType).vm.$emit('input', 'match');
+
+ await nextTick();
+
+ expect(namespacesTracker).toHaveBeenCalledWith({ namespaces: ['match1', 'match2'] });
+ });
+});
diff --git a/spec/frontend/import_entities/import_groups/components/import_table_row_spec.js b/spec/frontend/import_entities/import_groups/components/import_table_row_spec.js
index aa6a40cad18..654a8fd00d3 100644
--- a/spec/frontend/import_entities/import_groups/components/import_table_row_spec.js
+++ b/spec/frontend/import_entities/import_groups/components/import_table_row_spec.js
@@ -1,8 +1,9 @@
-import { GlButton, GlDropdown, GlDropdownItem, GlLink, GlFormInput } from '@gitlab/ui';
+import { GlButton, GlDropdownItem, GlLink, GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
+import ImportGroupDropdown from '~/import_entities/components/group_dropdown.vue';
import { STATUSES } from '~/import_entities/constants';
import ImportTableRow from '~/import_entities/import_groups/components/import_table_row.vue';
import addValidationErrorMutation from '~/import_entities/import_groups/graphql/mutations/add_validation_error.mutation.graphql';
@@ -41,7 +42,7 @@ describe('import table row', () => {
};
const findImportButton = () => findByText(GlButton, 'Import');
const findNameInput = () => wrapper.find(GlFormInput);
- const findNamespaceDropdown = () => wrapper.find(GlDropdown);
+ const findNamespaceDropdown = () => wrapper.find(ImportGroupDropdown);
const createComponent = (props) => {
apolloProvider = createMockApollo([
@@ -65,6 +66,7 @@ describe('import table row', () => {
wrapper = shallowMount(ImportTableRow, {
apolloProvider,
+ stubs: { ImportGroupDropdown },
propsData: {
availableNamespaces: availableNamespacesFixture,
groupPathRegex: /.*/,
diff --git a/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js b/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
index d9f4168f1a5..0e748baa313 100644
--- a/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
@@ -11,6 +11,8 @@ import state from '~/import_entities/import_projects/store/state';
describe('ImportProjectsTable', () => {
let wrapper;
+ const USER_NAMESPACE = 'root';
+
const findFilterField = () =>
wrapper
.findAllComponents(GlFormInput)
@@ -48,7 +50,7 @@ describe('ImportProjectsTable', () => {
localVue.use(Vuex);
const store = new Vuex.Store({
- state: { ...state(), ...initialState },
+ state: { ...state(), defaultTargetNamespace: USER_NAMESPACE, ...initialState },
getters: {
...getters,
...customGetters,
diff --git a/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
index e15389be53a..72640f3d601 100644
--- a/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
@@ -1,11 +1,11 @@
-import { GlBadge, GlButton } from '@gitlab/ui';
+import { GlBadge, GlButton, GlDropdown } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex';
import { STATUSES } from '~/import_entities//constants';
+import ImportGroupDropdown from '~/import_entities/components/group_dropdown.vue';
import ImportStatus from '~/import_entities/components/import_status.vue';
import ProviderRepoTableRow from '~/import_entities/import_projects/components/provider_repo_table_row.vue';
-import Select2Select from '~/vue_shared/components/select2_select.vue';
describe('ProviderRepoTableRow', () => {
let wrapper;
@@ -16,10 +16,8 @@ describe('ProviderRepoTableRow', () => {
newName: 'newName',
};
- const availableNamespaces = [
- { text: 'Groups', children: [{ id: 'test', text: 'test' }] },
- { text: 'Users', children: [{ id: 'root', text: 'root' }] },
- ];
+ const availableNamespaces = ['test'];
+ const userNamespace = 'root';
function initStore(initialState) {
const store = new Vuex.Store({
@@ -48,7 +46,7 @@ describe('ProviderRepoTableRow', () => {
wrapper = shallowMount(ProviderRepoTableRow, {
localVue,
store,
- propsData: { availableNamespaces, ...props },
+ propsData: { availableNamespaces, userNamespace, ...props },
});
}
@@ -81,9 +79,8 @@ describe('ProviderRepoTableRow', () => {
expect(wrapper.find(ImportStatus).props().status).toBe(STATUSES.NONE);
});
- it('renders a select2 namespace select', () => {
- expect(wrapper.find(Select2Select).exists()).toBe(true);
- expect(wrapper.find(Select2Select).props().options.data).toBe(availableNamespaces);
+ it('renders a group namespace select', () => {
+ expect(wrapper.find(ImportGroupDropdown).props().namespaces).toBe(availableNamespaces);
});
it('renders import button', () => {
@@ -133,7 +130,7 @@ describe('ProviderRepoTableRow', () => {
});
it('does not renders a namespace select', () => {
- expect(wrapper.find(Select2Select).exists()).toBe(false);
+ expect(wrapper.find(GlDropdown).exists()).toBe(false);
});
it('does not render import button', () => {
diff --git a/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js b/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js
index 7dd8a77d055..b797a664b75 100644
--- a/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js
+++ b/spec/frontend/pipeline_editor/components/editor/ci_config_merged_preview_spec.js
@@ -1,10 +1,12 @@
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { EDITOR_READY_EVENT } from '~/editor/constants';
import CiConfigMergedPreview from '~/pipeline_editor/components/editor/ci_config_merged_preview.vue';
import { mockLintResponse, mockCiConfigPath } from '../../mock_data';
+const DEFAULT_BRANCH = 'main';
+
describe('Text editor component', () => {
let wrapper;
@@ -16,7 +18,7 @@ describe('Text editor component', () => {
},
};
- const createComponent = ({ props = {} } = {}) => {
+ const createComponent = ({ props = {}, currentBranch = DEFAULT_BRANCH } = {}) => {
wrapper = shallowMount(CiConfigMergedPreview, {
propsData: {
ciConfigData: mockLintResponse,
@@ -24,20 +26,45 @@ describe('Text editor component', () => {
},
provide: {
ciConfigPath: mockCiConfigPath,
+ defaultBranch: DEFAULT_BRANCH,
},
stubs: {
SourceEditor: MockSourceEditor,
},
+ data() {
+ return {
+ currentBranch,
+ };
+ },
});
};
const findIcon = () => wrapper.findComponent(GlIcon);
+ const findAlert = () => wrapper.findComponent(GlAlert);
const findEditor = () => wrapper.findComponent(MockSourceEditor);
afterEach(() => {
wrapper.destroy();
});
+ // This is testing a temporary feature.
+ // It may be slightly hacky code that doesn't follow best practice.
+ // See the related MR for more information.
+ // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65972#note_626095644
+ describe('on a non-default branch', () => {
+ beforeEach(() => {
+ createComponent({ currentBranch: 'feature' });
+ });
+
+ it('does not load the editor', () => {
+ expect(findEditor().exists()).toBe(false);
+ });
+
+ it('renders an informational alert', () => {
+ expect(findAlert().exists()).toBe(true);
+ });
+ });
+
describe('when status is valid', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js
index 96c19776513..1d1ec58100f 100644
--- a/spec/frontend/repository/components/tree_content_spec.js
+++ b/spec/frontend/repository/components/tree_content_spec.js
@@ -19,6 +19,11 @@ function factory(path, data = () => ({})) {
mocks: {
$apollo,
},
+ provide: {
+ glFeatures: {
+ increasePageSizeExponentially: true,
+ },
+ },
});
}
diff --git a/spec/lib/api/entities/job_request/image_spec.rb b/spec/lib/api/entities/ci/job_request/image_spec.rb
index f13eab6a752..55aade03129 100644
--- a/spec/lib/api/entities/job_request/image_spec.rb
+++ b/spec/lib/api/entities/ci/job_request/image_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe API::Entities::JobRequest::Image do
+RSpec.describe API::Entities::Ci::JobRequest::Image do
let(:ports) { [{ number: 80, protocol: 'http', name: 'name' }]}
let(:image) { double(name: 'image_name', entrypoint: ['foo'], ports: ports)}
let(:entity) { described_class.new(image) }
diff --git a/spec/lib/api/entities/job_request/port_spec.rb b/spec/lib/api/entities/ci/job_request/port_spec.rb
index 4820c4a691b..8e0d2cabcfc 100644
--- a/spec/lib/api/entities/job_request/port_spec.rb
+++ b/spec/lib/api/entities/ci/job_request/port_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe ::API::Entities::JobRequest::Port do
+RSpec.describe ::API::Entities::Ci::JobRequest::Port do
let(:port) { double(number: 80, protocol: 'http', name: 'name')}
let(:entity) { described_class.new(port) }
diff --git a/spec/models/integrations/datadog_spec.rb b/spec/models/integrations/datadog_spec.rb
index aa4d3dd47f3..00b7a19ab05 100644
--- a/spec/models/integrations/datadog_spec.rb
+++ b/spec/models/integrations/datadog_spec.rb
@@ -10,13 +10,13 @@ RSpec.describe Integrations::Datadog do
let(:active) { true }
let(:dd_site) { 'datadoghq.com' }
- let(:default_url) { 'https://webhooks-http-intake.logs.datadoghq.com/v1/input/' }
+ let(:default_url) { 'https://webhooks-http-intake.logs.datadoghq.com/api/v2/webhook' }
let(:api_url) { '' }
let(:api_key) { SecureRandom.hex(32) }
let(:dd_env) { 'ci' }
let(:dd_service) { 'awesome-gitlab' }
- let(:expected_hook_url) { default_url + api_key + "?env=#{dd_env}&service=#{dd_service}" }
+ let(:expected_hook_url) { default_url + "?dd-api-key=#{api_key}&env=#{dd_env}&service=#{dd_service}" }
let(:instance) do
described_class.new(
@@ -65,7 +65,7 @@ RSpec.describe Integrations::Datadog do
context 'with custom api_url' do
let(:dd_site) { '' }
- let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/v1/input/' }
+ let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/api/v2/webhook' }
it { is_expected.not_to validate_presence_of(:datadog_site) }
it { is_expected.to validate_presence_of(:api_url) }
@@ -107,9 +107,9 @@ RSpec.describe Integrations::Datadog do
end
context 'with custom URL' do
- let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/v1/input/' }
+ let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/api/v2/webhook' }
- it { is_expected.to eq(api_url + api_key + "?env=#{dd_env}&service=#{dd_service}") }
+ it { is_expected.to eq(api_url + "?dd-api-key=#{api_key}&env=#{dd_env}&service=#{dd_service}") }
context 'blank' do
let(:api_url) { '' }
@@ -122,7 +122,7 @@ RSpec.describe Integrations::Datadog do
let(:dd_service) { '' }
let(:dd_env) { '' }
- it { is_expected.to eq(default_url + api_key) }
+ it { is_expected.to eq(default_url + "?dd-api-key=#{api_key}") }
end
end
diff --git a/spec/rubocop/cop/database/multiple_databases_spec.rb b/spec/rubocop/cop/database/multiple_databases_spec.rb
new file mode 100644
index 00000000000..16b916d61db
--- /dev/null
+++ b/spec/rubocop/cop/database/multiple_databases_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require_relative '../../../../rubocop/cop/database/multiple_databases'
+
+RSpec.describe RuboCop::Cop::Database::MultipleDatabases do
+ subject(:cop) { described_class.new }
+
+ it 'flags the use of ActiveRecord::Base.connection' do
+ expect_offense(<<~SOURCE)
+ ActiveRecord::Base.connection.inspect
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use methods from ActiveRecord::Base, [...]
+ SOURCE
+ end
+end
diff --git a/spec/services/service_ping/submit_service_ping_service_spec.rb b/spec/services/service_ping/submit_service_ping_service_spec.rb
index ded6194a586..8a3065e6bc6 100644
--- a/spec/services/service_ping/submit_service_ping_service_spec.rb
+++ b/spec/services/service_ping/submit_service_ping_service_spec.rb
@@ -245,11 +245,63 @@ RSpec.describe ServicePing::SubmitService do
context 'and usage data is nil' do
before do
+ allow(ServicePing::BuildPayloadService).to receive(:execute).and_return(nil)
allow(Gitlab::UsageData).to receive(:data).and_return(nil)
end
it_behaves_like 'does not send a blank usage ping payload'
end
+
+ context 'if payload service fails' do
+ before do
+ stub_response(body: with_dev_ops_score_params)
+ allow(ServicePing::BuildPayloadService).to receive(:execute).and_raise(described_class::SubmissionError, 'SubmissionError')
+ end
+
+ it 'calls UsageData .data method' do
+ usage_data = build_usage_data
+
+ expect(Gitlab::UsageData).to receive(:data).and_return(usage_data)
+
+ subject.execute
+ end
+ end
+
+ context 'calls BuildPayloadService first' do
+ before do
+ stub_response(body: with_dev_ops_score_params)
+ end
+
+ it 'returns usage data' do
+ usage_data = build_usage_data
+
+ expect_next_instance_of(ServicePing::BuildPayloadService) do |service|
+ expect(service).to receive(:execute).and_return(usage_data)
+ end
+
+ subject.execute
+ end
+ end
+
+ context 'if version app response fails' do
+ before do
+ stub_response(body: with_dev_ops_score_params, status: 404)
+
+ usage_data = build_usage_data
+ allow_next_instance_of(ServicePing::BuildPayloadService) do |service|
+ allow(service).to receive(:execute).and_return(usage_data)
+ end
+ end
+
+ it 'calls UsageData .data method' do
+ usage_data = build_usage_data
+
+ expect(Gitlab::UsageData).to receive(:data).and_return(usage_data)
+
+ # SubmissionError is raised as a result of 404 in response from HTTP Request
+ expect { subject.execute }.to raise_error(described_class::SubmissionError)
+ end
+ end
end
def stub_response(body:, status: 201)
@@ -260,4 +312,8 @@ RSpec.describe ServicePing::SubmitService do
status: status
)
end
+
+ def build_usage_data
+ { uuid: 'uuid', recorded_at: Time.current }
+ end
end
diff --git a/spec/tooling/danger/project_helper_spec.rb b/spec/tooling/danger/project_helper_spec.rb
index 9c4e9407f90..36142a1676d 100644
--- a/spec/tooling/danger/project_helper_spec.rb
+++ b/spec/tooling/danger/project_helper_spec.rb
@@ -221,7 +221,7 @@ RSpec.describe Tooling::Danger::ProjectHelper do
describe '.local_warning_message' do
it 'returns an informational message with rules that can run' do
- expect(described_class.local_warning_message).to eq('==> Only the following Danger rules can be run locally: changelog, database, datateam, documentation, duplicate_yarn_dependencies, eslint, gitaly, karma, pajamas, pipeline, prettier, product_intelligence, utility_css')
+ expect(described_class.local_warning_message).to eq('==> Only the following Danger rules can be run locally: changelog, database, documentation, duplicate_yarn_dependencies, eslint, gitaly, karma, pajamas, pipeline, prettier, product_intelligence, utility_css')
end
end
diff --git a/tooling/danger/project_helper.rb b/tooling/danger/project_helper.rb
index 45a53ac2922..c698b2b8f13 100644
--- a/tooling/danger/project_helper.rb
+++ b/tooling/danger/project_helper.rb
@@ -6,7 +6,6 @@ module Tooling
LOCAL_RULES ||= %w[
changelog
database
- datateam
documentation
duplicate_yarn_dependencies
eslint
@@ -22,6 +21,7 @@ module Tooling
CI_ONLY_RULES ||= %w[
ce_ee_vue_templates
ci_templates
+ datateam
metadata
feature_flag
roulette