Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-21 09:10:16 +00:00
parent 1cf90c7008
commit 34ad6d995b
44 changed files with 314 additions and 523 deletions

View File

@ -54,6 +54,7 @@ review-deploy:
extends:
- .review-workflow-base
- .review:rules:review-deploy
retry: 2
stage: review
needs: ["review-build-cng"]
resource_group: "review/${CI_COMMIT_REF_NAME}"

View File

@ -51,7 +51,6 @@ export default {
</template>
<div
v-if="issuableCount > -1"
data-testid="issuable-count-note"
class="gl-justify-content-start gl-align-items-center gl-p-4 gl-border-b-solid gl-border-1 gl-border-gray-50"
>
<gl-icon name="check" class="gl-color-green-400" />

View File

@ -72,9 +72,6 @@ export default {
importModalId() {
return `${this.issuableType}-import-modal`;
},
importButtonText() {
return this.showLabel ? this.$options.i18n.importIssuesText : null;
},
importButtonTooltipText() {
return this.showLabel ? null : this.$options.i18n.importIssuesText;
},
@ -90,29 +87,25 @@ export default {
<gl-button-group>
<gl-button
v-if="showExportButton"
v-gl-tooltip.hover="$options.i18n.exportAsCsvButtonText"
v-gl-tooltip="$options.i18n.exportAsCsvButtonText"
v-gl-modal="exportModalId"
icon="export"
:aria-label="$options.i18n.exportAsCsvButtonText"
data-qa-selector="export_as_csv_button"
data-testid="export-csv-button"
/>
<gl-dropdown
v-if="showImportButton"
v-gl-tooltip.hover="importButtonTooltipText"
v-gl-tooltip="importButtonTooltipText"
data-qa-selector="import_issues_dropdown"
data-testid="import-csv-dropdown"
:text="importButtonText"
:text="$options.i18n.importIssuesText"
:text-sr-only="!showLabel"
:icon="importButtonIcon"
>
<gl-dropdown-item v-gl-modal="importModalId" data-testid="import-csv-link">{{
__('Import CSV')
}}</gl-dropdown-item>
<gl-dropdown-item v-gl-modal="importModalId">{{ __('Import CSV') }}</gl-dropdown-item>
<gl-dropdown-item
v-if="canEdit"
:href="projectImportJiraPath"
data-qa-selector="import_from_jira_link"
data-testid="import-from-jira-link"
>{{ __('Import from Jira') }}</gl-dropdown-item
>
</gl-dropdown>

View File

@ -48,13 +48,7 @@ export default {
<template>
<gl-modal :modal-id="modalId" :title="__('Import issues')">
<form
ref="form"
:action="importCsvIssuesPath"
enctype="multipart/form-data"
method="post"
data-testid="import-csv-form"
>
<form ref="form" :action="importCsvIssuesPath" enctype="multipart/form-data" method="post">
<input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
<p>
{{

View File

@ -91,7 +91,7 @@ export default {
<template>
<div>
<gl-button v-gl-modal="$options.modalId" variant="link" data-testid="issuable-email-modal-btn"
<gl-button v-gl-modal="$options.modalId" variant="link"
><gl-sprintf :message="__('Email a new %{name} to this project')"
><template #name>{{ issuableName }}</template></gl-sprintf
></gl-button
@ -122,7 +122,6 @@ export default {
:title="$options.i18n.sendEmail"
:aria-label="$options.i18n.sendEmail"
icon="mail"
data-testid="mail-to-btn"
/>
</template>
</gl-form-input-group>
@ -156,12 +155,7 @@ export default {
/></gl-link>
</template>
<template #resetLink="{ content }">
<gl-button
variant="link"
data-testid="incoming-email-token-reset"
@click="resetIncomingEmailToken"
>{{ content }}</gl-button
>
<gl-button variant="link" @click="resetIncomingEmailToken">{{ content }}</gl-button>
</template>
</gl-sprintf>
</p>

View File

@ -91,11 +91,7 @@ export default {
<template>
<div :class="statusBoxClass" class="issuable-status-box status-box">
<gl-icon
:name="statusIconName"
class="gl-display-block gl-sm-display-none!"
data-testid="status-icon"
/>
<gl-icon :name="statusIconName" class="gl-display-block gl-sm-display-none!" />
<span class="gl-display-none gl-sm-display-block">
{{ statusHumanName }}
</span>

View File

@ -484,13 +484,17 @@ export const setUrlParams = (
searchParams.delete(key);
} else if (Array.isArray(params[key])) {
const keyName = railsArraySyntax ? `${key}[]` : key;
params[key].forEach((val, idx) => {
if (idx === 0) {
searchParams.set(keyName, val);
} else {
searchParams.append(keyName, val);
}
});
if (params[key].length === 0) {
searchParams.delete(keyName);
} else {
params[key].forEach((val, idx) => {
if (idx === 0) {
searchParams.set(keyName, val);
} else {
searchParams.append(keyName, val);
}
});
}
} else {
searchParams.set(key, params[key]);
}

View File

@ -4,7 +4,8 @@
.gfm-commit,
.gfm-commit_range {
@extend .commit-sha;
@include gl-font-monospace;
font-size: 95%;
}
.gfm-project_member {

View File

@ -45,7 +45,6 @@ input[type='checkbox']:hover {
margin: 0 8px;
form {
@extend .form-control;
margin: 0;
padding: 4px;
width: $search-input-width;
@ -139,7 +138,6 @@ input[type='checkbox']:hover {
&.search-active {
form {
@extend .form-control:focus;
border-color: $blue-300;
box-shadow: none;

View File

@ -83,7 +83,6 @@
}
}
.gl-text-purple { color: $purple; }
.gl-bg-purple-light { background-color: $purple-light; }
// move this to GitLab UI once onboarding experiment is considered a success

View File

@ -1,5 +1,5 @@
.search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input", track_value: "" } }
= form_tag search_path, method: :get, class: 'form-inline' do |_f|
= form_tag search_path, method: :get, class: 'form-inline form-control' do |_f|
.search-input-container
.search-input-wrap
.dropdown{ data: { url: search_autocomplete_path } }

View File

@ -0,0 +1,5 @@
---
title: Remove UnicornCheck service
merge_request: 62204
author:
type: removed

View File

@ -0,0 +1,5 @@
---
title: Simplify error code handling for external pipeline validation
merge_request: 61190
author:
type: changed

View File

@ -7,8 +7,6 @@
#
# Follow-up the issue: https://gitlab.com/gitlab-org/gitlab/issues/34107
if Gitlab::Runtime.puma?
Puma::Cluster.prepend(::Gitlab::Cluster::Mixins::PumaCluster)
elsif Gitlab::Runtime.unicorn?
Unicorn::HttpServer.prepend(::Gitlab::Cluster::Mixins::UnicornHttpServer)
end
return unless Gitlab::Runtime.puma?
Puma::Cluster.prepend(::Gitlab::Cluster::Mixins::PumaCluster)

View File

@ -17,7 +17,7 @@ data as payload. The response code from the external service determines if GitLa
should accept or reject the pipeline. If the response is:
- `200`, the pipeline is accepted.
- `4XX`, the pipeline is rejected.
- `406`, the pipeline is rejected.
- Other codes, the pipeline is accepted and logged.
If there's an error or the request times out, the pipeline is accepted.

View File

@ -45,6 +45,15 @@ You can select the branch in the version dropdown in the top left corner of GitL
If the highest number stable branch is unclear, check the [GitLab blog](https://about.gitlab.com/blog/) for installation guide links by version.
## Software requirements
| Software | Minimum version | Notes |
| -------- | --------------- | ----- |
| [Ruby](#2-ruby) | `2.7` | From GitLab 13.6, Ruby 2.7 is required. Ruby 3.0 is not supported yet (see [the relevant epic](https://gitlab.com/groups/gitlab-org/-/epics/5149) for the current status). You must use the standard MRI implementation of Ruby. We love [JRuby](https://www.jruby.org/) and [Rubinius](https://github.com/rubinius/rubinius#the-rubinius-language-platform), but GitLab needs several Gems that have native extensions. |
| [Go](#3-go) | `1.15` | |
| [Git](#git) | `2.31.x` | From GitLab 13.11, Git 2.31.x and later is required. It's highly recommended that you use the [Git version provided by Gitaly](#git). |
| [Node.js](#4-node) | `12.22.1` | GitLab uses [webpack](https://webpack.js.org/) to compile frontend assets. Node.js 14.x is recommended, as it's faster. You can check which version you're running with `node -v`. You need to update it to a newer version if needed. |
## GitLab directory structure
This is the main directory structure you end up with following the instructions
@ -207,7 +216,7 @@ sudo apt-get install -y libimage-exiftool-perl
## 2. Ruby
The Ruby interpreter is required to run GitLab.
See the [requirements page](requirements.md#ruby-versions) for the minimum
See the [requirements section of this page](#software-requirements) for the minimum
Ruby requirements.
The use of Ruby version managers such as [`RVM`](https://rvm.io/), [`rbenv`](https://github.com/rbenv/rbenv) or [`chruby`](https://github.com/postmodern/chruby) with GitLab

View File

@ -47,55 +47,6 @@ Please consider using a virtual machine to run GitLab.
## Software requirements
### Ruby versions
From GitLab 13.6:
- Ruby 2.7 and later is required.
From GitLab 12.2:
- Ruby 2.6 and later is required.
You must use the standard MRI implementation of Ruby.
We love [JRuby](https://www.jruby.org/) and [Rubinius](https://github.com/rubinius/rubinius#the-rubinius-language-platform), but GitLab
needs several Gems that have native extensions.
### Go versions
The minimum required Go version is 1.13.
### Git versions
From GitLab 13.11:
- Git 2.31.x and later is required. We recommend you use the
[Git version provided by Gitaly](installation.md#git).
From GitLab 13.6:
- Git 2.29.x and later is required.
From GitLab 13.1:
- Git 2.24.x and later is required.
- Git 2.28.x and later [is recommended](https://gitlab.com/gitlab-org/gitaly/-/issues/2959).
### Node.js versions
Beginning in GitLab 12.9, we only support Node.js 10.13.0 or higher, and we have dropped
support for Node.js 8. (Node.js 6 support was dropped in GitLab 11.8)
We recommend Node 14.x, as it's faster.
GitLab uses [webpack](https://webpack.js.org/) to compile frontend assets, which requires a minimum
version of Node.js 10.13.0.
You can check which version you're running with `node -v`. If you're running
a version older than `v10.13.0`, you need to update it to a newer version. You
can find instructions to install from community maintained packages or compile
from source at the [Node.js website](https://nodejs.org/en/download/).
### Redis versions
GitLab 13.0 and later requires Redis version 4.0 or higher.

View File

@ -82,7 +82,7 @@ sudo make install
### 4. Update Node.js
To check the minimum required Node.js version, see [Node.js versions](../install/requirements.md#nodejs-versions).
To check the minimum required Node.js version, see [Node.js versions](../install/installation.md#software-requirements).
GitLab also requires the use of Yarn `>= v1.10.0` to manage JavaScript
dependencies.
@ -99,7 +99,7 @@ More information can be found on the [Yarn website](https://classic.yarnpkg.com/
### 5. Update Go
To check the minimum required Go version, see [Go versions](../install/requirements.md#go-versions).
To check the minimum required Go version, see [Go versions](../install/installation.md#software-requirements).
You can check which version you are running with `go version`.
@ -119,12 +119,8 @@ rm go1.13.5.linux-amd64.tar.gz
### 6. Update Git
WARNING:
From GitLab 13.1, you must use at least Git v2.24 (previous minimum version was v2.22).
Git v2.28 is recommended.
To check you are running the minimum required Git version, see
[Git versions](../install/requirements.md#git-versions).
[Git versions](../install/installation.md#software-requirements).
From GitLab 13.6, we recommend you use the [Git version provided by
Gitaly](https://gitlab.com/gitlab-org/gitaly/-/issues/2729)

View File

@ -12,8 +12,7 @@ module Gitlab
DEFAULT_VALIDATION_REQUEST_TIMEOUT = 5
ACCEPTED_STATUS = 200
DOT_COM_REJECTED_STATUS = 406
GENERAL_REJECTED_STATUS = (400..499).freeze
REJECTED_STATUS = 406
def perform!
pipeline_authorized = validate_external
@ -34,14 +33,13 @@ module Gitlab
return true unless validation_service_url
# 200 - accepted
# 406 - not accepted on GitLab.com
# 4XX - not accepted for other installations
# 406 - rejected
# everything else - accepted and logged
response_code = validate_service_request.code
case response_code
when ACCEPTED_STATUS
true
when rejected_status
when REJECTED_STATUS
false
else
raise InvalidResponseCode, "Unsupported response code received from Validation Service: #{response_code}"
@ -52,14 +50,6 @@ module Gitlab
true
end
def rejected_status
if Gitlab.com?
DOT_COM_REJECTED_STATUS
else
GENERAL_REJECTED_STATUS
end
end
def validate_service_request
headers = {
'X-Gitlab-Correlation-id' => Labkit::Correlation::CorrelationId.current_id,

View File

@ -1,34 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Cluster
module Mixins
module UnicornHttpServer
def self.prepended(base)
unless base.method_defined?(:reexec) && base.method_defined?(:stop)
raise 'missing method Unicorn::HttpServer#reexec or Unicorn::HttpServer#stop'
end
end
def reexec
Gitlab::Cluster::LifecycleEvents.do_before_graceful_shutdown
super
end
# The stop on non-graceful shutdown is executed twice:
# `#stop(false)` and `#stop`.
#
# The first stop will wipe-out all workers, so we need to check
# the flag and a list of workers
def stop(graceful = true)
if graceful && @workers.any? # rubocop:disable Gitlab/ModuleWithInstanceVariables
Gitlab::Cluster::LifecycleEvents.do_before_graceful_shutdown
end
super
end
end
end
end
end

View File

@ -1,41 +0,0 @@
# frozen_string_literal: true
module Gitlab
module HealthChecks
# This check can only be run on Unicorn `master` process
class UnicornCheck
extend SimpleAbstractCheck
class << self
include Gitlab::Utils::StrongMemoize
private
def metric_prefix
'unicorn_check'
end
def successful?(result)
result > 0
end
def check
return unless http_servers
http_servers.sum(&:worker_processes)
end
# Traversal of ObjectSpace is expensive, on fully loaded application
# it takes around 80ms. The instances of HttpServers are not a subject
# to change so we can cache the list of servers.
def http_servers
strong_memoize(:http_servers) do
next unless Gitlab::Runtime.unicorn?
ObjectSpace.each_object(::Unicorn::HttpServer).to_a
end
end
end
end
end
end

View File

@ -30,8 +30,7 @@ module Gitlab
# application: https://gitlab.com/gitlab-org/gitlab/issues/35343
self.readiness_checks = [
WebExporter::ExporterCheck.new(self),
Gitlab::HealthChecks::PumaCheck,
Gitlab::HealthChecks::UnicornCheck
Gitlab::HealthChecks::PumaCheck
]
end

View File

@ -53,7 +53,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "1.197.0",
"@gitlab/tributejs": "1.0.0",
"@gitlab/ui": "29.27.0",
"@gitlab/ui": "29.28.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-4",
"@rails/ujs": "^6.0.3-4",

View File

@ -16,7 +16,7 @@ module QA
Flow::Login.sign_in
end
it 'creates a basic merge request', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1276' do
it 'creates a basic merge request', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1850' do
Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request|
merge_request.project = project
merge_request.title = merge_request_title

View File

@ -3,7 +3,7 @@
module QA
RSpec.describe 'Create' do
describe 'Git push over HTTP', :smoke do
it 'user using a personal access token pushes code to the repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1278' do
it 'user using a personal access token pushes code to the repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1848' do
Flow::Login.sign_in
access_token = Resource::PersonalAccessToken.fabricate!.token

View File

@ -24,7 +24,7 @@ module QA
runner.remove_via_api!
end
it 'users creates a pipeline which gets processed', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1279' do
it 'users creates a pipeline which gets processed', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1849' do
Flow::Login.sign_in
Resource::Repository::Commit.fabricate_via_api! do |commit|

View File

@ -115,7 +115,7 @@ module QA
end
end
it 'runs an AutoDevOps pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1277' do
it 'runs an AutoDevOps pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1847' do
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|

View File

@ -0,0 +1,63 @@
# frozen_string_literal: true
module RuboCop
module Cop
module UsageData
# This cop checks that metric instrumentation classes subclass one of the allowed base classes.
#
# @example
#
# # good
# class CountIssues < DatabaseMetric
# # ...
# end
#
# # bad
# class CountIssues < BaseMetric
# # ...
# end
class InstrumentationSuperclass < RuboCop::Cop::Cop
MSG = "Instrumentation classes should subclass one of the following: %{allowed_classes}."
BASE_PATTERN = "(const nil? !#allowed_class?)"
def_node_matcher :class_definition, <<~PATTERN
(class (const _ !#allowed_class?) #{BASE_PATTERN} ...)
PATTERN
def_node_matcher :class_new_definition, <<~PATTERN
[!^(casgn {nil? cbase} #allowed_class? ...)
!^^(casgn {nil? cbase} #allowed_class? (block ...))
(send (const {nil? cbase} :Class) :new #{BASE_PATTERN})]
PATTERN
def on_class(node)
class_definition(node) do
register_offense(node.children[1])
end
end
def on_send(node)
class_new_definition(node) do
register_offense(node.children.last)
end
end
private
def allowed_class?(class_name)
allowed_classes.include?(class_name)
end
def allowed_classes
cop_config['AllowedClasses'] || []
end
def register_offense(offense_node)
message = format(MSG, allowed_classes: allowed_classes.join(', '))
add_offense(offense_node, message: message)
end
end
end
end
end

View File

@ -57,3 +57,11 @@ UsageData/DistinctCountByLargeForeignKey:
- 'user_id'
- 'resource_owner_id'
- 'requirement_id'
UsageData/InstrumentationSuperclass:
Enabled: true
Include:
- 'lib/gitlab/usage/metrics/instrumentations/**/*.rb'
AllowedClasses:
- :DatabaseMetric
- :GenericMetric
- :RedisHLLMetric

View File

@ -16,9 +16,7 @@ RSpec.describe 'Issues csv', :js do
def request_csv(params = {})
visit project_issues_path(project, params)
page.within('.nav-controls') do
find('[data-testid="export-csv-button"]').click
end
click_button 'Export as CSV'
click_on 'Export issues'
end

View File

@ -16,11 +16,11 @@ RSpec.describe 'Issues > User resets their incoming email token' do
end
it 'changes incoming email address token', :js do
page.find('[data-testid="issuable-email-modal-btn"]').click
click_button 'Email a new issue to this project'
page.within '#issuable-email-modal' do
previous_token = page.find('input[type="text"]').value
page.find('[data-testid="incoming-email-token-reset"]').click
click_button 'reset it'
wait_for_requests

View File

@ -12,15 +12,9 @@ RSpec.describe 'Merge Requests > Exports as CSV', :js do
visit(project_merge_requests_path(project))
end
subject { page.find('.nav-controls') }
it { is_expected.to have_selector '[data-testid="export-csv-button"]' }
context 'button is clicked' do
before do
page.within('.nav-controls') do
find('[data-testid="export-csv-button"]').click
end
click_button 'Export as CSV'
end
it 'shows a success message' do

View File

@ -116,7 +116,8 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do
click_on('Save changes')
end
find('.flash-notice')
wait_for_all_requests
checkbox = find_field('project_printing_merge_request_link_enabled')
expect(checkbox).not_to be_checked
@ -139,7 +140,8 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do
click_on('Save changes')
end
find('.flash-notice')
wait_for_all_requests
checkbox = find_field('project_remove_source_branch_after_merge')
expect(checkbox).not_to be_checked

View File

@ -1,7 +1,6 @@
import { GlModal, GlIcon, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import CsvExportModal from '~/issuable/components/csv_export_modal.vue';
describe('CsvExportModal', () => {
@ -9,26 +8,24 @@ describe('CsvExportModal', () => {
function createComponent(options = {}) {
const { injectedProperties = {}, props = {} } = options;
return extendedWrapper(
mount(CsvExportModal, {
propsData: {
modalId: 'csv-export-modal',
exportCsvPath: 'export/csv/path',
issuableCount: 1,
...props,
},
provide: {
issuableType: 'issues',
...injectedProperties,
},
stubs: {
GlModal: stubComponent(GlModal, {
template:
'<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
}),
},
}),
);
return mount(CsvExportModal, {
propsData: {
modalId: 'csv-export-modal',
exportCsvPath: 'export/csv/path',
issuableCount: 1,
...props,
},
provide: {
issuableType: 'issues',
...injectedProperties,
},
stubs: {
GlModal: stubComponent(GlModal, {
template:
'<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
}),
},
});
}
afterEach(() => {
@ -61,14 +58,13 @@ describe('CsvExportModal', () => {
describe('issuable count info text', () => {
it('displays the info text when issuableCount is > -1', () => {
wrapper = createComponent({ props: { issuableCount: 10 } });
expect(wrapper.findByTestId('issuable-count-note').exists()).toBe(true);
expect(wrapper.findByTestId('issuable-count-note').text()).toContain('10 issues selected');
expect(wrapper.text()).toContain('10 issues selected');
expect(findIcon().exists()).toBe(true);
});
it("doesn't display the info text when issuableCount is -1", () => {
wrapper = createComponent({ props: { issuableCount: -1 } });
expect(wrapper.findByTestId('issuable-count-note').exists()).toBe(false);
expect(wrapper.text()).not.toContain('issues selected');
});
});

View File

@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { GlButton, GlDropdown } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import CsvExportModal from '~/issuable/components/csv_export_modal.vue';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import CsvImportModal from '~/issuable/components/csv_import_modal.vue';
@ -14,35 +14,33 @@ describe('CsvImportExportButtons', () => {
function createComponent(injectedProperties = {}) {
glModalDirective = jest.fn();
return extendedWrapper(
shallowMount(CsvImportExportButtons, {
directives: {
GlTooltip: createMockDirective(),
glModal: {
bind(_, { value }) {
glModalDirective(value);
},
return mountExtended(CsvImportExportButtons, {
directives: {
GlTooltip: createMockDirective(),
glModal: {
bind(_, { value }) {
glModalDirective(value);
},
},
provide: {
...injectedProperties,
},
propsData: {
exportCsvPath,
issuableCount,
},
}),
);
},
provide: {
...injectedProperties,
},
propsData: {
exportCsvPath,
issuableCount,
},
});
}
afterEach(() => {
wrapper.destroy();
});
const findExportCsvButton = () => wrapper.findByTestId('export-csv-button');
const findImportDropdown = () => wrapper.findByTestId('import-csv-dropdown');
const findImportCsvButton = () => wrapper.findByTestId('import-csv-dropdown');
const findImportFromJiraLink = () => wrapper.findByTestId('import-from-jira-link');
const findExportCsvButton = () => wrapper.findComponent(GlButton);
const findImportDropdown = () => wrapper.findComponent(GlDropdown);
const findImportCsvButton = () => wrapper.findByRole('menuitem', { name: 'Import CSV' });
const findImportFromJiraLink = () => wrapper.findByRole('menuitem', { name: 'Import from Jira' });
const findExportCsvModal = () => wrapper.findComponent(CsvExportModal);
const findImportCsvModal = () => wrapper.findComponent(CsvImportModal);
@ -97,7 +95,7 @@ describe('CsvImportExportButtons', () => {
expect(findImportDropdown().exists()).toBe(true);
});
it('renders the import button', () => {
it('renders the import csv menu item', () => {
expect(findImportCsvButton().exists()).toBe(true);
});
@ -106,8 +104,11 @@ describe('CsvImportExportButtons', () => {
wrapper = createComponent({ showImportButton: true, showLabel: false });
});
it('does not have a button text', () => {
expect(findImportCsvButton().props('text')).toBe(null);
it('hides button text', () => {
expect(findImportDropdown().props()).toMatchObject({
text: 'Import issues',
textSrOnly: true,
});
});
it('import button has a tooltip', () => {
@ -124,7 +125,10 @@ describe('CsvImportExportButtons', () => {
});
it('displays a button text', () => {
expect(findImportCsvButton().props('text')).toBe('Import issues');
expect(findImportDropdown().props()).toMatchObject({
text: 'Import issues',
textSrOnly: false,
});
});
it('import button has no tooltip', () => {

View File

@ -1,8 +1,6 @@
import { GlModal } from '@gitlab/ui';
import { getByRole, getByLabelText } from '@testing-library/dom';
import { mount } from '@vue/test-utils';
import { GlButton, GlModal } from '@gitlab/ui';
import { stubComponent } from 'helpers/stub_component';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import CsvImportModal from '~/issuable/components/csv_import_modal.vue';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
@ -13,23 +11,21 @@ describe('CsvImportModal', () => {
function createComponent(options = {}) {
const { injectedProperties = {}, props = {} } = options;
return extendedWrapper(
mount(CsvImportModal, {
propsData: {
modalId: 'csv-import-modal',
...props,
},
provide: {
issuableType: 'issues',
...injectedProperties,
},
stubs: {
GlModal: stubComponent(GlModal, {
template: '<div><slot></slot><slot name="modal-footer"></slot></div>',
}),
},
}),
);
return mountExtended(CsvImportModal, {
propsData: {
modalId: 'csv-import-modal',
...props,
},
provide: {
issuableType: 'issues',
...injectedProperties,
},
stubs: {
GlModal: stubComponent(GlModal, {
template: '<div><slot></slot><slot name="modal-footer"></slot></div>',
}),
},
});
}
beforeEach(() => {
@ -41,9 +37,9 @@ describe('CsvImportModal', () => {
});
const findModal = () => wrapper.findComponent(GlModal);
const findPrimaryButton = () => getByRole(wrapper.element, 'button', { name: 'Import issues' });
const findForm = () => wrapper.findByTestId('import-csv-form');
const findFileInput = () => getByLabelText(wrapper.element, 'Upload CSV file');
const findPrimaryButton = () => wrapper.findComponent(GlButton);
const findForm = () => wrapper.find('form');
const findFileInput = () => wrapper.findByLabelText('Upload CSV file');
const findAuthenticityToken = () => new FormData(findForm().element).get('authenticity_token');
describe('template', () => {
@ -76,8 +72,8 @@ describe('CsvImportModal', () => {
expect(findPrimaryButton()).toExist();
});
it('submits the form when the primary action is clicked', async () => {
findPrimaryButton().click();
it('submits the form when the primary action is clicked', () => {
findPrimaryButton().trigger('click');
expect(formSubmitSpy).toHaveBeenCalled();
});

View File

@ -58,10 +58,11 @@ describe('IssuableByEmail', () => {
mockAxios.restore();
});
const findFormInputGroup = () => wrapper.find(GlFormInputGroup);
const findButton = () => wrapper.findComponent(GlButton);
const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup);
const clickResetEmail = async () => {
wrapper.findByTestId('incoming-email-token-reset').vm.$emit('click');
wrapper.findAllComponents(GlButton).at(2).trigger('click');
await waitForPromises();
};
@ -75,14 +76,14 @@ describe('IssuableByEmail', () => {
'renders a link with "$buttonText" when type is "$issuableType"',
({ issuableType, buttonText }) => {
wrapper = createComponent({ issuableType });
expect(wrapper.findByTestId('issuable-email-modal-btn').text()).toBe(buttonText);
expect(findButton().text()).toBe(buttonText);
},
);
it('opens the modal when the user clicks the button', () => {
wrapper = createComponent();
wrapper.findByTestId('issuable-email-modal-btn').vm.$emit('click');
findButton().trigger('click');
expect(glModalDirective).toHaveBeenCalled();
});
@ -105,7 +106,7 @@ describe('IssuableByEmail', () => {
initialEmail,
});
expect(wrapper.findByTestId('mail-to-btn').attributes('href')).toBe(
expect(wrapper.findAllComponents(GlButton).at(1).attributes('href')).toBe(
`mailto:${initialEmail}?subject=${subject}&body=${body}`,
);
});

View File

@ -1,4 +1,4 @@
import { GlSprintf } from '@gitlab/ui';
import { GlIcon, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import StatusBox from '~/issuable/components/status_box.vue';
@ -64,7 +64,7 @@ describe('Merge request status box component', () => {
initialState: testCase.state,
});
expect(wrapper.find('[data-testid="status-icon"]').props('name')).toBe(testCase.icon);
expect(wrapper.findComponent(GlIcon).props('name')).toBe(testCase.icon);
});
});
});

View File

@ -798,15 +798,29 @@ describe('URL utility', () => {
);
});
it('handles arrays properly', () => {
it('adds parameters from arrays', () => {
const url = 'https://gitlab.com/test';
expect(urlUtils.setUrlParams({ label_name: ['foo', 'bar'] }, url)).toEqual(
'https://gitlab.com/test?label_name=foo&label_name=bar',
expect(urlUtils.setUrlParams({ labels: ['foo', 'bar'] }, url)).toEqual(
'https://gitlab.com/test?labels=foo&labels=bar',
);
});
it('handles arrays properly when railsArraySyntax=true', () => {
it('removes parameters from empty arrays', () => {
const url = 'https://gitlab.com/test?labels=foo&labels=bar';
expect(urlUtils.setUrlParams({ labels: [] }, url)).toEqual('https://gitlab.com/test');
});
it('removes parameters from empty arrays while keeping other parameters', () => {
const url = 'https://gitlab.com/test?labels=foo&labels=bar&unrelated=unrelated';
expect(urlUtils.setUrlParams({ labels: [] }, url)).toEqual(
'https://gitlab.com/test?unrelated=unrelated',
);
});
it('adds parameters from arrays when railsArraySyntax=true', () => {
const url = 'https://gitlab.com/test';
expect(urlUtils.setUrlParams({ labels: ['foo', 'bar'] }, url, false, true)).toEqual(
@ -814,6 +828,14 @@ describe('URL utility', () => {
);
});
it('removes parameters from empty arrays when railsArraySyntax=true', () => {
const url = 'https://gitlab.com/test?labels%5B%5D=foo&labels%5B%5D=bar';
expect(urlUtils.setUrlParams({ labels: [] }, url, false, true)).toEqual(
'https://gitlab.com/test',
);
});
it('decodes URI when decodeURI=true', () => {
const url = 'https://gitlab.com/test';

View File

@ -43,7 +43,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
let(:save_incompleted) { true }
let(:dot_com) { true }
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
project: project, current_user: user, yaml_processor_result: yaml_processor_result, save_incompleted: save_incompleted
@ -57,7 +56,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
before do
stub_env('EXTERNAL_VALIDATION_SERVICE_URL', validation_service_url)
allow(Gitlab).to receive(:com?).and_return(dot_com)
allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('correlation-id')
end
@ -199,34 +197,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
end
context 'when not on .com' do
let(:dot_com) { false }
before do
stub_request(:post, validation_service_url).to_return(status: 404, body: "{}")
end
it 'drops the pipeline' do
perform!
expect(pipeline.status).to eq('failed')
expect(pipeline).to be_persisted
expect(pipeline.errors.to_a).to include('External validation failed')
end
it 'breaks the chain' do
perform!
expect(step.break?).to be true
end
it 'logs the authorization' do
expect(Gitlab::AppLogger).to receive(:info).with(message: 'Pipeline not authorized', project_id: project.id, user_id: user.id)
perform!
end
end
context 'when validation returns 406 Not Acceptable' do
before do
stub_request(:post, validation_service_url).to_return(status: 406, body: "{}")

View File

@ -1,117 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
# For easier debugging set `UNICORN_DEBUG=1`
RSpec.describe Gitlab::Cluster::Mixins::UnicornHttpServer do
before do
stub_const('UNICORN_STARTUP_TIMEOUT', 30)
end
context 'when running Unicorn' do
using RSpec::Parameterized::TableSyntax
where(:signal, :exitstatus, :termsig) do
# executes phased restart block
:USR2 | 140 | nil
:QUIT | 140 | nil
# does not execute phased restart block
:INT | 0 | nil
:TERM | 0 | nil
end
with_them do
it 'properly handles process lifecycle' do
with_unicorn(workers: 1) do |pid|
Process.kill(signal, pid)
child_pid, child_status = Process.wait2(pid)
expect(child_pid).to eq(pid)
expect(child_status.exitstatus).to eq(exitstatus)
expect(child_status.termsig).to eq(termsig)
end
end
end
end
private
def with_unicorn(workers:, timeout: UNICORN_STARTUP_TIMEOUT)
with_unicorn_configs(workers: workers) do |unicorn_rb, config_ru|
cmdline = [
"bundle", "exec", "unicorn",
"-I", Rails.root.to_s,
"-c", unicorn_rb,
config_ru
]
IO.popen(cmdline) do |process|
# wait for process to start:
# I, [2019-10-15T13:21:27.565225 #3089] INFO -- : master process ready
wait_for_output(process, /master process ready/, timeout: timeout)
consume_output(process)
yield(process.pid)
ensure
begin
Process.kill(:KILL, process.pid)
rescue Errno::ESRCH
end
end
end
end
def with_unicorn_configs(workers:)
Dir.mktmpdir do |dir|
File.write "#{dir}/unicorn.rb", <<-EOF
require './lib/gitlab/cluster/lifecycle_events'
require './lib/gitlab/cluster/mixins/unicorn_http_server'
worker_processes #{workers}
listen "127.0.0.1:0"
preload_app true
Unicorn::HttpServer.prepend(#{described_class})
mutex = Mutex.new
Gitlab::Cluster::LifecycleEvents.on_before_blackout_period do
mutex.synchronize do
exit(140)
end
end
# redirect stderr to stdout
$stderr.reopen($stdout)
EOF
File.write "#{dir}/config.ru", <<-EOF
run -> (env) { [404, {}, ['']] }
EOF
yield("#{dir}/unicorn.rb", "#{dir}/config.ru")
end
end
def wait_for_output(process, output, timeout:)
Timeout.timeout(timeout) do
loop do
line = process.readline
puts "UNICORN_DEBUG: #{line}" if ENV['UNICORN_DEBUG']
break if line =~ output
end
end
end
def consume_output(process)
Thread.new do
loop do
line = process.readline
puts "UNICORN_DEBUG: #{line}" if ENV['UNICORN_DEBUG']
end
rescue StandardError
end
end
end

View File

@ -1,67 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::HealthChecks::UnicornCheck do
let(:result_class) { Gitlab::HealthChecks::Result }
let(:readiness) { described_class.readiness }
let(:metrics) { described_class.metrics }
before do
described_class.clear_memoization(:http_servers)
end
shared_examples 'with state' do |(state, message)|
it "does provide readiness" do
expect(readiness).to eq(result_class.new('unicorn_check', state, message))
end
it "does provide metrics" do
expect(metrics).to include(
an_object_having_attributes(name: 'unicorn_check_success', value: state ? 1 : 0))
expect(metrics).to include(
an_object_having_attributes(name: 'unicorn_check_latency_seconds', value: be >= 0))
end
end
context 'when Unicorn is not loaded' do
before do
allow(Gitlab::Runtime).to receive(:unicorn?).and_return(false)
hide_const('Unicorn')
end
it "does not provide readiness and metrics" do
expect(readiness).to be_nil
expect(metrics).to be_nil
end
end
context 'when Unicorn is loaded' do
let(:http_server_class) { Struct.new(:worker_processes) }
before do
allow(Gitlab::Runtime).to receive(:unicorn?).and_return(true)
stub_const('Unicorn::HttpServer', http_server_class)
end
context 'when no servers are running' do
it_behaves_like 'with state', [false, 'unexpected Unicorn check result: 0']
end
context 'when servers without workers are running' do
before do
http_server_class.new(0)
end
it_behaves_like 'with state', [false, 'unexpected Unicorn check result: 0']
end
context 'when servers with workers are running' do
before do
http_server_class.new(1)
end
it_behaves_like 'with state', true
end
end
end

View File

@ -0,0 +1,64 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require_relative '../../../../rubocop/cop/usage_data/instrumentation_superclass'
RSpec.describe RuboCop::Cop::UsageData::InstrumentationSuperclass do
let(:allowed_classes) { %i[GenericMetric DatabaseMetric RedisHllMetric] }
let(:msg) { "Instrumentation classes should subclass one of the following: #{allowed_classes.join(', ')}." }
let(:config) do
RuboCop::Config.new('UsageData/InstrumentationSuperclass' => {
'AllowedClasses' => allowed_classes
})
end
subject(:cop) { described_class.new(config) }
context 'with class definition' do
context 'when inheriting from allowed superclass' do
it 'does not register an offense' do
expect_no_offenses('class NewMetric < GenericMetric; end')
end
end
context 'when inheriting from some other superclass' do
it 'registers an offense' do
expect_offense(<<~CODE)
class NewMetric < BaseMetric; end
^^^^^^^^^^ #{msg}
CODE
end
end
context 'when not inheriting' do
it 'does not register an offense' do
expect_no_offenses('class NewMetric; end')
end
end
end
context 'with dynamic class definition' do
context 'when inheriting from allowed superclass' do
it 'does not register an offense' do
expect_no_offenses('NewMetric = Class.new(GenericMetric)')
end
end
context 'when inheriting from some other superclass' do
it 'registers an offense' do
expect_offense(<<~CODE)
NewMetric = Class.new(BaseMetric)
^^^^^^^^^^ #{msg}
CODE
end
end
context 'when not inheriting' do
it 'does not register an offense' do
expect_no_offenses('NewMetric = Class.new')
end
end
end
end

View File

@ -908,10 +908,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
"@gitlab/ui@29.27.0":
version "29.27.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.27.0.tgz#c5cbe1fee2164705ea6a431c85f6fdaa2ff7e166"
integrity sha512-VoiYrozWyE9l/ddX308vsu+wQqaJJN3csngIlrJit3DzVZV9Z/OVWU/X9CWV8m/ubyr5ysEMqnrtnsQClR9FiA==
"@gitlab/ui@29.28.0":
version "29.28.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.28.0.tgz#3444f6d26114f503d78b85fca67b5cc340a4a667"
integrity sha512-7jHqbnEy3P5J/G0/b+Nu+iw8XSOyTWLvyOEtNdFpBras1RxCE3C4AnPyGT7jei+WafTQN2Vzxz8VIgAxZ6PPvg==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"