Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
2475461aa2
commit
d51d5adf94
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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;
|
|
@ -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>
|
|
@ -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;
|
|
@ -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;
|
|
@ -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);
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow collaboration on merge requests across forks by default
|
||||
merge_request: 49904
|
||||
author: Jonston Chan @JonstonChan
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Updated UI text to make runner lowercase
|
||||
merge_request: 50477
|
||||
author:
|
||||
type: other
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
58f2bd74adf8b4ef616c8f341053dbeaa8116430a0f4493cbf5f8456d7f4b907
|
|
@ -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,
|
||||
|
|
|
@ -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**.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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));
|
||||
});
|
||||
});
|
|
@ -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|
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue