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--.haml-lint.yml1
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/actions.vue3
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue1
-rw-r--r--app/models/user.rb4
-rw-r--r--app/views/admin/runners/index.html.haml2
-rw-r--r--app/views/projects/_merge_request_merge_method_settings.html.haml2
-rw-r--r--app/views/shared/_group_form.html.haml4
-rw-r--r--app/views/shared/tokens/_scopes_form.html.haml2
-rw-r--r--app/workers/all_queues.yml8
-rw-r--r--app/workers/database/batched_background_migration_worker.rb6
-rw-r--r--app/workers/users/deactivate_dormant_users_worker.rb49
-rw-r--r--changelogs/unreleased/211754-automate-dormant-users-deactivation.yml5
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--db/migrate/20210421021510_add_deactivate_dormant_users_to_application_settings.rb7
-rw-r--r--db/migrate/20210421022010_add_index_for_dormant_users.rb19
-rw-r--r--db/schema_migrations/202104210215101
-rw-r--r--db/schema_migrations/202104210220101
-rw-r--r--db/structure.sql3
-rw-r--r--doc/api/graphql/reference/index.md56
-rw-r--r--doc/development/documentation/styleguide/index.md21
-rw-r--r--doc/user/packages/npm_registry/index.md42
-rw-r--r--lib/gitlab/database/migration_helpers.rb41
-rw-r--r--locale/gitlab.pot18
-rw-r--r--qa/qa.rb1
-rw-r--r--qa/qa/page/base.rb29
-rw-r--r--qa/qa/page/component/access_tokens.rb4
-rw-r--r--qa/qa/page/component/visibility_setting.rb23
-rw-r--r--qa/qa/page/group/new.rb15
-rw-r--r--qa/qa/page/group/settings/general.rb9
-rw-r--r--qa/qa/page/project/new.rb7
-rw-r--r--qa/qa/page/project/settings/merge_request.rb4
-rw-r--r--qa/qa/page/project/web_ide/edit.rb10
-rw-r--r--spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb32
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb114
-rw-r--r--spec/models/user_spec.rb43
-rw-r--r--spec/workers/users/deactivate_dormant_users_worker_spec.rb61
36 files changed, 564 insertions, 87 deletions
diff --git a/.haml-lint.yml b/.haml-lint.yml
index 622a70d047c..1d1c0fa1de2 100644
--- a/.haml-lint.yml
+++ b/.haml-lint.yml
@@ -110,7 +110,6 @@ linters:
- Layout/EmptyLineAfterGuardClause
- Layout/LeadingCommentSpace
- Layout/SpaceAroundOperators
- - Layout/SpaceBeforeBlockBraces
- Layout/SpaceBeforeComma
- Layout/SpaceBeforeFirstArg
- Layout/SpaceInsideHashLiteralBraces
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
index 273d8d972f7..fcc900bbc96 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
@@ -76,8 +76,9 @@ export default {
:value="$options.commitToCurrentBranch"
:disabled="!canPushToBranch"
:title="$options.currentBranchPermissionsTooltip"
+ data-qa-selector="commit_to_current_branch_radio_container"
>
- <span class="ide-option-label" data-qa-selector="commit_to_current_branch_radio">
+ <span class="ide-option-label">
<gl-sprintf :message="s__('IDE|Commit to %{branchName} branch')">
<template #branchName>
<strong class="monospace">{{ currentBranchText }}</strong>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
index 039b4a54b26..870355e884e 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
@@ -64,6 +64,7 @@ export default {
:disabled="disabled"
type="radio"
name="commit-action"
+ data-qa-selector="commit_type_radio"
@change="updateCommitAction($event.target.value)"
/>
<span class="gl-ml-3">
diff --git a/app/models/user.rb b/app/models/user.rb
index 5fc7cc5574e..482f028a06b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -337,6 +337,8 @@ class User < ApplicationRecord
end
event :deactivate do
+ # Any additional changes to this event should be also
+ # reflected in app/workers/users/deactivate_dormant_users_worker.rb
transition active: :deactivated
end
@@ -418,6 +420,8 @@ class User < ApplicationRecord
scope :order_recent_last_activity, -> { reorder(Gitlab::Database.nulls_last_order('last_activity_on', 'DESC')) }
scope :order_oldest_last_activity, -> { reorder(Gitlab::Database.nulls_first_order('last_activity_on', 'ASC')) }
scope :by_id_and_login, ->(id, login) { where(id: id).where('username = LOWER(:login) OR email = LOWER(:login)', login: login) }
+ scope :dormant, -> { active.where('last_activity_on <= ?', MINIMUM_INACTIVE_DAYS.day.ago.to_date) }
+ scope :with_no_activity, -> { active.where(last_activity_on: nil) }
def preferred_language
read_attribute('preferred_language') ||
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index a38615d9b1b..359e5b411b1 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -5,7 +5,7 @@
.col-sm-6
.bs-callout
%p
- = (_"Runners are processes that pick up and execute CI/CD jobs for GitLab.")
+ = _("Runners are processes that pick up and execute CI/CD jobs for GitLab.")
%br
= _('You can register runners as separate users, on separate servers, and on your local machine. Register as many runners as you want.')
%br
diff --git a/app/views/projects/_merge_request_merge_method_settings.html.haml b/app/views/projects/_merge_request_merge_method_settings.html.haml
index f55d840e14b..2d18285ba80 100644
--- a/app/views/projects/_merge_request_merge_method_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_method_settings.html.haml
@@ -22,7 +22,7 @@
= s_('ProjectSettings|When there is a merge conflict, the user is given the option to rebase.')
.form-check.mb-2
- = form.radio_button :merge_method, :ff, class: "js-merge-method-radio form-check-input", data: { qa_selector: 'merge_ff_radio_button' }
+ = form.radio_button :merge_method, :ff, class: "js-merge-method-radio form-check-input", data: { qa_selector: 'merge_ff_radio' }
= label_tag :project_merge_method_ff, class: 'form-check-label' do
= s_('ProjectSettings|Fast-forward merge')
.text-secondary
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index eea0c5f37de..7055dc8142a 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -6,7 +6,7 @@
.form-group.group-name-holder.col-sm-12
= f.label :name, class: 'label-bold' do
= _("Group name")
- = f.text_field :name, placeholder: _('My Awesome Group'), class: 'js-autofill-group-name form-control input-lg',
+ = f.text_field :name, placeholder: _('My Awesome Group'), class: 'js-autofill-group-name form-control input-lg', data: { qa_selector: 'group_name_field' },
required: true,
title: _('Please fill in a descriptive name for your group.'),
autofocus: true
@@ -22,7 +22,7 @@
- if parent
%strong= parent.full_path + '/'
= f.hidden_field :parent_id
- = f.text_field :path, placeholder: _('my-awesome-group'), class: 'form-control js-validate-group-path js-autofill-group-path',
+ = f.text_field :path, placeholder: _('my-awesome-group'), class: 'form-control js-validate-group-path js-autofill-group-path', data: { qa_selector: 'group_path_field' },
autofocus: local_assigns[:autofocus] || false, required: true,
pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS,
title: _('Please choose a group URL with no special characters.'),
diff --git a/app/views/shared/tokens/_scopes_form.html.haml b/app/views/shared/tokens/_scopes_form.html.haml
index 1c8e300fa8a..33e95446bd7 100644
--- a/app/views/shared/tokens/_scopes_form.html.haml
+++ b/app/views/shared/tokens/_scopes_form.html.haml
@@ -4,6 +4,6 @@
- scopes.each do |scope|
%fieldset.form-group.form-check
- = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}", class: "form-check-input qa-#{scope}-radio"
+ = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}", class: "form-check-input", data: { qa_selector: "#{scope}_checkbox" }
= label_tag "#{prefix}_scopes_#{scope}", scope, class: 'label-bold form-check-label'
.text-secondary= t scope, scope: scope_description(prefix)
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index fa6ea54e342..0417d214231 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -515,6 +515,14 @@
:weight: 1
:idempotent:
:tags: []
+- :name: cronjob:users_deactivate_dormant_users
+ :feature_category: :utilization
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent:
+ :tags: []
- :name: cronjob:x509_issuer_crl_check
:feature_category: :source_code_management
:has_external_dependencies: true
diff --git a/app/workers/database/batched_background_migration_worker.rb b/app/workers/database/batched_background_migration_worker.rb
index de274d58ad7..039b45a497f 100644
--- a/app/workers/database/batched_background_migration_worker.rb
+++ b/app/workers/database/batched_background_migration_worker.rb
@@ -38,7 +38,7 @@ module Database
end
def with_exclusive_lease(interval)
- timeout = max(interval * LEASE_TIMEOUT_MULTIPLIER, MINIMUM_LEASE_TIMEOUT)
+ timeout = [interval * LEASE_TIMEOUT_MULTIPLIER, MINIMUM_LEASE_TIMEOUT].max
lease = Gitlab::ExclusiveLease.new(lease_key, timeout: timeout)
yield if lease.try_obtain
@@ -46,10 +46,6 @@ module Database
lease&.cancel
end
- def max(left, right)
- left >= right ? left : right
- end
-
def lease_key
self.class.name.demodulize.underscore
end
diff --git a/app/workers/users/deactivate_dormant_users_worker.rb b/app/workers/users/deactivate_dormant_users_worker.rb
new file mode 100644
index 00000000000..f92a12310b6
--- /dev/null
+++ b/app/workers/users/deactivate_dormant_users_worker.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Users
+ class DeactivateDormantUsersWorker # rubocop:disable Scalability/IdempotentWorker
+ include ApplicationWorker
+ include CronjobQueue
+
+ feature_category :utilization
+
+ NUMBER_OF_BATCHES = 50
+ BATCH_SIZE = 200
+ PAUSE_SECONDS = 0.25
+
+ def perform
+ return if Gitlab.com?
+
+ return unless ::Gitlab::CurrentSettings.current_application_settings.deactivate_dormant_users
+
+ with_context(caller_id: self.class.name.to_s) do
+ NUMBER_OF_BATCHES.times do
+ result = User.connection.execute(update_query)
+
+ break if result.cmd_tuples == 0
+
+ sleep(PAUSE_SECONDS)
+ end
+ end
+ end
+
+ private
+
+ def update_query
+ <<~SQL
+ UPDATE "users"
+ SET "state" = 'deactivated'
+ WHERE "users"."id" IN (
+ (#{users.dormant.to_sql})
+ UNION
+ (#{users.with_no_activity.to_sql})
+ LIMIT #{BATCH_SIZE}
+ )
+ SQL
+ end
+
+ def users
+ User.select(:id).limit(BATCH_SIZE)
+ end
+ end
+end
diff --git a/changelogs/unreleased/211754-automate-dormant-users-deactivation.yml b/changelogs/unreleased/211754-automate-dormant-users-deactivation.yml
new file mode 100644
index 00000000000..f8af79e9a09
--- /dev/null
+++ b/changelogs/unreleased/211754-automate-dormant-users-deactivation.yml
@@ -0,0 +1,5 @@
+---
+title: Automate deactivation of dormant users for self-managed instances
+merge_request: 57778
+author:
+type: added
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 99335321f28..4beaa2da810 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -569,6 +569,9 @@ Settings.cron_jobs['namespaces_in_product_marketing_emails_worker']['job_class']
Settings.cron_jobs['ssh_keys_expiring_soon_notification_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['ssh_keys_expiring_soon_notification_worker']['cron'] ||= '0 1 * * *'
Settings.cron_jobs['ssh_keys_expiring_soon_notification_worker']['job_class'] = 'SshKeys::ExpiringSoonNotificationWorker'
+Settings.cron_jobs['users_deactivate_dormant_users_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['users_deactivate_dormant_users_worker']['cron'] ||= '21,42 0-4 * * *'
+Settings.cron_jobs['users_deactivate_dormant_users_worker']['job_class'] = 'Users::DeactivateDormantUsersWorker'
Gitlab.com do
Settings.cron_jobs['batched_background_migrations_worker'] ||= Settingslogic.new({})
diff --git a/db/migrate/20210421021510_add_deactivate_dormant_users_to_application_settings.rb b/db/migrate/20210421021510_add_deactivate_dormant_users_to_application_settings.rb
new file mode 100644
index 00000000000..74d197cd3b8
--- /dev/null
+++ b/db/migrate/20210421021510_add_deactivate_dormant_users_to_application_settings.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddDeactivateDormantUsersToApplicationSettings < ActiveRecord::Migration[6.0]
+ def change
+ add_column :application_settings, :deactivate_dormant_users, :boolean, default: false, null: false
+ end
+end
diff --git a/db/migrate/20210421022010_add_index_for_dormant_users.rb b/db/migrate/20210421022010_add_index_for_dormant_users.rb
new file mode 100644
index 00000000000..48eff184ca0
--- /dev/null
+++ b/db/migrate/20210421022010_add_index_for_dormant_users.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddIndexForDormantUsers < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ INDEX_NAME = 'index_users_on_id_and_last_activity_on_for_non_internal_active'
+
+ disable_ddl_transaction!
+
+ def up
+ index_condition = "state = 'active' AND (users.user_type IS NULL OR users.user_type IN (NULL, 6, 4))"
+
+ add_concurrent_index :users, [:id, :last_activity_on], where: index_condition, name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :users, INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20210421021510 b/db/schema_migrations/20210421021510
new file mode 100644
index 00000000000..775f083ac63
--- /dev/null
+++ b/db/schema_migrations/20210421021510
@@ -0,0 +1 @@
+6a278c90b8c97fc2255528605ee6bf4547e37ac8c4c17979483ed9db562fa021 \ No newline at end of file
diff --git a/db/schema_migrations/20210421022010 b/db/schema_migrations/20210421022010
new file mode 100644
index 00000000000..75abced628d
--- /dev/null
+++ b/db/schema_migrations/20210421022010
@@ -0,0 +1 @@
+454992d01fa140896ff2a9cea66fb855c9e659a5a7969ac9a3cb5a608de36161 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 3aaeb9fab16..e98c724e277 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -9479,6 +9479,7 @@ CREATE TABLE application_settings (
throttle_authenticated_packages_api_period_in_seconds integer DEFAULT 15 NOT NULL,
throttle_unauthenticated_packages_api_enabled boolean DEFAULT false NOT NULL,
throttle_authenticated_packages_api_enabled boolean DEFAULT false NOT NULL,
+ deactivate_dormant_users boolean DEFAULT false NOT NULL,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)),
@@ -24225,6 +24226,8 @@ CREATE INDEX index_users_on_feed_token ON users USING btree (feed_token);
CREATE INDEX index_users_on_group_view ON users USING btree (group_view);
+CREATE INDEX index_users_on_id_and_last_activity_on_for_non_internal_active ON users USING btree (id, last_activity_on) WHERE (((state)::text = 'active'::text) AND ((user_type IS NULL) OR (user_type = ANY (ARRAY[NULL::integer, 6, 4]))));
+
CREATE INDEX index_users_on_incoming_email_token ON users USING btree (incoming_email_token);
CREATE INDEX index_users_on_managing_group_id ON users USING btree (managing_group_id);
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 765478c73cd..fcf9e4b91cf 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -5239,6 +5239,29 @@ The edge type for [`Label`](#label).
| <a id="labeledgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="labeledgenode"></a>`node` | [`Label`](#label) | The item at the end of the edge. |
+#### `LfsObjectRegistryConnection`
+
+The connection type for [`LfsObjectRegistry`](#lfsobjectregistry).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="lfsobjectregistryconnectionedges"></a>`edges` | [`[LfsObjectRegistryEdge]`](#lfsobjectregistryedge) | A list of edges. |
+| <a id="lfsobjectregistryconnectionnodes"></a>`nodes` | [`[LfsObjectRegistry]`](#lfsobjectregistry) | A list of nodes. |
+| <a id="lfsobjectregistryconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `LfsObjectRegistryEdge`
+
+The edge type for [`LfsObjectRegistry`](#lfsobjectregistry).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="lfsobjectregistryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| <a id="lfsobjectregistryedgenode"></a>`node` | [`LfsObjectRegistry`](#lfsobjectregistry) | The item at the end of the edge. |
+
#### `LicenseHistoryEntryConnection`
The connection type for [`LicenseHistoryEntry`](#licensehistoryentry).
@@ -8317,6 +8340,22 @@ four standard [pagination arguments](#connection-pagination-arguments):
| ---- | ---- | ----------- |
| <a id="geonodegroupwikirepositoryregistriesids"></a>`ids` | [`[ID!]`](#id) | Filters registries by their ID. |
+##### `GeoNode.lfsObjectRegistries`
+
+Find LFS object registries on this Geo node. Available only when feature flag `geo_lfs_object_replication` is enabled.
+
+Returns [`LfsObjectRegistryConnection`](#lfsobjectregistryconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="geonodelfsobjectregistriesids"></a>`ids` | [`[ID!]`](#id) | Filters registries by their ID. |
+
##### `GeoNode.mergeRequestDiffRegistries`
Find merge request diff registries on this Geo node.
@@ -9327,6 +9366,23 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="labeltitle"></a>`title` | [`String!`](#string) | Content of the label. |
| <a id="labelupdatedat"></a>`updatedAt` | [`Time!`](#time) | When this label was last updated. |
+### `LfsObjectRegistry`
+
+Represents the Geo sync and verification state of an LFS object.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="lfsobjectregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the LfsObjectRegistry was created. |
+| <a id="lfsobjectregistryid"></a>`id` | [`ID!`](#id) | ID of the LfsObjectRegistry. |
+| <a id="lfsobjectregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the LfsObjectRegistry. |
+| <a id="lfsobjectregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the LfsObjectRegistry. |
+| <a id="lfsobjectregistrylfsobjectid"></a>`lfsObjectId` | [`ID!`](#id) | ID of the LFS object. |
+| <a id="lfsobjectregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the LfsObjectRegistry should be resynced. |
+| <a id="lfsobjectregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the LfsObjectRegistry. |
+| <a id="lfsobjectregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the LfsObjectRegistry. |
+
### `LicenseHistoryEntry`
Represents an entry from the Cloud License history.
diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md
index 25cdbaf75ba..912a046c2c0 100644
--- a/doc/development/documentation/styleguide/index.md
+++ b/doc/development/documentation/styleguide/index.md
@@ -1112,13 +1112,9 @@ document to ensure it links to the most recent version of the file.
When documenting navigation through the user interface:
- Use the exact wording as shown in the UI, including any capital letters as-is.
-- Use bold text for navigation items and the char "greater than" (`>`) as a
- separator. For example: `From your project, go to **Settings > CI/CD**`.
-- If there are any expandable menus, make sure to mention that the user needs to
- expand the tab to find the settings you're referring to. For example:
- `From your group, go to **Settings > CI/CD** and expand **General pipelines**`.
+- Use bold text for navigation items.
-### Navigational elements
+### What to call the menus
Use these terms when referring to the main GitLab user interface
elements:
@@ -1130,6 +1126,19 @@ elements:
- **Right sidebar**: This is the navigation sidebar on the right of the user
interface, specific to the open issue, merge request, or epic.
+### How to document the left sidebar
+
+To be consistent, use this format when you refer to the left sidebar.
+
+- Go to your project and select **Settings > CI/CD**.
+- Go to your group and select **Settings > CI/CD**.
+- Go to the Admin Area (**{admin}**) and select **Overview > Projects**.
+
+For expandable menus, use this format:
+
+1. Go to your group and select **Settings > CI/CD**.
+1. Expand **General pipelines**.
+
## Images
Images, including screenshots, can help a reader better understand a concept.
diff --git a/doc/user/packages/npm_registry/index.md b/doc/user/packages/npm_registry/index.md
index b6312002184..8e3718f3255 100644
--- a/doc/user/packages/npm_registry/index.md
+++ b/doc/user/packages/npm_registry/index.md
@@ -43,7 +43,7 @@ The npm version is shown in the output:
### Install Yarn
As an alternative to npm, you can install Yarn in your local environment by following the
-instructions at [yarnpkg.com](https://classic.yarnpkg.com/en/docs/install).
+instructions at [classic.yarnpkg.com](https://classic.yarnpkg.com/en/docs/install).
When installation is complete, verify you can use Yarn in your terminal by
running:
@@ -305,6 +305,46 @@ See the
[Publish npm packages to the GitLab Package Registry using semantic-release](../../../ci/examples/semantic-release.md)
step-by-step guide and demo project for a complete example.
+## Configure the GitLab npm registry with Yarn 2
+
+You can get started with Yarn 2 by following the documentation at
+[https://yarnpkg.com/getting-started/install](https://yarnpkg.com/getting-started/install).
+
+To publish and install with the project-level npm endpoint, set the following configuration in
+`.yarnrc.yml`:
+
+```yaml
+npmScopes:
+ foo:
+ npmRegistryServer: "https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/"
+ npmPublishRegistry: "https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/"
+
+npmRegistries:
+ //gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:
+ npmAlwaysAuth: true
+ npmAuthToken: "<your_token>"
+```
+
+For the instance-level npm endpoint, use this Yarn 2 configuration in `.yarnrc.yml`:
+
+```yaml
+npmScopes:
+ foo:
+ npmRegistryServer: "https://gitlab.example.com/api/v4/packages/npm/"
+
+npmRegistries:
+ //gitlab.example.com/api/v4/packages/npm/:
+ npmAlwaysAuth: true
+ npmAuthToken: "<your_token>"
+```
+
+In this configuration:
+
+- Replace `<your_token>` with your personal access token or deploy token.
+- Replace `<your_project_id>` with your project's ID, which you can find on the project's home page.
+- Replace `gitlab.example.com` with your domain name.
+- Your scope is `foo`, without `@`.
+
## Publishing packages with the same name or version
You cannot publish a package if a package of the same name and version already exists.
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 2becbbb9117..b7d667e2556 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -905,6 +905,10 @@ module Gitlab
end
end
+ def convert_to_bigint_column(column)
+ "#{column}_convert_to_bigint"
+ end
+
# Initializes the conversion of a set of integer columns to bigint
#
# It can be used for converting both a Primary Key and any Foreign Keys
@@ -948,7 +952,7 @@ module Gitlab
check_trigger_permissions!(table)
- conversions = columns.to_h { |column| [column, "#{column}_convert_to_bigint"] }
+ conversions = columns.to_h { |column| [column, convert_to_bigint_column(column)] }
with_lock_retries do
conversions.each do |(source_column, temporary_name)|
@@ -969,6 +973,20 @@ module Gitlab
end
end
+ # Reverts `initialize_conversion_of_integer_to_bigint`
+ #
+ # table - The name of the database table containing the columns
+ # columns - The name, or array of names, of the column(s) that we're converting to bigint.
+ def revert_initialize_conversion_of_integer_to_bigint(table, columns)
+ columns = Array.wrap(columns)
+ temporary_columns = columns.map { |column| convert_to_bigint_column(column) }
+
+ trigger_name = rename_trigger_name(table, columns, temporary_columns)
+ remove_rename_triggers_for_postgresql(table, trigger_name)
+
+ temporary_columns.each { |column| remove_column(table, column) }
+ end
+
# Backfills the new columns used in an integer-to-bigint conversion using background migrations.
#
# - This helper should be called from a post-deployment migration.
@@ -1025,7 +1043,7 @@ module Gitlab
conversions = Array.wrap(columns).to_h do |column|
raise ArgumentError, "Column #{column} does not exist on #{table}" unless column_exists?(table, column)
- temporary_name = "#{column}_convert_to_bigint"
+ temporary_name = convert_to_bigint_column(column)
raise ArgumentError, "Column #{temporary_name} does not exist on #{table}" unless column_exists?(table, temporary_name)
[column, temporary_name]
@@ -1042,6 +1060,25 @@ module Gitlab
sub_batch_size: sub_batch_size)
end
+ # Reverts `backfill_conversion_of_integer_to_bigint`
+ #
+ # table - The name of the database table containing the column
+ # columns - The name, or an array of names, of the column(s) we want to convert to bigint.
+ # primary_key - The name of the primary key column (most often :id)
+ def revert_backfill_conversion_of_integer_to_bigint(table, columns, primary_key: :id)
+ columns = Array.wrap(columns)
+
+ conditions = ActiveRecord::Base.sanitize_sql([
+ 'job_class_name = :job_class_name AND table_name = :table_name AND column_name = :column_name AND job_arguments = :job_arguments',
+ job_class_name: 'CopyColumnUsingBackgroundMigrationJob',
+ table_name: table,
+ column_name: primary_key,
+ job_arguments: [columns, columns.map { |column| convert_to_bigint_column(column) }].to_json
+ ])
+
+ execute("DELETE FROM batched_background_migrations WHERE #{conditions}")
+ end
+
# Performs a concurrent column rename when using PostgreSQL.
def install_rename_triggers_for_postgresql(table, old, new, trigger_name: nil)
Gitlab::Database::UnidirectionalCopyTrigger.on_table(table).create(old, new, trigger_name: trigger_name)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 98e5cd0d9ac..e26ee2ae424 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -14434,6 +14434,9 @@ msgstr ""
msgid "Geo|Connection timeout should be between 1-120"
msgstr ""
+msgid "Geo|Consult Geo troubleshooting information"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
@@ -14506,6 +14509,9 @@ msgstr ""
msgid "Geo|Learn more about Geo"
msgstr ""
+msgid "Geo|Learn more about Geo node statuses"
+msgstr ""
+
msgid "Geo|Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
@@ -14521,6 +14527,9 @@ msgstr ""
msgid "Geo|Node name should be between 1 and 255 characters"
msgstr ""
+msgid "Geo|Node's status was updated %{timeAgo}."
+msgstr ""
+
msgid "Geo|Not synced yet"
msgstr ""
@@ -14659,6 +14668,9 @@ msgstr ""
msgid "Geo|There are no %{replicable_type} to show"
msgstr ""
+msgid "Geo|There was an error fetching the Geo Nodes"
+msgstr ""
+
msgid "Geo|Tracking database entry will be removed. Are you sure?"
msgstr ""
@@ -14680,6 +14692,9 @@ msgstr ""
msgid "Geo|Unknown state"
msgstr ""
+msgid "Geo|Updated %{timeAgo}"
+msgstr ""
+
msgid "Geo|Verification"
msgstr ""
@@ -32085,9 +32100,6 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
-msgid "There was an error fetching the Geo Nodes"
-msgstr ""
-
msgid "There was an error fetching the Geo Settings"
msgstr ""
diff --git a/qa/qa.rb b/qa/qa.rb
index 8986bf658f5..2700e908fd2 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -505,6 +505,7 @@ module QA
autoload :WikiPageForm, 'qa/page/component/wiki_page_form'
autoload :AccessTokens, 'qa/page/component/access_tokens'
autoload :CommitModal, 'qa/page/component/commit_modal'
+ autoload :VisibilitySetting, 'qa/page/component/visibility_setting'
module Issuable
autoload :Common, 'qa/page/component/issuable/common'
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 289094268b6..bed6e639cd0 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -132,16 +132,16 @@ module QA
all(element_selector_css(name), **kwargs)
end
- def check_element(name, click_by_js = false)
- if find_element(name, visible: false).checked?
+ def check_element(name, click_by_js = false, visibility = false)
+ if find_element(name, visible: visibility).checked?
QA::Runtime::Logger.debug("#{name} is already checked")
return
end
retry_until(sleep_interval: 1) do
- click_checkbox_or_radio(name, click_by_js)
- checked = find_element(name, visible: false).checked?
+ click_checkbox_or_radio(name, click_by_js, visibility)
+ checked = find_element(name, visible: visibility).checked?
QA::Runtime::Logger.debug(checked ? "#{name} was checked" : "#{name} was not checked")
@@ -149,16 +149,16 @@ module QA
end
end
- def uncheck_element(name, click_by_js = false)
- unless find_element(name, visible: false).checked?
+ def uncheck_element(name, click_by_js = false, visibility = false)
+ unless find_element(name, visible: visibility).checked?
QA::Runtime::Logger.debug("#{name} is already unchecked")
return
end
retry_until(sleep_interval: 1) do
- click_checkbox_or_radio(name, click_by_js)
- unchecked = !find_element(name, visible: false).checked?
+ click_checkbox_or_radio(name, click_by_js, visibility)
+ unchecked = !find_element(name, visible: visibility).checked?
QA::Runtime::Logger.debug(unchecked ? "#{name} was unchecked" : "#{name} was not unchecked")
@@ -167,21 +167,22 @@ module QA
end
# Method for selecting radios
- def choose_element(name, click_by_js = false)
- if find_element(name, visible: false).checked?
+ def choose_element(name, click_by_js = false, visibility = false)
+ if find_element(name, visible: visibility).checked?
QA::Runtime::Logger.debug("#{name} is already selected")
return
end
retry_until(sleep_interval: 1) do
- click_checkbox_or_radio(name, click_by_js)
- selected = find_element(name, visible: false).checked?
+ click_checkbox_or_radio(name, click_by_js, visibility)
+ selected = find_element(name, visible: visibility).checked?
QA::Runtime::Logger.debug(selected ? "#{name} was selected" : "#{name} was not selected")
selected
end
+ wait_for_requests
end
# Use this to simulate moving the pointer to an element's coordinate
@@ -424,8 +425,8 @@ module QA
private
- def click_checkbox_or_radio(name, click_by_js)
- box = find_element(name, visible: false)
+ def click_checkbox_or_radio(name, click_by_js, visibility)
+ box = find_element(name, visible: visibility)
# Some checkboxes and radio buttons are hidden by their labels and cannot be clicked directly
click_by_js ? page.execute_script("arguments[0].click();", box) : box.click
end
diff --git a/qa/qa/page/component/access_tokens.rb b/qa/qa/page/component/access_tokens.rb
index d8e3d12b38b..3c8a6cf6a1d 100644
--- a/qa/qa/page/component/access_tokens.rb
+++ b/qa/qa/page/component/access_tokens.rb
@@ -19,7 +19,7 @@ module QA
end
base.view 'app/views/shared/tokens/_scopes_form.html.haml' do
- element :api_radio, 'qa-#{scope}-radio' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
+ element :api_checkbox, '#{scope}_checkbox' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
end
base.view 'app/views/shared/access_tokens/_created_container.html.haml' do
@@ -36,7 +36,7 @@ module QA
end
def check_api
- check_element(:api_radio)
+ check_element(:api_checkbox)
end
def click_create_token_button
diff --git a/qa/qa/page/component/visibility_setting.rb b/qa/qa/page/component/visibility_setting.rb
new file mode 100644
index 00000000000..4370cfb4564
--- /dev/null
+++ b/qa/qa/page/component/visibility_setting.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module VisibilitySetting
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.view 'app/views/shared/_visibility_radios.html.haml' do
+ element :visibility_radio, 'qa_selector: "#{visibility_level_label(level).downcase}_radio"' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
+ end
+ end
+
+ def set_visibility(visibility)
+ choose_element("#{visibility.downcase}_radio", false, true)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/group/new.rb b/qa/qa/page/group/new.rb
index 5f43cfb49c0..9a4e53d6053 100644
--- a/qa/qa/page/group/new.rb
+++ b/qa/qa/page/group/new.rb
@@ -4,23 +4,20 @@ module QA
module Page
module Group
class New < Page::Base
+ include Page::Component::VisibilitySetting
+
view 'app/views/shared/_group_form.html.haml' do
- element :group_path_field, 'text_field :path' # rubocop:disable QA/ElementWithPattern
- element :group_name_field, 'text_field :name' # rubocop:disable QA/ElementWithPattern
+ element :group_path_field
+ element :group_name_field
end
view 'app/views/groups/_new_group_fields.html.haml' do
element :create_group_button, "submit _('Create group')" # rubocop:disable QA/ElementWithPattern
- element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern
end
def set_path(path)
- fill_in 'group_path', with: path
- fill_in 'group_name', with: path
- end
-
- def set_visibility(visibility)
- choose visibility
+ fill_element(:group_path_field, path)
+ fill_element(:group_name_field, path)
end
def create
diff --git a/qa/qa/page/group/settings/general.rb b/qa/qa/page/group/settings/general.rb
index 1ab849d10b1..81ab6a0aa84 100644
--- a/qa/qa/page/group/settings/general.rb
+++ b/qa/qa/page/group/settings/general.rb
@@ -6,6 +6,7 @@ module QA
module Settings
class General < QA::Page::Base
include ::QA::Page::Settings::Common
+ include Page::Component::VisibilitySetting
view 'app/views/groups/edit.html.haml' do
element :permission_lfs_2fa_content
@@ -21,10 +22,6 @@ module QA
element :save_name_visibility_settings_button
end
- view 'app/views/shared/_visibility_radios.html.haml' do
- element :internal_radio, 'qa_selector: "#{visibility_level_label(level).downcase}_radio"' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
- end
-
view 'app/views/groups/settings/_lfs.html.haml' do
element :lfs_checkbox
end
@@ -56,10 +53,6 @@ module QA
find_element(:group_name_field).set name
end
- def set_group_visibility(visibility)
- find_element("#{visibility.downcase}_radio").click
- end
-
def click_save_name_visibility_settings_button
click_element(:save_name_visibility_settings_button)
end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index d1033dbfca9..ebef1a1a972 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -4,8 +4,9 @@ module QA
module Page
module Project
class New < Page::Base
- include Page::Component::Select2
include Page::Component::Project::Templates
+ include Page::Component::Select2
+ include Page::Component::VisibilitySetting
view 'app/views/projects/new.html.haml' do
element :project_create_from_template_tab
@@ -59,10 +60,6 @@ module QA
click_element(:project_create_from_template_tab)
end
- def set_visibility(visibility)
- choose visibility.capitalize
- end
-
def click_github_link
click_link 'GitHub'
end
diff --git a/qa/qa/page/project/settings/merge_request.rb b/qa/qa/page/project/settings/merge_request.rb
index 0b4a12dbb2e..96ef9ade292 100644
--- a/qa/qa/page/project/settings/merge_request.rb
+++ b/qa/qa/page/project/settings/merge_request.rb
@@ -12,7 +12,7 @@ module QA
end
view 'app/views/projects/_merge_request_merge_method_settings.html.haml' do
- element :merge_ff_radio_button
+ element :merge_ff_radio
end
view 'app/views/projects/_merge_request_merge_checks_settings.html.haml' do
@@ -24,7 +24,7 @@ module QA
end
def enable_ff_only
- click_element(:merge_ff_radio_button)
+ choose_element(:merge_ff_radio)
click_save_changes
end
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index fd68ac0de16..3f04538a311 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -36,7 +36,7 @@ module QA
end
view 'app/assets/javascripts/ide/components/commit_sidebar/actions.vue' do
- element :commit_to_current_branch_radio
+ element :commit_to_current_branch_radio_container
end
view 'app/assets/javascripts/ide/components/commit_sidebar/form.vue' do
@@ -44,6 +44,10 @@ module QA
element :commit_button
end
+ view 'app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue' do
+ element :commit_type_radio
+ end
+
view 'app/assets/javascripts/ide/components/repo_editor.vue' do
element :editor_container
end
@@ -216,7 +220,9 @@ module QA
# animation is still in process even when the buttons have the
# expected visibility.
commit_success = retry_until(sleep_interval: 5) do
- click_element(:commit_to_current_branch_radio) if has_element?(:commit_to_current_branch_radio)
+ within_element(:commit_to_current_branch_radio_container) do
+ choose_element(:commit_type_radio)
+ end
click_element(:commit_button) if has_element?(:commit_button)
# If this is the first commit, the commit SHA only appears after reloading
diff --git a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
index 68367eb67b8..3e378db04d4 100644
--- a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
+++ b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
@@ -8,6 +8,10 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
let(:sub_batch_size) { 1000 }
let(:pause_ms) { 0 }
+ let(:helpers) do
+ ActiveRecord::Migration.new.extend(Gitlab::Database::MigrationHelpers)
+ end
+
before do
ActiveRecord::Base.connection.execute(<<~SQL)
CREATE TABLE #{table_name}
@@ -15,8 +19,8 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
id integer NOT NULL,
name character varying,
fk integer NOT NULL,
- id_convert_to_bigint bigint DEFAULT 0 NOT NULL,
- fk_convert_to_bigint bigint DEFAULT 0 NOT NULL,
+ #{helpers.convert_to_bigint_column(:id)} bigint DEFAULT 0 NOT NULL,
+ #{helpers.convert_to_bigint_column(:fk)} bigint DEFAULT 0 NOT NULL,
name_convert_to_text text DEFAULT 'no name'
);
SQL
@@ -41,18 +45,20 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
let(:migration_class) { described_class.name }
it 'copies all primary keys in range' do
- copy_columns.perform(12, 15, table_name, 'id', sub_batch_size, pause_ms, 'id', 'id_convert_to_bigint')
+ temporary_column = helpers.convert_to_bigint_column(:id)
+ copy_columns.perform(12, 15, table_name, 'id', sub_batch_size, pause_ms, 'id', temporary_column)
- expect(test_table.where('id = id_convert_to_bigint').pluck(:id)).to contain_exactly(12, 15)
- expect(test_table.where(id_convert_to_bigint: 0).pluck(:id)).to contain_exactly(11, 19)
+ expect(test_table.where("id = #{temporary_column}").pluck(:id)).to contain_exactly(12, 15)
+ expect(test_table.where(temporary_column => 0).pluck(:id)).to contain_exactly(11, 19)
expect(test_table.all.count).to eq(4)
end
it 'copies all foreign keys in range' do
- copy_columns.perform(10, 14, table_name, 'id', sub_batch_size, pause_ms, 'fk', 'fk_convert_to_bigint')
+ temporary_column = helpers.convert_to_bigint_column(:fk)
+ copy_columns.perform(10, 14, table_name, 'id', sub_batch_size, pause_ms, 'fk', temporary_column)
- expect(test_table.where('fk = fk_convert_to_bigint').pluck(:id)).to contain_exactly(11, 12)
- expect(test_table.where(fk_convert_to_bigint: 0).pluck(:id)).to contain_exactly(15, 19)
+ expect(test_table.where("fk = #{temporary_column}").pluck(:id)).to contain_exactly(11, 12)
+ expect(test_table.where(temporary_column => 0).pluck(:id)).to contain_exactly(15, 19)
expect(test_table.all.count).to eq(4)
end
@@ -68,18 +74,20 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
it 'copies multiple columns when given' do
columns_to_copy_from = %w[id fk]
- columns_to_copy_to = %w[id_convert_to_bigint fk_convert_to_bigint]
+ id_tmp_column = helpers.convert_to_bigint_column('id')
+ fk_tmp_column = helpers.convert_to_bigint_column('fk')
+ columns_to_copy_to = [id_tmp_column, fk_tmp_column]
subject.perform(10, 15, table_name, 'id', sub_batch_size, pause_ms, columns_to_copy_from, columns_to_copy_to)
- expect(test_table.where('id = id_convert_to_bigint AND fk = fk_convert_to_bigint').pluck(:id)).to contain_exactly(11, 12, 15)
- expect(test_table.where(id_convert_to_bigint: 0).where(fk_convert_to_bigint: 0).pluck(:id)).to contain_exactly(19)
+ expect(test_table.where("id = #{id_tmp_column} AND fk = #{fk_tmp_column}").pluck(:id)).to contain_exactly(11, 12, 15)
+ expect(test_table.where(id_tmp_column => 0).where(fk_tmp_column => 0).pluck(:id)).to contain_exactly(19)
expect(test_table.all.count).to eq(4)
end
it 'raises error when number of source and target columns does not match' do
columns_to_copy_from = %w[id fk]
- columns_to_copy_to = %w[id_convert_to_bigint]
+ columns_to_copy_to = [helpers.convert_to_bigint_column(:id)]
expect do
subject.perform(10, 15, table_name, 'id', sub_batch_size, pause_ms, columns_to_copy_from, columns_to_copy_to)
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 9eca8dedea9..172af0ade90 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::MigrationHelpers do
include Database::TableSchemaHelpers
+ include Database::TriggerHelpers
let(:model) do
ActiveRecord::Migration.new.extend(described_class)
@@ -1702,10 +1703,16 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
+ describe '#convert_to_bigint_column' do
+ it 'returns the name of the temporary column used to convert to bigint' do
+ expect(model.convert_to_bigint_column(:id)).to eq('id_convert_to_bigint')
+ end
+ end
+
describe '#initialize_conversion_of_integer_to_bigint' do
let(:table) { :test_table }
let(:column) { :id }
- let(:tmp_column) { "#{column}_convert_to_bigint" }
+ let(:tmp_column) { model.convert_to_bigint_column(column) }
before do
model.create_table table, id: false do |t|
@@ -1774,11 +1781,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
context 'when multiple columns are given' do
it 'creates the correct columns and installs the trigger' do
columns_to_convert = %i[id non_nullable_column nullable_column]
- temporary_columns = %w[
- id_convert_to_bigint
- non_nullable_column_convert_to_bigint
- nullable_column_convert_to_bigint
- ]
+ temporary_columns = columns_to_convert.map { |column| model.convert_to_bigint_column(column) }
expect(model).to receive(:add_column).with(table, temporary_columns[0], :bigint, default: 0, null: false)
expect(model).to receive(:add_column).with(table, temporary_columns[1], :bigint, default: 0, null: false)
@@ -1791,10 +1794,55 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
+ describe '#revert_initialize_conversion_of_integer_to_bigint' do
+ let(:table) { :test_table }
+
+ before do
+ model.create_table table, id: false do |t|
+ t.integer :id, primary_key: true
+ t.integer :other_id
+ end
+
+ model.initialize_conversion_of_integer_to_bigint(table, columns)
+ end
+
+ context 'when single column is given' do
+ let(:columns) { :id }
+
+ it 'removes column, trigger, and function' do
+ temporary_column = model.convert_to_bigint_column(:id)
+ trigger_name = model.rename_trigger_name(table, :id, temporary_column)
+
+ model.revert_initialize_conversion_of_integer_to_bigint(table, columns)
+
+ expect(model.column_exists?(table, temporary_column)).to eq(false)
+ expect_trigger_not_to_exist(table, trigger_name)
+ expect_function_not_to_exist(trigger_name)
+ end
+ end
+
+ context 'when multiple columns are given' do
+ let(:columns) { [:id, :other_id] }
+
+ it 'removes column, trigger, and function' do
+ temporary_columns = columns.map { |column| model.convert_to_bigint_column(column) }
+ trigger_name = model.rename_trigger_name(table, columns, temporary_columns)
+
+ model.revert_initialize_conversion_of_integer_to_bigint(table, columns)
+
+ temporary_columns.each do |column|
+ expect(model.column_exists?(table, column)).to eq(false)
+ end
+ expect_trigger_not_to_exist(table, trigger_name)
+ expect_function_not_to_exist(trigger_name)
+ end
+ end
+ end
+
describe '#backfill_conversion_of_integer_to_bigint' do
let(:table) { :_test_backfill_table }
let(:column) { :id }
- let(:tmp_column) { "#{column}_convert_to_bigint" }
+ let(:tmp_column) { model.convert_to_bigint_column(column) }
before do
model.create_table table, id: false do |t|
@@ -1872,14 +1920,14 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
interval: 120,
batch_size: 2,
sub_batch_size: 1,
- job_arguments: [[column.to_s], ["#{column}_convert_to_bigint"]]
+ job_arguments: [[column.to_s], [model.convert_to_bigint_column(column)]]
)
end
end
context 'when multiple columns are being converted' do
let(:other_column) { :other_id }
- let(:other_tmp_column) { "#{other_column}_convert_to_bigint" }
+ let(:other_tmp_column) { model.convert_to_bigint_column(other_column) }
let(:columns) { [column, other_column] }
it 'creates the batched migration tracking record' do
@@ -1905,6 +1953,54 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
+ describe '#revert_backfill_conversion_of_integer_to_bigint' do
+ let(:table) { :_test_backfill_table }
+ let(:primary_key) { :id }
+
+ before do
+ model.create_table table, id: false do |t|
+ t.integer primary_key, primary_key: true
+ t.text :message, null: false
+ t.integer :other_id
+ t.timestamps
+ end
+
+ model.initialize_conversion_of_integer_to_bigint(table, columns, primary_key: primary_key)
+ model.backfill_conversion_of_integer_to_bigint(table, columns, primary_key: primary_key)
+ end
+
+ context 'when a single column is being converted' do
+ let(:columns) { :id }
+
+ it 'deletes the batched migration tracking record' do
+ expect do
+ model.revert_backfill_conversion_of_integer_to_bigint(table, columns)
+ end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(-1)
+ end
+ end
+
+ context 'when a multiple columns are being converted' do
+ let(:columns) { [:id, :other_id] }
+
+ it 'deletes the batched migration tracking record' do
+ expect do
+ model.revert_backfill_conversion_of_integer_to_bigint(table, columns)
+ end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(-1)
+ end
+ end
+
+ context 'when primary key column has custom name' do
+ let(:primary_key) { :other_pk }
+ let(:columns) { :other_id }
+
+ it 'deletes the batched migration tracking record' do
+ expect do
+ model.revert_backfill_conversion_of_integer_to_bigint(table, columns, primary_key: primary_key)
+ end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(-1)
+ end
+ end
+ end
+
describe '#index_exists_by_name?' do
it 'returns true if an index exists' do
ActiveRecord::Base.connection.execute(
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 1f7d5c5f91f..3a11bed4efe 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -5620,4 +5620,47 @@ RSpec.describe User do
end
end
end
+
+ describe '.dormant' do
+ it 'returns dormant users' do
+ freeze_time do
+ not_that_long_ago = (described_class::MINIMUM_INACTIVE_DAYS - 1).days.ago.to_date
+ too_long_ago = described_class::MINIMUM_INACTIVE_DAYS.days.ago.to_date
+
+ create(:user, :deactivated, last_activity_on: too_long_ago)
+
+ User::INTERNAL_USER_TYPES.map do |user_type|
+ create(:user, state: :active, user_type: user_type, last_activity_on: too_long_ago)
+ end
+
+ create(:user, last_activity_on: not_that_long_ago)
+
+ dormant_user = create(:user, last_activity_on: too_long_ago)
+
+ expect(described_class.dormant).to contain_exactly(dormant_user)
+ end
+ end
+ end
+
+ describe '.with_no_activity' do
+ it 'returns users with no activity' do
+ freeze_time do
+ not_that_long_ago = (described_class::MINIMUM_INACTIVE_DAYS - 1).days.ago.to_date
+ too_long_ago = described_class::MINIMUM_INACTIVE_DAYS.days.ago.to_date
+
+ create(:user, :deactivated, last_activity_on: nil)
+
+ User::INTERNAL_USER_TYPES.map do |user_type|
+ create(:user, state: :active, user_type: user_type, last_activity_on: nil)
+ end
+
+ create(:user, last_activity_on: not_that_long_ago)
+ create(:user, last_activity_on: too_long_ago)
+
+ user_with_no_activity = create(:user, last_activity_on: nil)
+
+ expect(described_class.with_no_activity).to contain_exactly(user_with_no_activity)
+ end
+ end
+ end
end
diff --git a/spec/workers/users/deactivate_dormant_users_worker_spec.rb b/spec/workers/users/deactivate_dormant_users_worker_spec.rb
new file mode 100644
index 00000000000..32291a143ee
--- /dev/null
+++ b/spec/workers/users/deactivate_dormant_users_worker_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::DeactivateDormantUsersWorker do
+ describe '#perform' do
+ subject(:worker) { described_class.new }
+
+ it 'does not run for GitLab.com' do
+ create(:user, last_activity_on: User::MINIMUM_INACTIVE_DAYS.days.ago.to_date)
+ create(:user, last_activity_on: nil)
+
+ expect(Gitlab).to receive(:com?).and_return(true)
+ expect(Gitlab::CurrentSettings).not_to receive(:current_application_settings)
+
+ worker.perform
+
+ expect(User.dormant.count).to eq(1)
+ expect(User.with_no_activity.count).to eq(1)
+ end
+
+ context 'when automatic deactivation of dormant users is enabled' do
+ before do
+ stub_application_setting(deactivate_dormant_users: true)
+ end
+
+ it 'deactivates dormant users' do
+ freeze_time do
+ stub_const("#{described_class.name}::BATCH_SIZE", 1)
+ stub_const("#{described_class.name}::PAUSE_SECONDS", 0)
+
+ create(:user, last_activity_on: User::MINIMUM_INACTIVE_DAYS.days.ago.to_date)
+ create(:user, last_activity_on: nil)
+
+ expect(worker).to receive(:sleep).twice
+
+ worker.perform
+
+ expect(User.dormant.count).to eq(0)
+ expect(User.with_no_activity.count).to eq(0)
+ end
+ end
+ end
+
+ context 'when automatic deactivation of dormant users is disabled' do
+ before do
+ stub_application_setting(deactivate_dormant_users: false)
+ end
+
+ it 'does nothing' do
+ create(:user, last_activity_on: User::MINIMUM_INACTIVE_DAYS.days.ago.to_date)
+ create(:user, last_activity_on: nil)
+
+ worker.perform
+
+ expect(User.dormant.count).to eq(1)
+ expect(User.with_no_activity.count).to eq(1)
+ end
+ end
+ end
+end