Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-12-22 21:10:39 +00:00
parent e5f8220301
commit 6c68583a42
35 changed files with 514 additions and 186 deletions

View File

@ -155,6 +155,7 @@ variables:
GIT_STRATEGY: "clone"
GIT_SUBMODULE_STRATEGY: "none"
GET_SOURCES_ATTEMPTS: "3"
# CI_FETCH_REPO_GIT_STRATEGY: "none" is from artifacts. "clone" is from cloning
CI_FETCH_REPO_GIT_STRATEGY: "none"
DEBIAN_VERSION: "bullseye"
UBI_VERSION: "8.6"

View File

@ -67,7 +67,7 @@ include:
extends:
- .rails-job-base
- .base-artifacts
- .repo-from-artifacts # Comment this to clone instead of using artifacts
- .repo-from-artifacts
stage: test
variables:
RUBY_GC_MALLOC_LIMIT: 67108864

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
module Ci
class RunnerManagersFinder
def initialize(runner:, params:)
@runner = runner
@params = params
end
def execute
items = runner_managers
filter_by_status(items)
end
private
attr_reader :runner, :params
def runner_managers
::Ci::RunnerManager.for_runner(runner)
end
def filter_by_status(items)
status = params[:status]
return items if status.blank?
items.with_status(status)
end
end
end

View File

@ -88,7 +88,7 @@ module Ci
end
def filter_by_status!
filter_by!(:status_status, Ci::Runner::AVAILABLE_STATUSES)
@runners = @runners.with_status(@params[:status_status]) if @params[:status_status].present?
end
def filter_by_upgrade_status!
@ -104,7 +104,10 @@ module Ci
end
def filter_by_runner_type!
filter_by!(:type_type, Ci::Runner::AVAILABLE_TYPES)
runner_type = @params[:type_type]
return if runner_type.blank?
@runners = @runners.with_runner_type(runner_type)
end
def filter_by_tag_list!
@ -137,14 +140,6 @@ module Ci
def request_tag_list!
@runners = @runners.with_tags if !@params[:preload].present? || @params.dig(:preload, :tag_name)
end
def filter_by!(scope_name, available_scopes)
scope = @params[scope_name]
if scope.present? && available_scopes.include?(scope)
@runners = @runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend
end
end
end
end

View File

@ -14,6 +14,7 @@ module Ci
include Presentable
include EachBatch
include Ci::HasRunnerExecutor
include Ci::HasRunnerStatus
extend ::Gitlab::Utils::Override
@ -85,22 +86,22 @@ module Ci
before_save :ensure_token
scope :active, -> (value = true) { where(active: value) }
scope :active, ->(value = true) { where(active: value) }
scope :paused, -> { active(false) }
scope :online, -> { where(arel_table[:contacted_at].gt(online_contact_time_deadline)) }
scope :recent, -> do
timestamp = stale_deadline
where(arel_table[:created_at].gteq(timestamp).or(arel_table[:contacted_at].gteq(timestamp)))
end
scope :stale, -> do
timestamp = stale_deadline
stale_timestamp = stale_deadline
where(arel_table[:created_at].lteq(timestamp))
.where(arel_table[:contacted_at].eq(nil).or(arel_table[:contacted_at].lteq(timestamp)))
created_before_stale_deadline = arel_table[:created_at].lteq(stale_timestamp)
contacted_before_stale_deadline = arel_table[:contacted_at].lteq(stale_timestamp)
never_contacted = arel_table[:contacted_at].eq(nil)
where(created_before_stale_deadline).where(never_contacted.or(contacted_before_stale_deadline))
end
scope :offline, -> { where(arel_table[:contacted_at].lteq(online_contact_time_deadline)) }
scope :never_contacted, -> { where(contacted_at: nil) }
scope :ordered, -> { order(id: :desc) }
scope :with_recent_runner_queue, -> { where(arel_table[:contacted_at].gt(recent_queue_deadline)) }
@ -220,6 +221,11 @@ module Ci
validate :exactly_one_group, if: :group_type?
scope :with_version_prefix, ->(value) { joins(:runner_managers).merge(RunnerManager.with_version_prefix(value)) }
scope :with_runner_type, ->(runner_type) do
return all if AVAILABLE_TYPES.exclude?(runner_type.to_s)
where(runner_type: runner_type)
end
acts_as_taggable
@ -348,23 +354,6 @@ module Ci
description
end
def online?
contacted_at && contacted_at > self.class.online_contact_time_deadline
end
def stale?
return false unless created_at
[created_at, contacted_at].compact.max <= self.class.stale_deadline
end
def status
return :stale if stale?
return :never_contacted unless contacted_at
online? ? :online : :offline
end
# DEPRECATED
# TODO Remove in v5 in favor of `status` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/344648
def deprecated_rest_status

View File

@ -5,10 +5,13 @@ module Ci
include FromUnion
include RedisCacheable
include Ci::HasRunnerExecutor
include Ci::HasRunnerStatus
# For legacy reasons, the table name is ci_runner_machines in the database
self.table_name = 'ci_runner_machines'
AVAILABLE_STATUSES = %w[online offline never_contacted stale].freeze
# The `UPDATE_CONTACT_COLUMN_EVERY` defines how often the Runner Machine DB entry can be updated
UPDATE_CONTACT_COLUMN_EVERY = (40.minutes)..(55.minutes)
@ -36,13 +39,16 @@ module Ci
STALE_TIMEOUT = 7.days
scope :stale, -> do
created_some_time_ago = arel_table[:created_at].lteq(STALE_TIMEOUT.ago)
contacted_some_time_ago = arel_table[:contacted_at].lteq(STALE_TIMEOUT.ago)
stale_timestamp = stale_deadline
created_before_stale_deadline = arel_table[:created_at].lteq(stale_timestamp)
contacted_before_stale_deadline = arel_table[:contacted_at].lteq(stale_timestamp)
from_union(
where(contacted_at: nil),
where(contacted_some_time_ago),
remove_duplicates: false).where(created_some_time_ago)
never_contacted,
where(contacted_before_stale_deadline),
remove_duplicates: false
).where(created_before_stale_deadline)
end
scope :for_runner, ->(runner_id) do
@ -114,25 +120,8 @@ module Ci
end
end
def status
return :stale if stale?
return :never_contacted unless contacted_at
online? ? :online : :offline
end
private
def online?
contacted_at && contacted_at > self.class.online_contact_time_deadline
end
def stale?
return false unless created_at
[created_at, contacted_at].compact.max <= self.class.stale_deadline
end
def persist_cached_data?
# Use a random threshold to prevent beating DB updates.
contacted_at_max_age = Random.rand(UPDATE_CONTACT_COLUMN_EVERY)

View File

@ -0,0 +1,50 @@
# frozen_string_literal: true
module Ci
module HasRunnerStatus
extend ActiveSupport::Concern
included do
scope :offline, -> { where(arel_table[:contacted_at].lteq(online_contact_time_deadline)) }
scope :never_contacted, -> { where(contacted_at: nil) }
scope :online, -> { where(arel_table[:contacted_at].gt(online_contact_time_deadline)) }
scope :with_status, ->(status) do
return all if available_statuses.exclude?(status.to_s)
public_send(status) # rubocop:disable GitlabSecurity/PublicSend -- safe to call
end
end
class_methods do
def available_statuses
self::AVAILABLE_STATUSES
end
def online_contact_time_deadline
raise NotImplementedError
end
def stale_deadline
raise NotImplementedError
end
end
def status
return :stale if stale?
return :never_contacted unless contacted_at
online? ? :online : :offline
end
def online?
contacted_at && contacted_at > self.class.online_contact_time_deadline
end
def stale?
return false unless created_at
[created_at, contacted_at].compact.max <= self.class.stale_deadline
end
end
end

View File

@ -1,4 +1,4 @@
%table.table.gl-text-gray-500
%table.table.gl-text-gray-500.gl-w-full
%tr
%td.gl-p-5!
= s_('AdminArea|Users without a Group and Project')

View File

@ -8,7 +8,7 @@
%p.gl-font-weight-bold.gl-mt-8
= s_('AdminArea|Totals')
%table.gl-table.gl-text-gray-500
%table.gl-table.gl-text-gray-500.gl-w-full
= render_if_exists 'admin/dashboard/stats_active_users_row', users_statistics: @users_statistics
%tr.bg-gray-light.gl-text-gray-900

View File

@ -1,6 +1,6 @@
.js-blob-result.gl-mt-3.gl-mb-5{ data: { qa_selector: 'result_item_content' } }
.js-blob-result.gl-mt-3.gl-mb-5{ data: { testid: 'result-item-content' } }
.file-holder.file-holder-top-border
.js-file-title.file-title{ data: { qa_selector: 'file_title_content' } }
.js-file-title.file-title{ data: { testid: 'file-title-content' } }
= link_to blob_link, data: {track_action: 'click_text', track_label: 'blob_path', track_property: 'search_result'} do
= sprite_icon('document')
%strong
@ -8,7 +8,7 @@
= copy_file_path_button(path)
- if blob.data
- if blob.data.size > 0
.file-content.code.term{ data: { qa_selector: 'file_text_content' } }
.file-content.code.term{ data: { testid: 'file-text-content' } }
= render 'search/results/blob_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link, blame_link: blame_link, highlight_line: blob.highlight_line
- else
.file-content.code

View File

@ -3,7 +3,7 @@
#search-blob-content.file-content.code.js-syntax-highlight{ class: 'gl-py-3!' }
- if blob.present?
.blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight, qa_selector: 'file_content' } }
.blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight } }
- blob_highlight = blob.present.highlight_and_trim(trim_length: 1024, ellipsis_svg: sprite_icon('ellipsis_h', size: 12, css_class: "gl-text-gray-700"))
- blob_highlight.lines.each_with_index do |line, index|
- i = index + offset

View File

@ -1,5 +1,5 @@
- if show_auto_devops_implicitly_enabled_banner?(project, current_user)
= render Pajamas::AlertComponent.new(alert_options: { class: 'auto-devops-implicitly-enabled-banner', data: { qa_selector: 'auto_devops_banner_content' } },
= render Pajamas::AlertComponent.new(alert_options: { class: 'auto-devops-implicitly-enabled-banner' },
close_button_options: { class: 'hide-auto-devops-implicitly-enabled-banner',
data: { project_id: project.id }}) do |c|
- c.with_body do

View File

@ -16,7 +16,7 @@
%a.file-line-num.diff-line-num{ class: line_class, href: "#L#{i}", id: "L#{i}", 'data-line-number' => i }
= i
.blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight, qa_selector: 'file_content' } }
.blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight } }
%pre.code.highlight
%code
= highlighted_blob

View File

@ -6,7 +6,7 @@
= form.gitlab_ui_radio_component model_method, level,
"#{visibility_level_icon(level)} #{visibility_level_label(level)}".html_safe,
help_text: '<span class="option-description">%{visibility_level_description}</span><span class="option-disabled-reason"></span>'.html_safe % { visibility_level_description: visibility_level_description(level, form_model)},
radio_options: { checked: (selected_level == level), data: { track_label: "blank_project", track_action: "activate_form_input", track_property: "#{model_method}_#{level}", track_value: "", qa_selector: "#{visibility_level_label(level).downcase}_radio" } },
radio_options: { checked: (selected_level == level), data: { track_label: "blank_project", track_action: "activate_form_input", track_property: "#{model_method}_#{level}", track_value: "" } },
label_options: { class: 'js-visibility-level-radio' }

View File

@ -9,7 +9,7 @@
-# Note this is just for individual members. For groups please see shared/members/_group
%li.member.js-member.py-2.px-3.d-flex.flex-column{ class: [dom_class(member), ("flex-md-row" unless force_mobile_view)], id: dom_id(member), data: { qa_selector: 'member_row' } }
%li.member.js-member.py-2.px-3.d-flex.flex-column{ class: [dom_class(member), ("flex-md-row" unless force_mobile_view)], id: dom_id(member) }
%span.list-item-name.mb-2.m-md-0
- if user
= render Pajamas::AvatarComponent.new(user, size: 32, class: 'gl-mr-3 flex-shrink-0 flex-grow-0')

View File

@ -7,7 +7,7 @@
.user-info
.block-truncated
= link_to user.name, user_path(user), class: 'user js-user-link', data: { user_id: user.id, qa_selector: 'user_link', qa_username: user.username }
= link_to user.name, user_path(user), class: 'user js-user-link', data: { user_id: user.id, testid: 'user-link', qa_username: user.username }
.block-truncated
%span.gl-text-gray-900= user.to_reference

View File

@ -11,7 +11,7 @@
- c.with_body do
= gitlab_ui_form_for @hook, as: :hook, url: url, html: { class: 'js-webhook-form gl-new-card-add-form gl-m-3 gl-display-none js-toggle-content' } do |f|
= render partial: partial, locals: { form: f, hook: @hook }
= f.submit _('Add webhook'), pajamas_button: true, data: { qa_selector: "create_webhook_button" }
= f.submit _('Add webhook'), pajamas_button: true
= render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'js-webhook-edit-close gl-ml-2 js-toggle-button' }) do
= _('Cancel')
- if hooks.any?

View File

@ -15,7 +15,7 @@
- if can?(current_user, :create_wiki, @wiki)
- edit_sidebar_url = wiki_page_path(@wiki, Wiki::SIDEBAR, action: :edit)
- link_class = (editing && @page&.slug == Wiki::SIDEBAR) ? 'active' : ''
= link_to edit_sidebar_url, class: link_class, data: { qa_selector: 'edit_sidebar_link' } do
= link_to edit_sidebar_url, class: link_class do
= sprite_icon('pencil', css_class: 'gl-mr-2')
%span= _("Edit sidebar")

View File

@ -8,7 +8,7 @@
.wiki-page-header.top-area.gl-flex-direction-column.gl-lg-flex-direction-row
.gl-mt-5.gl-mb-3
.gl-display-flex.gl-justify-content-space-between
%h2.gl-mt-0.gl-mb-5{ data: { qa_selector: 'wiki_page_title', testid: 'wiki_page_title' } }= @page ? @page.human_title : _('Failed to retrieve page')
.js-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki-page-content' } }
%h2.gl-mt-0.gl-mb-5{ data: { testid: 'wiki-page-title' } }= @page ? @page.human_title : _('Failed to retrieve page')
.js-wiki-page-content.md.gl-pt-2{ data: { testid: 'wiki-page-content' } }
= _('The page could not be displayed because it timed out.')
= html_escape(_('You can view the source or %{linkStart}%{cloneIcon} clone the repository%{linkEnd}')) % { linkStart: "<a href=\"#{git_access_url}\">".html_safe, linkEnd: '</a>'.html_safe, cloneIcon: sprite_icon('download', css_class: 'gl-mr-2').html_safe }

