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/LeadingCommentSpace
- Layout/SpaceAroundOperators
- Layout/SpaceBeforeBlockBraces
- Layout/SpaceBeforeComma
- Layout/SpaceBeforeFirstArg
- Layout/SpaceInsideHashLiteralBraces

View File

@ -76,8 +76,9 @@ export default {
:value="$options.commitToCurrentBranch"
:disabled="!canPushToBranch"
:title="$options.currentBranchPermissionsTooltip"
data-qa-selector="commit_to_current_branch_radio_container"
>
<span class="ide-option-label" data-qa-selector="commit_to_current_branch_radio">
<span class="ide-option-label">
<gl-sprintf :message="s__('IDE|Commit to %{branchName} branch')">
<template #branchName>
<strong class="monospace">{{ currentBranchText }}</strong>

View File

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

View File

@ -337,6 +337,8 @@ class User < ApplicationRecord
end
event :deactivate do
# Any additional changes to this event should be also
# reflected in app/workers/users/deactivate_dormant_users_worker.rb
transition active: :deactivated
end
@ -418,6 +420,8 @@ class User < ApplicationRecord
scope :order_recent_last_activity, -> { reorder(Gitlab::Database.nulls_last_order('last_activity_on', 'DESC')) }
scope :order_oldest_last_activity, -> { reorder(Gitlab::Database.nulls_first_order('last_activity_on', 'ASC')) }
scope :by_id_and_login, ->(id, login) { where(id: id).where('username = LOWER(:login) OR email = LOWER(:login)', login: login) }
scope :dormant, -> { active.where('last_activity_on <= ?', MINIMUM_INACTIVE_DAYS.day.ago.to_date) }
scope :with_no_activity, -> { active.where(last_activity_on: nil) }
def preferred_language
read_attribute('preferred_language') ||

View File

@ -5,7 +5,7 @@
.col-sm-6
.bs-callout
%p
= (_"Runners are processes that pick up and execute CI/CD jobs for GitLab.")
= _("Runners are processes that pick up and execute CI/CD jobs for GitLab.")
%br
= _('You can register runners as separate users, on separate servers, and on your local machine. Register as many runners as you want.')
%br

View File

@ -22,7 +22,7 @@
= s_('ProjectSettings|When there is a merge conflict, the user is given the option to rebase.')
.form-check.mb-2
= form.radio_button :merge_method, :ff, class: "js-merge-method-radio form-check-input", data: { qa_selector: 'merge_ff_radio_button' }
= form.radio_button :merge_method, :ff, class: "js-merge-method-radio form-check-input", data: { qa_selector: 'merge_ff_radio' }
= label_tag :project_merge_method_ff, class: 'form-check-label' do
= s_('ProjectSettings|Fast-forward merge')
.text-secondary

View File

@ -6,7 +6,7 @@
.form-group.group-name-holder.col-sm-12
= f.label :name, class: 'label-bold' do
= _("Group name")
= f.text_field :name, placeholder: _('My Awesome Group'), class: 'js-autofill-group-name form-control input-lg',
= f.text_field :name, placeholder: _('My Awesome Group'), class: 'js-autofill-group-name form-control input-lg', data: { qa_selector: 'group_name_field' },
required: true,
title: _('Please fill in a descriptive name for your group.'),
autofocus: true
@ -22,7 +22,7 @@
- if parent
%strong= parent.full_path + '/'
= f.hidden_field :parent_id
= f.text_field :path, placeholder: _('my-awesome-group'), class: 'form-control js-validate-group-path js-autofill-group-path',
= f.text_field :path, placeholder: _('my-awesome-group'), class: 'form-control js-validate-group-path js-autofill-group-path', data: { qa_selector: 'group_path_field' },
autofocus: local_assigns[:autofocus] || false, required: true,
pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS,
title: _('Please choose a group URL with no special characters.'),

View File

@ -4,6 +4,6 @@
- scopes.each do |scope|
%fieldset.form-group.form-check
= check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}", class: "form-check-input qa-#{scope}-radio"
= check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}", class: "form-check-input", data: { qa_selector: "#{scope}_checkbox" }
= label_tag "#{prefix}_scopes_#{scope}", scope, class: 'label-bold form-check-label'
.text-secondary= t scope, scope: scope_description(prefix)

