Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0d312b8d37
commit
4372b0ca29
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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')) {
|
||||
|
|
|
@ -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"
|
||||
:title="tooltipTitle"
|
||||
>
|
||||
<input
|
||||
:checked="value"
|
||||
:disabled="isDisabled"
|
||||
type="checkbox"
|
||||
name="squash"
|
||||
class="qa-squash-checkbox js-squash-checkbox"
|
||||
@change="$emit('input', $event.target.checked)"
|
||||
/>
|
||||
class="qa-squash-checkbox js-squash-checkbox gl-mb-0 gl-mr-2"
|
||||
:title="tooltipTitle"
|
||||
@change="checked => $emit('input', checked)"
|
||||
>
|
||||
{{ $options.i18n.checkboxLabel }}
|
||||
</label>
|
||||
</gl-form-checkbox>
|
||||
<a
|
||||
v-if="helpPath"
|
||||
v-gl-tooltip
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -31,7 +31,7 @@ module SearchHelper
|
|||
[
|
||||
resources_results,
|
||||
generic_results
|
||||
].flatten.uniq do |item|
|
||||
].flatten do |item|
|
||||
item[:label]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
-# 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" }
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix repository clone panel for wikis
|
||||
merge_request: 47676
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add GlFormCheckbox to squash commits
|
||||
merge_request: 48338
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Cleanup webauthn background migration
|
||||
merge_request: 46179
|
||||
author: Jan Beckmann
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove .issue-box from static (classic) Issuable list
|
||||
merge_request: 47998
|
||||
author: Takuya Noguchi
|
||||
type: performance
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix missing item with same name in autocomplete suggestions
|
||||
merge_request: 48410
|
||||
author: Paul Ungureanu @ungps
|
||||
type: fixed
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
a9ae0161c40b9c72371d6eb992bd0da8c3698e7784357faac0821e3f513e48d2
|
|
@ -0,0 +1 @@
|
|||
a39bad8b213833c84370cf64188a3ce444fd8aeeff239c29f5f2f633d94ac6bb
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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. |
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 ' \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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' } }
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue