Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-01-07 09:10:22 +00:00
parent 2475461aa2
commit d51d5adf94
34 changed files with 815 additions and 157 deletions

View File

@ -11,6 +11,7 @@ Please link to the respective test case in the testcases project
- [ ] Follow the end-to-end tests [style guide](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/style_guide.html) and [best practices](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/best_practices.html).
- [ ] Use the appropriate [RSpec metadata tag(s)](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/rspec_metadata_tests.html#rspec-metadata-for-end-to-end-tests).
- [ ] Ensure that a created resource is removed after test execution.
- [ ] Ensure that no [transient bugs](https://about.gitlab.com/handbook/engineering/quality/issue-triage/#transient-bugs) are hidden accidentally due to the usage of `waits` and `reloads`.
- [ ] Verify the tags to ensure it runs on the desired test environments.
- [ ] If this MR has a dependency on another MR, such as a GitLab QA MR, specify the order in which the MRs should be merged.
- [ ] (If applicable) Create a follow-up issue to document [the special setup](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/running_tests_that_require_special_setup.html) necessary to run the test: ISSUE_LINK

View File

@ -10,6 +10,7 @@ import initProjectPermissionsSettings from '../shared/permissions';
import initProjectDeleteButton from '~/projects/project_delete_button';
import UserCallout from '~/user_callout';
import initServiceDesk from '~/projects/settings_service_desk';
import mountSearchSettings from './mount_search_settings';
document.addEventListener('DOMContentLoaded', () => {
initFilePickers();
@ -30,4 +31,6 @@ document.addEventListener('DOMContentLoaded', () => {
'.js-general-settings-form, .js-mr-settings-form, .js-mr-approvals-form',
),
);
mountSearchSettings();
});

View File

@ -0,0 +1,12 @@
const mountSearchSettings = async () => {
const el = document.querySelector('.js-search-settings-app');
if (el) {
const { default: initSearch } = await import(
/* webpackChunkName: 'search_settings' */ '~/search_settings'
);
initSearch({ el });
}
};
export default mountSearchSettings;

View File

@ -0,0 +1,129 @@
<script>
import { GlSearchBoxByType } from '@gitlab/ui';
import { uniq } from 'lodash';
import { EXCLUDED_NODES, HIDE_CLASS, HIGHLIGHT_CLASS, TYPING_DELAY } from '../constants';
const findSettingsSection = (sectionSelector, node) => {
return node.parentElement.closest(sectionSelector);
};
const resetSections = ({ sectionSelector, expandSection, collapseSection }) => {
document.querySelectorAll(sectionSelector).forEach((section, index) => {
section.classList.remove(HIDE_CLASS);
if (index === 0) {
expandSection(section);
} else {
collapseSection(section);
}
});
};
const clearHighlights = () => {
document
.querySelectorAll(`.${HIGHLIGHT_CLASS}`)
.forEach((element) => element.classList.remove(HIGHLIGHT_CLASS));
};
const hideSectionsExcept = (sectionSelector, visibleSections) => {
Array.from(document.querySelectorAll(sectionSelector))
.filter((section) => !visibleSections.includes(section))
.forEach((section) => {
section.classList.add(HIDE_CLASS);
});
};
const highlightElements = (elements = []) => {
elements.forEach((element) => element.classList.add(HIGHLIGHT_CLASS));
};
const displayResults = ({ sectionSelector, expandSection }, matches) => {
const elements = matches.map((match) => match.parentElement);
const sections = uniq(elements.map((element) => findSettingsSection(sectionSelector, element)));
hideSectionsExcept(sectionSelector, sections);
sections.forEach(expandSection);
highlightElements(elements);
};
const clearResults = (params) => {
resetSections(params);
clearHighlights();
};
const includeNode = (node, lowerSearchTerm) =>
node.textContent.toLowerCase().includes(lowerSearchTerm) &&
EXCLUDED_NODES.every((excluded) => !node.parentElement.closest(excluded));
const search = (root, searchTerm) => {
const iterator = document.createNodeIterator(root, NodeFilter.SHOW_TEXT, {
acceptNode(node) {
return includeNode(node, searchTerm.toLowerCase())
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_REJECT;
},
});
const results = [];
for (let currentNode = iterator.nextNode(); currentNode; currentNode = iterator.nextNode()) {
results.push(currentNode);
}
return results;
};
export default {
components: {
GlSearchBoxByType,
},
props: {
searchRoot: {
type: Element,
required: true,
},
sectionSelector: {
type: String,
required: true,
},
},
data() {
return {
searchTerm: '',
};
},
methods: {
search(value) {
const displayOptions = {
sectionSelector: this.sectionSelector,
expandSection: this.expandSection,
collapseSection: this.collapseSection,
};
this.searchTerm = value;
clearResults(displayOptions);
if (value.length) {
displayResults(displayOptions, search(this.searchRoot, value));
}
},
expandSection(section) {
this.$emit('expand', section);
},
collapseSection(section) {
this.$emit('collapse', section);
},
},
TYPING_DELAY,
};
</script>
<template>
<div class="gl-mt-5">
<gl-search-box-by-type
:value="searchTerm"
:debounce="$options.TYPING_DELAY"
:placeholder="__('Search settings')"
@input="search"
/>
</div>
</template>

View File

@ -0,0 +1,11 @@
// We do not consider these nodes in the search index
export const EXCLUDED_NODES = ['OPTION'];
// Used to hide the sections that do not match * the search term
export const HIDE_CLASS = 'gl-display-none';
// used to highlight the text that matches the * search term
export const HIGHLIGHT_CLASS = 'gl-bg-orange-50';
// How many seconds to wait until the user * stops typing
export const TYPING_DELAY = 400;

View File

@ -0,0 +1,23 @@
import Vue from 'vue';
import $ from 'jquery';
import { expandSection, closeSection } from '~/settings_panels';
import SearchSettings from '~/search_settings/components/search_settings.vue';
const initSearch = ({ el }) =>
new Vue({
el,
render: (h) =>
h(SearchSettings, {
ref: 'searchSettings',
props: {
searchRoot: document.querySelector('#content-body'),
sectionSelector: 'section.settings',
},
on: {
collapse: (section) => closeSection($(section)),
expand: (section) => expandSection($(section)),
},
}),
});
export default initSearch;

View File

@ -1,7 +1,7 @@
import $ from 'jquery';
import { __ } from './locale';
function expandSection($section) {
export function expandSection($section) {
$section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Collapse'));
// eslint-disable-next-line @gitlab/no-global-event-off
$section.find('.settings-content').off('scroll.expandSection').scrollTop(0);
@ -13,7 +13,7 @@ function expandSection($section) {
}
}
function closeSection($section) {
export function closeSection($section) {
$section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Expand'));
$section.find('.settings-content').on('scroll.expandSection', () => expandSection($section));
$section.removeClass('expanded');
@ -24,7 +24,7 @@ function closeSection($section) {
}
}
function toggleSection($section) {
export function toggleSection($section) {
$section.removeClass('no-animate');
if ($section.hasClass('expanded')) {
closeSection($section);

View File

@ -27,7 +27,8 @@ module Ci
upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? },
refspecs: -> (build) { build.merge_request_ref? },
artifacts_exclude: -> (build) { build.supports_artifacts_exclude? },
multi_build_steps: -> (build) { build.multi_build_steps? }
multi_build_steps: -> (build) { build.multi_build_steps? },
return_exit_code: -> (build) { build.exit_codes_defined? }
}.freeze
DEFAULT_RETRIES = {
@ -1038,6 +1039,10 @@ module Ci
end
end
def exit_codes_defined?
options.dig(:allow_failure_criteria, :exit_codes).present?
end
protected
def run_status_commit_hooks!

View File

@ -5,34 +5,34 @@
.col-sm-6
.bs-callout
%p
= (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.")
= (_"Runners are processes that pick up and execute CI/CD jobs for GitLab.")
%br
= _('Runners can be placed on separate users, servers, even on your local machine.')
= _('You can register runners as separate users, on separate servers, and on your local machine. Register as many runners as you want.')
%br
%div
%span= _('Each Runner can be in one of the following states and/or belong to one of the following types:')
%span= _('Runners can be:')
%ul
%li
%span.badge.badge-success shared
\-
= _('Runner runs jobs from all unassigned projects')
= _('Runs jobs from all unassigned projects.')
%li
%span.badge.badge-success group
\-
= _('Runner runs jobs from all unassigned projects in its group')
= _('Runs jobs from all unassigned projects in its group.')
%li
%span.badge.badge-info specific
\-
= _('Runner runs jobs from assigned projects')
= _('Runs jobs from assigned projects.')
%li
%span.badge.badge-warning locked
\-
= _('Runner cannot be assigned to other projects')
= _('Cannot be assigned to other projects.')
%li
%span.badge.badge-danger paused
\-
= _('Runner will not receive any new jobs')
= _('Not available to run jobs.')
.col-sm-6
.bs-callout

View File

@ -15,17 +15,17 @@
- if @runner.instance_type?
.bs-callout.bs-callout-success
%h4 This Runner will process jobs from ALL UNASSIGNED projects
%h4= _('This runner processes jobs for all unassigned projects.')
%p
If you want Runners to build only specific projects, enable them in the table below.
Keep in mind that this is a one way transition.
= _('If you want a runner to build only specific projects, restrict the project in the table below. After you restrict a runner to a project, you cannot change it back to a shared runner.')
- elsif @runner.group_type?
.bs-callout.bs-callout-success
%h4 This runner will process jobs from all projects in its group and subgroups
%h4= _('This runner processes jobs for all projects in its group and subgroups.')
- else
.bs-callout.bs-callout-info
%h4 This Runner will process jobs only from ASSIGNED projects
%p You can't make this a shared Runner.
%h4= _('This runner processes jobs for assigned projects only.')
%p
= _('You cannot make this a shared runner.')
%hr
.gl-mb-6
@ -33,12 +33,12 @@
.row
.col-md-6
%h4 Restrict projects for this Runner
%h4= _('Restrict projects for this runner')
- if @runner.projects.any?
%table.table.assigned-projects
%thead
%tr
%th Assigned projects
%th= _('Assigned projects')
- @runner.runner_projects.each do |runner_project|
- project = runner_project.project
- if project
@ -55,7 +55,7 @@
%table.table.unassigned-projects
%thead
%tr
%th Project
%th= _('Project')
%th
%tr
@ -78,15 +78,15 @@
= paginate_without_count @projects
.col-md-6
%h4 Recent jobs served by this Runner
%h4= _('Recent jobs served by this runner')
%table.table.ci-table.runner-builds
%thead
%tr
%th Job
%th Status
%th Project
%th Commit
%th Finished at
%th= _('Job')
%th= _('Status')
%th= _('Project')
%th= _('Commit')
%th= _('Finished at')
- @builds.each do |build|
- project = build.project

View File

@ -3,8 +3,7 @@
- @content_class = "limit-container-width" unless fluid_layout
- expanded = expanded_by_default?
- if Feature.enabled?(:search_settings_in_page, @project, default_enabled: false)
= render "shared/search_settings"
= render "shared/search_settings"
%section.settings.general-settings.no-animate.expanded#js-general-settings
.settings-header

View File

@ -1,3 +1,2 @@
.search-box-by-type.gl-mt-5
= sprite_icon('search', css_class: 'gl-search-box-by-type-search-icon gl-icon s16')
%input#search-settings-input.gl-form-input.gl-w-full.gl-search-box-by-type-input{ type: 'search', placeholder: _('Search settings') }
- if Feature.enabled?(:search_settings_in_page, @project, default_enabled: false)
.js-search-settings-app

View File

@ -5,7 +5,7 @@
.col-sm-10
.form-check
= f.check_box :active, { class: 'form-check-input' }
%label.light{ for: :runner_active }= _("Paused Runners don't accept new jobs")
%label.light{ for: :runner_active }= _("Paused runners don't accept new jobs")
.form-group.row
= label :protected, _("Protected"), class: 'col-form-label col-sm-2'
.col-sm-10
@ -40,13 +40,13 @@
= _('Maximum job timeout')
.col-sm-10
= f.text_field :maximum_timeout_human_readable, class: 'form-control'
.form-text.text-muted= _('This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like "1 hour". Values without specification represent seconds.')
.form-text.text-muted= _('Enter the number of seconds, or other human-readable input, like "1 hour". This timeout takes precedence over lower timeouts set for the project.')
.form-group.row
= label_tag :tag_list, class: 'col-form-label col-sm-2' do
= _('Tags')
.col-sm-10
= f.text_field :tag_list, value: runner.tag_list.sort.join(', '), class: 'form-control'
.form-text.text-muted= _('You can set up jobs to only use Runners with specific tags. Separate tags with commas.')
.form-text.text-muted= _('You can configure jobs to use runners that are assigned specific tags. Separate tags with commas.')
- if local_assigns[:in_gitlab_com_admin_context]
.form-group.row
= label_tag :public_projects_minutes_cost_factor, class: 'col-form-label col-sm-2' do

View File

@ -0,0 +1,5 @@
---
title: Allow collaboration on merge requests across forks by default
merge_request: 49904
author: Jonston Chan @JonstonChan
type: changed

View File

@ -0,0 +1,5 @@
---
title: Updated UI text to make runner lowercase
merge_request: 50477
author:
type: other

View File

@ -0,0 +1,17 @@
class ChangeMrAllowMaintainerToPushDefault < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
change_column_default :merge_requests, :allow_maintainer_to_push, from: nil, to: true
end
end
def down
with_lock_retries do
change_column_default :merge_requests, :allow_maintainer_to_push, from: true, to: nil
end
end
end

View File

@ -0,0 +1 @@
58f2bd74adf8b4ef616c8f341053dbeaa8116430a0f4493cbf5f8456d7f4b907

View File

@ -14018,7 +14018,7 @@ CREATE TABLE merge_requests (
merge_jid character varying,
discussion_locked boolean,
latest_merge_request_diff_id integer,
allow_maintainer_to_push boolean,
allow_maintainer_to_push boolean DEFAULT true,
state_id smallint DEFAULT 1 NOT NULL,
rebase_jid character varying,
squash_commit_sha bytea,

View File

@ -23,10 +23,10 @@ of the merge request.
## Enabling commit edits from upstream members
The feature can only be enabled by users who already have push access to the
source project and only lasts while the merge request is open. Once enabled,
upstream members will also be able to retry the pipelines and jobs of the
merge request:
From [GitLab 13.7 onwards](https://gitlab.com/gitlab-org/gitlab/-/issues/23308),
this setting is enabled by default. It can be changed by users with Developer
permissions to the source project. Once enabled, upstream members will also be
able to retry the pipelines and jobs of the merge request:
1. While creating or editing a merge request, select the checkbox **Allow
commits from members who can merge to the target branch**.

View File

@ -2,6 +2,8 @@
require_relative 'teammate'
require_relative 'request_helper' unless defined?(Gitlab::Danger::RequestHelper)
require_relative 'weightage/reviewers'
require_relative 'weightage/maintainers'
module Gitlab
module Danger
@ -151,20 +153,14 @@ module Gitlab
%i[reviewer traintainer maintainer].map do |role|
spin_role_for_category(team, role, project, category)
end
hungry_reviewers = reviewers.select { |member| member.hungry }
hungry_traintainers = traintainers.select { |member| member.hungry }
# TODO: take CODEOWNERS into account?
# https://gitlab.com/gitlab-org/gitlab/issues/26723
random = new_random(mr_source_branch)
# Make hungry traintainers have 4x the chance to be picked as a reviewer
# Make traintainers have 3x the chance to be picked as a reviewer
# Make hungry reviewers have 2x the chance to be picked as a reviewer
weighted_reviewers = reviewers + hungry_reviewers + traintainers + traintainers + traintainers + hungry_traintainers
weighted_reviewers = Weightage::Reviewers.new(reviewers, traintainers).execute
weighted_maintainers = Weightage::Maintainers.new(maintainers).execute
reviewer = spin_for_person(weighted_reviewers, random: random, timezone_experiment: timezone_experiment)
maintainer = spin_for_person(maintainers, random: random, timezone_experiment: timezone_experiment)
maintainer = spin_for_person(weighted_maintainers, random: random, timezone_experiment: timezone_experiment)
Spin.new(category, reviewer, maintainer, false, timezone_experiment)
end

View File

@ -3,7 +3,7 @@
module Gitlab
module Danger
class Teammate
attr_reader :options, :username, :name, :role, :projects, :available, :hungry, :tz_offset_hours
attr_reader :options, :username, :name, :role, :projects, :available, :hungry, :reduced_capacity, :tz_offset_hours
# The options data are produced by https://gitlab.com/gitlab-org/gitlab-roulette/-/blob/master/lib/team_member.rb
def initialize(options = {})
@ -15,6 +15,7 @@ module Gitlab
@projects = options['projects']
@available = options['available']
@hungry = options['hungry']
@reduced_capacity = options['reduced_capacity']
@tz_offset_hours = options['tz_offset_hours']
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
module Gitlab
module Danger
module Weightage
CAPACITY_MULTIPLIER = 2 # change this number to change what it means to be a reduced capacity reviewer 1/this number
BASE_REVIEWER_WEIGHT = 1
end
end
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
require_relative '../weightage'
module Gitlab
module Danger
module Weightage
class Maintainers
def initialize(maintainers)
@maintainers = maintainers
end
def execute
maintainers.each_with_object([]) do |maintainer, weighted_maintainers|
add_weighted_reviewer(weighted_maintainers, maintainer, BASE_REVIEWER_WEIGHT)
end
end
private
attr_reader :maintainers
def add_weighted_reviewer(reviewers, reviewer, weight)
if reviewer.reduced_capacity
reviewers.fill(reviewer, reviewers.size, weight)
else
reviewers.fill(reviewer, reviewers.size, weight * CAPACITY_MULTIPLIER)
end
end
end
end
end
end

View File

@ -0,0 +1,65 @@
# frozen_string_literal: true
require_relative '../weightage'
module Gitlab
module Danger
module Weightage
# Weights after (current multiplier of 2)
#
# +------------------------------+--------------------------------+
# | reviewer type | weight(times in reviewer pool) |
# +------------------------------+--------------------------------+
# | reduced capacity reviewer | 1 |
# | reviewer | 2 |
# | hungry reviewer | 4 |
# | reduced capacity traintainer | 3 |
# | traintainer | 6 |
# | hungry traintainer | 8 |
# +------------------------------+--------------------------------+
#
class Reviewers
DEFAULT_REVIEWER_WEIGHT = CAPACITY_MULTIPLIER * BASE_REVIEWER_WEIGHT
TRAINTAINER_WEIGHT = 3
def initialize(reviewers, traintainers)
@reviewers = reviewers
@traintainers = traintainers
end
def execute
# TODO: take CODEOWNERS into account?
# https://gitlab.com/gitlab-org/gitlab/issues/26723
weighted_reviewers + weighted_traintainers
end
private
attr_reader :reviewers, :traintainers
def weighted_reviewers
reviewers.each_with_object([]) do |reviewer, total_reviewers|
add_weighted_reviewer(total_reviewers, reviewer, BASE_REVIEWER_WEIGHT)
end
end
def weighted_traintainers
traintainers.each_with_object([]) do |reviewer, total_traintainers|
add_weighted_reviewer(total_traintainers, reviewer, TRAINTAINER_WEIGHT)
end
end
def add_weighted_reviewer(reviewers, reviewer, weight)
if reviewer.reduced_capacity
reviewers.fill(reviewer, reviewers.size, weight)
elsif reviewer.hungry
reviewers.fill(reviewer, reviewers.size, weight * CAPACITY_MULTIPLIER + DEFAULT_REVIEWER_WEIGHT)
else
reviewers.fill(reviewer, reviewers.size, weight * CAPACITY_MULTIPLIER)
end
end
end
end
end
end

View File

@ -1205,9 +1205,6 @@ msgstr ""
msgid "A %{incident_docs_start}modified issue%{incident_docs_end} to guide the resolution of incidents."
msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
msgid "A .NET Core console application template, customizable for any .NET Core project"
msgstr ""
@ -3916,6 +3913,9 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
msgid "Assigned projects"
msgstr ""
msgid "Assigned to %{assigneeName}"
msgstr ""
@ -5086,6 +5086,9 @@ msgstr ""
msgid "Cancelling Preview"
msgstr ""
msgid "Cannot be assigned to other projects."
msgstr ""
msgid "Cannot be merged automatically"
msgstr ""
@ -10263,9 +10266,6 @@ msgstr ""
msgid "Dynamic Application Security Testing (DAST)"
msgstr ""
msgid "Each Runner can be in one of the following states and/or belong to one of the following types:"
msgstr ""
msgid "Each Runner can be in one of the following states:"
msgstr ""
@ -10791,6 +10791,9 @@ msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
msgid "Enter the number of seconds, or other human-readable input, like \"1 hour\". This timeout takes precedence over lower timeouts set for the project."
msgstr ""
msgid "Enter weights for storages for new repositories."
msgstr ""
@ -12422,6 +12425,9 @@ msgstr ""
msgid "Finished"
msgstr ""
msgid "Finished at"
msgstr ""
msgid "First Seen"
msgstr ""
@ -13085,9 +13091,6 @@ msgstr ""
msgid "GitLab Billing Team."
msgstr ""
msgid "GitLab Gold trial (optional)"
msgstr ""
msgid "GitLab Group Runners can execute code for all the projects in this group."
msgstr ""
@ -14334,9 +14337,6 @@ msgstr ""
msgid "How many days need to pass between marking entity for deletion and actual removing it."
msgstr ""
msgid "How many employees will use Gitlab?"
msgstr ""
msgid "How many replicas each Elasticsearch shard has."
msgstr ""
@ -14505,6 +14505,9 @@ msgstr ""
msgid "If you remove this license, GitLab will fall back on the previous license, if any."
msgstr ""
msgid "If you want a runner to build only specific projects, restrict the project in the table below. After you restrict a runner to a project, you cannot change it back to a shared runner."
msgstr ""
msgid "If you want to re-enable two-factor authentication, visit %{two_factor_link}"
msgstr ""
@ -19223,6 +19226,9 @@ msgstr ""
msgid "Not available for protected branches"
msgstr ""
msgid "Not available to run jobs."
msgstr ""
msgid "Not confidential"
msgstr ""
@ -20368,7 +20374,7 @@ msgstr ""
msgid "Pause replication"
msgstr ""
msgid "Paused Runners don't accept new jobs"
msgid "Paused runners don't accept new jobs"
msgstr ""
msgid "Pending"
@ -23080,6 +23086,9 @@ msgstr ""
msgid "Recent Searches Service is unavailable"
msgstr ""
msgid "Recent jobs served by this runner"
msgstr ""
msgid "Recent searches"
msgstr ""
@ -24033,6 +24042,9 @@ msgstr ""
msgid "Restrict membership by email domain"
msgstr ""
msgid "Restrict projects for this runner"
msgstr ""
msgid "Restricts sign-ups for email addresses that match the given regex. See the %{supported_syntax_link_start}supported syntax%{supported_syntax_link_end} for more information."
msgstr ""
@ -24187,21 +24199,9 @@ msgstr ""
msgid "Run untagged jobs"
msgstr ""
msgid "Runner cannot be assigned to other projects"
msgstr ""
msgid "Runner is %{status}, last contact was %{runner_contact} ago"
msgstr ""
msgid "Runner runs jobs from all unassigned projects"
msgstr ""
msgid "Runner runs jobs from all unassigned projects in its group"
msgstr ""
msgid "Runner runs jobs from assigned projects"
msgstr ""
msgid "Runner token"
msgstr ""
@ -24217,9 +24217,6 @@ msgstr ""
msgid "Runner was successfully updated."
msgstr ""
msgid "Runner will not receive any new jobs"
msgstr ""
msgid "Runners"
msgstr ""
@ -24229,13 +24226,16 @@ msgstr ""
msgid "Runners activated for this project"
msgstr ""
msgid "Runners are processes that pick up and execute CI/CD jobs for GitLab."
msgstr ""
msgid "Runners are processes that pick up and execute jobs for GitLab. Here you can register and see your Runners for this project."
msgstr ""
msgid "Runners can be placed on separate users, servers, and even on your local machine."
msgstr ""
msgid "Runners can be placed on separate users, servers, even on your local machine."
msgid "Runners can be:"
msgstr ""
msgid "Runners currently online: %{active_runners_count}"
@ -24313,6 +24313,15 @@ msgstr ""
msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
msgid "Runs jobs from all unassigned projects in its group."
msgstr ""
msgid "Runs jobs from all unassigned projects."
msgstr ""
msgid "Runs jobs from assigned projects."
msgstr ""
msgid "SAML"
msgstr ""
@ -28851,6 +28860,15 @@ msgstr ""
msgid "This repository was last checked %{last_check_timestamp}. The check passed."
msgstr ""
msgid "This runner processes jobs for all projects in its group and subgroups."
msgstr ""
msgid "This runner processes jobs for all unassigned projects."
msgstr ""
msgid "This runner processes jobs for assigned projects only."
msgstr ""
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@ -28866,9 +28884,6 @@ msgstr ""
msgid "This suggestion already matches its content."
msgstr ""
msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
@ -29673,12 +29688,39 @@ msgstr ""
msgid "Trials|You won't get a free trial right now but you can always resume this process by clicking on your avatar and choosing 'Start a free trial'"
msgstr ""
msgid "Trial|Company name"
msgstr ""
msgid "Trial|Continue using the basic features of GitLab for free."
msgstr ""
msgid "Trial|Country"
msgstr ""
msgid "Trial|Dismiss"
msgstr ""
msgid "Trial|GitLab Gold trial (optional)"
msgstr ""
msgid "Trial|How many employees will use Gitlab?"
msgstr ""
msgid "Trial|Number of employees"
msgstr ""
msgid "Trial|Successful trial activation image"
msgstr ""
msgid "Trial|Telephone number"
msgstr ""
msgid "Trial|Upgrade to Gold to keep using GitLab with advanced features."
msgstr ""
msgid "Trial|We will activate your trial on your group after you complete this step. After 30 days, you can:"
msgstr ""
msgid "Trigger"
msgstr ""
@ -31321,9 +31363,6 @@ msgstr ""
msgid "We want to be sure it is you, please confirm you are not a robot."
msgstr ""
msgid "We will activate your trial on your group once you complete this step. After 30 days, you can upgrade to any plan"
msgstr ""
msgid "We will notify %{inviter} that you declined their invitation to join GitLab. You will stop receiving reminders."
msgstr ""
@ -31941,6 +31980,9 @@ msgstr ""
msgid "You can always edit this later"
msgstr ""
msgid "You can configure jobs to use runners that are assigned specific tags. Separate tags with commas."
msgstr ""
msgid "You can create a new %{link}."
msgstr ""
@ -32031,6 +32073,9 @@ msgstr ""
msgid "You can recover this project until %{date}"
msgstr ""
msgid "You can register runners as separate users, on separate servers, and on your local machine. Register as many runners as you want."
msgstr ""
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@ -32040,9 +32085,6 @@ msgstr ""
msgid "You can set up as many Runners as you need to run your jobs."
msgstr ""
msgid "You can set up jobs to only use Runners with specific tags. Separate tags with commas."
msgstr ""
msgid "You can specify notification level per group or per project."
msgstr ""
@ -32064,6 +32106,9 @@ msgstr ""
msgid "You cannot impersonate an internal user"
msgstr ""
msgid "You cannot make this a shared runner."
msgstr ""
msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
msgstr ""

View File

@ -0,0 +1,31 @@
import waitForPromises from 'helpers/wait_for_promises';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import initSearch from '~/search_settings';
import mountSearchSettings from '~/pages/projects/edit/mount_search_settings';
jest.mock('~/search_settings');
describe('pages/projects/edit/mount_search_settings', () => {
afterEach(() => {
initSearch.mockReset();
resetHTMLFixture();
});
it('initializes search settings when js-search-settings-app is available', async () => {
setHTMLFixture('<div class="js-search-settings-app"></div>');
mountSearchSettings();
await waitForPromises();
expect(initSearch).toHaveBeenCalled();
});
it('does not initialize search settings when js-search-settings-app is unavailable', async () => {
mountSearchSettings();
await waitForPromises();
expect(initSearch).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,106 @@
import { GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SearchSettings from '~/search_settings/components/search_settings.vue';
import { HIGHLIGHT_CLASS, HIDE_CLASS } from '~/search_settings/constants';
describe('search_settings/components/search_settings.vue', () => {
const ROOT_ID = 'content-body';
const SECTION_SELECTOR = 'section.settings';
const SEARCH_TERM = 'Delete project';
const GENERAL_SETTINGS_ID = 'js-general-settings';
const ADVANCED_SETTINGS_ID = 'js-advanced-settings';
let wrapper;
const buildWrapper = () => {
wrapper = shallowMount(SearchSettings, {
propsData: {
searchRoot: document.querySelector(`#${ROOT_ID}`),
sectionSelector: SECTION_SELECTOR,
},
});
};
const sections = () => Array.from(document.querySelectorAll(SECTION_SELECTOR));
const sectionsCount = () => sections().length;
const visibleSectionsCount = () =>
document.querySelectorAll(`${SECTION_SELECTOR}:not(.${HIDE_CLASS})`).length;
const highlightedElementsCount = () => document.querySelectorAll(`.${HIGHLIGHT_CLASS}`).length;
const findSearchBox = () => wrapper.find(GlSearchBoxByType);
const search = (term) => {
findSearchBox().vm.$emit('input', term);
};
const clearSearch = () => search('');
beforeEach(() => {
setFixtures(`
<div>
<div class="js-search-app"></div>
<div id="${ROOT_ID}">
<section id="${GENERAL_SETTINGS_ID}" class="settings">
<span>General</span>
</section>
<section id="${ADVANCED_SETTINGS_ID}" class="settings">
<span>${SEARCH_TERM}</span>
</section>
</div>
</div>
`);
buildWrapper();
});
afterEach(() => {
wrapper.destroy();
});
it('expands first section and collapses the rest', () => {
clearSearch();
const [firstSection, ...otherSections] = sections();
expect(wrapper.emitted()).toEqual({
expand: [[firstSection]],
collapse: otherSections.map((x) => [x]),
});
});
it('hides sections that do not match the search term', () => {
const hiddenSection = document.querySelector(`#${GENERAL_SETTINGS_ID}`);
search(SEARCH_TERM);
expect(visibleSectionsCount()).toBe(1);
expect(hiddenSection.classList).toContain(HIDE_CLASS);
});
it('expands section that matches the search term', () => {
const section = document.querySelector(`#${ADVANCED_SETTINGS_ID}`);
search(SEARCH_TERM);
// Last called because expand is always called once to reset the page state
expect(wrapper.emitted().expand[1][0]).toBe(section);
});
it('highlight elements that match the search term', () => {
search(SEARCH_TERM);
expect(highlightedElementsCount()).toBe(1);
});
describe('when search term is cleared', () => {
beforeEach(() => {
search(SEARCH_TERM);
});
it('displays all sections', () => {
expect(visibleSectionsCount()).toBe(1);
clearSearch();
expect(visibleSectionsCount()).toBe(sectionsCount());
});
it('removes the highlight from all elements', () => {
expect(highlightedElementsCount()).toBe(1);
clearSearch();
expect(highlightedElementsCount()).toBe(0);
});
});
});

View File

@ -0,0 +1,36 @@
import $ from 'jquery';
import { setHTMLFixture } from 'helpers/fixtures';
import initSearch from '~/search_settings';
import { expandSection, closeSection } from '~/settings_panels';
jest.mock('~/settings_panels');
describe('search_settings/index', () => {
let app;
beforeEach(() => {
const el = document.createElement('div');
setHTMLFixture('<div id="content-body"></div>');
app = initSearch({ el });
});
afterEach(() => {
app.$destroy();
});
it('calls settings_panel.onExpand when expand event is emitted', () => {
const section = { name: 'section' };
app.$refs.searchSettings.$emit('expand', section);
expect(expandSection).toHaveBeenCalledWith($(section));
});
it('calls settings_panel.closeSection when collapse event is emitted', () => {
const section = { name: 'section' };
app.$refs.searchSettings.$emit('collapse', section);
expect(closeSection).toHaveBeenCalledWith($(section));
});
});

View File

@ -245,69 +245,6 @@ RSpec.describe Gitlab::Danger::Roulette do
end
end
end
describe 'reviewer suggestion probability' do
let(:reviewer) { teammate_with_capability('reviewer', 'reviewer backend') }
let(:hungry_reviewer) { teammate_with_capability('hungry_reviewer', 'reviewer backend', hungry: true) }
let(:traintainer) { teammate_with_capability('traintainer', 'trainee_maintainer backend') }
let(:hungry_traintainer) { teammate_with_capability('hungry_traintainer', 'trainee_maintainer backend', hungry: true) }
let(:teammates) do
[
reviewer.to_h,
hungry_reviewer.to_h,
traintainer.to_h,
hungry_traintainer.to_h
]
end
let(:categories) { [:backend] }
# This test is testing probability with inherent randomness.
# The variance is inversely related to sample size
# Given large enough sample size, the variance would be smaller,
# but the test would take longer.
# Given smaller sample size, the variance would be larger,
# but the test would take less time.
let!(:sample_size) { 500 }
let!(:variance) { 0.1 }
before do
# This test needs actual randomness to simulate probabilities
allow(subject).to receive(:new_random).and_return(Random.new)
WebMock
.stub_request(:get, described_class::ROULETTE_DATA_URL)
.to_return(body: teammate_json)
end
it 'has 1:2:3:4 probability of picking reviewer, hungry_reviewer, traintainer, hungry_traintainer' do
picks = Array.new(sample_size).map do
spins = subject.spin(project, categories, timezone_experiment: timezone_experiment)
spins.first.reviewer.name
end
expect(probability(picks, 'reviewer')).to be_within(variance).of(0.1)
expect(probability(picks, 'hungry_reviewer')).to be_within(variance).of(0.2)
expect(probability(picks, 'traintainer')).to be_within(variance).of(0.3)
expect(probability(picks, 'hungry_traintainer')).to be_within(variance).of(0.4)
end
def probability(picks, role)
picks.count(role).to_f / picks.length
end
def teammate_with_capability(name, capability, hungry: false)
Gitlab::Danger::Teammate.new(
{
'name' => name,
'projects' => {
'gitlab' => capability
},
'available' => true,
'hungry' => hungry
}
)
end
end
end
RSpec::Matchers.define :match_teammates do |expected|

View File

@ -0,0 +1,34 @@
# frozen_string_literal: true
require 'gitlab/danger/weightage/maintainers'
RSpec.describe Gitlab::Danger::Weightage::Maintainers do
let(:multiplier) { Gitlab::Danger::Weightage::CAPACITY_MULTIPLIER }
let(:regular_maintainer) { double('Teammate', reduced_capacity: false) }
let(:reduced_capacity_maintainer) { double('Teammate', reduced_capacity: true) }
let(:maintainers) do
[
regular_maintainer,
reduced_capacity_maintainer
]
end
let(:maintainer_count) { Gitlab::Danger::Weightage::BASE_REVIEWER_WEIGHT * multiplier }
let(:reduced_capacity_maintainer_count) { Gitlab::Danger::Weightage::BASE_REVIEWER_WEIGHT }
subject(:weighted_maintainers) { described_class.new(maintainers).execute }
describe '#execute' do
it 'weights the maintainers overall' do
expect(weighted_maintainers.count).to eq maintainer_count + reduced_capacity_maintainer_count
end
it 'has total count of regular maintainers' do
expect(weighted_maintainers.count { |r| r.object_id == regular_maintainer.object_id }).to eq maintainer_count
end
it 'has count of reduced capacity maintainers' do
expect(weighted_maintainers.count { |r| r.object_id == reduced_capacity_maintainer.object_id }).to eq reduced_capacity_maintainer_count
end
end
end

View File

@ -0,0 +1,63 @@
# frozen_string_literal: true
require 'gitlab/danger/weightage/reviewers'
RSpec.describe Gitlab::Danger::Weightage::Reviewers do
let(:multiplier) { Gitlab::Danger::Weightage::CAPACITY_MULTIPLIER }
let(:regular_reviewer) { double('Teammate', hungry: false, reduced_capacity: false) }
let(:hungry_reviewer) { double('Teammate', hungry: true, reduced_capacity: false) }
let(:reduced_capacity_reviewer) { double('Teammate', hungry: false, reduced_capacity: true) }
let(:reviewers) do
[
hungry_reviewer,
regular_reviewer,
reduced_capacity_reviewer
]
end
let(:regular_traintainer) { double('Teammate', hungry: false, reduced_capacity: false) }
let(:hungry_traintainer) { double('Teammate', hungry: true, reduced_capacity: false) }
let(:reduced_capacity_traintainer) { double('Teammate', hungry: false, reduced_capacity: true) }
let(:traintainers) do
[
hungry_traintainer,
regular_traintainer,
reduced_capacity_traintainer
]
end
let(:hungry_reviewer_count) { Gitlab::Danger::Weightage::BASE_REVIEWER_WEIGHT * multiplier + described_class::DEFAULT_REVIEWER_WEIGHT }
let(:hungry_traintainer_count) { described_class::TRAINTAINER_WEIGHT * multiplier + described_class::DEFAULT_REVIEWER_WEIGHT }
let(:reviewer_count) { Gitlab::Danger::Weightage::BASE_REVIEWER_WEIGHT * multiplier }
let(:traintainer_count) { Gitlab::Danger::Weightage::BASE_REVIEWER_WEIGHT * described_class::TRAINTAINER_WEIGHT * multiplier }
let(:reduced_capacity_reviewer_count) { Gitlab::Danger::Weightage::BASE_REVIEWER_WEIGHT }
let(:reduced_capacity_traintainer_count) { described_class::TRAINTAINER_WEIGHT }
subject(:weighted_reviewers) { described_class.new(reviewers, traintainers).execute }
describe '#execute', :aggregate_failures do
it 'weights the reviewers overall' do
reviewers_count = hungry_reviewer_count + reviewer_count + reduced_capacity_reviewer_count
traintainers_count = hungry_traintainer_count + traintainer_count + reduced_capacity_traintainer_count
expect(weighted_reviewers.count).to eq reviewers_count + traintainers_count
end
it 'has total count of hungry reviewers and traintainers' do
expect(weighted_reviewers.count(&:hungry)).to eq hungry_reviewer_count + hungry_traintainer_count
expect(weighted_reviewers.count { |r| r.object_id == hungry_reviewer.object_id }).to eq hungry_reviewer_count
expect(weighted_reviewers.count { |r| r.object_id == hungry_traintainer.object_id }).to eq hungry_traintainer_count
end
it 'has total count of regular reviewers and traintainers' do
expect(weighted_reviewers.count { |r| r.object_id == regular_reviewer.object_id }).to eq reviewer_count
expect(weighted_reviewers.count { |r| r.object_id == regular_traintainer.object_id }).to eq traintainer_count
end
it 'has count of reduced capacity reviewers' do
expect(weighted_reviewers.count(&:reduced_capacity)).to eq reduced_capacity_reviewer_count + reduced_capacity_traintainer_count
expect(weighted_reviewers.count { |r| r.object_id == reduced_capacity_reviewer.object_id }).to eq reduced_capacity_reviewer_count
expect(weighted_reviewers.count { |r| r.object_id == reduced_capacity_traintainer.object_id }).to eq reduced_capacity_traintainer_count
end
end
end

View File

@ -891,6 +891,10 @@ RSpec.describe Gitlab::GitAccess do
# Expectations are given a custom failure message proc so that it's
# easier to identify which check(s) failed.
it "has the correct permissions for #{role}s" do
if role == :admin_without_admin_mode
skip("All admins are allowed to perform actions https://gitlab.com/gitlab-org/gitlab/-/issues/296509")
end
if [:admin_with_admin_mode, :admin_without_admin_mode].include?(role)
user.update_attribute(:admin, true)
enable_admin_mode!(user) if role == :admin_with_admin_mode

View File

@ -4360,7 +4360,7 @@ RSpec.describe Ci::Build do
end
describe '#supported_runner?' do
let_it_be(:build) { create(:ci_build) }
let_it_be_with_refind(:build) { create(:ci_build) }
subject { build.supported_runner?(runner_features) }
@ -4425,6 +4425,41 @@ RSpec.describe Ci::Build do
it { is_expected.to be_falsey }
end
end
context 'when `return_exit_code` feature is required by build' do
let(:options) { { allow_failure_criteria: { exit_codes: [1] } } }
before do
build.update!(options: options)
end
context 'when runner provides given feature' do
let(:runner_features) { { return_exit_code: true } }
it { is_expected.to be_truthy }
end
context 'when runner does not provide given feature' do
let(:runner_features) { {} }
it { is_expected.to be_falsey }
end
context 'when the runner does not provide all of the required features' do
let(:options) do
{
allow_failure_criteria: { exit_codes: [1] },
artifacts: { reports: { junit: "junit.xml" } }
}
end
let(:runner_features) { { return_exit_code: true } }
it 'requires `upload_multiple_artifacts` too' do
is_expected.to be_falsey
end
end
end
end
describe '#deployment_status' do
@ -4927,4 +4962,56 @@ RSpec.describe Ci::Build do
end
end
end
describe '#exit_codes_defined?' do
let(:options) { {} }
before do
build.options.merge!(options)
end
subject(:exit_codes_defined) do
build.exit_codes_defined?
end
context 'without allow_failure_criteria' do
it { is_expected.to be_falsey }
end
context 'when exit_codes is nil' do
let(:options) do
{
allow_failure_criteria: {
exit_codes: nil
}
}
end
it { is_expected.to be_falsey }
end
context 'when exit_codes is an empty array' do
let(:options) do
{
allow_failure_criteria: {
exit_codes: []
}
}
end
it { is_expected.to be_falsey }
end
context 'when exit_codes are defined' do
let(:options) do
{
allow_failure_criteria: {
exit_codes: [5, 6]
}
}
end
it { is_expected.to be_truthy }
end
end
end

View File

@ -842,20 +842,20 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
it 'does not allow a maintainer of the target project to set `allow_collaboration`' do
target_project.add_developer(user)
update_merge_request(allow_collaboration: true, title: 'Updated title')
update_merge_request(allow_collaboration: false, title: 'Updated title')
expect(merge_request.title).to eq('Updated title')
expect(merge_request.allow_collaboration).to be_falsy
expect(merge_request.allow_collaboration).to be_truthy
end
it 'is allowed by a user that can push to the source and can update the merge request' do
merge_request.update!(assignees: [user])
source_project.add_developer(user)
update_merge_request(allow_collaboration: true, title: 'Updated title')
update_merge_request(allow_collaboration: false, title: 'Updated title')
expect(merge_request.title).to eq('Updated title')
expect(merge_request.allow_collaboration).to be_truthy
expect(merge_request.allow_collaboration).to be_falsy
end
end