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: extends:
- .review-workflow-base - .review-workflow-base
- .review:rules:review-deploy - .review:rules:review-deploy
retry: 2
stage: review stage: review
needs: ["review-build-cng"] needs: ["review-build-cng"]
resource_group: "review/${CI_COMMIT_REF_NAME}" resource_group: "review/${CI_COMMIT_REF_NAME}"

View File

@ -51,7 +51,6 @@ export default {
</template> </template>
<div <div
v-if="issuableCount > -1" 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" 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" /> <gl-icon name="check" class="gl-color-green-400" />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -83,7 +83,6 @@
} }
} }
.gl-text-purple { color: $purple; }
.gl-bg-purple-light { background-color: $purple-light; } .gl-bg-purple-light { background-color: $purple-light; }
// move this to GitLab UI once onboarding experiment is considered a success // 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: "" } } .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-container
.search-input-wrap .search-input-wrap
.dropdown{ data: { url: search_autocomplete_path } } .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 # Follow-up the issue: https://gitlab.com/gitlab-org/gitlab/issues/34107
if Gitlab::Runtime.puma? return unless Gitlab::Runtime.puma?
Puma::Cluster.prepend(::Gitlab::Cluster::Mixins::PumaCluster)
elsif Gitlab::Runtime.unicorn? Puma::Cluster.prepend(::Gitlab::Cluster::Mixins::PumaCluster)
Unicorn::HttpServer.prepend(::Gitlab::Cluster::Mixins::UnicornHttpServer)
end

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: should accept or reject the pipeline. If the response is:
- `200`, the pipeline is accepted. - `200`, the pipeline is accepted.
- `4XX`, the pipeline is rejected. - `406`, the pipeline is rejected.
- Other codes, the pipeline is accepted and logged. - Other codes, the pipeline is accepted and logged.
If there's an error or the request times out, the pipeline is accepted. 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. 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 ## GitLab directory structure
This is the main directory structure you end up with following the instructions 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 ## 2. Ruby
The Ruby interpreter is required to run GitLab. 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. 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 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 ## 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 ### Redis versions
GitLab 13.0 and later requires Redis version 4.0 or higher. 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 ### 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 GitLab also requires the use of Yarn `>= v1.10.0` to manage JavaScript
dependencies. dependencies.
@ -99,7 +99,7 @@ More information can be found on the [Yarn website](https://classic.yarnpkg.com/
### 5. Update Go ### 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`. 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 ### 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 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 From GitLab 13.6, we recommend you use the [Git version provided by
Gitaly](https://gitlab.com/gitlab-org/gitaly/-/issues/2729) Gitaly](https://gitlab.com/gitlab-org/gitaly/-/issues/2729)

View File

@ -12,8 +12,7 @@ module Gitlab
DEFAULT_VALIDATION_REQUEST_TIMEOUT = 5 DEFAULT_VALIDATION_REQUEST_TIMEOUT = 5
ACCEPTED_STATUS = 200 ACCEPTED_STATUS = 200
DOT_COM_REJECTED_STATUS = 406 REJECTED_STATUS = 406
GENERAL_REJECTED_STATUS = (400..499).freeze
def perform! def perform!
pipeline_authorized = validate_external pipeline_authorized = validate_external
@ -34,14 +33,13 @@ module Gitlab
return true unless validation_service_url return true unless validation_service_url
# 200 - accepted # 200 - accepted
# 406 - not accepted on GitLab.com # 406 - rejected
# 4XX - not accepted for other installations
# everything else - accepted and logged # everything else - accepted and logged
response_code = validate_service_request.code response_code = validate_service_request.code
case response_code case response_code
when ACCEPTED_STATUS when ACCEPTED_STATUS
true true
when rejected_status when REJECTED_STATUS
false false
else else
raise InvalidResponseCode, "Unsupported response code received from Validation Service: #{response_code}" raise InvalidResponseCode, "Unsupported response code received from Validation Service: #{response_code}"
@ -52,14 +50,6 @@ module Gitlab
true true
end end
def rejected_status
if Gitlab.com?
DOT_COM_REJECTED_STATUS
else
GENERAL_REJECTED_STATUS
end
end
def validate_service_request def validate_service_request
headers = { headers = {
'X-Gitlab-Correlation-id' => Labkit::Correlation::CorrelationId.current_id, '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 # application: https://gitlab.com/gitlab-org/gitlab/issues/35343
self.readiness_checks = [ self.readiness_checks = [
WebExporter::ExporterCheck.new(self), WebExporter::ExporterCheck.new(self),
Gitlab::HealthChecks::PumaCheck, Gitlab::HealthChecks::PumaCheck
Gitlab::HealthChecks::UnicornCheck
] ]
end end

View File

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

View File

@ -16,7 +16,7 @@ module QA
Flow::Login.sign_in Flow::Login.sign_in
end 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| Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request|
merge_request.project = project merge_request.project = project
merge_request.title = merge_request_title merge_request.title = merge_request_title

View File

@ -3,7 +3,7 @@
module QA module QA
RSpec.describe 'Create' do RSpec.describe 'Create' do
describe 'Git push over HTTP', :smoke 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 Flow::Login.sign_in
access_token = Resource::PersonalAccessToken.fabricate!.token access_token = Resource::PersonalAccessToken.fabricate!.token

View File

@ -24,7 +24,7 @@ module QA
runner.remove_via_api! runner.remove_via_api!
end 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 Flow::Login.sign_in
Resource::Repository::Commit.fabricate_via_api! do |commit| Resource::Repository::Commit.fabricate_via_api! do |commit|

View File

@ -115,7 +115,7 @@ module QA
end end
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 Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |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' - 'user_id'
- 'resource_owner_id' - 'resource_owner_id'
- 'requirement_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 = {}) def request_csv(params = {})
visit project_issues_path(project, params) visit project_issues_path(project, params)
page.within('.nav-controls') do click_button 'Export as CSV'
find('[data-testid="export-csv-button"]').click
end
click_on 'Export issues' click_on 'Export issues'
end end

View File

@ -16,11 +16,11 @@ RSpec.describe 'Issues > User resets their incoming email token' do
end end
it 'changes incoming email address token', :js do 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 page.within '#issuable-email-modal' do
previous_token = page.find('input[type="text"]').value previous_token = page.find('input[type="text"]').value
page.find('[data-testid="incoming-email-token-reset"]').click click_button 'reset it'
wait_for_requests wait_for_requests

View File

@ -12,15 +12,9 @@ RSpec.describe 'Merge Requests > Exports as CSV', :js do
visit(project_merge_requests_path(project)) visit(project_merge_requests_path(project))
end end
subject { page.find('.nav-controls') }
it { is_expected.to have_selector '[data-testid="export-csv-button"]' }
context 'button is clicked' do context 'button is clicked' do
before do before do
page.within('.nav-controls') do click_button 'Export as CSV'
find('[data-testid="export-csv-button"]').click
end
end end
it 'shows a success message' do 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') click_on('Save changes')
end end
find('.flash-notice') wait_for_all_requests
checkbox = find_field('project_printing_merge_request_link_enabled') checkbox = find_field('project_printing_merge_request_link_enabled')
expect(checkbox).not_to be_checked expect(checkbox).not_to be_checked
@ -139,7 +140,8 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do
click_on('Save changes') click_on('Save changes')
end end
find('.flash-notice') wait_for_all_requests
checkbox = find_field('project_remove_source_branch_after_merge') checkbox = find_field('project_remove_source_branch_after_merge')
expect(checkbox).not_to be_checked expect(checkbox).not_to be_checked

View File

@ -1,7 +1,6 @@
import { GlModal, GlIcon, GlButton } from '@gitlab/ui'; import { GlModal, GlIcon, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component'; import { stubComponent } from 'helpers/stub_component';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import CsvExportModal from '~/issuable/components/csv_export_modal.vue'; import CsvExportModal from '~/issuable/components/csv_export_modal.vue';
describe('CsvExportModal', () => { describe('CsvExportModal', () => {
@ -9,26 +8,24 @@ describe('CsvExportModal', () => {
function createComponent(options = {}) { function createComponent(options = {}) {
const { injectedProperties = {}, props = {} } = options; const { injectedProperties = {}, props = {} } = options;
return extendedWrapper( return mount(CsvExportModal, {
mount(CsvExportModal, { propsData: {
propsData: { modalId: 'csv-export-modal',
modalId: 'csv-export-modal', exportCsvPath: 'export/csv/path',
exportCsvPath: 'export/csv/path', issuableCount: 1,
issuableCount: 1, ...props,
...props, },
}, provide: {
provide: { issuableType: 'issues',
issuableType: 'issues', ...injectedProperties,
...injectedProperties, },
}, stubs: {
stubs: { GlModal: stubComponent(GlModal, {
GlModal: stubComponent(GlModal, { template:
template: '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
'<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>', }),
}), },
}, });
}),
);
} }
afterEach(() => { afterEach(() => {
@ -61,14 +58,13 @@ describe('CsvExportModal', () => {
describe('issuable count info text', () => { describe('issuable count info text', () => {
it('displays the info text when issuableCount is > -1', () => { it('displays the info text when issuableCount is > -1', () => {
wrapper = createComponent({ props: { issuableCount: 10 } }); wrapper = createComponent({ props: { issuableCount: 10 } });
expect(wrapper.findByTestId('issuable-count-note').exists()).toBe(true); expect(wrapper.text()).toContain('10 issues selected');
expect(wrapper.findByTestId('issuable-count-note').text()).toContain('10 issues selected');
expect(findIcon().exists()).toBe(true); expect(findIcon().exists()).toBe(true);
}); });
it("doesn't display the info text when issuableCount is -1", () => { it("doesn't display the info text when issuableCount is -1", () => {
wrapper = createComponent({ props: { issuableCount: -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 { 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 CsvExportModal from '~/issuable/components/csv_export_modal.vue';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue'; import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import CsvImportModal from '~/issuable/components/csv_import_modal.vue'; import CsvImportModal from '~/issuable/components/csv_import_modal.vue';
@ -14,35 +14,33 @@ describe('CsvImportExportButtons', () => {
function createComponent(injectedProperties = {}) { function createComponent(injectedProperties = {}) {
glModalDirective = jest.fn(); glModalDirective = jest.fn();
return extendedWrapper( return mountExtended(CsvImportExportButtons, {
shallowMount(CsvImportExportButtons, { directives: {
directives: { GlTooltip: createMockDirective(),
GlTooltip: createMockDirective(), glModal: {
glModal: { bind(_, { value }) {
bind(_, { value }) { glModalDirective(value);
glModalDirective(value);
},
}, },
}, },
provide: { },
...injectedProperties, provide: {
}, ...injectedProperties,
propsData: { },
exportCsvPath, propsData: {
issuableCount, exportCsvPath,
}, issuableCount,
}), },
); });
} }
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
const findExportCsvButton = () => wrapper.findByTestId('export-csv-button'); const findExportCsvButton = () => wrapper.findComponent(GlButton);
const findImportDropdown = () => wrapper.findByTestId('import-csv-dropdown'); const findImportDropdown = () => wrapper.findComponent(GlDropdown);
const findImportCsvButton = () => wrapper.findByTestId('import-csv-dropdown'); const findImportCsvButton = () => wrapper.findByRole('menuitem', { name: 'Import CSV' });
const findImportFromJiraLink = () => wrapper.findByTestId('import-from-jira-link'); const findImportFromJiraLink = () => wrapper.findByRole('menuitem', { name: 'Import from Jira' });
const findExportCsvModal = () => wrapper.findComponent(CsvExportModal); const findExportCsvModal = () => wrapper.findComponent(CsvExportModal);
const findImportCsvModal = () => wrapper.findComponent(CsvImportModal); const findImportCsvModal = () => wrapper.findComponent(CsvImportModal);
@ -97,7 +95,7 @@ describe('CsvImportExportButtons', () => {
expect(findImportDropdown().exists()).toBe(true); expect(findImportDropdown().exists()).toBe(true);
}); });
it('renders the import button', () => { it('renders the import csv menu item', () => {
expect(findImportCsvButton().exists()).toBe(true); expect(findImportCsvButton().exists()).toBe(true);
}); });
@ -106,8 +104,11 @@ describe('CsvImportExportButtons', () => {
wrapper = createComponent({ showImportButton: true, showLabel: false }); wrapper = createComponent({ showImportButton: true, showLabel: false });
}); });
it('does not have a button text', () => { it('hides button text', () => {
expect(findImportCsvButton().props('text')).toBe(null); expect(findImportDropdown().props()).toMatchObject({
text: 'Import issues',
textSrOnly: true,
});
}); });
it('import button has a tooltip', () => { it('import button has a tooltip', () => {
@ -124,7 +125,10 @@ describe('CsvImportExportButtons', () => {
}); });
it('displays a button text', () => { 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', () => { it('import button has no tooltip', () => {

View File

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

View File

@ -58,10 +58,11 @@ describe('IssuableByEmail', () => {
mockAxios.restore(); mockAxios.restore();
}); });
const findFormInputGroup = () => wrapper.find(GlFormInputGroup); const findButton = () => wrapper.findComponent(GlButton);
const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup);
const clickResetEmail = async () => { const clickResetEmail = async () => {
wrapper.findByTestId('incoming-email-token-reset').vm.$emit('click'); wrapper.findAllComponents(GlButton).at(2).trigger('click');
await waitForPromises(); await waitForPromises();
}; };
@ -75,14 +76,14 @@ describe('IssuableByEmail', () => {
'renders a link with "$buttonText" when type is "$issuableType"', 'renders a link with "$buttonText" when type is "$issuableType"',
({ issuableType, buttonText }) => { ({ issuableType, buttonText }) => {
wrapper = createComponent({ issuableType }); 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', () => { it('opens the modal when the user clicks the button', () => {
wrapper = createComponent(); wrapper = createComponent();
wrapper.findByTestId('issuable-email-modal-btn').vm.$emit('click'); findButton().trigger('click');
expect(glModalDirective).toHaveBeenCalled(); expect(glModalDirective).toHaveBeenCalled();
}); });
@ -105,7 +106,7 @@ describe('IssuableByEmail', () => {
initialEmail, 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}`, `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 { shallowMount } from '@vue/test-utils';
import StatusBox from '~/issuable/components/status_box.vue'; import StatusBox from '~/issuable/components/status_box.vue';
@ -64,7 +64,7 @@ describe('Merge request status box component', () => {
initialState: testCase.state, 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'; const url = 'https://gitlab.com/test';
expect(urlUtils.setUrlParams({ label_name: ['foo', 'bar'] }, url)).toEqual( expect(urlUtils.setUrlParams({ labels: ['foo', 'bar'] }, url)).toEqual(
'https://gitlab.com/test?label_name=foo&label_name=bar', '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'; const url = 'https://gitlab.com/test';
expect(urlUtils.setUrlParams({ labels: ['foo', 'bar'] }, url, false, true)).toEqual( 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', () => { it('decodes URI when decodeURI=true', () => {
const url = 'https://gitlab.com/test'; const url = 'https://gitlab.com/test';

View File

@ -43,7 +43,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end end
let(:save_incompleted) { true } let(:save_incompleted) { true }
let(:dot_com) { true }
let(:command) do let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new( Gitlab::Ci::Pipeline::Chain::Command.new(
project: project, current_user: user, yaml_processor_result: yaml_processor_result, save_incompleted: save_incompleted 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 before do
stub_env('EXTERNAL_VALIDATION_SERVICE_URL', validation_service_url) 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') allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('correlation-id')
end end
@ -199,34 +197,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end end
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 context 'when validation returns 406 Not Acceptable' do
before do before do
stub_request(:post, validation_service_url).to_return(status: 406, body: "{}") 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" resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw== integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
"@gitlab/ui@29.27.0": "@gitlab/ui@29.28.0":
version "29.27.0" version "29.28.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.27.0.tgz#c5cbe1fee2164705ea6a431c85f6fdaa2ff7e166" resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.28.0.tgz#3444f6d26114f503d78b85fca67b5cc340a4a667"
integrity sha512-VoiYrozWyE9l/ddX308vsu+wQqaJJN3csngIlrJit3DzVZV9Z/OVWU/X9CWV8m/ubyr5ysEMqnrtnsQClR9FiA== integrity sha512-7jHqbnEy3P5J/G0/b+Nu+iw8XSOyTWLvyOEtNdFpBras1RxCE3C4AnPyGT7jei+WafTQN2Vzxz8VIgAxZ6PPvg==
dependencies: dependencies:
"@babel/standalone" "^7.0.0" "@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0" "@gitlab/vue-toasted" "^1.3.0"