Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-06-09 21:08:13 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-06-09 21:08:13 +0300
commitf8b0e661f885d8d7df2414eaf4a465df0133a626 (patch)
tree3e10888fc084e5f67dee62c5ee25db5d1c77440d
parentd2675fa4de909714fcc6dc1bdd7bee9ce5e3af34 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml32
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue10
-rw-r--r--app/assets/javascripts/projects/compare/components/app.vue4
-rw-r--r--app/assets/javascripts/projects/compare/components/revision_card.vue10
-rw-r--r--app/assets/javascripts/runner/components/registration/registration_dropdown.vue14
-rw-r--r--app/assets/javascripts/runner/components/registration/registration_token.vue11
-rw-r--r--app/assets/javascripts/token_access/components/token_projects_table.vue6
-rw-r--r--app/components/pajamas/button_component.html.haml8
-rw-r--r--app/components/pajamas/button_component.rb114
-rw-r--r--app/models/user.rb3
-rw-r--r--app/views/admin/application_settings/general.html.haml4
-rw-r--r--app/views/projects/compare/index.html.haml2
-rw-r--r--app/views/projects/compare/show.html.haml2
-rw-r--r--app/views/shared/_broadcast_message.html.haml14
-rw-r--r--config/application.rb4
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/user/admin_area/moderate_users.md8
-rw-r--r--lib/bulk_imports/projects/pipelines/releases_pipeline.rb16
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/components/pajamas/button_component_spec.rb229
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js4
-rw-r--r--spec/frontend/projects/compare/components/revision_card_spec.js8
-rw-r--r--spec/frontend/runner/components/registration/registration_dropdown_spec.js7
-rw-r--r--spec/frontend/runner/components/registration/registration_token_spec.js1
-rw-r--r--spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb36
-rw-r--r--spec/models/user_spec.rb15
-rw-r--r--spec/requests/oauth/tokens_controller_spec.rb5
-rw-r--r--spec/requests/openid_connect_spec.rb2
-rw-r--r--spec/workers/users/deactivate_dormant_users_worker_spec.rb9
30 files changed, 503 insertions, 82 deletions
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 463d110b274..87af5fd5ca5 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -52,7 +52,6 @@ qa:nightly-auto-quarantine-dequarantine:
- bundle exec confiner -r .confiner/nightly.yml
allow_failure: true
-
qa:selectors-as-if-foss:
extends:
- qa:selectors
@@ -68,6 +67,30 @@ update-qa-cache:
script:
- echo "Cache has been updated and ready to be uploaded."
+populate-qa-tests-var:
+ extends:
+ - .qa:rules:package-and-qa
+ image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
+ stage: prepare
+ script:
+ - tooling/bin/qa/check_if_qa_only_spec_changes ${CHANGES_FILE} ${ONLY_QA_CHANGES_FILE}
+ - '[ -f $ONLY_QA_CHANGES_FILE ] && export QA_TESTS="`cat $ONLY_QA_CHANGES_FILE`"'
+ - 'echo "QA_TESTS=$QA_TESTS" >> qa_tests_var.env'
+ - 'echo "QA_TESTS: $QA_TESTS"'
+ artifacts:
+ expire_in: 2d
+ reports:
+ dotenv: qa_tests_var.env
+ paths:
+ - ${CHANGES_FILE}
+ - ${ONLY_QA_CHANGES_FILE}
+ - qa_tests_var.env
+ variables:
+ CHANGES_FILE: tmp/changed_files.txt
+ ONLY_QA_CHANGES_FILE: tmp/qa_only_changed_files.txt
+ needs:
+ - detect-tests
+
.package-and-qa-base:
image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
stage: qa
@@ -77,8 +100,6 @@ update-qa-cache:
- install_gitlab_gem
- tooling/bin/find_change_diffs ${CHANGES_DIFFS_DIR}
script:
- - tooling/bin/qa/check_if_qa_only_spec_changes ${CHANGES_FILE} ${ONLY_QA_CHANGES_FILE}
- - '[ -f $ONLY_QA_CHANGES_FILE ] && export QA_TESTS="`cat $ONLY_QA_CHANGES_FILE`"'
- 'echo "QA_TESTS: $QA_TESTS"'
- exit_code=0 && tooling/bin/qa/package_and_qa_check ${CHANGES_DIFFS_DIR} || exit_code=$?
- echo $exit_code
@@ -99,16 +120,13 @@ update-qa-cache:
artifacts: false
- job: build-assets-image
artifacts: false
+ - job: populate-qa-tests-var
- detect-tests
artifacts:
expire_in: 7d
paths:
- - ${CHANGES_FILE}
- - ${ONLY_QA_CHANGES_FILE}
- ${CHANGES_DIFFS_DIR}/*
variables:
- CHANGES_FILE: tmp/changed_files.txt
- ONLY_QA_CHANGES_FILE: tmp/qa_only_changed_files.txt
CHANGES_DIFFS_DIR: tmp/diffs
ALLURE_JOB_NAME: $CI_JOB_NAME
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue
index 43e31037c36..d7e55d36ff6 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue
@@ -23,8 +23,7 @@ import JobItem from './job_item.vue';
export default {
i18n: {
stage: __('Stage:'),
- loadingTextLineOne: __('Loading, please wait.'),
- loadingTextLineTwo: __('Cue dramatic background music...'),
+ loadingText: __('Loading, please wait.'),
},
dropdownPopperOpts: {
placement: 'bottom',
@@ -138,14 +137,11 @@ export default {
</template>
<div
v-if="isLoading"
- class="gl-display-flex gl-justify-content-center gl-p-3"
+ class="gl-display-flex gl-justify-content-center gl-p-2"
data-testid="pipeline-stage-loading-state"
>
<gl-loading-icon size="sm" class="gl-mr-3" />
- <div>
- <p class="gl-mb-0">{{ $options.i18n.loadingTextLineOne }}</p>
- <p class="gl-mb-0">{{ $options.i18n.loadingTextLineTwo }}</p>
- </div>
+ <p class="gl-mb-0">{{ $options.i18n.loadingText }}</p>
</div>
<ul
v-else
diff --git a/app/assets/javascripts/projects/compare/components/app.vue b/app/assets/javascripts/projects/compare/components/app.vue
index f2c1c843878..3945bed9649 100644
--- a/app/assets/javascripts/projects/compare/components/app.vue
+++ b/app/assets/javascripts/projects/compare/components/app.vue
@@ -104,7 +104,7 @@ export default {
@selectRevision="onSelectRevision"
/>
<div
- class="compare-ellipsis gl-display-flex gl-justify-content-center gl-align-items-center gl-my-4 gl-md-my-0"
+ class="compare-ellipsis gl-display-flex gl-justify-content-center gl-align-items-center gl-align-self-end gl-my-4 gl-md-my-0"
data-testid="ellipsis"
>
...
@@ -121,7 +121,7 @@ export default {
@selectRevision="onSelectRevision"
/>
</div>
- <div class="gl-mt-4">
+ <div class="gl-mt-6">
<gl-button category="primary" variant="confirm" @click="onSubmit">
{{ s__('CompareRevisions|Compare') }}
</gl-button>
diff --git a/app/assets/javascripts/projects/compare/components/revision_card.vue b/app/assets/javascripts/projects/compare/components/revision_card.vue
index 02a329221cc..d6ada24604d 100644
--- a/app/assets/javascripts/projects/compare/components/revision_card.vue
+++ b/app/assets/javascripts/projects/compare/components/revision_card.vue
@@ -1,5 +1,4 @@
<script>
-import { GlCard } from '@gitlab/ui';
import RepoDropdown from './repo_dropdown.vue';
import RevisionDropdown from './revision_dropdown.vue';
@@ -7,7 +6,6 @@ export default {
components: {
RepoDropdown,
RevisionDropdown,
- GlCard,
},
props: {
refsProjectPath: {
@@ -41,10 +39,10 @@ export default {
</script>
<template>
- <gl-card header-class="gl-py-2 gl-px-3 gl-font-weight-bold" body-class="gl-px-3">
- <template #header>
+ <div class="revision-card gl-flex-basis-half">
+ <h2 class="gl-font-size-h2">
{{ s__(`CompareRevisions|${revisionText}`) }}
- </template>
+ </h2>
<div class="gl-sm-display-flex gl-align-items-center">
<repo-dropdown
class="gl-sm-w-half"
@@ -61,5 +59,5 @@ export default {
v-on="$listeners"
/>
</div>
- </gl-card>
+ </div>
</template>
diff --git a/app/assets/javascripts/runner/components/registration/registration_dropdown.vue b/app/assets/javascripts/runner/components/registration/registration_dropdown.vue
index 26de4e35631..212ad5fa5a0 100644
--- a/app/assets/javascripts/runner/components/registration/registration_dropdown.vue
+++ b/app/assets/javascripts/runner/components/registration/registration_dropdown.vue
@@ -1,11 +1,5 @@
<script>
-import {
- GlFormGroup,
- GlDropdown,
- GlDropdownForm,
- GlDropdownItem,
- GlDropdownDivider,
-} from '@gitlab/ui';
+import { GlDropdown, GlDropdownForm, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
import { s__ } from '~/locale';
import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../../constants';
@@ -17,10 +11,8 @@ export default {
showInstallationInstructions: s__(
'Runners|Show runner installation and registration instructions',
),
- registrationToken: s__('Runners|Registration token'),
},
components: {
- GlFormGroup,
GlDropdown,
GlDropdownForm,
GlDropdownItem,
@@ -92,9 +84,7 @@ export default {
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown-form class="gl-p-4!">
- <gl-form-group class="gl-mb-0" :label="$options.i18n.registrationToken">
- <registration-token :value="currentRegistrationToken" />
- </gl-form-group>
+ <registration-token input-id="token-value" :value="currentRegistrationToken" />
</gl-dropdown-form>
<gl-dropdown-divider />
<registration-token-reset-dropdown-item :type="type" @tokenReset="onTokenReset" />
diff --git a/app/assets/javascripts/runner/components/registration/registration_token.vue b/app/assets/javascripts/runner/components/registration/registration_token.vue
index 3e0b610e5f5..6b4e6a929b7 100644
--- a/app/assets/javascripts/runner/components/registration/registration_token.vue
+++ b/app/assets/javascripts/runner/components/registration/registration_token.vue
@@ -6,7 +6,14 @@ export default {
components: {
InputCopyToggleVisibility,
},
+ i18n: {
+ registrationToken: s__('Runners|Registration token'),
+ },
props: {
+ inputId: {
+ type: String,
+ required: true,
+ },
value: {
type: String,
required: false,
@@ -16,7 +23,7 @@ export default {
computed: {
formInputGroupProps() {
return {
- name: 'token-value',
+ id: this.inputId,
};
},
},
@@ -33,6 +40,8 @@ export default {
<input-copy-toggle-visibility
class="gl-m-0"
:value="value"
+ :label="$options.i18n.registrationToken"
+ :label-for="inputId"
:copy-button-title="$options.I18N_COPY_BUTTON_TITLE"
:form-input-group-props="formInputGroupProps"
@copy="onCopy"
diff --git a/app/assets/javascripts/token_access/components/token_projects_table.vue b/app/assets/javascripts/token_access/components/token_projects_table.vue
index b6c9330c754..82ef3371d91 100644
--- a/app/assets/javascripts/token_access/components/token_projects_table.vue
+++ b/app/assets/javascripts/token_access/components/token_projects_table.vue
@@ -2,10 +2,6 @@
import { GlButton, GlTable } from '@gitlab/ui';
import { __, s__ } from '~/locale';
-const defaultTableClasses = {
- thClass: 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!',
-};
-
export default {
i18n: {
emptyText: s__('CI/CD|No projects have been added to the scope'),
@@ -15,14 +11,12 @@ export default {
key: 'project',
label: __('Projects that can be accessed'),
tdClass: 'gl-p-5!',
- ...defaultTableClasses,
columnClass: 'gl-w-85p',
},
{
key: 'actions',
label: '',
tdClass: 'gl-p-5! gl-text-right',
- ...defaultTableClasses,
columnClass: 'gl-w-15p',
},
],
diff --git a/app/components/pajamas/button_component.html.haml b/app/components/pajamas/button_component.html.haml
new file mode 100644
index 00000000000..8ce7d9e0315
--- /dev/null
+++ b/app/components/pajamas/button_component.html.haml
@@ -0,0 +1,8 @@
+= content_tag tag, {**@button_options, **base_attributes, class: button_class, href: @href, target: @target } do
+ - if @loading
+ = gl_loading_icon(inline: true, css_class: 'gl-button-icon gl-button-loading-indicator')
+ - if @icon && (!@loading || content)
+ = sprite_icon(@icon, css_class: "gl-icon gl-button-icon #{@icon_classes}")
+ - if content
+ %span.gl-button-text{ class: @button_text_classes }
+ = content
diff --git a/app/components/pajamas/button_component.rb b/app/components/pajamas/button_component.rb
new file mode 100644
index 00000000000..da00301516a
--- /dev/null
+++ b/app/components/pajamas/button_component.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+module Pajamas
+ class ButtonComponent < Pajamas::Component
+ # @param [Symbol] category
+ # @param [Symbol] variant
+ # @param [Symbol] size
+ # @param [Boolean] disabled
+ # @param [Boolean] loading
+ # @param [Boolean] block
+ # @param [Boolean] selected
+ # @param [String] icon
+ # @param [String] href
+ # @param [String] target
+ # @param [Hash] button_options
+ # @param [String] button_text_classes
+ # @param [String] icon_classes
+ def initialize(
+ category: :primary,
+ variant: :default,
+ size: :medium,
+ disabled: false,
+ loading: false,
+ block: false,
+ selected: false,
+ icon: nil,
+ href: nil,
+ target: nil,
+ button_options: {},
+ button_text_classes: nil,
+ icon_classes: nil
+ )
+ @category = filter_attribute(category.to_sym, CATEGORY_OPTIONS)
+ @variant = filter_attribute(variant.to_sym, VARIANT_OPTIONS)
+ @size = filter_attribute(size.to_sym, SIZE_OPTIONS)
+ @disabled = disabled
+ @loading = loading
+ @block = block
+ @selected = selected
+ @icon = icon
+ @href = href
+ @target = filter_attribute(target, TARGET_OPTIONS)
+ @button_options = button_options
+ @button_text_classes = button_text_classes
+ @icon_classes = icon_classes
+ end
+
+ private
+
+ def button_class
+ classes = ['gl-button btn']
+ classes.push('disabled') if @disabled || @loading
+ classes.push('selected') if @selected
+ classes.push('btn-block') if @block
+ classes.push('btn-icon') if @icon && !content
+
+ classes.push(SIZE_CLASSES[@size])
+
+ classes.push(VARIANT_CLASSES[@variant])
+
+ unless NON_CATEGORY_VARIANTS.include?(@variant) || @category == :primary
+ classes.push(VARIANT_CLASSES[@variant] + '-' + CATEGORY_CLASSES[@category])
+ end
+
+ classes.push(@button_options[:class])
+
+ classes.join(' ')
+ end
+
+ CATEGORY_OPTIONS = [:primary, :secondary, :tertiary].freeze
+ VARIANT_OPTIONS = [:default, :confirm, :danger, :dashed, :link, :reset].freeze
+ SIZE_OPTIONS = [:small, :medium].freeze
+ TARGET_OPTIONS = %w[_self _blank _parent _top].freeze
+
+ CATEGORY_CLASSES = {
+ primary: '',
+ secondary: 'secondary',
+ tertiary: 'tertiary'
+ }.freeze
+
+ VARIANT_CLASSES = {
+ default: 'btn-default',
+ confirm: 'btn-confirm',
+ danger: 'btn-danger',
+ dashed: 'btn-dashed',
+ link: 'btn-link',
+ reset: 'btn-gl-reset'
+ }.freeze
+
+ NON_CATEGORY_VARIANTS = [:dashed, :link, :reset].freeze
+
+ SIZE_CLASSES = {
+ small: 'btn-sm',
+ medium: 'btn-md'
+ }.freeze
+
+ delegate :sprite_icon, to: :helpers
+ delegate :gl_loading_icon, to: :helpers
+
+ def tag
+ @href ? 'a' : 'button'
+ end
+
+ def base_attributes
+ attributes = {}
+
+ attributes['disabled'] = '' if @disabled || @loading
+ attributes['aria-disabled'] = true if @disabled || @loading
+ attributes['type'] = 'button' unless @href
+
+ attributes
+ end
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 51d312ed597..c86fb56795c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -90,6 +90,7 @@ class User < ApplicationRecord
include ForcedEmailConfirmation
MINIMUM_INACTIVE_DAYS = 90
+ MINIMUM_DAYS_CREATED = 7
# Override Devise::Models::Trackable#update_tracked_fields!
# to limit database writes to at most once every hour
@@ -479,7 +480,7 @@ class User < ApplicationRecord
scope :order_oldest_last_activity, -> { reorder(arel_table[:last_activity_on].asc.nulls_first) }
scope :by_id_and_login, ->(id, login) { where(id: id).where('username = LOWER(:login) OR email = LOWER(:login)', login: login) }
scope :dormant, -> { with_state(:active).human_or_service_user.where('last_activity_on <= ?', MINIMUM_INACTIVE_DAYS.day.ago.to_date) }
- scope :with_no_activity, -> { with_state(:active).human_or_service_user.where(last_activity_on: nil) }
+ scope :with_no_activity, -> { with_state(:active).human_or_service_user.where(last_activity_on: nil).where('created_at <= ?', MINIMUM_DAYS_CREATED.day.ago.to_date) }
scope :by_provider_and_extern_uid, ->(provider, extern_uid) { joins(:identities).merge(Identity.with_extern_uid(provider, extern_uid)) }
scope :by_ids_or_usernames, -> (ids, usernames) { where(username: usernames).or(where(id: ids)) }
scope :without_forbidden_states, -> { where.not(state: FORBIDDEN_SEARCH_STATES) }
diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml
index 76d4a00342f..9f1f29efeca 100644
--- a/app/views/admin/application_settings/general.html.haml
+++ b/app/views/admin/application_settings/general.html.haml
@@ -6,7 +6,7 @@
.settings-header
%h4
= _('Visibility and access controls')
- %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
+ = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Set default and restrict visibility levels. Configure import sources and git access protocol.')
@@ -118,4 +118,4 @@
= render 'admin/application_settings/eks'
= render 'admin/application_settings/floc'
= render_if_exists 'admin/application_settings/add_license'
-= render 'admin/application_settings/jira_connect_application_key' if Feature.enabled?(:jira_connect_oauth)
+= render 'admin/application_settings/jira_connect_application_key' if Feature.enabled?(:jira_connect_oauth, current_user)
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index 70a94c58a3b..b3590eea631 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -3,7 +3,7 @@
%h1.page-title.gl-font-size-h-display
= _("Compare Git revisions")
-.sub-header-block
+%div
- example_branch = capture do
%code.ref-name= @project.default_branch_or_main
- example_sha = capture do
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index cb2c2d488e8..a6be6695b75 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -17,7 +17,7 @@
paginate_diffs: true,
paginate_diffs_per_page: Projects::CompareController::COMMIT_DIFFS_PER_PAGE
- else
- .card.bg-light
+ .card.gl-bg-gray-50.gl-border-none.gl-p-2
.center
%h4
= s_("CompareBranches|There isn't anything to compare.")
diff --git a/app/views/shared/_broadcast_message.html.haml b/app/views/shared/_broadcast_message.html.haml
index ab6423e9ade..f7794677dc1 100644
--- a/app/views/shared/_broadcast_message.html.haml
+++ b/app/views/shared/_broadcast_message.html.haml
@@ -13,8 +13,11 @@
- else
= yield
- if dismissable && !preview
- %button.btn.gl-close-btn-color-inherit.gl-broadcast-message-dismiss.btn-default.btn-sm.gl-button.btn-default-tertiary.btn-icon.js-dismiss-current-broadcast-notification{ 'aria-label' => _('Close'), :type => 'button', data: { id: message.id, expire_date: message.ends_at.iso8601 } }
- = sprite_icon('close', size: 16, css_class: "gl-icon gl-mx-3! gl-text-white")
+ = render Pajamas::ButtonComponent.new(category: :tertiary,
+ icon: 'close',
+ size: :small,
+ button_options: { class: 'gl-close-btn-color-inherit gl-broadcast-message-dismiss js-dismiss-current-broadcast-notification', 'aria-label': _('Close'), data: { id: message.id, expire_date: message.ends_at.iso8601 } },
+ icon_classes: 'gl-mx-3! gl-text-white')
- else
- notification_class = "js-broadcast-notification-#{message.id}"
- notification_class << ' preview' if preview
@@ -25,5 +28,8 @@
- else
= yield
- if !preview
- %button.js-dismiss-current-broadcast-notification.btn.btn-link.gl-button{ 'aria-label' => _('Close'), :type => 'button', data: { id: message.id, expire_date: message.ends_at.iso8601 } }
- = sprite_icon('close', size: 16, css_class: "gl-icon gl-mx-3! gl-text-gray-700")
+ = render Pajamas::ButtonComponent.new(variant: :link,
+ icon: 'close',
+ size: :small,
+ button_options: { class: 'js-dismiss-current-broadcast-notification', 'aria-label': _('Close'), data: { id: message.id, expire_date: message.ends_at.iso8601 } },
+ icon_classes: 'gl-mx-3! gl-text-gray-700')
diff --git a/config/application.rb b/config/application.rb
index 10d0e175afd..ad76a6d8e7e 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -400,7 +400,7 @@ module Gitlab
resource oauth_path,
headers: %w(Authorization),
credentials: false,
- methods: %i(post)
+ methods: %i(post options)
end
end
@@ -411,7 +411,7 @@ module Gitlab
resource '/oauth/userinfo',
headers: %w(Authorization),
credentials: false,
- methods: %i(get head post)
+ methods: %i(get head post options)
end
%w(/oauth/discovery/keys /.well-known/openid-configuration /.well-known/webfinger).each do |openid_path|
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index f96611cf88b..9c14e129783 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -14659,7 +14659,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="pipelinesecurityreportfindingidentifiers"></a>`identifiers` | [`[VulnerabilityIdentifier!]!`](#vulnerabilityidentifier) | Identifiers of the vulnerability finding. |
| <a id="pipelinesecurityreportfindinglinks"></a>`links` | [`[VulnerabilityLink!]`](#vulnerabilitylink) | List of links associated with the vulnerability. |
| <a id="pipelinesecurityreportfindinglocation"></a>`location` | [`VulnerabilityLocation`](#vulnerabilitylocation) | Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability. |
-| <a id="pipelinesecurityreportfindingname"></a>`name` | [`String`](#string) | Name of the vulnerability finding. |
+| <a id="pipelinesecurityreportfindingname"></a>`name` **{warning-solid}** | [`String`](#string) | **Deprecated** in 15.1. Use `title`. |
| <a id="pipelinesecurityreportfindingproject"></a>`project` | [`Project`](#project) | Project on which the vulnerability finding was found. |
| <a id="pipelinesecurityreportfindingprojectfingerprint"></a>`projectFingerprint` | [`String`](#string) | Name of the vulnerability finding. |
| <a id="pipelinesecurityreportfindingreporttype"></a>`reportType` | [`VulnerabilityReportType`](#vulnerabilityreporttype) | Type of the security report that found the vulnerability finding. |
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 740b98ae315..cc2e57aac96 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -246,7 +246,7 @@ page](https://go.dev/dl).
# Remove former Go installation folder
sudo rm -rf /usr/local/go
-curl --remote-name --location --progress-bar "https://go.dev/dl/go1.17.10.linux-amd64.tar.gz""
+curl --remote-name --location --progress-bar "https://go.dev/dl/go1.17.10.linux-amd64.tar.gz"
echo '87fc728c9c731e2f74e4a999ef53cf07302d7ed3504b0839027bd9c10edaa3fd go1.17.10.linux-amd64.tar.gz' | shasum -a256 -c - && \
sudo tar -C /usr/local -xzf go1.17.10.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/{go,gofmt} /usr/local/bin/
diff --git a/doc/user/admin_area/moderate_users.md b/doc/user/admin_area/moderate_users.md
index d2e68a8329c..dc6ff96c31f 100644
--- a/doc/user/admin_area/moderate_users.md
+++ b/doc/user/admin_area/moderate_users.md
@@ -171,8 +171,12 @@ Users can also be deactivated using the [GitLab API](../../api/users.md#deactiva
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/320875) in GitLab 14.0.
-Administrators can enable automatic deactivation of users who have not signed in, or have no activity
-in the last 90 days. To do this:
+Administrators can enable automatic deactivation of users who either:
+
+- Were created more than a week ago and have not signed in.
+- Have no activity in the last 90 days.
+
+To do this:
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Settings > General**.
diff --git a/lib/bulk_imports/projects/pipelines/releases_pipeline.rb b/lib/bulk_imports/projects/pipelines/releases_pipeline.rb
index 8f9c6a5749f..c77e53b9aec 100644
--- a/lib/bulk_imports/projects/pipelines/releases_pipeline.rb
+++ b/lib/bulk_imports/projects/pipelines/releases_pipeline.rb
@@ -9,6 +9,22 @@ module BulkImports
relation_name 'releases'
extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
+
+ def after_run(_context)
+ super
+
+ portable.releases.find_each do |release|
+ create_release_evidence(release)
+ end
+ end
+
+ private
+
+ def create_release_evidence(release)
+ return if release.historical_release? || release.upcoming_release?
+
+ ::Releases::CreateEvidenceWorker.perform_async(release.id)
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 867b51e155f..fd4d25fd605 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -10933,9 +10933,6 @@ msgstr ""
msgid "CsvParser|Unable to auto-detect delimiter; defaulted to \",\""
msgstr ""
-msgid "Cue dramatic background music..."
-msgstr ""
-
msgid "Current"
msgstr ""
diff --git a/spec/components/pajamas/button_component_spec.rb b/spec/components/pajamas/button_component_spec.rb
new file mode 100644
index 00000000000..0009228e58c
--- /dev/null
+++ b/spec/components/pajamas/button_component_spec.rb
@@ -0,0 +1,229 @@
+# frozen_string_literal: true
+require "spec_helper"
+
+RSpec.describe Pajamas::ButtonComponent, type: :component do
+ subject do
+ described_class.new(**options)
+ end
+
+ let(:content) { "Button content" }
+ let(:options) { {} }
+
+ describe 'basic usage' do
+ before do
+ render_inline(subject) do |c|
+ content
+ end
+ end
+
+ it 'renders its content' do
+ expect(rendered_component).to have_text content
+ end
+
+ it 'adds default styling' do
+ expect(rendered_component).to have_css ".btn.btn-default.btn-md.gl-button"
+ end
+
+ describe 'button_options' do
+ let(:options) { { button_options: { id: 'baz', data: { foo: 'bar' } } } }
+
+ it 'are added to the button' do
+ expect(rendered_component).to have_css ".gl-button#baz[data-foo='bar']"
+ end
+
+ context 'with custom classes' do
+ let(:options) { { variant: :danger, category: :tertiary, button_options: { class: 'custom-class' } } }
+
+ it 'don\'t conflict with internal button_classes' do
+ expect(rendered_component).to have_css '.gl-button.btn-danger.btn-danger-tertiary.custom-class'
+ end
+ end
+ end
+
+ describe 'button_text_classes' do
+ let(:options) { { button_text_classes: 'custom-text-class' } }
+
+ it 'is added to the button text' do
+ expect(rendered_component).to have_css ".gl-button-text.custom-text-class"
+ end
+ end
+
+ describe 'disabled' do
+ context 'by default (false)' do
+ it 'does not have disabled styling and behavior' do
+ expect(rendered_component).not_to have_css ".disabled[disabled='disabled'][aria-disabled='true']"
+ end
+ end
+
+ context 'when set to true' do
+ let(:options) { { disabled: true } }
+
+ it 'has disabled styling and behavior' do
+ expect(rendered_component).to have_css ".disabled[disabled='disabled'][aria-disabled='true']"
+ end
+ end
+ end
+
+ describe 'loading' do
+ context 'by default (false)' do
+ it 'is not disabled' do
+ expect(rendered_component).not_to have_css ".disabled[disabled='disabled']"
+ end
+
+ it 'does not render a spinner' do
+ expect(rendered_component).not_to have_css ".gl-spinner[aria-label='Loading']"
+ end
+ end
+
+ context 'when set to true' do
+ let(:options) { { loading: true } }
+
+ it 'is disabled' do
+ expect(rendered_component).to have_css ".disabled[disabled='disabled']"
+ end
+
+ it 'renders a spinner' do
+ expect(rendered_component).to have_css ".gl-spinner[aria-label='Loading']"
+ end
+ end
+ end
+
+ describe 'block' do
+ context 'by default (false)' do
+ it 'is inline' do
+ expect(rendered_component).not_to have_css ".btn-block"
+ end
+ end
+
+ context 'when set to true' do
+ let(:options) { { block: true } }
+
+ it 'is block element' do
+ expect(rendered_component).to have_css ".btn-block"
+ end
+ end
+ end
+
+ describe 'selected' do
+ context 'by default (false)' do
+ it 'does not have selected styling and behavior' do
+ expect(rendered_component).not_to have_css ".selected"
+ end
+ end
+
+ context 'when set to true' do
+ let(:options) { { selected: true } }
+
+ it 'has selected styling and behavior' do
+ expect(rendered_component).to have_css ".selected"
+ end
+ end
+ end
+
+ describe 'category & variant' do
+ context 'with category variants' do
+ where(:variant) { [:default, :confirm, :danger] }
+
+ let(:options) { { variant: variant, category: :tertiary } }
+
+ with_them do
+ it 'renders the button in correct variant && category' do
+ expect(rendered_component).to have_css(".#{described_class::VARIANT_CLASSES[variant]}")
+ expect(rendered_component).to have_css(".#{described_class::VARIANT_CLASSES[variant]}-tertiary")
+ end
+ end
+ end
+
+ context 'with non-category variants' do
+ where(:variant) { [:dashed, :link, :reset] }
+
+ let(:options) { { variant: variant, category: :tertiary } }
+
+ with_them do
+ it 'renders the button in correct variant && category' do
+ expect(rendered_component).to have_css(".#{described_class::VARIANT_CLASSES[variant]}")
+ expect(rendered_component).not_to have_css(".#{described_class::VARIANT_CLASSES[variant]}-tertiary")
+ end
+ end
+ end
+
+ context 'with primary category' do
+ where(:variant) { [:default, :confirm, :danger] }
+
+ let(:options) { { variant: variant, category: :primary } }
+
+ with_them do
+ it 'renders the button in correct variant && category' do
+ expect(rendered_component).to have_css(".#{described_class::VARIANT_CLASSES[variant]}")
+ expect(rendered_component).not_to have_css(".#{described_class::VARIANT_CLASSES[variant]}-primary")
+ end
+ end
+ end
+ end
+
+ describe 'size' do
+ context 'by default (medium)' do
+ it 'applies medium class' do
+ expect(rendered_component).to have_css ".btn-md"
+ end
+ end
+
+ context 'when set to small' do
+ let(:options) { { size: :small } }
+
+ it "applies the small class to the button" do
+ expect(rendered_component).to have_css ".btn-sm"
+ end
+ end
+ end
+
+ describe 'icon' do
+ it 'has none by default' do
+ expect(rendered_component).not_to have_css ".gl-icon"
+ end
+
+ context 'with icon' do
+ let(:options) { { icon: 'star-o', icon_classes: 'custom-icon' } }
+
+ it 'renders an icon with custom CSS class' do
+ expect(rendered_component).to have_css "svg.gl-icon.gl-button-icon.custom-icon[data-testid='star-o-icon']"
+ expect(rendered_component).not_to have_css ".btn-icon"
+ end
+ end
+
+ context 'with icon only and no content' do
+ let(:content) { nil }
+ let(:options) { { icon: 'star-o' } }
+
+ it 'adds a "btn-icon" CSS class' do
+ expect(rendered_component).to have_css ".btn.btn-icon"
+ end
+ end
+
+ context 'with icon only and when loading' do
+ let(:content) { nil }
+ let(:options) { { icon: 'star-o', loading: true } }
+
+ it 'renders only a loading icon' do
+ expect(rendered_component).not_to have_css "svg.gl-icon.gl-button-icon.custom-icon[data-testid='star-o-icon']"
+ expect(rendered_component).to have_css ".gl-spinner[aria-label='Loading']"
+ end
+ end
+ end
+
+ describe 'link button' do
+ it 'renders a button tag with type="button" when "href" is not set' do
+ expect(rendered_component).to have_css "button[type='button']"
+ end
+
+ context 'when "href" is provided' do
+ let(:options) { { href: 'https://gitlab.com', target: '_blank' } }
+
+ it "renders a link instead of the button" do
+ expect(rendered_component).not_to have_css "button[type='button']"
+ expect(rendered_component).to have_css "a[href='https://gitlab.com'][target='_blank']"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js b/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js
index e57dd3508eb..1ff32b03344 100644
--- a/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js
@@ -78,10 +78,8 @@ describe('Pipelines stage component', () => {
});
it('displays loading state while jobs are being fetched', () => {
- const expectedLoadingText = `${PipelineStage.i18n.loadingTextLineOne} ${PipelineStage.i18n.loadingTextLineTwo}`;
-
expect(findLoadingState().exists()).toBe(true);
- expect(findLoadingState().text()).toBe(expectedLoadingText);
+ expect(findLoadingState().text()).toBe(PipelineStage.i18n.loadingText);
});
it('does not display loading state after jobs have been fetched', async () => {
diff --git a/spec/frontend/projects/compare/components/revision_card_spec.js b/spec/frontend/projects/compare/components/revision_card_spec.js
index 57906045337..a741393fcf3 100644
--- a/spec/frontend/projects/compare/components/revision_card_spec.js
+++ b/spec/frontend/projects/compare/components/revision_card_spec.js
@@ -1,4 +1,3 @@
-import { GlCard } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RepoDropdown from '~/projects/compare/components/repo_dropdown.vue';
import RevisionCard from '~/projects/compare/components/revision_card.vue';
@@ -14,9 +13,6 @@ describe('RepoDropdown component', () => {
...defaultProps,
...props,
},
- stubs: {
- GlCard,
- },
});
};
@@ -29,8 +25,10 @@ describe('RepoDropdown component', () => {
createComponent();
});
+ const RevisionCardWrapper = () => wrapper.find('.revision-card');
+
it('displays revision text', () => {
- expect(wrapper.find(GlCard).text()).toContain(defaultProps.revisionText);
+ expect(RevisionCardWrapper().text()).toContain(defaultProps.revisionText);
});
it('renders RepoDropdown component', () => {
diff --git a/spec/frontend/runner/components/registration/registration_dropdown_spec.js b/spec/frontend/runner/components/registration/registration_dropdown_spec.js
index e27963161d8..d3f38bc1d26 100644
--- a/spec/frontend/runner/components/registration/registration_dropdown_spec.js
+++ b/spec/frontend/runner/components/registration/registration_dropdown_spec.js
@@ -34,7 +34,8 @@ describe('RegistrationDropdown', () => {
const findRegistrationInstructionsDropdownItem = () => wrapper.findComponent(GlDropdownItem);
const findTokenDropdownItem = () => wrapper.findComponent(GlDropdownForm);
const findRegistrationToken = () => wrapper.findComponent(RegistrationToken);
- const findRegistrationTokenInput = () => wrapper.find('[name=token-value]');
+ const findRegistrationTokenInput = () =>
+ wrapper.findByLabelText(RegistrationToken.i18n.registrationToken);
const findTokenResetDropdownItem = () =>
wrapper.findComponent(RegistrationTokenResetDropdownItem);
const findModal = () => wrapper.findComponent(GlModal);
@@ -172,10 +173,10 @@ describe('RegistrationDropdown', () => {
await nextTick();
};
- it('Updates token in input', async () => {
+ it('Updates token input', async () => {
createComponent({}, mount);
- expect(findRegistrationTokenInput().props('value')).not.toBe(newToken);
+ expect(findRegistrationToken().props('value')).not.toBe(newToken);
await resetToken();
diff --git a/spec/frontend/runner/components/registration/registration_token_spec.js b/spec/frontend/runner/components/registration/registration_token_spec.js
index cb42c7c8493..ed1a698d36f 100644
--- a/spec/frontend/runner/components/registration/registration_token_spec.js
+++ b/spec/frontend/runner/components/registration/registration_token_spec.js
@@ -29,6 +29,7 @@ describe('RegistrationToken', () => {
wrapper = mountFn(RegistrationToken, {
propsData: {
value: mockToken,
+ inputId: 'token-value',
...props,
},
localVue,
diff --git a/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb
index 2279e66720e..85c25938fcc 100644
--- a/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb
@@ -45,11 +45,11 @@ RSpec.describe BulkImports::Projects::Pipelines::ReleasesPipeline do
allow_next_instance_of(BulkImports::Common::Extractors::NdjsonExtractor) do |extractor|
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [with_index]))
end
-
- pipeline.run
end
it 'imports release into destination project' do
+ pipeline.run
+
expect(project.releases.count).to eq(1)
imported_release = project.releases.last
@@ -78,6 +78,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ReleasesPipeline do
let(:attributes) {{ 'links' => [link] }}
it 'restores release links' do
+ pipeline.run
+
release_link = project.releases.last.links.first
aggregate_failures do
@@ -105,6 +107,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ReleasesPipeline do
let(:attributes) {{ 'milestone_releases' => [{ 'milestone' => milestone }] }}
it 'restores release milestone' do
+ pipeline.run
+
release_milestone = project.releases.last.milestone_releases.first.milestone
aggregate_failures do
@@ -118,5 +122,33 @@ RSpec.describe BulkImports::Projects::Pipelines::ReleasesPipeline do
end
end
end
+
+ context 'evidences' do
+ it 'creates release evidence' do
+ expect(::Releases::CreateEvidenceWorker).to receive(:perform_async)
+
+ pipeline.run
+ end
+
+ context 'when release is historical' do
+ let(:attributes) {{ 'released_at' => '2018-12-26T10:17:14.621Z' }}
+
+ it 'does not create release evidence' do
+ expect(::Releases::CreateEvidenceWorker).not_to receive(:perform_async)
+
+ pipeline.run
+ end
+ end
+
+ context 'when release is upcoming' do
+ let(:attributes) {{ 'released_at' => Time.zone.now + 30.days }}
+
+ it 'does not create release evidence' do
+ expect(::Releases::CreateEvidenceWorker).not_to receive(:perform_async)
+
+ pipeline.run
+ end
+ end
+ end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index d46bdfa65a8..dcf6b224009 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -6637,8 +6637,10 @@ RSpec.describe User do
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
+ active_not_that_long_ago = (described_class::MINIMUM_INACTIVE_DAYS - 1).days.ago.to_date
+ active_too_long_ago = described_class::MINIMUM_INACTIVE_DAYS.days.ago.to_date
+ created_recently = (described_class::MINIMUM_DAYS_CREATED - 1).days.ago.to_date
+ created_not_recently = described_class::MINIMUM_DAYS_CREATED.days.ago.to_date
create(:user, :deactivated, last_activity_on: nil)
@@ -6646,12 +6648,13 @@ RSpec.describe User do
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)
+ create(:user, last_activity_on: active_not_that_long_ago)
+ create(:user, last_activity_on: active_too_long_ago)
+ create(:user, last_activity_on: nil, created_at: created_recently)
- user_with_no_activity = create(:user, last_activity_on: nil)
+ old_enough_user_with_no_activity = create(:user, last_activity_on: nil, created_at: created_not_recently)
- expect(described_class.with_no_activity).to contain_exactly(user_with_no_activity)
+ expect(described_class.with_no_activity).to contain_exactly(old_enough_user_with_no_activity)
end
end
end
diff --git a/spec/requests/oauth/tokens_controller_spec.rb b/spec/requests/oauth/tokens_controller_spec.rb
index 1967d0ba8b1..3895304dbde 100644
--- a/spec/requests/oauth/tokens_controller_spec.rb
+++ b/spec/requests/oauth/tokens_controller_spec.rb
@@ -6,11 +6,12 @@ RSpec.describe Oauth::TokensController do
let(:cors_request_headers) { { 'Origin' => 'http://notgitlab.com' } }
let(:other_headers) { {} }
let(:headers) { cors_request_headers.merge(other_headers)}
+ let(:allowed_methods) { 'POST, OPTIONS' }
shared_examples 'cross-origin POST request' do
it 'allows cross-origin requests' do
expect(response.headers['Access-Control-Allow-Origin']).to eq '*'
- expect(response.headers['Access-Control-Allow-Methods']).to eq 'POST'
+ expect(response.headers['Access-Control-Allow-Methods']).to eq allowed_methods
expect(response.headers['Access-Control-Allow-Headers']).to be_nil
expect(response.headers['Access-Control-Allow-Credentials']).to be_nil
end
@@ -23,7 +24,7 @@ RSpec.describe Oauth::TokensController do
it 'allows cross-origin requests' do
expect(response.headers['Access-Control-Allow-Origin']).to eq '*'
- expect(response.headers['Access-Control-Allow-Methods']).to eq 'POST'
+ expect(response.headers['Access-Control-Allow-Methods']).to eq allowed_methods
expect(response.headers['Access-Control-Allow-Headers']).to eq 'Authorization'
expect(response.headers['Access-Control-Allow-Credentials']).to be_nil
end
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index 70a310ba0d5..c647fee1564 100644
--- a/spec/requests/openid_connect_spec.rb
+++ b/spec/requests/openid_connect_spec.rb
@@ -98,7 +98,7 @@ RSpec.describe 'OpenID Connect requests' do
shared_examples 'cross-origin GET and POST request' do
it 'allows cross-origin request' do
expect(response.headers['Access-Control-Allow-Origin']).to eq '*'
- expect(response.headers['Access-Control-Allow-Methods']).to eq 'GET, HEAD, POST'
+ expect(response.headers['Access-Control-Allow-Methods']).to eq 'GET, HEAD, POST, OPTIONS'
expect(response.headers['Access-Control-Allow-Headers']).to be_nil
expect(response.headers['Access-Control-Allow-Credentials']).to be_nil
end
diff --git a/spec/workers/users/deactivate_dormant_users_worker_spec.rb b/spec/workers/users/deactivate_dormant_users_worker_spec.rb
index 20cd55e19eb..297301c45e2 100644
--- a/spec/workers/users/deactivate_dormant_users_worker_spec.rb
+++ b/spec/workers/users/deactivate_dormant_users_worker_spec.rb
@@ -7,7 +7,8 @@ RSpec.describe Users::DeactivateDormantUsersWorker do
describe '#perform' do
let_it_be(:dormant) { create(:user, last_activity_on: User::MINIMUM_INACTIVE_DAYS.days.ago.to_date) }
- let_it_be(:inactive) { create(:user, last_activity_on: nil) }
+ let_it_be(:inactive) { create(:user, last_activity_on: nil, created_at: User::MINIMUM_DAYS_CREATED.days.ago.to_date) }
+ let_it_be(:inactive_recently_created) { create(:user, last_activity_on: nil, created_at: (User::MINIMUM_DAYS_CREATED - 1).days.ago.to_date) }
subject(:worker) { described_class.new }
@@ -71,6 +72,12 @@ RSpec.describe Users::DeactivateDormantUsersWorker do
expect(human_user.reload.state).to eq('blocked')
expect(service_user.reload.state).to eq('blocked')
end
+
+ it 'does not deactivate recently created users' do
+ worker.perform
+
+ expect(inactive_recently_created.reload.state).to eq('active')
+ end
end
context 'when automatic deactivation of dormant users is disabled' do