View File

@ -35,6 +35,6 @@
.gl-display-flex.gl-justify-content-space-between
%h2.gl-mt-0.gl-mb-5{ data: { testid: 'wiki-page-title' } }= @page.human_title
.js-async-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki-page-content', tracking_context: wiki_page_tracking_context(@page).to_json, get_wiki_content_url: wiki_page_render_api_endpoint(@page) } }
.js-async-wiki-page-content.md.gl-pt-2{ data: { testid: 'wiki-page-content', tracking_context: wiki_page_tracking_context(@page).to_json, get_wiki_content_url: wiki_page_render_api_endpoint(@page) } }
= render 'shared/wikis/sidebar'

View File

@ -149,6 +149,7 @@ that are scoped to a single [configuration keyword](../../ci/yaml/index.md#job-k
|------------------|-------------|
| `.default-retry` | Allows a job to [retry](../../ci/yaml/index.md#retry) upon `unknown_failure`, `api_failure`, `runner_system_failure`, `job_execution_timeout`, or `stuck_or_timeout_failure`. |
| `.default-before_script` | Allows a job to use a default `before_script` definition suitable for Ruby/Rails tasks that may need a database running (for example, tests). |
| `.repo-from-artifacts` | Allows a job to fetch the repository from artifacts in `clone-gitlab-repo` instead of cloning. This should reduce GitLab.com Gitaly load and also slightly improve the speed because downloading from artifacts is faster than cloning. Note that this should be avoided to be used with jobs having `needs: []` because otherwise it'll start later and we normally want all jobs to start as soon as possible. Use this only on jobs which has other dependencies so that we don't wait longer than just cloning. Note that this behavior can be controlled via `CI_FETCH_REPO_GIT_STRATEGY`. See [Fetch repository via artifacts instead of cloning/fetching from Gitaly](performance.md#fetch-repository-via-artifacts-instead-of-cloningfetching-from-gitaly) for more details. |
| `.setup-test-env-cache` | Allows a job to use a default `cache` definition suitable for setting up test environment for subsequent Ruby/Rails tasks. |
| `.ruby-cache` | Allows a job to use a default `cache` definition suitable for Ruby tasks. |
| `.static-analysis-cache` | Allows a job to use a default `cache` definition suitable for static analysis tasks. |

View File

@ -29,6 +29,45 @@ This works well for the following reasons:
- We use [shallow clone](../../ci/pipelines/settings.md#limit-the-number-of-changes-fetched-during-clone) to avoid downloading the full Git
history for every job.
### Fetch repository via artifacts instead of cloning/fetching from Gitaly
Lately we see errors from Gitaly look like this: (see [the issue](https://gitlab.com/gitlab-org/gitlab/-/issues/435456))
```plaintext
fatal: remote error: GitLab is currently unable to handle this request due to load.
```
While GitLab.com uses [pack-objects cache](../../administration/gitaly/configure_gitaly.md#pack-objects-cache),
sometimes the load is still too heavy for Gitaly to handle, and
[thundering herds](https://gitlab.com/gitlab-org/gitlab/-/issues/423830) can
also be a concern that we have a lot of jobs cloning the repository around
the same time.
To mitigate and reduce loads for Gitaly, we changed some jobs to fetch the
repository from artifacts in a job instead of all cloning from Gitaly at once.
For now this applies to most of the RSpec jobs, which has the most concurrent
jobs in most pipelines. This also slightly improved the speed because fetching
from the artifacts is also slightly faster than cloning, at the cost of saving
more artifacts for each pipeline.
Based on the numbers on 2023-12-20 at [Fetch repo from artifacts for RSpec jobs](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140330),
the extra storage cost was about 280M for each pipeline, and we save 15 seconds
for each RSpec jobs.
We do not apply this to jobs having no other job dependencies because we don't
want to delay any jobs from starting.
This behavior can be controlled by variable `CI_FETCH_REPO_GIT_STRATEGY`:
- Set to `none` means jobs using `.repo-from-artifacts` fetch repository from
artifacts in job `clone-gitlab-repo` rather than cloning.
- Set to `clone` means jobs using `.repo-from-artifacts` clone repository
as usual. Job `clone-gitlab-repo` does not run in this case.
To disable it, set `CI_FETCH_REPO_GIT_STRATEGY` to `clone`. To enable it,
set `CI_FETCH_REPO_GIT_STRATEGY` to `none`.
## Caching strategy
1. All jobs must only pull caches by default.

View File

@ -2912,6 +2912,9 @@ msgstr ""
msgid "Add approvers"
msgstr ""
msgid "Add branch target"
msgstr ""
msgid "Add child epic to an epic"
msgstr ""
@ -3050,9 +3053,6 @@ msgstr ""
msgid "Add tag"
msgstr ""
msgid "Add target branch rule"
msgstr ""
msgid "Add text to the sign-in page. Markdown enabled."
msgstr ""
@ -6516,6 +6516,9 @@ msgstr ""
msgid "Are you sure you want to delete this SSH key?"
msgstr ""
msgid "Are you sure you want to delete this branch target?"
msgstr ""
msgid "Are you sure you want to delete this comment?"
msgstr ""
@ -6531,9 +6534,6 @@ msgstr ""
msgid "Are you sure you want to delete this pipeline? Doing so will expire all pipeline caches and delete all related objects, such as builds, logs, artifacts, and triggers. This action cannot be undone."
msgstr ""
msgid "Are you sure you want to delete this target branch rule?"
msgstr ""
msgid "Are you sure you want to deploy this environment?"
msgstr ""
@ -8576,6 +8576,9 @@ msgstr ""
msgid "Branch name"
msgstr ""
msgid "Branch name pattern"
msgstr ""
msgid "Branch name template"
msgstr ""
@ -8585,6 +8588,18 @@ msgstr ""
msgid "Branch rules"
msgstr ""
msgid "Branch target"
msgstr ""
msgid "Branch target created."
msgstr ""
msgid "Branch target deleted."
msgstr ""
msgid "Branch target does not exist"
msgstr ""
msgid "BranchRules|%{linkStart}Wildcards%{linkEnd} such as *-stable or production/ are supported"
msgstr ""
@ -14365,6 +14380,9 @@ msgstr ""
msgid "Create a merge request"
msgstr ""
msgid "Create a merge request branch target."
msgstr ""
msgid "Create a new %{codeStart}.gitlab-ci.yml%{codeEnd} file at the root of the repository to get started."
msgstr ""
@ -14524,9 +14542,6 @@ msgstr ""
msgid "Create requirement"
msgstr ""
msgid "Create rules for target branches in merge requests."
msgstr ""
msgid "Create service account"
msgstr ""
@ -20404,10 +20419,10 @@ msgstr ""
msgid "Failed to create wiki"
msgstr ""
msgid "Failed to delete custom emoji. Please try again."
msgid "Failed to delete branch target"
msgstr ""
msgid "Failed to delete target branch rule"
msgid "Failed to delete custom emoji. Please try again."
msgstr ""
msgid "Failed to deploy to"
@ -30182,6 +30197,9 @@ msgstr ""
msgid "Merge request author cannot push to target project"
msgstr ""
msgid "Merge request branch workflow"
msgstr ""
msgid "Merge request change summary"
msgstr ""
@ -39378,12 +39396,18 @@ msgstr ""
msgid "ProtectedEnvironment|Environment"
msgstr ""
msgid "ProtectedEnvironment|Environment '%{environment_name}' is already protected"
msgstr ""
msgid "ProtectedEnvironment|Environments protected upstream"
msgstr ""
msgid "ProtectedEnvironment|Failed to load details for this group."
msgstr ""
msgid "ProtectedEnvironment|Failed to protect the environment."
msgstr ""
msgid "ProtectedEnvironment|No environments in this project are protected."
msgstr ""
@ -41364,9 +41388,6 @@ msgstr ""
msgid "Ruby"
msgstr ""
msgid "Rule name"
msgstr ""
msgid "Rule name is already taken."
msgstr ""
@ -48223,21 +48244,6 @@ msgstr ""
msgid "Target branch"
msgstr ""
msgid "Target branch rule"
msgstr ""
msgid "Target branch rule created."
msgstr ""
msgid "Target branch rule deleted."
msgstr ""
msgid "Target branch rule does not exist"
msgstr ""
msgid "Target branch rules"
msgstr ""
msgid "Target branch: %{target_branch}"
msgstr ""
@ -49263,10 +49269,10 @@ msgstr ""
msgid "The vulnerability is no longer detected. Verify the vulnerability has been remediated before changing its status."
msgstr ""
msgid "There are currently no mirrored repositories."
msgid "There are currently no merge request branch targets"
msgstr ""
msgid "There are currently no target branch rules"
msgid "There are currently no mirrored repositories."
msgstr ""
msgid "There are no GPG keys associated with this account."
@ -56282,7 +56288,7 @@ msgstr ""
msgid "You have insufficient permissions to create organizations"
msgstr ""
msgid "You have insufficient permissions to delete a target branch rule"
msgid "You have insufficient permissions to delete a branch target"
msgstr ""
msgid "You have insufficient permissions to manage alerts for this project"

View File

@ -1,13 +0,0 @@
# frozen_string_literal: true
module QA
module Page
module Alert
class AutoDevopsAlert < Page::Base
view 'app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml' do
element :auto_devops_banner_content
end
end
end
end
end

View File

@ -6,14 +6,6 @@ module QA
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)
find('label', text: visibility.capitalize).click
end

View File

@ -5,9 +5,9 @@ module QA
module Search
class Results < QA::Page::Base
view 'app/views/search/results/_blob_data.html.haml' do
element :result_item_content
element :file_title_content
element :file_text_content
element 'result-item-content'
element 'file-title-content'
element 'file-text-content'
end
view 'app/views/shared/projects/_project.html.haml' do
@ -23,19 +23,19 @@ module QA
end
def has_project_in_search_result?(project_name)
has_element?(:result_item_content, text: project_name)
has_element?('result-item-content', text: project_name)
end
def has_file_in_project?(file_name, project_name)
within_element(:result_item_content, text: project_name) do
has_element?(:file_title_content, text: file_name)
within_element('result-item-content', text: project_name) do
has_element?('file-title-content', text: file_name)
end
end
def has_file_in_project_with_content?(file_text, file_path)
within_element(:result_item_content,
within_element('result-item-content',
text: file_path) do
has_element?(:file_text_content, text: file_text)
has_element?('file-text-content', text: file_text)
end
end

View File

@ -9,7 +9,7 @@ module QA
end
view 'app/views/shared/users/_user.html.haml' do
element :user_link
element 'user-link'
end
view 'app/views/users/_overview.html.haml' do
@ -25,7 +25,7 @@ module QA
end
def click_user_link(username)
click_element(:user_link, username: username)
click_element('user-link', username: username)
end
def has_activity?(activity)

View File

@ -6,13 +6,20 @@ module QA
include QA::Support::Helpers::Project
let(:group_access_token) { create(:group_access_token) }
let(:api_client) { Runtime::API::Client.new(:gitlab, personal_access_token: group_access_token.token) }
let(:api_client_with_group_token) do
Runtime::API::Client.new(:gitlab, personal_access_token: group_access_token.token)
end
let(:project) do
create(:project, name: 'project-for-group-access-token', group: group_access_token.group)
end
before do
wait_until_project_is_ready(project)
# Associating a group access token to a project requires a job to be processed in sidekiq
# We need to be sure that this has happened or else we may get flaky test failures
wait_until_token_associated_to_project(project, api_client_with_group_token)
end
it(
@ -21,7 +28,7 @@ module QA
) do
expect do
create(:file,
api_client: api_client,
api_client: api_client_with_group_token,
project: project,
branch: "new_branch_#{SecureRandom.hex(8)}")
rescue StandardError => e
@ -36,7 +43,7 @@ module QA
) do
expect do
create(:commit,
api_client: api_client,
api_client: api_client_with_group_token,
project: project,
branch: "new_branch_#{SecureRandom.hex(8)}",
start_branch: project.default_branch,

View File

@ -11,6 +11,9 @@ module QA
before do
wait_until_project_is_ready(project)
# Associating an access token to a project requires a job to be processed in sidekiq
# We need to be sure that this has happened or else we may get flaky test failures
wait_until_token_associated_to_project(project, user_api_client)
end
context 'for the same project' do

View File

@ -6,12 +6,23 @@ module QA
module Project
def wait_until_project_is_ready(project)
# Repository can take a short time to become ready after project is created
Support::Retrier.retry_on_exception(sleep_interval: 5) do
Support::Retrier.retry_on_exception(sleep_interval: 1, max_attempts: 60) do
create(:commit, project: project, commit_message: 'Add new file', actions: [
{ action: 'create', file_path: 'new_file', content: '# This is a new file' }
{ action: 'create', file_path: SecureRandom.hex(4), content: '# This is a new file' }
])
end
end
def wait_until_token_associated_to_project(project, api_client)
Support::Retrier.retry_on_exception(sleep_interval: 1, max_attempts: 60) do
create(
:commit,
project: project,
actions: [{ action: 'create', file_path: SecureRandom.hex(4) }],
api_client: api_client
)
end
end
end
end
end

View File

@ -0,0 +1,77 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::RunnerManagersFinder, '#execute', feature_category: :fleet_visibility do
subject(:runner_managers) { described_class.new(runner: runner, params: params).execute }
let_it_be(:runner) { create(:ci_runner) }
describe 'filter by status' do
before_all do
freeze_time
end
after :all do
unfreeze_time
end
let_it_be(:offline_runner_manager) { create(:ci_runner_machine, runner: runner, contacted_at: 2.hours.ago) }
let_it_be(:online_runner_manager) { create(:ci_runner_machine, runner: runner, contacted_at: 1.second.ago) }
let_it_be(:never_contacted_runner_manager) { create(:ci_runner_machine, runner: runner, contacted_at: nil) }
let_it_be(:stale_runner_manager) do
create(
:ci_runner_machine,
runner: runner,
created_at: Ci::RunnerManager.stale_deadline - 1.second,
contacted_at: nil
)
end
let(:params) { { status: status } }
context 'for offline' do
let(:status) { :offline }
it { is_expected.to contain_exactly(offline_runner_manager) }
end
context 'for online' do
let(:status) { :online }
it { is_expected.to contain_exactly(online_runner_manager) }
end
context 'for stale' do
let(:status) { :stale }
it { is_expected.to contain_exactly(stale_runner_manager) }
end
context 'for never_contacted' do
let(:status) { :never_contacted }
it { is_expected.to contain_exactly(never_contacted_runner_manager, stale_runner_manager) }
end
context 'for invalid status' do
let(:status) { :invalid_status }
it 'returns all runner managers' do
expect(runner_managers).to contain_exactly(
offline_runner_manager, online_runner_manager, never_contacted_runner_manager, stale_runner_manager
)
end
end
end
context 'without any filters' do
let(:params) { {} }
let_it_be(:runner_manager) { create(:ci_runner_machine, runner: runner) }
it 'returns all runner managers' do
expect(runner_managers).to contain_exactly(runner_manager)
end
end
end

View File

@ -112,7 +112,7 @@ RSpec.describe Ci::RunnersFinder, feature_category: :fleet_visibility do
context 'by status' do
Ci::Runner::AVAILABLE_STATUSES.each do |status|
it "calls the corresponding :#{status} scope on Ci::Runner" do
expect(Ci::Runner).to receive(status.to_sym).and_call_original
expect(Ci::Runner).to receive(:with_status).with(status).and_call_original
described_class.new(current_user: admin, params: { status_status: status }).execute
end
@ -134,10 +134,14 @@ RSpec.describe Ci::RunnersFinder, feature_category: :fleet_visibility do
end
context 'by runner type' do
it 'calls the corresponding scope on Ci::Runner' do
expect(Ci::Runner).to receive(:project_type).and_call_original
Ci::Runner.runner_types.each_key do |runner_type|
context "when runner type is #{runner_type}" do
it "calls the corresponding scope on Ci::Runner" do
expect(Ci::Runner).to receive(:with_runner_type).with(runner_type).and_call_original
described_class.new(current_user: admin, params: { type_type: 'project_type' }).execute
described_class.new(current_user: admin, params: { type_type: runner_type }).execute
end
end
end
end

View File

@ -41,20 +41,70 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
end
end
describe '.stale', :freeze_time do
subject { described_class.stale.ids }
describe 'status scopes' do
let_it_be(:runner) { create(:ci_runner, :instance) }
let!(:runner_manager1) { create(:ci_runner_machine, :stale) }
let!(:runner_manager2) { create(:ci_runner_machine, :stale, contacted_at: nil) }
let!(:runner_manager3) { create(:ci_runner_machine, created_at: 6.months.ago, contacted_at: Time.current) }
let!(:runner_manager4) { create(:ci_runner_machine, created_at: 5.days.ago) }
let!(:runner_manager5) do
create(:ci_runner_machine, created_at: (7.days - 1.second).ago, contacted_at: (7.days - 1.second).ago)
let_it_be(:offline_runner_manager) { create(:ci_runner_machine, runner: runner, contacted_at: 2.hours.ago) }
let_it_be(:online_runner_manager) { create(:ci_runner_machine, runner: runner, contacted_at: 1.second.ago) }
let_it_be(:never_contacted_runner_manager) { create(:ci_runner_machine, runner: runner, contacted_at: nil) }
describe '.online' do
subject(:runner_managers) { described_class.online }
it 'returns online runner managers' do
expect(runner_managers).to contain_exactly(online_runner_manager)
end
end
it 'returns stale runner managers' do
is_expected.to match_array([runner_manager1.id, runner_manager2.id])
describe '.offline' do
subject(:runner_managers) { described_class.offline }
it 'returns offline runner managers' do
expect(runner_managers).to contain_exactly(offline_runner_manager)
end
end
describe '.never_contacted' do
subject(:runner_managers) { described_class.never_contacted }
it 'returns never contacted runner managers' do
expect(runner_managers).to contain_exactly(never_contacted_runner_manager)
end
end
describe '.stale', :freeze_time do
subject { described_class.stale }
let!(:stale_runner_manager1) do
create(
:ci_runner_machine,
runner: runner,
created_at: described_class.stale_deadline - 1.second,
contacted_at: nil
)
end
let!(:stale_runner_manager2) do
create(
:ci_runner_machine,
runner: runner,
created_at: 8.days.ago,
contacted_at: described_class.stale_deadline - 1.second
)
end
it 'returns stale runner managers' do
is_expected.to contain_exactly(stale_runner_manager1, stale_runner_manager2)
end
end
include_examples 'runner with status scope'
end
describe '.available_statuses' do
subject { described_class.available_statuses }
it { is_expected.to eq(%w[online offline never_contacted stale]) }
end
describe '.online_contact_time_deadline', :freeze_time do

View File

@ -557,19 +557,6 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
end
end
describe '.stale', :freeze_time do
subject { described_class.stale }
let!(:runner1) { create(:ci_runner, :instance, created_at: 4.months.ago, contacted_at: 3.months.ago + 1.second) }
let!(:runner2) { create(:ci_runner, :instance, created_at: 4.months.ago, contacted_at: 3.months.ago) }
let!(:runner3) { create(:ci_runner, :instance, created_at: 3.months.ago, contacted_at: nil) }
let!(:runner4) { create(:ci_runner, :instance, created_at: 2.months.ago, contacted_at: nil) }
it 'returns stale runners' do
is_expected.to match_array([runner2, runner3])
end
end
describe '#stale?', :clean_gitlab_redis_cache, :freeze_time do
let(:runner) { build(:ci_runner, :instance) }
@ -632,15 +619,6 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
end
end
describe '.online', :freeze_time do
subject { described_class.online }
let!(:runner1) { create(:ci_runner, :instance, contacted_at: 2.hours.ago) }
let!(:runner2) { create(:ci_runner, :instance, contacted_at: 1.second.ago) }
it { is_expected.to match_array([runner2]) }
end
describe '#online?', :clean_gitlab_redis_cache, :freeze_time do
let(:runner) { build(:ci_runner, :instance) }
@ -715,15 +693,6 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
end
end
describe '.offline' do
subject { described_class.offline }
let!(:runner1) { create(:ci_runner, :instance, contacted_at: 2.hours.ago) }
let!(:runner2) { create(:ci_runner, :instance, contacted_at: 1.second.ago) }
it { is_expected.to eq([runner1]) }
end
describe '.with_running_builds' do
subject { described_class.with_running_builds }
@ -2126,4 +2095,102 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
end
end
end
describe 'status scopes' do
let_it_be(:online_runner) { create(:ci_runner, :instance, contacted_at: 1.second.ago) }
let_it_be(:offline_runner) { create(:ci_runner, :instance, contacted_at: 2.hours.ago) }
let_it_be(:never_contacted_runner) { create(:ci_runner, :instance, contacted_at: nil) }
describe '.online' do
subject(:runners) { described_class.online }
it 'returns online runners' do
expect(runners).to contain_exactly(online_runner)
end
end
describe '.offline' do
subject(:runners) { described_class.offline }
it 'returns offline runners' do
expect(runners).to contain_exactly(offline_runner)
end
end
describe '.never_contacted' do
subject(:runners) { described_class.never_contacted }
it 'returns never contacted runners' do
expect(runners).to contain_exactly(never_contacted_runner)
end
end
describe '.stale', :freeze_time do
subject { described_class.stale }
let!(:stale_runner1) do
create(:ci_runner, :instance, created_at: described_class.stale_deadline - 1.second, contacted_at: nil)
end
let!(:stale_runner2) do
create(:ci_runner, :instance, created_at: 4.months.ago, contacted_at: described_class.stale_deadline - 1.second)
end
it 'returns stale runners' do
is_expected.to contain_exactly(stale_runner1, stale_runner2)
end
end
include_examples 'runner with status scope'
end
describe '.available_statuses' do
subject { described_class.available_statuses }
it { is_expected.to eq(%w[active paused online offline never_contacted stale]) }
end
describe '.online_contact_time_deadline', :freeze_time do
subject { described_class.online_contact_time_deadline }
it { is_expected.to eq(2.hours.ago) }
end
describe '.stale_deadline', :freeze_time do
subject { described_class.stale_deadline }
it { is_expected.to eq(3.months.ago) }
end
describe '.with_runner_type' do
subject { described_class.with_runner_type(runner_type) }
let_it_be(:instance_runner) { create(:ci_runner, :instance) }
let_it_be(:group_runner) { create(:ci_runner, :group) }
let_it_be(:project_runner) { create(:ci_runner, :project) }
context 'with instance_type' do
let(:runner_type) { 'instance_type' }
it { is_expected.to contain_exactly(instance_runner) }
end
context 'with group_type' do
let(:runner_type) { 'group_type' }
it { is_expected.to contain_exactly(group_runner) }
end
context 'with project_type' do
let(:runner_type) { 'project_type' }
it { is_expected.to contain_exactly(project_runner) }
end
context 'with invalid runner type' do
let(:runner_type) { 'invalid runner type' }
it { is_expected.to contain_exactly(instance_runner, group_runner, project_runner) }
end
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
RSpec.shared_examples 'runner with status scope' do
describe '.with_status' do
subject(:scope) { described_class.with_status(status) }
described_class::AVAILABLE_STATUSES.each do |status|
context "with #{status} status" do
let(:status) { status }
it "calls corresponding :#{status} scope" do
expect(described_class).to receive(status.to_sym).and_call_original
scope
end
end
end
context 'with invalid status' do
let(:status) { :invalid_status }
it 'returns all records' do
expect(described_class).to receive(:all).at_least(:once).and_call_original
expect { scope }.not_to raise_error
end
end
end
end