Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
bc62085601
commit
f6b349ed51
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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') ||
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.'),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Automate deactivation of dormant users for self-managed instances
|
||||||
|
merge_request: 57778
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -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({})
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
6a278c90b8c97fc2255528605ee6bf4547e37ac8c4c17979483ed9db562fa021
|
|
@ -0,0 +1 @@
|
||||||
|
454992d01fa140896ff2a9cea66fb855c9e659a5a7969ac9a3cb5a608de36161
|
|
@ -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);
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 ""
|
||||||
|
|
||||||
|
|
1
qa/qa.rb
1
qa/qa.rb
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue