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--.dockerignore1
-rw-r--r--.rubocop_todo/gitlab/strong_memoize_attr.yml1
-rw-r--r--.rubocop_todo/layout/line_length.yml1
-rw-r--r--.rubocop_todo/layout/space_in_lambda_literal.yml17
-rw-r--r--.rubocop_todo/style/format_string.yml2
-rw-r--r--.rubocop_todo/style/percent_literal_delimiters.yml2
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.checksum1
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/batch_comments/components/draft_note.vue45
-rw-r--r--app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue4
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_job_status_badge.vue54
-rw-r--r--app/assets/javascripts/ci/runner/constants.js9
-rw-r--r--app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql1
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_token.vue11
-rw-r--r--app/assets/javascripts/clusters_list/components/available_agents_dropdown.vue103
-rw-r--r--app/assets/javascripts/clusters_list/constants.js5
-rw-r--r--app/assets/javascripts/diffs/components/diff_code_quality.vue37
-rw-r--r--app/assets/javascripts/diffs/components/diff_view.vue12
-rw-r--r--app/assets/javascripts/diffs/i18n.js2
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue4
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue5
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/reviewers.vue20
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue8
-rw-r--r--app/assets/javascripts/sidebar/sidebar_mediator.js3
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue27
-rw-r--r--app/assets/javascripts/work_items/components/work_item_information.vue53
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue2
-rw-r--r--app/assets/javascripts/work_items/constants.js2
-rw-r--r--app/assets/stylesheets/page_bundles/clusters.scss6
-rw-r--r--app/assets/stylesheets/pages/notes.scss23
-rw-r--r--app/assets/stylesheets/pages/projects.scss2
-rw-r--r--app/controllers/concerns/issuable_actions.rb2
-rw-r--r--app/controllers/projects/ci/daily_build_group_report_results_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/finders/releases/group_releases_finder.rb4
-rw-r--r--app/graphql/mutations/ci/runner/update.rb2
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/helpers/routing/pseudonymization_helper.rb1
-rw-r--r--app/helpers/search_helper.rb30
-rw-r--r--app/models/abuse_report.rb2
-rw-r--r--app/models/alert_management/alert.rb20
-rw-r--r--app/models/alert_management/http_integration.rb2
-rw-r--r--app/models/analytics/cycle_analytics/aggregation.rb2
-rw-r--r--app/models/analytics/usage_trends/measurement.rb6
-rw-r--r--app/models/application_setting.rb8
-rw-r--r--app/models/audit_event.rb10
-rw-r--r--app/models/award_emoji.rb4
-rw-r--r--app/models/board_group_recent_visit.rb2
-rw-r--r--app/models/board_project_recent_visit.rb2
-rw-r--r--app/models/bulk_import.rb2
-rw-r--r--app/models/bulk_imports/entity.rb2
-rw-r--r--app/models/ci/running_build.rb7
-rw-r--r--app/models/integration.rb2
-rw-r--r--app/models/integrations/flowdock.rb43
-rw-r--r--app/models/project.rb1
-rw-r--r--app/policies/base_policy.rb2
-rw-r--r--app/policies/merge_request_policy.rb4
-rw-r--r--app/services/projects/container_repository/cleanup_tags_base_service.rb6
-rw-r--r--app/services/projects/container_repository/destroy_service.rb3
-rw-r--r--app/views/admin/application_settings/_terminal.html.haml4
-rw-r--r--app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml2
-rw-r--r--app/views/layouts/_google_tag_manager_head.html.haml11
-rw-r--r--app/views/registrations/welcome/show.html.haml36
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/workers/container_registry/delete_container_repository_worker.rb4
-rw-r--r--config/metrics/counts_all/20210216175837_projects_flowdock_active.yml4
-rw-r--r--config/metrics/counts_all/20210216175839_groups_flowdock_active.yml4
-rw-r--r--config/metrics/counts_all/20210216175842_instances_flowdock_active.yml4
-rw-r--r--config/metrics/counts_all/20210216175844_projects_inheriting_flowdock_active.yml4
-rw-r--r--config/metrics/counts_all/20210216175846_groups_inheriting_flowdock_active.yml4
-rw-r--r--data/removals/15_7/15-7-remove-flowdock-integration.yml18
-rw-r--r--db/docs/ci_pending_builds.yml2
-rw-r--r--db/docs/ci_running_builds.yml8
-rw-r--r--db/docs/integrations.yml1
-rw-r--r--db/migrate/20221114145103_add_last_seat_refresh_at_to_gitlab_subscriptions.rb16
-rw-r--r--db/schema_migrations/202211141451031
-rw-r--r--db/structure.sql1
-rw-r--r--doc/.vale/gitlab/spelling-exceptions.txt1
-rw-r--r--doc/api/graphql/reference/index.md3
-rw-r--r--doc/api/integrations.md36
-rw-r--r--doc/architecture/blueprints/work_items/index.md2
-rw-r--r--doc/integration/index.md2
-rw-r--r--doc/update/removals.md6
-rw-r--r--doc/user/project/integrations/index.md1
-rw-r--r--doc/user/tasks.md6
-rw-r--r--lib/api/helpers/integrations_helpers.rb9
-rw-r--r--lib/api/merge_request_approvals.rb17
-rw-r--r--lib/api/merge_requests.rb19
-rw-r--r--lib/flowdock/git.rb67
-rw-r--r--lib/flowdock/git/builder.rb145
-rw-r--r--lib/gitlab/ci/build/context/build.rb2
-rw-r--r--lib/gitlab/process_management.rb9
-rw-r--r--lib/gitlab/process_supervisor.rb2
-rw-r--r--lib/gitlab/ssh/signature.rb15
-rw-r--r--locale/gitlab.pot42
-rw-r--r--sidekiq_cluster/cli.rb10
-rw-r--r--sidekiq_cluster/sidekiq_cluster.rb8
-rw-r--r--spec/commands/sidekiq_cluster/cli_spec.rb43
-rw-r--r--spec/features/boards/boards_spec.rb102
-rw-r--r--spec/features/clusters/create_agent_spec.rb4
-rw-r--r--spec/features/merge_request/user_assigns_themselves_reviewer_spec.rb42
-rw-r--r--spec/features/merge_request/user_assigns_themselves_spec.rb14
-rw-r--r--spec/features/projects/integrations/user_activates_flowdock_spec.rb22
-rw-r--r--spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson1
-rw-r--r--spec/frontend/batch_comments/components/draft_note_spec.js5
-rw-r--r--spec/frontend/ci/runner/components/cells/runner_summary_cell_spec.js6
-rw-r--r--spec/frontend/ci/runner/components/runner_job_status_badge_spec.js44
-rw-r--r--spec/frontend/clusters_list/components/agent_token_spec.js20
-rw-r--r--spec/frontend/clusters_list/components/available_agents_dropwdown_spec.js95
-rw-r--r--spec/frontend/diffs/components/diff_code_quality_spec.js11
-rw-r--r--spec/frontend/filtered_search/filtered_search_manager_spec.js6
-rw-r--r--spec/frontend/filtered_search/visual_token_value_spec.js4
-rw-r--r--spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js77
-rw-r--r--spec/frontend/sidebar/sidebar_mediator_spec.js7
-rw-r--r--spec/frontend/work_items/components/work_item_detail_spec.js28
-rw-r--r--spec/frontend/work_items/components/work_item_information_spec.js43
-rw-r--r--spec/graphql/types/projects/service_type_enum_spec.rb1
-rw-r--r--spec/helpers/search_helper_spec.rb75
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/process_management_spec.rb9
-rw-r--r--spec/lib/gitlab/ssh/signature_spec.rb20
-rw-r--r--spec/models/integrations/flowdock_spec.rb54
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/requests/api/merge_requests_spec.rb26
-rw-r--r--spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb3
-rw-r--r--spec/services/projects/container_repository/third_party/cleanup_tags_service_spec.rb10
-rw-r--r--spec/sidekiq_cluster/sidekiq_cluster_spec.rb21
-rw-r--r--spec/support/rspec_order_todo.yml2
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb2
-rw-r--r--spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb13
-rw-r--r--spec/workers/container_registry/delete_container_repository_worker_spec.rb20
132 files changed, 1023 insertions, 982 deletions
diff --git a/.dockerignore b/.dockerignore
index e145f368cb1..0782627230a 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -49,7 +49,6 @@
/lib/registry/
/lib/policy/
/lib/feature/
-/lib/flowdock/
/lib/generators/
/lib/gitaly/
/lib/api/
diff --git a/.rubocop_todo/gitlab/strong_memoize_attr.yml b/.rubocop_todo/gitlab/strong_memoize_attr.yml
index 63379b6f550..c1ba066a181 100644
--- a/.rubocop_todo/gitlab/strong_memoize_attr.yml
+++ b/.rubocop_todo/gitlab/strong_memoize_attr.yml
@@ -565,7 +565,6 @@ Gitlab/StrongMemoizeAttr:
- 'lib/container_registry/client.rb'
- 'lib/container_registry/gitlab_api_client.rb'
- 'lib/container_registry/tag.rb'
- - 'lib/flowdock/git/builder.rb'
- 'lib/gitlab/alert_management/alert_status_counts.rb'
- 'lib/gitlab/alert_management/payload/base.rb'
- 'lib/gitlab/alert_management/payload/managed_prometheus.rb'
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index 8fdae5e8660..b6056ae4f50 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -391,7 +391,6 @@ Layout/LineLength:
- 'app/models/integrations/emails_on_push.rb'
- 'app/models/integrations/ewm.rb'
- 'app/models/integrations/external_wiki.rb'
- - 'app/models/integrations/flowdock.rb'
- 'app/models/integrations/hangouts_chat.rb'
- 'app/models/integrations/harbor.rb'
- 'app/models/integrations/jenkins.rb'
diff --git a/.rubocop_todo/layout/space_in_lambda_literal.yml b/.rubocop_todo/layout/space_in_lambda_literal.yml
index 70d0786173c..73b8a354a58 100644
--- a/.rubocop_todo/layout/space_in_lambda_literal.yml
+++ b/.rubocop_todo/layout/space_in_lambda_literal.yml
@@ -3,23 +3,6 @@
Layout/SpaceInLambdaLiteral:
Details: grace period
Exclude:
- - 'app/controllers/concerns/issuable_actions.rb'
- - 'app/controllers/projects/ci/daily_build_group_report_results_controller.rb'
- - 'app/controllers/projects/merge_requests_controller.rb'
- - 'app/finders/releases/group_releases_finder.rb'
- - 'app/graphql/mutations/ci/runner/update.rb'
- - 'app/models/abuse_report.rb'
- - 'app/models/alert_management/alert.rb'
- - 'app/models/alert_management/http_integration.rb'
- - 'app/models/analytics/cycle_analytics/aggregation.rb'
- - 'app/models/analytics/usage_trends/measurement.rb'
- - 'app/models/application_setting.rb'
- - 'app/models/audit_event.rb'
- - 'app/models/award_emoji.rb'
- - 'app/models/board_group_recent_visit.rb'
- - 'app/models/board_project_recent_visit.rb'
- - 'app/models/bulk_import.rb'
- - 'app/models/bulk_imports/entity.rb'
- 'app/models/bulk_imports/tracker.rb'
- 'app/models/ci/build.rb'
- 'app/models/ci/daily_build_group_report_result.rb'
diff --git a/.rubocop_todo/style/format_string.yml b/.rubocop_todo/style/format_string.yml
index 1e4356aed17..c1ba754edca 100644
--- a/.rubocop_todo/style/format_string.yml
+++ b/.rubocop_todo/style/format_string.yml
@@ -105,7 +105,6 @@ Style/FormatString:
- 'app/models/integrations/emails_on_push.rb'
- 'app/models/integrations/ewm.rb'
- 'app/models/integrations/external_wiki.rb'
- - 'app/models/integrations/flowdock.rb'
- 'app/models/integrations/hangouts_chat.rb'
- 'app/models/integrations/irker.rb'
- 'app/models/integrations/jenkins.rb'
@@ -281,7 +280,6 @@ Style/FormatString:
- 'lib/api/helpers/packages/conan/api_helpers.rb'
- 'lib/bulk_imports/network_error.rb'
- 'lib/bulk_imports/users_mapper.rb'
- - 'lib/flowdock/git/builder.rb'
- 'lib/gitlab/bitbucket_server_import/importer.rb'
- 'lib/gitlab/checks/push_file_count_check.rb'
- 'lib/gitlab/ci/ansi2json/line.rb'
diff --git a/.rubocop_todo/style/percent_literal_delimiters.yml b/.rubocop_todo/style/percent_literal_delimiters.yml
index fee5decab22..2f042829e35 100644
--- a/.rubocop_todo/style/percent_literal_delimiters.yml
+++ b/.rubocop_todo/style/percent_literal_delimiters.yml
@@ -87,7 +87,6 @@ Style/PercentLiteralDelimiters:
- 'app/models/integrations/emails_on_push.rb'
- 'app/models/integrations/external_wiki.rb'
- 'app/models/integrations/field.rb'
- - 'app/models/integrations/flowdock.rb'
- 'app/models/integrations/jenkins.rb'
- 'app/models/integrations/jira.rb'
- 'app/models/integrations/packagist.rb'
@@ -485,7 +484,6 @@ Style/PercentLiteralDelimiters:
- 'lib/bitbucket/representation/issue.rb'
- 'lib/container_registry/path.rb'
- 'lib/feature.rb'
- - 'lib/flowdock/git/builder.rb'
- 'lib/generators/gitlab/usage_metric_definition_generator.rb'
- 'lib/generators/gitlab/usage_metric_generator.rb'
- 'lib/gitlab.rb'
diff --git a/Gemfile b/Gemfile
index f245f8f5961..34b730da95d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -264,9 +264,6 @@ gem 'discordrb-webhooks', '~> 3.4', require: false
gem 'jira-ruby', '~> 2.1.4'
gem 'atlassian-jwt', '~> 0.2.0'
-# Flowdock integration
-gem 'flowdock', '~> 0.7'
-
# Slack integration
gem 'slack-messenger', '~> 2.3.4'
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 6c03431e3b0..20cc921205c 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -178,7 +178,6 @@
{"name":"flipper","version":"0.25.0","platform":"ruby","checksum":"ccb2776752b8378bc994c9d873ccde290c090341940761b873494695ee697add"},
{"name":"flipper-active_record","version":"0.25.0","platform":"ruby","checksum":"85a5c99465e2cc6a09e91931a9998b0dbd463cd6c80dd513129377132e3eb67f"},
{"name":"flipper-active_support_cache_store","version":"0.25.0","platform":"ruby","checksum":"7282bf994b08d1a076b65c6f3b51e3dc04fcb00fa6e7b20089e60db25c7b531b"},
-{"name":"flowdock","version":"0.7.1","platform":"ruby","checksum":"cfa95b2ac96e5f883f6e419d7a891f76cfcc17a28c416b6b714bbdffc8dbd912"},
{"name":"fog-aliyun","version":"0.3.3","platform":"ruby","checksum":"d0aa317f7c1473a1d684fff51699f216bb9cb78b9ee9ce55a81c9bcc93fb85ee"},
{"name":"fog-aws","version":"3.15.0","platform":"ruby","checksum":"09752931ea0c6165b018e1a89253248d86b246645086ccf19bc44fabe3381e8c"},
{"name":"fog-core","version":"2.1.0","platform":"ruby","checksum":"53e5d793554d7080d015ef13cd44b54027e421d924d9dba4ce3d83f95f37eda9"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 4c3dc0067cf..6d478b08a0e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -484,9 +484,6 @@ GEM
flipper-active_support_cache_store (0.25.0)
activesupport (>= 4.2, < 8)
flipper (~> 0.25.0)
- flowdock (0.7.1)
- httparty (~> 0.7)
- multi_json
fog-aliyun (0.3.3)
fog-core
fog-json
@@ -1649,7 +1646,6 @@ DEPENDENCIES
flipper (~> 0.25.0)
flipper-active_record (~> 0.25.0)
flipper-active_support_cache_store (~> 0.25.0)
- flowdock (~> 0.7)
fog-aliyun (~> 0.3)
fog-aws (~> 3.15)
fog-core (= 2.1.0)
diff --git a/app/assets/javascripts/batch_comments/components/draft_note.vue b/app/assets/javascripts/batch_comments/components/draft_note.vue
index 794bf4cc51c..5bb310afac7 100644
--- a/app/assets/javascripts/batch_comments/components/draft_note.vue
+++ b/app/assets/javascripts/batch_comments/components/draft_note.vue
@@ -85,32 +85,25 @@ export default {
};
</script>
<template>
- <article
- class="draft-note-component note-wrapper"
- @mouseenter="handleMouseEnter(draft)"
- @mouseleave="handleMouseLeave(draft)"
+ <noteable-note
+ :note="draft"
+ :line="line"
+ :discussion-root="true"
+ :class="{ 'gl-mb-0!': glFeatures.mrReviewSubmitComment }"
+ class="draft-note-component draft-note"
+ @handleEdit="handleEditing"
+ @cancelForm="handleNotEditing"
+ @updateSuccess="handleNotEditing"
+ @handleDeleteNote="deleteDraft"
+ @handleUpdateNote="update"
+ @toggleResolveStatus="toggleResolveDiscussion(draft.id)"
+ @mouseenter.native="handleMouseEnter(draft)"
+ @mouseleave.native="handleMouseLeave(draft)"
>
- <ul class="notes draft-notes">
- <noteable-note
- :note="draft"
- :line="line"
- :discussion-root="true"
- :class="{ 'gl-mb-0!': glFeatures.mrReviewSubmitComment }"
- class="draft-note"
- @handleEdit="handleEditing"
- @cancelForm="handleNotEditing"
- @updateSuccess="handleNotEditing"
- @handleDeleteNote="deleteDraft"
- @handleUpdateNote="update"
- @toggleResolveStatus="toggleResolveDiscussion(draft.id)"
- >
- <template #note-header-info>
- <gl-badge variant="warning" class="gl-mr-2">{{ __('Pending') }}</gl-badge>
- </template>
- </noteable-note>
- </ul>
-
- <template v-if="!isEditingDraft">
+ <template #note-header-info>
+ <gl-badge variant="warning" class="gl-mr-2">{{ __('Pending') }}</gl-badge>
+ </template>
+ <template v-if="!isEditingDraft" #after-note-body>
<div
v-if="draftCommands"
v-safe-html:[$options.safeHtmlConfig]="draftCommands"
@@ -134,5 +127,5 @@ export default {
</gl-button>
</p>
</template>
- </article>
+ </noteable-note>
</template>
diff --git a/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue b/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue
index 1e44d5fccc2..2d0f702c636 100644
--- a/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue
+++ b/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue
@@ -6,6 +6,7 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import RunnerName from '../runner_name.vue';
import RunnerTags from '../runner_tags.vue';
import RunnerTypeBadge from '../runner_type_badge.vue';
+import RunnerJobStatusBadge from '../runner_job_status_badge.vue';
import { formatJobCount } from '../../utils';
import {
@@ -25,6 +26,7 @@ export default {
RunnerName,
RunnerTags,
RunnerTypeBadge,
+ RunnerJobStatusBadge,
RunnerUpgradeStatusIcon: () =>
import('ee_component/ci/runner/components/runner_upgrade_status_icon.vue'),
TooltipOnTruncate,
@@ -81,6 +83,8 @@ export default {
</div>
<div>
+ <runner-job-status-badge :job-status="runner.jobExecutionStatus" />
+
<runner-summary-field icon="clock">
<gl-sprintf :message="$options.i18n.I18N_LAST_CONTACT_LABEL">
<template #timeAgo>
diff --git a/app/assets/javascripts/ci/runner/components/runner_job_status_badge.vue b/app/assets/javascripts/ci/runner/components/runner_job_status_badge.vue
new file mode 100644
index 00000000000..176fe57eebb
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/components/runner_job_status_badge.vue
@@ -0,0 +1,54 @@
+<script>
+import { GlBadge, GlTooltipDirective } from '@gitlab/ui';
+import {
+ I18N_JOB_STATUS_RUNNING,
+ I18N_JOB_STATUS_IDLE,
+ JOB_STATUS_RUNNING,
+ JOB_STATUS_IDLE,
+} from '../constants';
+
+export default {
+ components: {
+ GlBadge,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ jobStatus: {
+ required: false,
+ default: null,
+ type: String,
+ },
+ },
+ computed: {
+ badge() {
+ switch (this.jobStatus) {
+ case JOB_STATUS_RUNNING:
+ return {
+ classes: 'gl-text-blue-600! gl-border gl-border-blue-600!',
+ label: I18N_JOB_STATUS_RUNNING,
+ };
+ case JOB_STATUS_IDLE:
+ return {
+ classes: 'gl-text-gray-700! gl-border gl-border-gray-500!',
+ label: I18N_JOB_STATUS_IDLE,
+ };
+ default:
+ return null;
+ }
+ },
+ },
+};
+</script>
+<template>
+ <gl-badge
+ v-if="badge"
+ size="sm"
+ class="gl-mr-3 gl-bg-transparent!"
+ variant="muted"
+ :class="badge.classes"
+ >
+ {{ badge.label }}
+ </gl-badge>
+</template>
diff --git a/app/assets/javascripts/ci/runner/constants.js b/app/assets/javascripts/ci/runner/constants.js
index dfc5f0c4152..686f0fde9d7 100644
--- a/app/assets/javascripts/ci/runner/constants.js
+++ b/app/assets/javascripts/ci/runner/constants.js
@@ -32,6 +32,10 @@ export const I18N_STATUS_NEVER_CONTACTED = s__('Runners|Never contacted');
export const I18N_STATUS_OFFLINE = s__('Runners|Offline');
export const I18N_STATUS_STALE = s__('Runners|Stale');
+// Executor Status
+export const I18N_JOB_STATUS_RUNNING = s__('Runners|Running');
+export const I18N_JOB_STATUS_IDLE = s__('Runners|Idle');
+
// Status help popover
export const I18N_STATUS_POPOVER_TITLE = s__('Runners|Runner statuses');
@@ -134,6 +138,11 @@ export const STATUS_NEVER_CONTACTED = 'NEVER_CONTACTED';
export const STATUS_OFFLINE = 'OFFLINE';
export const STATUS_STALE = 'STALE';
+// CiRunnerJobExecutionStatus
+
+export const JOB_STATUS_RUNNING = 'RUNNING';
+export const JOB_STATUS_IDLE = 'IDLE';
+
// CiRunnerAccessLevel
export const ACCESS_LEVEL_NOT_PROTECTED = 'NOT_PROTECTED';
diff --git a/app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql b/app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql
index 0dff011daaa..6f72509f599 100644
--- a/app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql
+++ b/app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql
@@ -12,6 +12,7 @@ fragment ListItemShared on CiRunner {
createdAt
contactedAt
status(legacyMode: null)
+ jobExecutionStatus
userPermissions {
updateRunner
deleteRunner
diff --git a/app/assets/javascripts/clusters_list/components/agent_token.vue b/app/assets/javascripts/clusters_list/components/agent_token.vue
index 4dd6d84566c..93c37226a09 100644
--- a/app/assets/javascripts/clusters_list/components/agent_token.vue
+++ b/app/assets/javascripts/clusters_list/components/agent_token.vue
@@ -1,22 +1,24 @@
<script>
-import { GlAlert, GlFormInputGroup, GlLink, GlSprintf } from '@gitlab/ui';
+import { GlAlert, GlFormInputGroup, GlLink, GlSprintf, GlIcon } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import { generateAgentRegistrationCommand } from '../clusters_util';
-import { I18N_AGENT_TOKEN } from '../constants';
+import { I18N_AGENT_TOKEN, HELM_VERSION_POLICY_URL } from '../constants';
export default {
i18n: I18N_AGENT_TOKEN,
advancedInstallPath: helpPagePath('user/clusters/agent/install/index', {
anchor: 'advanced-installation-method',
}),
+ HELM_VERSION_POLICY_URL,
components: {
GlAlert,
CodeBlock,
GlFormInputGroup,
GlLink,
GlSprintf,
+ GlIcon,
ModalCopyButton,
},
inject: ['kasAddress', 'kasVersion'],
@@ -77,6 +79,11 @@ export default {
<p>
{{ $options.i18n.basicInstallBody }}
+ <gl-sprintf :message="$options.i18n.helmVersionText">
+ <template #link="{ content }"
+ ><gl-link :href="$options.HELM_VERSION_POLICY_URL" target="_blank"
+ >{{ content }} <gl-icon name="external-link" :size="12" /></gl-link></template
+ ></gl-sprintf>
</p>
<p class="gl-display-flex gl-align-items-flex-start">
diff --git a/app/assets/javascripts/clusters_list/components/available_agents_dropdown.vue b/app/assets/javascripts/clusters_list/components/available_agents_dropdown.vue
index bde76c46b4b..365e0384d87 100644
--- a/app/assets/javascripts/clusters_list/components/available_agents_dropdown.vue
+++ b/app/assets/javascripts/clusters_list/components/available_agents_dropdown.vue
@@ -1,23 +1,13 @@
<script>
-import {
- GlDropdown,
- GlDropdownItem,
- GlDropdownDivider,
- GlDropdownText,
- GlSearchBoxByType,
- GlSprintf,
-} from '@gitlab/ui';
+import { GlCollapsibleListbox, GlButton, GlSprintf } from '@gitlab/ui';
import { I18N_AVAILABLE_AGENTS_DROPDOWN } from '../constants';
export default {
name: 'AvailableAgentsDropdown',
i18n: I18N_AVAILABLE_AGENTS_DROPDOWN,
components: {
- GlDropdown,
- GlDropdownItem,
- GlDropdownDivider,
- GlDropdownText,
- GlSearchBoxByType,
+ GlCollapsibleListbox,
+ GlButton,
GlSprintf,
},
props: {
@@ -46,13 +36,21 @@ export default {
return this.selectedAgent;
},
+ dropdownItems() {
+ return this.availableAgents.map((agent) => {
+ return {
+ value: agent,
+ text: agent,
+ };
+ });
+ },
shouldRenderCreateButton() {
return this.searchTerm && !this.availableAgents.includes(this.searchTerm);
},
filteredResults() {
const lowerCasedSearchTerm = this.searchTerm.toLowerCase();
- return this.availableAgents.filter((resultString) =>
- resultString.toLowerCase().includes(lowerCasedSearchTerm),
+ return this.dropdownItems.filter((item) =>
+ item.value.toLowerCase().includes(lowerCasedSearchTerm),
);
},
},
@@ -60,59 +58,48 @@ export default {
selectAgent(agent) {
this.$emit('agentSelected', agent);
this.selectedAgent = agent;
- this.clearSearch();
- },
- isSelected(agent) {
- return this.selectedAgent === agent;
- },
- clearSearch() {
- this.searchTerm = '';
- },
- focusSearch() {
- this.$refs.searchInput.focusInput();
- },
- handleShow() {
- this.clearSearch();
- this.focusSearch();
+
+ this.$refs.dropdown.closeAndFocus();
},
onKeyEnter() {
if (!this.searchTerm?.length) {
return;
}
- this.$refs.dropdown.hide();
this.selectAgent(this.searchTerm);
},
+ searchAgent(searchQuery) {
+ this.searchTerm = searchQuery;
+ },
},
};
</script>
<template>
- <gl-dropdown ref="dropdown" :text="dropdownText" :loading="isRegistering" @shown="handleShow">
- <template #header>
- <gl-search-box-by-type
- ref="searchInput"
- v-model.trim="searchTerm"
- @keydown.enter.stop.prevent="onKeyEnter"
- />
- </template>
- <gl-dropdown-item
- v-for="agent in filteredResults"
- :key="agent"
- :is-checked="isSelected(agent)"
- is-check-item
- @click="selectAgent(agent)"
+ <div @keydown.enter.stop.prevent="onKeyEnter">
+ <gl-collapsible-listbox
+ ref="dropdown"
+ v-model="selectedAgent"
+ class="gl-w-full"
+ toggle-class="select-agent-dropdown"
+ :items="filteredResults"
+ :toggle-text="dropdownText"
+ :loading="isRegistering"
+ :searchable="true"
+ :no-results-text="$options.i18n.noResults"
+ @search="searchAgent"
+ @select="selectAgent"
>
- {{ agent }}
- </gl-dropdown-item>
- <gl-dropdown-text v-if="!filteredResults.length" ref="noMatchingResults">{{
- $options.i18n.noResults
- }}</gl-dropdown-text>
- <template v-if="shouldRenderCreateButton">
- <gl-dropdown-divider />
- <gl-dropdown-item data-testid="create-config-button" @click="selectAgent(searchTerm)">
- <gl-sprintf :message="$options.i18n.createButton">
- <template #searchTerm>{{ searchTerm }}</template>
- </gl-sprintf>
- </gl-dropdown-item>
- </template>
- </gl-dropdown>
+ <template v-if="shouldRenderCreateButton" #footer>
+ <gl-button
+ category="tertiary"
+ class="gl-justify-content-start! gl-border-t-1! gl-border-t-solid gl-border-t-gray-200 gl-pl-7! gl-rounded-top-left-none! gl-rounded-top-right-none!"
+ :class="{ 'gl-mt-3': !filteredResults.length }"
+ @click="selectAgent(searchTerm)"
+ >
+ <gl-sprintf :message="$options.i18n.createButton">
+ <template #searchTerm>{{ searchTerm }}</template>
+ </gl-sprintf>
+ </gl-button>
+ </template>
+ </gl-collapsible-listbox>
+ </div>
</template>
diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js
index 03530a0314b..615754459d6 100644
--- a/app/assets/javascripts/clusters_list/constants.js
+++ b/app/assets/javascripts/clusters_list/constants.js
@@ -101,6 +101,9 @@ export const I18N_AGENT_TOKEN = {
basicInstallBody: s__(
'ClusterAgents|From a terminal, connect to your cluster and run this command. The token is included in the command.',
),
+ helmVersionText: s__(
+ 'ClusterAgents|Use a Helm version compatible with your Kubernetes version (see %{linkStart}Helm version support policy%{linkEnd}).',
+ ),
advancedInstallTitle: s__('ClusterAgents|Advanced installation methods'),
advancedInstallBody: s__(
@@ -108,6 +111,8 @@ export const I18N_AGENT_TOKEN = {
),
};
+export const HELM_VERSION_POLICY_URL = 'https://helm.sh/docs/topics/version_skew/';
+
export const I18N_AGENT_MODAL = {
registerAgentButton: s__('ClusterAgents|Register'),
close: __('Close'),
diff --git a/app/assets/javascripts/diffs/components/diff_code_quality.vue b/app/assets/javascripts/diffs/components/diff_code_quality.vue
index 78f34bf355e..11aa856619b 100644
--- a/app/assets/javascripts/diffs/components/diff_code_quality.vue
+++ b/app/assets/javascripts/diffs/components/diff_code_quality.vue
@@ -1,8 +1,12 @@
<script>
import { GlButton, GlIcon } from '@gitlab/ui';
import { SEVERITY_CLASSES, SEVERITY_ICONS } from '~/ci/reports/codequality_report/constants';
+import { NEW_CODE_QUALITY_FINDINGS } from '../i18n';
export default {
+ i18n: {
+ newFindings: NEW_CODE_QUALITY_FINDINGS,
+ },
components: { GlButton, GlIcon },
props: {
codeQuality: {
@@ -22,22 +26,33 @@ export default {
</script>
<template>
- <div data-testid="diff-codequality" class="gl-relative">
- <ul
- class="gl-list-style-none gl-mb-0 gl-p-0 codequality-findings-list gl-border-top-1 gl-border-bottom-1 gl-bg-gray-10"
+ <div
+ data-testid="diff-codequality"
+ class="gl-relative codequality-findings-list gl-border-top-1 gl-border-bottom-1 gl-bg-gray-10 gl-pl-5 gl-pt-4 gl-pb-4"
+ >
+ <h4
+ data-testid="diff-codequality-findings-heading"
+ class="gl-mt-0 gl-mb-0 gl-font-base gl-font-regular"
>
+ {{ $options.i18n.newFindings }}
+ </h4>
+ <ul class="gl-list-style-none gl-mb-0 gl-p-0">
<li
v-for="finding in codeQuality"
:key="finding.description"
- class="gl-pt-1 gl-pb-1 gl-pl-3 gl-border-solid gl-border-bottom-0 gl-border-right-0 gl-border-1 gl-border-gray-100 gl-font-regular"
+ class="gl-pt-1 gl-pb-1 gl-font-regular gl-display-flex"
>
- <gl-icon
- :size="12"
- :name="severityIcon(finding.severity)"
- :class="severityClass(finding.severity)"
- class="codequality-severity-icon"
- />
- {{ finding.description }}
+ <span class="gl-mr-3">
+ <gl-icon
+ :size="12"
+ :name="severityIcon(finding.severity)"
+ :class="severityClass(finding.severity)"
+ class="codequality-severity-icon"
+ />
+ </span>
+ <span>
+ <span class="severity-copy">{{ finding.severity }}</span> - {{ finding.description }}
+ </span>
</li>
</ul>
<gl-button
diff --git a/app/assets/javascripts/diffs/components/diff_view.vue b/app/assets/javascripts/diffs/components/diff_view.vue
index 4a5a626af8d..aa9a17d18e3 100644
--- a/app/assets/javascripts/diffs/components/diff_view.vue
+++ b/app/assets/javascripts/diffs/components/diff_view.vue
@@ -303,7 +303,11 @@ export default {
class="diff-td notes-content parallel old"
>
<div v-for="draft in lineDrafts(line, 'left')" :key="draft.id" class="content">
- <draft-note :draft="draft" :line="line.left" />
+ <article class="note-wrapper">
+ <ul class="notes draft-notes">
+ <draft-note :draft="draft" :line="line.left" />
+ </ul>
+ </article>
</div>
</div>
<div
@@ -311,7 +315,11 @@ export default {
class="diff-td notes-content parallel new"
>
<div v-for="draft in lineDrafts(line, 'right')" :key="draft.id" class="content">
- <draft-note :draft="draft" :line="line.right" />
+ <article class="note-wrapper">
+ <ul class="notes draft-notes">
+ <draft-note :draft="draft" :line="line.right" />
+ </ul>
+ </article>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/diffs/i18n.js b/app/assets/javascripts/diffs/i18n.js
index f7f4aad3ad0..b6978689f7f 100644
--- a/app/assets/javascripts/diffs/i18n.js
+++ b/app/assets/javascripts/diffs/i18n.js
@@ -49,3 +49,5 @@ export const CONFLICT_TEXT = {
};
export const HIDE_COMMENTS = __('Hide comments');
+
+export const NEW_CODE_QUALITY_FINDINGS = __('New code quality findings');
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index b668d6ec182..fe1a2f0da2a 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -307,7 +307,7 @@ export default {
:draft="draftForDiscussion(discussion.reply_id)"
:line="line"
/>
- <div
+ <li
v-else-if="canShowReplyActions && showReplies"
:class="{ 'is-replying': isReplying }"
class="discussion-reply-holder gl-border-t-0! clearfix"
@@ -334,7 +334,7 @@ export default {
@cancelForm="cancelReplyForm"
/>
<note-signed-out-widget v-if="!isLoggedIn" />
- </div>
+ </li>
</template>
</discussion-notes>
</component>
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index b3c124e511f..99809013bf6 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -443,7 +443,7 @@ export default {
</gl-avatar-link>
</div>
- <div v-else-if="!isDraft" class="timeline-avatar gl-float-left">
+ <div v-else class="timeline-avatar gl-float-left">
<gl-avatar-link :href="author.path">
<gl-avatar
:src="author.avatar_url"
@@ -516,6 +516,9 @@ export default {
@handleFormUpdate="formUpdateHandler"
@cancelForm="formCancelHandler"
/>
+ <div class="timeline-discussion-body-footer">
+ <slot name="after-note-body"></slot>
+ </div>
</div>
</div>
</timeline-entry-item>
diff --git a/app/assets/javascripts/sidebar/components/reviewers/reviewers.vue b/app/assets/javascripts/sidebar/components/reviewers/reviewers.vue
index 5e1172ad835..7af8dcb4e3e 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/reviewers.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/reviewers.vue
@@ -58,11 +58,21 @@ export default {
<collapsed-reviewer-list :users="sortedReviewers" :issuable-type="issuableType" />
<div class="value hide-collapsed">
- <template v-if="hasNoUsers">
- <span class="no-value">
- {{ __('None') }}
- </span>
- </template>
+ <span v-if="hasNoUsers" class="no-value" data-testid="no-value">
+ {{ __('None') }}
+ <template v-if="editable">
+ -
+ <button
+ type="button"
+ class="gl-button btn-link gl-reset-color!"
+ data-testid="assign-yourself"
+ data-qa-selector="assign_yourself_button"
+ @click="assignSelf"
+ >
+ {{ __('assign yourself') }}
+ </button>
+ </template>
+ </span>
<uncollapsed-reviewer-list
v-else
diff --git a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
index 9d55daaddd8..faa36f3d8d2 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
@@ -143,6 +143,13 @@ export default {
eventHub.$off('sidebar.saveReviewers', this.saveReviewers);
},
methods: {
+ reviewBySelf() {
+ // Notify gl dropdown that we are now assigning to current user
+ this.$el.parentElement.dispatchEvent(new Event('assignYourself'));
+
+ this.mediator.addSelfReview();
+ this.saveReviewers();
+ },
saveReviewers() {
this.loading = true;
@@ -181,6 +188,7 @@ export default {
:editable="canUpdate"
:issuable-type="issuableType"
@request-review="requestReview"
+ @assign-self="reviewBySelf"
/>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js
index 159d3eff276..c6a66ab2275 100644
--- a/app/assets/javascripts/sidebar/sidebar_mediator.js
+++ b/app/assets/javascripts/sidebar/sidebar_mediator.js
@@ -31,6 +31,9 @@ export default class SidebarMediator {
assignYourself() {
this.store.addAssignee(this.store.currentUser);
}
+ addSelfReview() {
+ this.store.addReviewer(this.store.currentUser);
+ }
async saveAssignees(field) {
const selected = this.store.assignees.map((u) => u.id);
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index 9822a999ff3..c871489fc07 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -15,7 +15,6 @@ import { s__ } from '~/locale';
import { parseBoolean } from '~/lib/utils/common_utils';
import { getParameterByName } from '~/lib/utils/url_utility';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import {
i18n,
@@ -25,7 +24,6 @@ import {
WIDGET_TYPE_START_AND_DUE_DATE,
WIDGET_TYPE_WEIGHT,
WIDGET_TYPE_HIERARCHY,
- WORK_ITEM_VIEWED_STORAGE_KEY,
WIDGET_TYPE_MILESTONE,
WIDGET_TYPE_ITERATION,
WORK_ITEM_TYPE_VALUE_ISSUE,
@@ -49,7 +47,6 @@ import WorkItemDueDate from './work_item_due_date.vue';
import WorkItemAssignees from './work_item_assignees.vue';
import WorkItemLabels from './work_item_labels.vue';
import WorkItemMilestone from './work_item_milestone.vue';
-import WorkItemInformation from './work_item_information.vue';
export default {
i18n,
@@ -72,8 +69,6 @@ export default {
WorkItemTitle,
WorkItemState,
WorkItemWeight: () => import('ee_component/work_items/components/work_item_weight.vue'),
- WorkItemInformation,
- LocalStorageSync,
WorkItemTypeIcon,
WorkItemIteration: () => import('ee_component/work_items/components/work_item_iteration.vue'),
WorkItemMilestone,
@@ -108,7 +103,6 @@ export default {
error: undefined,
updateError: undefined,
workItem: {},
- showInfoBanner: true,
updateInProgress: false,
};
},
@@ -276,18 +270,10 @@ export default {
};
},
},
- beforeDestroy() {
- /** make sure that if the user has not even dismissed the alert ,
- * should no be able to see the information next time and update the local storage * */
- this.dismissBanner();
- },
methods: {
isWidgetPresent(type) {
return this.workItem?.widgets?.find((widget) => widget.type === type);
},
- dismissBanner() {
- this.showInfoBanner = false;
- },
toggleConfidentiality(confidentialStatus) {
this.updateInProgress = true;
let updateMutation = updateWorkItemMutation;
@@ -341,7 +327,6 @@ export default {
document.title = s__('404|Not found');
},
},
- WORK_ITEM_VIEWED_STORAGE_KEY,
WORK_ITEM_TYPE_VALUE_OBJECTIVE,
};
</script>
@@ -431,16 +416,6 @@ export default {
@click="$emit('close')"
/>
</div>
- <local-storage-sync
- v-model="showInfoBanner"
- :storage-key="$options.WORK_ITEM_VIEWED_STORAGE_KEY"
- >
- <work-item-information
- v-if="showInfoBanner && !error"
- :show-info-banner="showInfoBanner"
- @work-item-banner-dismissed="dismissBanner"
- />
- </local-storage-sync>
<work-item-title
v-if="workItem.title"
:work-item-id="workItem.id"
@@ -485,7 +460,7 @@ export default {
:work-item-type="workItemType"
@error="updateError = $event"
/>
- <template v-if="workItemsMvc2Enabled">
+ <template v-if="workItemsMvcEnabled">
<work-item-milestone
v-if="workItemMilestone"
:work-item-id="workItem.id"
diff --git a/app/assets/javascripts/work_items/components/work_item_information.vue b/app/assets/javascripts/work_items/components/work_item_information.vue
deleted file mode 100644
index ce75cc98a75..00000000000
--- a/app/assets/javascripts/work_items/components/work_item_information.vue
+++ /dev/null
@@ -1,53 +0,0 @@
-<script>
-import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
-import { s__ } from '~/locale';
-import { helpPagePath } from '~/helpers/help_page_helper';
-
-export default {
- i18n: {
- learnTasksLinkText: s__('WorkItem|Learn about tasks.'),
- tasksInformationTitle: s__('WorkItem|Introducing tasks'),
- tasksInformationBody: s__(
- 'WorkItem|Use tasks to break down your work in an issue into smaller pieces. %{learnMoreLink}',
- ),
- },
- helpPageLinks: {
- tasksDocLinkPath: helpPagePath('user/tasks'),
- },
- components: {
- GlAlert,
- GlSprintf,
- GlLink,
- },
- props: {
- showInfoBanner: {
- type: Boolean,
- required: false,
- default: true,
- },
- },
- emits: ['work-item-banner-dismissed'],
-};
-</script>
-
-<template>
- <section class="gl-display-block gl-mb-2">
- <gl-alert
- v-if="showInfoBanner"
- variant="tip"
- :title="$options.i18n.tasksInformationTitle"
- data-testid="work-item-information"
- class="gl-mt-3"
- @dismiss="$emit('work-item-banner-dismissed')"
- >
- <gl-sprintf :message="$options.i18n.tasksInformationBody">
- <template #learnMoreLink>
- <gl-link :href="$options.helpPageLinks.tasksDocLinkPath">{{
- $options.i18n.learnTasksLinkText
- }}</gl-link>
- </template>
- ></gl-sprintf
- >
- </gl-alert>
- </section>
-</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
index be1a1584a91..4fcdd19784d 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
@@ -168,7 +168,7 @@ export default {
return this.parentMilestone?.id;
},
associateMilestone() {
- return this.parentMilestoneId && this.workItemsMvc2Enabled;
+ return this.parentMilestoneId && this.workItemsMvcEnabled;
},
isSubmitButtonDisabled() {
return this.isCreateForm ? this.search.length === 0 : this.workItemsToAdd.length === 0;
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index e4a50de88e9..939cc416b9e 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -20,8 +20,6 @@ export const WIDGET_TYPE_HIERARCHY = 'HIERARCHY';
export const WIDGET_TYPE_MILESTONE = 'MILESTONE';
export const WIDGET_TYPE_ITERATION = 'ITERATION';
-export const WORK_ITEM_VIEWED_STORAGE_KEY = 'gl-show-work-item-banner';
-
export const WORK_ITEM_TYPE_ENUM_INCIDENT = 'INCIDENT';
export const WORK_ITEM_TYPE_ENUM_ISSUE = 'ISSUE';
export const WORK_ITEM_TYPE_ENUM_TASK = 'TASK';
diff --git a/app/assets/stylesheets/page_bundles/clusters.scss b/app/assets/stylesheets/page_bundles/clusters.scss
index 2f5c40de6e4..4d75159e87a 100644
--- a/app/assets/stylesheets/page_bundles/clusters.scss
+++ b/app/assets/stylesheets/page_bundles/clusters.scss
@@ -24,3 +24,9 @@
.cluster-button-container:focus-within {
@include gl-focus;
}
+
+.select-agent-dropdown {
+ .gl-button-text {
+ @include gl-flex-grow-1;
+ }
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 28bb1c7a5e7..75d52424fd9 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -60,6 +60,10 @@ $system-note-svg-size: 1rem;
padding: $gl-padding-4 $gl-padding-8;
}
+ &.draft-note .timeline-content:not(.flash-container) {
+ border: 0;
+ }
+
.note-header-info {
min-height: 2rem;
display: flex;
@@ -94,6 +98,7 @@ $system-note-svg-size: 1rem;
}
&.draft-note .timeline-content:not(.flash-container) {
+ margin-left: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
@@ -104,10 +109,14 @@ $system-note-svg-size: 1rem;
border-right: 1px solid $border-color;
background-color: $white;
- .timeline-content {
+ .timeline-content:not(.flash-container) {
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 $gl-padding;
}
+ .timeline-discussion-body-footer {
+ padding: 0 $gl-padding-8 0;
+ }
+
.timeline-avatar {
margin: $gl-padding-8 0 0 $gl-padding;
}
@@ -116,6 +125,12 @@ $system-note-svg-size: 1rem;
margin-left: 2rem;
}
}
+
+ &:last-of-type .timeline-entry-inner {
+ border-bottom: 1px solid $border-color;
+ border-bottom-left-radius: $gl-border-radius-base;
+ border-bottom-right-radius: $gl-border-radius-base;
+ }
}
.diff-content {
@@ -1055,7 +1070,7 @@ $system-note-svg-size: 1rem;
padding-left: 0;
ul.notes li.note-wrapper {
- .timeline-content {
+ .timeline-content:not(.flash-container) {
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 $gl-padding;
}
@@ -1071,7 +1086,7 @@ $system-note-svg-size: 1rem;
border-right: 0;
}
- div.discussion-reply-holder {
+ .discussion-reply-holder {
margin-left: 0;
}
}
@@ -1102,7 +1117,7 @@ $system-note-svg-size: 1rem;
}
}
- .draft-note-component .draft-note.timeline-entry {
+ .draft-note-component.draft-note.timeline-entry {
.timeline-content:not(.flash-container) {
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 $gl-padding;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index a119c815cf4..eb47ad7efe6 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -247,7 +247,7 @@
.repository-languages-bar {
height: 8px;
- margin-bottom: $gl-padding-8;
+ margin-bottom: $gl-padding;
background-color: $white;
border-radius: $border-radius-default;
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index bea184e44b9..a3adeff3d33 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -173,7 +173,7 @@ module IssuableActions
def render_cached_discussions(discussions, serializer, cache_context)
render_cached(discussions,
with: serializer,
- cache_context: -> (_) { cache_context },
+ cache_context: ->(_) { cache_context },
context: self)
end
diff --git a/app/controllers/projects/ci/daily_build_group_report_results_controller.rb b/app/controllers/projects/ci/daily_build_group_report_results_controller.rb
index b2b5e096105..37138afc719 100644
--- a/app/controllers/projects/ci/daily_build_group_report_results_controller.rb
+++ b/app/controllers/projects/ci/daily_build_group_report_results_controller.rb
@@ -25,7 +25,7 @@ class Projects::Ci::DailyBuildGroupReportResultsController < Projects::Applicati
{
date: 'date',
group_name: 'group_name',
- param_type => -> (record) { record.data[param_type] }
+ param_type => ->(record) { record.data[param_type] }
}
).render
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 4ba79d43f27..f32ddee00e7 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -147,7 +147,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
render_cached(@merge_request,
with: serializer,
- cache_context: -> (_) { [Digest::SHA256.hexdigest(cache_context.to_s)] },
+ cache_context: ->(_) { [Digest::SHA256.hexdigest(cache_context.to_s)] },
serializer: params[:serializer])
else
render json: serializer.represent(@merge_request, serializer: params[:serializer])
diff --git a/app/finders/releases/group_releases_finder.rb b/app/finders/releases/group_releases_finder.rb
index 08530f63ea6..67784d6579c 100644
--- a/app/finders/releases/group_releases_finder.rb
+++ b/app/finders/releases/group_releases_finder.rb
@@ -33,8 +33,8 @@ module Releases
Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder.new(
scope: releases_scope,
array_scope: Project.for_group_and_its_subgroups(parent).select(:id),
- array_mapping_scope: -> (project_id_expression) { Release.where(Release.arel_table[:project_id].eq(project_id_expression)) },
- finder_query: -> (order_by, id_expression) { Release.where(Release.arel_table[:id].eq(id_expression)) }
+ array_mapping_scope: ->(project_id_expression) { Release.where(Release.arel_table[:project_id].eq(project_id_expression)) },
+ finder_query: ->(order_by, id_expression) { Release.where(Release.arel_table[:id].eq(id_expression)) }
)
.execute
end
diff --git a/app/graphql/mutations/ci/runner/update.rb b/app/graphql/mutations/ci/runner/update.rb
index 3c99cde60a4..4f0bf19f09c 100644
--- a/app/graphql/mutations/ci/runner/update.rb
+++ b/app/graphql/mutations/ci/runner/update.rb
@@ -54,7 +54,7 @@ module Mutations
argument :associated_projects, [::Types::GlobalIDType[::Project]],
required: false,
description: 'Projects associated with the runner. Available only for project runners.',
- prepare: -> (global_ids, ctx) { global_ids&.filter_map { |gid| gid.model_id.to_i } }
+ prepare: ->(global_ids, ctx) { global_ids&.filter_map { |gid| gid.model_id.to_i } }
field :runner,
Types::Ci::RunnerType,
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index d44ba35bb44..849cb646025 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -142,6 +142,8 @@ module ProjectsHelper
end
def project_search_tabs?(tab)
+ return false unless @project.present?
+
abilities = Array(search_tab_ability_map[tab])
abilities.any? { |ability| can?(current_user, ability, @project) }
diff --git a/app/helpers/routing/pseudonymization_helper.rb b/app/helpers/routing/pseudonymization_helper.rb
index dce0517690d..63e2b377fef 100644
--- a/app/helpers/routing/pseudonymization_helper.rb
+++ b/app/helpers/routing/pseudonymization_helper.rb
@@ -12,6 +12,7 @@ module Routing
tab
glm_source
glm_content
+ _gl
].freeze
def initialize(request_object, group, project)
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 3696721a062..37b213a3185 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -447,20 +447,38 @@ module SearchHelper
result
end
+ def code_tab_condition
+ return true if project_search_tabs?(:blobs)
+
+ @project.nil? && search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_code_tab)
+ end
+
+ def wiki_tab_condition
+ return true if project_search_tabs?(:wiki)
+
+ @project.nil? && search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_wiki_tab)
+ end
+
+ def commits_tab_condition
+ return true if project_search_tabs?(:commits)
+
+ @project.nil? && search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_commits_tab)
+ end
+
# search page scope navigation
def search_navigation
{
projects: { sort: 1, label: _("Projects"), data: { qa_selector: 'projects_tab' }, condition: @project.nil? },
- blobs: { sort: 2, label: _("Code"), data: { qa_selector: 'code_tab' }, condition: project_search_tabs?(:blobs) || (search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_code_tab)) },
+ blobs: { sort: 2, label: _("Code"), data: { qa_selector: 'code_tab' }, condition: code_tab_condition },
# sort: 3 is reserved for EE items
issues: { sort: 4, label: _("Issues"), condition: project_search_tabs?(:issues) || feature_flag_tab_enabled?(:global_search_issues_tab) },
merge_requests: { sort: 5, label: _("Merge requests"), condition: project_search_tabs?(:merge_requests) || feature_flag_tab_enabled?(:global_search_merge_requests_tab) },
- wiki_blobs: { sort: 6, label: _("Wiki"), condition: project_search_tabs?(:wiki) || search_service.show_elasticsearch_tabs? },
- commits: { sort: 7, label: _("Commits"), condition: project_search_tabs?(:commits) || (search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_commits_tab)) },
+ wiki_blobs: { sort: 6, label: _("Wiki"), condition: wiki_tab_condition },
+ commits: { sort: 7, label: _("Commits"), condition: commits_tab_condition },
notes: { sort: 8, label: _("Comments"), condition: project_search_tabs?(:notes) || search_service.show_elasticsearch_tabs? },
milestones: { sort: 9, label: _("Milestones"), condition: project_search_tabs?(:milestones) || @project.nil? },
- users: { sort: 10, label: _("Users"), condition: show_user_search_tab? },
- snippet_titles: { sort: 11, label: _("Titles and Descriptions"), search: { snippets: true, group_id: nil, project_id: nil }, condition: @show_snippets.present? && @project.nil? }
+ users: { sort: 10, label: _("Users"), condition: show_user_search_tab? },
+ snippet_titles: { sort: 11, label: _("Titles and Descriptions"), search: { snippets: true, group_id: nil, project_id: nil }, condition: @show_snippets.present? && @project.nil? }
}
end
@@ -584,7 +602,7 @@ module SearchHelper
end
def feature_flag_tab_enabled?(flag)
- @group || Feature.enabled?(flag, current_user, type: :ops)
+ @group.present? || Feature.enabled?(flag, current_user, type: :ops)
end
def sanitized_search_params
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index 7cfebf0473f..f1f22d94061 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -14,7 +14,7 @@ class AbuseReport < ApplicationRecord
validates :message, presence: true
validates :user_id, uniqueness: { message: 'has already been reported' }
- scope :by_user, -> (user) { where(user_id: user) }
+ scope :by_user, ->(user) { where(user_id: user) }
scope :with_users, -> { includes(:reporter, :user) }
# For CacheMarkdownField
diff --git a/app/models/alert_management/alert.rb b/app/models/alert_management/alert.rb
index 9f05c87018d..a5a539eae75 100644
--- a/app/models/alert_management/alert.rb
+++ b/app/models/alert_management/alert.rb
@@ -53,7 +53,7 @@ module AlertManagement
validates :fingerprint, allow_blank: true, uniqueness: {
scope: :project,
conditions: -> { not_resolved },
- message: -> (object, data) { _('Cannot have multiple unresolved alerts') }
+ message: ->(object, data) { _('Cannot have multiple unresolved alerts') }
}, unless: :resolved?
validate :hosts_format
@@ -74,23 +74,23 @@ module AlertManagement
delegate :iid, to: :issue, prefix: true, allow_nil: true
delegate :details_url, to: :present
- scope :for_iid, -> (iid) { where(iid: iid) }
- scope :for_fingerprint, -> (project, fingerprint) { where(project: project, fingerprint: fingerprint) }
- scope :for_environment, -> (environment) { where(environment: environment) }
- scope :for_assignee_username, -> (assignee_username) { joins(:assignees).merge(User.by_username(assignee_username)) }
- scope :search, -> (query) { fuzzy_search(query, [:title, :description, :monitoring_tool, :service]) }
+ scope :for_iid, ->(iid) { where(iid: iid) }
+ scope :for_fingerprint, ->(project, fingerprint) { where(project: project, fingerprint: fingerprint) }
+ scope :for_environment, ->(environment) { where(environment: environment) }
+ scope :for_assignee_username, ->(assignee_username) { joins(:assignees).merge(User.by_username(assignee_username)) }
+ scope :search, ->(query) { fuzzy_search(query, [:title, :description, :monitoring_tool, :service]) }
scope :not_resolved, -> { without_status(:resolved) }
scope :with_prometheus_alert, -> { includes(:prometheus_alert) }
scope :with_operations_alerts, -> { where(domain: :operations) }
- scope :order_start_time, -> (sort_order) { order(started_at: sort_order) }
- scope :order_end_time, -> (sort_order) { order(ended_at: sort_order) }
- scope :order_event_count, -> (sort_order) { order(events: sort_order) }
+ scope :order_start_time, ->(sort_order) { order(started_at: sort_order) }
+ scope :order_end_time, ->(sort_order) { order(ended_at: sort_order) }
+ scope :order_event_count, ->(sort_order) { order(events: sort_order) }
# Ascending sort order sorts severity from less critical to more critical.
# Descending sort order sorts severity from more critical to less critical.
# https://gitlab.com/gitlab-org/gitlab/-/issues/221242#what-is-the-expected-correct-behavior
- scope :order_severity, -> (sort_order) { order(severity: sort_order == :asc ? :desc : :asc) }
+ scope :order_severity, ->(sort_order) { order(severity: sort_order == :asc ? :desc : :asc) }
scope :order_severity_with_open_prometheus_alert, -> { open.with_prometheus_alert.order(severity: :asc, started_at: :desc) }
scope :counts_by_project_id, -> { group(:project_id).count }
diff --git a/app/models/alert_management/http_integration.rb b/app/models/alert_management/http_integration.rb
index b2686924363..906855d6dfc 100644
--- a/app/models/alert_management/http_integration.rb
+++ b/app/models/alert_management/http_integration.rb
@@ -28,7 +28,7 @@ module AlertManagement
before_validation :ensure_token
before_validation :ensure_payload_example_not_nil
- scope :for_endpoint_identifier, -> (endpoint_identifier) { where(endpoint_identifier: endpoint_identifier) }
+ scope :for_endpoint_identifier, ->(endpoint_identifier) { where(endpoint_identifier: endpoint_identifier) }
scope :active, -> { where(active: true) }
scope :ordered_by_id, -> { order(:id) }
diff --git a/app/models/analytics/cycle_analytics/aggregation.rb b/app/models/analytics/cycle_analytics/aggregation.rb
index 880b3a7e310..a888422a6b4 100644
--- a/app/models/analytics/cycle_analytics/aggregation.rb
+++ b/app/models/analytics/cycle_analytics/aggregation.rb
@@ -7,7 +7,7 @@ class Analytics::CycleAnalytics::Aggregation < ApplicationRecord
validates :incremental_runtimes_in_seconds, :incremental_processed_records, :full_runtimes_in_seconds, :full_processed_records, presence: true, length: { maximum: 10 }, allow_blank: true
- scope :priority_order, -> (column_to_sort = :last_incremental_run_at) { order(arel_table[column_to_sort].asc.nulls_first) }
+ scope :priority_order, ->(column_to_sort = :last_incremental_run_at) { order(arel_table[column_to_sort].asc.nulls_first) }
scope :enabled, -> { where('enabled IS TRUE') }
def cursor_for(mode, model)
diff --git a/app/models/analytics/usage_trends/measurement.rb b/app/models/analytics/usage_trends/measurement.rb
index 02e239ca0ef..c1245d8dce7 100644
--- a/app/models/analytics/usage_trends/measurement.rb
+++ b/app/models/analytics/usage_trends/measurement.rb
@@ -23,9 +23,9 @@ module Analytics
validates :recorded_at, uniqueness: { scope: :identifier }
scope :order_by_latest, -> { order(recorded_at: :desc) }
- scope :with_identifier, -> (identifier) { where(identifier: identifier) }
- scope :recorded_after, -> (date) { where(self.model.arel_table[:recorded_at].gteq(date)) if date.present? }
- scope :recorded_before, -> (date) { where(self.model.arel_table[:recorded_at].lteq(date)) if date.present? }
+ scope :with_identifier, ->(identifier) { where(identifier: identifier) }
+ scope :recorded_after, ->(date) { where(self.model.arel_table[:recorded_at].gteq(date)) if date.present? }
+ scope :recorded_before, ->(date) { where(self.model.arel_table[:recorded_at].lteq(date)) if date.present? }
def self.identifier_query_mapping
{
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 797e2632d84..3a67e1bc276 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -464,7 +464,7 @@ class ApplicationSetting < ApplicationRecord
validates :external_auth_client_key,
presence: true,
- if: -> (setting) { setting.external_auth_client_cert.present? }
+ if: ->(setting) { setting.external_auth_client_cert.present? }
validates :lets_encrypt_notification_email,
devise_email: true,
@@ -486,17 +486,17 @@ class ApplicationSetting < ApplicationRecord
validates :eks_access_key_id,
length: { in: 16..128 },
- if: -> (setting) { setting.eks_integration_enabled? && setting.eks_access_key_id.present? }
+ if: ->(setting) { setting.eks_integration_enabled? && setting.eks_access_key_id.present? }
validates :eks_secret_access_key,
presence: true,
- if: -> (setting) { setting.eks_integration_enabled? && setting.eks_access_key_id.present? }
+ if: ->(setting) { setting.eks_integration_enabled? && setting.eks_access_key_id.present? }
validates_with X509CertificateCredentialsValidator,
certificate: :external_auth_client_cert,
pkey: :external_auth_client_key,
pass: :external_auth_client_key_pass,
- if: -> (setting) { setting.external_auth_client_cert.present? }
+ if: ->(setting) { setting.external_auth_client_cert.present? }
validates :default_ci_config_path,
format: { without: %r{(\.{2}|\A/)},
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 0ad17cd8869..5cc87be388f 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -28,11 +28,11 @@ class AuditEvent < ApplicationRecord
validates :entity_type, presence: true
validates :ip_address, ip_address: true
- scope :by_entity_type, -> (entity_type) { where(entity_type: entity_type) }
- scope :by_entity_id, -> (entity_id) { where(entity_id: entity_id) }
- scope :by_author_id, -> (author_id) { where(author_id: author_id) }
- scope :by_entity_username, -> (username) { where(entity_id: find_user_id(username)) }
- scope :by_author_username, -> (username) { where(author_id: find_user_id(username)) }
+ scope :by_entity_type, ->(entity_type) { where(entity_type: entity_type) }
+ scope :by_entity_id, ->(entity_id) { where(entity_id: entity_id) }
+ scope :by_author_id, ->(author_id) { where(author_id: author_id) }
+ scope :by_entity_username, ->(username) { where(entity_id: find_user_id(username)) }
+ scope :by_author_username, ->(username) { where(author_id: find_user_id(username)) }
after_initialize :initialize_details
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index e9530a80d9f..9d758cf75d8 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -23,8 +23,8 @@ class AwardEmoji < ApplicationRecord
scope :downvotes, -> { named(DOWNVOTE_NAME) }
scope :upvotes, -> { named(UPVOTE_NAME) }
- scope :named, -> (names) { where(name: names) }
- scope :awarded_by, -> (users) { where(user: users) }
+ scope :named, ->(names) { where(name: names) }
+ scope :awarded_by, ->(users) { where(user: users) }
after_save :expire_cache
after_destroy :expire_cache
diff --git a/app/models/board_group_recent_visit.rb b/app/models/board_group_recent_visit.rb
index dc273e256a8..65299d6dd12 100644
--- a/app/models/board_group_recent_visit.rb
+++ b/app/models/board_group_recent_visit.rb
@@ -12,7 +12,7 @@ class BoardGroupRecentVisit < ApplicationRecord
validates :group, presence: true
validates :board, presence: true
- scope :by_user_parent, -> (user, group) { where(user: user, group: group) }
+ scope :by_user_parent, ->(user, group) { where(user: user, group: group) }
def self.board_parent_relation
:group
diff --git a/app/models/board_project_recent_visit.rb b/app/models/board_project_recent_visit.rb
index 723afd6feab..c5122392b91 100644
--- a/app/models/board_project_recent_visit.rb
+++ b/app/models/board_project_recent_visit.rb
@@ -12,7 +12,7 @@ class BoardProjectRecentVisit < ApplicationRecord
validates :project, presence: true
validates :board, presence: true
- scope :by_user_parent, -> (user, project) { where(user: user, project: project) }
+ scope :by_user_parent, ->(user, project) { where(user: user, project: project) }
def self.board_parent_relation
:project
diff --git a/app/models/bulk_import.rb b/app/models/bulk_import.rb
index 2200a66b3c2..2565ad5f2b8 100644
--- a/app/models/bulk_import.rb
+++ b/app/models/bulk_import.rb
@@ -17,7 +17,7 @@ class BulkImport < ApplicationRecord
enum source_type: { gitlab: 0 }
scope :stale, -> { where('created_at < ?', 8.hours.ago).where(status: [0, 1]) }
- scope :order_by_created_at, -> (direction) { order(created_at: direction) }
+ scope :order_by_created_at, ->(direction) { order(created_at: direction) }
state_machine :status, initial: :created do
state :created, value: 0
diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb
index a2542e669e1..e49c4e09a50 100644
--- a/app/models/bulk_imports/entity.rb
+++ b/app/models/bulk_imports/entity.rb
@@ -53,7 +53,7 @@ class BulkImports::Entity < ApplicationRecord
scope :by_user_id, ->(user_id) { joins(:bulk_import).where(bulk_imports: { user_id: user_id }) }
scope :stale, -> { where('created_at < ?', 8.hours.ago).where(status: [0, 1]) }
scope :by_bulk_import_id, ->(bulk_import_id) { where(bulk_import_id: bulk_import_id) }
- scope :order_by_created_at, -> (direction) { order(created_at: direction) }
+ scope :order_by_created_at, ->(direction) { order(created_at: direction) }
alias_attribute :destination_slug, :destination_name
diff --git a/app/models/ci/running_build.rb b/app/models/ci/running_build.rb
index 62a428a0c1e..43214b0c336 100644
--- a/app/models/ci/running_build.rb
+++ b/app/models/ci/running_build.rb
@@ -1,6 +1,13 @@
# frozen_string_literal: true
module Ci
+ # This model represents metadata for a running build.
+ # Despite the generic RunningBuild name, in this first iteration it applies only to shared runners
+ # (see Ci::RunningBuild.upsert_shared_runner_build!).
+ # The decision to insert all of the running builds here was deferred to avoid the pressure on the database as
+ # at this time that was not necessary.
+ # We can reconsider the decision to limit this only to shared runners when there is more evidence that inserting all
+ # of the running builds there is worth the additional pressure.
class RunningBuild < Ci::ApplicationRecord
include Ci::Partitionable
diff --git a/app/models/integration.rb b/app/models/integration.rb
index 41278dce22d..dfa3642cfe4 100644
--- a/app/models/integration.rb
+++ b/app/models/integration.rb
@@ -19,7 +19,7 @@ class Integration < ApplicationRecord
INTEGRATION_NAMES = %w[
asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord
- drone_ci emails_on_push ewm external_wiki flowdock hangouts_chat harbor irker jira
+ 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 teamcity unify_circuit webex_teams youtrack zentao
].freeze
diff --git a/app/models/integrations/flowdock.rb b/app/models/integrations/flowdock.rb
index 52efb29f2c1..d7625cfb3d2 100644
--- a/app/models/integrations/flowdock.rb
+++ b/app/models/integrations/flowdock.rb
@@ -1,28 +1,12 @@
# frozen_string_literal: true
+# This integration is scheduled for removal.
+# All records must be deleted before the class can be removed.
+# https://gitlab.com/gitlab-org/gitlab/-/issues/379197
module Integrations
class Flowdock < Integration
- validates :token, presence: true, if: :activated?
-
- field :token,
- type: 'password',
- help: -> { s_('FlowdockService|Enter your Flowdock token.') },
- non_empty_password_title: -> { s_('ProjectService|Enter new token') },
- non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current token.') },
- placeholder: '1b609b52537...',
- required: true
-
- def title
- 'Flowdock'
- end
-
- def description
- s_('FlowdockService|Send event notifications from GitLab to Flowdock flows.')
- end
-
- def help
- docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('api/services', anchor: 'flowdock'), target: '_blank', rel: 'noopener noreferrer'
- s_('FlowdockService|Send event notifications from GitLab to Flowdock flows. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
+ def readonly?
+ true
end
def self.to_param
@@ -30,22 +14,7 @@ module Integrations
end
def self.supported_events
- %w(push)
- end
-
- def execute(data)
- return unless supported_events.include?(data[:object_kind])
-
- ::Flowdock::Git.post(
- data[:ref],
- data[:before],
- data[:after],
- token: token,
- repo: project.repository,
- repo_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}",
- commit_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/-/commit/%s",
- diff_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/compare/%s...%s"
- )
+ %w[]
end
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 8d66c0d6ea5..5d66e7d2854 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -196,7 +196,6 @@ class Project < ApplicationRecord
has_one :emails_on_push_integration, class_name: 'Integrations::EmailsOnPush'
has_one :ewm_integration, class_name: 'Integrations::Ewm'
has_one :external_wiki_integration, class_name: 'Integrations::ExternalWiki'
- has_one :flowdock_integration, class_name: 'Integrations::Flowdock'
has_one :hangouts_chat_integration, class_name: 'Integrations::HangoutsChat'
has_one :harbor_integration, class_name: 'Integrations::Harbor'
has_one :irker_integration, class_name: 'Integrations::Irker'
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index f8e7a912896..41c924029d7 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -68,6 +68,8 @@ class BasePolicy < DeclarativePolicy::Base
rule { default }.enable :read_cross_project
condition(:is_gitlab_com, score: 0, scope: :global) { ::Gitlab.com? }
+
+ condition(:is_bot?) { @user&.bot? }
end
BasePolicy.prepend_mod_with('BasePolicy')
diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb
index bda327cb661..62840b0129f 100644
--- a/app/policies/merge_request_policy.rb
+++ b/app/policies/merge_request_policy.rb
@@ -18,6 +18,10 @@ class MergeRequestPolicy < IssuablePolicy
enable :approve_merge_request
end
+ rule { can?(:approve_merge_request) & is_bot? }.policy do
+ enable :reset_merge_request_approvals
+ end
+
rule { ~anonymous & can?(:read_merge_request) }.policy do
enable :create_todo
enable :update_subscription
diff --git a/app/services/projects/container_repository/cleanup_tags_base_service.rb b/app/services/projects/container_repository/cleanup_tags_base_service.rb
index 5393c2c080d..45557d03502 100644
--- a/app/services/projects/container_repository/cleanup_tags_base_service.rb
+++ b/app/services/projects/container_repository/cleanup_tags_base_service.rb
@@ -6,6 +6,8 @@ module Projects
private
def filter_out_latest!(tags)
+ return unless keep_latest
+
tags.reject!(&:latest?)
end
@@ -84,6 +86,10 @@ module Projects
params['keep_n']
end
+ def keep_latest
+ params.fetch('keep_latest', true)
+ end
+
def project
container_repository.project
end
diff --git a/app/services/projects/container_repository/destroy_service.rb b/app/services/projects/container_repository/destroy_service.rb
index ee5bb20b7e0..fb32fdfa911 100644
--- a/app/services/projects/container_repository/destroy_service.rb
+++ b/app/services/projects/container_repository/destroy_service.rb
@@ -5,7 +5,8 @@ module Projects
class DestroyService < BaseService
CLEANUP_TAGS_SERVICE_PARAMS = {
'name_regex_delete' => '.*',
- 'container_expiration_policy' => true # to avoid permissions checks
+ 'container_expiration_policy' => true, # to avoid permissions checks
+ 'keep_latest' => false
}.freeze
def execute(container_repository, disable_timeout: true)
diff --git a/app/views/admin/application_settings/_terminal.html.haml b/app/views/admin/application_settings/_terminal.html.haml
index c53f63e124b..b07db09d06c 100644
--- a/app/views/admin/application_settings/_terminal.html.haml
+++ b/app/views/admin/application_settings/_terminal.html.haml
@@ -1,4 +1,4 @@
-= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-terminal-settings'), html: { class: 'fieldset-form', id: 'terminal-settings' } do |f|
+= gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-terminal-settings'), html: { class: 'fieldset-form', id: 'terminal-settings' } do |f|
= form_errors(@application_setting)
%fieldset
@@ -7,4 +7,4 @@
= f.number_field :terminal_max_session_time, class: 'form-control gl-form-input'
.form-text.text-muted
= _('Maximum time, in seconds, for a web terminal websocket connection. 0 for unlimited.')
- = f.submit _('Save changes'), class: "gl-button btn btn-confirm"
+ = f.submit _('Save changes'), pajamas_button: true
diff --git a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
index c3b881df98d..af4c934fd72 100644
--- a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
+++ b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
@@ -6,5 +6,5 @@
= c.body do
= s_('ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab\'s Google Kubernetes Engine Integration.').html_safe % { sign_up_link: link }
= c.actions do
- %a.gl-button.btn-confirm.text-decoration-none{ href: 'https://cloud.google.com/partners/partnercredit/?pcn_code=0014M00001h35gDQAQ#contact-form', target: '_blank', rel: 'noopener noreferrer' }
+ = render Pajamas::ButtonComponent.new(variant: :confirm, href: 'https://cloud.google.com/partners/partnercredit/?pcn_code=0014M00001h35gDQAQ#contact-form', target: '_blank', button_options: { rel: 'noopener noreferrer' }) do
= s_("ClusterIntegration|Apply for credit")
diff --git a/app/views/layouts/_google_tag_manager_head.html.haml b/app/views/layouts/_google_tag_manager_head.html.haml
index 97e118aba93..21b9a604a35 100644
--- a/app/views/layouts/_google_tag_manager_head.html.haml
+++ b/app/views/layouts/_google_tag_manager_head.html.haml
@@ -20,6 +20,17 @@
'wait_for_update': 500
});
+ window.geofeed = (options) => {
+ dataLayer.push({
+ 'event' : 'OneTrustCountryLoad',
+ 'oneTrustCountryId': options.country.toString()
+ })
+ }
+
+ const json = document.createElement('script');
+ json.setAttribute('src', 'https://geolocation.onetrust.com/cookieconsentpub/v1/geo/location/geofeed');
+ document.head.appendChild(json);
+
- if Feature.enabled?(:gtm_nonce, type: :ops)
= javascript_tag nonce: content_security_policy_nonce do
:plain
diff --git a/app/views/registrations/welcome/show.html.haml b/app/views/registrations/welcome/show.html.haml
index 283659875ef..f4e9a597fe2 100644
--- a/app/views/registrations/welcome/show.html.haml
+++ b/app/views/registrations/welcome/show.html.haml
@@ -18,22 +18,24 @@
%p.gl-text-center= html_escape(_('%{gitlab_experience_text}. Don\'t worry, this information isn\'t shared outside of your self-managed GitLab instance.')) % { gitlab_experience_text: gitlab_experience_text }
= gitlab_ui_form_for(current_user,
url: users_sign_up_welcome_path(glm_tracking_params),
- html: { class: 'card gl-w-full! gl-p-5 js-users-signup-welcome',
+ html: { class: 'gl-w-full! gl-p-5 js-users-signup-welcome',
'aria-live' => 'assertive',
data: { testid: 'welcome-form' } }) do |f|
- .devise-errors
- = render 'devise/shared/error_messages', resource: current_user
- .row
- .form-group.col-sm-12
- = f.label :role, _('Role'), class: 'label-bold'
- = f.select :role, ::User.roles.keys.map { |role| [role.titleize, role] }, { include_blank: _('Select a role') }, class: 'form-control js-user-role-dropdown', autofocus: true, required: true, data: { qa_selector: 'role_dropdown' }
- = render_if_exists "registrations/welcome/jobs_to_be_done", f: f
- = render_if_exists "registrations/welcome/setup_for_company", f: f
- = render_if_exists "registrations/welcome/joining_project"
- = render 'devise/shared/email_opted_in', f: f
- .row
- .form-group.col-sm-12.gl-mb-0
- - if partial_exists? "registrations/welcome/button"
- = render "registrations/welcome/button"
- - else
- = f.submit _('Get started!'), class: 'btn-confirm gl-button btn btn-block gl-mb-0 gl-p-3', data: { qa_selector: 'get_started_button' }
+ = render Pajamas::CardComponent.new do |c|
+ - c.body do
+ .devise-errors
+ = render 'devise/shared/error_messages', resource: current_user
+ .row
+ .form-group.col-sm-12
+ = f.label :role, _('Role'), class: 'label-bold'
+ = f.select :role, ::User.roles.keys.map { |role| [role.titleize, role] }, { include_blank: _('Select a role') }, class: 'form-control js-user-role-dropdown', autofocus: true, required: true, data: { qa_selector: 'role_dropdown' }
+ = render_if_exists "registrations/welcome/jobs_to_be_done", f: f
+ = render_if_exists "registrations/welcome/setup_for_company", f: f
+ = render_if_exists "registrations/welcome/joining_project"
+ = render 'devise/shared/email_opted_in', f: f
+ .row
+ .form-group.col-sm-12.gl-mb-0
+ - if partial_exists? "registrations/welcome/button"
+ = render "registrations/welcome/button"
+ - else
+ = f.submit _('Get started!'), class: 'btn-confirm gl-button btn btn-block gl-mb-0 gl-p-3', data: { qa_selector: 'get_started_button' }
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 0fd128df997..327461b25fd 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -20,7 +20,7 @@
.js-sidebar-todo-widget-root{ data: { project_path: issuable_sidebar[:project_full_path], iid: issuable_sidebar[:iid], id: issuable_sidebar[:id] } }
= form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f|
- .block.assignee{ class: "#{'gl-mt-3' if !signed_in && moved_sidebar_enabled}", data: { qa_selector: 'assignee_block_container' } }
+ .block.assignee{ class: "#{'gl-mt-3' if !signed_in && moved_sidebar_enabled}", data: { qa_selector: 'assignee_block_container', testid: 'assignee-block-container' } }
= render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees, signed_in: signed_in
- if issuable_sidebar[:supports_severity]
diff --git a/app/workers/container_registry/delete_container_repository_worker.rb b/app/workers/container_registry/delete_container_repository_worker.rb
index 1f94b1b9e71..25df3b6dd5f 100644
--- a/app/workers/container_registry/delete_container_repository_worker.rb
+++ b/app/workers/container_registry/delete_container_repository_worker.rb
@@ -17,10 +17,12 @@ module ContainerRegistry
MAX_CAPACITY = 2
CLEANUP_TAGS_SERVICE_PARAMS = {
'name_regex_delete' => '.*',
+ 'keep_latest' => false,
'container_expiration_policy' => true # to avoid permissions checks
}.freeze
def perform_work
+ return unless Feature.enabled?(:container_registry_delete_repository_with_cron_worker)
return unless next_container_repository
result = delete_tags
@@ -38,6 +40,8 @@ module ContainerRegistry
end
def remaining_work_count
+ return 0 unless Feature.enabled?(:container_registry_delete_repository_with_cron_worker)
+
::ContainerRepository.delete_scheduled.limit(max_running_jobs + 1).count
end
diff --git a/config/metrics/counts_all/20210216175837_projects_flowdock_active.yml b/config/metrics/counts_all/20210216175837_projects_flowdock_active.yml
index 46db9f97e85..b88351eb4dc 100644
--- a/config/metrics/counts_all/20210216175837_projects_flowdock_active.yml
+++ b/config/metrics/counts_all/20210216175837_projects_flowdock_active.yml
@@ -7,7 +7,9 @@ product_stage: manage
product_group: integrations
product_category: integrations
value_type: number
-status: active
+status: removed
+removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102394
+milestone_removed: '15.7'
time_frame: all
data_source: database
distribution:
diff --git a/config/metrics/counts_all/20210216175839_groups_flowdock_active.yml b/config/metrics/counts_all/20210216175839_groups_flowdock_active.yml
index d5da36978b6..f77fe5ec728 100644
--- a/config/metrics/counts_all/20210216175839_groups_flowdock_active.yml
+++ b/config/metrics/counts_all/20210216175839_groups_flowdock_active.yml
@@ -7,7 +7,9 @@ product_stage: manage
product_group: integrations
product_category: integrations
value_type: number
-status: active
+status: removed
+removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102394
+milestone_removed: '15.7'
time_frame: all
data_source: database
distribution:
diff --git a/config/metrics/counts_all/20210216175842_instances_flowdock_active.yml b/config/metrics/counts_all/20210216175842_instances_flowdock_active.yml
index 198af43a99d..f22e6e6bc77 100644
--- a/config/metrics/counts_all/20210216175842_instances_flowdock_active.yml
+++ b/config/metrics/counts_all/20210216175842_instances_flowdock_active.yml
@@ -7,7 +7,9 @@ product_stage: manage
product_group: integrations
product_category: integrations
value_type: number
-status: active
+status: removed
+removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102394
+milestone_removed: '15.7'
time_frame: all
data_source: database
distribution:
diff --git a/config/metrics/counts_all/20210216175844_projects_inheriting_flowdock_active.yml b/config/metrics/counts_all/20210216175844_projects_inheriting_flowdock_active.yml
index f094f894ded..a291191eaeb 100644
--- a/config/metrics/counts_all/20210216175844_projects_inheriting_flowdock_active.yml
+++ b/config/metrics/counts_all/20210216175844_projects_inheriting_flowdock_active.yml
@@ -7,7 +7,9 @@ product_stage: manage
product_group: integrations
product_category: integrations
value_type: number
-status: active
+status: removed
+removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102394
+milestone_removed: '15.7'
time_frame: all
data_source: database
distribution:
diff --git a/config/metrics/counts_all/20210216175846_groups_inheriting_flowdock_active.yml b/config/metrics/counts_all/20210216175846_groups_inheriting_flowdock_active.yml
index fb7931ddf09..c3c4a01a809 100644
--- a/config/metrics/counts_all/20210216175846_groups_inheriting_flowdock_active.yml
+++ b/config/metrics/counts_all/20210216175846_groups_inheriting_flowdock_active.yml
@@ -7,7 +7,9 @@ product_stage: manage
product_group: integrations
product_category: integrations
value_type: number
-status: active
+status: removed
+removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102394
+milestone_removed: '15.7'
time_frame: all
data_source: database
distribution:
diff --git a/data/removals/15_7/15-7-remove-flowdock-integration.yml b/data/removals/15_7/15-7-remove-flowdock-integration.yml
new file mode 100644
index 00000000000..46f8ed6bdf9
--- /dev/null
+++ b/data/removals/15_7/15-7-remove-flowdock-integration.yml
@@ -0,0 +1,18 @@
+- title: "Flowdock integration" # (required) Actionable title. e.g., The `confidential` field for a `Note` is deprecated. Use `internal` instead.
+ announcement_milestone: "15.7" # (required) The milestone when this feature was deprecated.
+ announcement_date: "2022-12-22" # (required) The date of the milestone release when this feature was deprecated. This should almost always be the 22nd of a month (YYYY-MM-DD), unless you did an out of band blog post.
+ removal_milestone: "15.7" # (required) The milestone when this feature is being removed.
+ removal_date: "2022-12-22" # (required) This should almost always be the 22nd of a month (YYYY-MM-DD), the date of the milestone release when this feature will be removed.
+ breaking_change: false # (required) Change to true if this removal is a breaking change.
+ reporter: arturoherrero # (required) GitLab username of the person reporting the removal
+ stage: manage # (required) String value of the stage that the feature was created in. e.g., Growth
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/379197 # (required) Link to the deprecation issue in GitLab
+ body: | # (required) Do not modify this line, instead modify the lines below.
+ As of December 22, 2022, we are removing the Flowdock integration because the service was shut down on August 15, 2022.
+#
+# OPTIONAL FIELDS
+#
+ tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
+ documentation_url: # (optional) This is a link to the current documentation page
+ image_url: # (optional) This is a link to a thumbnail image depicting the feature
+ video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
diff --git a/db/docs/ci_pending_builds.yml b/db/docs/ci_pending_builds.yml
index 4abcb77a499..5622df4feab 100644
--- a/db/docs/ci_pending_builds.yml
+++ b/db/docs/ci_pending_builds.yml
@@ -4,7 +4,7 @@ classes:
- Ci::PendingBuild
feature_categories:
- continuous_integration
-description: TODO
+description: Pending builds metadata
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61581
milestone: '14.0'
gitlab_schema: gitlab_ci
diff --git a/db/docs/ci_running_builds.yml b/db/docs/ci_running_builds.yml
index de337d628eb..72e941a8665 100644
--- a/db/docs/ci_running_builds.yml
+++ b/db/docs/ci_running_builds.yml
@@ -4,7 +4,13 @@ classes:
- Ci::RunningBuild
feature_categories:
- continuous_integration
-description: TODO
+description: >
+ Running builds metadata.
+ Despite the generic `RunningBuild` name, in this first iteration it applies only to shared runners.
+ The decision to insert all of the running builds here was deferred to avoid the pressure on the database as
+ at this time that was not necessary.
+ We can reconsider the decision to limit this only to shared runners when there is more evidence that inserting all
+ of the running builds there is worth the additional pressure.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62912
milestone: '14.0'
gitlab_schema: gitlab_ci
diff --git a/db/docs/integrations.yml b/db/docs/integrations.yml
index 9c3d97492fb..52d719e19da 100644
--- a/db/docs/integrations.yml
+++ b/db/docs/integrations.yml
@@ -21,7 +21,6 @@ classes:
- Integrations::EmailsOnPush
- Integrations::Ewm
- Integrations::ExternalWiki
-- Integrations::Flowdock
- Integrations::Github
- Integrations::GitlabSlackApplication
- Integrations::HangoutsChat
diff --git a/db/migrate/20221114145103_add_last_seat_refresh_at_to_gitlab_subscriptions.rb b/db/migrate/20221114145103_add_last_seat_refresh_at_to_gitlab_subscriptions.rb
new file mode 100644
index 00000000000..77d6bb42f02
--- /dev/null
+++ b/db/migrate/20221114145103_add_last_seat_refresh_at_to_gitlab_subscriptions.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddLastSeatRefreshAtToGitlabSubscriptions < Gitlab::Database::Migration[2.0]
+ enable_lock_retries!
+
+ TABLE_NAME = 'gitlab_subscriptions'
+ COLUMN_NAME = 'last_seat_refresh_at'
+
+ def up
+ add_column(TABLE_NAME, COLUMN_NAME, :datetime_with_timezone)
+ end
+
+ def down
+ remove_column(TABLE_NAME, COLUMN_NAME)
+ end
+end
diff --git a/db/schema_migrations/20221114145103 b/db/schema_migrations/20221114145103
new file mode 100644
index 00000000000..da49d8f76b1
--- /dev/null
+++ b/db/schema_migrations/20221114145103
@@ -0,0 +1 @@
+1621f0ac141f24c15beef34f5f411158c1eb8a89f5022dd426533d705aa859fe \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index c0da93eccbf..065c1db2f77 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -15982,6 +15982,7 @@ CREATE TABLE gitlab_subscriptions (
seats_owed integer DEFAULT 0 NOT NULL,
trial_extension_type smallint,
max_seats_used_changed_at timestamp with time zone,
+ last_seat_refresh_at timestamp with time zone,
CONSTRAINT check_77fea3f0e7 CHECK ((namespace_id IS NOT NULL))
);
diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt
index 7835270d4bd..3c76e35826d 100644
--- a/doc/.vale/gitlab/spelling-exceptions.txt
+++ b/doc/.vale/gitlab/spelling-exceptions.txt
@@ -348,7 +348,6 @@ Flamegraph
flamegraphs
Flawfinder
Flickr
-Flowdock
Fluentd
Flutterwave
Flycheck
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 963dbda0231..b35beec5ea6 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -16885,7 +16885,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="pipelinesecurityreportfindingsolution"></a>`solution` | [`String`](#string) | URL to the vulnerability's details page. |
| <a id="pipelinesecurityreportfindingstate"></a>`state` | [`VulnerabilityState`](#vulnerabilitystate) | Finding status. |
| <a id="pipelinesecurityreportfindingtitle"></a>`title` | [`String`](#string) | Title of the vulnerability finding. |
-| <a id="pipelinesecurityreportfindinguuid"></a>`uuid` | [`String`](#string) | Name of the vulnerability finding. |
+| <a id="pipelinesecurityreportfindinguuid"></a>`uuid` | [`String`](#string) | UUIDv5 digest based on the vulnerability's report type, primary identifier, location, fingerprint, project identifier. |
### `PreviewBillableUserChange`
@@ -22509,7 +22509,6 @@ State of a Sentry error.
| <a id="servicetypeemails_on_push_service"></a>`EMAILS_ON_PUSH_SERVICE` | EmailsOnPushService type. |
| <a id="servicetypeewm_service"></a>`EWM_SERVICE` | EwmService type. |
| <a id="servicetypeexternal_wiki_service"></a>`EXTERNAL_WIKI_SERVICE` | ExternalWikiService type. |
-| <a id="servicetypeflowdock_service"></a>`FLOWDOCK_SERVICE` | FlowdockService type. |
| <a id="servicetypegithub_service"></a>`GITHUB_SERVICE` | GithubService type. |
| <a id="servicetypegitlab_slack_application_service"></a>`GITLAB_SLACK_APPLICATION_SERVICE` | GitlabSlackApplicationService type (Gitlab.com only). |
| <a id="servicetypehangouts_chat_service"></a>`HANGOUTS_CHAT_SERVICE` | HangoutsChatService type. |
diff --git a/doc/api/integrations.md b/doc/api/integrations.md
index a6090228af2..c71f136f132 100644
--- a/doc/api/integrations.md
+++ b/doc/api/integrations.md
@@ -755,42 +755,6 @@ Get External wiki integration settings for a project.
GET /projects/:id/integrations/external-wiki
```
-## Flowdock
-
-Flowdock is a ChatOps application for collaboration in software engineering
-companies. You can send notifications from GitLab events to Flowdock flows.
-For integration instructions, see the [Flowdock documentation](https://www.flowdock.com/help/gitlab).
-
-### Create/Edit Flowdock integration
-
-Set Flowdock integration for a project.
-
-```plaintext
-PUT /projects/:id/integrations/flowdock
-```
-
-Parameters:
-
-| Parameter | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `token` | string | true | Flowdock Git source token |
-
-### Disable Flowdock integration
-
-Disable the Flowdock integration for a project. Integration settings are preserved.
-
-```plaintext
-DELETE /projects/:id/integrations/flowdock
-```
-
-### Get Flowdock integration settings
-
-Get Flowdock integration settings for a project.
-
-```plaintext
-GET /projects/:id/integrations/flowdock
-```
-
## GitHub **(PREMIUM)**
Code collaboration software.
diff --git a/doc/architecture/blueprints/work_items/index.md b/doc/architecture/blueprints/work_items/index.md
index 7f980c2a017..37054c61a85 100644
--- a/doc/architecture/blueprints/work_items/index.md
+++ b/doc/architecture/blueprints/work_items/index.md
@@ -61,7 +61,7 @@ All Work Item types share the same pool of predefined widgets and are customized
| description | |
| hierarchy | |
| [iteration](https://gitlab.com/gitlab-org/gitlab/-/issues/367456) | |
-| [milestone](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) | work_items_mvc_2 |
+| [milestone](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) | work_items_mvc |
| labels | |
| start and due date | |
| status\* | |
diff --git a/doc/integration/index.md b/doc/integration/index.md
index b2a4201e88c..bdf6475b6d2 100644
--- a/doc/integration/index.md
+++ b/doc/integration/index.md
@@ -11,7 +11,7 @@ You can integrate GitLab with external services for enhanced functionality.
## Services
-Services such as Campfire, Flowdock, Jira, Pivotal Tracker, and Slack
+Services such as Campfire, Jira, Pivotal Tracker, and Slack
are available as [integrations](../user/project/integrations/index.md).
## Issue trackers
diff --git a/doc/update/removals.md b/doc/update/removals.md
index c2a14a5cb2e..e4338bae1fc 100644
--- a/doc/update/removals.md
+++ b/doc/update/removals.md
@@ -57,6 +57,12 @@ If you want to preserve this functionality, you can follow one of these two path
1. Fork the [GitLab Auto Deploy Helm chart](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image/-/tree/master/assets/auto-deploy-app) into the `chart/` path within your project
1. Set `AUTO_DEPLOY_IMAGE_VERSION` and `DAST_AUTO_DEPLOY_IMAGE_VERSION` to the most recent version of the image that included the CiliumNetworkPolicy
+## Removed in 15.7
+
+### Flowdock integration
+
+As of December 22, 2022, we are removing the Flowdock integration because the service was shut down on August 15, 2022.
+
## Removed in 15.6
### NFS as Git repository storage is no longer supported. Migrate to Gitaly Cluster as soon as possible
diff --git a/doc/user/project/integrations/index.md b/doc/user/project/integrations/index.md
index 6dbb6a3df37..769a45fc6ff 100644
--- a/doc/user/project/integrations/index.md
+++ b/doc/user/project/integrations/index.md
@@ -58,7 +58,6 @@ You can configure the following integrations.
| [Emails on push](emails_on_push.md) | Send commits and diff of each push by email. | **{dotted-circle}** No |
| [EWM](ewm.md) | Use IBM Engineering Workflow Management as the issue tracker. | **{dotted-circle}** No |
| [External wiki](../wiki/index.md#link-an-external-wiki) | Link an external wiki. | **{dotted-circle}** No |
-| [Flowdock](../../../api/integrations.md#flowdock) | Send notifications from GitLab to Flowdock flows. | **{dotted-circle}** No |
| [GitHub](github.md) | Obtain statuses for commits and pull requests. | **{dotted-circle}** No |
| [Google Chat](hangouts_chat.md) | Send notifications from your GitLab project to a room in Google Chat. | **{dotted-circle}** No |
| [Harbor](harbor.md) | Use Harbor as the container registry. | **{dotted-circle}** No |
diff --git a/doc/user/tasks.md b/doc/user/tasks.md
index 71f427f11c1..9226f8d15c9 100644
--- a/doc/user/tasks.md
+++ b/doc/user/tasks.md
@@ -207,10 +207,12 @@ To set a start date:
## Add a task to a milestone
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) in GitLab 15.5 [with a flag](../administration/feature_flags.md) named `work_items_mvc_2`. Disabled by default.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) in GitLab 15.5 [with a flag](../administration/feature_flags.md) named `work_items_mvc_2`. Disabled by default.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) to feature flag named `work_items_mvc` in GitLab 15.7. Disabled by default.
FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available per group, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `work_items_mvc_2`. On GitLab.com, this feature is not available. The feature is not ready for production use.
+On self-managed GitLab, by default this feature is not available. To make it available per group, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `work_items_mvc`.
+On GitLab.com, this feature is not available. The feature is not ready for production use.
You can add a task to a [milestone](project/milestones/index.md).
You can see the milestone title when you view a task.
diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb
index 99273e81730..543449c0349 100644
--- a/lib/api/helpers/integrations_helpers.rb
+++ b/lib/api/helpers/integrations_helpers.rb
@@ -415,14 +415,6 @@ module API
desc: 'The URL of the external wiki'
}
],
- 'flowdock' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'Flowdock token'
- }
- ],
'hangouts-chat' => [
{
required: true,
@@ -893,7 +885,6 @@ module API
::Integrations::EmailsOnPush,
::Integrations::Ewm,
::Integrations::ExternalWiki,
- ::Integrations::Flowdock,
::Integrations::HangoutsChat,
::Integrations::Harbor,
::Integrations::Irker,
diff --git a/lib/api/merge_request_approvals.rb b/lib/api/merge_request_approvals.rb
index 7622ec717cc..5dea41bfdb7 100644
--- a/lib/api/merge_request_approvals.rb
+++ b/lib/api/merge_request_approvals.rb
@@ -88,6 +88,23 @@ module API
present_approval(merge_request)
end
+ desc 'Remove all merge request approvals' do
+ detail 'Clear all approvals of merge request. This feature was added in GitLab 15.4'
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 404, message: 'Not found' }
+ ]
+ tags %w[merge_requests]
+ end
+ put 'reset_approvals', urgency: :low do
+ merge_request = find_project_merge_request(params[:merge_request_iid])
+
+ unauthorized! unless current_user.can?(:reset_merge_request_approvals, merge_request)
+
+ merge_request.approvals.delete_all
+
+ status :accepted
+ end
end
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index c826872af01..a9572cf7ce6 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -733,25 +733,6 @@ module API
rescue ::MergeRequest::RebaseLockTimeout => e
render_api_error!(e.message, 409)
end
-
- desc 'Reset approvals of a merge request' do
- detail 'Clear all approvals of merge request. This feature was added in GitLab 15.4'
- failure [
- { code: 401, message: 'Unauthorized' },
- { code: 404, message: 'Not found' }
- ]
- tags %w[merge_requests]
- end
- put ':id/merge_requests/:merge_request_iid/reset_approvals', feature_category: :code_review, urgency: :low do
- merge_request = find_project_merge_request(params[:merge_request_iid])
-
- unauthorized! unless current_user.bot? && merge_request.eligible_for_approval_by?(current_user)
-
- merge_request.approvals.delete_all
-
- status :accepted
- end
-
desc 'List issues that close on merge' do
detail 'Get all the issues that would be closed by merging the provided merge request.'
success Entities::MRNote
diff --git a/lib/flowdock/git.rb b/lib/flowdock/git.rb
deleted file mode 100644
index 897ee647d87..00000000000
--- a/lib/flowdock/git.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-# frozen_string_literal: true
-require 'flowdock'
-require 'flowdock/git/builder'
-
-module Flowdock
- class Git
- TokenError = Class.new(StandardError)
-
- DEFAULT_PERMANENT_REFS = [
- Regexp.new('refs/heads/master')
- ].freeze
-
- class << self
- def post(ref, from, to, options = {})
- Git.new(ref, from, to, options).post
- end
- end
-
- def initialize(ref, from, to, options = {})
- raise TokenError, "Flowdock API token not found" unless options[:token]
-
- @ref = ref
- @from = from
- @to = to
- @options = options
- @token = options[:token]
- @commit_url = options[:commit_url]
- @diff_url = options[:diff_url]
- @repo_url = options[:repo_url]
- @repo_name = options[:repo_name]
- @permanent_refs = options.fetch(:permanent_refs, DEFAULT_PERMANENT_REFS)
- end
-
- # Send git push notification to Flowdock
- def post
- messages.each do |message|
- ::Flowdock::Client.new(flow_token: @token).post_to_thread(message)
- end
- end
-
- def repo
- @options[:repo]
- end
-
- private
-
- def messages
- Git::Builder.new(repo: repo,
- ref: @ref,
- before: @from,
- after: @to,
- commit_url: @commit_url,
- branch_url: @branch_url,
- diff_url: @diff_url,
- repo_url: @repo_url,
- repo_name: @repo_name,
- permanent_refs: @permanent_refs,
- tags: tags
- ).to_hashes
- end
-
- # Flowdock tags attached to the push notification
- def tags
- Array(@options[:tags]).map { |tag| CGI.escape(tag) }
- end
- end
-end
diff --git a/lib/flowdock/git/builder.rb b/lib/flowdock/git/builder.rb
deleted file mode 100644
index 88d9814950a..00000000000
--- a/lib/flowdock/git/builder.rb
+++ /dev/null
@@ -1,145 +0,0 @@
-# frozen_string_literal: true
-module Flowdock
- class Git
- class Commit
- def initialize(external_thread_id, thread, tags, commit)
- @commit = commit
- @external_thread_id = external_thread_id
- @thread = thread
- @tags = tags
- end
-
- def to_hash
- hash = {
- external_thread_id: @external_thread_id,
- event: "activity",
- author: {
- name: @commit[:author][:name],
- email: @commit[:author][:email]
- },
- title: title,
- thread: @thread,
- body: body
- }
- hash[:tags] = @tags if @tags
- encode(hash)
- end
-
- private
-
- def encode(hash)
- return hash unless "".respond_to?(:encode)
-
- encode_as_utf8(hash)
- end
-
- # This only works on Ruby 1.9
- def encode_as_utf8(obj)
- if obj.is_a? Hash
- obj.each_pair do |key, val|
- encode_as_utf8(val)
- end
- elsif obj.is_a?(Array)
- obj.each do |val|
- encode_as_utf8(val)
- end
- elsif obj.is_a?(String) && obj.encoding != Encoding::UTF_8
- unless obj.force_encoding("UTF-8").valid_encoding?
- obj.force_encoding("ISO-8859-1").encode!(Encoding::UTF_8, invalid: :replace, undef: :replace)
- end
- end
- end
-
- def body
- content = @commit[:message][first_line.size..]
- content.strip! if content
- "<pre>#{content}</pre>" unless content.empty?
- end
-
- def first_line
- @first_line ||= (@commit[:message].split("\n")[0] || @commit[:message])
- end
-
- def title
- commit_id = @commit[:id][0, 7]
- if @commit[:url]
- "<a href=\"#{@commit[:url]}\">#{commit_id}</a> #{message_title}"
- else
- "#{commit_id} #{message_title}"
- end
- end
-
- def message_title
- CGI.escape_html(first_line.strip)
- end
- end
-
- # Class used to build Git payload
- class Builder
- include ::Gitlab::Utils::StrongMemoize
-
- def initialize(opts)
- @repo = opts[:repo]
- @ref = opts[:ref]
- @before = opts[:before]
- @after = opts[:after]
- @opts = opts
- end
-
- def commits
- @repo.commits_between(@before, @after).map do |commit|
- {
- url: @opts[:commit_url] ? @opts[:commit_url] % [commit.sha] : nil,
- id: commit.sha,
- message: commit.message,
- author: {
- name: commit.author_name,
- email: commit.author_email
- }
- }
- end
- end
-
- def ref_name
- @ref.to_s.sub(%r{\Arefs/(heads|tags)/}, '')
- end
-
- def to_hashes
- commits.map do |commit|
- Commit.new(external_thread_id, thread, @opts[:tags], commit).to_hash
- end
- end
-
- private
-
- def thread
- @thread ||= {
- title: thread_title,
- external_url: @opts[:repo_url]
- }
- end
-
- def permanent?
- strong_memoize(:permanent) do
- @opts[:permanent_refs].any? { |regex| regex.match(@ref) }
- end
- end
-
- def thread_title
- action = "updated" if permanent?
- type = @ref =~ %r(^refs/heads/) ? "branch" : "tag"
-
- [@opts[:repo_name], type, ref_name, action].compact.join(" ")
- end
-
- def external_thread_id
- @external_thread_id ||=
- if permanent?
- SecureRandom.hex
- else
- @ref
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/build/context/build.rb b/lib/gitlab/ci/build/context/build.rb
index a2b330f19b1..a1a8e9288c7 100644
--- a/lib/gitlab/ci/build/context/build.rb
+++ b/lib/gitlab/ci/build/context/build.rb
@@ -50,5 +50,3 @@ module Gitlab
end
end
end
-
-Gitlab::Ci::Build::Context::Build.prepend_mod_with('Gitlab::Ci::Build::Context::Build')
diff --git a/lib/gitlab/process_management.rb b/lib/gitlab/process_management.rb
index f8a1a3a97de..89ffd71c2d8 100644
--- a/lib/gitlab/process_management.rb
+++ b/lib/gitlab/process_management.rb
@@ -40,15 +40,6 @@ module Gitlab
pids.each { |pid| signal(pid, signal) }
end
- # Waits for the given process to complete using a separate thread.
- def self.wait_async(pid)
- Thread.new do
- Process.wait(pid)
- rescue StandardError
- nil # There is no reason to return `Errno::ECHILD` if it catches a `TypeError`
- end
- end
-
# Returns true if all the processes are alive.
def self.all_alive?(pids)
pids.each do |pid|
diff --git a/lib/gitlab/process_supervisor.rb b/lib/gitlab/process_supervisor.rb
index 714034f043d..09e923d1449 100644
--- a/lib/gitlab/process_supervisor.rb
+++ b/lib/gitlab/process_supervisor.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require_relative './daemon'
+
module Gitlab
# Given a set of process IDs, the supervisor can monitor processes
# for being alive and invoke a callback if some or all should go away.
diff --git a/lib/gitlab/ssh/signature.rb b/lib/gitlab/ssh/signature.rb
index b1cad8d76c9..3500d1346e2 100644
--- a/lib/gitlab/ssh/signature.rb
+++ b/lib/gitlab/ssh/signature.rb
@@ -18,11 +18,16 @@ module Gitlab
def verification_status
strong_memoize(:verification_status) do
next :unverified unless all_attributes_present?
- next :unverified unless valid_signature_blob? && committer
+ next :unverified unless valid_signature_blob?
next :unknown_key unless signed_by_key
+ next :other_user unless committer
next :other_user unless signed_by_key.user == committer
- :verified
+ if signed_by_user_email_verified?
+ :verified
+ else
+ :unverified
+ end
end
end
@@ -55,7 +60,11 @@ module Gitlab
def committer
# Lookup by email because users can push verified commits that were made
# by someone else. For example: Doing a rebase.
- strong_memoize(:committer) { User.find_by_any_email(@committer_email, confirmed: true) }
+ strong_memoize(:committer) { User.find_by_any_email(@committer_email) }
+ end
+
+ def signed_by_user_email_verified?
+ signed_by_key.user.verified_emails.include?(@committer_email)
end
def signature
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index bba56eeae78..975f8b50c87 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1279,12 +1279,18 @@ msgstr ""
msgid "(Group Managed Account)"
msgstr ""
+msgid "(Limited to %{quota} pipeline minutes per month)"
+msgstr ""
+
msgid "(No changes)"
msgstr ""
msgid "(UTC %{offset}) %{timezone}"
msgstr ""
+msgid "(Unlimited pipeline minutes)"
+msgstr ""
+
msgid "(check progress)"
msgstr ""
@@ -9256,6 +9262,9 @@ msgstr ""
msgid "ClusterAgents|Unknown user"
msgstr ""
+msgid "ClusterAgents|Use a Helm version compatible with your Kubernetes version (see %{linkStart}Helm version support policy%{linkEnd})."
+msgstr ""
+
msgid "ClusterAgents|Valid access token"
msgstr ""
@@ -17295,15 +17304,6 @@ msgstr ""
msgid "FloC|Participate in FLoC"
msgstr ""
-msgid "FlowdockService|Enter your Flowdock token."
-msgstr ""
-
-msgid "FlowdockService|Send event notifications from GitLab to Flowdock flows."
-msgstr ""
-
-msgid "FlowdockService|Send event notifications from GitLab to Flowdock flows. %{docs_link}"
-msgstr ""
-
msgid "Focus filter bar"
msgstr ""
@@ -27117,6 +27117,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New code quality findings"
+msgstr ""
+
msgid "New confidential epic title "
msgstr ""
@@ -31336,6 +31339,9 @@ msgstr ""
msgid "Proceed"
msgstr ""
+msgid "Product Analytics|Onboarding view"
+msgstr ""
+
msgid "Product analytics"
msgstr ""
@@ -35502,6 +35508,9 @@ msgstr ""
msgid "Runners|IP Address"
msgstr ""
+msgid "Runners|Idle"
+msgstr ""
+
msgid "Runners|Install a runner"
msgstr ""
@@ -35708,6 +35717,9 @@ msgstr ""
msgid "Runners|Runners are the agents that run your CI/CD jobs. To register new runners, please contact your administrator."
msgstr ""
+msgid "Runners|Running"
+msgstr ""
+
msgid "Runners|Runs untagged jobs"
msgstr ""
@@ -38056,6 +38068,9 @@ msgstr ""
msgid "Shared Runners"
msgstr ""
+msgid "Shared Runners:"
+msgstr ""
+
msgid "Shared projects"
msgstr ""
@@ -46704,9 +46719,6 @@ msgstr ""
msgid "WorkItem|Incident"
msgstr ""
-msgid "WorkItem|Introducing tasks"
-msgstr ""
-
msgid "WorkItem|Issue"
msgstr ""
@@ -46716,9 +46728,6 @@ msgstr ""
msgid "WorkItem|Key result"
msgstr ""
-msgid "WorkItem|Learn about tasks."
-msgstr ""
-
msgid "WorkItem|Milestone"
msgstr ""
@@ -46836,9 +46845,6 @@ msgstr ""
msgid "WorkItem|Undo"
msgstr ""
-msgid "WorkItem|Use tasks to break down your work in an issue into smaller pieces. %{learnMoreLink}"
-msgstr ""
-
msgid "WorkItem|View current version"
msgstr ""
diff --git a/sidekiq_cluster/cli.rb b/sidekiq_cluster/cli.rb
index 341ebd9019a..760a5f14c2d 100644
--- a/sidekiq_cluster/cli.rb
+++ b/sidekiq_cluster/cli.rb
@@ -112,7 +112,7 @@ module Gitlab
end
def start_and_supervise_workers(queue_groups)
- worker_pids = SidekiqCluster.start(
+ wait_threads = SidekiqCluster.start(
queue_groups,
env: @environment,
directory: @rails_path,
@@ -135,6 +135,7 @@ module Gitlab
)
metrics_server_pid = start_metrics_server
+ worker_pids = wait_threads.map(&:pid)
supervisor.supervise(worker_pids + Array(metrics_server_pid)) do |dead_pids|
# If we're not in the process of shutting down the cluster,
# and the metrics server died, restart it.
@@ -149,6 +150,13 @@ module Gitlab
[]
end
end
+
+ exit_statuses = wait_threads.map do |thread|
+ thread.join
+ thread.value
+ end
+
+ exit 1 unless exit_statuses.compact.all?(&:success?)
end
def start_metrics_server
diff --git a/sidekiq_cluster/sidekiq_cluster.rb b/sidekiq_cluster/sidekiq_cluster.rb
index 66fb5603d2b..1ed08e7e839 100644
--- a/sidekiq_cluster/sidekiq_cluster.rb
+++ b/sidekiq_cluster/sidekiq_cluster.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative '../lib/gitlab/process_management'
+require_relative '../lib/gitlab/process_supervisor'
module Gitlab
module SidekiqCluster
@@ -33,7 +34,8 @@ module Gitlab
#
# directory - The directory of the Rails application.
#
- # Returns an Array containing the PIDs of the started processes.
+ # Returns an Array containing the waiter threads (from Process.detach) of
+ # the started processes.
def self.start(queues, env: :development, directory: Dir.pwd, max_concurrency: 20, min_concurrency: 0, timeout: DEFAULT_SOFT_TIMEOUT_SECONDS, dryrun: false)
queues.map.with_index do |pair, index|
start_sidekiq(pair, env: env,
@@ -82,9 +84,7 @@ module Gitlab
)
end
- ProcessManagement.wait_async(pid)
-
- pid
+ Process.detach(pid)
end
def self.count_by_queue(queues)
diff --git a/spec/commands/sidekiq_cluster/cli_spec.rb b/spec/commands/sidekiq_cluster/cli_spec.rb
index 4618c6681d3..c2ea9455de6 100644
--- a/spec/commands/sidekiq_cluster/cli_spec.rb
+++ b/spec/commands/sidekiq_cluster/cli_spec.rb
@@ -299,11 +299,11 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
end
context 'starting the server' do
- context 'without --dryrun' do
- before do
- allow(Gitlab::SidekiqCluster).to receive(:start).and_return([])
- end
+ before do
+ allow(Gitlab::SidekiqCluster).to receive(:start).and_return([])
+ end
+ context 'without --dryrun' do
it 'wipes the metrics directory before starting workers' do
expect(metrics_cleanup_service).to receive(:execute).ordered
expect(Gitlab::SidekiqCluster).to receive(:start).ordered.and_return([])
@@ -403,9 +403,42 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
let(:sidekiq_exporter_enabled) { true }
let(:metrics_server_pid) { 99 }
let(:sidekiq_worker_pids) { [2, 42] }
+ let(:waiter_threads) { [instance_double('Process::Waiter'), instance_double('Process::Waiter')] }
+ let(:process_status) { instance_double('Process::Status') }
before do
- allow(Gitlab::SidekiqCluster).to receive(:start).and_return(sidekiq_worker_pids)
+ allow(Gitlab::SidekiqCluster).to receive(:start).and_return(waiter_threads)
+ allow(process_status).to receive(:success?).and_return(true)
+ allow(cli).to receive(:exit)
+
+ waiter_threads.each.with_index do |thread, i|
+ allow(thread).to receive(:join)
+ allow(thread).to receive(:pid).and_return(sidekiq_worker_pids[i])
+ allow(thread).to receive(:value).and_return(process_status)
+ end
+ end
+
+ context 'when one of the workers has been terminated gracefully' do
+ it 'stops the entire process cluster' do
+ expect(MetricsServer).to receive(:start_for_sidekiq).once.and_return(metrics_server_pid)
+ expect(supervisor).to receive(:supervise).and_yield([2, 99])
+ expect(supervisor).to receive(:shutdown)
+ expect(cli).not_to receive(:exit).with(1)
+
+ cli.run(%w(foo))
+ end
+ end
+
+ context 'when one of the workers has failed' do
+ it 'stops the entire process cluster and exits with a non-zero code' do
+ expect(MetricsServer).to receive(:start_for_sidekiq).once.and_return(metrics_server_pid)
+ expect(supervisor).to receive(:supervise).and_yield([2, 99])
+ expect(supervisor).to receive(:shutdown)
+ expect(process_status).to receive(:success?).and_return(false)
+ expect(cli).to receive(:exit).with(1)
+
+ cli.run(%w(foo))
+ end
end
it 'stops the entire process cluster if one of the workers has been terminated' do
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 9e0a507626a..072cfacbe3b 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -2,16 +2,31 @@
require 'spec_helper'
+# Flaky spec warning: the queries in this file routinely exceed the defined GraphQL query limit of 100.
+# Until those queries are optimized, we need to disable query limit checking in order for these tests
+# to pass consistently. Note that removing the disabling code can lead to flaky failures locally and in CI.
+#
+# In addition, it seems as though the use of `let_it_be` might be causing some of the
+# flakiness, as discussed in https://github.com/test-prof/test-prof/blob/master/docs/recipes/let_it_be.md#modifiers.
+# `reload: true` has been added to all `let_it_be` statements.
+#
+# See:
+# - https://gitlab.com/gitlab-org/gitlab/-/issues/323426
+# - https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56458#note_535900110
+# - https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102719
+# - https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105849
+# - https://gitlab.com/gitlab-org/gitlab/-/issues/383970
+#
RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do
include DragTo
include MobileHelpers
include BoardHelpers
- let_it_be(:group) { create(:group, :nested) }
- let_it_be(:project) { create(:project, :public, namespace: group) }
- let_it_be(:board) { create(:board, project: project) }
- let_it_be(:user) { create(:user) }
- let_it_be(:user2) { create(:user) }
+ let_it_be(:group, reload: true) { create(:group, :nested) }
+ let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
+ let_it_be(:board, reload: true) { create(:board, project: project) }
+ let_it_be(:user, reload: true) { create(:user) }
+ let_it_be(:user2, reload: true) { create(:user) }
let(:filtered_search) { find('[data-testid="issue-board-filtered-search"]') }
let(:filter_input) { find('.gl-filtered-search-term-input') }
@@ -47,31 +62,31 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do
end
context 'with lists' do
- let_it_be(:milestone) { create(:milestone, project: project) }
-
- let_it_be(:planning) { create(:label, project: project, name: 'Planning', description: 'Test') }
- let_it_be(:development) { create(:label, project: project, name: 'Development') }
- let_it_be(:testing) { create(:label, project: project, name: 'Testing') }
- let_it_be(:bug) { create(:label, project: project, name: 'Bug') }
- let_it_be(:backlog) { create(:label, project: project, name: 'Backlog') }
- let_it_be(:closed) { create(:label, project: project, name: 'Closed') }
- let_it_be(:accepting) { create(:label, project: project, name: 'Accepting Merge Requests') }
- let_it_be(:a_plus) { create(:label, project: project, name: 'A+') }
- let_it_be(:list1) { create(:list, board: board, label: planning, position: 0) }
- let_it_be(:list2) { create(:list, board: board, label: development, position: 1) }
- let_it_be(:backlog_list) { create(:backlog_list, board: board) }
-
- let_it_be(:confidential_issue) { create(:labeled_issue, :confidential, project: project, author: user, labels: [planning], relative_position: 9) }
- let_it_be(:issue1) { create(:labeled_issue, project: project, title: 'aaa', description: '111', assignees: [user], labels: [planning], relative_position: 8) }
- let_it_be(:issue2) { create(:labeled_issue, project: project, title: 'bbb', description: '222', author: user2, labels: [planning], relative_position: 7) }
- let_it_be(:issue3) { create(:labeled_issue, project: project, title: 'ccc', description: '333', labels: [planning], relative_position: 6) }
- let_it_be(:issue4) { create(:labeled_issue, project: project, title: 'ddd', description: '444', labels: [planning], relative_position: 5) }
- let_it_be(:issue5) { create(:labeled_issue, project: project, title: 'eee', description: '555', labels: [planning], milestone: milestone, relative_position: 4) }
- let_it_be(:issue6) { create(:labeled_issue, project: project, title: 'fff', description: '666', labels: [planning, development], relative_position: 3) }
- let_it_be(:issue7) { create(:labeled_issue, project: project, title: 'ggg', description: '777', labels: [development], relative_position: 2) }
- let_it_be(:issue8) { create(:closed_issue, project: project, title: 'hhh', description: '888') }
- let_it_be(:issue9) { create(:labeled_issue, project: project, title: 'iii', description: '999', labels: [planning, testing, bug, accepting], relative_position: 1) }
- let_it_be(:issue10) { create(:labeled_issue, project: project, title: 'issue +', description: 'A+ great issue', labels: [a_plus]) }
+ let_it_be(:milestone, reload: true) { create(:milestone, project: project) }
+
+ let_it_be(:planning, reload: true) { create(:label, project: project, name: 'Planning', description: 'Test') }
+ let_it_be(:development, reload: true) { create(:label, project: project, name: 'Development') }
+ let_it_be(:testing, reload: true) { create(:label, project: project, name: 'Testing') }
+ let_it_be(:bug, reload: true) { create(:label, project: project, name: 'Bug') }
+ let_it_be(:backlog, reload: true) { create(:label, project: project, name: 'Backlog') }
+ let_it_be(:closed, reload: true) { create(:label, project: project, name: 'Closed') }
+ let_it_be(:accepting, reload: true) { create(:label, project: project, name: 'Accepting Merge Requests') }
+ let_it_be(:a_plus, reload: true) { create(:label, project: project, name: 'A+') }
+ let_it_be(:list1, reload: true) { create(:list, board: board, label: planning, position: 0) }
+ let_it_be(:list2, reload: true) { create(:list, board: board, label: development, position: 1) }
+ let_it_be(:backlog_list, reload: true) { create(:backlog_list, board: board) }
+
+ let_it_be(:confidential_issue, reload: true) { create(:labeled_issue, :confidential, project: project, author: user, labels: [planning], relative_position: 9) }
+ let_it_be(:issue1, reload: true) { create(:labeled_issue, project: project, title: 'aaa', description: '111', assignees: [user], labels: [planning], relative_position: 8) }
+ let_it_be(:issue2, reload: true) { create(:labeled_issue, project: project, title: 'bbb', description: '222', author: user2, labels: [planning], relative_position: 7) }
+ let_it_be(:issue3, reload: true) { create(:labeled_issue, project: project, title: 'ccc', description: '333', labels: [planning], relative_position: 6) }
+ let_it_be(:issue4, reload: true) { create(:labeled_issue, project: project, title: 'ddd', description: '444', labels: [planning], relative_position: 5) }
+ let_it_be(:issue5, reload: true) { create(:labeled_issue, project: project, title: 'eee', description: '555', labels: [planning], milestone: milestone, relative_position: 4) }
+ let_it_be(:issue6, reload: true) { create(:labeled_issue, project: project, title: 'fff', description: '666', labels: [planning, development], relative_position: 3) }
+ let_it_be(:issue7, reload: true) { create(:labeled_issue, project: project, title: 'ggg', description: '777', labels: [development], relative_position: 2) }
+ let_it_be(:issue8, reload: true) { create(:closed_issue, project: project, title: 'hhh', description: '888') }
+ let_it_be(:issue9, reload: true) { create(:labeled_issue, project: project, title: 'iii', description: '999', labels: [planning, testing, bug, accepting], relative_position: 1) }
+ let_it_be(:issue10, reload: true) { create(:labeled_issue, project: project, title: 'issue +', description: 'A+ great issue', labels: [a_plus]) }
before do
visit_project_board(project, board)
@@ -125,7 +140,7 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do
it 'infinite scrolls list' do
create_list(:labeled_issue, 30, project: project, labels: [planning])
- visit_project_board(project, board)
+ visit_project_board_path_without_query_limit(project, board)
page.within(find('.board:nth-child(2)')) do
expect(page.find('.board-header')).to have_content('38')
@@ -204,31 +219,26 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do
expect(find('.board:nth-child(3) [data-testid="board-list-header"]')).to have_content(planning.title)
# Make sure list positions are preserved after a reload
- visit_project_board(project, board)
+ visit_project_board_path_without_query_limit(project, board)
expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(development.title)
expect(find('.board:nth-child(3) [data-testid="board-list-header"]')).to have_content(planning.title)
end
context 'without backlog and closed lists' do
- let_it_be(:board) { create(:board, project: project, hide_backlog_list: true, hide_closed_list: true) }
- let_it_be(:list1) { create(:list, board: board, label: planning, position: 0) }
- let_it_be(:list2) { create(:list, board: board, label: development, position: 1) }
+ let_it_be(:board, reload: true) { create(:board, project: project, hide_backlog_list: true, hide_closed_list: true) }
+ let_it_be(:list1, reload: true) { create(:list, board: board, label: planning, position: 0) }
+ let_it_be(:list2, reload: true) { create(:list, board: board, label: development, position: 1) }
it 'changes position of list' do
- inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
- visit_project_board(project, board)
- end
+ visit_project_board_path_without_query_limit(project, board)
drag(list_from_index: 0, list_to_index: 1, selector: '.board-header')
expect(find('.board:nth-child(1) [data-testid="board-list-header"]')).to have_content(development.title)
expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(planning.title)
- inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
- # Make sure list positions are preserved after a reload
- visit_project_board(project, board)
- end
+ visit_project_board_path_without_query_limit(project, board)
expect(find('.board:nth-child(1) [data-testid="board-list-header"]')).to have_content(development.title)
expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(planning.title)
@@ -531,7 +541,7 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do
end
context 'as guest user' do
- let_it_be(:user_guest) { create(:user) }
+ let_it_be(:user_guest, reload: true) { create(:user) }
before do
stub_feature_flags(apollo_boards: false)
@@ -601,4 +611,10 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do
wait_for_requests
end
+
+ def visit_project_board_path_without_query_limit(project, board)
+ inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
+ visit_project_board(project, board)
+ end
+ end
end
diff --git a/spec/features/clusters/create_agent_spec.rb b/spec/features/clusters/create_agent_spec.rb
index d01fa520cb0..01902c36e99 100644
--- a/spec/features/clusters/create_agent_spec.rb
+++ b/spec/features/clusters/create_agent_spec.rb
@@ -30,8 +30,8 @@ RSpec.describe 'Cluster agent registration', :js, feature_category: :kubernetes_
click_button('Connect a cluster')
expect(page).to have_content('Register')
- click_button('Select an agent')
- click_button('example-agent-2')
+ click_button('Select an agent or enter a name to create new')
+ page.find('li', text: 'example-agent-2').click
click_button('Register')
expect(page).to have_content('You cannot see this token again after you close this window.')
diff --git a/spec/features/merge_request/user_assigns_themselves_reviewer_spec.rb b/spec/features/merge_request/user_assigns_themselves_reviewer_spec.rb
new file mode 100644
index 00000000000..2b93f88e96b
--- /dev/null
+++ b/spec/features/merge_request/user_assigns_themselves_reviewer_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Merge request > User assigns themselves as a reviewer', feature_category: :code_review do
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "test mr") }
+
+ context 'when logged in as a member of the project' do
+ before do
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'updates updated_by', :js do
+ wait_for_requests
+
+ expect do
+ page.within('.reviewer') do
+ click_button 'assign yourself'
+ end
+
+ expect(find('.reviewer')).to have_content(user.name)
+ wait_for_all_requests
+ end.to change { merge_request.reload.updated_at }
+ end
+
+ context 'when logged in as a non-member of the project' do
+ before do
+ sign_in(create(:user))
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not show link to assign self as Reviewer' do
+ page.within('.reviewer') do
+ expect(page).not_to have_content 'Assign yourself'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_request/user_assigns_themselves_spec.rb b/spec/features/merge_request/user_assigns_themselves_spec.rb
index 8244668b444..826904bd165 100644
--- a/spec/features/merge_request/user_assigns_themselves_spec.rb
+++ b/spec/features/merge_request/user_assigns_themselves_spec.rb
@@ -22,8 +22,12 @@ RSpec.describe 'Merge request > User assigns themselves', feature_category: :cod
end
it 'updates updated_by', :js do
+ wait_for_requests
+
expect do
- click_button 'assign yourself'
+ page.within('[data-testid="assignee-block-container"]') do
+ click_button 'assign yourself'
+ end
expect(find('.assignee')).to have_content(user.name)
wait_for_all_requests
@@ -36,7 +40,9 @@ RSpec.describe 'Merge request > User assigns themselves', feature_category: :cod
end
it 'does not display if related issues are already assigned' do
- expect(page).not_to have_content 'Assign yourself'
+ page.within('[data-testid="assignee-block-container"]') do
+ expect(page).not_to have_content 'Assign yourself'
+ end
end
end
end
@@ -48,7 +54,9 @@ RSpec.describe 'Merge request > User assigns themselves', feature_category: :cod
end
it 'does not show assignment link' do
- expect(page).not_to have_content 'Assign yourself'
+ page.within('[data-testid="assignee-block-container"]') do
+ expect(page).not_to have_content 'Assign yourself'
+ end
end
end
end
diff --git a/spec/features/projects/integrations/user_activates_flowdock_spec.rb b/spec/features/projects/integrations/user_activates_flowdock_spec.rb
deleted file mode 100644
index 9786ea9a692..00000000000
--- a/spec/features/projects/integrations/user_activates_flowdock_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'User activates Flowdock', feature_category: :integrations do
- include_context 'project integration activation' do
- let(:project) { create(:project, :repository) }
- end
-
- before do
- stub_request(:post, /.*api.flowdock.com.*/)
- end
-
- it 'activates integration', :js do
- visit_project_integration('Flowdock')
- fill_in('Token', with: 'verySecret')
-
- click_test_then_save_integration(expect_test_to_fail: false)
-
- expect(page).to have_content('Flowdock settings saved and active.')
- end
-end
diff --git a/spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson b/spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson
index e5d39512255..2d092d4e916 100644
--- a/spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson
+++ b/spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson
@@ -7,7 +7,6 @@
{"id":95,"project_id":5,"created_at":"2016-06-14T15:01:51.255Z","updated_at":"2016-06-14T15:01:51.255Z","active":false,"properties":{"api_url":"","jira_issue_transition_id":"2"},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"JiraService","category":"issue_tracker","default":false,"wiki_page_events":true}
{"id":94,"project_id":5,"created_at":"2016-06-14T15:01:51.232Z","updated_at":"2016-06-14T15:01:51.232Z","active":true,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"IrkerService","category":"common","default":false,"wiki_page_events":true}
{"id":93,"project_id":5,"created_at":"2016-06-14T15:01:51.219Z","updated_at":"2016-06-14T15:01:51.219Z","active":false,"properties":{"notify_only_broken_pipelines":true},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"pipeline_events":true,"type":"HipchatService","category":"common","default":false,"wiki_page_events":true}
-{"id":91,"project_id":5,"created_at":"2016-06-14T15:01:51.182Z","updated_at":"2016-06-14T15:01:51.182Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"FlowdockService","category":"common","default":false,"wiki_page_events":true}
{"id":90,"project_id":5,"created_at":"2016-06-14T15:01:51.166Z","updated_at":"2016-06-14T15:01:51.166Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"ExternalWikiService","category":"common","default":false,"wiki_page_events":true}
{"id":89,"project_id":5,"created_at":"2016-06-14T15:01:51.153Z","updated_at":"2016-06-14T15:01:51.153Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"EmailsOnPushService","category":"common","default":false,"wiki_page_events":true}
{"id":88,"project_id":5,"created_at":"2016-06-14T15:01:51.139Z","updated_at":"2016-06-14T15:01:51.139Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"DroneCiService","category":"ci","default":false,"wiki_page_events":true}
diff --git a/spec/frontend/batch_comments/components/draft_note_spec.js b/spec/frontend/batch_comments/components/draft_note_spec.js
index 03ecbc01a56..3d4eddedf34 100644
--- a/spec/frontend/batch_comments/components/draft_note_spec.js
+++ b/spec/frontend/batch_comments/components/draft_note_spec.js
@@ -1,6 +1,5 @@
import { nextTick } from 'vue';
import { GlButton, GlBadge } from '@gitlab/ui';
-import { getByRole } from '@testing-library/dom';
import { shallowMount } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
import DraftNote from '~/batch_comments/components/draft_note.vue';
@@ -14,6 +13,7 @@ const NoteableNoteStub = stubComponent(NoteableNote, {
template: `
<div>
<slot name="note-header-info">Test</slot>
+ <slot name="after-note-body">Test</slot>
</div>
`,
});
@@ -29,7 +29,6 @@ describe('Batch comments draft note component', () => {
},
};
- const getList = () => getByRole(wrapper.element, 'list');
const findSubmitReviewButton = () => wrapper.findComponent(PublishButton);
const findAddCommentButton = () => wrapper.findComponent(GlButton);
@@ -189,7 +188,7 @@ describe('Batch comments draft note component', () => {
});
it(`calls store ${expectedCalls.length} times on ${event}`, () => {
- getList().dispatchEvent(new MouseEvent(event, { bubbles: true }));
+ wrapper.element.dispatchEvent(new MouseEvent(event, { bubbles: true }));
expect(store.dispatch.mock.calls).toEqual(expectedCalls);
});
});
diff --git a/spec/frontend/ci/runner/components/cells/runner_summary_cell_spec.js b/spec/frontend/ci/runner/components/cells/runner_summary_cell_spec.js
index 20463a63419..2bd7b20512d 100644
--- a/spec/frontend/ci/runner/components/cells/runner_summary_cell_spec.js
+++ b/spec/frontend/ci/runner/components/cells/runner_summary_cell_spec.js
@@ -3,6 +3,7 @@ import { mountExtended } from 'helpers/vue_test_utils_helper';
import RunnerSummaryCell from '~/ci/runner/components/cells/runner_summary_cell.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import RunnerTags from '~/ci/runner/components/runner_tags.vue';
+import RunnerJobStatusBadge from '~/ci/runner/components/runner_job_status_badge.vue';
import RunnerSummaryField from '~/ci/runner/components/cells/runner_summary_field.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
@@ -16,6 +17,7 @@ describe('RunnerTypeCell', () => {
let wrapper;
const findLockIcon = () => wrapper.findByTestId('lock-icon');
+ const findRunnerJobStatusBadge = () => wrapper.findComponent(RunnerJobStatusBadge);
const findRunnerTags = () => wrapper.findComponent(RunnerTags);
const findRunnerSummaryField = (icon) =>
wrapper.findAllComponents(RunnerSummaryField).filter((w) => w.props('icon') === icon)
@@ -80,6 +82,10 @@ describe('RunnerTypeCell', () => {
expect(wrapper.text()).toContain(mockRunner.description);
});
+ it('Displays job execution status', () => {
+ expect(findRunnerJobStatusBadge().props('jobStatus')).toBe(mockRunner.jobExecutionStatus);
+ });
+
it('Displays last contact', () => {
createComponent({
contactedAt: '2022-01-02',
diff --git a/spec/frontend/ci/runner/components/runner_job_status_badge_spec.js b/spec/frontend/ci/runner/components/runner_job_status_badge_spec.js
new file mode 100644
index 00000000000..8cd3fb3cb4c
--- /dev/null
+++ b/spec/frontend/ci/runner/components/runner_job_status_badge_spec.js
@@ -0,0 +1,44 @@
+import { GlBadge } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import RunnerJobStatusBadge from '~/ci/runner/components/runner_job_status_badge.vue';
+import {
+ I18N_JOB_STATUS_RUNNING,
+ I18N_JOB_STATUS_IDLE,
+ JOB_STATUS_RUNNING,
+ JOB_STATUS_IDLE,
+} from '~/ci/runner/constants';
+
+describe('RunnerTypeBadge', () => {
+ let wrapper;
+
+ const findBadge = () => wrapper.findComponent(GlBadge);
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(RunnerJobStatusBadge, {
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ it.each`
+ jobStatus | classes | text
+ ${JOB_STATUS_RUNNING} | ${['gl-mr-3', 'gl-bg-transparent!', 'gl-text-blue-600!', 'gl-border', 'gl-border-blue-600!']} | ${I18N_JOB_STATUS_RUNNING}
+ ${JOB_STATUS_IDLE} | ${['gl-mr-3', 'gl-bg-transparent!', 'gl-text-gray-700!', 'gl-border', 'gl-border-gray-500!']} | ${I18N_JOB_STATUS_IDLE}
+ `(
+ 'renders $jobStatus job status with "$text" text and styles',
+ ({ jobStatus, classes, text }) => {
+ createComponent({ jobStatus });
+
+ expect(findBadge().props()).toMatchObject({ size: 'sm', variant: 'muted' });
+ expect(findBadge().classes().sort()).toEqual(classes.sort());
+ expect(findBadge().text()).toBe(text);
+ },
+ );
+
+ it('does not render an unknown status', () => {
+ createComponent({ jobStatus: 'UNKNOWN_STATUS' });
+
+ expect(wrapper.html()).toBe('');
+ });
+});
diff --git a/spec/frontend/clusters_list/components/agent_token_spec.js b/spec/frontend/clusters_list/components/agent_token_spec.js
index e656a601699..a92a03fedb6 100644
--- a/spec/frontend/clusters_list/components/agent_token_spec.js
+++ b/spec/frontend/clusters_list/components/agent_token_spec.js
@@ -1,10 +1,12 @@
-import { GlAlert, GlFormInputGroup } from '@gitlab/ui';
+import { GlAlert, GlFormInputGroup, GlSprintf, GlLink, GlIcon } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { sprintf } from '~/locale';
import AgentToken from '~/clusters_list/components/agent_token.vue';
import {
I18N_AGENT_TOKEN,
INSTALL_AGENT_MODAL_ID,
NAME_MAX_LENGTH,
+ HELM_VERSION_POLICY_URL,
} from '~/clusters_list/constants';
import { generateAgentRegistrationCommand } from '~/clusters_list/clusters_util';
import CodeBlock from '~/vue_shared/components/code_block.vue';
@@ -23,6 +25,8 @@ describe('InstallAgentModal', () => {
const findCodeBlock = () => wrapper.findComponent(CodeBlock);
const findCopyButton = () => wrapper.findComponent(ModalCopyButton);
const findInput = () => wrapper.findComponent(GlFormInputGroup);
+ const findHelmVersionPolicyLink = () => wrapper.findComponent(GlLink);
+ const findHelmExternalLinkIcon = () => wrapper.findComponent(GlIcon);
const createWrapper = (newAgentName = agentName) => {
const provide = {
@@ -39,6 +43,9 @@ describe('InstallAgentModal', () => {
wrapper = shallowMountExtended(AgentToken, {
provide,
propsData,
+ stubs: {
+ GlSprintf,
+ },
});
};
@@ -56,6 +63,17 @@ describe('InstallAgentModal', () => {
expect(wrapper.text()).toContain(I18N_AGENT_TOKEN.basicInstallBody);
});
+ it('shows Helm version policy text with an external link', () => {
+ expect(wrapper.text()).toContain(
+ sprintf(I18N_AGENT_TOKEN.helmVersionText, { linkStart: '', linkEnd: ' ' }),
+ );
+ expect(findHelmVersionPolicyLink().attributes()).toMatchObject({
+ href: HELM_VERSION_POLICY_URL,
+ target: '_blank',
+ });
+ expect(findHelmExternalLinkIcon().props()).toMatchObject({ name: 'external-link', size: 12 });
+ });
+
it('shows advanced agent installation instructions', () => {
expect(wrapper.text()).toContain(I18N_AGENT_TOKEN.advancedInstallTitle);
});
diff --git a/spec/frontend/clusters_list/components/available_agents_dropwdown_spec.js b/spec/frontend/clusters_list/components/available_agents_dropwdown_spec.js
index 197735d3c77..0a8447c5d80 100644
--- a/spec/frontend/clusters_list/components/available_agents_dropwdown_spec.js
+++ b/spec/frontend/clusters_list/components/available_agents_dropwdown_spec.js
@@ -1,34 +1,32 @@
-import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
+import { GlSearchBoxByType, GlCollapsibleListbox, GlButton } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { ENTER_KEY } from '~/lib/utils/keys';
import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue';
import { I18N_AVAILABLE_AGENTS_DROPDOWN } from '~/clusters_list/constants';
describe('AvailableAgentsDropdown', () => {
let wrapper;
+ const configuredAgent = 'configured-agent';
+ const searchAgentName = 'search-agent';
+ const newAgentName = 'new-agent';
+
const i18n = I18N_AVAILABLE_AGENTS_DROPDOWN;
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
- const findFirstAgentItem = () => findDropdownItems().at(0);
- const findSearchInput = () => wrapper.findComponent(GlSearchBoxByType);
- const findCreateButton = () => wrapper.findByTestId('create-config-button');
+ const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
+ const findSearchInput = () => findDropdown().findComponent(GlSearchBoxByType);
+ const findCreateButton = () => wrapper.findComponent(GlButton);
const createWrapper = ({ propsData }) => {
wrapper = shallowMountExtended(AvailableAgentsDropdown, {
propsData,
- stubs: { GlDropdown },
+ stubs: { GlCollapsibleListbox },
});
- wrapper.vm.$refs.dropdown.hide = jest.fn();
+ wrapper.vm.$refs.dropdown.closeAndFocus = jest.fn();
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('there are agents available', () => {
const propsData = {
- availableAgents: ['configured-agent', 'search-agent', 'test-agent'],
+ availableAgents: [configuredAgent, searchAgentName, 'test-agent'],
isRegistering: false,
};
@@ -37,91 +35,92 @@ describe('AvailableAgentsDropdown', () => {
});
it('prompts to select an agent', () => {
- expect(findDropdown().props('text')).toBe(i18n.selectAgent);
+ expect(findDropdown().props('toggleText')).toBe(i18n.selectAgent);
});
describe('search agent', () => {
it('renders search button', () => {
- expect(findSearchInput().exists()).toBe(true);
+ expect(findDropdown().props('searchable')).toBe(true);
});
it('renders all agents when search term is empty', () => {
- expect(findDropdownItems()).toHaveLength(3);
+ expect(findDropdown().props('items')).toHaveLength(3);
});
it('renders only the agent searched for when the search item exists', async () => {
- await findSearchInput().vm.$emit('input', 'search-agent');
-
- expect(findDropdownItems()).toHaveLength(1);
- expect(findFirstAgentItem().text()).toBe('search-agent');
- });
+ findSearchInput().vm.$emit('input', searchAgentName);
+ await nextTick();
- it('renders create button when search started', async () => {
- await findSearchInput().vm.$emit('input', 'new-agent');
-
- expect(findCreateButton().exists()).toBe(true);
+ expect(findDropdown().props('items')).toMatchObject([
+ { text: searchAgentName, value: searchAgentName },
+ ]);
});
- it("doesn't render create button when search item is found", async () => {
- await findSearchInput().vm.$emit('input', 'search-agent');
-
- expect(findCreateButton().exists()).toBe(false);
+ describe('create button', () => {
+ it.each`
+ condition | search | createButtonRendered
+ ${'is rendered'} | ${newAgentName} | ${true}
+ ${'is not rendered'} | ${''} | ${false}
+ ${'is not rendered'} | ${searchAgentName} | ${false}
+ `('$condition when search is "$search"', async ({ search, createButtonRendered }) => {
+ findSearchInput().vm.$emit('input', search);
+ await nextTick();
+
+ expect(findCreateButton().exists()).toBe(createButtonRendered);
+ });
});
});
describe('select existing agent configuration', () => {
beforeEach(() => {
- findFirstAgentItem().vm.$emit('click');
+ findDropdown().vm.$emit('select', configuredAgent);
});
- it('emits agentSelected with the name of the clicked agent', () => {
- expect(wrapper.emitted('agentSelected')).toEqual([['configured-agent']]);
+ it('emits `agentSelected` with the name of the clicked agent', () => {
+ expect(wrapper.emitted('agentSelected')).toEqual([[configuredAgent]]);
});
it('marks the clicked item as selected', () => {
- expect(findDropdown().props('text')).toBe('configured-agent');
- expect(findFirstAgentItem().props('isChecked')).toBe(true);
+ expect(findDropdown().props('toggleText')).toBe(configuredAgent);
});
});
describe('create new agent configuration', () => {
beforeEach(async () => {
- await findSearchInput().vm.$emit('input', 'new-agent');
+ findSearchInput().vm.$emit('input', newAgentName);
+ await nextTick();
findCreateButton().vm.$emit('click');
});
it('emits agentSelected with the name of the clicked agent', () => {
- expect(wrapper.emitted('agentSelected')).toEqual([['new-agent']]);
+ expect(wrapper.emitted('agentSelected')).toEqual([[newAgentName]]);
});
it('marks the clicked item as selected', () => {
- expect(findDropdown().props('text')).toBe('new-agent');
+ expect(findDropdown().props('toggleText')).toBe(newAgentName);
});
});
describe('click enter to register new agent without configuration', () => {
beforeEach(async () => {
- await findSearchInput().vm.$emit('input', 'new-agent');
- await findSearchInput().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
+ findSearchInput().vm.$emit('input', newAgentName);
+ await nextTick();
+ await findDropdown().trigger('keydown.enter');
});
it('emits agentSelected with the name of the clicked agent', () => {
- expect(wrapper.emitted('agentSelected')).toEqual([['new-agent']]);
+ expect(wrapper.emitted('agentSelected')).toEqual([[newAgentName]]);
});
it('marks the clicked item as selected', () => {
- expect(findDropdown().props('text')).toBe('new-agent');
- });
-
- it('closes the dropdown', () => {
- expect(wrapper.vm.$refs.dropdown.hide).toHaveBeenCalledTimes(1);
+ expect(findDropdown().props('toggleText')).toBe(newAgentName);
});
});
});
describe('registration in progress', () => {
const propsData = {
- availableAgents: ['configured-agent'],
+ availableAgents: [configuredAgent],
isRegistering: true,
};
@@ -130,7 +129,7 @@ describe('AvailableAgentsDropdown', () => {
});
it('updates the text in the dropdown', () => {
- expect(findDropdown().props('text')).toBe(i18n.registeringAgent);
+ expect(findDropdown().props('toggleText')).toBe(i18n.registeringAgent);
});
it('displays a loading icon', () => {
diff --git a/spec/frontend/diffs/components/diff_code_quality_spec.js b/spec/frontend/diffs/components/diff_code_quality_spec.js
index 5fc5a57ee5b..7bd9afab648 100644
--- a/spec/frontend/diffs/components/diff_code_quality_spec.js
+++ b/spec/frontend/diffs/components/diff_code_quality_spec.js
@@ -2,11 +2,13 @@ import { GlIcon } from '@gitlab/ui';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import DiffCodeQuality from '~/diffs/components/diff_code_quality.vue';
import { SEVERITY_CLASSES, SEVERITY_ICONS } from '~/ci/reports/codequality_report/constants';
+import { NEW_CODE_QUALITY_FINDINGS } from '~/diffs/i18n';
import { multipleFindingsArr } from '../mock_data/diff_code_quality';
let wrapper;
const findIcon = () => wrapper.findComponent(GlIcon);
+const findHeading = () => wrapper.findByTestId(`diff-codequality-findings-heading`);
describe('DiffCodeQuality', () => {
afterEach(() => {
@@ -30,14 +32,17 @@ describe('DiffCodeQuality', () => {
expect(wrapper.emitted('hideCodeQualityFindings').length).toBe(1);
});
- it('renders correct amount of list items for codequality array and their description', async () => {
+ it('renders heading and correct amount of list items for codequality array and their description', async () => {
wrapper = createWrapper(multipleFindingsArr);
- const listItems = wrapper.findAll('li');
+ expect(findHeading().text()).toEqual(NEW_CODE_QUALITY_FINDINGS);
+ const listItems = wrapper.findAll('li');
expect(wrapper.findAll('li').length).toBe(5);
listItems.wrappers.map((e, i) => {
- return expect(e.text()).toEqual(multipleFindingsArr[i].description);
+ return expect(e.text()).toContain(
+ `${multipleFindingsArr[i].severity} - ${multipleFindingsArr[i].description}`,
+ );
});
});
diff --git a/spec/frontend/filtered_search/filtered_search_manager_spec.js b/spec/frontend/filtered_search/filtered_search_manager_spec.js
index 5e68725c03e..26af7af701b 100644
--- a/spec/frontend/filtered_search/filtered_search_manager_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_manager_spec.js
@@ -8,7 +8,7 @@ import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered
import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { BACKSPACE_KEY_CODE, DELETE_KEY_CODE } from '~/lib/utils/keycodes';
import { visitUrl, getParameterByName } from '~/lib/utils/url_utility';
@@ -130,14 +130,14 @@ describe('Filtered Search Manager', () => {
manager = new FilteredSearchManager({ page });
});
- it('should not instantiate Flash if an RecentSearchesServiceError is caught', () => {
+ it('should not show an alert if an RecentSearchesServiceError is caught', () => {
jest
.spyOn(RecentSearchesService.prototype, 'fetch')
.mockImplementation(() => Promise.reject(new RecentSearchesServiceError()));
manager.setup();
- expect(createFlash).not.toHaveBeenCalled();
+ expect(createAlert).not.toHaveBeenCalled();
});
});
diff --git a/spec/frontend/filtered_search/visual_token_value_spec.js b/spec/frontend/filtered_search/visual_token_value_spec.js
index e52ffa7bd9f..43c10090739 100644
--- a/spec/frontend/filtered_search/visual_token_value_spec.js
+++ b/spec/frontend/filtered_search/visual_token_value_spec.js
@@ -5,7 +5,7 @@ import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper';
import { TEST_HOST } from 'helpers/test_constants';
import DropdownUtils from '~/filtered_search/dropdown_utils';
import VisualTokenValue from '~/filtered_search/visual_token_value';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import AjaxCache from '~/lib/utils/ajax_cache';
import UsersCache from '~/lib/utils/users_cache';
@@ -61,7 +61,7 @@ describe('Filtered Search Visual Tokens', () => {
};
await subject.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue);
- expect(createFlash.mock.calls.length).toBe(0);
+ expect(createAlert).toHaveBeenCalledTimes(0);
});
it('does nothing if user cannot be found', async () => {
diff --git a/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js b/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js
new file mode 100644
index 00000000000..57ae146a27a
--- /dev/null
+++ b/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js
@@ -0,0 +1,77 @@
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import axios from 'axios';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import SidebarReviewers from '~/sidebar/components/reviewers/sidebar_reviewers.vue';
+import SidebarService from '~/sidebar/services/sidebar_service';
+import SidebarMediator from '~/sidebar/sidebar_mediator';
+import SidebarStore from '~/sidebar/stores/sidebar_store';
+import Mock from '../../mock_data';
+
+Vue.use(VueApollo);
+
+describe('sidebar reviewers', () => {
+ const apolloMock = createMockApollo();
+ let wrapper;
+ let mediator;
+ let axiosMock;
+
+ const createComponent = (props) => {
+ wrapper = shallowMount(SidebarReviewers, {
+ apolloProvider: apolloMock,
+ propsData: {
+ issuableIid: '1',
+ issuableId: 1,
+ mediator,
+ field: '',
+ projectPath: 'projectPath',
+ changing: false,
+ ...props,
+ },
+ // Attaching to document is required because this component emits something from the parent element :/
+ attachTo: document.body,
+ });
+ };
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ mediator = new SidebarMediator(Mock.mediator);
+
+ jest.spyOn(mediator, 'saveReviewers');
+ jest.spyOn(mediator, 'addSelfReview');
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+
+ SidebarService.singleton = null;
+ SidebarStore.singleton = null;
+ SidebarMediator.singleton = null;
+ axiosMock.restore();
+ });
+
+ it('calls the mediator when it saves the reviewers', () => {
+ createComponent();
+
+ expect(mediator.saveReviewers).not.toHaveBeenCalled();
+
+ wrapper.vm.saveReviewers();
+
+ expect(mediator.saveReviewers).toHaveBeenCalled();
+ });
+
+ it('calls the mediator when "reviewBySelf" method is called', () => {
+ createComponent();
+
+ expect(mediator.addSelfReview).not.toHaveBeenCalled();
+ expect(mediator.store.reviewers.length).toBe(0);
+
+ wrapper.vm.reviewBySelf();
+
+ expect(mediator.addSelfReview).toHaveBeenCalled();
+ expect(mediator.store.reviewers.length).toBe(1);
+ });
+});
diff --git a/spec/frontend/sidebar/sidebar_mediator_spec.js b/spec/frontend/sidebar/sidebar_mediator_spec.js
index 254489e49a5..cdb9ced70b8 100644
--- a/spec/frontend/sidebar/sidebar_mediator_spec.js
+++ b/spec/frontend/sidebar/sidebar_mediator_spec.js
@@ -43,6 +43,13 @@ describe('Sidebar mediator', () => {
});
});
+ it('assigns yourself as a reviewer', () => {
+ mediator.addSelfReview();
+
+ expect(mediator.store.currentUser).toEqual(mediatorMockData.currentUser);
+ expect(mediator.store.reviewers[0]).toEqual(mediatorMockData.currentUser);
+ });
+
describe('saves reviewers', () => {
const mockUpdateResponseData = {
reviewers: [1, 2],
diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js
index a016960a85a..2a347892973 100644
--- a/spec/frontend/work_items/components/work_item_detail_spec.js
+++ b/spec/frontend/work_items/components/work_item_detail_spec.js
@@ -12,7 +12,6 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import setWindowLocation from 'helpers/set_window_location_helper';
-import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import WorkItemDetail from '~/work_items/components/work_item_detail.vue';
import WorkItemActions from '~/work_items/components/work_item_actions.vue';
import WorkItemDescription from '~/work_items/components/work_item_description.vue';
@@ -22,7 +21,6 @@ import WorkItemTitle from '~/work_items/components/work_item_title.vue';
import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
import WorkItemLabels from '~/work_items/components/work_item_labels.vue';
import WorkItemMilestone from '~/work_items/components/work_item_milestone.vue';
-import WorkItemInformation from '~/work_items/components/work_item_information.vue';
import { i18n } from '~/work_items/constants';
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
@@ -32,7 +30,6 @@ import workItemAssigneesSubscription from '~/work_items/graphql/work_item_assign
import workItemMilestoneSubscription from '~/work_items/graphql/work_item_milestone.subscription.graphql';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
import updateWorkItemTaskMutation from '~/work_items/graphql/update_work_item_task.mutation.graphql';
-import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import {
mockParent,
workItemDatesSubscriptionResponse,
@@ -45,7 +42,6 @@ import {
describe('WorkItemDetail component', () => {
let wrapper;
- useLocalStorageSpy();
Vue.use(VueApollo);
@@ -82,8 +78,6 @@ describe('WorkItemDetail component', () => {
const findParentButton = () => findParent().findComponent(GlButton);
const findCloseButton = () => wrapper.find('[data-testid="work-item-close"]');
const findWorkItemType = () => wrapper.find('[data-testid="work-item-type"]');
- const findWorkItemInformationAlert = () => wrapper.findComponent(WorkItemInformation);
- const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
const createComponent = ({
isModal = false,
@@ -93,6 +87,7 @@ describe('WorkItemDetail component', () => {
subscriptionHandler = titleSubscriptionHandler,
confidentialityMock = [updateWorkItemMutation, jest.fn()],
error = undefined,
+ workItemsMvcEnabled = false,
workItemsMvc2Enabled = false,
fetchByIid = false,
} = {}) => {
@@ -117,6 +112,7 @@ describe('WorkItemDetail component', () => {
},
provide: {
glFeatures: {
+ workItemsMvc: workItemsMvcEnabled,
workItemsMvc2: workItemsMvc2Enabled,
useIidInWorkItemsPath: fetchByIid,
},
@@ -579,7 +575,7 @@ describe('WorkItemDetail component', () => {
`('$description', async ({ milestoneWidgetPresent, exists }) => {
const response = workItemResponseFactory({ milestoneWidgetPresent });
const handler = jest.fn().mockResolvedValue(response);
- createComponent({ handler, workItemsMvc2Enabled: true });
+ createComponent({ handler, workItemsMvcEnabled: true });
await waitForPromises();
expect(findWorkItemMilestone().exists()).toBe(exists);
@@ -610,24 +606,6 @@ describe('WorkItemDetail component', () => {
});
});
- describe('work item information', () => {
- beforeEach(() => {
- createComponent();
- return waitForPromises();
- });
-
- it('is visible when viewed for the first time and sets localStorage value', async () => {
- localStorage.clear();
- expect(findWorkItemInformationAlert().exists()).toBe(true);
- expect(findLocalStorageSync().props('value')).toBe(true);
- });
-
- it('is not visible after reading local storage input', async () => {
- await findLocalStorageSync().vm.$emit('input', false);
- expect(findWorkItemInformationAlert().exists()).toBe(false);
- });
- });
-
it('calls the global ID work item query when `useIidInWorkItemsPath` feature flag is false', async () => {
createComponent();
await waitForPromises();
diff --git a/spec/frontend/work_items/components/work_item_information_spec.js b/spec/frontend/work_items/components/work_item_information_spec.js
deleted file mode 100644
index 887c5f615e9..00000000000
--- a/spec/frontend/work_items/components/work_item_information_spec.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import { mount } from '@vue/test-utils';
-import { GlAlert, GlLink } from '@gitlab/ui';
-import WorkItemInformation from '~/work_items/components/work_item_information.vue';
-import { helpPagePath } from '~/helpers/help_page_helper';
-
-const createComponent = () => mount(WorkItemInformation);
-
-describe('Work item information alert', () => {
- let wrapper;
- const tasksHelpPath = helpPagePath('user/tasks');
-
- const findAlert = () => wrapper.findComponent(GlAlert);
- const findHelpLink = () => wrapper.findComponent(GlLink);
- beforeEach(() => {
- wrapper = createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('should be visible', () => {
- expect(findAlert().exists()).toBe(true);
- });
-
- it('should emit `work-item-banner-dismissed` event when cross icon is clicked', () => {
- findAlert().vm.$emit('dismiss');
- expect(wrapper.emitted('work-item-banner-dismissed').length).toBe(1);
- });
-
- it('the alert variant should be tip', () => {
- expect(findAlert().props('variant')).toBe('tip');
- });
-
- it('should have the correct text for title', () => {
- expect(findAlert().props('title')).toBe(WorkItemInformation.i18n.tasksInformationTitle);
- });
-
- it('should have the correct link to work item link', () => {
- expect(findHelpLink().exists()).toBe(true);
- expect(findHelpLink().attributes('href')).toBe(tasksHelpPath);
- });
-});
diff --git a/spec/graphql/types/projects/service_type_enum_spec.rb b/spec/graphql/types/projects/service_type_enum_spec.rb
index f7256910bb0..8b444a08c3b 100644
--- a/spec/graphql/types/projects/service_type_enum_spec.rb
+++ b/spec/graphql/types/projects/service_type_enum_spec.rb
@@ -23,7 +23,6 @@ RSpec.describe GitlabSchema.types['ServiceType'] do
EMAILS_ON_PUSH_SERVICE
EWM_SERVICE
EXTERNAL_WIKI_SERVICE
- FLOWDOCK_SERVICE
HANGOUTS_CHAT_SERVICE
IRKER_SERVICE
JENKINS_SERVICE
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 74a59aa37ce..0450ecc0f21 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -886,17 +886,18 @@ RSpec.describe SearchHelper do
end
context 'code' do
- where(:feature_flag_tab_enabled, :show_elasticsearch_tabs, :project_search_tabs, :condition) do
- false | false | false | false
- true | true | true | true
- true | false | false | false
- false | true | false | false
- false | false | true | true
- true | false | true | true
+ where(:feature_flag_tab_enabled, :show_elasticsearch_tabs, :global_project, :project_search_tabs, :condition) do
+ false | false | nil | false | false
+ true | true | nil | true | true
+ true | false | nil | false | false
+ false | true | nil | false | false
+ false | false | ref(:project) | true | true
+ true | false | ref(:project) | false | false
end
with_them do
it 'data item condition is set correctly' do
+ @project = global_project
allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs)
allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_code_tab).and_return(feature_flag_tab_enabled)
allow(self).to receive(:project_search_tabs?).with(:blobs).and_return(project_search_tabs)
@@ -907,16 +908,16 @@ RSpec.describe SearchHelper do
end
context 'issues' do
- where(:feature_flag_tab_enabled, :project_search_tabs, :condition) do
- false | false | false
- true | true | true
- true | false | true
- false | true | true
+ where(:project_search_tabs, :global_search_issues_tab, :condition) do
+ false | false | false
+ false | true | true
+ true | false | true
+ true | true | true
end
with_them do
it 'data item condition is set correctly' do
- allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_issues_tab).and_return(feature_flag_tab_enabled)
+ allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_issues_tab).and_return(global_search_issues_tab)
allow(self).to receive(:project_search_tabs?).with(:issues).and_return(project_search_tabs)
expect(search_navigation[:issues][:condition]).to eq(condition)
@@ -925,11 +926,11 @@ RSpec.describe SearchHelper do
end
context 'merge requests' do
- where(:feature_flag_tab_enabled, :project_search_tabs, :condition) do
- false | false | false
- true | true | true
- true | false | true
- false | true | true
+ where(:project_search_tabs, :feature_flag_tab_enabled, :condition) do
+ false | false | false
+ true | false | true
+ false | true | true
+ true | true | true
end
with_them do
@@ -943,16 +944,19 @@ RSpec.describe SearchHelper do
end
context 'wiki' do
- where(:project_search_tabs, :show_elasticsearch_tabs, :condition) do
- false | false | false
- true | true | true
- true | false | true
- false | true | true
+ where(:global_search_wiki_tab, :show_elasticsearch_tabs, :global_project, :project_search_tabs, :condition) do
+ false | false | nil | true | true
+ false | false | nil | false | false
+ false | true | nil | false | false
+ true | false | nil | false | false
+ true | true | ref(:project) | false | false
end
with_them do
it 'data item condition is set correctly' do
+ @project = global_project
allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs)
+ allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_wiki_tab).and_return(global_search_wiki_tab)
allow(self).to receive(:project_search_tabs?).with(:wiki).and_return(project_search_tabs)
expect(search_navigation[:wiki_blobs][:condition]).to eq(condition)
@@ -961,17 +965,20 @@ RSpec.describe SearchHelper do
end
context 'commits' do
- where(:feature_flag_tab_enabled, :show_elasticsearch_tabs, :project_search_tabs, :condition) do
- false | false | false | false
- true | true | true | true
- true | false | false | false
- false | true | true | true
+ where(:global_search_commits_tab, :show_elasticsearch_tabs, :global_project, :project_search_tabs, :condition) do
+ false | false | nil | true | true
+ false | false | nil | false | false
+ false | true | nil | false | false
+ true | false | nil | false | false
+ true | true | ref(:project) | false | false
+ true | true | nil | false | true
end
with_them do
it 'data item condition is set correctly' do
+ @project = global_project
allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(show_elasticsearch_tabs)
- allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_commits_tab).and_return(feature_flag_tab_enabled)
+ allow(self).to receive(:feature_flag_tab_enabled?).with(:global_search_commits_tab).and_return(global_search_commits_tab)
allow(self).to receive(:project_search_tabs?).with(:commits).and_return(project_search_tabs)
expect(search_navigation[:commits][:condition]).to eq(condition)
@@ -980,11 +987,11 @@ RSpec.describe SearchHelper do
end
context 'comments' do
- where(:show_elasticsearch_tabs, :project_search_tabs, :condition) do
- true | true | true
- false | false | false
- true | false | true
- false | true | true
+ where(:project_search_tabs, :show_elasticsearch_tabs, :condition) do
+ true | true | true
+ false | false | false
+ false | true | true
+ true | false | true
end
with_them do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index a9ec285f13c..b9867f794dc 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -424,7 +424,6 @@ project:
- packagist_integration
- pivotaltracker_integration
- prometheus_integration
-- flowdock_integration
- assembla_integration
- asana_integration
- slack_integration
diff --git a/spec/lib/gitlab/process_management_spec.rb b/spec/lib/gitlab/process_management_spec.rb
index a71a476b540..fbd39702efb 100644
--- a/spec/lib/gitlab/process_management_spec.rb
+++ b/spec/lib/gitlab/process_management_spec.rb
@@ -41,15 +41,6 @@ RSpec.describe Gitlab::ProcessManagement do
end
end
- describe '.wait_async' do
- it 'waits for a process in a separate thread' do
- thread = described_class.wait_async(Process.spawn('true'))
-
- # Upon success Process.wait just returns the PID.
- expect(thread.value).to be_a_kind_of(Numeric)
- end
- end
-
# In the X_alive? checks, we check negative PIDs sometimes as a simple way
# to be sure the pids are definitely for non-existent processes.
# Note that -1 is special, and sends the signal to every process we have permission
diff --git a/spec/lib/gitlab/ssh/signature_spec.rb b/spec/lib/gitlab/ssh/signature_spec.rb
index f3f1ba84f9e..4868ed68db6 100644
--- a/spec/lib/gitlab/ssh/signature_spec.rb
+++ b/spec/lib/gitlab/ssh/signature_spec.rb
@@ -151,16 +151,32 @@ RSpec.describe Gitlab::Ssh::Signature do
context 'when user email is not verified' do
before do
+ email = user.emails.find_by(email: committer_email)
+ email.update!(confirmed_at: nil)
user.update!(confirmed_at: nil)
end
- it_behaves_like 'unverified signature'
+ it 'reports unverified status' do
+ expect(signature.verification_status).to eq(:unverified)
+ end
+ end
+
+ context 'when no user exist with the committer email' do
+ before do
+ user.delete
+ end
+
+ it 'reports other_user status' do
+ expect(signature.verification_status).to eq(:other_user)
+ end
end
context 'when no user exists with the committer email' do
let(:committer_email) { 'different-email+ssh-commit-test@example.com' }
- it_behaves_like 'unverified signature'
+ it 'reports other_user status' do
+ expect(signature.verification_status).to eq(:other_user)
+ end
end
context 'when signature is invalid' do
diff --git a/spec/models/integrations/flowdock_spec.rb b/spec/models/integrations/flowdock_spec.rb
deleted file mode 100644
index daafb1b3958..00000000000
--- a/spec/models/integrations/flowdock_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Integrations::Flowdock do
- describe 'Validations' do
- context 'when integration is active' do
- before do
- subject.active = true
- end
-
- it { is_expected.to validate_presence_of(:token) }
- end
-
- context 'when integration is inactive' do
- before do
- subject.active = false
- end
-
- it { is_expected.not_to validate_presence_of(:token) }
- end
- end
-
- describe "Execute" do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
- let(:api_url) { 'https://api.flowdock.com/v1/messages' }
-
- subject(:flowdock_integration) { described_class.new }
-
- before do
- allow(flowdock_integration).to receive_messages(
- project_id: project.id,
- project: project,
- token: 'verySecret'
- )
- WebMock.stub_request(:post, api_url)
- end
-
- it "calls FlowDock API" do
- flowdock_integration.execute(sample_data)
-
- sample_data[:commits].each do |commit|
- # One request to Flowdock per new commit
- next if commit[:id] == sample_data[:before]
-
- expect(WebMock).to have_requested(:post, api_url).with(
- body: /#{commit[:id]}.*#{project.path}/
- ).once
- end
- end
- end
-end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index a6dbaef3a66..e800a468f8c 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -58,7 +58,6 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to have_one(:pipelines_email_integration) }
it { is_expected.to have_one(:irker_integration) }
it { is_expected.to have_one(:pivotaltracker_integration) }
- it { is_expected.to have_one(:flowdock_integration) }
it { is_expected.to have_one(:assembla_integration) }
it { is_expected.to have_one(:slack_slash_commands_integration) }
it { is_expected.to have_one(:mattermost_slash_commands_integration) }
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 18d3fdb2d50..0affc522d4e 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -3627,12 +3627,6 @@ RSpec.describe API::MergeRequests do
expect(merge_request.approvals).to be_empty
end
- it 'for users with bot role' do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", bot)
-
- expect(response).to have_gitlab_http_status(:accepted)
- end
-
context 'for users with non-bot roles' do
let(:human_user) { create(:user) }
@@ -3642,7 +3636,9 @@ RSpec.describe API::MergeRequests do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", human_user)
+ merge_request.reload
expect(response).to have_gitlab_http_status(:unauthorized)
+ expect(merge_request.approvals.pluck(:user_id)).to eql([user2.id])
end
end
end
@@ -3658,7 +3654,9 @@ RSpec.describe API::MergeRequests do
it 'returns 401' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", external_bot)
+ merge_request.reload
expect(response).to have_gitlab_http_status(:unauthorized)
+ expect(merge_request.approvals.pluck(:user_id)).to eql([user2.id])
end
end
@@ -3670,10 +3668,26 @@ RSpec.describe API::MergeRequests do
it 'returns 401' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", external_bot)
+ merge_request.reload
expect(response).to have_gitlab_http_status(:unauthorized)
+ expect(merge_request.approvals.pluck(:user_id)).to eql([user2.id])
end
end
end
+
+ context 'for a bot user who approved the merge request' do
+ before do
+ merge_request.approvals.create!(user: bot)
+ end
+
+ it "returns 200" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", bot)
+
+ merge_request.reload
+ expect(response).to have_gitlab_http_status(:accepted)
+ expect(merge_request.approvals).to be_empty
+ end
+ end
end
end
diff --git a/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb
index 4a532fa9ecb..b06a5709bd5 100644
--- a/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb
+++ b/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb
@@ -49,6 +49,9 @@ RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService do
it_behaves_like 'when regex matching everything is specified',
delete_expectations: [%w[A], %w[Ba Bb], %w[C D], %w[E]]
+ it_behaves_like 'when regex matching everything is specified and latest is not kept',
+ delete_expectations: [%w[latest A], %w[Ba Bb], %w[C D], %w[E]]
+
it_behaves_like 'when delete regex matching specific tags is used'
it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex'
diff --git a/spec/services/projects/container_repository/third_party/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/third_party/cleanup_tags_service_spec.rb
index 2d034d577ac..7227834b131 100644
--- a/spec/services/projects/container_repository/third_party/cleanup_tags_service_spec.rb
+++ b/spec/services/projects/container_repository/third_party/cleanup_tags_service_spec.rb
@@ -51,6 +51,16 @@ RSpec.describe Projects::ContainerRepository::ThirdParty::CleanupTagsService, :c
},
supports_caching: true
+ it_behaves_like 'when regex matching everything is specified and latest is not kept',
+ delete_expectations: [%w[A Ba Bb C D E latest]],
+ service_response_extra: {
+ before_truncate_size: 7,
+ after_truncate_size: 7,
+ before_delete_size: 7,
+ cached_tags_count: 0
+ },
+ supports_caching: true
+
it_behaves_like 'when delete regex matching specific tags is used',
service_response_extra: {
before_truncate_size: 2,
diff --git a/spec/sidekiq_cluster/sidekiq_cluster_spec.rb b/spec/sidekiq_cluster/sidekiq_cluster_spec.rb
index 822acc3fe0f..25a600405fe 100644
--- a/spec/sidekiq_cluster/sidekiq_cluster_spec.rb
+++ b/spec/sidekiq_cluster/sidekiq_cluster_spec.rb
@@ -20,13 +20,16 @@ RSpec.describe Gitlab::SidekiqCluster do # rubocop:disable RSpec/FilePath
"SIDEKIQ_WORKER_ID" => "0"
},
"bundle", "exec", "sidekiq", "-c10", "-eproduction", "-t25", "-gqueues:foo", "-rfoo/bar", "-qfoo,1", process_options
- )
+ ).and_return(1)
+ expect(Process).to receive(:detach).ordered.with(1)
+
expect(Process).to receive(:spawn).ordered.with({
"ENABLE_SIDEKIQ_CLUSTER" => "1",
"SIDEKIQ_WORKER_ID" => "1"
},
"bundle", "exec", "sidekiq", "-c10", "-eproduction", "-t25", "-gqueues:bar,baz", "-rfoo/bar", "-qbar,1", "-qbaz,1", process_options
- )
+ ).and_return(2)
+ expect(Process).to receive(:detach).ordered.with(2)
described_class.start([%w(foo), %w(bar baz)], env: :production, directory: 'foo/bar', max_concurrency: 20, min_concurrency: 10)
end
@@ -58,11 +61,13 @@ RSpec.describe Gitlab::SidekiqCluster do # rubocop:disable RSpec/FilePath
let(:env) { { "ENABLE_SIDEKIQ_CLUSTER" => "1", "SIDEKIQ_WORKER_ID" => first_worker_id.to_s } }
let(:args) { ['bundle', 'exec', 'sidekiq', anything, '-eproduction', '-t10', *([anything] * 5)] }
+ let(:waiter_thread) { instance_double('Process::Waiter') }
+
it 'starts a Sidekiq process' do
allow(Process).to receive(:spawn).and_return(1)
+ allow(Process).to receive(:detach).with(1).and_return(waiter_thread)
- expect(Gitlab::ProcessManagement).to receive(:wait_async).with(1)
- expect(described_class.start_sidekiq(%w(foo), **options)).to eq(1)
+ expect(described_class.start_sidekiq(%w(foo), **options)).to eq(waiter_thread)
end
it 'handles duplicate queue names' do
@@ -70,9 +75,9 @@ RSpec.describe Gitlab::SidekiqCluster do # rubocop:disable RSpec/FilePath
.to receive(:spawn)
.with(env, *args, anything)
.and_return(1)
+ allow(Process).to receive(:detach).with(1).and_return(waiter_thread)
- expect(Gitlab::ProcessManagement).to receive(:wait_async).with(1)
- expect(described_class.start_sidekiq(%w(foo foo bar baz), **options)).to eq(1)
+ expect(described_class.start_sidekiq(%w(foo foo bar baz), **options)).to eq(waiter_thread)
end
it 'runs the sidekiq process in a new process group' do
@@ -80,9 +85,9 @@ RSpec.describe Gitlab::SidekiqCluster do # rubocop:disable RSpec/FilePath
.to receive(:spawn)
.with(anything, *args, a_hash_including(pgroup: true))
.and_return(1)
+ allow(Process).to receive(:detach).with(1).and_return(waiter_thread)
- allow(Gitlab::ProcessManagement).to receive(:wait_async)
- expect(described_class.start_sidekiq(%w(foo bar baz), **options)).to eq(1)
+ expect(described_class.start_sidekiq(%w(foo bar baz), **options)).to eq(waiter_thread)
end
end
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 8080a7a3edb..26e9cd6cbd9 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -4261,7 +4261,6 @@
- './spec/features/projects/integrations/user_activates_assembla_spec.rb'
- './spec/features/projects/integrations/user_activates_atlassian_bamboo_ci_spec.rb'
- './spec/features/projects/integrations/user_activates_emails_on_push_spec.rb'
-- './spec/features/projects/integrations/user_activates_flowdock_spec.rb'
- './spec/features/projects/integrations/user_activates_irker_spec.rb'
- './spec/features/projects/integrations/user_activates_issue_tracker_spec.rb'
- './spec/features/projects/integrations/user_activates_jetbrains_teamcity_ci_spec.rb'
@@ -8291,7 +8290,6 @@
- './spec/models/integrations/ewm_spec.rb'
- './spec/models/integrations/external_wiki_spec.rb'
- './spec/models/integrations/field_spec.rb'
-- './spec/models/integrations/flowdock_spec.rb'
- './spec/models/integrations/hangouts_chat_spec.rb'
- './spec/models/integrations/harbor_spec.rb'
- './spec/models/integrations/irker_spec.rb'
diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
index 68c0d06e7d0..91e465871a4 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -301,7 +301,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
if resource_name == 'merge request'
let(:note_id) { find("#{comments_selector} .note:first-child", match: :first)['data-note-id'] }
- let(:reply_id) { find("#{comments_selector} .note:last-of-type", match: :first)['data-note-id'] }
+ let(:reply_id) { all("#{comments_selector} [data-note-id]")[1]['data-note-id'] }
it 'can be replied to after resolving' do
find('button[data-testid="resolve-discussion-button"]').click
diff --git a/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb b/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb
index 7acef9efd3a..f70621673d5 100644
--- a/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb
+++ b/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb
@@ -23,6 +23,19 @@ RSpec.shared_examples 'when regex matching everything is specified' do
end
end
+RSpec.shared_examples 'when regex matching everything is specified and latest is not kept' do
+ |delete_expectations:, service_response_extra: {}, supports_caching: false|
+
+ let(:params) do
+ { 'name_regex_delete' => '.*', 'keep_latest' => false }
+ end
+
+ it_behaves_like 'removing the expected tags',
+ service_response_extra: service_response_extra,
+ supports_caching: supports_caching,
+ delete_expectations: delete_expectations
+end
+
RSpec.shared_examples 'when delete regex matching specific tags is used' do
|service_response_extra: {}, supports_caching: false|
let(:params) do
diff --git a/spec/workers/container_registry/delete_container_repository_worker_spec.rb b/spec/workers/container_registry/delete_container_repository_worker_spec.rb
index 381e0cc164c..c98bf867c99 100644
--- a/spec/workers/container_registry/delete_container_repository_worker_spec.rb
+++ b/spec/workers/container_registry/delete_container_repository_worker_spec.rb
@@ -103,6 +103,18 @@ RSpec.describe ContainerRegistry::DeleteContainerRepositoryWorker, :aggregate_fa
end
end
+ context 'with container_registry_delete_repository_with_cron_worker disabled' do
+ before do
+ stub_feature_flags(container_registry_delete_repository_with_cron_worker: false)
+ end
+
+ it 'will not delete any container repository' do
+ expect(::Projects::ContainerRepository::CleanupTagsService).not_to receive(:new)
+
+ expect { perform_work }.to not_change { ContainerRepository.count }
+ end
+ end
+
def expect_next_pending_destruction_container_repository
original_method = ContainerRepository.method(:next_pending_destruction)
expect(ContainerRepository).to receive(:next_pending_destruction).with(order_by: nil) do
@@ -142,5 +154,13 @@ RSpec.describe ContainerRegistry::DeleteContainerRepositoryWorker, :aggregate_fa
subject { worker.remaining_work_count }
it { is_expected.to eq(described_class::MAX_CAPACITY + 1) }
+
+ context 'with container_registry_delete_repository_with_cron_worker disabled' do
+ before do
+ stub_feature_flags(container_registry_delete_repository_with_cron_worker: false)
+ end
+
+ it { is_expected.to eq(0) }
+ end
end
end