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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml3
-rw-r--r--.rubocop_todo/gitlab/namespaced_class.yml2
-rw-r--r--.rubocop_todo/layout/argument_alignment.yml2
-rw-r--r--.rubocop_todo/layout/line_length.yml4
-rw-r--r--.rubocop_todo/lint/mixed_regexp_capture_types.yml1
-rw-r--r--.rubocop_todo/rails/file_path.yml2
-rw-r--r--.rubocop_todo/rails/transaction_exit_statement.yml3
-rw-r--r--.rubocop_todo/rspec/context_wording.yml4
-rw-r--r--.rubocop_todo/rspec/expect_in_hook.yml2
-rw-r--r--.rubocop_todo/rspec/missing_feature_category.yml8
-rw-r--r--.rubocop_todo/style/numbered_parameters.yml1
-rw-r--r--.rubocop_todo/style/percent_literal_delimiters.yml2
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/graphql/resolvers.js52
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_details.vue45
-rw-r--r--app/assets/javascripts/ci/runner/constants.js2
-rw-r--r--app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue2
-rw-r--r--app/controllers/projects/cycle_analytics_controller.rb1
-rw-r--r--app/models/ci/catalog/listing.rb17
-rw-r--r--app/models/ci/catalog/resource.rb3
-rw-r--r--app/models/integration.rb2
-rw-r--r--app/models/integrations/clickup.rb39
-rw-r--r--app/models/integrations/jira.rb33
-rw-r--r--app/models/issue.rb16
-rw-r--r--app/models/project.rb37
-rw-r--r--app/services/ci/delete_unit_tests_service.rb4
-rw-r--r--app/services/concerns/update_repository_storage_methods.rb8
-rw-r--r--app/services/merge_requests/build_service.rb2
-rw-r--r--app/services/projects/create_service.rb2
-rw-r--r--app/services/work_items/delete_task_service.rb2
-rw-r--r--app/views/projects/network/show.html.haml9
-rw-r--r--config/feature_flags/development/record_issue_and_mr_assignee_events.yml8
-rw-r--r--config/feature_flags/development/vsa_group_and_project_parity.yml8
-rw-r--r--config/initializers/00_deprecations.rb4
-rw-r--r--config/metrics/counts_all/20230515153810_groups_clickup_active.yml21
-rw-r--r--config/metrics/counts_all/20230515153826_groups_inheriting_clickup_active.yml21
-rw-r--r--config/metrics/counts_all/20230515153827_instances_clickup_active.yml21
-rw-r--r--config/metrics/counts_all/20230515153829_projects_clickup_active.yml21
-rw-r--r--config/metrics/counts_all/20230515153834_projects_inheriting_clickup_active.yml21
-rw-r--r--data/deprecations/15-9-rails-error-tracking.yml16
-rw-r--r--db/docs/batched_background_migrations/backfill_resource_link_events.yml6
-rw-r--r--db/fixtures/development/24_forks.rb2
-rw-r--r--db/post_migrate/20230426085615_queue_backfill_resource_link_events.rb43
-rw-r--r--db/schema_migrations/202304260856151
-rw-r--r--doc/api/graphql/reference/index.md19
-rw-r--r--doc/api/integrations.md37
-rw-r--r--doc/development/ai_features.md1
-rw-r--r--doc/integration/external-issue-tracker.md1
-rw-r--r--doc/raketasks/backup_restore.md6
-rw-r--r--doc/update/deprecations.md25
-rw-r--r--doc/user/project/integrations/clickup.md53
-rw-r--r--doc/user/project/integrations/index.md1
-rw-r--r--doc/user/project/protected_branches.md2
-rw-r--r--doc/user/workspace/index.md38
-rw-r--r--lib/api/helpers/integrations_helpers.rb15
-rw-r--r--lib/gitlab/background_migration/backfill_resource_link_events.rb71
-rw-r--r--lib/gitlab/ci/templates/Jobs/CF-Provision.gitlab-ci.yml2
-rw-r--r--lib/gitlab/database/background_migration/batched_job.rb2
-rw-r--r--lib/gitlab/resource_events/assignment_event_recorder.rb2
-rw-r--r--lib/sidebars/projects/menus/monitor_menu.rb18
-rw-r--r--lib/sidebars/projects/super_sidebar_menus/monitor_menu.rb1
-rw-r--r--locale/gitlab.pot9
-rwxr-xr-xscripts/verify-tff-mapping11
-rw-r--r--spec/factories/integrations.rb6
-rw-r--r--spec/features/monitor_sidebar_link_spec.rb6
-rw-r--r--spec/features/projects/integrations/user_activates_issue_tracker_spec.rb1
-rw-r--r--spec/features/projects/integrations/user_activates_jira_spec.rb2
-rw-r--r--spec/features/projects/navbar_spec.rb1
-rw-r--r--spec/features/projects/settings/monitor_settings_spec.rb4
-rw-r--r--spec/features/projects/user_uses_shortcuts_spec.rb11
-rw-r--r--spec/frontend/ci/ci_lint/mock_data.js23
-rw-r--r--spec/frontend/ci/pipeline_editor/components/validate/ci_validate_spec.js171
-rw-r--r--spec/frontend/ci/pipeline_editor/mock_data.js8
-rw-r--r--spec/frontend/ci/runner/components/runner_details_spec.js29
-rw-r--r--spec/graphql/types/projects/service_type_enum_spec.rb1
-rw-r--r--spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb38
-rw-r--r--spec/lib/gitlab/background_migration/backfill_resource_link_events_spec.rb197
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/jira_import_spec.rb2
-rw-r--r--spec/lib/gitlab/resource_events/assignment_event_recorder_spec.rb12
-rw-r--r--spec/lib/gitlab/task_helpers_spec.rb (renamed from spec/tasks/gitlab/task_helpers_spec.rb)0
-rw-r--r--spec/lib/sidebars/projects/menus/monitor_menu_spec.rb10
-rw-r--r--spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb1
-rw-r--r--spec/migrations/20230426085615_queue_backfill_resource_link_events_spec.rb89
-rw-r--r--spec/models/ci/catalog/listing_spec.rb31
-rw-r--r--spec/models/ci/catalog/resource_spec.rb30
-rw-r--r--spec/models/integrations/clickup_spec.rb48
-rw-r--r--spec/models/integrations/jira_spec.rb8
-rw-r--r--spec/models/issue_spec.rb66
-rw-r--r--spec/models/project_spec.rb23
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb1
-rw-r--r--spec/tasks/cache_rake_spec.rb (renamed from spec/tasks/cache/clear/redis_spec.rb)0
-rw-r--r--spec/tasks/config_lint_rake_spec.rb (renamed from spec/tasks/config_lint_spec.rb)0
-rw-r--r--spec/tasks/gitlab/db/decomposition/connection_status_rake_spec.rb (renamed from spec/tasks/gitlab/db/decomposition/connection_status_spec.rb)0
-rw-r--r--spec/tasks/gitlab/generate_sample_prometheus_data_rake_spec.rb (renamed from spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb)0
-rw-r--r--spec/tasks/gitlab/metrics_exporter_rake_spec.rb (renamed from spec/tasks/gitlab/metrics_exporter_task_spec.rb)0
-rw-r--r--spec/tasks/tokens_rake_spec.rb (renamed from spec/tasks/tokens_spec.rb)0
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb22
-rw-r--r--tests.yml4
99 files changed, 1314 insertions, 352 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index c6d2d48db9b..1630d59c17a 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1017,3 +1017,6 @@ RSpec/FactoryBot/LocalStaticAssignment:
Include:
- spec/factories/**/*.rb
- ee/spec/factories/**/*.rb
+
+Rails/TransactionExitStatement:
+ Enabled: true
diff --git a/.rubocop_todo/gitlab/namespaced_class.yml b/.rubocop_todo/gitlab/namespaced_class.yml
index 3a81130a47c..98cedc10bbe 100644
--- a/.rubocop_todo/gitlab/namespaced_class.yml
+++ b/.rubocop_todo/gitlab/namespaced_class.yml
@@ -1274,5 +1274,5 @@ Gitlab/NamespacedClass:
- 'spec/support/helpers/require_migration.rb'
- 'spec/support/models/merge_request_without_merge_request_diff.rb'
- 'spec/support/renameable_upload.rb'
- - 'spec/tasks/gitlab/task_helpers_spec.rb'
+ - 'spec/lib/gitlab/task_helpers_spec.rb'
- 'spec/uploaders/object_storage_spec.rb'
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml
index bdd3a15489d..b74cb450320 100644
--- a/.rubocop_todo/layout/argument_alignment.yml
+++ b/.rubocop_todo/layout/argument_alignment.yml
@@ -2378,7 +2378,7 @@ Layout/ArgumentAlignment:
- 'spec/support/shared_examples/integrations/integration_settings_form.rb'
- 'spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb'
- 'spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb'
- - 'spec/tasks/cache/clear/redis_spec.rb'
+ - 'spec/tasks/cache_rake_spec.rb'
- 'spec/tasks/gitlab/cleanup_rake_spec.rb'
- 'spec/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences_rake_spec.rb'
- 'spec/tasks/gitlab/db/truncate_legacy_tables_rake_spec.rb'
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index 3e29279693e..221b5cc0f1e 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -5195,7 +5195,7 @@ Layout/LineLength:
- 'spec/tasks/gitlab/db/validate_config_rake_spec.rb'
- 'spec/tasks/gitlab/db_rake_spec.rb'
- 'spec/tasks/gitlab/external_diffs_rake_spec.rb'
- - 'spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb'
+ - 'spec/tasks/gitlab/generate_sample_prometheus_data_rake_spec.rb'
- 'spec/tasks/gitlab/gitaly_rake_spec.rb'
- 'spec/tasks/gitlab/ldap_rake_spec.rb'
- 'spec/tasks/gitlab/lfs/check_rake_spec.rb'
@@ -5205,7 +5205,7 @@ Layout/LineLength:
- 'spec/tasks/gitlab/smtp_rake_spec.rb'
- 'spec/tasks/gitlab/snippets_rake_spec.rb'
- 'spec/tasks/gitlab/storage_rake_spec.rb'
- - 'spec/tasks/gitlab/task_helpers_spec.rb'
+ - 'spec/lib/gitlab/task_helpers_spec.rb'
- 'spec/tasks/gitlab/terraform/migrate_rake_spec.rb'
- 'spec/tasks/gitlab/uploads/check_rake_spec.rb'
- 'spec/tasks/gitlab/workhorse_rake_spec.rb'
diff --git a/.rubocop_todo/lint/mixed_regexp_capture_types.yml b/.rubocop_todo/lint/mixed_regexp_capture_types.yml
index 70f3773eb31..b2af57158de 100644
--- a/.rubocop_todo/lint/mixed_regexp_capture_types.yml
+++ b/.rubocop_todo/lint/mixed_regexp_capture_types.yml
@@ -2,6 +2,7 @@
Lint/MixedRegexpCaptureTypes:
Exclude:
- 'app/models/alert_management/alert.rb'
+ - 'app/models/integrations/clickup.rb'
- 'app/models/integrations/ewm.rb'
- 'app/uploaders/file_uploader.rb'
- 'ee/lib/gitlab/code_owners/reference_extractor.rb'
diff --git a/.rubocop_todo/rails/file_path.yml b/.rubocop_todo/rails/file_path.yml
index e536c22e518..0214b471e59 100644
--- a/.rubocop_todo/rails/file_path.yml
+++ b/.rubocop_todo/rails/file_path.yml
@@ -126,5 +126,5 @@ Rails/FilePath:
- 'spec/support/shared_examples/models/application_setting_shared_examples.rb'
- 'spec/support/shared_examples/models/wiki_shared_examples.rb'
- 'spec/tasks/gitlab/db_rake_spec.rb'
- - 'spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb'
+ - 'spec/tasks/gitlab/generate_sample_prometheus_data_rake_spec.rb'
- 'spec/tasks/gitlab/usage_data_rake_spec.rb'
diff --git a/.rubocop_todo/rails/transaction_exit_statement.yml b/.rubocop_todo/rails/transaction_exit_statement.yml
new file mode 100644
index 00000000000..695e696f776
--- /dev/null
+++ b/.rubocop_todo/rails/transaction_exit_statement.yml
@@ -0,0 +1,3 @@
+---
+Rails/TransactionExitStatement:
+ Details: grace period
diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml
index a2886ff9679..3355be60b49 100644
--- a/.rubocop_todo/rspec/context_wording.yml
+++ b/.rubocop_todo/rspec/context_wording.yml
@@ -3118,7 +3118,7 @@ RSpec/ContextWording:
- 'spec/support_specs/helpers/migrations_helpers_spec.rb'
- 'spec/support_specs/helpers/stub_feature_flags_spec.rb'
- 'spec/support_specs/helpers/stub_method_calls_spec.rb'
- - 'spec/tasks/cache/clear/redis_spec.rb'
+ - 'spec/tasks/cache_rake_spec.rb'
- 'spec/tasks/dev_rake_spec.rb'
- 'spec/tasks/gitlab/cleanup_rake_spec.rb'
- 'spec/tasks/gitlab/db/validate_config_rake_spec.rb'
@@ -3128,7 +3128,7 @@ RSpec/ContextWording:
- 'spec/tasks/gitlab/lfs/migrate_rake_spec.rb'
- 'spec/tasks/gitlab/packages/migrate_rake_spec.rb'
- 'spec/tasks/gitlab/storage_rake_spec.rb'
- - 'spec/tasks/gitlab/task_helpers_spec.rb'
+ - 'spec/lib/gitlab/task_helpers_spec.rb'
- 'spec/tasks/gitlab/terraform/migrate_rake_spec.rb'
- 'spec/tasks/gitlab/workhorse_rake_spec.rb'
- 'spec/tooling/danger/project_helper_spec.rb'
diff --git a/.rubocop_todo/rspec/expect_in_hook.yml b/.rubocop_todo/rspec/expect_in_hook.yml
index 4003908c97e..ceeffd1f098 100644
--- a/.rubocop_todo/rspec/expect_in_hook.yml
+++ b/.rubocop_todo/rspec/expect_in_hook.yml
@@ -469,7 +469,7 @@ RSpec/ExpectInHook:
- 'spec/tasks/gitlab/cleanup_rake_spec.rb'
- 'spec/tasks/gitlab/gitaly_rake_spec.rb'
- 'spec/tasks/gitlab/praefect_rake_spec.rb'
- - 'spec/tasks/gitlab/task_helpers_spec.rb'
+ - 'spec/lib/gitlab/task_helpers_spec.rb'
- 'spec/tooling/danger/feature_flag_spec.rb'
- 'spec/tooling/rspec_flaky/listener_spec.rb'
- 'spec/uploaders/file_mover_spec.rb'
diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml
index 0a77e2d8b15..f4ecd85bd47 100644
--- a/.rubocop_todo/rspec/missing_feature_category.yml
+++ b/.rubocop_todo/rspec/missing_feature_category.yml
@@ -5607,7 +5607,7 @@ RSpec/MissingFeatureCategory:
- 'spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb'
- 'spec/support_specs/time_travel_spec.rb'
- 'spec/tasks/admin_mode_spec.rb'
- - 'spec/tasks/config_lint_spec.rb'
+ - 'spec/tasks/config_lint_rake_spec.rb'
- 'spec/tasks/dev_rake_spec.rb'
- 'spec/tasks/gitlab/artifacts/check_rake_spec.rb'
- 'spec/tasks/gitlab/artifacts/migrate_rake_spec.rb'
@@ -5615,7 +5615,7 @@ RSpec/MissingFeatureCategory:
- 'spec/tasks/gitlab/container_registry_rake_spec.rb'
- 'spec/tasks/gitlab/dependency_proxy/migrate_rake_spec.rb'
- 'spec/tasks/gitlab/external_diffs_rake_spec.rb'
- - 'spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb'
+ - 'spec/tasks/gitlab/generate_sample_prometheus_data_rake_spec.rb'
- 'spec/tasks/gitlab/git_rake_spec.rb'
- 'spec/tasks/gitlab/gitaly_rake_spec.rb'
- 'spec/tasks/gitlab/ldap_rake_spec.rb'
@@ -5631,7 +5631,7 @@ RSpec/MissingFeatureCategory:
- 'spec/tasks/gitlab/sidekiq_rake_spec.rb'
- 'spec/tasks/gitlab/smtp_rake_spec.rb'
- 'spec/tasks/gitlab/snippets_rake_spec.rb'
- - 'spec/tasks/gitlab/task_helpers_spec.rb'
+ - 'spec/lib/gitlab/task_helpers_spec.rb'
- 'spec/tasks/gitlab/terraform/migrate_rake_spec.rb'
- 'spec/tasks/gitlab/uploads/check_rake_spec.rb'
- 'spec/tasks/gitlab/uploads/migrate_rake_spec.rb'
@@ -5640,7 +5640,7 @@ RSpec/MissingFeatureCategory:
- 'spec/tasks/gitlab/x509/update_rake_spec.rb'
- 'spec/tasks/migrate/schema_check_rake_spec.rb'
- 'spec/tasks/rubocop_rake_spec.rb'
- - 'spec/tasks/tokens_spec.rb'
+ - 'spec/tasks/tokens_rake_spec.rb'
- 'spec/tooling/danger/config_files_spec.rb'
- 'spec/tooling/danger/customer_success_spec.rb'
- 'spec/tooling/danger/datateam_spec.rb'
diff --git a/.rubocop_todo/style/numbered_parameters.yml b/.rubocop_todo/style/numbered_parameters.yml
index fc08515bc2f..d73fab2a86a 100644
--- a/.rubocop_todo/style/numbered_parameters.yml
+++ b/.rubocop_todo/style/numbered_parameters.yml
@@ -15,6 +15,7 @@ Style/NumberedParameters:
- 'app/models/concerns/integrations/reset_secret_fields.rb'
- 'app/models/hooks/web_hook.rb'
- 'app/models/integration.rb'
+ - 'app/models/integrations/clickup.rb'
- 'app/models/integrations/datadog.rb'
- 'app/models/integrations/youtrack.rb'
- 'app/models/project.rb'
diff --git a/.rubocop_todo/style/percent_literal_delimiters.yml b/.rubocop_todo/style/percent_literal_delimiters.yml
index 496f05c7cab..9ad2fb06ae3 100644
--- a/.rubocop_todo/style/percent_literal_delimiters.yml
+++ b/.rubocop_todo/style/percent_literal_delimiters.yml
@@ -1081,7 +1081,7 @@ Style/PercentLiteralDelimiters:
- 'spec/support_specs/helpers/active_record/query_recorder_spec.rb'
- 'spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb'
- 'spec/tasks/gitlab/db_rake_spec.rb'
- - 'spec/tasks/gitlab/task_helpers_spec.rb'
+ - 'spec/lib/gitlab/task_helpers_spec.rb'
- 'spec/tooling/danger/customer_success_spec.rb'
- 'spec/tooling/danger/datateam_spec.rb'
- 'spec/tooling/danger/sidekiq_queues_spec.rb'
diff --git a/app/assets/javascripts/ci/pipeline_editor/graphql/resolvers.js b/app/assets/javascripts/ci/pipeline_editor/graphql/resolvers.js
index fa1c70c1994..ed5be66d07a 100644
--- a/app/assets/javascripts/ci/pipeline_editor/graphql/resolvers.js
+++ b/app/assets/javascripts/ci/pipeline_editor/graphql/resolvers.js
@@ -7,30 +7,36 @@ import getPipelineEtag from './queries/client/pipeline_etag.query.graphql';
export const resolvers = {
Mutation: {
lintCI: (_, { endpoint, content, dry_run }) => {
- return axios.post(endpoint, { content, dry_run }).then(({ data }) => ({
- valid: data.valid,
- errors: data.errors,
- warnings: data.warnings,
- jobs: data.jobs.map((job) => {
- const only = job.only ? { refs: job.only.refs, __typename: 'CiLintJobOnlyPolicy' } : null;
+ return axios.post(endpoint, { content, dry_run }).then(({ data }) => {
+ const { errors, warnings, valid, jobs } = data;
- return {
- name: job.name,
- stage: job.stage,
- beforeScript: job.before_script,
- script: job.script,
- afterScript: job.after_script,
- tags: job.tag_list,
- environment: job.environment,
- when: job.when,
- allowFailure: job.allow_failure,
- only,
- except: job.except,
- __typename: 'CiLintJob',
- };
- }),
- __typename: 'CiLintContent',
- }));
+ return {
+ valid,
+ errors,
+ warnings,
+ jobs: jobs.map((job) => {
+ const only = job.only
+ ? { refs: job.only.refs, __typename: 'CiLintJobOnlyPolicy' }
+ : null;
+
+ return {
+ name: job.name,
+ stage: job.stage,
+ beforeScript: job.before_script,
+ script: job.script,
+ afterScript: job.after_script,
+ tags: job.tag_list,
+ environment: job.environment,
+ when: job.when,
+ allowFailure: job.allow_failure,
+ only,
+ except: job.except,
+ __typename: 'CiLintJob',
+ };
+ }),
+ __typename: 'CiLintContent',
+ };
+ });
},
updateAppStatus: (_, { appStatus }, { cache }) => {
cache.writeQuery({
diff --git a/app/assets/javascripts/ci/runner/components/runner_details.vue b/app/assets/javascripts/ci/runner/components/runner_details.vue
index 6eba8f2e49f..0608d63897b 100644
--- a/app/assets/javascripts/ci/runner/components/runner_details.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_details.vue
@@ -1,11 +1,16 @@
<script>
-import { GlIntersperse, GlLink } from '@gitlab/ui';
+import { GlIcon, GlIntersperse, GlLink, GlSprintf } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
-import { s__ } from '~/locale';
+import { s__, formatNumber } from '~/locale';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
-import { ACCESS_LEVEL_REF_PROTECTED, GROUP_TYPE, PROJECT_TYPE } from '../constants';
+import {
+ ACCESS_LEVEL_REF_PROTECTED,
+ GROUP_TYPE,
+ PROJECT_TYPE,
+ RUNNER_MANAGERS_HELP_URL,
+} from '../constants';
import RunnerDetail from './runner_detail.vue';
import RunnerGroups from './runner_groups.vue';
import RunnerProjects from './runner_projects.vue';
@@ -13,8 +18,10 @@ import RunnerTags from './runner_tags.vue';
export default {
components: {
+ GlIcon,
GlIntersperse,
GlLink,
+ GlSprintf,
HelpPopover,
RunnerDetail,
RunnerMaintenanceNoteDetail: () =>
@@ -74,8 +81,12 @@ export default {
anchor: 'authentication-token-security',
});
},
+ runnerManagersCount() {
+ return formatNumber(this.runner?.managers?.count || 0);
+ },
},
ACCESS_LEVEL_REF_PROTECTED,
+ RUNNER_MANAGERS_HELP_URL,
};
</script>
@@ -150,6 +161,34 @@ export default {
class="gl-pt-4 gl-border-t-gray-100 gl-border-t-1 gl-border-t-solid"
:value="runner.maintenanceNoteHtml"
/>
+
+ <runner-detail>
+ <template #label>
+ {{ s__('Runners|Runners') }}
+ <help-popover>
+ <gl-sprintf
+ :message="
+ s__(
+ 'Runners|Runners are grouped when they have the same authentication token. This happens when you re-use a runner configuration in more than one runner manager. %{linkStart}How does this work?%{linkEnd}',
+ )
+ "
+ >
+ <template #link="{ content }"
+ ><gl-link
+ :href="$options.RUNNER_MANAGERS_HELP_URL"
+ target="_blank"
+ class="gl-reset-font-size"
+ >{{ content }}</gl-link
+ ></template
+ >
+ </gl-sprintf>
+ </help-popover>
+ </template>
+ <template #value>
+ <gl-icon name="container-image" class="gl-text-secondary" />
+ {{ runnerManagersCount }}
+ </template>
+ </runner-detail>
</dl>
</div>
diff --git a/app/assets/javascripts/ci/runner/constants.js b/app/assets/javascripts/ci/runner/constants.js
index 4e36a410a66..b5fcc14be37 100644
--- a/app/assets/javascripts/ci/runner/constants.js
+++ b/app/assets/javascripts/ci/runner/constants.js
@@ -256,3 +256,5 @@ export const SERVICE_COMMANDS_HELP_URL =
export const CHANGELOG_URL = 'https://gitlab.com/gitlab-org/gitlab-runner/blob/main/CHANGELOG.md';
export const DOCKER_HELP_URL = 'https://docs.gitlab.com/runner/install/docker.html';
export const KUBERNETES_HELP_URL = 'https://docs.gitlab.com/runner/install/kubernetes.html';
+export const RUNNER_MANAGERS_HELP_URL =
+ 'https://docs.gitlab.com/runner/fleet_scaling/#workers-executors-and-autoscaling-capabilities';
diff --git a/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql b/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql
index 87d92b8e263..1a2ad59650e 100644
--- a/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql
+++ b/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql
@@ -20,6 +20,9 @@ fragment RunnerDetailsShared on CiRunner {
tokenExpiresAt
version
editAdminUrl
+ managers {
+ count
+ }
userPermissions {
updateRunner
deleteRunner
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
index 25cf5335fb5..ad78de8728f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
@@ -187,7 +187,7 @@ export default {
this.updateApproval(
() => this.service.approveMergeRequestWithAuth(data),
(error) => {
- if (error && error.response && error.response.status === HTTP_STATUS_UNAUTHORIZED) {
+ if (error?.response?.status === HTTP_STATUS_UNAUTHORIZED) {
this.hasApprovalAuthError = true;
return;
}
diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb
index 10dd18c0c86..9d7569047f6 100644
--- a/app/controllers/projects/cycle_analytics_controller.rb
+++ b/app/controllers/projects/cycle_analytics_controller.rb
@@ -26,7 +26,6 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
if project.licensed_feature_available?(:cycle_analytics_for_projects)
push_licensed_feature(:cycle_analytics_for_projects)
- push_frontend_feature_flag(:vsa_group_and_project_parity, @project)
end
end
diff --git a/app/models/ci/catalog/listing.rb b/app/models/ci/catalog/listing.rb
index b9e777f27a0..1cb030c67c3 100644
--- a/app/models/ci/catalog/listing.rb
+++ b/app/models/ci/catalog/listing.rb
@@ -14,16 +14,25 @@ module Ci
@current_user = current_user
end
- def resources
- Ci::Catalog::Resource
- .joins(:project).includes(:project)
- .merge(projects_in_namespace_visible_to_user)
+ def resources(sort: nil)
+ case sort.to_s
+ when 'name_desc' then all_resources.order_by_name_desc
+ when 'name_asc' then all_resources.order_by_name_asc
+ else
+ all_resources.order_by_created_at_desc
+ end
end
private
attr_reader :namespace, :current_user
+ def all_resources
+ Ci::Catalog::Resource
+ .joins(:project).includes(:project)
+ .merge(projects_in_namespace_visible_to_user)
+ end
+
def projects_in_namespace_visible_to_user
Project
.in_namespace(namespace.self_and_descendant_ids)
diff --git a/app/models/ci/catalog/resource.rb b/app/models/ci/catalog/resource.rb
index bb4584aacae..f400b8fe046 100644
--- a/app/models/ci/catalog/resource.rb
+++ b/app/models/ci/catalog/resource.rb
@@ -13,6 +13,9 @@ module Ci
belongs_to :project
scope :for_projects, ->(project_ids) { where(project_id: project_ids) }
+ scope :order_by_created_at_desc, -> { reorder(created_at: :desc) }
+ scope :order_by_name_desc, -> { joins(:project).merge(Project.sorted_by_name_desc) }
+ scope :order_by_name_asc, -> { joins(:project).merge(Project.sorted_by_name_asc) }
delegate :avatar_path, :description, :name, to: :project
diff --git a/app/models/integration.rb b/app/models/integration.rb
index 860739fe5aa..0f716862032 100644
--- a/app/models/integration.rb
+++ b/app/models/integration.rb
@@ -18,7 +18,7 @@ class Integration < ApplicationRecord
self.inheritance_column = :type_new
INTEGRATION_NAMES = %w[
- asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord
+ asana assembla bamboo bugzilla buildkite campfire clickup confluence custom_issue_tracker datadog discord
drone_ci emails_on_push ewm external_wiki hangouts_chat harbor irker jira
mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email
pivotaltracker prometheus pumble pushover redmine slack slack_slash_commands squash_tm teamcity
diff --git a/app/models/integrations/clickup.rb b/app/models/integrations/clickup.rb
new file mode 100644
index 00000000000..7cc05d41e14
--- /dev/null
+++ b/app/models/integrations/clickup.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Integrations
+ class Clickup < BaseIssueTracker
+ include HasIssueTrackerFields
+
+ validates :project_url, :issues_url, presence: true, public_url: true, if: :activated?
+
+ def reference_pattern(*)
+ @reference_pattern ||= /((#|CU-)(?<issue>[a-z0-9]+)|(?<issue>[A-Z0-9_]{2,10}-\d+))\b/
+ end
+
+ def title
+ 'ClickUp'
+ end
+
+ def description
+ s_("IssueTracker|Use Clickup as this project's issue tracker.")
+ end
+
+ def help
+ docs_link = ActionController::Base.helpers.link_to _('Learn more.'),
+ Rails.application.routes.url_helpers.help_page_url('user/project/integrations/clickup'),
+ target: '_blank',
+ rel: 'noopener noreferrer'
+ format(s_(
+ "IssueTracker|Use ClickUp as this project's issue tracker. %{docs_link}"
+ ).html_safe, docs_link: docs_link.html_safe)
+ end
+
+ def self.to_param
+ 'clickup'
+ end
+
+ def fields
+ super.select { _1.name.in?(%w[project_url issues_url]) }
+ end
+ end
+end
diff --git a/app/models/integrations/jira.rb b/app/models/integrations/jira.rb
index b4f2282fd7a..f10b7872277 100644
--- a/app/models/integrations/jira.rb
+++ b/app/models/integrations/jira.rb
@@ -14,6 +14,14 @@ module Integrations
ATLASSIAN_REFERRER_GITLAB_COM = { atlOrigin: 'eyJpIjoiY2QyZTJiZDRkNGZhNGZlMWI3NzRkNTBmZmVlNzNiZTkiLCJwIjoianN3LWdpdGxhYi1pbnQifQ' }.freeze
ATLASSIAN_REFERRER_SELF_MANAGED = { atlOrigin: 'eyJpIjoiYjM0MTA4MzUyYTYxNDVkY2IwMzVjOGQ3ZWQ3NzMwM2QiLCJwIjoianN3LWdpdGxhYlNNLWludCJ9' }.freeze
+ API_ENDPOINTS = {
+ find_issue: "/rest/api/2/issue/%s",
+ server_info: "/rest/api/2/serverInfo",
+ transition_issue: "/rest/api/2/issue/%s/transitions",
+ issue_comments: "/rest/api/2/issue/%s/comment",
+ link_remote_issue: "/rest/api/2/issue/%s/remotelink"
+ }.freeze
+
SECTION_TYPE_JIRA_TRIGGER = 'jira_trigger'
SECTION_TYPE_JIRA_ISSUES = 'jira_issues'
@@ -277,7 +285,9 @@ module Integrations
expands << 'transitions' if transitions
options = { expand: expands.join(',') } if expands.any?
- jira_request { client.Issue.find(issue_key, options || {}) }
+ path = API_ENDPOINTS[:find_issue] % issue_key
+
+ jira_request(path) { client.Issue.find(issue_key, options || {}) }
end
def close_issue(entity, external_issue, current_user)
@@ -389,7 +399,7 @@ module Integrations
def server_info
strong_memoize(:server_info) do
- client_url.present? ? jira_request { client.ServerInfo.all.attrs } : nil
+ client_url.present? ? jira_request(API_ENDPOINTS[:server_info]) { client.ServerInfo.all.attrs } : nil
end
end
@@ -419,7 +429,8 @@ module Integrations
true
rescue StandardError => e
- log_exception(e, message: 'Issue transition failed', client_url: client_url)
+ path = API_ENDPOINTS[:transition_issue] % issue.id
+ log_exception(e, message: 'Issue transition failed', client_url: client_url, client_path: path, client_status: '400')
false
end
@@ -518,7 +529,8 @@ module Integrations
end
def comment_exists?(issue, message)
- comments = jira_request { issue.comments }
+ path = API_ENDPOINTS[:issue_comments] % issue.id
+ comments = jira_request(path) { issue.comments }
comments.present? && comments.any? { |comment| comment.body.include?(message) }
end
@@ -526,14 +538,16 @@ module Integrations
def send_message(issue, message, remote_link_props)
return unless client_url.present?
- jira_request do
+ path = API_ENDPOINTS[:link_remote_issue] % issue.id
+
+ jira_request(path) do
remote_link = find_remote_link(issue, remote_link_props[:object][:url])
create_issue_comment(issue, message) unless remote_link
remote_link ||= issue.remotelink.build
remote_link.save!(remote_link_props)
- log_info("Successfully posted", client_url: client_url)
+ log_info("Successfully posted", client_url: client_url, client_path: path)
"SUCCESS: Successfully posted to #{client_url}."
end
end
@@ -545,7 +559,8 @@ module Integrations
end
def find_remote_link(issue, url)
- links = jira_request { issue.remotelink.all }
+ path = API_ENDPOINTS[:link_remote_issue] % issue.id
+ links = jira_request(path) { issue.remotelink.all }
return unless links
links.find { |link| link.object["url"] == url }
@@ -612,11 +627,11 @@ module Integrations
end
# Handle errors when doing Jira API calls
- def jira_request
+ def jira_request(path)
yield
rescue StandardError => e
@error = e
- log_exception(e, message: 'Error sending message', client_url: client_url)
+ log_exception(e, message: 'Error sending message', client_url: client_url, client_path: path, client_status: e.code)
nil
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 51deb8dea68..66c62ddf0b5 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -220,8 +220,20 @@ class Issue < ApplicationRecord
project: [:project_namespace, :project_feature, :route, { group: :route }, { namespace: :route }],
duplicated_to: { project: [:project_feature] })
}
- scope :with_issue_type, ->(types) { where(issue_type: types) }
- scope :without_issue_type, ->(types) { where.not(issue_type: types) }
+ scope :with_issue_type, ->(types) {
+ if Feature.enabled?(:issue_type_uses_work_item_types_table)
+ joins(:work_item_type).where(work_item_types: { base_type: types })
+ else
+ where(issue_type: types)
+ end
+ }
+ scope :without_issue_type, ->(types) {
+ if Feature.enabled?(:issue_type_uses_work_item_types_table)
+ joins(:work_item_type).where.not(work_item_types: { base_type: types })
+ else
+ where.not(issue_type: types)
+ end
+ }
scope :public_only, -> { where(confidential: false) }
diff --git a/app/models/project.rb b/app/models/project.rb
index 462a169ea95..ef87b611fca 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -185,6 +185,7 @@ class Project < ApplicationRecord
has_one :bugzilla_integration, class_name: 'Integrations::Bugzilla'
has_one :buildkite_integration, class_name: 'Integrations::Buildkite'
has_one :campfire_integration, class_name: 'Integrations::Campfire'
+ has_one :clickup_integration, class_name: 'Integrations::Clickup'
has_one :confluence_integration, class_name: 'Integrations::Confluence'
has_one :custom_issue_tracker_integration, class_name: 'Integrations::CustomIssueTracker'
has_one :datadog_integration, class_name: 'Integrations::Datadog'
@@ -602,6 +603,42 @@ class Project < ApplicationRecord
.or(arel_table[:storage_version].eq(nil)))
end
+ scope :sorted_by_name_desc, -> {
+ keyset_order = Gitlab::Pagination::Keyset::Order.build([
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: :name,
+ column_expression: Project.arel_table[:name],
+ order_expression: Project.arel_table[:name].desc,
+ distinct: false,
+ nullable: :nulls_last
+ ),
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: :id,
+ order_expression: Project.arel_table[:id].desc
+ )
+ ])
+
+ reorder(keyset_order)
+ }
+
+ scope :sorted_by_name_asc, -> {
+ keyset_order = Gitlab::Pagination::Keyset::Order.build([
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: :name,
+ column_expression: Project.arel_table[:name],
+ order_expression: Project.arel_table[:name].asc,
+ distinct: false,
+ nullable: :nulls_last
+ ),
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: :id,
+ order_expression: Project.arel_table[:id].asc
+ )
+ ])
+
+ reorder(keyset_order)
+ }
+
scope :sorted_by_updated_asc, -> { reorder(self.arel_table['updated_at'].asc) }
scope :sorted_by_updated_desc, -> { reorder(self.arel_table['updated_at'].desc) }
scope :sorted_by_stars_desc, -> { reorder(self.arel_table['star_count'].desc) }
diff --git a/app/services/ci/delete_unit_tests_service.rb b/app/services/ci/delete_unit_tests_service.rb
index 230661a107d..a2fb44ff3fc 100644
--- a/app/services/ci/delete_unit_tests_service.rb
+++ b/app/services/ci/delete_unit_tests_service.rb
@@ -25,9 +25,7 @@ module Ci
klass.transaction do
ids = klass.deletable.lock('FOR UPDATE SKIP LOCKED').limit(BATCH_SIZE).pluck(:id)
- break if ids.empty?
-
- deleted = klass.where(id: ids).delete_all
+ deleted = klass.where(id: ids).delete_all if ids.any?
end
deleted > 0
diff --git a/app/services/concerns/update_repository_storage_methods.rb b/app/services/concerns/update_repository_storage_methods.rb
index a0b4040cff7..bb43cab79bb 100644
--- a/app/services/concerns/update_repository_storage_methods.rb
+++ b/app/services/concerns/update_repository_storage_methods.rb
@@ -14,12 +14,16 @@ module UpdateRepositoryStorageMethods
end
def execute
- repository_storage_move.with_lock do
- return ServiceResponse.success unless repository_storage_move.scheduled? # rubocop:disable Cop/AvoidReturnFromBlocks
+ response = repository_storage_move.with_lock do
+ next ServiceResponse.success unless repository_storage_move.scheduled?
repository_storage_move.start!
+
+ nil
end
+ return response if response
+
mirror_repositories unless same_filesystem?
repository_storage_move.transaction do
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 3a7b577d59a..b8853e8bcbc 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -334,7 +334,7 @@ module MergeRequests
strong_memoize(:issue_iid) do
@params_issue_iid || begin
id = if target_project.external_issue_tracker
- source_branch.match(target_project.external_issue_reference_pattern).try(:[], 0)
+ target_project.external_issue_reference_pattern.match(source_branch).try(:[], 0)
end
id || source_branch.match(/\A(\d+)-/).try(:[], 1)
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 8a51b210d93..e37b6516d21 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -231,7 +231,7 @@ module Projects
@project.create_labels unless @project.gitlab_project_import?
- break if @project.import?
+ next if @project.import?
unless @project.create_repository(default_branch: default_branch)
raise 'Failed to create repository'
diff --git a/app/services/work_items/delete_task_service.rb b/app/services/work_items/delete_task_service.rb
index 3d66716543a..4c0ee2f827d 100644
--- a/app/services/work_items/delete_task_service.rb
+++ b/app/services/work_items/delete_task_service.rb
@@ -22,7 +22,7 @@ module WorkItems
current_user: @current_user
).execute
- break ::ServiceResponse.error(message: replacement_result.errors, http_status: 422) if replacement_result.error?
+ next ::ServiceResponse.error(message: replacement_result.errors, http_status: 422) if replacement_result.error?
delete_result = ::WorkItems::DeleteService.new(
container: @task.project,
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index c8d4f02274b..b31e8919832 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -8,11 +8,10 @@
= form_tag network_path, method: :get, class: 'form-inline network-form' do |f|
= text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: _("Git revision"), class: 'search-input form-control gl-form-input input-mx-250 search-sha gl-mr-2'
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, icon: 'search')
- .inline.gl-ml-5
- .form-check.light
- = check_box_tag :filter_ref, 1, @options[:filter_ref], class: 'form-check-input'
- = label_tag :filter_ref, class: 'form-check-label' do
- %span= _("Begin with the selected commit")
+ .form-group{ class: 'gl-ml-5 gl-mb-n3!' }
+ = render Pajamas::CheckboxTagComponent.new(name: :filter_ref, checked: @options[:filter_ref]) do |c|
+ = c.label do
+ = _("Begin with the selected commit")
- if @commit
.network-graph.gl-bg-white.gl-overflow-scroll.gl-overflow-x-hidden{ data: { url: @url, commit_url: @commit_url, ref: @ref, commit_id: @commit.id } }
diff --git a/config/feature_flags/development/record_issue_and_mr_assignee_events.yml b/config/feature_flags/development/record_issue_and_mr_assignee_events.yml
deleted file mode 100644
index 83660a2a4a6..00000000000
--- a/config/feature_flags/development/record_issue_and_mr_assignee_events.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: record_issue_and_mr_assignee_events
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117545
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/397050
-milestone: '15.11'
-type: development
-group: group::optimize
-default_enabled: true
diff --git a/config/feature_flags/development/vsa_group_and_project_parity.yml b/config/feature_flags/development/vsa_group_and_project_parity.yml
deleted file mode 100644
index d75621a8e5d..00000000000
--- a/config/feature_flags/development/vsa_group_and_project_parity.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: vsa_group_and_project_parity
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114056
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/396865
-milestone: '15.11'
-type: development
-group: group::optimize
-default_enabled: true
diff --git a/config/initializers/00_deprecations.rb b/config/initializers/00_deprecations.rb
index 915e8d704c9..8aff095d88b 100644
--- a/config/initializers/00_deprecations.rb
+++ b/config/initializers/00_deprecations.rb
@@ -30,7 +30,9 @@ else
# https://gitlab.com/gitlab-org/gitlab/-/issues/333086
/default_hash is deprecated/,
# https://gitlab.com/gitlab-org/gitlab/-/issues/369970
- /Passing an Active Record object to `\w+` directly is deprecated/
+ /Passing an Active Record object to `\w+` directly is deprecated/,
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/410086
+ /Using `return`, `break` or `throw` to exit a transaction block/
]
ActiveSupport::Deprecation.disallowed_warnings = rails7_deprecation_warnings
diff --git a/config/metrics/counts_all/20230515153810_groups_clickup_active.yml b/config/metrics/counts_all/20230515153810_groups_clickup_active.yml
new file mode 100644
index 00000000000..b5d999e76fd
--- /dev/null
+++ b/config/metrics/counts_all/20230515153810_groups_clickup_active.yml
@@ -0,0 +1,21 @@
+---
+key_path: counts.groups_clickup_active
+description: Count of groups with active integrations for ClickUp
+product_section: dev
+product_stage: manage
+product_group: integrations
+value_type: number
+status: active
+milestone: "16.1"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732
+time_frame: all
+data_source: database
+data_category: optional
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20230515153826_groups_inheriting_clickup_active.yml b/config/metrics/counts_all/20230515153826_groups_inheriting_clickup_active.yml
new file mode 100644
index 00000000000..66a1efdc47b
--- /dev/null
+++ b/config/metrics/counts_all/20230515153826_groups_inheriting_clickup_active.yml
@@ -0,0 +1,21 @@
+---
+key_path: counts.groups_inheriting_clickup_active
+description: Count of active groups inheriting integrations for ClickUp
+product_section: dev
+product_stage: manage
+product_group: integrations
+value_type: number
+status: active
+milestone: "16.1"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732
+time_frame: all
+data_source: database
+data_category: optional
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20230515153827_instances_clickup_active.yml b/config/metrics/counts_all/20230515153827_instances_clickup_active.yml
new file mode 100644
index 00000000000..de631475e5d
--- /dev/null
+++ b/config/metrics/counts_all/20230515153827_instances_clickup_active.yml
@@ -0,0 +1,21 @@
+---
+key_path: counts.instances_clickup_active
+description: Count of active instance-level integrations for ClickUp
+product_section: dev
+product_stage: manage
+product_group: integrations
+value_type: number
+status: active
+milestone: "16.1"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732
+time_frame: all
+data_source: database
+data_category: optional
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20230515153829_projects_clickup_active.yml b/config/metrics/counts_all/20230515153829_projects_clickup_active.yml
new file mode 100644
index 00000000000..4f7c8775614
--- /dev/null
+++ b/config/metrics/counts_all/20230515153829_projects_clickup_active.yml
@@ -0,0 +1,21 @@
+---
+key_path: counts.projects_clickup_active
+description: Count of projects with active integrations for ClickUp
+product_section: dev
+product_stage: manage
+product_group: integrations
+value_type: number
+status: active
+milestone: "16.1"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732
+time_frame: all
+data_source: database
+data_category: optional
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20230515153834_projects_inheriting_clickup_active.yml b/config/metrics/counts_all/20230515153834_projects_inheriting_clickup_active.yml
new file mode 100644
index 00000000000..1e45317f2a5
--- /dev/null
+++ b/config/metrics/counts_all/20230515153834_projects_inheriting_clickup_active.yml
@@ -0,0 +1,21 @@
+---
+key_path: counts.projects_inheriting_clickup_active
+description: Count of active projects inheriting integrations for ClickUp
+product_section: dev
+product_stage: manage
+product_group: integrations
+value_type: number
+status: active
+milestone: "16.1"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732
+time_frame: all
+data_source: database
+data_category: optional
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/data/deprecations/15-9-rails-error-tracking.yml b/data/deprecations/15-9-rails-error-tracking.yml
deleted file mode 100644
index 4d9fc371213..00000000000
--- a/data/deprecations/15-9-rails-error-tracking.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-- title: "Error Tracking UI in GitLab Rails is deprecated"
- announcement_milestone: "15.9"
- removal_milestone: "16.6"
- breaking_change: true
- reporter: kbychu
- stage: monitor
- issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/389991
- body: |
- The [Error Tracking UI](https://docs.gitlab.com/ee/operations/error_tracking.html) is deprecated in 15.9 and will be removed in 16.6 (milestone might change) once GitLab Observability UI is made available. In future versions, you should use the [GitLab Observability UI](https://gitlab.com/gitlab-org/opstrace/opstrace-ui/), which will gradually be made available on GitLab.com over the next few releases.
-
- During the transition to the GitLab Observability UI, we will migrate the [GitLab Observability Backend](https://gitlab.com/gitlab-org/opstrace/opstrace) from a per-cluster deployment model to a per-tenant deployment model. Because [Integrated Error Tracking](https://docs.gitlab.com/ee/operations/error_tracking.html#integrated-error-tracking) is in Open Beta, we will not migrate any existing user data. For more details about the migration, see the direction pages for:
-
- - [Observability](https://about.gitlab.com/direction/monitor/observability/data-visualization/).
- - The [Observability Backend](https://about.gitlab.com/direction/monitor/observability/data-management/).
- - [Data visualization](https://about.gitlab.com/direction/monitor/observability/data-visualization/).
- documentation_url: https://docs.gitlab.com/ee/operations/error_tracking.html
diff --git a/db/docs/batched_background_migrations/backfill_resource_link_events.yml b/db/docs/batched_background_migrations/backfill_resource_link_events.yml
new file mode 100644
index 00000000000..224cf5fe06b
--- /dev/null
+++ b/db/docs/batched_background_migrations/backfill_resource_link_events.yml
@@ -0,0 +1,6 @@
+---
+migration_job_name: BackfillResourceLinkEvents
+description: Backfills resource_link_events table based off system_note_metadata and notes
+feature_category: team_planning
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118605
+milestone: 16.1
diff --git a/db/fixtures/development/24_forks.rb b/db/fixtures/development/24_forks.rb
index 1476681f4d7..f92b7972de5 100644
--- a/db/fixtures/development/24_forks.rb
+++ b/db/fixtures/development/24_forks.rb
@@ -8,7 +8,7 @@ Sidekiq::Testing.inline! do
##
# 03_project.rb might not have created a public project because
# we use randomized approach (e.g. `Array#sample`).
- return unless source_project
+ next unless source_project
Sidekiq::Worker.skipping_transaction_check do
fork_project = Projects::ForkService.new(
diff --git a/db/post_migrate/20230426085615_queue_backfill_resource_link_events.rb b/db/post_migrate/20230426085615_queue_backfill_resource_link_events.rb
new file mode 100644
index 00000000000..fe4ea099d2e
--- /dev/null
+++ b/db/post_migrate/20230426085615_queue_backfill_resource_link_events.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class QueueBackfillResourceLinkEvents < Gitlab::Database::Migration[2.1]
+ MIGRATION = "BackfillResourceLinkEvents"
+ DELAY_INTERVAL = 2.minutes
+ BATCH_SIZE = 5000
+ SUB_BATCH_SIZE = 10
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ class SystemNoteMetadata < MigrationRecord
+ self.table_name = 'system_note_metadata'
+
+ def self.batch_start_id
+ SystemNoteMetadata
+ .select(:id)
+ .where("action='relate_to_parent' OR action='unrelate_from_parent'")
+ .order(id: :asc)
+ .limit(1)
+ .first&.id
+ end
+ end
+
+ def up
+ batch_min_value = SystemNoteMetadata.batch_start_id
+
+ return unless batch_min_value
+
+ queue_batched_background_migration(
+ MIGRATION,
+ :system_note_metadata,
+ :id,
+ job_interval: DELAY_INTERVAL,
+ batch_size: BATCH_SIZE,
+ sub_batch_size: SUB_BATCH_SIZE,
+ batch_min_value: batch_min_value
+ )
+ end
+
+ def down
+ delete_batched_background_migration(MIGRATION, :system_note_metadata, :id, [])
+ end
+end
diff --git a/db/schema_migrations/20230426085615 b/db/schema_migrations/20230426085615
new file mode 100644
index 00000000000..a0166399442
--- /dev/null
+++ b/db/schema_migrations/20230426085615
@@ -0,0 +1 @@
+e331d6f2d5934cce0ac78862abb3eac5c0567be95b0865f5ac38299ee7e147ca \ No newline at end of file
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 6384d896292..2b633e37fc3 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -74,6 +74,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="querycicatalogresourcesprojectpath"></a>`projectPath` | [`ID`](#id) | Project with the namespace catalog. |
+| <a id="querycicatalogresourcessort"></a>`sort` | [`CiCatalogResourceSort`](#cicatalogresourcesort) | Sort Catalog Resources by given criteria. |
### `Query.ciConfig`
@@ -23995,6 +23996,23 @@ Types of blob viewers.
| <a id="blobviewerstyperich"></a>`rich` | Rich blob viewers type. |
| <a id="blobviewerstypesimple"></a>`simple` | Simple blob viewers type. |
+### `CiCatalogResourceSort`
+
+Values for sorting catalog resources.
+
+| Value | Description |
+| ----- | ----------- |
+| <a id="cicatalogresourcesortcreated_asc"></a>`CREATED_ASC` | Created at ascending order. |
+| <a id="cicatalogresourcesortcreated_desc"></a>`CREATED_DESC` | Created at descending order. |
+| <a id="cicatalogresourcesortname_asc"></a>`NAME_ASC` | Name by ascending order. |
+| <a id="cicatalogresourcesortname_desc"></a>`NAME_DESC` | Name by descending order. |
+| <a id="cicatalogresourcesortupdated_asc"></a>`UPDATED_ASC` | Updated at ascending order. |
+| <a id="cicatalogresourcesortupdated_desc"></a>`UPDATED_DESC` | Updated at descending order. |
+| <a id="cicatalogresourcesortcreated_asc"></a>`created_asc` **{warning-solid}** | **Deprecated** in 13.5. This was renamed. Use: `CREATED_ASC`. |
+| <a id="cicatalogresourcesortcreated_desc"></a>`created_desc` **{warning-solid}** | **Deprecated** in 13.5. This was renamed. Use: `CREATED_DESC`. |
+| <a id="cicatalogresourcesortupdated_asc"></a>`updated_asc` **{warning-solid}** | **Deprecated** in 13.5. This was renamed. Use: `UPDATED_ASC`. |
+| <a id="cicatalogresourcesortupdated_desc"></a>`updated_desc` **{warning-solid}** | **Deprecated** in 13.5. This was renamed. Use: `UPDATED_DESC`. |
+
### `CiConfigIncludeType`
Include type.
@@ -25652,6 +25670,7 @@ State of a Sentry error.
| <a id="servicetypebugzilla_service"></a>`BUGZILLA_SERVICE` | BugzillaService type. |
| <a id="servicetypebuildkite_service"></a>`BUILDKITE_SERVICE` | BuildkiteService type. |
| <a id="servicetypecampfire_service"></a>`CAMPFIRE_SERVICE` | CampfireService type. |
+| <a id="servicetypeclickup_service"></a>`CLICKUP_SERVICE` | ClickupService type. |
| <a id="servicetypeconfluence_service"></a>`CONFLUENCE_SERVICE` | ConfluenceService type. |
| <a id="servicetypecustom_issue_tracker_service"></a>`CUSTOM_ISSUE_TRACKER_SERVICE` | CustomIssueTrackerService type. |
| <a id="servicetypedatadog_service"></a>`DATADOG_SERVICE` | DatadogService type. |
diff --git a/doc/api/integrations.md b/doc/api/integrations.md
index 5b6c4d17915..0a05759c8c2 100644
--- a/doc/api/integrations.md
+++ b/doc/api/integrations.md
@@ -334,6 +334,43 @@ Get Campfire integration settings for a project.
GET /projects/:id/integrations/campfire
```
+## ClickUp
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732) in GitLab 16.1.
+
+ClickUp issue tracker.
+
+### Create or edit ClickUp integration
+
+Set up ClickUp integration for a project.
+
+```plaintext
+PUT /projects/:id/integrations/clickup
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `issues_url` | string | true | Issue URL |
+| `project_url` | string | true | Project URL |
+
+### Disable ClickUp integration
+
+Disable the ClickUp integration for a project. Integration settings are reset.
+
+```plaintext
+DELETE /projects/:id/integrations/clickup
+```
+
+### Get ClickUp integration settings
+
+Get ClickUp integration settings for a project.
+
+```plaintext
+GET /projects/:id/integrations/clickup
+```
+
## Datadog
Datadog system monitoring.
diff --git a/doc/development/ai_features.md b/doc/development/ai_features.md
index f8d19dc26bf..c1493e3f843 100644
--- a/doc/development/ai_features.md
+++ b/doc/development/ai_features.md
@@ -115,6 +115,7 @@ Gitlab::CurrentSettings.update(tofa_credentials: File.read('/YOUR_FILE.json'))
# Note: These credential examples will not work locally for all models
Gitlab::CurrentSettings.update(tofa_host: "<root-domain>") # Example: us-central1-aiplatform.googleapis.com
Gitlab::CurrentSettings.update(tofa_url: "<full-api-endpoint>") # Example: https://ROOT-DOMAIN/v1/projects/MY-COOL-PROJECT/locations/us-central1/publishers/google/models/MY-SPECIAL-MODEL:predict
+Gitlab::CurrentSettings.update(vertex_project: "<project-id>") # Example: cloud-large-language-models
```
Internal team members can [use this snippet](https://gitlab.com/gitlab-com/gl-infra/production/-/snippets/2541742) for help configuring these endpoints.
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index c63c2e3fd24..04073ad611f 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -28,6 +28,7 @@ To enable an external issue tracker, you must configure the appropriate [integra
The following external issue tracker integrations are available:
- [Bugzilla](../user/project/integrations/bugzilla.md)
+- [ClickUp](../user/project/integrations/clickup.md)
- [Custom Issue Tracker](../user/project/integrations/custom_issue_tracker.md)
- [Engineering Workflow Management](../user/project/integrations/ewm.md)
- [Jira](../integration/jira/index.md)
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 18303a5f45c..5cb7b00ddc5 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -288,7 +288,7 @@ To prepare the new server:
Edit `/etc/gitlab/gitlab.rb` and set the following:
```ruby
- nginx['custom_gitlab_server_config'] = "location /api/v4/jobs/request {\n deny all;\n return 503;\n}\n"
+ nginx['custom_gitlab_server_config'] = "location = /api/v4/jobs/request {\n deny all;\n return 503;\n }\n"
```
1. Reconfigure GitLab:
@@ -319,7 +319,7 @@ To prepare the new server:
1. Edit `/etc/gitlab/gitlab.rb`, and set the following:
```ruby
- nginx['custom_gitlab_server_config'] = "location /api/v4/jobs/request {\n deny all;\n return 503;\n}\n"
+ nginx['custom_gitlab_server_config'] = "location = /api/v4/jobs/request {\n deny all;\n return 503;\n }\n"
```
1. Reconfigure GitLab:
@@ -425,7 +425,7 @@ To prepare the new server:
```ruby
# The following line must be removed
- nginx['custom_gitlab_server_config'] = "location /api/v4/jobs/request {\n deny all;\n return 503;\n}\n"
+ nginx['custom_gitlab_server_config'] = "location = /api/v4/jobs/request {\n deny all;\n return 503;\n }\n"
```
1. Reconfigure GitLab:
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index 8d4b45209c9..0d9e399f25c 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -744,31 +744,6 @@ Previous work helped [align the vulnerabilities calls for pipeline security tabs
</div>
</div>
-<div class="milestone-wrapper" data-milestone="16.6">
-
-## GitLab 16.6
-
-<div class="deprecation breaking-change" data-milestone="16.6">
-
-### Error Tracking UI in GitLab Rails is deprecated
-
-<div class="deprecation-notes">
-- Announced in: GitLab <span class="milestone">15.9</span>
-- This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
-- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/389991).
-</div>
-
-The [Error Tracking UI](https://docs.gitlab.com/ee/operations/error_tracking.html) is deprecated in 15.9 and will be removed in 16.6 (milestone might change) once GitLab Observability UI is made available. In future versions, you should use the [GitLab Observability UI](https://gitlab.com/gitlab-org/opstrace/opstrace-ui/), which will gradually be made available on GitLab.com over the next few releases.
-
-During the transition to the GitLab Observability UI, we will migrate the [GitLab Observability Backend](https://gitlab.com/gitlab-org/opstrace/opstrace) from a per-cluster deployment model to a per-tenant deployment model. Because [Integrated Error Tracking](https://docs.gitlab.com/ee/operations/error_tracking.html#integrated-error-tracking) is in Open Beta, we will not migrate any existing user data. For more details about the migration, see the direction pages for:
-
-- [Observability](https://about.gitlab.com/direction/monitor/observability/data-visualization/).
-- The [Observability Backend](https://about.gitlab.com/direction/monitor/observability/data-management/).
-- [Data visualization](https://about.gitlab.com/direction/monitor/observability/data-visualization/).
-
-</div>
-</div>
-
<div class="milestone-wrapper" data-milestone="16.5">
## GitLab 16.5
diff --git a/doc/user/project/integrations/clickup.md b/doc/user/project/integrations/clickup.md
new file mode 100644
index 00000000000..255f0c3f56b
--- /dev/null
+++ b/doc/user/project/integrations/clickup.md
@@ -0,0 +1,53 @@
+---
+stage: Manage
+group: Import and Integrate
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+---
+
+# ClickUp **(FREE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732) in GitLab 16.1.
+
+You can use [ClickUp](https://clickup.com/) as an external issue tracker.
+To enable the ClickUp integration in a project:
+
+1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > Integrations**.
+1. Select **ClickUp**.
+1. Select the checkbox under **Enable integration**.
+1. Fill in the required fields:
+
+ - **Project URL**: The URL to the ClickUp project to link to this GitLab project.
+ - **Issue URL**: The URL to the ClickUp project issue to link to this GitLab project.
+ The URL must contain `:id`. GitLab replaces this ID with the issue number.
+
+1. Select **Save changes** or optionally select **Test settings**.
+
+After you have configured and enabled ClickUp, you see the ClickUp link on the GitLab project pages,
+which takes you to your ClickUp project.
+
+For example, this is a configuration for a project named `gitlab-ci`:
+
+- Project URL: `https://app.clickup.com/1234567`
+- Issue URL: `https://app.clickup.com/t/:id`
+
+You can also disable [GitLab internal issue tracking](../issues/index.md) in this project.
+For more information about the steps and consequences of disabling GitLab issues, see
+[Configure project visibility, features, and permissions](../settings/index.md#configure-project-visibility-features-and-permissions).
+
+## Reference ClickUp issues in GitLab
+
+You can reference your ClickUp issues using:
+
+- `#<ID>`, where `<ID>` is a alphanumerical string (example `#8wrtcd932`).
+- `CU-<ID>`, where `<ID>` is a alphanumerical string (example `CU-8wrtcd932`).
+- `<PROJECT>-<ID>`, for example `API_32-143`, where:
+ - `<PROJECT>` is a ClickUp list custom prefix ID.
+ - `<ID>` is a number.
+
+In links, the `CU-` part is ignored and it links to the global URL of the issue. When a custom
+prefix is used in a ClickUp list, the prefix part is part of the link.
+
+We suggest using the `CU-` format (`CU-<ID>`) if you have both internal and external issue
+trackers enabled. If you use the shorter format, and an issue with the same ID exists in the
+internal issue tracker, the internal issue is linked.
diff --git a/doc/user/project/integrations/index.md b/doc/user/project/integrations/index.md
index f7019b2eeb2..3bdb6ada00e 100644
--- a/doc/user/project/integrations/index.md
+++ b/doc/user/project/integrations/index.md
@@ -50,6 +50,7 @@ You can configure the following integrations.
| [Bugzilla](bugzilla.md) | Use Bugzilla as the issue tracker. | **{dotted-circle}** No |
| Buildkite | Run CI/CD pipelines with Buildkite. | **{check-circle}** Yes |
| Campfire | Connect to chat. | **{dotted-circle}** No |
+| [ClickUp](clickup.md) | Use ClickUp as the issue tracker. | **{dotted-circle}** No |
| [Confluence Workspace](../../../api/integrations.md#confluence-integration) | Use Confluence Cloud Workspace as an internal wiki. | **{dotted-circle}** No |
| [Custom issue tracker](custom_issue_tracker.md) | Use a custom issue tracker. | **{dotted-circle}** No |
| [Datadog](../../../integration/datadog.md) | Trace your GitLab pipelines with Datadog. | **{check-circle}** Yes |
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index 8c2bcb250a9..3255c38fd25 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -276,6 +276,8 @@ Members who can push to this branch can now also force push.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35097) in GitLab 13.5, users and groups who can push to protected branches do not have to use a merge request to merge their feature branches. This means they can skip merge request approval rules.
For a protected branch, you can require at least one approval by a [Code Owner](codeowners/index.md).
+If a branch is protected by multiple rules, code owner approval is required if _any_ of
+the applicable rules have **Required approval from code owners** enabled.
To protect a new branch and enable Code Owner's approval:
diff --git a/doc/user/workspace/index.md b/doc/user/workspace/index.md
index 9d8fb45b5f7..647960459bf 100644
--- a/doc/user/workspace/index.md
+++ b/doc/user/workspace/index.md
@@ -29,31 +29,27 @@ Each workspace includes its own set of dependencies, libraries, and tools, which
- In the Kubernetes cluster, install an Ingress controller of your choice (for example, `ingress-nginx`), and make that controller accessible over a domain. For example, point `*.workspaces.example.dev` and `workspaces.example.dev` to the load balancer exposed by the Ingress controller.
- In the Kubernetes cluster, [install `gitlab-workspaces-proxy`](https://gitlab.com/gitlab-org/remote-development/gitlab-workspaces-proxy#installation-instructions).
- In the Kubernetes cluster, [install the GitLab agent for Kubernetes](../clusters/agent/install/index.md).
-- Configure remote development settings for the GitLab agent with this snippet:
+- Configure remote development settings for the GitLab agent with this snippet, and update `dns_zone` as needed:
- ```yaml
- remote_development:
- enabled: true
- dns_zone: "workspaces.example.dev"
- ```
+ ```yaml
+ remote_development:
+ enabled: true
+ dns_zone: "workspaces.example.dev"
+ ```
- Update `dns_zone` as needed.
-
-- In each public project you want to use this feature for, define a [devfile](#devfile). Ensure the container images used in the devfile support [arbitrary user IDs](#arbitrary-user-ids).
+- In each public project you want to use this feature for, create a [devfile](#devfile):
+ 1. On the top bar, select **Main menu > Projects** and find your project.
+ 1. In the root directory of your project, create a file named `.devfile.yaml`. You can use one of the [example configurations](#example-configurations).
+- Ensure the container images used in the devfile support [arbitrary user IDs](#arbitrary-user-ids).
### Create a workspace
-Prepare your projects and create a [devfile](#devfile):
-
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. In the root directory of your project, create a file named `.devfile.yaml`.
- - Optional: For a quick demo, you can use one of the [example configurations](#example-definition) below.
-
-Next, go to the main menu to create a workspace:
+To create a workspace:
-1. Go to **Main Menu > Your Work > Workspaces**.
-1. In the upper right, select **New workspace**.
-1. From the **Select project** dropdown list, select a project with a `.devfile.yaml` file. You can only create workspaces for public projects.
+1. On the top bar, select **Main menu > Your work**.
+1. On the left sidebar, select **Workspaces**.
+1. Select **New workspace**.
+1. From the **Select project** dropdown list, [select a project with a `.devfile.yaml` file](#prerequisites). You can only create workspaces for public projects.
1. From the **Select cluster agent** dropdown list, select a cluster agent owned by the group the project belongs to.
1. In **Time before automatic termination**, enter the number of hours until the workspace automatically terminates. This timeout is a safety measure to prevent a workspace from consuming excessive resources or running indefinitely.
1. Select **Create workspace**.
@@ -88,9 +84,9 @@ Only these properties are relevant to the GitLab implementation of the `containe
| `endpoints` | Port mappings to expose from the container. |
| `volumeMounts` | Storage volume to mount in the container. |
-### Example definition
+### Example configurations
-The following is an example devfile:
+The following is an example devfile configuration:
```yaml
schemaVersion: 2.2.0
diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb
index 4c37a2a5aba..8154ba36072 100644
--- a/lib/api/helpers/integrations_helpers.rb
+++ b/lib/api/helpers/integrations_helpers.rb
@@ -839,6 +839,20 @@ module API
desc: 'The issues URL'
}
],
+ 'clickup' => [
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'The project URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'The issues URL'
+ }
+ ],
'slack' => [
chat_notification_settings,
chat_notification_flags,
@@ -968,6 +982,7 @@ module API
::Integrations::Bugzilla,
::Integrations::Buildkite,
::Integrations::Campfire,
+ ::Integrations::Clickup,
::Integrations::Confluence,
::Integrations::CustomIssueTracker,
::Integrations::Datadog,
diff --git a/lib/gitlab/background_migration/backfill_resource_link_events.rb b/lib/gitlab/background_migration/backfill_resource_link_events.rb
new file mode 100644
index 00000000000..a2499e90e1f
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_resource_link_events.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Backfills resource_link_events from system_note_metadata and notes records
+ class BackfillResourceLinkEvents < BatchedMigrationJob
+ operation_name :backfill_resource_link_events
+ feature_category :team_planning
+
+ # AR model for resource_link_events inlined
+ class ResourceLinkEvent < ApplicationRecord
+ self.table_name = 'resource_link_events'
+
+ enum action: {
+ add: 1,
+ remove: 2
+ }
+ end
+
+ scope_to ->(relation) { relation.where("action='relate_to_parent' OR action='unrelate_from_parent'") }
+
+ def perform
+ each_sub_batch do |sub_batch|
+ values_subquery = resource_link_event_values_query(sub_batch.select(:id).to_sql)
+
+ connection.execute(<<~SQL)
+ INSERT INTO resource_link_events (action, issue_id, child_work_item_id, user_id, created_at, system_note_metadata_id)
+ #{values_subquery}
+ ON CONFLICT (system_note_metadata_id) DO NOTHING;
+ SQL
+ end
+ end
+
+ def resource_link_event_values_query(ids_subquery)
+ <<~SQL
+ SELECT
+ CASE WHEN system_note_metadata.action='relate_to_parent' THEN #{ResourceLinkEvent.actions[:add]}
+ ELSE #{ResourceLinkEvent.actions[:remove]}
+ END AS action,
+ parent_issues.id AS issue_id,
+ notes.noteable_id AS child_work_item_id,
+ notes.author_id AS user_id,
+ system_note_metadata.created_at AS created_at,
+ system_note_metadata.id AS system_note_metadata_id
+ FROM system_note_metadata
+ INNER JOIN notes ON system_note_metadata.note_id = notes.id
+ INNER JOIN issues as work_items ON work_items.id = notes.noteable_id,
+ LATERAL (
+ -- This lateral join searches for the id of the parent issue.
+ --
+ -- When a child work item is added to its parent,
+ -- "relate_to_parent" is recorded as `system_note_metadata.action`
+ -- and a note records to which parent the child work item is added e.g, "added #1 (iid) as parent".
+ --
+ -- Based on the iid of the parent extracted from the note and using the child work item's project id,
+ -- we can find out the id of the parent issue.
+ SELECT issues.id
+ FROM issues
+ WHERE
+ issues.project_id = work_items.project_id
+ AND issues.iid = CASE WHEN system_note_metadata.action='relate_to_parent' THEN substring(notes.note from 'added #(\\d+) as parent')::bigint
+ ELSE substring(notes.note from 'removed parent \\S+ #(\\d+)')::bigint
+ END
+ ) parent_issues
+ WHERE
+ system_note_metadata.id IN (#{ids_subquery})
+ SQL
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/templates/Jobs/CF-Provision.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/CF-Provision.gitlab-ci.yml
index 6e8cf15204a..de3f688bdb6 100644
--- a/lib/gitlab/ci/templates/Jobs/CF-Provision.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/CF-Provision.gitlab-ci.yml
@@ -7,7 +7,7 @@ cloud_formation:
script:
- gl-cloudformation create-stack
rules:
- - if: '($AUTO_DEVOPS_PLATFORM_TARGET != "EC2") || ($AUTO_DEVOPS_PLATFORM_TARGET != "ECS")'
+ - if: '($AUTO_DEVOPS_PLATFORM_TARGET != "EC2") && ($AUTO_DEVOPS_PLATFORM_TARGET != "ECS")'
when: never
- if: '$CI_KUBERNETES_ACTIVE || $KUBECONFIG'
when: never
diff --git a/lib/gitlab/database/background_migration/batched_job.rb b/lib/gitlab/database/background_migration/batched_job.rb
index 523ab2a9f27..458b099924b 100644
--- a/lib/gitlab/database/background_migration/batched_job.rb
+++ b/lib/gitlab/database/background_migration/batched_job.rb
@@ -139,7 +139,7 @@ module Gitlab
new_batch_size = batch_size / 2
- break update!(attempts: 0) if new_batch_size < 1
+ next update!(attempts: 0) if new_batch_size < 1
batching_strategy = batched_migration.batch_class.new(connection: self.class.connection)
next_batch_bounds = batching_strategy.next_batch(
diff --git a/lib/gitlab/resource_events/assignment_event_recorder.rb b/lib/gitlab/resource_events/assignment_event_recorder.rb
index 94bd05a17ba..0f1ceeb2a66 100644
--- a/lib/gitlab/resource_events/assignment_event_recorder.rb
+++ b/lib/gitlab/resource_events/assignment_event_recorder.rb
@@ -11,8 +11,6 @@ module Gitlab
end
def record
- return if Feature.disabled?(:record_issue_and_mr_assignee_events, parent.project)
-
case parent
when Issue
record_for_parent(
diff --git a/lib/sidebars/projects/menus/monitor_menu.rb b/lib/sidebars/projects/menus/monitor_menu.rb
index f1fc9f70ef8..a74448d0bdc 100644
--- a/lib/sidebars/projects/menus/monitor_menu.rb
+++ b/lib/sidebars/projects/menus/monitor_menu.rb
@@ -8,7 +8,6 @@ module Sidebars
def configure_menu_items
return false unless feature_enabled?
- add_item(metrics_dashboard_menu_item)
add_item(error_tracking_menu_item)
add_item(alert_management_menu_item)
add_item(incidents_menu_item)
@@ -49,23 +48,6 @@ module Sidebars
context.project.feature_available?(:monitor, context.current_user)
end
- def metrics_dashboard_menu_item
- return ::Sidebars::NilMenuItem.new(item_id: :metrics) if Feature.enabled?(:remove_monitor_metrics)
-
- unless can?(context.current_user, :metrics_dashboard, context.project)
- return ::Sidebars::NilMenuItem.new(item_id: :metrics)
- end
-
- ::Sidebars::MenuItem.new(
- title: _('Metrics'),
- link: project_metrics_dashboard_path(context.project),
- super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::MonitorMenu,
- active_routes: { path: 'metrics_dashboard#show' },
- container_html_options: { class: 'shortcuts-metrics' },
- item_id: :metrics
- )
- end
-
def error_tracking_menu_item
unless can?(context.current_user, :read_sentry_issue, context.project)
return ::Sidebars::NilMenuItem.new(item_id: :error_tracking)
diff --git a/lib/sidebars/projects/super_sidebar_menus/monitor_menu.rb b/lib/sidebars/projects/super_sidebar_menus/monitor_menu.rb
index fb56f6f3792..6e64ac01ffa 100644
--- a/lib/sidebars/projects/super_sidebar_menus/monitor_menu.rb
+++ b/lib/sidebars/projects/super_sidebar_menus/monitor_menu.rb
@@ -17,7 +17,6 @@ module Sidebars
override :configure_menu_items
def configure_menu_items
[
- :metrics,
:error_tracking,
:alert_management,
:incidents,
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 437f090921d..accf2afca30 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -24926,6 +24926,12 @@ msgstr ""
msgid "IssueTracker|Use Bugzilla as this project's issue tracker. %{docs_link}"
msgstr ""
+msgid "IssueTracker|Use ClickUp as this project's issue tracker. %{docs_link}"
+msgstr ""
+
+msgid "IssueTracker|Use Clickup as this project's issue tracker."
+msgstr ""
+
msgid "IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker."
msgstr ""
@@ -39395,6 +39401,9 @@ msgstr ""
msgid "Runners|Runners are either:"
msgstr ""
+msgid "Runners|Runners are grouped when they have the same authentication token. This happens when you re-use a runner configuration in more than one runner manager. %{linkStart}How does this work?%{linkEnd}"
+msgstr ""
+
msgid "Runners|Runners are the agents that run your CI/CD jobs. Follow the %{linkStart}installation and registration instructions%{linkEnd} to set up a runner."
msgstr ""
diff --git a/scripts/verify-tff-mapping b/scripts/verify-tff-mapping
index 86ab7548b19..496e0b75f84 100755
--- a/scripts/verify-tff-mapping
+++ b/scripts/verify-tff-mapping
@@ -220,6 +220,17 @@ tests = [
explanation: 'https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/1683#note_1385966977',
source: 'app/finders/members_finder.rb',
expected: ['spec/finders/members_finder_spec.rb', 'spec/graphql/types/project_member_relation_enum_spec.rb']
+ },
+
+ {
+ explanation: 'Map FOSS rake tasks',
+ source: 'lib/tasks/import.rake',
+ expected: ['spec/tasks/import_rake_spec.rb']
+ },
+ {
+ explanation: 'Map EE rake tasks',
+ source: 'ee/lib/tasks/geo.rake',
+ expected: ['ee/spec/tasks/geo_rake_spec.rb']
}
]
diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb
index 10568d7f1cd..74545c6eec1 100644
--- a/spec/factories/integrations.rb
+++ b/spec/factories/integrations.rb
@@ -197,6 +197,12 @@ FactoryBot.define do
issue_tracker
end
+ factory :clickup_integration, class: 'Integrations::Clickup' do
+ project
+ active { true }
+ issue_tracker
+ end
+
trait :issue_tracker do
transient do
create_data { true }
diff --git a/spec/features/monitor_sidebar_link_spec.rb b/spec/features/monitor_sidebar_link_spec.rb
index 6a1413c04f6..6e464cb8752 100644
--- a/spec/features/monitor_sidebar_link_spec.rb
+++ b/spec/features/monitor_sidebar_link_spec.rb
@@ -11,7 +11,6 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category
before do
project.add_role(user, role) if role
sign_in(user)
- stub_feature_flags(remove_monitor_metrics: false)
end
shared_examples 'shows Monitor menu based on the access level' do
@@ -53,7 +52,6 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category
expect(page).to have_link('Incidents', href: project_incidents_path(project))
expect(page).to have_link('Environments', href: project_environments_path(project))
- expect(page).not_to have_link('Metrics', href: project_metrics_dashboard_path(project))
expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).not_to have_link('Error Tracking', href: project_error_tracking_index_path(project))
expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project))
@@ -85,7 +83,6 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category
expect(page).to have_link('Incidents', href: project_incidents_path(project))
expect(page).to have_link('Environments', href: project_environments_path(project))
- expect(page).not_to have_link('Metrics', href: project_metrics_dashboard_path(project))
expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).not_to have_link('Error Tracking', href: project_error_tracking_index_path(project))
expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project))
@@ -99,7 +96,6 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category
it 'has the correct `Monitor` menu items' do
visit project_issues_path(project)
- expect(page).to have_link('Metrics', href: project_metrics_dashboard_path(project))
expect(page).to have_link('Incidents', href: project_incidents_path(project))
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).to have_link('Error Tracking', href: project_error_tracking_index_path(project))
@@ -116,7 +112,6 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category
it 'has the correct `Monitor` menu items' do
visit project_issues_path(project)
- expect(page).to have_link('Metrics', href: project_metrics_dashboard_path(project))
expect(page).to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).to have_link('Incidents', href: project_incidents_path(project))
expect(page).to have_link('Environments', href: project_environments_path(project))
@@ -132,7 +127,6 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category
it 'has the correct `Monitor` menu items' do
visit project_issues_path(project)
- expect(page).to have_link('Metrics', href: project_metrics_dashboard_path(project))
expect(page).to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).to have_link('Incidents', href: project_incidents_path(project))
expect(page).to have_link('Environments', href: project_environments_path(project))
diff --git a/spec/features/projects/integrations/user_activates_issue_tracker_spec.rb b/spec/features/projects/integrations/user_activates_issue_tracker_spec.rb
index d2c48cb2af0..9fc91e03c94 100644
--- a/spec/features/projects/integrations/user_activates_issue_tracker_spec.rb
+++ b/spec/features/projects/integrations/user_activates_issue_tracker_spec.rb
@@ -89,4 +89,5 @@ RSpec.describe 'User activates issue tracker', :js, feature_category: :integrati
it_behaves_like 'external issue tracker activation', tracker: 'Bugzilla'
it_behaves_like 'external issue tracker activation', tracker: 'Custom issue tracker'
it_behaves_like 'external issue tracker activation', tracker: 'EWM', skip_test: true
+ it_behaves_like 'external issue tracker activation', tracker: 'ClickUp', skip_new_issue_url: true
end
diff --git a/spec/features/projects/integrations/user_activates_jira_spec.rb b/spec/features/projects/integrations/user_activates_jira_spec.rb
index e4b10aeb340..03d5e68d2aa 100644
--- a/spec/features/projects/integrations/user_activates_jira_spec.rb
+++ b/spec/features/projects/integrations/user_activates_jira_spec.rb
@@ -48,7 +48,7 @@ RSpec.describe 'User activates Jira', :js, feature_category: :integrations do
it 'activates the Jira integration' do
stub_request(:get, test_url).with(basic_auth: %w(username password))
- .to_raise(JIRA::HTTPError.new(double(message: 'message')))
+ .to_raise(JIRA::HTTPError.new(double(message: 'message', code: '200')))
visit_project_integration('Jira')
fill_form
diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb
index 532dd7d0a84..31f4e9dcf95 100644
--- a/spec/features/projects/navbar_spec.rb
+++ b/spec/features/projects/navbar_spec.rb
@@ -20,7 +20,6 @@ RSpec.describe 'Project navbar', :with_license, feature_category: :projects do
stub_config(registry: { enabled: false })
stub_feature_flags(harbor_registry_integration: false)
stub_feature_flags(ml_experiment_tracking: false)
- stub_feature_flags(remove_monitor_metrics: false)
insert_package_nav(_('Deployments'))
insert_infrastructure_registry_nav
insert_infrastructure_google_cloud_nav
diff --git a/spec/features/projects/settings/monitor_settings_spec.rb b/spec/features/projects/settings/monitor_settings_spec.rb
index 1367ffb0009..89fee1cdb49 100644
--- a/spec/features/projects/settings/monitor_settings_spec.rb
+++ b/spec/features/projects/settings/monitor_settings_spec.rb
@@ -19,8 +19,8 @@ RSpec.describe 'Projects > Settings > For a forked project', :js, feature_catego
visit project_path(project)
wait_for_requests
- expect(page).to have_selector('.sidebar-sub-level-items a[aria-label="Monitor"]',
- text: 'Monitor', visible: :hidden)
+ expect(page).to have_selector('.sidebar-sub-level-items a[aria-label="Error Tracking"]',
+ text: 'Error Tracking', visible: :hidden)
end
end
diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb
index 1d4ab242308..e90e540ae32 100644
--- a/spec/features/projects/user_uses_shortcuts_spec.rb
+++ b/spec/features/projects/user_uses_shortcuts_spec.rb
@@ -9,7 +9,6 @@ RSpec.describe 'User uses shortcuts', :js, feature_category: :projects do
before do
sign_in(user)
- stub_feature_flags(remove_monitor_metrics: false)
visit(project_path(project))
@@ -183,16 +182,6 @@ RSpec.describe 'User uses shortcuts', :js, feature_category: :projects do
end
end
- context 'when navigating to the Monitor pages' do
- it 'redirects to the Metrics page' do
- find('body').native.send_key('g')
- find('body').native.send_key('l')
-
- expect(page).to have_active_navigation('Monitor')
- expect(page).to have_active_sub_navigation('Metrics')
- end
- end
-
context 'when navigating to the Infrastructure pages' do
it 'redirects to the Kubernetes page' do
find('body').native.send_key('g')
diff --git a/spec/frontend/ci/ci_lint/mock_data.js b/spec/frontend/ci/ci_lint/mock_data.js
index 05582470dfa..1a9888817d0 100644
--- a/spec/frontend/ci/ci_lint/mock_data.js
+++ b/spec/frontend/ci/ci_lint/mock_data.js
@@ -1,4 +1,5 @@
import { mockJobs } from 'jest/ci/pipeline_editor/mock_data';
+import { convertObjectPropsToSnakeCase } from '~/lib/utils/common_utils';
export const mockLintDataError = {
data: {
@@ -6,7 +7,11 @@ export const mockLintDataError = {
errors: ['Error message'],
warnings: ['Warning message'],
valid: false,
- jobs: mockJobs,
+ jobs: mockJobs.map((j) => {
+ const job = { ...j, tags: j.tagList };
+ delete job.tagList;
+ return job;
+ }),
},
},
};
@@ -17,7 +22,21 @@ export const mockLintDataValid = {
errors: [],
warnings: [],
valid: true,
- jobs: mockJobs,
+ jobs: mockJobs.map((j) => {
+ const job = { ...j, tags: j.tagList };
+ delete job.tagList;
+ return job;
+ }),
},
},
};
+
+export const mockLintDataErrorRest = {
+ ...mockLintDataError.data.lintCI,
+ jobs: mockJobs.map((j) => convertObjectPropsToSnakeCase(j)),
+};
+
+export const mockLintDataValidRest = {
+ ...mockLintDataValid.data.lintCI,
+ jobs: mockJobs.map((j) => convertObjectPropsToSnakeCase(j)),
+};
diff --git a/spec/frontend/ci/pipeline_editor/components/validate/ci_validate_spec.js b/spec/frontend/ci/pipeline_editor/components/validate/ci_validate_spec.js
index 2349816fa86..f2818277c59 100644
--- a/spec/frontend/ci/pipeline_editor/components/validate/ci_validate_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/validate/ci_validate_spec.js
@@ -1,15 +1,20 @@
+import Vue from 'vue';
import { GlAlert, GlDisclosureDropdown, GlIcon, GlLoadingIcon, GlPopover } from '@gitlab/ui';
-import { nextTick } from 'vue';
-import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
+import MockAdapter from 'axios-mock-adapter';
+
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+
+import axios from '~/lib/utils/axios_utils';
+import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
+import { resolvers } from '~/ci/pipeline_editor/graphql/resolvers';
import CiLintResults from '~/ci/pipeline_editor/components/lint/ci_lint_results.vue';
import CiValidate, { i18n } from '~/ci/pipeline_editor/components/validate/ci_validate.vue';
import ValidatePipelinePopover from '~/ci/pipeline_editor/components/popovers/validate_pipeline_popover.vue';
import getBlobContent from '~/ci/pipeline_editor/graphql/queries/blob_content.query.graphql';
-import lintCIMutation from '~/ci/pipeline_editor/graphql/mutations/client/lint_ci.mutation.graphql';
import { pipelineEditorTrackingOptions } from '~/ci/pipeline_editor/constants';
import {
mockBlobContentQueryResponse,
@@ -17,68 +22,45 @@ import {
mockCiYml,
mockSimulatePipelineHelpPagePath,
} from '../../mock_data';
-import { mockLintDataError, mockLintDataValid } from '../../../ci_lint/mock_data';
+import {
+ mockLintDataError,
+ mockLintDataValid,
+ mockLintDataErrorRest,
+ mockLintDataValidRest,
+} from '../../../ci_lint/mock_data';
+
+let mockAxios;
+
+Vue.use(VueApollo);
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+const defaultProvide = {
+ ciConfigPath: '/path/to/ci-config',
+ ciLintPath: mockCiLintPath,
+ currentBranch: 'main',
+ projectFullPath: '/path/to/project',
+ validateTabIllustrationPath: '/path/to/img',
+ simulatePipelineHelpPagePath: mockSimulatePipelineHelpPagePath,
+};
describe('Pipeline Editor Validate Tab', () => {
let wrapper;
- let mockApollo;
let mockBlobContentData;
let trackingSpy;
- const createComponent = ({
- props,
- stubs,
- options,
- isBlobLoading = false,
- isSimulationLoading = false,
- } = {}) => {
+ const createComponent = ({ props, stubs } = {}) => {
+ const handlers = [[getBlobContent, mockBlobContentData]];
+ const mockApollo = createMockApollo(handlers, resolvers);
+
wrapper = shallowMountExtended(CiValidate, {
propsData: {
ciFileContent: mockCiYml,
...props,
},
- provide: {
- ciConfigPath: '/path/to/ci-config',
- ciLintPath: mockCiLintPath,
- currentBranch: 'main',
- projectFullPath: '/path/to/project',
- validateTabIllustrationPath: '/path/to/img',
- simulatePipelineHelpPagePath: mockSimulatePipelineHelpPagePath,
- },
- stubs,
- mocks: {
- $apollo: {
- queries: {
- initialBlobContent: {
- loading: isBlobLoading,
- },
- },
- mutations: {
- lintCiMutation: {
- loading: isSimulationLoading,
- },
- },
- },
- },
- ...options,
- });
- };
-
- const createComponentWithApollo = ({ props, stubs } = {}) => {
- const handlers = [[getBlobContent, mockBlobContentData]];
- mockApollo = createMockApollo(handlers);
-
- createComponent({
- props,
stubs,
- options: {
- localVue,
- apolloProvider: mockApollo,
- mocks: {},
+ provide: {
+ ...defaultProvide,
},
+ apolloProvider: mockApollo,
});
};
@@ -96,12 +78,21 @@ describe('Pipeline Editor Validate Tab', () => {
const findResultsCta = () => wrapper.findByTestId('resimulate-pipeline-button');
beforeEach(() => {
+ mockAxios = new MockAdapter(axios);
+ mockAxios.onPost(defaultProvide.ciLintPath).reply(HTTP_STATUS_OK, mockLintDataValidRest);
+
mockBlobContentData = jest.fn();
});
+ afterEach(() => {
+ mockAxios.restore();
+ });
+
describe('while initial CI content is loading', () => {
beforeEach(() => {
- createComponent({ isBlobLoading: true });
+ mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse);
+
+ createComponent();
});
it('renders disabled CTA with tooltip', () => {
@@ -113,7 +104,7 @@ describe('Pipeline Editor Validate Tab', () => {
describe('after initial CI content is loaded', () => {
beforeEach(async () => {
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse);
- await createComponentWithApollo({ stubs: { GlPopover, ValidatePipelinePopover } });
+ await createComponent({ stubs: { GlPopover, ValidatePipelinePopover } });
});
it('renders disabled pipeline source dropdown', () => {
@@ -137,10 +128,9 @@ describe('Pipeline Editor Validate Tab', () => {
describe('simulating the pipeline', () => {
beforeEach(async () => {
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse);
- await createComponentWithApollo();
+ await createComponent();
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockLintDataValid);
});
afterEach(() => {
@@ -158,32 +148,32 @@ describe('Pipeline Editor Validate Tab', () => {
});
it('renders loading state while simulation is ongoing', async () => {
- findCta().vm.$emit('click');
- await nextTick();
+ await findCta().vm.$emit('click');
expect(findLoadingIcon().exists()).toBe(true);
expect(findCancelBtn().exists()).toBe(true);
expect(findCta().props('loading')).toBe(true);
});
- it('calls mutation with the correct input', async () => {
- await findCta().vm.$emit('click');
+ it('calls endpoint with the correct input', async () => {
+ findCta().vm.$emit('click');
+
+ await waitForPromises();
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: lintCIMutation,
- variables: {
- dry: true,
+ expect(mockAxios.history.post).toHaveLength(1);
+ expect(mockAxios.history.post[0].data).toBe(
+ JSON.stringify({
content: mockCiYml,
- endpoint: mockCiLintPath,
- },
- });
+ dry_run: true,
+ }),
+ );
});
describe('when results are successful', () => {
beforeEach(async () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockLintDataValid);
- await findCta().vm.$emit('click');
+ findCta().vm.$emit('click');
+
+ await waitForPromises();
});
it('renders success alert', () => {
@@ -210,8 +200,10 @@ describe('Pipeline Editor Validate Tab', () => {
describe('when results have errors', () => {
beforeEach(async () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockLintDataError);
- await findCta().vm.$emit('click');
+ mockAxios.onPost(defaultProvide.ciLintPath).reply(HTTP_STATUS_OK, mockLintDataErrorRest);
+ findCta().vm.$emit('click');
+
+ await waitForPromises();
});
it('renders error alert', () => {
@@ -236,11 +228,11 @@ describe('Pipeline Editor Validate Tab', () => {
describe('when CI content has changed after a simulation', () => {
beforeEach(async () => {
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse);
- await createComponentWithApollo();
+ await createComponent();
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockLintDataValid);
- await findCta().vm.$emit('click');
+ findCta().vm.$emit('click');
+ await waitForPromises();
});
afterEach(() => {
@@ -267,25 +259,26 @@ describe('Pipeline Editor Validate Tab', () => {
});
it('calls mutation with new content', async () => {
- await wrapper.setProps({ ciFileContent: 'new yaml content' });
- await findResultsCta().vm.$emit('click');
-
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(2);
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: lintCIMutation,
- variables: {
- dry: true,
- content: 'new yaml content',
- endpoint: mockCiLintPath,
- },
- });
+ const newContent = 'new yaml content';
+ await wrapper.setProps({ ciFileContent: newContent });
+ findResultsCta().vm.$emit('click');
+
+ await waitForPromises();
+
+ expect(mockAxios.history.post).toHaveLength(2);
+ expect(mockAxios.history.post[1].data).toBe(
+ JSON.stringify({
+ content: newContent,
+ dry_run: true,
+ }),
+ );
});
});
describe('canceling a simulation', () => {
beforeEach(async () => {
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse);
- await createComponentWithApollo();
+ await createComponent();
});
it('returns to init state', async () => {
@@ -294,9 +287,7 @@ describe('Pipeline Editor Validate Tab', () => {
expect(findCiLintResults().exists()).toBe(false);
// mutations should have successful results
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockLintDataValid);
- findCta().vm.$emit('click');
- await nextTick();
+ await findCta().vm.$emit('click');
// cancel before simulation succeeds
expect(findCancelBtn().exists()).toBe(true);
diff --git a/spec/frontend/ci/pipeline_editor/mock_data.js b/spec/frontend/ci/pipeline_editor/mock_data.js
index 865dd34fbfe..dddf2dd7602 100644
--- a/spec/frontend/ci/pipeline_editor/mock_data.js
+++ b/spec/frontend/ci/pipeline_editor/mock_data.js
@@ -43,7 +43,7 @@ job_build:
export const mockCiTemplateQueryResponse = {
data: {
project: {
- id: 'project-1',
+ id: 'gid://gitlab/Project/1',
ciTemplate: {
content: mockCiYml,
},
@@ -54,7 +54,7 @@ export const mockCiTemplateQueryResponse = {
export const mockBlobContentQueryResponse = {
data: {
project: {
- id: 'project-1',
+ id: 'gid://gitlab/Project/1',
repository: { blobs: { nodes: [{ id: 'blob-1', rawBlob: mockCiYml }] } },
},
},
@@ -62,13 +62,13 @@ export const mockBlobContentQueryResponse = {
export const mockBlobContentQueryResponseNoCiFile = {
data: {
- project: { id: 'project-1', repository: { blobs: { nodes: [] } } },
+ project: { id: 'gid://gitlab/Project/1', repository: { blobs: { nodes: [] } } },
},
};
export const mockBlobContentQueryResponseEmptyCiFile = {
data: {
- project: { id: 'project-1', repository: { blobs: { nodes: [{ rawBlob: '' }] } } },
+ project: { id: 'gid://gitlab/Project/1', repository: { blobs: { nodes: [{ rawBlob: '' }] } } },
},
};
diff --git a/spec/frontend/ci/runner/components/runner_details_spec.js b/spec/frontend/ci/runner/components/runner_details_spec.js
index c2d9e86aa91..47e09a1a7fd 100644
--- a/spec/frontend/ci/runner/components/runner_details_spec.js
+++ b/spec/frontend/ci/runner/components/runner_details_spec.js
@@ -1,4 +1,5 @@
import { GlSprintf, GlIntersperse } from '@gitlab/ui';
+import { s__ } from '~/locale';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { useFakeDate } from 'helpers/fake_date';
@@ -24,6 +25,7 @@ describe('RunnerDetails', () => {
useFakeDate(mockNow);
const findDetailGroups = () => wrapper.findComponent(RunnerGroups);
+ const findDdContent = (label) => findDd(label, wrapper).text().replace(/\s+/g, ' ');
const createComponent = ({ props = {}, stubs, mountFn = shallowMountExtended } = {}) => {
wrapper = mountFn(RunnerDetails, {
@@ -61,6 +63,7 @@ describe('RunnerDetails', () => {
${'Maximum job timeout'} | ${{ maximumTimeout: 10 * 60 + 5 }} | ${'10 minutes 5 seconds'}
${'Token expiry'} | ${{ tokenExpiresAt: mockOneHourAgo }} | ${'1 hour ago'}
${'Token expiry'} | ${{ tokenExpiresAt: null }} | ${'Never expires'}
+ ${'Runners'} | ${{ managers: { count: 2 } }} | ${'2'}
`('"$field" field', ({ field, runner, expectedValue }) => {
beforeEach(() => {
createComponent({
@@ -94,7 +97,7 @@ describe('RunnerDetails', () => {
stubs,
});
- expect(findDd('Tags', wrapper).text().replace(/\s+/g, ' ')).toBe('tag-1 tag-2');
+ expect(findDdContent(s__('Runners|Tags'))).toBe('tag-1 tag-2');
});
it('displays "None" when runner has no tags', () => {
@@ -105,7 +108,29 @@ describe('RunnerDetails', () => {
stubs,
});
- expect(findDd('Tags', wrapper).text().replace(/\s+/g, ' ')).toBe('None');
+ expect(findDdContent(s__('Runners|Tags'))).toBe('None');
+ });
+ });
+
+ describe('"Runners" field', () => {
+ it.each`
+ count | expected
+ ${0} | ${'0'}
+ ${1} | ${'1'}
+ ${1000} | ${'1,000'}
+ `('displays runner managers count of $count', ({ count, expected }) => {
+ createComponent({
+ props: {
+ runner: {
+ ...mockRunner,
+ managers: {
+ count,
+ },
+ },
+ },
+ });
+
+ expect(findDdContent(s__('Runners|Runners'))).toBe(expected);
});
});
diff --git a/spec/graphql/types/projects/service_type_enum_spec.rb b/spec/graphql/types/projects/service_type_enum_spec.rb
index 8b444a08c3b..a5b1ba24a44 100644
--- a/spec/graphql/types/projects/service_type_enum_spec.rb
+++ b/spec/graphql/types/projects/service_type_enum_spec.rb
@@ -15,6 +15,7 @@ RSpec.describe GitlabSchema.types['ServiceType'] do
BUGZILLA_SERVICE
BUILDKITE_SERVICE
CAMPFIRE_SERVICE
+ CLICKUP_SERVICE
CONFLUENCE_SERVICE
CUSTOM_ISSUE_TRACKER_SERVICE
DATADOG_SERVICE
diff --git a/spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb
index d40041d890e..79500f43394 100644
--- a/spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb
@@ -184,6 +184,44 @@ RSpec.describe Banzai::Filter::References::ExternalIssueReferenceFilter, feature
end
end
+ context "clickup project" do
+ before_all do
+ create(:clickup_integration, project: project)
+ end
+
+ before do
+ project.update!(issues_enabled: false)
+ end
+
+ context "with right markdown" do
+ let(:issue) { ExternalIssue.new("PRJ-123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "with underscores in the prefix" do
+ let(:issue) { ExternalIssue.new("PRJ_1-123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "with a hash prefix and alphanumeric" do
+ let(:issue) { ExternalIssue.new("#abcd123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "with prefix and alphanumeric" do
+ let(:issue) { ExternalIssue.new("CU-abcd123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+ end
+
context "jira project" do
let_it_be(:service) { create(:jira_integration, project: project) }
diff --git a/spec/lib/gitlab/background_migration/backfill_resource_link_events_spec.rb b/spec/lib/gitlab/background_migration/backfill_resource_link_events_spec.rb
new file mode 100644
index 00000000000..4b8495cc004
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_resource_link_events_spec.rb
@@ -0,0 +1,197 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillResourceLinkEvents, schema: 20230426085615, feature_category: :team_planning do
+ include MigrationHelpers::WorkItemTypesHelper
+
+ let(:users) { table(:users) }
+ let(:namespaces) { table(:namespaces) }
+ let(:notes) { table(:notes) }
+ let(:system_note_metadata) { table(:system_note_metadata) }
+ let(:resource_link_events) { table(:resource_link_events) }
+ let(:projects) { table(:projects) }
+ let(:issues) { table(:issues) }
+ let(:work_item_issue_type_id) { table(:work_item_types).find_by(namespace_id: nil, name: 'Issue').id }
+ let(:work_item_task_type_id) { table(:work_item_types).find_by(namespace_id: nil, name: 'Task').id }
+
+ # rubocop:disable Layout/LineLength
+ let!(:namespace) { namespaces.create!(name: "namespace", path: "namespace") }
+ let!(:project) { projects.create!(namespace_id: namespace.id, project_namespace_id: namespace.id) }
+ let!(:issue) { issues.create!(iid: 100, project_id: project.id, namespace_id: project.project_namespace_id, work_item_type_id: work_item_issue_type_id) }
+ let!(:work_item) { issues.create!(iid: 200, project_id: project.id, namespace_id: project.project_namespace_id, work_item_type_id: work_item_task_type_id) }
+ let!(:user) { users.create!(name: 'user', projects_limit: 10) }
+
+ # Given a system note generated for a child work item, "Added #100 as parent issue",
+ # the migration searches for the parent issue with iid #100 using the child work item's project scope.
+ # Creating antoher issue that has the identical iid under another project ensures the migration is picking up the correct issue.
+ let!(:other_namespace) { namespaces.create!(name: "other_namespace", path: "other_namespace") }
+ let!(:other_project) { projects.create!(namespace_id: other_namespace.id, project_namespace_id: other_namespace.id) }
+ let!(:other_issue) { issues.create!(iid: issue.iid, project_id: other_project.id, namespace_id: other_project.project_namespace_id, work_item_type_id: work_item_issue_type_id) }
+ let!(:other_work_item) { issues.create!(iid: 200, project_id: other_project.id, namespace_id: other_project.project_namespace_id, work_item_type_id: work_item_task_type_id) }
+ # rubocop:enable Layout/LineLength
+
+ subject(:migration) do
+ described_class.new(
+ start_id: system_note_metadata.minimum(:id),
+ end_id: system_note_metadata.maximum(:id),
+ batch_table: :system_note_metadata,
+ batch_column: :id,
+ sub_batch_size: 1,
+ pause_ms: 0,
+ connection: ActiveRecord::Base.connection
+ )
+ end
+
+ describe '#perform' do
+ it 'does nothing when relevant notes do not exist' do
+ expect { migration.perform }
+ .to not_change { resource_link_events.count }
+ end
+
+ shared_examples 'a resource_link_event is correctly created' do
+ it "correctly backfills a resource_link_event record", :aggregate_failures do
+ expect { migration.perform }
+ .to change { resource_link_events.count }.from(0).to(1)
+
+ expect(resource_link_events.last.attributes).to match(a_hash_including(expected_attributes))
+ expect(resource_link_events.last.created_at).to be_like_time(system_note.created_at)
+ end
+ end
+
+ context "for 'relate_to_parent' system_note_metadata record" do
+ let!(:system_note) do
+ create_relate_to_parent_note(parent: issue, child: work_item, issue_type_name: issue_type_name)
+ end
+
+ let(:expected_attributes) do
+ {
+ "action" => described_class::ResourceLinkEvent.actions[:add],
+ "user_id" => user.id,
+ "issue_id" => issue.id,
+ "child_work_item_id" => work_item.id,
+ "system_note_metadata_id" => system_note.id
+ }
+ end
+
+ context 'when issue_type_name is `issue`' do
+ let(:issue_type_name) { 'issue' }
+
+ it_behaves_like 'a resource_link_event is correctly created'
+ end
+
+ context "when issue_type_name is not `issue`" do
+ let(:issue_type_name) { 'objective' }
+
+ it_behaves_like 'a resource_link_event is correctly created'
+ end
+ end
+
+ context "for 'unrelate_to_parent' system_note_metadata record" do
+ let!(:system_note) do
+ create_unrelate_from_parent_note(parent: issue, child: work_item, issue_type_name: issue_type_name)
+ end
+
+ let(:expected_attributes) do
+ {
+ "action" => described_class::ResourceLinkEvent.actions[:remove],
+ "user_id" => user.id,
+ "issue_id" => issue.id,
+ "child_work_item_id" => work_item.id,
+ "system_note_metadata_id" => system_note.id
+ }
+ end
+
+ context 'when issue_type_name is `issue`' do
+ let(:issue_type_name) { 'issue' }
+
+ it_behaves_like 'a resource_link_event is correctly created'
+ end
+
+ context "when issue_type_name is not `issue`" do
+ let(:issue_type_name) { 'objective' }
+
+ it_behaves_like 'a resource_link_event is correctly created'
+ end
+ end
+
+ context "when a backfilled note exists" do
+ let!(:backfilled_system_note) do
+ create_relate_to_parent_note(parent: other_issue, child: other_work_item, issue_type_name: 'issue')
+ end
+
+ let!(:backfilled_resource_link_event) do
+ resource_link_events.create!(
+ action: described_class::ResourceLinkEvent.actions[:add],
+ user_id: user.id,
+ issue_id: other_issue.id,
+ child_work_item_id: other_work_item.id,
+ created_at: backfilled_system_note.created_at,
+ system_note_metadata_id: backfilled_system_note.id)
+ end
+
+ before do
+ # Create two system notes for which resource_link_events should be created (backfilled)
+ create_relate_to_parent_note(parent: issue, child: work_item, issue_type_name: 'issue')
+ create_unrelate_from_parent_note(parent: issue, child: work_item, issue_type_name: 'objective')
+
+ # A backfilled resource_link_event exists for `backfilled_system_note`
+ # No resource_link_event record should be created for `backfilled_system_note`
+ # To test, update `backfilled_system_note` and check `backfilled_resource_link_event` does not change
+ backfilled_system_note.update!(created_at: 1.week.ago)
+ end
+
+ it "correctly backfills the system notes without those that have been backfilled" do
+ expect { migration.perform }
+ .to change { resource_link_events.count }.from(1).to(3)
+ .and not_change { backfilled_resource_link_event }
+ end
+ end
+
+ context 'with unexpected note content' do
+ context 'when note iid is prefixed' do
+ before do
+ note = notes.create!(
+ noteable_type: 'Issue',
+ noteable_id: work_item.id,
+ author_id: user.id,
+ # Cross-project linking is not supported currently.
+ # When an issue is referenced not in its own project,
+ # the iid is prefixed by the project name like gitlab#1
+ # Test the scenario to ensure no resource_link_event is wrongly created.
+ note: "added gitlab##{issue.iid} as parent issue"
+ )
+
+ system_note_metadata.create!(action: 'relate_to_parent', note_id: note.id)
+ end
+
+ it 'does not create resource_link_events record' do
+ expect { migration.perform }
+ .to not_change { resource_link_events.count }
+ end
+ end
+ end
+ end
+
+ def create_relate_to_parent_note(parent:, child:, issue_type_name:)
+ note = notes.create!(
+ noteable_type: 'Issue',
+ noteable_id: child.id,
+ author_id: user.id,
+ note: "added ##{parent.iid} as parent #{issue_type_name}"
+ )
+
+ system_note_metadata.create!(action: 'relate_to_parent', note_id: note.id)
+ end
+
+ def create_unrelate_from_parent_note(parent:, child:, issue_type_name:)
+ note = notes.create!(
+ noteable_type: 'Issue',
+ noteable_id: child.id,
+ author_id: user.id,
+ note: "removed parent #{issue_type_name} ##{parent.iid}"
+ )
+
+ system_note_metadata.create!(action: 'unrelate_from_parent', note_id: note.id)
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index e6829363922..72073ced21f 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -581,6 +581,7 @@ project:
- custom_issue_tracker_integration
- bugzilla_integration
- ewm_integration
+- clickup_integration
- external_wiki_integration
- mock_ci_integration
- mock_monitoring_integration
diff --git a/spec/lib/gitlab/jira_import_spec.rb b/spec/lib/gitlab/jira_import_spec.rb
index c0c1a28b9ff..64a5758d152 100644
--- a/spec/lib/gitlab/jira_import_spec.rb
+++ b/spec/lib/gitlab/jira_import_spec.rb
@@ -41,7 +41,7 @@ RSpec.describe Gitlab::JiraImport do
context 'when Jira connection is not valid' do
before do
WebMock.stub_request(:get, 'https://jira.example.com/rest/api/2/serverInfo')
- .to_raise(JIRA::HTTPError.new(double(message: 'Some failure.')))
+ .to_raise(JIRA::HTTPError.new(double(message: 'Some failure.', code: '400')))
end
it_behaves_like 'raise Jira import error', 'Unable to connect to the Jira instance. Please check your Jira integration configuration.'
diff --git a/spec/lib/gitlab/resource_events/assignment_event_recorder_spec.rb b/spec/lib/gitlab/resource_events/assignment_event_recorder_spec.rb
index b15f95dbd9c..768ff368602 100644
--- a/spec/lib/gitlab/resource_events/assignment_event_recorder_spec.rb
+++ b/spec/lib/gitlab/resource_events/assignment_event_recorder_spec.rb
@@ -76,16 +76,4 @@ RSpec.describe Gitlab::ResourceEvents::AssignmentEventRecorder, feature_category
end.to change { ResourceEvents::MergeRequestAssignmentEvent.count }.by(1)
end
end
-
- context 'when the record_issue_and_mr_assignee_events FF is off' do
- before do
- stub_feature_flags(record_issue_and_mr_assignee_events: false)
- end
-
- it 'does nothing' do
- expect do
- described_class.new(parent: mr_with_one_assignee, old_assignees: [user2, user3]).record
- end.not_to change { mr_with_one_assignee.assignment_events.count }
- end
- end
end
diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/lib/gitlab/task_helpers_spec.rb
index 0c43dd15e8c..0c43dd15e8c 100644
--- a/spec/tasks/gitlab/task_helpers_spec.rb
+++ b/spec/lib/gitlab/task_helpers_spec.rb
diff --git a/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb b/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb
index aa1e67085cd..8bb7e8c8518 100644
--- a/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb
@@ -57,10 +57,6 @@ RSpec.describe Sidebars::Projects::Menus::MonitorMenu do
end
context 'Menu items' do
- before do
- stub_feature_flags(remove_monitor_metrics: false)
- end
-
subject { described_class.new(context).renderable_items.index { |e| e.item_id == item_id } }
shared_examples 'access rights checks' do
@@ -73,12 +69,6 @@ RSpec.describe Sidebars::Projects::Menus::MonitorMenu do
end
end
- describe 'Metrics Dashboard' do
- let(:item_id) { :metrics }
-
- it_behaves_like 'access rights checks'
- end
-
describe 'Error Tracking' do
let(:item_id) { :error_tracking }
diff --git a/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb b/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb
index 9344bbc76db..e59062c7eaf 100644
--- a/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb
+++ b/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb
@@ -15,7 +15,6 @@ RSpec.describe Sidebars::Projects::SuperSidebarMenus::MonitorMenu, feature_categ
it 'defines list of NilMenuItem placeholders' do
expect(items.map(&:class).uniq).to eq([Sidebars::NilMenuItem])
expect(items.map(&:item_id)).to eq([
- :metrics,
:error_tracking,
:alert_management,
:incidents,
diff --git a/spec/migrations/20230426085615_queue_backfill_resource_link_events_spec.rb b/spec/migrations/20230426085615_queue_backfill_resource_link_events_spec.rb
new file mode 100644
index 00000000000..d0d948dad9d
--- /dev/null
+++ b/spec/migrations/20230426085615_queue_backfill_resource_link_events_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe QueueBackfillResourceLinkEvents, feature_category: :team_planning do
+ include MigrationHelpers::WorkItemTypesHelper
+
+ let(:users) { table(:users) }
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:issues) { table(:issues) }
+ let(:notes) { table(:notes) }
+ let(:system_note_metadata) { table(:system_note_metadata) }
+
+ let(:namespace) { namespaces.create!(name: "namespace", path: "namespace") }
+ let(:project) { projects.create!(namespace_id: namespace.id, project_namespace_id: namespace.id) }
+ let(:work_item_issue_type_id) { table(:work_item_types).find_by(namespace_id: nil, name: 'Issue').id }
+ let(:issue) { issues.create!(project_id: project.id, namespace_id: project.project_namespace_id, work_item_type_id: work_item_issue_type_id) } # rubocop:disable Layout/LineLength
+ let(:user) { users.create!(name: 'user', projects_limit: 10) }
+
+ let!(:system_note_metadata_record1) do
+ note = notes.create!(noteable_type: 'Issue', noteable_id: issue.id, author_id: user.id, note: "foobar")
+
+ system_note_metadata.create!(action: 'foobar', note_id: note.id)
+ end
+
+ let!(:batched_migration) { described_class::MIGRATION }
+
+ describe '#up' do
+ %w[relate_to_parent unrelate_from_parent].each do |action_value|
+ context 'when system_note_metadata table has a row with targeted action values' do
+ let!(:system_note_metadata_record2) do
+ note = notes.create!(noteable_type: 'Issue', noteable_id: issue.id, author_id: user.id, note: "foobar")
+
+ system_note_metadata.create!(action: action_value, note_id: note.id)
+ end
+
+ let!(:system_note_metadata_record3) do
+ note = notes.create!(noteable_type: 'Issue', noteable_id: issue.id, author_id: user.id, note: "foobar")
+
+ system_note_metadata.create!(action: action_value, note_id: note.id)
+ end
+
+ it 'schedules a new batched migration with the lowest system_note_metadat record id' do
+ reversible_migration do |migration|
+ migration.before -> {
+ expect(batched_migration).not_to have_scheduled_batched_migration
+ }
+
+ migration.after -> {
+ expect(batched_migration).to have_scheduled_batched_migration(
+ table_name: :system_note_metadata,
+ column_name: :id,
+ interval: described_class::DELAY_INTERVAL,
+ batch_size: described_class::BATCH_SIZE,
+ sub_batch_size: described_class::SUB_BATCH_SIZE,
+ batch_min_value: system_note_metadata_record2.id
+ )
+ }
+ end
+ end
+ end
+ end
+
+ context 'when system_note_metadata table does not ahve a row with the targeted action values' do
+ it 'does not a new batched migration' do
+ reversible_migration do |migration|
+ migration.before -> {
+ expect(batched_migration).not_to have_scheduled_batched_migration
+ }
+
+ migration.after -> {
+ expect(batched_migration).not_to have_scheduled_batched_migration
+ }
+ end
+ end
+ end
+ end
+
+ describe '#down' do
+ it 'deletes all batched migration records' do
+ migrate!
+ schema_migrate_down!
+
+ expect(batched_migration).not_to have_scheduled_batched_migration
+ end
+ end
+end
diff --git a/spec/models/ci/catalog/listing_spec.rb b/spec/models/ci/catalog/listing_spec.rb
index 93d70a3f63e..159b70d7f8f 100644
--- a/spec/models/ci/catalog/listing_spec.rb
+++ b/spec/models/ci/catalog/listing_spec.rb
@@ -4,8 +4,8 @@ require 'spec_helper'
RSpec.describe Ci::Catalog::Listing, feature_category: :pipeline_composition do
let_it_be(:namespace) { create(:group) }
- let_it_be(:project_1) { create(:project, namespace: namespace) }
- let_it_be(:project_2) { create(:project, namespace: namespace) }
+ let_it_be(:project_1) { create(:project, namespace: namespace, name: 'X Project') }
+ let_it_be(:project_2) { create(:project, namespace: namespace, name: 'B Project') }
let_it_be(:project_3) { create(:project) }
let_it_be(:user) { create(:user) }
@@ -34,11 +34,32 @@ RSpec.describe Ci::Catalog::Listing, feature_category: :pipeline_composition do
end
context 'when the namespace has catalog resources' do
- let!(:resource) { create(:catalog_resource, project: project_1) }
- let!(:other_namespace_resource) { create(:catalog_resource, project: project_3) }
+ let_it_be(:resource) { create(:catalog_resource, project: project_1) }
+ let_it_be(:resource_2) { create(:catalog_resource, project: project_2) }
+ let_it_be(:other_namespace_resource) { create(:catalog_resource, project: project_3) }
it 'contains only catalog resources for projects in that namespace' do
- is_expected.to contain_exactly(resource)
+ is_expected.to contain_exactly(resource, resource_2)
+ end
+
+ context 'with a sort parameter' do
+ subject(:resources) { list.resources(sort: sort) }
+
+ context 'when the sort is name ascending' do
+ let_it_be(:sort) { :name_asc }
+
+ it 'contains catalog resources for projects sorted by name' do
+ is_expected.to eq([resource_2, resource])
+ end
+ end
+
+ context 'when the sort is name descending' do
+ let_it_be(:sort) { :name_desc }
+
+ it 'contains catalog resources for projects sorted by name' do
+ is_expected.to eq([resource, resource_2])
+ end
+ end
end
end
end
diff --git a/spec/models/ci/catalog/resource_spec.rb b/spec/models/ci/catalog/resource_spec.rb
index a239bbad857..dfb7b311d96 100644
--- a/spec/models/ci/catalog/resource_spec.rb
+++ b/spec/models/ci/catalog/resource_spec.rb
@@ -3,8 +3,12 @@
require 'spec_helper'
RSpec.describe Ci::Catalog::Resource, feature_category: :pipeline_composition do
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, name: 'A') }
+ let_it_be(:project_2) { build(:project, name: 'Z') }
+ let_it_be(:project_3) { build(:project, name: 'L') }
let_it_be(:resource) { create(:catalog_resource, project: project) }
+ let_it_be(:resource_2) { create(:catalog_resource, project: project_2) }
+ let_it_be(:resource_3) { create(:catalog_resource, project: project_3) }
let_it_be(:releases) do
[
@@ -28,6 +32,30 @@ RSpec.describe Ci::Catalog::Resource, feature_category: :pipeline_composition do
end
end
+ describe '.order_by_created_at_desc' do
+ it 'returns catalog resources sorted by descending created at' do
+ ordered_resources = described_class.order_by_created_at_desc
+
+ expect(ordered_resources.to_a).to eq([resource_3, resource_2, resource])
+ end
+ end
+
+ describe '.order_by_name_desc' do
+ it 'returns catalog resources sorted by descending name' do
+ ordered_resources = described_class.order_by_name_desc
+
+ expect(ordered_resources.pluck(:name)).to eq(%w[Z L A])
+ end
+ end
+
+ describe '.order_by_name_asc' do
+ it 'returns catalog resources sorted by ascending name' do
+ ordered_resources = described_class.order_by_name_asc
+
+ expect(ordered_resources.pluck(:name)).to eq(%w[A L Z])
+ end
+ end
+
describe '#versions' do
it 'returns releases ordered by released date descending' do
expect(resource.versions).to eq(releases.reverse)
diff --git a/spec/models/integrations/clickup_spec.rb b/spec/models/integrations/clickup_spec.rb
new file mode 100644
index 00000000000..f83fb3ddabc
--- /dev/null
+++ b/spec/models/integrations/clickup_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Integrations::Clickup, feature_category: :integrations do
+ describe 'Validations' do
+ context 'when integration is active' do
+ before do
+ subject.active = true
+ end
+
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:issues_url) }
+
+ it_behaves_like 'issue tracker integration URL attribute', :project_url
+ it_behaves_like 'issue tracker integration URL attribute', :issues_url
+ end
+
+ context 'when integration is inactive' do
+ before do
+ subject.active = false
+ end
+
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ end
+ end
+
+ describe '#reference_pattern' do
+ it 'does allow project prefix on the reference' do
+ expect(subject.reference_pattern.match('PRJ-123')[:issue]).to eq('PRJ-123')
+ end
+
+ it 'allows a hash with an alphanumeric key on the reference' do
+ expect(subject.reference_pattern.match('#abcd123')[:issue]).to eq('abcd123')
+ end
+
+ it 'allows a global prefix with an alphanumeric key on the reference' do
+ expect(subject.reference_pattern.match('CU-abcd123')[:issue]).to eq('abcd123')
+ end
+ end
+
+ describe '#fields' do
+ it 'only returns the project_url and issues_url fields' do
+ expect(subject.fields.pluck(:name)).to eq(%w[project_url issues_url])
+ end
+ end
+end
diff --git a/spec/models/integrations/jira_spec.rb b/spec/models/integrations/jira_spec.rb
index 71dd543b3ec..9bb77f6d6d4 100644
--- a/spec/models/integrations/jira_spec.rb
+++ b/spec/models/integrations/jira_spec.rb
@@ -871,6 +871,8 @@ RSpec.describe Integrations::Jira, feature_category: :integrations do
expect(jira_integration).to have_received(:log_exception).with(
kind_of(StandardError),
message: 'Issue transition failed',
+ client_path: '/rest/api/2/issue/JIRA-123/transitions',
+ client_status: '400',
client_url: "http://jira.example.com"
)
end
@@ -1175,12 +1177,14 @@ RSpec.describe Integrations::Jira, feature_category: :integrations do
error_message = 'Some specific failure.'
WebMock.stub_request(:get, test_url).with(basic_auth: [username, password])
- .to_raise(JIRA::HTTPError.new(double(message: error_message)))
+ .to_raise(JIRA::HTTPError.new(double(message: error_message, code: '403')))
expect(jira_integration).to receive(:log_exception).with(
kind_of(JIRA::HTTPError),
message: 'Error sending message',
- client_url: 'http://jira.example.com'
+ client_url: 'http://jira.example.com',
+ client_path: '/rest/api/2/serverInfo',
+ client_status: '403'
)
expect(jira_integration.test(nil)).to eq(success: false, result: error_message)
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index a3d015fd768..9bab6fcd942 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -423,15 +423,77 @@ RSpec.describe Issue, feature_category: :team_planning do
let_it_be(:issue) { create(:issue, project: reusable_project) }
let_it_be(:incident) { create(:incident, project: reusable_project) }
- it 'gives issues with the given issue type' do
+ it 'returns issues with the given issue type' do
expect(described_class.with_issue_type('issue'))
.to contain_exactly(issue)
end
- it 'gives issues with the given issue type' do
+ it 'returns issues with the given issue types' do
expect(described_class.with_issue_type(%w(issue incident)))
.to contain_exactly(issue, incident)
end
+
+ it 'uses the work_item_types table for filtering' do
+ expect do
+ described_class.with_issue_type(:issue).to_a
+ end.to make_queries_matching(
+ %r{
+ INNER\sJOIN\s"work_item_types"\sON\s"work_item_types"\."id"\s=\s"issues"\."work_item_type_id"
+ \sWHERE\s"work_item_types"\."base_type"\s=\s0
+ }x
+ )
+ end
+
+ context 'when the issue_type_uses_work_item_types_table feature flag is disabled' do
+ before do
+ stub_feature_flags(issue_type_uses_work_item_types_table: false)
+ end
+
+ it 'uses the issue_type column for filtering' do
+ expect do
+ described_class.with_issue_type(:issue).to_a
+ end.to make_queries_matching(/"issues"\."issue_type" = 0/)
+ end
+ end
+ end
+
+ describe '.without_issue_type' do
+ let_it_be(:issue) { create(:issue, project: reusable_project) }
+ let_it_be(:incident) { create(:incident, project: reusable_project) }
+ let_it_be(:task) { create(:issue, :task, project: reusable_project) }
+
+ it 'returns issues without the given issue type' do
+ expect(described_class.without_issue_type('issue'))
+ .to contain_exactly(incident, task)
+ end
+
+ it 'returns issues without the given issue types' do
+ expect(described_class.without_issue_type(%w(issue incident)))
+ .to contain_exactly(task)
+ end
+
+ it 'uses the work_item_types table for filtering' do
+ expect do
+ described_class.without_issue_type(:issue).to_a
+ end.to make_queries_matching(
+ %r{
+ INNER\sJOIN\s"work_item_types"\sON\s"work_item_types"\."id"\s=\s"issues"\."work_item_type_id"
+ \sWHERE\s"work_item_types"\."base_type"\s!=\s0
+ }x
+ )
+ end
+
+ context 'when the issue_type_uses_work_item_types_table feature flag is disabled' do
+ before do
+ stub_feature_flags(issue_type_uses_work_item_types_table: false)
+ end
+
+ it 'uses the issue_type column for filtering' do
+ expect do
+ described_class.without_issue_type(:issue).to_a
+ end.to make_queries_matching(/"issues"\."issue_type" != 0/)
+ end
+ end
end
describe '.order_severity' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index e9bb01f4b23..32158ef9509 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -76,6 +76,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do
it { is_expected.to have_one(:harbor_integration) }
it { is_expected.to have_one(:redmine_integration) }
it { is_expected.to have_one(:youtrack_integration) }
+ it { is_expected.to have_one(:clickup_integration) }
it { is_expected.to have_one(:custom_issue_tracker_integration) }
it { is_expected.to have_one(:bugzilla_integration) }
it { is_expected.to have_one(:ewm_integration) }
@@ -2047,6 +2048,28 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do
end
end
+ describe 'sorting by name' do
+ let_it_be(:project1) { create(:project, name: 'A') }
+ let_it_be(:project2) { create(:project, name: 'Z') }
+ let_it_be(:project3) { create(:project, name: 'L') }
+
+ context 'when using .sort_by_name_desc' do
+ it 'reorders the projects by descending name order' do
+ projects = described_class.sorted_by_name_desc
+
+ expect(projects.pluck(:name)).to eq(%w[Z L A])
+ end
+ end
+
+ context 'when using .sort_by_name_asc' do
+ it 'reorders the projects by ascending name order' do
+ projects = described_class.sorted_by_name_asc
+
+ expect(projects.pluck(:name)).to eq(%w[A L Z])
+ end
+ end
+ end
+
describe '.with_shared_runners_enabled' do
subject { described_class.with_shared_runners_enabled }
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index 7b839594816..efb4d244c10 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -82,7 +82,6 @@ RSpec.shared_context 'project navbar structure' do
{
nav_item: _('Monitor'),
nav_sub_items: [
- _('Metrics'),
_('Error Tracking'),
_('Alerts'),
_('Incidents')
diff --git a/spec/tasks/cache/clear/redis_spec.rb b/spec/tasks/cache_rake_spec.rb
index 375d01bf2ba..375d01bf2ba 100644
--- a/spec/tasks/cache/clear/redis_spec.rb
+++ b/spec/tasks/cache_rake_spec.rb
diff --git a/spec/tasks/config_lint_spec.rb b/spec/tasks/config_lint_rake_spec.rb
index 34899c84888..34899c84888 100644
--- a/spec/tasks/config_lint_spec.rb
+++ b/spec/tasks/config_lint_rake_spec.rb
diff --git a/spec/tasks/gitlab/db/decomposition/connection_status_spec.rb b/spec/tasks/gitlab/db/decomposition/connection_status_rake_spec.rb
index 78f86049ebb..78f86049ebb 100644
--- a/spec/tasks/gitlab/db/decomposition/connection_status_spec.rb
+++ b/spec/tasks/gitlab/db/decomposition/connection_status_rake_spec.rb
diff --git a/spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb b/spec/tasks/gitlab/generate_sample_prometheus_data_rake_spec.rb
index 67bf512c6da..67bf512c6da 100644
--- a/spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb
+++ b/spec/tasks/gitlab/generate_sample_prometheus_data_rake_spec.rb
diff --git a/spec/tasks/gitlab/metrics_exporter_task_spec.rb b/spec/tasks/gitlab/metrics_exporter_rake_spec.rb
index ca37fc1b5d7..ca37fc1b5d7 100644
--- a/spec/tasks/gitlab/metrics_exporter_task_spec.rb
+++ b/spec/tasks/gitlab/metrics_exporter_rake_spec.rb
diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_rake_spec.rb
index 3f7271d4be1..3f7271d4be1 100644
--- a/spec/tasks/tokens_spec.rb
+++ b/spec/tasks/tokens_rake_spec.rb
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index 94ea9043857..3ec731c8eb7 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -382,34 +382,12 @@ RSpec.describe 'layouts/nav/sidebar/_project', feature_category: :navigation do
end
describe 'Monitor' do
- before do
- stub_feature_flags(remove_monitor_metrics: false)
- end
-
it 'top level navigation link is visible for user with permissions' do
render
expect(rendered).to have_link('Monitor')
end
- describe 'Metrics Dashboard' do
- it 'has a link to the metrics dashboard page' do
- render
-
- expect(rendered).to have_link('Metrics', href: project_metrics_dashboard_path(project))
- end
-
- describe 'when the user does not have access' do
- let(:user) { nil }
-
- it 'does not have a link to the metrics page' do
- render
-
- expect(rendered).not_to have_link('Metrics')
- end
- end
- end
-
describe 'Error Tracking' do
it 'has a link to the error tracking page' do
render
diff --git a/tests.yml b/tests.yml
index b2d8311fb7e..dee78aae486 100644
--- a/tests.yml
+++ b/tests.yml
@@ -24,6 +24,10 @@ mapping:
- source: lib/(.+)\.rb
test: spec/lib/%s_spec.rb
+ # Map rake tasks to its respective specs
+ - source: '(ee/)?lib/tasks/(.+)\.rake'
+ test: '%sspec/tasks/%s_rake_spec.rb'
+
# See https://gitlab.com/gitlab-org/gitlab/-/issues/368628
- source: lib/gitlab/usage_data_counters/(.+)\.rb
test: spec/lib/gitlab/usage_data_spec.rb