Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-04-23 21:09:46 +00:00
parent bc62085601
commit f6b349ed51
36 changed files with 564 additions and 87 deletions

View File

@ -110,7 +110,6 @@ linters:
- Layout/EmptyLineAfterGuardClause - Layout/EmptyLineAfterGuardClause
- Layout/LeadingCommentSpace - Layout/LeadingCommentSpace
- Layout/SpaceAroundOperators - Layout/SpaceAroundOperators
- Layout/SpaceBeforeBlockBraces
- Layout/SpaceBeforeComma - Layout/SpaceBeforeComma
- Layout/SpaceBeforeFirstArg - Layout/SpaceBeforeFirstArg
- Layout/SpaceInsideHashLiteralBraces - Layout/SpaceInsideHashLiteralBraces

View File

@ -76,8 +76,9 @@ export default {
:value="$options.commitToCurrentBranch" :value="$options.commitToCurrentBranch"
:disabled="!canPushToBranch" :disabled="!canPushToBranch"
:title="$options.currentBranchPermissionsTooltip" :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')"> <gl-sprintf :message="s__('IDE|Commit to %{branchName} branch')">
<template #branchName> <template #branchName>
<strong class="monospace">{{ currentBranchText }}</strong> <strong class="monospace">{{ currentBranchText }}</strong>

View File

@ -64,6 +64,7 @@ export default {
:disabled="disabled" :disabled="disabled"
type="radio" type="radio"
name="commit-action" name="commit-action"
data-qa-selector="commit_type_radio"
@change="updateCommitAction($event.target.value)" @change="updateCommitAction($event.target.value)"
/> />
<span class="gl-ml-3"> <span class="gl-ml-3">

View File

@ -337,6 +337,8 @@ class User < ApplicationRecord
end end
event :deactivate do 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 transition active: :deactivated
end 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_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 :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 :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 def preferred_language
read_attribute('preferred_language') || read_attribute('preferred_language') ||

View File

@ -5,7 +5,7 @@
.col-sm-6 .col-sm-6
.bs-callout .bs-callout
%p %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 %br
= _('You can register runners as separate users, on separate servers, and on your local machine. Register as many runners as you want.') = _('You can register runners as separate users, on separate servers, and on your local machine. Register as many runners as you want.')
%br %br

View File

@ -22,7 +22,7 @@
= s_('ProjectSettings|When there is a merge conflict, the user is given the option to rebase.') = s_('ProjectSettings|When there is a merge conflict, the user is given the option to rebase.')
.form-check.mb-2 .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 = label_tag :project_merge_method_ff, class: 'form-check-label' do
= s_('ProjectSettings|Fast-forward merge') = s_('ProjectSettings|Fast-forward merge')
.text-secondary .text-secondary

View File

@ -6,7 +6,7 @@
.form-group.group-name-holder.col-sm-12 .form-group.group-name-holder.col-sm-12
= f.label :name, class: 'label-bold' do = f.label :name, class: 'label-bold' do
= _("Group name") = _("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, required: true,
title: _('Please fill in a descriptive name for your group.'), title: _('Please fill in a descriptive name for your group.'),
autofocus: true autofocus: true
@ -22,7 +22,7 @@
- if parent - if parent
%strong= parent.full_path + '/' %strong= parent.full_path + '/'
= f.hidden_field :parent_id = 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, autofocus: local_assigns[:autofocus] || false, required: true,
pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS,
title: _('Please choose a group URL with no special characters.'), title: _('Please choose a group URL with no special characters.'),

View File

@ -4,6 +4,6 @@
- scopes.each do |scope| - scopes.each do |scope|
%fieldset.form-group.form-check %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' = label_tag "#{prefix}_scopes_#{scope}", scope, class: 'label-bold form-check-label'
.text-secondary= t scope, scope: scope_description(prefix) .text-secondary= t scope, scope: scope_description(prefix)

View File

@ -515,6 +515,14 @@
:weight: 1 :weight: 1
:idempotent: :idempotent:
:tags: [] :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 - :name: cronjob:x509_issuer_crl_check
:feature_category: :source_code_management :feature_category: :source_code_management
:has_external_dependencies: true :has_external_dependencies: true

View File

@ -38,7 +38,7 @@ module Database
end end
def with_exclusive_lease(interval) 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) lease = Gitlab::ExclusiveLease.new(lease_key, timeout: timeout)
yield if lease.try_obtain yield if lease.try_obtain
@ -46,10 +46,6 @@ module Database
lease&.cancel lease&.cancel
end end
def max(left, right)
left >= right ? left : right
end
def lease_key def lease_key
self.class.name.demodulize.underscore self.class.name.demodulize.underscore
end end

View File

@ -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

View File

@ -0,0 +1,5 @@
---
title: Automate deactivation of dormant users for self-managed instances
merge_request: 57778
author:
type: added

View File

@ -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'] ||= Settingslogic.new({})
Settings.cron_jobs['ssh_keys_expiring_soon_notification_worker']['cron'] ||= '0 1 * * *' 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['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 Gitlab.com do
Settings.cron_jobs['batched_background_migrations_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['batched_background_migrations_worker'] ||= Settingslogic.new({})

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
6a278c90b8c97fc2255528605ee6bf4547e37ac8c4c17979483ed9db562fa021

View File

@ -0,0 +1 @@
454992d01fa140896ff2a9cea66fb855c9e659a5a7969ac9a3cb5a608de36161

View File

@ -9479,6 +9479,7 @@ CREATE TABLE application_settings (
throttle_authenticated_packages_api_period_in_seconds integer DEFAULT 15 NOT NULL, throttle_authenticated_packages_api_period_in_seconds integer DEFAULT 15 NOT NULL,
throttle_unauthenticated_packages_api_enabled boolean DEFAULT false NOT NULL, throttle_unauthenticated_packages_api_enabled boolean DEFAULT false NOT NULL,
throttle_authenticated_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_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_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)), 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_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_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); CREATE INDEX index_users_on_managing_group_id ON users USING btree (managing_group_id);

View File

@ -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="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. | | <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` #### `LicenseHistoryEntryConnection`
The connection type for [`LicenseHistoryEntry`](#licensehistoryentry). 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. | | <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` ##### `GeoNode.mergeRequestDiffRegistries`
Find merge request diff registries on this Geo node. 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="labeltitle"></a>`title` | [`String!`](#string) | Content of the label. |
| <a id="labelupdatedat"></a>`updatedAt` | [`Time!`](#time) | When this label was last updated. | | <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` ### `LicenseHistoryEntry`
Represents an entry from the Cloud License history. Represents an entry from the Cloud License history.

View File

@ -1112,13 +1112,9 @@ document to ensure it links to the most recent version of the file.
When documenting navigation through the user interface: When documenting navigation through the user interface:
- Use the exact wording as shown in the UI, including any capital letters as-is. - 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 - Use bold text for navigation items.
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**`.
### Navigational elements ### What to call the menus
Use these terms when referring to the main GitLab user interface Use these terms when referring to the main GitLab user interface
elements: elements:
@ -1130,6 +1126,19 @@ elements:
- **Right sidebar**: This is the navigation sidebar on the right of the user - **Right sidebar**: This is the navigation sidebar on the right of the user
interface, specific to the open issue, merge request, or epic. 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
Images, including screenshots, can help a reader better understand a concept. Images, including screenshots, can help a reader better understand a concept.

View File

@ -43,7 +43,7 @@ The npm version is shown in the output:
### Install Yarn ### Install Yarn
As an alternative to npm, you can install Yarn in your local environment by following the 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 When installation is complete, verify you can use Yarn in your terminal by
running: running:
@ -305,6 +305,46 @@ See the
[Publish npm packages to the GitLab Package Registry using semantic-release](../../../ci/examples/semantic-release.md) [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. 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 ## Publishing packages with the same name or version
You cannot publish a package if a package of the same name and version already exists. You cannot publish a package if a package of the same name and version already exists.

View File

@ -905,6 +905,10 @@ module Gitlab
end end
end end
def convert_to_bigint_column(column)
"#{column}_convert_to_bigint"
end
# Initializes the conversion of a set of integer columns to bigint # 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 # It can be used for converting both a Primary Key and any Foreign Keys
@ -948,7 +952,7 @@ module Gitlab
check_trigger_permissions!(table) 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 with_lock_retries do
conversions.each do |(source_column, temporary_name)| conversions.each do |(source_column, temporary_name)|
@ -969,6 +973,20 @@ module Gitlab
end end
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. # Backfills the new columns used in an integer-to-bigint conversion using background migrations.
# #
# - This helper should be called from a post-deployment migration. # - This helper should be called from a post-deployment migration.
@ -1025,7 +1043,7 @@ module Gitlab
conversions = Array.wrap(columns).to_h do |column| conversions = Array.wrap(columns).to_h do |column|
raise ArgumentError, "Column #{column} does not exist on #{table}" unless column_exists?(table, 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) raise ArgumentError, "Column #{temporary_name} does not exist on #{table}" unless column_exists?(table, temporary_name)
[column, temporary_name] [column, temporary_name]
@ -1042,6 +1060,25 @@ module Gitlab
sub_batch_size: sub_batch_size) sub_batch_size: sub_batch_size)
end 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. # Performs a concurrent column rename when using PostgreSQL.
def install_rename_triggers_for_postgresql(table, old, new, trigger_name: nil) 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) Gitlab::Database::UnidirectionalCopyTrigger.on_table(table).create(old, new, trigger_name: trigger_name)

View File

@ -14434,6 +14434,9 @@ msgstr ""
msgid "Geo|Connection timeout should be between 1-120" msgid "Geo|Connection timeout should be between 1-120"
msgstr "" msgstr ""
msgid "Geo|Consult Geo troubleshooting information"
msgstr ""
msgid "Geo|Could not remove tracking entry for an existing project." msgid "Geo|Could not remove tracking entry for an existing project."
msgstr "" msgstr ""
@ -14506,6 +14509,9 @@ msgstr ""
msgid "Geo|Learn more about Geo" msgid "Geo|Learn more about Geo"
msgstr "" 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." 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 "" msgstr ""
@ -14521,6 +14527,9 @@ msgstr ""
msgid "Geo|Node name should be between 1 and 255 characters" msgid "Geo|Node name should be between 1 and 255 characters"
msgstr "" msgstr ""
msgid "Geo|Node's status was updated %{timeAgo}."
msgstr ""
msgid "Geo|Not synced yet" msgid "Geo|Not synced yet"
msgstr "" msgstr ""
@ -14659,6 +14668,9 @@ msgstr ""
msgid "Geo|There are no %{replicable_type} to show" msgid "Geo|There are no %{replicable_type} to show"
msgstr "" msgstr ""
msgid "Geo|There was an error fetching the Geo Nodes"
msgstr ""
msgid "Geo|Tracking database entry will be removed. Are you sure?" msgid "Geo|Tracking database entry will be removed. Are you sure?"
msgstr "" msgstr ""
@ -14680,6 +14692,9 @@ msgstr ""
msgid "Geo|Unknown state" msgid "Geo|Unknown state"
msgstr "" msgstr ""
msgid "Geo|Updated %{timeAgo}"
msgstr ""
msgid "Geo|Verification" msgid "Geo|Verification"
msgstr "" msgstr ""
@ -32085,9 +32100,6 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}" msgid "There was an error fetching the %{replicableType}"
msgstr "" msgstr ""
msgid "There was an error fetching the Geo Nodes"
msgstr ""
msgid "There was an error fetching the Geo Settings" msgid "There was an error fetching the Geo Settings"
msgstr "" msgstr ""

View File

@ -505,6 +505,7 @@ module QA
autoload :WikiPageForm, 'qa/page/component/wiki_page_form' autoload :WikiPageForm, 'qa/page/component/wiki_page_form'
autoload :AccessTokens, 'qa/page/component/access_tokens' autoload :AccessTokens, 'qa/page/component/access_tokens'
autoload :CommitModal, 'qa/page/component/commit_modal' autoload :CommitModal, 'qa/page/component/commit_modal'
autoload :VisibilitySetting, 'qa/page/component/visibility_setting'
module Issuable module Issuable
autoload :Common, 'qa/page/component/issuable/common' autoload :Common, 'qa/page/component/issuable/common'

View File

@ -132,16 +132,16 @@ module QA
all(element_selector_css(name), **kwargs) all(element_selector_css(name), **kwargs)
end end
def check_element(name, click_by_js = false) def check_element(name, click_by_js = false, visibility = false)
if find_element(name, visible: false).checked? if find_element(name, visible: visibility).checked?
QA::Runtime::Logger.debug("#{name} is already checked") QA::Runtime::Logger.debug("#{name} is already checked")
return return
end end
retry_until(sleep_interval: 1) do retry_until(sleep_interval: 1) do
click_checkbox_or_radio(name, click_by_js) click_checkbox_or_radio(name, click_by_js, visibility)
checked = find_element(name, visible: false).checked? checked = find_element(name, visible: visibility).checked?
QA::Runtime::Logger.debug(checked ? "#{name} was checked" : "#{name} was not checked") QA::Runtime::Logger.debug(checked ? "#{name} was checked" : "#{name} was not checked")
@ -149,16 +149,16 @@ module QA
end end
end end
def uncheck_element(name, click_by_js = false) def uncheck_element(name, click_by_js = false, visibility = false)
unless find_element(name, visible: false).checked? unless find_element(name, visible: visibility).checked?
QA::Runtime::Logger.debug("#{name} is already unchecked") QA::Runtime::Logger.debug("#{name} is already unchecked")
return return
end end
retry_until(sleep_interval: 1) do retry_until(sleep_interval: 1) do
click_checkbox_or_radio(name, click_by_js) click_checkbox_or_radio(name, click_by_js, visibility)
unchecked = !find_element(name, visible: false).checked? unchecked = !find_element(name, visible: visibility).checked?
QA::Runtime::Logger.debug(unchecked ? "#{name} was unchecked" : "#{name} was not unchecked") QA::Runtime::Logger.debug(unchecked ? "#{name} was unchecked" : "#{name} was not unchecked")
@ -167,21 +167,22 @@ module QA
end end
# Method for selecting radios # Method for selecting radios
def choose_element(name, click_by_js = false) def choose_element(name, click_by_js = false, visibility = false)
if find_element(name, visible: false).checked? if find_element(name, visible: visibility).checked?
QA::Runtime::Logger.debug("#{name} is already selected") QA::Runtime::Logger.debug("#{name} is already selected")
return return
end end
retry_until(sleep_interval: 1) do retry_until(sleep_interval: 1) do
click_checkbox_or_radio(name, click_by_js) click_checkbox_or_radio(name, click_by_js, visibility)
selected = find_element(name, visible: false).checked? selected = find_element(name, visible: visibility).checked?
QA::Runtime::Logger.debug(selected ? "#{name} was selected" : "#{name} was not selected") QA::Runtime::Logger.debug(selected ? "#{name} was selected" : "#{name} was not selected")
selected selected
end end
wait_for_requests
end end
# Use this to simulate moving the pointer to an element's coordinate # Use this to simulate moving the pointer to an element's coordinate
@ -424,8 +425,8 @@ module QA
private private
def click_checkbox_or_radio(name, click_by_js) def click_checkbox_or_radio(name, click_by_js, visibility)
box = find_element(name, visible: false) box = find_element(name, visible: visibility)
# Some checkboxes and radio buttons are hidden by their labels and cannot be clicked directly # 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 click_by_js ? page.execute_script("arguments[0].click();", box) : box.click
end end

View File

@ -19,7 +19,7 @@ module QA
end end
base.view 'app/views/shared/tokens/_scopes_form.html.haml' do 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 end
base.view 'app/views/shared/access_tokens/_created_container.html.haml' do base.view 'app/views/shared/access_tokens/_created_container.html.haml' do
@ -36,7 +36,7 @@ module QA
end end
def check_api def check_api
check_element(:api_radio) check_element(:api_checkbox)
end end
def click_create_token_button def click_create_token_button

View File

@ -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

View File

@ -4,23 +4,20 @@ module QA
module Page module Page
module Group module Group
class New < Page::Base class New < Page::Base
include Page::Component::VisibilitySetting
view 'app/views/shared/_group_form.html.haml' do view 'app/views/shared/_group_form.html.haml' do
element :group_path_field, 'text_field :path' # rubocop:disable QA/ElementWithPattern element :group_path_field
element :group_name_field, 'text_field :name' # rubocop:disable QA/ElementWithPattern element :group_name_field
end end
view 'app/views/groups/_new_group_fields.html.haml' do view 'app/views/groups/_new_group_fields.html.haml' do
element :create_group_button, "submit _('Create group')" # rubocop:disable QA/ElementWithPattern element :create_group_button, "submit _('Create group')" # rubocop:disable QA/ElementWithPattern
element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern
end end
def set_path(path) def set_path(path)
fill_in 'group_path', with: path fill_element(:group_path_field, path)
fill_in 'group_name', with: path fill_element(:group_name_field, path)
end
def set_visibility(visibility)
choose visibility
end end
def create def create

View File

@ -6,6 +6,7 @@ module QA
module Settings module Settings
class General < QA::Page::Base class General < QA::Page::Base
include ::QA::Page::Settings::Common include ::QA::Page::Settings::Common
include Page::Component::VisibilitySetting
view 'app/views/groups/edit.html.haml' do view 'app/views/groups/edit.html.haml' do
element :permission_lfs_2fa_content element :permission_lfs_2fa_content
@ -21,10 +22,6 @@ module QA
element :save_name_visibility_settings_button element :save_name_visibility_settings_button
end 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 view 'app/views/groups/settings/_lfs.html.haml' do
element :lfs_checkbox element :lfs_checkbox
end end
@ -56,10 +53,6 @@ module QA
find_element(:group_name_field).set name find_element(:group_name_field).set name
end end
def set_group_visibility(visibility)
find_element("#{visibility.downcase}_radio").click
end
def click_save_name_visibility_settings_button def click_save_name_visibility_settings_button
click_element(:save_name_visibility_settings_button) click_element(:save_name_visibility_settings_button)
end end

View File

@ -4,8 +4,9 @@ module QA
module Page module Page
module Project module Project
class New < Page::Base class New < Page::Base
include Page::Component::Select2
include Page::Component::Project::Templates include Page::Component::Project::Templates
include Page::Component::Select2
include Page::Component::VisibilitySetting
view 'app/views/projects/new.html.haml' do view 'app/views/projects/new.html.haml' do
element :project_create_from_template_tab element :project_create_from_template_tab
@ -59,10 +60,6 @@ module QA
click_element(:project_create_from_template_tab) click_element(:project_create_from_template_tab)
end end
def set_visibility(visibility)
choose visibility.capitalize
end
def click_github_link def click_github_link
click_link 'GitHub' click_link 'GitHub'
end end

View File

@ -12,7 +12,7 @@ module QA
end end
view 'app/views/projects/_merge_request_merge_method_settings.html.haml' do view 'app/views/projects/_merge_request_merge_method_settings.html.haml' do
element :merge_ff_radio_button element :merge_ff_radio
end end
view 'app/views/projects/_merge_request_merge_checks_settings.html.haml' do view 'app/views/projects/_merge_request_merge_checks_settings.html.haml' do
@ -24,7 +24,7 @@ module QA
end end
def enable_ff_only def enable_ff_only
click_element(:merge_ff_radio_button) choose_element(:merge_ff_radio)
click_save_changes click_save_changes
end end

View File

@ -36,7 +36,7 @@ module QA
end end
view 'app/assets/javascripts/ide/components/commit_sidebar/actions.vue' do 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 end
view 'app/assets/javascripts/ide/components/commit_sidebar/form.vue' do view 'app/assets/javascripts/ide/components/commit_sidebar/form.vue' do
@ -44,6 +44,10 @@ module QA
element :commit_button element :commit_button
end 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 view 'app/assets/javascripts/ide/components/repo_editor.vue' do
element :editor_container element :editor_container
end end
@ -216,7 +220,9 @@ module QA
# animation is still in process even when the buttons have the # animation is still in process even when the buttons have the
# expected visibility. # expected visibility.
commit_success = retry_until(sleep_interval: 5) do 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) click_element(:commit_button) if has_element?(:commit_button)
# If this is the first commit, the commit SHA only appears after reloading # If this is the first commit, the commit SHA only appears after reloading

View File

@ -8,6 +8,10 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
let(:sub_batch_size) { 1000 } let(:sub_batch_size) { 1000 }
let(:pause_ms) { 0 } let(:pause_ms) { 0 }
let(:helpers) do
ActiveRecord::Migration.new.extend(Gitlab::Database::MigrationHelpers)
end
before do before do
ActiveRecord::Base.connection.execute(<<~SQL) ActiveRecord::Base.connection.execute(<<~SQL)
CREATE TABLE #{table_name} CREATE TABLE #{table_name}
@ -15,8 +19,8 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
id integer NOT NULL, id integer NOT NULL,
name character varying, name character varying,
fk integer NOT NULL, fk integer NOT NULL,
id_convert_to_bigint bigint DEFAULT 0 NOT NULL, #{helpers.convert_to_bigint_column(:id)} bigint DEFAULT 0 NOT NULL,
fk_convert_to_bigint bigint DEFAULT 0 NOT NULL, #{helpers.convert_to_bigint_column(:fk)} bigint DEFAULT 0 NOT NULL,
name_convert_to_text text DEFAULT 'no name' name_convert_to_text text DEFAULT 'no name'
); );
SQL SQL
@ -41,18 +45,20 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
let(:migration_class) { described_class.name } let(:migration_class) { described_class.name }
it 'copies all primary keys in range' do 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 = #{temporary_column}").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(temporary_column => 0).pluck(:id)).to contain_exactly(11, 19)
expect(test_table.all.count).to eq(4) expect(test_table.all.count).to eq(4)
end end
it 'copies all foreign keys in range' do 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 = #{temporary_column}").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(temporary_column => 0).pluck(:id)).to contain_exactly(15, 19)
expect(test_table.all.count).to eq(4) expect(test_table.all.count).to eq(4)
end end
@ -68,18 +74,20 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
it 'copies multiple columns when given' do it 'copies multiple columns when given' do
columns_to_copy_from = %w[id fk] 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) 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 = #{id_tmp_column} AND fk = #{fk_tmp_column}").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_tmp_column => 0).where(fk_tmp_column => 0).pluck(:id)).to contain_exactly(19)
expect(test_table.all.count).to eq(4) expect(test_table.all.count).to eq(4)
end end
it 'raises error when number of source and target columns does not match' do it 'raises error when number of source and target columns does not match' do
columns_to_copy_from = %w[id fk] 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 expect do
subject.perform(10, 15, table_name, 'id', sub_batch_size, pause_ms, columns_to_copy_from, columns_to_copy_to) subject.perform(10, 15, table_name, 'id', sub_batch_size, pause_ms, columns_to_copy_from, columns_to_copy_to)

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::MigrationHelpers do RSpec.describe Gitlab::Database::MigrationHelpers do
include Database::TableSchemaHelpers include Database::TableSchemaHelpers
include Database::TriggerHelpers
let(:model) do let(:model) do
ActiveRecord::Migration.new.extend(described_class) ActiveRecord::Migration.new.extend(described_class)
@ -1702,10 +1703,16 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end end
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 describe '#initialize_conversion_of_integer_to_bigint' do
let(:table) { :test_table } let(:table) { :test_table }
let(:column) { :id } let(:column) { :id }
let(:tmp_column) { "#{column}_convert_to_bigint" } let(:tmp_column) { model.convert_to_bigint_column(column) }
before do before do
model.create_table table, id: false do |t| 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 context 'when multiple columns are given' do
it 'creates the correct columns and installs the trigger' do it 'creates the correct columns and installs the trigger' do
columns_to_convert = %i[id non_nullable_column nullable_column] columns_to_convert = %i[id non_nullable_column nullable_column]
temporary_columns = %w[ temporary_columns = columns_to_convert.map { |column| model.convert_to_bigint_column(column) }
id_convert_to_bigint
non_nullable_column_convert_to_bigint
nullable_column_convert_to_bigint
]
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[0], :bigint, default: 0, null: false)
expect(model).to receive(:add_column).with(table, temporary_columns[1], :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
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 describe '#backfill_conversion_of_integer_to_bigint' do
let(:table) { :_test_backfill_table } let(:table) { :_test_backfill_table }
let(:column) { :id } let(:column) { :id }
let(:tmp_column) { "#{column}_convert_to_bigint" } let(:tmp_column) { model.convert_to_bigint_column(column) }
before do before do
model.create_table table, id: false do |t| model.create_table table, id: false do |t|
@ -1872,14 +1920,14 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
interval: 120, interval: 120,
batch_size: 2, batch_size: 2,
sub_batch_size: 1, 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
end end
context 'when multiple columns are being converted' do context 'when multiple columns are being converted' do
let(:other_column) { :other_id } 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] } let(:columns) { [column, other_column] }
it 'creates the batched migration tracking record' do it 'creates the batched migration tracking record' do
@ -1905,6 +1953,54 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end end
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 describe '#index_exists_by_name?' do
it 'returns true if an index exists' do it 'returns true if an index exists' do
ActiveRecord::Base.connection.execute( ActiveRecord::Base.connection.execute(

View File

@ -5620,4 +5620,47 @@ RSpec.describe User do
end end
end 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 end

View File

@ -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