Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-11-24 09:09:32 +00:00
parent 0d312b8d37
commit 4372b0ca29
74 changed files with 634 additions and 638 deletions

View File

@ -38,7 +38,7 @@ review-build-cng:
- BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
# When the job is manual, review-deploy is also manual and we don't want people
# to have to manually start the jobs in sequence, so we do it for them.
- '[ -z $CI_JOB_MANUAL ] || scripts/api/play_job --job-name "review-deploy"'
- '[ -z $CI_JOB_MANUAL ] || play_job "review-deploy"'
.review-workflow-base:
extends:
@ -78,8 +78,8 @@ review-deploy:
- disable_sign_ups || (delete_release && exit 1)
# When the job is manual, review-qa-smoke is also manual and we don't want people
# to have to manually start the jobs in sequence, so we do it for them.
- '[ -z $CI_JOB_MANUAL ] || scripts/api/play_job --job-name "review-qa-smoke"'
- '[ -z $CI_JOB_MANUAL ] || scripts/api/play_job --job-name "review-performance"'
- '[ -z $CI_JOB_MANUAL ] || play_job "review-qa-smoke"'
- '[ -z $CI_JOB_MANUAL ] || play_job "review-performance"'
after_script:
# Run seed-dast-test-data.sh only when DAST_RUN is set to true. This is to pupulate review app with data for DAST scan.
# Set DAST_RUN to true when jobs are manually scheduled.

View File

@ -1,5 +1,6 @@
.tests-metadata-state:
image: ruby:2.7
variables:
TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache"
before_script:
- source scripts/utils.sh
artifacts:
@ -16,8 +17,7 @@ retrieve-tests-metadata:
- .test-metadata:rules:retrieve-tests-metadata
stage: prepare
script:
- install_gitlab_gem
- source ./scripts/rspec_helpers.sh
- source scripts/rspec_helpers.sh
- retrieve_tests_metadata
update-tests-metadata:

View File

@ -0,0 +1,42 @@
import $ from 'jquery';
export default function initClonePanel() {
const $cloneOptions = $('ul.clone-options-dropdown');
if ($cloneOptions.length) {
const $cloneUrlField = $('#clone_url');
const $cloneBtnLabel = $('.js-git-clone-holder .js-clone-dropdown-label');
const mobileCloneField = document.querySelector(
'.js-mobile-git-clone .js-clone-dropdown-label',
);
const selectedCloneOption = $cloneBtnLabel.text().trim();
if (selectedCloneOption.length > 0) {
$(`a:contains('${selectedCloneOption}')`, $cloneOptions).addClass('is-active');
}
$('a', $cloneOptions).on('click', e => {
e.preventDefault();
const $this = $(e.currentTarget);
const url = $this.attr('href');
const cloneType = $this.data('cloneType');
$('.is-active', $cloneOptions).removeClass('is-active');
$(`a[data-clone-type="${cloneType}"]`).each(function switchProtocol() {
const $el = $(this);
const activeText = $el.find('.dropdown-menu-inner-title').text();
const $container = $el.closest('.js-git-clone-holder, .js-mobile-git-clone');
const $label = $container.find('.js-clone-dropdown-label');
$el.toggleClass('is-active');
$label.text(activeText);
});
if (mobileCloneField) {
mobileCloneField.dataset.clipboardText = url;
} else {
$cloneUrlField.val(url);
}
$('.js-git-empty .js-clone').text(url);
});
}
}

View File

@ -9,47 +9,11 @@ import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as flash } from '~/flash';
import projectSelect from '../../project_select';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import initClonePanel from '~/clone_panel';
export default class Project {
constructor() {
const $cloneOptions = $('ul.clone-options-dropdown');
if ($cloneOptions.length) {
const $projectCloneField = $('#project_clone');
const $cloneBtnLabel = $('.js-git-clone-holder .js-clone-dropdown-label');
const mobileCloneField = document.querySelector(
'.js-mobile-git-clone .js-clone-dropdown-label',
);
const selectedCloneOption = $cloneBtnLabel.text().trim();
if (selectedCloneOption.length > 0) {
$(`a:contains('${selectedCloneOption}')`, $cloneOptions).addClass('is-active');
}
$('a', $cloneOptions).on('click', e => {
e.preventDefault();
const $this = $(e.currentTarget);
const url = $this.attr('href');
const cloneType = $this.data('cloneType');
$('.is-active', $cloneOptions).removeClass('is-active');
$(`a[data-clone-type="${cloneType}"]`).each(function() {
const $el = $(this);
const activeText = $el.find('.dropdown-menu-inner-title').text();
const $container = $el.closest('.project-clone-holder');
const $label = $container.find('.js-clone-dropdown-label');
$el.toggleClass('is-active');
$label.text(activeText);
});
if (mobileCloneField) {
mobileCloneField.dataset.clipboardText = url;
} else {
$projectCloneField.val(url);
}
$('.js-git-empty .js-clone').text(url);
});
}
initClonePanel();
// Ref switcher
if (document.querySelector('.js-project-refs-dropdown')) {

View File

@ -1,10 +1,11 @@
<script>
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { GlIcon, GlTooltipDirective, GlFormCheckbox } from '@gitlab/ui';
import { SQUASH_BEFORE_MERGE } from '../../i18n';
export default {
components: {
GlIcon,
GlFormCheckbox,
},
directives: {
GlTooltip: GlTooltipDirective,
@ -32,32 +33,23 @@ export default {
tooltipTitle() {
return this.isDisabled ? this.$options.i18n.tooltipTitle : null;
},
tooltipFocusable() {
return this.isDisabled ? '0' : null;
},
},
};
</script>
<template>
<div class="inline">
<label
<div class="gl-display-flex gl-align-items-center">
<gl-form-checkbox
v-gl-tooltip
:class="{ 'gl-text-gray-400': isDisabled }"
:tabindex="tooltipFocusable"
data-testid="squashLabel"
:checked="value"
:disabled="isDisabled"
name="squash"
class="qa-squash-checkbox js-squash-checkbox gl-mb-0 gl-mr-2"
:title="tooltipTitle"
@change="checked => $emit('input', checked)"
>
<input
:checked="value"
:disabled="isDisabled"
type="checkbox"
name="squash"
class="qa-squash-checkbox js-squash-checkbox"
@change="$emit('input', $event.target.checked)"
/>
{{ $options.i18n.checkboxLabel }}
</label>
</gl-form-checkbox>
<a
v-if="helpPath"
v-gl-tooltip

View File

@ -49,12 +49,12 @@ module ApplicationSettingsHelper
all_protocols_enabled? || Gitlab::CurrentSettings.enabled_git_access_protocol == 'http'
end
def enabled_project_button(project, protocol)
def enabled_protocol_button(container, protocol)
case protocol
when 'ssh'
ssh_clone_button(project, append_link: false)
ssh_clone_button(container, append_link: false)
else
http_clone_button(project, append_link: false)
http_clone_button(container, append_link: false)
end
end

View File

@ -58,10 +58,10 @@ module ButtonHelper
end
end
def http_clone_button(project, append_link: true)
def http_clone_button(container, append_link: true)
protocol = gitlab_config.protocol.upcase
dropdown_description = http_dropdown_description(protocol)
append_url = project.http_url_to_repo if append_link
append_url = container.http_url_to_repo if append_link
dropdown_item_with_description(protocol, dropdown_description, href: append_url, data: { clone_type: 'http' })
end
@ -74,13 +74,13 @@ module ButtonHelper
end
end
def ssh_clone_button(project, append_link: true)
def ssh_clone_button(container, append_link: true)
if Gitlab::CurrentSettings.user_show_add_ssh_key_message? &&
current_user.try(:require_ssh_key?)
dropdown_description = _("You won't be able to pull or push project code via SSH until you add an SSH key to your profile")
dropdown_description = s_("MissingSSHKeyWarningLink|You won't be able to pull or push repositories via SSH until you add an SSH key to your profile")
end
append_url = project.ssh_url_to_repo if append_link
append_url = container.ssh_url_to_repo if append_link
dropdown_item_with_description('SSH', dropdown_description, href: append_url, data: { clone_type: 'ssh' })
end

View File

@ -31,7 +31,7 @@ module SearchHelper
[
resources_results,
generic_results
].flatten.uniq do |item|
].flatten do |item|
item[:label]
end
end

View File

@ -2,7 +2,7 @@
- dropdown_class = local_assigns.fetch(:dropdown_class, '')
.git-clone-holder.js-git-clone-holder
%a#clone-dropdown.gl-button.btn.btn-primary.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
%a#clone-dropdown.gl-button.btn.btn-info.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
%span.gl-mr-2.js-clone-dropdown-label
= _('Clone')
= sprite_icon("chevron-down", css_class: "icon")
@ -12,7 +12,7 @@
%label.label-bold
= _('Clone with SSH')
.input-group
= text_field_tag :ssh_project_clone, project.ssh_url_to_repo, class: "js-select-on-focus form-control qa-ssh-clone-url", readonly: true, aria: { label: 'Project clone URL' }
= text_field_tag :ssh_project_clone, project.ssh_url_to_repo, class: "js-select-on-focus form-control qa-ssh-clone-url", readonly: true, aria: { label: _('Repository clone URL') }
.input-group-append
= clipboard_button(target: '#ssh_project_clone', title: _("Copy URL"), class: "input-group-text btn-default btn-clipboard")
= render_if_exists 'projects/buttons/geo'
@ -21,7 +21,7 @@
%label.label-bold
= _('Clone with %{http_label}') % { http_label: gitlab_config.protocol.upcase }
.input-group
= text_field_tag :http_project_clone, project.http_url_to_repo, class: "js-select-on-focus form-control qa-http-clone-url", readonly: true, aria: { label: 'Project clone URL' }
= text_field_tag :http_project_clone, project.http_url_to_repo, class: "js-select-on-focus form-control qa-http-clone-url", readonly: true, aria: { label: _('Repository clone URL') }
.input-group-append
= clipboard_button(target: '#http_project_clone', title: _("Copy URL"), class: "input-group-text btn-default btn-clipboard")
= render_if_exists 'projects/buttons/geo'

View File

@ -1,67 +1,66 @@
-# DANGER: Any changes to this file need to be reflected in issuables_list/components/issuable.vue!
%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id, qa_selector: 'issue_container', qa_issue_title: issue.title } }
.issue-box
.issuable-info-container
- if @can_bulk_update
.issue-check.hidden
= check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected-issuable"
.issuable-info-container
.issuable-main-info
.issue-title.title
%span.issue-title-text.js-onboarding-issue-item{ dir: "auto" }
- if issue.confidential?
%span.has-tooltip{ title: _('Confidential') }
= confidential_icon(issue)
= link_to issue.title, issue_path(issue)
= render_if_exists 'projects/issues/subepic_flag', issue: issue
- if issue.tasks?
%span.task-status.d-none.d-sm-inline-block
&nbsp;
= issue.task_status
.issuable-info
%span.issuable-reference
#{issuable_reference(issue)}
%span.issuable-authored.d-none.d-sm-inline-block
&middot;
opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
by #{link_to_member(@project, issue.author, avatar: false)}
= render_if_exists 'shared/issuable/gitlab_team_member_badge', {author: issue.author}
- if issue.milestone
%span.issuable-milestone.d-none.d-sm-inline-block
&nbsp;
= link_to project_issues_path(issue.project, milestone_title: issue.milestone.title), data: { html: 'true', toggle: 'tooltip', title: milestone_tooltip_due_date(issue.milestone) } do
= sprite_icon('clock', css_class: 'gl-vertical-align-text-bottom')
= issue.milestone.title
- if issue.due_date
%span.issuable-due-date.d-none.d-sm-inline-block.has-tooltip{ class: "#{'cred' if issue.overdue?}", title: _('Due date') }
&nbsp;
= sprite_icon('calendar')
= issue.due_date.to_s(:medium)
= render_if_exists "projects/issues/issue_weight", issue: issue
= render_if_exists "projects/issues/health_status", issue: issue
- if issue.labels.any?
.issuable-main-info
.issue-title.title
%span.issue-title-text.js-onboarding-issue-item{ dir: "auto" }
- if issue.confidential?
%span.has-tooltip{ title: _('Confidential') }
= confidential_icon(issue)
= link_to issue.title, issue_path(issue)
= render_if_exists 'projects/issues/subepic_flag', issue: issue
- if issue.tasks?
%span.task-status.d-none.d-sm-inline-block
&nbsp;
- presented_labels_sorted_by_title(issue.labels, issue.project).each do |label|
= link_to_label(label, small: true)
= issue.task_status
= render "projects/issues/issue_estimate", issue: issue
.issuable-info
%span.issuable-reference
#{issuable_reference(issue)}
%span.issuable-authored.d-none.d-sm-inline-block
&middot;
opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
by #{link_to_member(@project, issue.author, avatar: false)}
= render_if_exists 'shared/issuable/gitlab_team_member_badge', {author: issue.author}
- if issue.milestone
%span.issuable-milestone.d-none.d-sm-inline-block
&nbsp;
= link_to project_issues_path(issue.project, milestone_title: issue.milestone.title), data: { html: 'true', toggle: 'tooltip', title: milestone_tooltip_due_date(issue.milestone) } do
= sprite_icon('clock', css_class: 'gl-vertical-align-text-bottom')
= issue.milestone.title
- if issue.due_date
%span.issuable-due-date.d-none.d-sm-inline-block.has-tooltip{ class: "#{'cred' if issue.overdue?}", title: _('Due date') }
&nbsp;
= sprite_icon('calendar')
= issue.due_date.to_s(:medium)
.issuable-meta
%ul.controls
- if issue.closed? && issue.moved?
%li.issuable-status
= _('CLOSED (MOVED)')
- elsif issue.closed?
%li.issuable-status
= _('CLOSED')
- if issue.assignees.any?
%li.gl-display-flex
= render 'shared/issuable/assignees', project: @project, issuable: issue
= render_if_exists "projects/issues/issue_weight", issue: issue
= render_if_exists "projects/issues/health_status", issue: issue
= render 'shared/issuable_meta_data', issuable: issue
- if issue.labels.any?
&nbsp;
- presented_labels_sorted_by_title(issue.labels, issue.project).each do |label|
= link_to_label(label, small: true)
.float-right.issuable-updated-at.d-none.d-sm-inline-block
%span
= _('updated %{time_ago}').html_safe % { time_ago: time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago') }
= render "projects/issues/issue_estimate", issue: issue
.issuable-meta
%ul.controls
- if issue.closed? && issue.moved?
%li.issuable-status
= _('CLOSED (MOVED)')
- elsif issue.closed?
%li.issuable-status
= _('CLOSED')
- if issue.assignees.any?
%li.gl-display-flex
= render 'shared/issuable/assignees', project: @project, issuable: issue
= render 'shared/issuable_meta_data', issuable: issue
.float-right.issuable-updated-at.d-none.d-sm-inline-block
%span
= _('updated %{time_ago}').html_safe % { time_ago: time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago') }

View File

@ -11,7 +11,7 @@
%strong= @wiki.full_path
.pt-3.pt-lg-0.w-100
= render "shared/clone_panel", project: @wiki
= render "shared/clone_panel", container: @wiki
.wiki-git-access
%h3= s_("WikiClone|Install Gollum")

View File

@ -1,11 +1,9 @@
- project = project || @project
.git-clone-holder.js-git-clone-holder.input-group
.input-group-prepend
- if allowed_protocols_present?
.input-group-text.clone-dropdown-btn.btn
%span.js-clone-dropdown-label
= enabled_project_button(project, enabled_protocol)
= enabled_protocol_button(container, enabled_protocol)
- else
%a#clone-dropdown.input-group-text.btn.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
%span.js-clone-dropdown-label
@ -13,12 +11,12 @@
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-selectable.clone-options-dropdown
%li
= ssh_clone_button(project)
= ssh_clone_button(container)
%li
= http_clone_button(project)
= render_if_exists 'shared/kerberos_clone_button', project: project
= http_clone_button(container)
= render_if_exists 'shared/kerberos_clone_button', container: container
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true, aria: { label: _('Project clone URL') }
= text_field_tag :clone_url, default_url_to_repo(container), class: "js-select-on-focus form-control", readonly: true, aria: { label: _('Repository clone URL') }
.input-group-append
= clipboard_button(target: '#project_clone', title: _("Copy URL"), class: "input-group-text btn-default btn-clipboard")
= clipboard_button(target: '#clone_url', title: _("Copy URL"), class: "input-group-text btn-default btn-clipboard")

View File

@ -5,7 +5,7 @@
= sprite_icon('close', size: 16, css_class: 'gl-icon')
.gl-alert-body
- translation_params = { protocol: gitlab_config.protocol.upcase, set_password_link: link_to_set_password }
- set_password_message = _("You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account") % translation_params
- set_password_message = _("You won't be able to pull or push repositories via %{protocol} until you %{set_password_link} on your account") % translation_params
= set_password_message.html_safe
.gl-alert-actions
= link_to _('Remind later'), '#', class: 'hide-no-password-message btn gl-alert-action btn-info btn-md gl-button'

View File

@ -4,7 +4,7 @@
%button{ class: 'gl-alert-dismiss hide-no-ssh-message', type: 'button', 'aria-label': _('Dismiss') }
= sprite_icon('close', css_class: 'gl-icon s16')
.gl-alert-body
= s_("MissingSSHKeyWarningLink|You won't be able to pull or push project code via SSH until you add an SSH key to your profile").html_safe
= s_("MissingSSHKeyWarningLink|You won't be able to pull or push repositories via SSH until you add an SSH key to your profile")
.gl-alert-actions
= link_to s_('MissingSSHKeyWarningLink|Add SSH key'), profile_keys_path, class: "btn gl-alert-action btn-warning btn-md new-gl-button"
= link_to s_("MissingSSHKeyWarningLink|Don't show again"), profile_path(user: {hide_no_ssh_key: true}), method: :put, role: 'button', class: 'btn gl-alert-action btn-md btn-warning gl-button btn-warning-secondary'

View File

@ -0,0 +1,5 @@
---
title: Fix repository clone panel for wikis
merge_request: 47676
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add GlFormCheckbox to squash commits
merge_request: 48338
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Cleanup webauthn background migration
merge_request: 46179
author: Jan Beckmann
type: added

View File

@ -0,0 +1,5 @@
---
title: Remove .issue-box from static (classic) Issuable list
merge_request: 47998
author: Takuya Noguchi
type: performance

View File

@ -0,0 +1,5 @@
---
title: Fix missing item with same name in autocomplete suggestions
merge_request: 48410
author: Paul Ungureanu @ungps
type: fixed

View File

@ -1,8 +0,0 @@
---
name: usage_data_design_action
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46626
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/287630
milestone: '13.7'
type: development
group: group::knowledge
default_enabled: true

View File

@ -0,0 +1,51 @@
---
- title: Auto Deploy to EC2
body: Auto DevOps has been expanded to support deployments to Amazon Web Services. You can now deploy to AWS Cloud Compute (EC2) and take advantage of Auto DevOps, even without Kubernetes. To enable this workflow, you must enable Auto DevOps and define the AWS-typed environment variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_DEFAULT_REGION. This allows you to provision your own infrastructure by leveraging the AWS CloudFormation API. Then, you can push your previously built artifact to an AWS S3 bucket, and deploy the content to an AWS EC2 instance. Your EC2 deployment then automatically builds you a complete, automatic delivery pipeline without extra manual steps.
stage: Release
self-managed: true
gitlab-com: true
packages: [core, starter, premium, ultimate]
url: https://docs.gitlab.com/ee/ci/cloud_deployment/#custom-build-job-for-auto-devops
image_url: https://img.youtube.com/vi/4B-qSwKnacA/hqdefault.jpg
published_at: 2020-11-22
release: 13.6
- title: Display Code Quality severity ratings
body: The Code Quality feature in GitLab is great at showing what quality violations exist in a project or are changing in the Merge Request. However, understanding which of those violations is the most important is not clear in the GitLab interface today. With the Full Code Quality Report and Merge Request Widget, now you can see the severity rating. This makes it easy for you to understand which code quality violations are most important to resolve before merging and reduces the technical debt in your project.
stage: Verify
self-managed: true
gitlab-com: true
packages: [core, starter, premium, ultimate]
url: https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html#code-quality-widget
image_url: https://about.gitlab.com/images/13_6/code_quality_severity.png
published_at: 2020-11-22
release: 13.6
- title: Display code coverage data for selected projects
body: In 13.4, we released the first iteration of Code Coverage data for Groups that enables you to compare the coverage of multiple projects and download the data in a single file from a single screen. However, to analyze the data, you had to open the file to check it manually, and probably imported it into a spreadsheet for further analysis. In GitLab 13.6, you can now select specific projects in a group to see their latest coverage values directly in GitLab itself without needing to download a file or waste development time accessing code coverage data.
stage: Verify
self-managed: true
gitlab-com: true
packages: [premium, ultimate]
url: https://docs.gitlab.com/ee/user/group/repositories_analytics/index.html#latest-project-test-coverage-list
image_url: https://about.gitlab.com/images/13_6/display_selected_coverage_projects_example.png
published_at: 2020-11-22
release: 13.6
- title: Group-level management of project integrations
body: In GitLab 13.3, we added the ability to enable an integration across an entire instance. With GitLab 13.6, that feature is being expanded to allow integrations to be managed at the group level as well! Group owners can now add an integration to a group, and that integration will be inherited by all projects under that group. This has the potential for saving massive amounts of time, as many organizations have specific integrations that they want rolled out to every project they create. A great example of this is using our Jira integration. If you're using Jira, it's almost always across the whole company. Some of these companies have _thousands of projects_ and therefore had to configure each and every one of those integrations individually.With group-level management of project integrations, you can add the integration at each parent group, reducing the amount of configuration required by orders of magnitude!
stage: Create
self-managed: true
gitlab-com: true
packages: [core, starter, premium, ultimate]
url: https://docs.gitlab.com/ee/user/admin_area/settings/project_integration_management.html
image_url: https://about.gitlab.com/images/13_6/project-integration-inheriting-settings.png
published_at: 2020-11-22
release: 13.6
- title: Milestone Burnup Charts and historically accurate reporting
body: A milestone or iteration burndown chart helps track completion progress of total scope, but it doesn't provide insight into how the scope changed during the course of the timebox. Neither has it previously retained historical accuracy regarding how much of the initial committed scope of the milestone or iteration was actually completed. To solve these problems and help teams have better insights into scope creep, milestones and iterations now show a burnup chart that tracks the daily total count and weight of issues added to and completed within a given timebox. This change only applies to milestones and iterations created in GitLab 13.6 or later. Milestones created prior to 13.6 will still have access to legacy burndown charts.
stage: Plan
self-managed: true
gitlab-com: true
packages: [starter, premium, ultimate]
url: https://docs.gitlab.com/ee/user/project/milestones/burndown_and_burnup_charts.html#burnup-charts
image_url: https://about.gitlab.com/images/13_6/burnup-chart.png
published_at: 2020-11-22
release: 13.6

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
class ChangeWebauthnXidLength < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_text_limit :webauthn_registrations, :credential_xid, 340, constraint_name: check_constraint_name(:webauthn_registrations, :credential_xid, 'max_length_v2')
remove_text_limit :webauthn_registrations, :credential_xid, constraint_name: check_constraint_name(:webauthn_registrations, :credential_xid, 'max_length')
end
def down
# no-op: Danger of failling if there are records with length(credential_xid) > 255
end
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
class EnsureU2fRegistrationsMigrated < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
BACKGROUND_MIGRATION_CLASS = 'MigrateU2fWebauthn'
BATCH_SIZE = 100
DOWNTIME = false
disable_ddl_transaction!
class U2fRegistration < ActiveRecord::Base
include EachBatch
self.table_name = 'u2f_registrations'
end
def up
Gitlab::BackgroundMigration.steal(BACKGROUND_MIGRATION_CLASS)
# Do a manual update in case we lost BG jobs. The expected record count should be 0 or very low.
U2fRegistration
.joins("LEFT JOIN webauthn_registrations ON webauthn_registrations.u2f_registration_id = u2f_registrations.id")
.where("webauthn_registrations.u2f_registration_id IS NULL")
.each_batch(of: BATCH_SIZE) do |batch, index|
batch.each do |record|
Gitlab::BackgroundMigration::MigrateU2fWebauthn.new.perform(record.id, record.id)
rescue => e
Gitlab::ErrorTracking.track_exception(e, u2f_registration_id: record.id)
end
end
end
def down
# no-op (we can't "unsteal" migrations)
end
end

View File

@ -0,0 +1 @@
a9ae0161c40b9c72371d6eb992bd0da8c3698e7784357faac0821e3f513e48d2

View File

@ -0,0 +1 @@
a39bad8b213833c84370cf64188a3ce444fd8aeeff239c29f5f2f633d94ac6bb

View File

@ -17571,8 +17571,8 @@ CREATE TABLE webauthn_registrations (
name text NOT NULL,
public_key text NOT NULL,
u2f_registration_id integer,
CONSTRAINT check_242f0cc65c CHECK ((char_length(credential_xid) <= 255)),
CONSTRAINT check_2f02e74321 CHECK ((char_length(name) <= 255))
CONSTRAINT check_2f02e74321 CHECK ((char_length(name) <= 255)),
CONSTRAINT check_e54008d9ce CHECK ((char_length(credential_xid) <= 340))
);
CREATE SEQUENCE webauthn_registrations_id_seq

View File

@ -12,8 +12,8 @@ Our current CI parallelization setup is as follows:
1. The `retrieve-tests-metadata` job in the `prepare` stage ensures we have a
`knapsack/report-master.json` file:
- The `knapsack/report-master.json` file is fetched from the latest `master` pipeline which runs `update-tests-metadata`
(for now it's the 2-hourly scheduled master pipeline), if it's not here we initialize the file with `{}`.
- The `knapsack/report-master.json` file is fetched from S3, if it's not here
we initialize the file with `{}`.
1. Each `[rspec|rspec-ee] [unit|integration|system|geo] n m` job are run with
`knapsack rspec` and should have an evenly distributed share of tests:
- It works because the jobs have access to the `knapsack/report-master.json`
@ -25,7 +25,7 @@ Our current CI parallelization setup is as follows:
1. The `update-tests-metadata` job (which only runs on scheduled pipelines for
[the canonical project](https://gitlab.com/gitlab-org/gitlab) takes all the
`knapsack/rspec*_pg_*.json` files and merge them all together into a single
`knapsack/report-master.json` file that is saved as artifact.
`knapsack/report-master.json` file that is then uploaded to S3.
After that, the next pipeline will use the up-to-date `knapsack/report-master.json` file.

View File

@ -38,13 +38,15 @@ GitLab.com uses the SAML NameID to identify users. The NameID element:
- Must be unique to each user.
- Must be a persistent value that will never change, such as a randomly generated unique user ID.
- Is case sensitive. The NameID must match exactly on subsequent login attempts, so should not rely on user input that could change between upper and lower case.
- Should not be an email address or username. We strongly recommend against these as it is hard to guarantee they will never change, for example when a person's name changes. Email addresses are also case-insensitive, which can result in users being unable to sign in.
- Should not be an email address or username. We strongly recommend against these as it's hard to
guarantee it doesn't ever change, for example, when a person's name changes. Email addresses are
also case-insensitive, which can result in users being unable to sign in.
The relevant field name and recommended value for supported providers are in the [provider specific notes](#providers).
appropriate corresponding field.
CAUTION: **Warning:**
Once users have signed into GitLab using the SSO SAML setup, changing the `NameID` will break the configuration and potentially lock users out of the GitLab group.
Once users have signed into GitLab using the SSO SAML setup, changing the `NameID` breaks the configuration and potentially locks users out of the GitLab group.
#### NameID Format
@ -56,11 +58,11 @@ GitLab provides metadata XML that can be used to configure your Identity Provide
1. Navigate to the group and click **Settings > SAML SSO**.
1. Copy the provided **GitLab metadata URL**.
1. Follow your Identity Provider's documentation and paste the metadata URL when it is requested.
1. Follow your Identity Provider's documentation and paste the metadata URL when it's requested.
## Configuring GitLab
Once you've set up your identity provider to work with GitLab, you'll need to configure GitLab to use it for authentication:
After you set up your identity provider to work with GitLab, you must configure GitLab to use it for authentication:
1. Navigate to the group's **Settings > SAML SSO**.
1. Find the SSO URL from your Identity Provider and enter it the **Identity provider single sign-on URL** field.
@ -79,14 +81,14 @@ Please note that the certificate [fingerprint algorithm](#additional-providers-a
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5291) in GitLab 11.8.
- [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/9255) in GitLab 11.11 with ongoing enforcement in the GitLab UI.
With this option enabled, users must go through your group's GitLab single sign-on URL. They may also be added via SCIM, if configured. Users cannot be added manually, and may only access project/group resources via the UI by signing in through the SSO URL.
With this option enabled, users must go through your group's GitLab single sign-on URL. They may also be added via SCIM, if configured. Users can't be added manually, and may only access project/group resources via the UI by signing in through the SSO URL.
However, users will not be prompted to sign in through SSO on each visit. GitLab will check whether a user has authenticated through SSO, and will only prompt the user to sign in via SSO if the session has expired.
However, users are not prompted to sign in through SSO on each visit. GitLab checks whether a user has authenticated through SSO, and only prompts the user to sign in via SSO if the session has expired.
You can see more information about how long a session is valid in our [user profile documentation](../../profile/#why-do-i-keep-getting-signed-out).
We intend to add a similar SSO requirement for [Git and API activity](https://gitlab.com/gitlab-org/gitlab/-/issues/9152).
When SSO enforcement is enabled for a group, users cannot share a project in the group outside the top-level group, even if the project is forked.
When SSO enforcement is enabled for a group, users can't share a project in the group outside the top-level group, even if the project is forked.
## Providers
@ -192,7 +194,7 @@ If the information you need isn't listed above you may wish to check our [troubl
Once Group SSO is configured and enabled, users can access the GitLab.com group through the identity provider's dashboard. If [SCIM](scim_setup.md) is configured, please see the [user access and linking setup section on the SCIM page](scim_setup.md#user-access-and-linking-setup).
When a user tries to sign in with Group SSO, they will need an account that's configured with one of the following:
When a user tries to sign in with Group SSO, they need an account that's configured with one of the following:
- [SCIM](scim_setup.md).
- [Group-managed accounts](group_managed_accounts.md).
@ -203,18 +205,18 @@ When a user tries to sign in with Group SSO, they will need an account that's co
To link SAML to your existing GitLab.com account:
1. Sign in to your GitLab.com account.
1. Locate and visit the **GitLab single sign-on URL** for the group you are signing in to. A group Admin can find this on the group's **Settings > SAML SSO** page. If the sign-in URL is configured, users can connect to the GitLab app from the Identity Provider.
1. Locate and visit the **GitLab single sign-on URL** for the group you're signing in to. A group Admin can find this on the group's **Settings > SAML SSO** page. If the sign-in URL is configured, users can connect to the GitLab app from the Identity Provider.
1. Click **Authorize**.
1. Enter your credentials on the Identity Provider if prompted.
1. You will be redirected back to GitLab.com and should now have access to the group. In the future, you can use SAML to sign in to GitLab.com.
1. You are then redirected back to GitLab.com and should now have access to the group. In the future, you can use SAML to sign in to GitLab.com.
On subsequent visits, you should be able to go [sign in to GitLab.com with SAML](#signing-in-to-gitlabcom-with-saml) or by visiting links directly. If the **enforce SSO** option is turned on, you will be redirected to sign in through the identity provider.
On subsequent visits, you should be able to go [sign in to GitLab.com with SAML](#signing-in-to-gitlabcom-with-saml) or by visiting links directly. If the **enforce SSO** option is turned on, you are then redirected to sign in through the identity provider.
### Signing in to GitLab.com with SAML
1. Sign in to your identity provider.
1. From the list of apps, click on the "GitLab.com" app (The name is set by the administrator of the identity provider).
1. You will be signed in to GitLab.com and redirected to the group.
1. You are then signed in to GitLab.com and redirected to the group.
### Role
@ -238,7 +240,7 @@ Users can unlink SAML for a group from their profile page. This can be helpful i
- You no longer want a group to be able to sign you in to GitLab.com.
- Your SAML NameID has changed and so GitLab can no longer find your user.
For example, to unlink the `MyOrg` account, the following **Disconnect** button will be available under **Profile > Accounts**:
For example, to unlink the `MyOrg` account, the following **Disconnect** button is available under **Profile > Accounts**:
![Unlink Group SAML](img/unlink_group_saml.png)
@ -286,7 +288,7 @@ access.
| Service Provider | SAML considers GitLab to be a service provider. |
| Assertion | A piece of information about a user's identity, such as their name or role. Also know as claims or attributes. |
| SSO | Single Sign On. |
| Assertion consumer service URL | The callback on GitLab where users will be redirected after successfully authenticating with the identity provider. |
| Assertion consumer service URL | The callback on GitLab where users are redirected after successfully authenticating with the identity provider. |
| Issuer | How GitLab identifies itself to the identity provider. Also known as a "Relying party trust identifier". |
| Certificate fingerprint | Used to confirm that communications over SAML are secure by checking that the server is signing communications with the correct certificate. Also known as a certificate thumbprint. |

View File

@ -49,6 +49,8 @@ module Gitlab
MAX_ALLOWED_LOOPS = 10_000
SLEEP_TIME_IN_SECONDS = 0.01 # 10 msec sleep
ALLOWED_MODES = [:itself, :distinct].freeze
FALLBACK_FINISH = 0
OFFSET_BY_ONE = 1
# Each query should take < 500ms https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22705
DEFAULT_DISTINCT_BATCH_SIZE = 10_000
@ -65,7 +67,7 @@ module Gitlab
(@operation == :count && batch_size <= MIN_REQUIRED_BATCH_SIZE) ||
(@operation == :sum && batch_size < DEFAULT_SUM_BATCH_SIZE) ||
(finish - start) / batch_size >= MAX_ALLOWED_LOOPS ||
start > finish
start >= finish
end
def count(batch_size: nil, mode: :itself, start: nil, finish: nil)
@ -85,11 +87,13 @@ module Gitlab
results = nil
batch_start = start
while batch_start <= finish
batch_relation = build_relation_batch(batch_start, batch_start + batch_size, mode)
while batch_start < finish
batch_end = [batch_start + batch_size, finish].min
batch_relation = build_relation_batch(batch_start, batch_end, mode)
begin
results = merge_results(results, batch_relation.send(@operation, *@operation_args)) # rubocop:disable GitlabSecurity/PublicSend
batch_start += batch_size
batch_start = batch_end
rescue ActiveRecord::QueryCanceled => error
# retry with a safe batch size & warmer cache
if batch_size >= 2 * MIN_REQUIRED_BATCH_SIZE
@ -99,6 +103,7 @@ module Gitlab
return FALLBACK
end
end
sleep(SLEEP_TIME_IN_SECONDS)
end
@ -138,7 +143,7 @@ module Gitlab
end
def actual_finish(finish)
finish || @relation.unscope(:group, :having).maximum(@column) || 0
(finish || @relation.unscope(:group, :having).maximum(@column) || FALLBACK_FINISH) + OFFSET_BY_ONE
end
def check_mode!(mode)

View File

@ -17794,7 +17794,7 @@ msgstr ""
msgid "MissingSSHKeyWarningLink|Don't show again"
msgstr ""
msgid "MissingSSHKeyWarningLink|You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
msgid "MissingSSHKeyWarningLink|You won't be able to pull or push repositories via SSH until you add an SSH key to your profile"
msgstr ""
msgid "ModalButton|Add projects"
@ -21111,9 +21111,6 @@ msgstr ""
msgid "Project cannot be shared with the group it is in or one of its ancestors."
msgstr ""
msgid "Project clone URL"
msgstr ""
msgid "Project configuration, excluding integrations"
msgstr ""
@ -23088,6 +23085,9 @@ msgstr ""
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
msgstr ""
msgid "Repository clone URL"
msgstr ""
msgid "Repository files count over the limit"
msgstr ""
@ -24252,6 +24252,9 @@ msgstr ""
msgid "SecurityReports|Scan details"
msgstr ""
msgid "SecurityReports|Scanner"
msgstr ""
msgid "SecurityReports|Security Dashboard"
msgstr ""
@ -31430,10 +31433,7 @@ msgstr ""
msgid "You won't be able to create new projects because you have reached your project limit."
msgstr ""
msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account"
msgstr ""
msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
msgid "You won't be able to pull or push repositories via %{protocol} until you %{set_password_link} on your account"
msgstr ""
msgid "You'll be charged for %{true_up_link_start}users over license%{link_end} on a quarterly or annual basis, depending on the terms of your agreement."

View File

@ -12,7 +12,7 @@ module QA
base.view 'app/views/shared/_clone_panel.html.haml' do
element :clone_dropdown
element :clone_options_dropdown, '.clone-options-dropdown' # rubocop:disable QA/ElementWithPattern
element :project_repository_location, 'text_field_tag :project_clone' # rubocop:disable QA/ElementWithPattern
element :clone_url, 'text_field_tag :clone_url' # rubocop:disable QA/ElementWithPattern
end
end
@ -28,7 +28,7 @@ module QA
end
def repository_location
Git::Location.new(find('#project_clone').value)
Git::Location.new(find('#clone_url').value)
end
private

View File

@ -1,58 +0,0 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'rubygems'
require 'gitlab'
require 'optparse'
require_relative 'get_job_id'
class CancelPipeline
DEFAULT_OPTIONS = {
project: ENV['CI_PROJECT_ID'],
pipeline_id: ENV['CI_PIPELINE_ID'],
api_token: ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN']
}.freeze
def initialize(options)
@project = options.delete(:project)
@pipeline_id = options.delete(:pipeline_id)
Gitlab.configure do |config|
config.endpoint = 'https://gitlab.com/api/v4'
config.private_token = options.delete(:api_token)
end
end
def execute
Gitlab.cancel_pipeline(project, pipeline_id)
end
private
attr_reader :project, :pipeline_id
end
if $0 == __FILE__
options = CancelPipeline::DEFAULT_OPTIONS.dup
OptionParser.new do |opts|
opts.on("-p", "--project PROJECT", String, "Project where to find the job (defaults to $CI_PROJECT_ID)") do |value|
options[:project] = value
end
opts.on("-i", "--pipeline-id PIPELINE_ID", String, "A pipeline ID (defaults to $CI_PIPELINE_ID)") do |value|
options[:pipeline_id] = value
end
opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `read_api` scope") do |value|
options[:api_token] = value
end
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
end
end.parse!
CancelPipeline.new(options).execute
end

View File

@ -1,92 +0,0 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'rubygems'
require 'optparse'
require 'fileutils'
require 'uri'
require 'cgi'
require 'net/http'
class ArtifactFinder
DEFAULT_OPTIONS = {
project: ENV['CI_PROJECT_ID'],
api_token: ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN']
}.freeze
def initialize(options)
@project = options.delete(:project)
@job_id = options.delete(:job_id)
@api_token = options.delete(:api_token)
@artifact_path = options.delete(:artifact_path)
end
def execute
url = "https://gitlab.com/api/v4/projects/#{CGI.escape(project)}/jobs/#{job_id}/artifacts"
if artifact_path
FileUtils.mkdir_p(File.dirname(artifact_path))
url += "/#{artifact_path}"
end
fetch(url)
end
private
attr_reader :project, :job_id, :api_token, :artifact_path
def fetch(uri_str, limit = 10)
raise 'Too many HTTP redirects' if limit == 0
uri = URI(uri_str)
request = Net::HTTP::Get.new(uri)
request['Private-Token'] = api_token
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
http.request(request) do |response|
case response
when Net::HTTPSuccess then
File.open(artifact_path || 'artifacts.zip', 'w') do |file|
response.read_body(&file.method(:write))
end
when Net::HTTPRedirection then
location = response['location']
warn "Redirected (#{limit - 1} redirections remaining)."
fetch(location, limit - 1)
else
raise "Unexpected response: #{response.value}"
end
end
end
end
end
if $0 == __FILE__
options = ArtifactFinder::DEFAULT_OPTIONS.dup
OptionParser.new do |opts|
opts.on("-p", "--project PROJECT", String, "Project where to find the job (defaults to $CI_PROJECT_ID)") do |value|
options[:project] = value
end
opts.on("-j", "--job-id JOB_ID", String, "A job ID") do |value|
options[:job_id] = value
end
opts.on("-a", "--artifact-path ARTIFACT_PATH", String, "A valid artifact path") do |value|
options[:artifact_path] = value
end
opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `read_api` scope") do |value|
options[:api_token] = value
end
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
end
end.parse!
ArtifactFinder.new(options).execute
end

View File

@ -1,108 +0,0 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'rubygems'
require 'gitlab'
require 'optparse'
class JobFinder
DEFAULT_OPTIONS = {
project: ENV['CI_PROJECT_ID'],
pipeline_id: ENV['CI_PIPELINE_ID'],
pipeline_query: {},
job_query: {},
api_token: ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN']
}.freeze
def initialize(options)
@project = options.delete(:project)
@pipeline_query = options.delete(:pipeline_query)
@job_query = options.delete(:job_query)
@pipeline_id = options.delete(:pipeline_id)
@job_name = options.delete(:job_name)
Gitlab.configure do |config|
config.endpoint = 'https://gitlab.com/api/v4'
config.private_token = options.delete(:api_token)
end
end
def execute
find_job_with_filtered_pipelines || find_job_in_pipeline
end
private
attr_reader :project, :pipeline_query, :job_query, :pipeline_id, :job_name
def find_job_with_filtered_pipelines
return if pipeline_query.empty?
Gitlab.pipelines(project, pipeline_query_params).auto_paginate do |pipeline|
Gitlab.pipeline_jobs(project, pipeline.id, job_query_params).auto_paginate do |job|
return job if job.name == job_name # rubocop:disable Cop/AvoidReturnFromBlocks
end
end
raise 'Job not found!'
end
def find_job_in_pipeline
return unless pipeline_id
Gitlab.pipeline_jobs(project, pipeline_id, job_query_params).auto_paginate do |job|
return job if job.name == job_name # rubocop:disable Cop/AvoidReturnFromBlocks
end
raise 'Job not found!'
end
def pipeline_query_params
@pipeline_query_params ||= { per_page: 100, **pipeline_query }
end
def job_query_params
@job_query_params ||= { per_page: 100, **job_query }
end
end
if $0 == __FILE__
options = JobFinder::DEFAULT_OPTIONS.dup
OptionParser.new do |opts|
opts.on("-p", "--project PROJECT", String, "Project where to find the job (defaults to $CI_PROJECT_ID)") do |value|
options[:project] = value
end
opts.on("-i", "--pipeline-id pipeline_id", String, "A pipeline ID (defaults to $CI_PIPELINE_ID)") do |value|
options[:pipeline_id] = value
end
opts.on("-q", "--pipeline-query pipeline_query", String, "Query to pass to the Pipeline API request") do |value|
options[:pipeline_query].merge!(Hash[*value.split('=')])
end
opts.on("-Q", "--job-query job_query", String, "Query to pass to the Job API request") do |value|
options[:job_query].merge!(Hash[*value.split('=')])
end
opts.on("-j", "--job-name job_name", String, "A job name that needs to exist in the found pipeline") do |value|
options[:job_name] = value
end
opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `read_api` scope") do |value|
options[:api_token] = value
end
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
end
end.parse!
job = JobFinder.new(options).execute
return if job.nil?
puts job.id
end

View File

@ -1,60 +0,0 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'rubygems'
require 'gitlab'
require 'optparse'
require_relative 'get_job_id'
class PlayJob
DEFAULT_OPTIONS = {
project: ENV['CI_PROJECT_ID'],
pipeline_id: ENV['CI_PIPELINE_ID'],
api_token: ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN']
}.freeze
def initialize(options)
@project = options.delete(:project)
@options = options
Gitlab.configure do |config|
config.endpoint = 'https://gitlab.com/api/v4'
config.private_token = options.fetch(:api_token)
end
end
def execute
job = JobFinder.new(project, options.slice(:api_token, :pipeline_id, :job_name).merge(scope: 'manual')).execute
Gitlab.job_play(project, job.id)
end
private
attr_reader :project, :options
end
if $0 == __FILE__
options = PlayJob::DEFAULT_OPTIONS.dup
OptionParser.new do |opts|
opts.on("-p", "--project PROJECT", String, "Project where to find the job (defaults to $CI_PROJECT_ID)") do |value|
options[:project] = value
end
opts.on("-j", "--job-name JOB_NAME", String, "A job name that needs to exist in the found pipeline") do |value|
options[:job_name] = value
end
opts.on("-t", "--api-token API_TOKEN", String, "A value API token with the `read_api` scope") do |value|
options[:api_token] = value
end
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
end
end.parse!
PlayJob.new(options).execute
end

43
scripts/get-job-id Executable file
View File

@ -0,0 +1,43 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'gitlab'
require 'optparse'
#
# Configure credentials to be used with gitlab gem
#
Gitlab.configure do |config|
config.endpoint = 'https://gitlab.com/api/v4'
config.private_token = ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN']
end
options = {}
OptionParser.new do |opts|
opts.on("-s", "--scope=SCOPE", "Find job with matching scope") do |scope|
options[:scope] = scope
end
end.parse!
class PipelineJobFinder
def initialize(project_id, pipeline_id, job_name, options)
@project_id = project_id
@pipeline_id = pipeline_id
@job_name = job_name
@options = options
end
def execute
Gitlab.pipeline_jobs(@project_id, @pipeline_id, @options).auto_paginate do |job|
break job if job.name == @job_name
end
end
end
project_id, pipeline_id, job_name = ARGV
job = PipelineJobFinder.new(project_id, pipeline_id, job_name, options).execute
return if job.nil?
puts job.id

View File

@ -1,39 +1,44 @@
#!/usr/bin/env bash
function retrieve_tests_metadata() {
mkdir -p crystalball/ knapsack/ rspec_flaky/ rspec_profiling/
local project_path="gitlab-org/gitlab"
local test_metadata_job_id
# Ruby
test_metadata_job_id=$(scripts/api/get_job_id --project "${project_path}" -q "status=success" -q "ref=master" -q "username=gitlab-bot" -Q "scope=success" --job-name "update-tests-metadata")
mkdir -p knapsack/ rspec_flaky/ rspec_profiling/
if [[ ! -f "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" ]]; then
scripts/api/download_job_artifact --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
wget -O "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" "http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
fi
if [[ ! -f "${FLAKY_RSPEC_SUITE_REPORT_PATH}" ]]; then
scripts/api/download_job_artifact --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${FLAKY_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
wget -O "${FLAKY_RSPEC_SUITE_REPORT_PATH}" "http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/${FLAKY_RSPEC_SUITE_REPORT_PATH}" || echo "{}" > "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
fi
# FIXME: We will need to find a pipeline where the $RSPEC_PACKED_TESTS_MAPPING_PATH.gz actually exists (Crystalball only runs every two-hours, but the `update-tests-metadata` runs for all `master` pipelines...).
# if [[ ! -f "${RSPEC_PACKED_TESTS_MAPPING_PATH}" ]]; then
# (scripts/api/download_job_artifact --project "${project_path}" --job-id "${test_metadata_job_id}" --artifact-path "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz" && gzip -d "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz") || echo "{}" > "${RSPEC_PACKED_TESTS_MAPPING_PATH}"
# fi
#
# scripts/unpack-test-mapping "${RSPEC_PACKED_TESTS_MAPPING_PATH}" "${RSPEC_TESTS_MAPPING_PATH}"
}
function update_tests_metadata() {
echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
scripts/merge-reports "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" knapsack/rspec*.json
if [[ -n "${TESTS_METADATA_S3_BUCKET}" ]]; then
if [[ "$CI_PIPELINE_SOURCE" == "schedule" ]]; then
scripts/sync-reports put "${TESTS_METADATA_S3_BUCKET}" "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
else
echo "Not uplaoding report to S3 as the pipeline is not a scheduled one."
fi
fi
rm -f knapsack/rspec*.json
export FLAKY_RSPEC_GENERATE_REPORT="true"
scripts/merge-reports "${FLAKY_RSPEC_SUITE_REPORT_PATH}" rspec_flaky/all_*.json
export FLAKY_RSPEC_GENERATE_REPORT="true"
scripts/flaky_examples/prune-old-flaky-examples "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
if [[ -n ${TESTS_METADATA_S3_BUCKET} ]]; then
if [[ "$CI_PIPELINE_SOURCE" == "schedule" ]]; then
scripts/sync-reports put "${TESTS_METADATA_S3_BUCKET}" "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
else
echo "Not uploading report to S3 as the pipeline is not a scheduled one."
fi
fi
rm -f rspec_flaky/all_*.json rspec_flaky/new_*.json
if [[ "$CI_PIPELINE_SOURCE" == "schedule" ]]; then
@ -43,6 +48,16 @@ function update_tests_metadata() {
fi
}
function retrieve_tests_mapping() {
mkdir -p crystalball/
if [[ ! -f "${RSPEC_PACKED_TESTS_MAPPING_PATH}" ]]; then
(wget -O "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz" "http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz" && gzip -d "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz") || echo "{}" > "${RSPEC_PACKED_TESTS_MAPPING_PATH}"
fi
scripts/unpack-test-mapping "${RSPEC_PACKED_TESTS_MAPPING_PATH}" "${RSPEC_TESTS_MAPPING_PATH}"
}
function update_tests_mapping() {
if ! crystalball_rspec_data_exists; then
echo "No crystalball rspec data found."
@ -50,9 +65,20 @@ function update_tests_mapping() {
fi
scripts/generate-test-mapping "${RSPEC_TESTS_MAPPING_PATH}" crystalball/rspec*.yml
scripts/pack-test-mapping "${RSPEC_TESTS_MAPPING_PATH}" "${RSPEC_PACKED_TESTS_MAPPING_PATH}"
gzip "${RSPEC_PACKED_TESTS_MAPPING_PATH}"
rm -f crystalball/rspec*.yml "${RSPEC_PACKED_TESTS_MAPPING_PATH}"
if [[ -n "${TESTS_METADATA_S3_BUCKET}" ]]; then
if [[ "$CI_PIPELINE_SOURCE" == "schedule" ]]; then
scripts/sync-reports put "${TESTS_METADATA_S3_BUCKET}" "${RSPEC_PACKED_TESTS_MAPPING_PATH}.gz"
else
echo "Not uploading report to S3 as the pipeline is not a scheduled one."
fi
fi
rm -f crystalball/rspec*.yml
}
function crystalball_rspec_data_exists() {

View File

@ -87,14 +87,65 @@ function echosuccess() {
fi
}
function get_job_id() {
local job_name="${1}"
local query_string="${2:+&${2}}"
local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}"
if [ -z "${api_token}" ]; then
echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN."
return
fi
local max_page=3
local page=1
while true; do
local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/jobs?per_page=100&page=${page}${query_string}"
echoinfo "GET ${url}"
local job_id
job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq "map(select(.name == \"${job_name}\")) | map(.id) | last")
[[ "${job_id}" == "null" && "${page}" -lt "$max_page" ]] || break
let "page++"
done
if [[ "${job_id}" == "null" ]]; then # jq prints "null" for non-existent attribute
echoerr "The '${job_name}' job ID couldn't be retrieved!"
else
echoinfo "The '${job_name}' job ID is ${job_id}"
echo "${job_id}"
fi
}
function play_job() {
local job_name="${1}"
local job_id
job_id=$(get_job_id "${job_name}" "scope=manual");
if [ -z "${job_id}" ]; then return; fi
local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}"
if [ -z "${api_token}" ]; then
echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN."
return
fi
local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}/play"
echoinfo "POST ${url}"
local job_url
job_url=$(curl --silent --show-error --request POST --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq ".web_url")
echoinfo "Manual job '${job_name}' started at: ${job_url}"
}
function fail_pipeline_early() {
local dont_interrupt_me_job_id
dont_interrupt_me_job_id=$(scripts/api/get_job_id --job-query "scope=success" --job-name "dont-interrupt-me")
dont_interrupt_me_job_id=$(get_job_id 'dont-interrupt-me' 'scope=success')
if [[ -n "${dont_interrupt_me_job_id}" ]]; then
echoinfo "This pipeline cannot be interrupted due to \`dont-interrupt-me\` job ${dont_interrupt_me_job_id}"
else
echoinfo "Failing pipeline early for fast feedback due to test failures in rspec fail-fast."
scripts/api/cancel_pipeline
curl --request POST --header "PRIVATE-TOKEN: ${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}" "https://${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/cancel"
fi
}

View File

@ -56,8 +56,8 @@ RSpec.describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
visit profile_active_sessions_path
expect(page).to(
have_selector('ul.list-group li.list-group-item', { text: 'Signed in on',
count: 2 }))
have_selector('ul.list-group li.list-group-item', text: 'Signed in on',
count: 2))
expect(page).to have_content(
'127.0.0.1 ' \

View File

@ -15,7 +15,7 @@ RSpec.describe 'No Password Alert' do
let(:user) { create(:user) }
it 'shows no alert' do
expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account"
expect(page).not_to have_content "You won't be able to pull or push repositories via HTTP until you set a password on your account"
end
end
@ -23,7 +23,7 @@ RSpec.describe 'No Password Alert' do
let(:user) { create(:user, password_automatically_set: true) }
it 'shows a password alert' do
expect(page).to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account"
expect(page).to have_content "You won't be able to pull or push repositories via HTTP until you set a password on your account"
end
end
end
@ -41,7 +41,7 @@ RSpec.describe 'No Password Alert' do
gitlab_sign_in_via('saml', user, 'my-uid')
visit project_path(project)
expect(page).to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
expect(page).to have_content "You won't be able to pull or push repositories via HTTP until you create a personal access token on your account"
end
end
@ -51,7 +51,7 @@ RSpec.describe 'No Password Alert' do
gitlab_sign_in_via('saml', user, 'my-uid')
visit project_path(project)
expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
expect(page).not_to have_content "You won't be able to pull or push repositories via HTTP until you create a personal access token on your account"
end
end
end
@ -65,7 +65,7 @@ RSpec.describe 'No Password Alert' do
end
it 'shows no alert' do
expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you"
expect(page).not_to have_content "You won't be able to pull or push repositories via HTTP until you"
end
end
end

View File

@ -1,21 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Projects > Wiki > User views Git access wiki page' do
let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, :public) }
let(:wiki_page) { create(:wiki_page, wiki: project.wiki, title: 'home', content: '[some link](other-page)') }
before do
sign_in(user)
end
it 'Visit Wiki Page Current Commit' do
visit project_wiki_path(project, wiki_page)
click_link 'Clone repository'
expect(page).to have_text("Clone repository #{project.wiki.full_path}")
expect(page).to have_text(project.wiki.http_url_to_repo)
end
end

View File

@ -17,4 +17,5 @@ RSpec.describe 'Project wikis' do
it_behaves_like 'User views a wiki page'
it_behaves_like 'User views wiki pages'
it_behaves_like 'User views wiki sidebar'
it_behaves_like 'User views Git access wiki page'
end

View File

@ -8,7 +8,7 @@ RSpec.describe Ci::PipelineSchedulesFinder do
let!(:active_schedule) { create(:ci_pipeline_schedule, project: project) }
let!(:inactive_schedule) { create(:ci_pipeline_schedule, :inactive, project: project) }
subject { described_class.new(project).execute(params) }
subject { described_class.new(project).execute(**params) }
describe "#execute" do
context 'when the scope is nil' do

View File

@ -18,7 +18,7 @@ RSpec.describe FeatureFlagsFinder do
end
describe '#execute' do
subject { finder.execute(args) }
subject { finder.execute(**args) }
let!(:feature_flag_1) { create(:operations_feature_flag, name: 'flag-a', project: project) }
let!(:feature_flag_2) { create(:operations_feature_flag, name: 'flag-b', project: project) }

View File

@ -1,4 +1,5 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlFormCheckbox } from '@gitlab/ui';
import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
import { SQUASH_BEFORE_MERGE } from '~/vue_merge_request_widget/i18n';
@ -20,17 +21,15 @@ describe('Squash before merge component', () => {
wrapper.destroy();
});
const findLabel = () => wrapper.find('[data-testid="squashLabel"]');
const findCheckbox = () => wrapper.find(GlFormCheckbox);
describe('checkbox', () => {
const findCheckbox = () => wrapper.find('.js-squash-checkbox');
it('is unchecked if passed value prop is false', () => {
createComponent({
value: false,
});
expect(findCheckbox().element.checked).toBeFalsy();
expect(findCheckbox().vm.$attrs.checked).toBe(false);
});
it('is checked if passed value prop is true', () => {
@ -38,22 +37,7 @@ describe('Squash before merge component', () => {
value: true,
});
expect(findCheckbox().element.checked).toBeTruthy();
});
it('changes value on click', done => {
createComponent({
value: false,
});
findCheckbox().element.checked = true;
findCheckbox().trigger('change');
wrapper.vm.$nextTick(() => {
expect(findCheckbox().element.checked).toBeTruthy();
done();
});
expect(findCheckbox().vm.$attrs.checked).toBe(true);
});
it('is disabled if isDisabled prop is true', () => {
@ -62,31 +46,12 @@ describe('Squash before merge component', () => {
isDisabled: true,
});
expect(findCheckbox().attributes('disabled')).toBeTruthy();
});
});
describe('label', () => {
describe.each`
isDisabled | expectation
${true} | ${'grays out text if it is true'}
${false} | ${'does not gray out text if it is false'}
`('isDisabled prop', ({ isDisabled, expectation }) => {
beforeEach(() => {
createComponent({
value: false,
isDisabled,
});
});
it(expectation, () => {
expect(findLabel().classes('gl-text-gray-400')).toBe(isDisabled);
});
expect(findCheckbox().vm.$attrs.disabled).toBe(true);
});
});
describe('tooltip', () => {
const tooltipTitle = () => findLabel().attributes('title');
const tooltipTitle = () => findCheckbox().attributes('title');
it('does not render when isDisabled is false', () => {
createComponent({
@ -114,7 +79,7 @@ describe('Squash before merge component', () => {
const aboutLink = wrapper.find('a');
expect(aboutLink.exists()).toBeFalsy();
expect(aboutLink.exists()).toBe(false);
});
it('is rendered if help path is passed', () => {
@ -125,7 +90,7 @@ describe('Squash before merge component', () => {
const aboutLink = wrapper.find('a');
expect(aboutLink.exists()).toBeTruthy();
expect(aboutLink.exists()).toBe(true);
});
it('should have a correct help path if passed', () => {

View File

@ -11,7 +11,7 @@ RSpec.describe Mutations::AlertManagement::HttpIntegration::Destroy do
specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
describe '#resolve' do
subject(:resolve) { mutation_for(project, current_user).resolve(args) }
subject(:resolve) { mutation_for(project, current_user).resolve(**args) }
context 'user has access to project' do
before do

View File

@ -11,7 +11,7 @@ RSpec.describe Mutations::AlertManagement::HttpIntegration::ResetToken do
specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
describe '#resolve' do
subject(:resolve) { mutation_for(project, current_user).resolve(args) }
subject(:resolve) { mutation_for(project, current_user).resolve(**args) }
context 'user has sufficient access to project' do
before do

View File

@ -11,7 +11,7 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::ResetToken do
specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
describe '#resolve' do
subject(:resolve) { mutation_for(project, current_user).resolve(args) }
subject(:resolve) { mutation_for(project, current_user).resolve(**args) }
context 'user has sufficient access to project' do
before do

View File

@ -12,7 +12,7 @@ RSpec.describe Mutations::AlertManagement::UpdateAlertStatus do
specify { expect(described_class).to require_graphql_authorizations(:update_alert_management_alert) }
describe '#resolve' do
subject(:resolve) { mutation_for(project, current_user).resolve(args) }
subject(:resolve) { mutation_for(project, current_user).resolve(**args) }
context 'user has access to project' do
before do

View File

@ -34,18 +34,18 @@ RSpec.describe Mutations::Boards::Issues::IssueMoveList do
end
subject do
mutation.resolve(params.merge(move_params))
mutation.resolve(**params.merge(move_params))
end
describe '#ready?' do
it 'raises an error if required arguments are missing' do
expect { mutation.ready?(params) }
expect { mutation.ready?(**params) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "At least one of the arguments " \
"fromListId, toListId, afterId or beforeId is required")
end
it 'raises an error if only one of fromListId and toListId is present' do
expect { mutation.ready?(params.merge(from_list_id: list1.id)) }
expect { mutation.ready?(**params.merge(from_list_id: list1.id)) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError,
'Both fromListId and toListId must be present'
)

View File

@ -14,7 +14,7 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do
specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) }
describe '#resolve' do
subject { described_class.new(object: project, context: { current_user: user }, field: nil).resolve(params) }
subject { described_class.new(object: project, context: { current_user: user }, field: nil).resolve(**params) }
RSpec.shared_examples 'returning a success' do
it 'returns the container expiration policy with no errors' do

View File

@ -11,7 +11,7 @@ RSpec.describe Mutations::Discussions::ToggleResolve do
describe '#resolve' do
subject do
mutation.resolve({ id: id_arg, resolve: resolve_arg })
mutation.resolve(id: id_arg, resolve: resolve_arg)
end
let(:id_arg) { discussion.to_global_id.to_s }

View File

@ -51,7 +51,7 @@ RSpec.describe Mutations::Issues::Create do
project.add_guest(assignee2)
end
subject { mutation.resolve(mutation_params) }
subject { mutation.resolve(**mutation_params) }
context 'when the user does not have permission to create an issue' do
it 'raises an error' do
@ -117,7 +117,7 @@ RSpec.describe Mutations::Issues::Create do
end
it 'raises exception when mutually exclusive params are given' do
expect { mutation.ready?(mutation_params) }
expect { mutation.ready?(**mutation_params) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
end
end
@ -128,7 +128,7 @@ RSpec.describe Mutations::Issues::Create do
end
it 'raises exception when mutually exclusive params are given' do
expect { mutation.ready?(mutation_params) }
expect { mutation.ready?(**mutation_params) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /to resolve a discussion please also provide `merge_request_to_resolve_discussions_of` parameter/)
end
end
@ -139,7 +139,7 @@ RSpec.describe Mutations::Issues::Create do
end
it 'raises exception when mutually exclusive params are given' do
expect { mutation.ready?(mutation_params) }.not_to raise_error
expect { mutation.ready?(**mutation_params) }.not_to raise_error
end
end
end

View File

@ -58,7 +58,7 @@ RSpec.describe Mutations::Labels::Create do
end
describe '#ready?' do
subject { mutation.ready?(attributes.merge(extra_params)) }
subject { mutation.ready?(**attributes.merge(extra_params)) }
context 'when passing both project_path and group_path' do
let(:extra_params) { { project_path: 'foo', group_path: 'bar' } }

View File

@ -7,7 +7,7 @@ RSpec.describe Mutations::Notes::RepositionImageDiffNote do
describe '#resolve' do
subject do
mutation.resolve({ note: note, position: new_position })
mutation.resolve(note: note, position: new_position)
end
let_it_be(:noteable) { create(:merge_request) }

View File

@ -116,9 +116,9 @@ RSpec.describe ApplicationHelper do
Time.use_zone('UTC') { example.run }
end
def element(*arguments)
def element(**arguments)
@time = Time.zone.parse('2015-07-02 08:23')
element = helper.time_ago_with_tooltip(@time, *arguments)
element = helper.time_ago_with_tooltip(@time, **arguments)
Nokogiri::HTML::DocumentFragment.parse(element).first_element_child
end

View File

@ -89,7 +89,7 @@ RSpec.describe ButtonHelper do
it 'shows a warning on the dropdown description' do
description = element.search('.dropdown-menu-inner-content').first
expect(description.inner_text).to eq "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
expect(description.inner_text).to eq "You won't be able to pull or push repositories via SSH until you add an SSH key to your profile"
end
end

View File

@ -104,6 +104,37 @@ RSpec.describe SearchHelper do
})
end
it 'includes the users recently viewed issues with the exact same name', :aggregate_failures do
recent_issues = instance_double(::Gitlab::Search::RecentIssues)
expect(::Gitlab::Search::RecentIssues).to receive(:new).with(user: user).and_return(recent_issues)
project1 = create(:project, namespace: user.namespace)
project2 = create(:project, namespace: user.namespace)
issue1 = create(:issue, title: 'issue same_name', project: project1)
issue2 = create(:issue, title: 'issue same_name', project: project2)
expect(recent_issues).to receive(:search).with('the search term').and_return(Issue.id_in_ordered([issue1.id, issue2.id]))
results = search_autocomplete_opts("the search term")
expect(results.count).to eq(2)
expect(results[0]).to include({
category: 'Recent issues',
id: issue1.id,
label: 'issue same_name',
url: Gitlab::Routing.url_helpers.project_issue_path(issue1.project, issue1),
avatar_url: '' # This project didn't have an avatar so set this to ''
})
expect(results[1]).to include({
category: 'Recent issues',
id: issue2.id,
label: 'issue same_name',
url: Gitlab::Routing.url_helpers.project_issue_path(issue2.project, issue2),
avatar_url: '' # This project didn't have an avatar so set this to ''
})
end
it 'includes the users recently viewed merge requests', :aggregate_failures do
recent_merge_requests = instance_double(::Gitlab::Search::RecentMergeRequests)
expect(::Gitlab::Search::RecentMergeRequests).to receive(:new).with(user: user).and_return(recent_merge_requests)

View File

@ -15,10 +15,10 @@ RSpec.describe Gitlab::Cleanup::ProjectUploads do
describe '#run!' do
shared_examples_for 'moves the file' do
shared_examples_for 'a real run' do
let(:args) { [dry_run: false] }
let(:args) { { dry_run: false } }
it 'moves the file to its proper location' do
subject.run!(*args)
subject.run!(**args)
expect(File.exist?(path)).to be_falsey
expect(File.exist?(new_path)).to be_truthy
@ -28,13 +28,13 @@ RSpec.describe Gitlab::Cleanup::ProjectUploads do
expect(logger).to receive(:info).with("Looking for orphaned project uploads to clean up...")
expect(logger).to receive(:info).with("Did #{action}")
subject.run!(*args)
subject.run!(**args)
end
end
shared_examples_for 'a dry run' do
it 'does not move the file' do
subject.run!(*args)
subject.run!(**args)
expect(File.exist?(path)).to be_truthy
expect(File.exist?(new_path)).to be_falsey
@ -44,30 +44,30 @@ RSpec.describe Gitlab::Cleanup::ProjectUploads do
expect(logger).to receive(:info).with("Looking for orphaned project uploads to clean up. Dry run...")
expect(logger).to receive(:info).with("Can #{action}")
subject.run!(*args)
subject.run!(**args)
end
end
context 'when dry_run is false' do
let(:args) { [dry_run: false] }
let(:args) { { dry_run: false } }
it_behaves_like 'a real run'
end
context 'when dry_run is nil' do
let(:args) { [dry_run: nil] }
let(:args) { { dry_run: nil } }
it_behaves_like 'a real run'
end
context 'when dry_run is true' do
let(:args) { [dry_run: true] }
let(:args) { { dry_run: true } }
it_behaves_like 'a dry run'
end
context 'with dry_run not specified' do
let(:args) { [] }
let(:args) { {} }
it_behaves_like 'a dry run'
end

View File

@ -130,6 +130,16 @@ RSpec.describe Gitlab::Database::BatchCount do
expect(described_class.batch_count(model, start: model.minimum(:id), finish: model.maximum(:id))).to eq(5)
end
it 'stops counting when finish value is reached' do
stub_const('Gitlab::Database::BatchCounter::MIN_REQUIRED_BATCH_SIZE', 0)
expect(described_class.batch_count(model,
start: model.minimum(:id),
finish: model.maximum(:id) - 1, # Do not count the last record
batch_size: model.count - 2 # Ensure there are multiple batches
)).to eq(model.count - 1)
end
it "defaults the batch size to #{Gitlab::Database::BatchCounter::DEFAULT_BATCH_SIZE}" do
min_id = model.minimum(:id)
relation = instance_double(ActiveRecord::Relation)
@ -242,6 +252,19 @@ RSpec.describe Gitlab::Database::BatchCount do
expect(described_class.batch_distinct_count(model, column, start: model.minimum(column), finish: model.maximum(column))).to eq(2)
end
it 'stops counting when finish value is reached' do
# Create a new unique author that should not be counted
create(:issue)
stub_const('Gitlab::Database::BatchCounter::MIN_REQUIRED_BATCH_SIZE', 0)
expect(described_class.batch_distinct_count(model, column,
start: User.minimum(:id),
finish: User.maximum(:id) - 1, # Do not count the newly created issue
batch_size: model.count - 2 # Ensure there are multiple batches
)).to eq(2)
end
it 'counts with User min and max as start and finish' do
expect(described_class.batch_distinct_count(model, column, start: User.minimum(:id), finish: User.maximum(:id))).to eq(2)
end

View File

@ -357,7 +357,7 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
end
it 'sends an RPC request with the correct payload' do
expect(client.commits_by_message(query, options)).to match_array(wrap_commits(commits))
expect(client.commits_by_message(query, **options)).to match_array(wrap_commits(commits))
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20201026185514_ensure_u2f_registrations_migrated.rb')
RSpec.describe EnsureU2fRegistrationsMigrated, schema: 20201022144501 do
let(:u2f_registrations) { table(:u2f_registrations) }
let(:webauthn_registrations) { table(:webauthn_registrations) }
let(:users) { table(:users) }
let(:user) { users.create!(email: 'email@email.com', name: 'foo', username: 'foo', projects_limit: 0) }
before do
create_u2f_registration(1, 'reg1')
create_u2f_registration(2, 'reg2')
webauthn_registrations.create!({ name: 'reg1', u2f_registration_id: 1, credential_xid: '', public_key: '', user_id: user.id })
end
it 'correctly migrates u2f registrations previously not migrated' do
expect { migrate! }.to change { webauthn_registrations.count }.from(1).to(2)
end
it 'migrates all valid u2f registrations depite errors' do
create_u2f_registration(3, 'reg3', 'invalid!')
create_u2f_registration(4, 'reg4')
expect { migrate! }.to change { webauthn_registrations.count }.from(1).to(3)
end
def create_u2f_registration(id, name, public_key = nil)
device = U2F::FakeU2F.new(FFaker::BaconIpsum.characters(5), { key_handle: SecureRandom.random_bytes(255) })
public_key ||= Base64.strict_encode64(device.origin_public_key_raw)
u2f_registrations.create!({ id: id,
certificate: Base64.strict_encode64(device.cert_raw),
key_handle: U2F.urlsafe_encode64(device.key_handle_raw),
public_key: public_key,
counter: 5,
name: name,
user_id: user.id })
end
end

View File

@ -242,7 +242,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
}
expect { authorize_artifacts_with_token_in_headers(artifact_type: :lsif) }
.to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(tracking_params) }
.to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(**tracking_params) }
.by(1)
end
end

View File

@ -428,7 +428,7 @@ RSpec.describe API::GoProxy do
context 'with a non-existent project' do
def get_resource(user = nil, **params)
get api("/projects/not%2fa%2fproject/packages/go/#{base}/@v/list", user, params)
get api("/projects/not%2fa%2fproject/packages/go/#{base}/@v/list", user, **params)
end
describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
@ -465,7 +465,7 @@ RSpec.describe API::GoProxy do
end
def get_resource(user = nil, headers: {}, **params)
get api("/projects/#{project.id}/packages/go/#{module_name}/@v/#{resource}", user, params), headers: headers
get api("/projects/#{project.id}/packages/go/#{module_name}/@v/#{resource}", user, **params), headers: headers
end
def fmt_pseudo_version(prefix, commit)

View File

@ -13,7 +13,7 @@ RSpec.describe EventCreateService do
tracking_params = { event_action: event_action, date_from: Date.yesterday, date_to: Date.today }
expect { subject }
.to change { Gitlab::UsageDataCounters::TrackUniqueEvents.count_unique_events(tracking_params) }
.to change { Gitlab::UsageDataCounters::TrackUniqueEvents.count_unique_events(**tracking_params) }
.by(1)
end
end
@ -386,7 +386,7 @@ RSpec.describe EventCreateService do
counter_class = Gitlab::UsageDataCounters::TrackUniqueEvents
tracking_params = { event_action: event_action, date_from: Date.yesterday, date_to: Date.today }
expect { subject }.not_to change { counter_class.count_unique_events(tracking_params) }
expect { subject }.not_to change { counter_class.count_unique_events(**tracking_params) }
end
end
end

View File

@ -91,7 +91,7 @@ RSpec.describe Projects::MoveAccessService do
it 'does not remove remaining memberships' do
target_project.add_maintainer(maintainer_user)
subject.execute(project_with_access, options)
subject.execute(project_with_access, **options)
expect(project_with_access.project_members.count).not_to eq 0
end
@ -99,7 +99,7 @@ RSpec.describe Projects::MoveAccessService do
it 'does not remove remaining group links' do
target_project.project_group_links.create!(group: maintainer_group, group_access: Gitlab::Access::MAINTAINER)
subject.execute(project_with_access, options)
subject.execute(project_with_access, **options)
expect(project_with_access.project_group_links.count).not_to eq 0
end
@ -107,7 +107,7 @@ RSpec.describe Projects::MoveAccessService do
it 'does not remove remaining authorizations' do
target_project.add_developer(developer_user)
subject.execute(project_with_access, options)
subject.execute(project_with_access, **options)
expect(project_with_access.project_authorizations.count).not_to eq 0
end

View File

@ -51,7 +51,7 @@ RSpec.describe Projects::MoveDeployKeysProjectsService do
it 'does not remove remaining deploy keys projects' do
target_project.deploy_keys << project_with_deploy_keys.deploy_keys.first
subject.execute(project_with_deploy_keys, options)
subject.execute(project_with_deploy_keys, **options)
expect(project_with_deploy_keys.deploy_keys_projects.count).not_to eq 0
end

View File

@ -48,7 +48,7 @@ RSpec.describe Projects::MoveLfsObjectsProjectsService do
it 'does not remove remaining lfs objects' do
target_project.lfs_objects << project_with_lfs_objects.lfs_objects.first(2)
subject.execute(project_with_lfs_objects, options)
subject.execute(project_with_lfs_objects, **options)
expect(project_with_lfs_objects.lfs_objects.count).not_to eq 0
end

View File

@ -49,7 +49,7 @@ RSpec.describe Projects::MoveNotificationSettingsService do
let(:options) { { remove_remaining_elements: false } }
it 'does not remove remaining notification settings' do
subject.execute(project_with_notifications, options)
subject.execute(project_with_notifications, **options)
expect(project_with_notifications.notification_settings.count).not_to eq 0
end

View File

@ -49,7 +49,7 @@ RSpec.describe Projects::MoveProjectAuthorizationsService do
target_project.add_maintainer(developer_user)
target_project.add_developer(reporter_user)
subject.execute(project_with_users, options)
subject.execute(project_with_users, **options)
expect(project_with_users.project_authorizations.count).not_to eq 0
end

View File

@ -58,7 +58,7 @@ RSpec.describe Projects::MoveProjectGroupLinksService do
target_project.project_group_links.create!(group: maintainer_group, group_access: Gitlab::Access::MAINTAINER)
target_project.project_group_links.create!(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
subject.execute(project_with_groups, options)
subject.execute(project_with_groups, **options)
expect(project_with_groups.project_group_links.count).not_to eq 0
end

View File

@ -58,7 +58,7 @@ RSpec.describe Projects::MoveProjectMembersService do
target_project.add_maintainer(developer_user)
target_project.add_developer(reporter_user)
subject.execute(project_with_users, options)
subject.execute(project_with_users, **options)
expect(project_with_users.project_members.count).not_to eq 0
end

View File

@ -20,7 +20,7 @@ RSpec.describe Suggestions::ApplyService do
position_args = args.slice(:old_path, :new_path, :old_line, :new_line)
content_args = args.slice(:from_content, :to_content)
position = build_position(position_args)
position = build_position(**position_args)
diff_note = create(:diff_note_on_merge_request,
noteable: merge_request,

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
RSpec.shared_examples 'User views Git access wiki page' do
let(:wiki_page) { create(:wiki_page, wiki: wiki) }
before do
sign_in(user)
end
it 'shows the correct clone URLs', :js do
visit wiki_page_path(wiki, wiki_page)
click_link 'Clone repository'
expect(page).to have_text("Clone repository #{wiki.full_path}")
within('.git-clone-holder') do
expect(page).to have_css('#clone-dropdown', text: 'HTTP')
expect(page).to have_field('clone_url', with: wiki.http_url_to_repo)
click_link 'HTTP' # open the dropdown
click_link 'SSH' # select the dropdown item
expect(page).to have_css('#clone-dropdown', text: 'SSH')
expect(page).to have_field('clone_url', with: wiki.ssh_url_to_repo)
end
end
end