View File

@ -515,6 +515,14 @@
:weight: 1
:idempotent:
:tags: []
- :name: cronjob:users_deactivate_dormant_users
:feature_category: :utilization
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:tags: []
- :name: cronjob:x509_issuer_crl_check
:feature_category: :source_code_management
:has_external_dependencies: true

View File

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

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']['cron'] ||= '0 1 * * *'
Settings.cron_jobs['ssh_keys_expiring_soon_notification_worker']['job_class'] = 'SshKeys::ExpiringSoonNotificationWorker'
Settings.cron_jobs['users_deactivate_dormant_users_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['users_deactivate_dormant_users_worker']['cron'] ||= '21,42 0-4 * * *'
Settings.cron_jobs['users_deactivate_dormant_users_worker']['job_class'] = 'Users::DeactivateDormantUsersWorker'
Gitlab.com do
Settings.cron_jobs['batched_background_migrations_worker'] ||= Settingslogic.new({})

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_unauthenticated_packages_api_enabled boolean DEFAULT false NOT NULL,
throttle_authenticated_packages_api_enabled boolean DEFAULT false NOT NULL,
deactivate_dormant_users boolean DEFAULT false NOT NULL,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)),
@ -24225,6 +24226,8 @@ CREATE INDEX index_users_on_feed_token ON users USING btree (feed_token);
CREATE INDEX index_users_on_group_view ON users USING btree (group_view);
CREATE INDEX index_users_on_id_and_last_activity_on_for_non_internal_active ON users USING btree (id, last_activity_on) WHERE (((state)::text = 'active'::text) AND ((user_type IS NULL) OR (user_type = ANY (ARRAY[NULL::integer, 6, 4]))));
CREATE INDEX index_users_on_incoming_email_token ON users USING btree (incoming_email_token);
CREATE INDEX index_users_on_managing_group_id ON users USING btree (managing_group_id);

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="labeledgenode"></a>`node` | [`Label`](#label) | The item at the end of the edge. |
#### `LfsObjectRegistryConnection`
The connection type for [`LfsObjectRegistry`](#lfsobjectregistry).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="lfsobjectregistryconnectionedges"></a>`edges` | [`[LfsObjectRegistryEdge]`](#lfsobjectregistryedge) | A list of edges. |
| <a id="lfsobjectregistryconnectionnodes"></a>`nodes` | [`[LfsObjectRegistry]`](#lfsobjectregistry) | A list of nodes. |
| <a id="lfsobjectregistryconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `LfsObjectRegistryEdge`
The edge type for [`LfsObjectRegistry`](#lfsobjectregistry).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="lfsobjectregistryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="lfsobjectregistryedgenode"></a>`node` | [`LfsObjectRegistry`](#lfsobjectregistry) | The item at the end of the edge. |
#### `LicenseHistoryEntryConnection`
The connection type for [`LicenseHistoryEntry`](#licensehistoryentry).
@ -8317,6 +8340,22 @@ four standard [pagination arguments](#connection-pagination-arguments):
| ---- | ---- | ----------- |
| <a id="geonodegroupwikirepositoryregistriesids"></a>`ids` | [`[ID!]`](#id) | Filters registries by their ID. |
##### `GeoNode.lfsObjectRegistries`
Find LFS object registries on this Geo node. Available only when feature flag `geo_lfs_object_replication` is enabled.
Returns [`LfsObjectRegistryConnection`](#lfsobjectregistryconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="geonodelfsobjectregistriesids"></a>`ids` | [`[ID!]`](#id) | Filters registries by their ID. |
##### `GeoNode.mergeRequestDiffRegistries`
Find merge request diff registries on this Geo node.
@ -9327,6 +9366,23 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="labeltitle"></a>`title` | [`String!`](#string) | Content of the label. |
| <a id="labelupdatedat"></a>`updatedAt` | [`Time!`](#time) | When this label was last updated. |
### `LfsObjectRegistry`
Represents the Geo sync and verification state of an LFS object.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="lfsobjectregistrycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp when the LfsObjectRegistry was created. |
| <a id="lfsobjectregistryid"></a>`id` | [`ID!`](#id) | ID of the LfsObjectRegistry. |
| <a id="lfsobjectregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the LfsObjectRegistry. |
| <a id="lfsobjectregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the LfsObjectRegistry. |
| <a id="lfsobjectregistrylfsobjectid"></a>`lfsObjectId` | [`ID!`](#id) | ID of the LFS object. |
| <a id="lfsobjectregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the LfsObjectRegistry should be resynced. |
| <a id="lfsobjectregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the LfsObjectRegistry. |
| <a id="lfsobjectregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the LfsObjectRegistry. |
### `LicenseHistoryEntry`
Represents an entry from the Cloud License history.

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:
- Use the exact wording as shown in the UI, including any capital letters as-is.
- Use bold text for navigation items and the char "greater than" (`>`) as a
separator. For example: `From your project, go to **Settings > CI/CD**`.
- If there are any expandable menus, make sure to mention that the user needs to
expand the tab to find the settings you're referring to. For example:
`From your group, go to **Settings > CI/CD** and expand **General pipelines**`.
- Use bold text for navigation items.
### Navigational elements
### What to call the menus
Use these terms when referring to the main GitLab user interface
elements:
@ -1130,6 +1126,19 @@ elements:
- **Right sidebar**: This is the navigation sidebar on the right of the user
interface, specific to the open issue, merge request, or epic.
### How to document the left sidebar
To be consistent, use this format when you refer to the left sidebar.
- Go to your project and select **Settings > CI/CD**.
- Go to your group and select **Settings > CI/CD**.
- Go to the Admin Area (**{admin}**) and select **Overview > Projects**.
For expandable menus, use this format:
1. Go to your group and select **Settings > CI/CD**.
1. Expand **General pipelines**.
## Images
Images, including screenshots, can help a reader better understand a concept.

View File

@ -43,7 +43,7 @@ The npm version is shown in the output:
### Install Yarn
As an alternative to npm, you can install Yarn in your local environment by following the
instructions at [yarnpkg.com](https://classic.yarnpkg.com/en/docs/install).
instructions at [classic.yarnpkg.com](https://classic.yarnpkg.com/en/docs/install).
When installation is complete, verify you can use Yarn in your terminal by
running:
@ -305,6 +305,46 @@ See the
[Publish npm packages to the GitLab Package Registry using semantic-release](../../../ci/examples/semantic-release.md)
step-by-step guide and demo project for a complete example.
## Configure the GitLab npm registry with Yarn 2
You can get started with Yarn 2 by following the documentation at
[https://yarnpkg.com/getting-started/install](https://yarnpkg.com/getting-started/install).
To publish and install with the project-level npm endpoint, set the following configuration in
`.yarnrc.yml`:
```yaml
npmScopes:
foo:
npmRegistryServer: "https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/"
npmPublishRegistry: "https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/"
npmRegistries:
//gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:
npmAlwaysAuth: true
npmAuthToken: "<your_token>"
```
For the instance-level npm endpoint, use this Yarn 2 configuration in `.yarnrc.yml`:
```yaml
npmScopes:
foo:
npmRegistryServer: "https://gitlab.example.com/api/v4/packages/npm/"
npmRegistries:
//gitlab.example.com/api/v4/packages/npm/:
npmAlwaysAuth: true
npmAuthToken: "<your_token>"
```
In this configuration:
- Replace `<your_token>` with your personal access token or deploy token.
- Replace `<your_project_id>` with your project's ID, which you can find on the project's home page.
- Replace `gitlab.example.com` with your domain name.
- Your scope is `foo`, without `@`.
## Publishing packages with the same name or version
You cannot publish a package if a package of the same name and version already exists.

View File

@ -905,6 +905,10 @@ module Gitlab
end
end
def convert_to_bigint_column(column)
"#{column}_convert_to_bigint"
end
# Initializes the conversion of a set of integer columns to bigint
#
# It can be used for converting both a Primary Key and any Foreign Keys
@ -948,7 +952,7 @@ module Gitlab
check_trigger_permissions!(table)
conversions = columns.to_h { |column| [column, "#{column}_convert_to_bigint"] }
conversions = columns.to_h { |column| [column, convert_to_bigint_column(column)] }
with_lock_retries do
conversions.each do |(source_column, temporary_name)|
@ -969,6 +973,20 @@ module Gitlab
end
end
# Reverts `initialize_conversion_of_integer_to_bigint`
#
# table - The name of the database table containing the columns
# columns - The name, or array of names, of the column(s) that we're converting to bigint.
def revert_initialize_conversion_of_integer_to_bigint(table, columns)
columns = Array.wrap(columns)
temporary_columns = columns.map { |column| convert_to_bigint_column(column) }
trigger_name = rename_trigger_name(table, columns, temporary_columns)
remove_rename_triggers_for_postgresql(table, trigger_name)
temporary_columns.each { |column| remove_column(table, column) }
end
# Backfills the new columns used in an integer-to-bigint conversion using background migrations.
#
# - This helper should be called from a post-deployment migration.
@ -1025,7 +1043,7 @@ module Gitlab
conversions = Array.wrap(columns).to_h do |column|
raise ArgumentError, "Column #{column} does not exist on #{table}" unless column_exists?(table, column)
temporary_name = "#{column}_convert_to_bigint"
temporary_name = convert_to_bigint_column(column)
raise ArgumentError, "Column #{temporary_name} does not exist on #{table}" unless column_exists?(table, temporary_name)
[column, temporary_name]
@ -1042,6 +1060,25 @@ module Gitlab
sub_batch_size: sub_batch_size)
end
# Reverts `backfill_conversion_of_integer_to_bigint`
#
# table - The name of the database table containing the column
# columns - The name, or an array of names, of the column(s) we want to convert to bigint.
# primary_key - The name of the primary key column (most often :id)
def revert_backfill_conversion_of_integer_to_bigint(table, columns, primary_key: :id)
columns = Array.wrap(columns)
conditions = ActiveRecord::Base.sanitize_sql([
'job_class_name = :job_class_name AND table_name = :table_name AND column_name = :column_name AND job_arguments = :job_arguments',
job_class_name: 'CopyColumnUsingBackgroundMigrationJob',
table_name: table,
column_name: primary_key,
job_arguments: [columns, columns.map { |column| convert_to_bigint_column(column) }].to_json
])
execute("DELETE FROM batched_background_migrations WHERE #{conditions}")
end
# Performs a concurrent column rename when using PostgreSQL.
def install_rename_triggers_for_postgresql(table, old, new, trigger_name: nil)
Gitlab::Database::UnidirectionalCopyTrigger.on_table(table).create(old, new, trigger_name: trigger_name)

View File

@ -14434,6 +14434,9 @@ msgstr ""
msgid "Geo|Connection timeout should be between 1-120"
msgstr ""
msgid "Geo|Consult Geo troubleshooting information"
msgstr ""
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
@ -14506,6 +14509,9 @@ msgstr ""
msgid "Geo|Learn more about Geo"
msgstr ""
msgid "Geo|Learn more about Geo node statuses"
msgstr ""
msgid "Geo|Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
@ -14521,6 +14527,9 @@ msgstr ""
msgid "Geo|Node name should be between 1 and 255 characters"
msgstr ""
msgid "Geo|Node's status was updated %{timeAgo}."
msgstr ""
msgid "Geo|Not synced yet"
msgstr ""
@ -14659,6 +14668,9 @@ msgstr ""
msgid "Geo|There are no %{replicable_type} to show"
msgstr ""
msgid "Geo|There was an error fetching the Geo Nodes"
msgstr ""
msgid "Geo|Tracking database entry will be removed. Are you sure?"
msgstr ""
@ -14680,6 +14692,9 @@ msgstr ""
msgid "Geo|Unknown state"
msgstr ""
msgid "Geo|Updated %{timeAgo}"
msgstr ""
msgid "Geo|Verification"
msgstr ""
@ -32085,9 +32100,6 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}"
msgstr ""
msgid "There was an error fetching the Geo Nodes"
msgstr ""
msgid "There was an error fetching the Geo Settings"
msgstr ""

View File

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

View File

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

View File

@ -19,7 +19,7 @@ module QA
end
base.view 'app/views/shared/tokens/_scopes_form.html.haml' do
element :api_radio, 'qa-#{scope}-radio' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
element :api_checkbox, '#{scope}_checkbox' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
end
base.view 'app/views/shared/access_tokens/_created_container.html.haml' do
@ -36,7 +36,7 @@ module QA
end
def check_api
check_element(:api_radio)
check_element(:api_checkbox)
end
def click_create_token_button

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

View File

@ -6,6 +6,7 @@ module QA
module Settings
class General < QA::Page::Base
include ::QA::Page::Settings::Common
include Page::Component::VisibilitySetting
view 'app/views/groups/edit.html.haml' do
element :permission_lfs_2fa_content
@ -21,10 +22,6 @@ module QA
element :save_name_visibility_settings_button
end
view 'app/views/shared/_visibility_radios.html.haml' do
element :internal_radio, 'qa_selector: "#{visibility_level_label(level).downcase}_radio"' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
end
view 'app/views/groups/settings/_lfs.html.haml' do
element :lfs_checkbox
end
@ -56,10 +53,6 @@ module QA
find_element(:group_name_field).set name
end
def set_group_visibility(visibility)
find_element("#{visibility.downcase}_radio").click
end
def click_save_name_visibility_settings_button
click_element(:save_name_visibility_settings_button)
end

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::MigrationHelpers do
include Database::TableSchemaHelpers
include Database::TriggerHelpers
let(:model) do
ActiveRecord::Migration.new.extend(described_class)
@ -1702,10 +1703,16 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
describe '#convert_to_bigint_column' do
it 'returns the name of the temporary column used to convert to bigint' do
expect(model.convert_to_bigint_column(:id)).to eq('id_convert_to_bigint')
end
end
describe '#initialize_conversion_of_integer_to_bigint' do
let(:table) { :test_table }
let(:column) { :id }
let(:tmp_column) { "#{column}_convert_to_bigint" }
let(:tmp_column) { model.convert_to_bigint_column(column) }
before do
model.create_table table, id: false do |t|
@ -1774,11 +1781,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
context 'when multiple columns are given' do
it 'creates the correct columns and installs the trigger' do
columns_to_convert = %i[id non_nullable_column nullable_column]
temporary_columns = %w[
id_convert_to_bigint
non_nullable_column_convert_to_bigint
nullable_column_convert_to_bigint
]
temporary_columns = columns_to_convert.map { |column| model.convert_to_bigint_column(column) }
expect(model).to receive(:add_column).with(table, temporary_columns[0], :bigint, default: 0, null: false)
expect(model).to receive(:add_column).with(table, temporary_columns[1], :bigint, default: 0, null: false)
@ -1791,10 +1794,55 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
describe '#revert_initialize_conversion_of_integer_to_bigint' do
let(:table) { :test_table }
before do
model.create_table table, id: false do |t|
t.integer :id, primary_key: true
t.integer :other_id
end
model.initialize_conversion_of_integer_to_bigint(table, columns)
end
context 'when single column is given' do
let(:columns) { :id }
it 'removes column, trigger, and function' do
temporary_column = model.convert_to_bigint_column(:id)
trigger_name = model.rename_trigger_name(table, :id, temporary_column)
model.revert_initialize_conversion_of_integer_to_bigint(table, columns)
expect(model.column_exists?(table, temporary_column)).to eq(false)
expect_trigger_not_to_exist(table, trigger_name)
expect_function_not_to_exist(trigger_name)
end
end
context 'when multiple columns are given' do
let(:columns) { [:id, :other_id] }
it 'removes column, trigger, and function' do
temporary_columns = columns.map { |column| model.convert_to_bigint_column(column) }
trigger_name = model.rename_trigger_name(table, columns, temporary_columns)
model.revert_initialize_conversion_of_integer_to_bigint(table, columns)
temporary_columns.each do |column|
expect(model.column_exists?(table, column)).to eq(false)
end
expect_trigger_not_to_exist(table, trigger_name)
expect_function_not_to_exist(trigger_name)
end
end
end
describe '#backfill_conversion_of_integer_to_bigint' do
let(:table) { :_test_backfill_table }
let(:column) { :id }
let(:tmp_column) { "#{column}_convert_to_bigint" }
let(:tmp_column) { model.convert_to_bigint_column(column) }
before do
model.create_table table, id: false do |t|
@ -1872,14 +1920,14 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
interval: 120,
batch_size: 2,
sub_batch_size: 1,
job_arguments: [[column.to_s], ["#{column}_convert_to_bigint"]]
job_arguments: [[column.to_s], [model.convert_to_bigint_column(column)]]
)
end
end
context 'when multiple columns are being converted' do
let(:other_column) { :other_id }
let(:other_tmp_column) { "#{other_column}_convert_to_bigint" }
let(:other_tmp_column) { model.convert_to_bigint_column(other_column) }
let(:columns) { [column, other_column] }
it 'creates the batched migration tracking record' do
@ -1905,6 +1953,54 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
describe '#revert_backfill_conversion_of_integer_to_bigint' do
let(:table) { :_test_backfill_table }
let(:primary_key) { :id }
before do
model.create_table table, id: false do |t|
t.integer primary_key, primary_key: true
t.text :message, null: false
t.integer :other_id
t.timestamps
end
model.initialize_conversion_of_integer_to_bigint(table, columns, primary_key: primary_key)
model.backfill_conversion_of_integer_to_bigint(table, columns, primary_key: primary_key)
end
context 'when a single column is being converted' do
let(:columns) { :id }
it 'deletes the batched migration tracking record' do
expect do
model.revert_backfill_conversion_of_integer_to_bigint(table, columns)
end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(-1)
end
end
context 'when a multiple columns are being converted' do
let(:columns) { [:id, :other_id] }
it 'deletes the batched migration tracking record' do
expect do
model.revert_backfill_conversion_of_integer_to_bigint(table, columns)
end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(-1)
end
end
context 'when primary key column has custom name' do
let(:primary_key) { :other_pk }
let(:columns) { :other_id }
it 'deletes the batched migration tracking record' do
expect do
model.revert_backfill_conversion_of_integer_to_bigint(table, columns, primary_key: primary_key)
end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(-1)
end
end
end
describe '#index_exists_by_name?' do
it 'returns true if an index exists' do
ActiveRecord::Base.connection.execute(

View File

@ -5620,4 +5620,47 @@ RSpec.describe User do
end
end
end
describe '.dormant' do
it 'returns dormant users' do
freeze_time do
not_that_long_ago = (described_class::MINIMUM_INACTIVE_DAYS - 1).days.ago.to_date
too_long_ago = described_class::MINIMUM_INACTIVE_DAYS.days.ago.to_date
create(:user, :deactivated, last_activity_on: too_long_ago)
User::INTERNAL_USER_TYPES.map do |user_type|
create(:user, state: :active, user_type: user_type, last_activity_on: too_long_ago)
end
create(:user, last_activity_on: not_that_long_ago)
dormant_user = create(:user, last_activity_on: too_long_ago)
expect(described_class.dormant).to contain_exactly(dormant_user)
end
end
end
describe '.with_no_activity' do
it 'returns users with no activity' do
freeze_time do
not_that_long_ago = (described_class::MINIMUM_INACTIVE_DAYS - 1).days.ago.to_date
too_long_ago = described_class::MINIMUM_INACTIVE_DAYS.days.ago.to_date
create(:user, :deactivated, last_activity_on: nil)
User::INTERNAL_USER_TYPES.map do |user_type|
create(:user, state: :active, user_type: user_type, last_activity_on: nil)
end
create(:user, last_activity_on: not_that_long_ago)
create(:user, last_activity_on: too_long_ago)
user_with_no_activity = create(:user, last_activity_on: nil)
expect(described_class.with_no_activity).to contain_exactly(user_with_no_activity)
end
end
end
end

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