diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-09 21:08:13 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-09 21:08:13 +0300 |
commit | f8b0e661f885d8d7df2414eaf4a465df0133a626 (patch) | |
tree | 3e10888fc084e5f67dee62c5ee25db5d1c77440d | |
parent | d2675fa4de909714fcc6dc1bdd7bee9ce5e3af34 (diff) |
Add latest changes from gitlab-org/gitlab@master
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 |