Commit acce00c3 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch 'dmishunov/refactor-performance-utils' into 'master'

Moved the performance scripts

See merge request gitlab-org/gitlab!46970
parents 184b59d5 30d72b13
...@@ -7,9 +7,11 @@ require: ...@@ -7,9 +7,11 @@ require:
- rubocop-rspec - rubocop-rspec
inherit_from: inherit_from:
- .rubocop_manual_todo.yml
- .rubocop_todo.yml - .rubocop_todo.yml
- ./rubocop/rubocop-migrations.yml - ./rubocop/rubocop-migrations.yml
- ./rubocop/rubocop-usage-data.yml - ./rubocop/rubocop-usage-data.yml
- ./rubocop/rubocop-code_reuse.yml
inherit_mode: inherit_mode:
merge: merge:
......
This diff is collapsed.
This diff is collapsed.
...@@ -370,11 +370,7 @@ group :development, :test do ...@@ -370,11 +370,7 @@ group :development, :test do
gem 'spring', '~> 2.0.0' gem 'spring', '~> 2.0.0'
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'gitlab-styles', '~> 4.3.0', require: false gem 'gitlab-styles', '~> 5.0.0', require: false
# Pin these dependencies, otherwise a new rule could break the CI pipelines
gem 'rubocop', '~> 0.82.0'
gem 'rubocop-performance', '~> 1.5.2'
gem 'rubocop-rspec', '~> 1.37.0'
gem 'scss_lint', '~> 0.56.0', require: false gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.36.0', require: false gem 'haml_lint', '~> 0.36.0', require: false
......
...@@ -449,12 +449,12 @@ GEM ...@@ -449,12 +449,12 @@ GEM
gitlab-puma (>= 2.7, < 5) gitlab-puma (>= 2.7, < 5)
gitlab-sidekiq-fetcher (0.5.2) gitlab-sidekiq-fetcher (0.5.2)
sidekiq (~> 5) sidekiq (~> 5)
gitlab-styles (4.3.0) gitlab-styles (5.0.0)
rubocop (~> 0.82.0) rubocop (~> 0.89.1)
rubocop-gitlab-security (~> 0.1.0) rubocop-gitlab-security (~> 0.1.0)
rubocop-performance (~> 1.5.2) rubocop-performance (~> 1.8.1)
rubocop-rails (~> 2.5) rubocop-rails (~> 2.8)
rubocop-rspec (~> 1.36) rubocop-rspec (~> 1.44)
gitlab_chronic_duration (0.10.6.2) gitlab_chronic_duration (0.10.6.2)
numerizer (~> 0.2) numerizer (~> 0.2)
gitlab_omniauth-ldap (2.1.1) gitlab_omniauth-ldap (2.1.1)
...@@ -602,7 +602,6 @@ GEM ...@@ -602,7 +602,6 @@ GEM
jaeger-client (1.1.0) jaeger-client (1.1.0)
opentracing (~> 0.3) opentracing (~> 0.3)
thrift thrift
jaro_winkler (1.5.4)
jira-ruby (2.0.0) jira-ruby (2.0.0)
activesupport activesupport
atlassian-jwt atlassian-jwt
...@@ -834,7 +833,7 @@ GEM ...@@ -834,7 +833,7 @@ GEM
rubypants (~> 0.2) rubypants (~> 0.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (1.0.0) os (1.0.0)
parallel (1.19.1) parallel (1.19.2)
parser (2.7.2.0) parser (2.7.2.0)
ast (~> 2.4.1) ast (~> 2.4.1)
parslet (1.8.2) parslet (1.8.2)
...@@ -958,7 +957,7 @@ GEM ...@@ -958,7 +957,7 @@ GEM
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.8.1) redis-store (1.8.1)
redis (>= 4, < 5) redis (>= 4, < 5)
regexp_parser (1.5.1) regexp_parser (1.8.2)
regexp_property_values (0.3.5) regexp_property_values (0.3.5)
representable (3.0.4) representable (3.0.4)
declarative (< 0.1.0) declarative (< 0.1.0)
...@@ -1019,24 +1018,29 @@ GEM ...@@ -1019,24 +1018,29 @@ GEM
pg pg
rails rails
sqlite3 sqlite3
rubocop (0.82.0) rubocop (0.89.1)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.7.0.1) parser (>= 2.7.1.1)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.7)
rexml rexml
rubocop-ast (>= 0.3.0, < 1.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0) unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (0.8.0)
parser (>= 2.7.1.5)
rubocop-gitlab-security (0.1.1) rubocop-gitlab-security (0.1.1)
rubocop (>= 0.51) rubocop (>= 0.51)
rubocop-performance (1.5.2) rubocop-performance (1.8.1)
rubocop (>= 0.71.0) rubocop (>= 0.87.0)
rubocop-rails (2.5.2) rubocop-ast (>= 0.4.0)
activesupport rubocop-rails (2.8.1)
activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 0.72.0) rubocop (>= 0.87.0)
rubocop-rspec (1.37.0) rubocop-rspec (1.44.1)
rubocop (>= 0.68.1) rubocop (~> 0.87)
rubocop-ast (>= 0.7.1)
ruby-enum (0.7.2) ruby-enum (0.7.2)
i18n i18n
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
...@@ -1249,7 +1253,7 @@ GEM ...@@ -1249,7 +1253,7 @@ GEM
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
yajl-ruby (1.4.1) yajl-ruby (1.4.1)
zeitwerk (2.4.0) zeitwerk (2.4.1)
PLATFORMS PLATFORMS
ruby ruby
...@@ -1353,7 +1357,7 @@ DEPENDENCIES ...@@ -1353,7 +1357,7 @@ DEPENDENCIES
gitlab-puma (~> 4.3.3.gitlab.2) gitlab-puma (~> 4.3.3.gitlab.2)
gitlab-puma_worker_killer (~> 0.1.1.gitlab.1) gitlab-puma_worker_killer (~> 0.1.1.gitlab.1)
gitlab-sidekiq-fetcher (= 0.5.2) gitlab-sidekiq-fetcher (= 0.5.2)
gitlab-styles (~> 4.3.0) gitlab-styles (~> 5.0.0)
gitlab_chronic_duration (~> 0.10.6.2) gitlab_chronic_duration (~> 0.10.6.2)
gitlab_omniauth-ldap (~> 2.1.1) gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2) gon (~> 6.2)
...@@ -1473,9 +1477,6 @@ DEPENDENCIES ...@@ -1473,9 +1477,6 @@ DEPENDENCIES
rspec-retry (~> 0.6.1) rspec-retry (~> 0.6.1)
rspec_junit_formatter rspec_junit_formatter
rspec_profiling (~> 0.0.6) rspec_profiling (~> 0.0.6)
rubocop (~> 0.82.0)
rubocop-performance (~> 1.5.2)
rubocop-rspec (~> 1.37.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 1.3.0) ruby-prof (~> 1.3.0)
ruby-progressbar ruby-progressbar
......
...@@ -52,7 +52,7 @@ export default { ...@@ -52,7 +52,7 @@ export default {
<p class="gl-mb-0"> <p class="gl-mb-0">
{{ {{
s__( s__(
'Integrations|Projects using custom settings will not be impacted unless the project owner chooses to use instance-level defaults.', 'Integrations|Projects using custom settings will not be impacted unless the project owner chooses to use parent level defaults.',
) )
}} }}
</p> </p>
......
...@@ -38,8 +38,11 @@ export default { ...@@ -38,8 +38,11 @@ export default {
isJira() { isJira() {
return this.propsSource.type === 'jira'; return this.propsSource.type === 'jira';
}, },
isInstanceLevel() { isInstanceOrGroupLevel() {
return this.propsSource.integrationLevel === integrationLevels.INSTANCE; return (
this.propsSource.integrationLevel === integrationLevels.INSTANCE ||
this.propsSource.integrationLevel === integrationLevels.GROUP
);
}, },
showJiraIssuesFields() { showJiraIssuesFields() {
return this.isJira && this.glFeatures.jiraIssuesIntegration; return this.isJira && this.glFeatures.jiraIssuesIntegration;
...@@ -91,7 +94,7 @@ export default { ...@@ -91,7 +94,7 @@ export default {
v-bind="propsSource.jiraIssuesProps" v-bind="propsSource.jiraIssuesProps"
/> />
<div v-if="isEditable" class="footer-block row-content-block"> <div v-if="isEditable" class="footer-block row-content-block">
<template v-if="isInstanceLevel"> <template v-if="isInstanceOrGroupLevel">
<gl-button <gl-button
v-gl-modal.confirmSaveIntegration v-gl-modal.confirmSaveIntegration
category="primary" category="primary"
......
...@@ -108,12 +108,7 @@ export default { ...@@ -108,12 +108,7 @@ export default {
:label="s__('Integrations|Comment detail:')" :label="s__('Integrations|Comment detail:')"
data-testid="comment-detail" data-testid="comment-detail"
> >
<input <input name="service[comment_detail]" type="hidden" :value="commentDetail" />
v-if="isInheriting"
name="service[comment_detail]"
type="hidden"
:value="commentDetail"
/>
<gl-form-radio <gl-form-radio
v-for="commentDetailOption in commentDetailOptions" v-for="commentDetailOption in commentDetailOptions"
:key="commentDetailOption.value" :key="commentDetailOption.value"
......
...@@ -150,6 +150,14 @@ module PageLayoutHelper ...@@ -150,6 +150,14 @@ module PageLayoutHelper
css_class.join(' ') css_class.join(' ')
end end
def page_itemtype(itemtype = nil)
if itemtype
@page_itemtype = { itemscope: true, itemtype: itemtype }
else
@page_itemtype || {}
end
end
private private
def generic_canonical_url def generic_canonical_url
......
...@@ -91,18 +91,18 @@ module UsersHelper ...@@ -91,18 +91,18 @@ module UsersHelper
end end
end end
def work_information(user) def work_information(user, with_schema_markup: false)
return unless user return unless user
organization = user.organization organization = user.organization
job_title = user.job_title job_title = user.job_title
if organization.present? && job_title.present? if organization.present? && job_title.present?
s_('Profile|%{job_title} at %{organization}') % { job_title: job_title, organization: organization } render_job_title_and_organization(job_title, organization, with_schema_markup: with_schema_markup)
elsif job_title.present? elsif job_title.present?
job_title render_job_title(job_title, with_schema_markup: with_schema_markup)
elsif organization.present? elsif organization.present?
organization render_organization(organization, with_schema_markup: with_schema_markup)
end end
end end
...@@ -151,6 +151,35 @@ module UsersHelper ...@@ -151,6 +151,35 @@ module UsersHelper
items items
end end
def render_job_title(job_title, with_schema_markup: false)
if with_schema_markup
content_tag :span, itemprop: 'jobTitle' do
job_title
end
else
job_title
end
end
def render_organization(organization, with_schema_markup: false)
if with_schema_markup
content_tag :span, itemprop: 'worksFor' do
organization
end
else
organization
end
end
def render_job_title_and_organization(job_title, organization, with_schema_markup: false)
if with_schema_markup
job_title = '<span itemprop="jobTitle">'.html_safe + job_title + "</span>".html_safe
organization = '<span itemprop="worksFor">'.html_safe + organization + "</span>".html_safe
end
html_escape(s_('Profile|%{job_title} at %{organization}')) % { job_title: job_title, organization: organization }
end
end end
UsersHelper.prepend_if_ee('EE::UsersHelper') UsersHelper.prepend_if_ee('EE::UsersHelper')
...@@ -4,10 +4,16 @@ class Analytics::DevopsAdoption::Segment < ApplicationRecord ...@@ -4,10 +4,16 @@ class Analytics::DevopsAdoption::Segment < ApplicationRecord
ALLOWED_SEGMENT_COUNT = 20 ALLOWED_SEGMENT_COUNT = 20
has_many :segment_selections has_many :segment_selections
has_many :groups, through: :segment_selections
validates :name, presence: true, uniqueness: true, length: { maximum: 255 } validates :name, presence: true, uniqueness: true, length: { maximum: 255 }
validate :validate_segment_count validate :validate_segment_count
accepts_nested_attributes_for :segment_selections, allow_destroy: true
scope :ordered_by_name, -> { order(:name) }
scope :with_groups, -> { preload(:groups) }
private private
def validate_segment_count def validate_segment_count
......
...@@ -70,6 +70,8 @@ ...@@ -70,6 +70,8 @@
= render 'shared/members/sort_dropdown' = render 'shared/members/sort_dropdown'
- if vue_members_list_enabled - if vue_members_list_enabled
.js-group-members-list{ data: group_members_list_data_attributes(@group, @members) } .js-group-members-list{ data: group_members_list_data_attributes(@group, @members) }
.loading
.spinner.spinner-md
- else - else
%ul.content-list.members-list{ data: { qa_selector: 'members_list' } } %ul.content-list.members-list{ data: { qa_selector: 'members_list' } }
= render partial: 'shared/members/member', = render partial: 'shared/members/member',
...@@ -86,6 +88,8 @@ ...@@ -86,6 +88,8 @@
= html_escape(_('Groups with access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe } = html_escape(_('Groups with access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- if vue_members_list_enabled - if vue_members_list_enabled
.js-group-linked-list{ data: linked_groups_list_data_attributes(@group) } .js-group-linked-list{ data: linked_groups_list_data_attributes(@group) }
.loading
.spinner.spinner-md
- else - else
%ul.content-list.members-list{ data: { qa_selector: 'groups_list' } } %ul.content-list.members-list{ data: { qa_selector: 'groups_list' } }
- @group.shared_with_group_links.each do |group_link| - @group.shared_with_group_links.each do |group_link|
...@@ -100,6 +104,8 @@ ...@@ -100,6 +104,8 @@
= render 'shared/members/search_field', name: 'search_invited' = render 'shared/members/search_field', name: 'search_invited'
- if vue_members_list_enabled - if vue_members_list_enabled
.js-group-invited-members-list{ data: group_members_list_data_attributes(@group, @invited_members) } .js-group-invited-members-list{ data: group_members_list_data_attributes(@group, @invited_members) }
.loading
.spinner.spinner-md
- else - else
%ul.content-list.members-list %ul.content-list.members-list
= render partial: 'shared/members/member', = render partial: 'shared/members/member',
...@@ -116,6 +122,8 @@ ...@@ -116,6 +122,8 @@
= html_escape(_('Users requesting access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe } = html_escape(_('Users requesting access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- if vue_members_list_enabled - if vue_members_list_enabled
.js-group-access-requests-list{ data: group_members_list_data_attributes(@group, @requesters) } .js-group-access-requests-list{ data: group_members_list_data_attributes(@group, @requesters) }
.loading
.spinner.spinner-md
- else - else
%ul.content-list.members-list %ul.content-list.members-list
= render partial: 'shared/members/member', = render partial: 'shared/members/member',
......
...@@ -20,6 +20,6 @@ ...@@ -20,6 +20,6 @@
- unless @hide_breadcrumbs - unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs" = render "layouts/nav/breadcrumbs"
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" } %div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" } .content{ id: "content-body", **page_itemtype }
= render "layouts/flash", extra_flash_class: 'limit-container-width' = render "layouts/flash", extra_flash_class: 'limit-container-width'
= yield = yield
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
- page_title @user.blocked? ? s_('UserProfile|Blocked user') : @user.name - page_title @user.blocked? ? s_('UserProfile|Blocked user') : @user.name
- page_description @user.bio_html - page_description @user.bio_html
- header_title @user.name, user_path(@user) - header_title @user.name, user_path(@user)
- page_itemtype 'http://schema.org/Person'
- link_classes = "flex-grow-1 mx-1 " - link_classes = "flex-grow-1 mx-1 "
= content_for :meta_tags do = content_for :meta_tags do
...@@ -35,7 +36,7 @@ ...@@ -35,7 +36,7 @@
.profile-header{ class: [('with-no-profile-tabs' if profile_tabs.empty?)] } .profile-header{ class: [('with-no-profile-tabs' if profile_tabs.empty?)] }
.avatar-holder .avatar-holder
= link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
= image_tag avatar_icon_for_user(@user, 90), class: "avatar s90", alt: '' = image_tag avatar_icon_for_user(@user, 90), class: "avatar s90", alt: '', itemprop: 'image'
- if @user.blocked? - if @user.blocked?
.user-info .user-info
...@@ -44,7 +45,7 @@ ...@@ -44,7 +45,7 @@
= render "users/profile_basic_info" = render "users/profile_basic_info"
- else - else
.user-info .user-info
.cover-title .cover-title{ itemprop: 'name' }
= @user.name = @user.name
- if @user.status - if @user.status
...@@ -54,15 +55,15 @@ ...@@ -54,15 +55,15 @@
= render "users/profile_basic_info" = render "users/profile_basic_info"
.cover-desc.cgray.mb-1.mb-sm-2 .cover-desc.cgray.mb-1.mb-sm-2
- unless @user.location.blank? - unless @user.location.blank?
.profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mb-1.mb-sm-0 .profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mb-1.mb-sm-0{ itemprop: 'address', itemscope: true, itemtype: 'https://schema.org/PostalAddress' }
= sprite_icon('location', css_class: 'vertical-align-sub fgray') = sprite_icon('location', css_class: 'vertical-align-sub fgray')
%span.vertical-align-middle %span.vertical-align-middle{ itemprop: 'addressLocality' }
= @user.location = @user.location
- unless work_information(@user).blank? - unless work_information(@user).blank?
.profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline .profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline
= sprite_icon('work', css_class: 'vertical-align-middle fgray') = sprite_icon('work', css_class: 'vertical-align-middle fgray')
%span.vertical-align-middle %span.vertical-align-middle
= work_information(@user) = work_information(@user, with_schema_markup: true)
.cover-desc.cgray.mb-1.mb-sm-2 .cover-desc.cgray.mb-1.mb-sm-2
- unless @user.skype.blank? - unless @user.skype.blank?
.profile-link-holder.middle-dot-divider .profile-link-holder.middle-dot-divider
...@@ -80,10 +81,10 @@ ...@@ -80,10 +81,10 @@
.profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mt-1.mt-sm-0 .profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mt-1.mt-sm-0
- if Feature.enabled?(:security_auto_fix) && @user.bot? - if Feature.enabled?(:security_auto_fix) && @user.bot?
= sprite_icon('question', css_class: 'gl-text-blue-600') = sprite_icon('question', css_class: 'gl-text-blue-600')
= link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'me noopener noreferrer nofollow' = link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'me noopener noreferrer nofollow', itemprop: 'url'
- unless @user.public_email.blank? - unless @user.public_email.blank?
.profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mt-1.mt-sm-0 .profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mt-1.mt-sm-0
= link_to @user.public_email, "mailto:#{@user.public_email}", class: 'text-link' = link_to @user.public_email, "mailto:#{@user.public_email}", class: 'text-link', itemprop: 'email'
- if @user.bio.present? - if @user.bio.present?
.cover-desc.cgray .cover-desc.cgray
.profile-user-bio .profile-user-bio
......
---
title: Fix setting Comment detail for Jira and modal for groups
merge_request: 46945
author:
type: fixed
---
title: Add structured markup for users
merge_request: 46553
author:
type: added
---
name: security_on_demand_scans_http_header_validation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42812
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/276403
milestone: '13.6'
type: development
group: group::dynamic analysis
default_enabled: false
...@@ -6025,6 +6025,81 @@ type DetailedStatus { ...@@ -6025,6 +6025,81 @@ type DetailedStatus {
tooltip: String tooltip: String
} }
"""
Segment
"""
type DevopsAdoptionSegment {
"""
Assigned groups
"""
groups(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): GroupConnection
"""
ID of the segment
"""
id: ID!
"""
Name of the segment
"""
name: String!
}
"""
The connection type for DevopsAdoptionSegment.
"""
type DevopsAdoptionSegmentConnection {
"""
A list of edges.
"""
edges: [DevopsAdoptionSegmentEdge]
"""
A list of nodes.
"""
nodes: [DevopsAdoptionSegment]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type DevopsAdoptionSegmentEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: DevopsAdoptionSegment
}
input DiffImagePositionInput { input DiffImagePositionInput {
""" """
Merge base of the branch the comment was made on Merge base of the branch the comment was made on
...@@ -9165,6 +9240,41 @@ type Group { ...@@ -9165,6 +9240,41 @@ type Group {
webUrl: String! webUrl: String!
} }
"""
The connection type for Group.
"""
type GroupConnection {
"""
A list of edges.
"""
edges: [GroupEdge]
"""
A list of nodes.
"""
nodes: [Group]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type GroupEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: Group
}
""" """
Identifier of Group Identifier of Group
""" """
...@@ -16540,6 +16650,31 @@ type Query { ...@@ -16540,6 +16650,31 @@ type Query {
""" """
designManagement: DesignManagement! designManagement: DesignManagement!
"""
Get configured DevOps adoption segments on the instance
"""
devopsAdoptionSegments(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): DevopsAdoptionSegmentConnection
""" """
Text to echo back Text to echo back
""" """
......
...@@ -1000,6 +1000,16 @@ Autogenerated return type of DestroySnippet. ...@@ -1000,6 +1000,16 @@ Autogenerated return type of DestroySnippet.
| `text` | String | Text of the status | | `text` | String | Text of the status |
| `tooltip` | String | Tooltip associated with the status | | `tooltip` | String | Tooltip associated with the status |
### DevopsAdoptionSegment
Segment.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `groups` | GroupConnection | Assigned groups |
| `id` | ID! | ID of the segment |
| `name` | String! | Name of the segment |
### DiffPosition ### DiffPosition
| Field | Type | Description | | Field | Type | Description |
......
...@@ -18,6 +18,12 @@ If you choose a size larger than what is currently configured for the web server ...@@ -18,6 +18,12 @@ If you choose a size larger than what is currently configured for the web server
you will likely get errors. See the [troubleshooting section](#troubleshooting) for more you will likely get errors. See the [troubleshooting section](#troubleshooting) for more
details. details.
## Max push size
You can change the maximum push size for your repository.
Navigate to **Admin Area (wrench icon) > Settings > General**, then expand **Account and Limit**.
From here, you can increase or decrease by changing the value in `Maximum push size (MB)`.
## Max import size ## Max import size
You can change the maximum file size for imports in GitLab. You can change the maximum file size for imports in GitLab.
......
This diff is collapsed.
...@@ -31,23 +31,31 @@ authenticate with GitLab by using the `CI_JOB_TOKEN`. ...@@ -31,23 +31,31 @@ authenticate with GitLab by using the `CI_JOB_TOKEN`.
CI/CD templates, which you can use to get started, are in [this repo](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates). CI/CD templates, which you can use to get started, are in [this repo](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates).
Learn more about [using CI/CD to build Maven packages](../maven_repository/index.md#create-maven-packages-with-gitlab-cicd), [NPM packages](../npm_registry/index.md#publish-an-npm-package-by-using-cicd), [Composer packages](../composer_repository/index.md#publish-a-composer-package-by-using-cicd), [NuGet Packages](../nuget_repository/index.md#publishing-a-nuget-package-with-cicd), [Conan Packages](../conan_repository/index.md#publish-a-conan-package-by-using-cicd), [PyPI packages](../pypi_repository/index.md#using-gitlab-ci-with-pypi-packages), and [generic packages](../generic_packages/index.md#publish-a-generic-package-by-using-cicd). Learn more about using CI/CD to build:
If you use CI/CD to build a package, extended activity - [Maven packages](../maven_repository/index.md#create-maven-packages-with-gitlab-cicd)
information is displayed when you view the package details: - [NPM packages](../npm_registry/index.md#publish-an-npm-package-by-using-cicd)
- [Composer packages](../composer_repository/index.md#publish-a-composer-package-by-using-cicd)
- [NuGet packages](../nuget_repository/index.md#publish-a-nuget-package-by-using-cicd)
- [Conan packages](../conan_repository/index.md#publish-a-conan-package-by-using-cicd)
- [PyPI packages](../pypi_repository/index.md#using-gitlab-ci-with-pypi-packages)
- [Generic packages](../generic_packages/index.md#publish-a-generic-package-by-using-cicd)
If you use CI/CD to build a package, extended activity information is displayed
when you view the package details:
![Package CI/CD activity](img/package_activity_v12_10.png) ![Package CI/CD activity](img/package_activity_v12_10.png)
When using Maven and NPM, you can view which pipeline published the package, as well as the commit and When using Maven and NPM, you can view which pipeline published the package, and
user who triggered it. the commit and user who triggered it.
## Download a package ## Download a package
To download a package: To download a package:
1. Go to **Packages & Registries > Package Registry**. 1. Go to **Packages & Registries > Package Registry**.
1. Click the name of the package you want to download. 1. Select the name of the package you want to download.
1. In the **Activity** section, click the name of the package you want to download. 1. In the **Activity** section, select the name of the package you want to download.
## Delete a package ## Delete a package
......
...@@ -11,11 +11,16 @@ import { ...@@ -11,11 +11,16 @@ import {
GlInputGroupText, GlInputGroupText,
GlLoadingIcon, GlLoadingIcon,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { omit } from 'lodash';
import * as Sentry from '~/sentry/wrapper'; import * as Sentry from '~/sentry/wrapper';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import download from '~/lib/utils/downloader'; import download from '~/lib/utils/downloader';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { cleanLeadingSeparator, joinPaths, stripPathTail } from '~/lib/utils/url_utility'; import { cleanLeadingSeparator, joinPaths, stripPathTail } from '~/lib/utils/url_utility';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
import { import {
DAST_SITE_VALIDATION_HTTP_HEADER_KEY,
DAST_SITE_VALIDATION_METHOD_HTTP_HEADER,
DAST_SITE_VALIDATION_METHOD_TEXT_FILE, DAST_SITE_VALIDATION_METHOD_TEXT_FILE,
DAST_SITE_VALIDATION_METHODS, DAST_SITE_VALIDATION_METHODS,
DAST_SITE_VALIDATION_STATUS, DAST_SITE_VALIDATION_STATUS,
...@@ -27,6 +32,7 @@ import dastSiteValidationQuery from '../graphql/dast_site_validation.query.graph ...@@ -27,6 +32,7 @@ import dastSiteValidationQuery from '../graphql/dast_site_validation.query.graph
export default { export default {
name: 'DastSiteValidation', name: 'DastSiteValidation',
components: { components: {
ClipboardButton,
GlAlert, GlAlert,
GlButton, GlButton,
GlCard, GlCard,
...@@ -38,6 +44,7 @@ export default { ...@@ -38,6 +44,7 @@ export default {
GlInputGroupText, GlInputGroupText,
GlLoadingIcon, GlLoadingIcon,
}, },
mixins: [glFeatureFlagsMixin()],
apollo: { apollo: {
dastSiteValidation: { dastSiteValidation: {
query: dastSiteValidationQuery, query: dastSiteValidationQuery,
...@@ -103,6 +110,16 @@ export default { ...@@ -103,6 +110,16 @@ export default {
}; };
}, },
computed: { computed: {
validationMethodOptions() {
const isHttpHeaderValidationEnabled = this.glFeatures
.securityOnDemandScansHttpHeaderValidation;
const enabledValidationMethods = omit(DAST_SITE_VALIDATION_METHODS, [
!isHttpHeaderValidationEnabled ? DAST_SITE_VALIDATION_METHOD_HTTP_HEADER : '',
]);
return Object.values(enabledValidationMethods);
},
urlObject() { urlObject() {
try { try {
return new URL(this.targetUrl); return new URL(this.targetUrl);
...@@ -119,12 +136,18 @@ export default { ...@@ -119,12 +136,18 @@ export default {
isTextFileValidation() { isTextFileValidation() {
return this.validationMethod === DAST_SITE_VALIDATION_METHOD_TEXT_FILE; return this.validationMethod === DAST_SITE_VALIDATION_METHOD_TEXT_FILE;
}, },
isHttpHeaderValidation() {
return this.validationMethod === DAST_SITE_VALIDATION_METHOD_HTTP_HEADER;
},
textFileName() { textFileName() {
return `GitLab-DAST-Site-Validation-${this.token}.txt`; return `GitLab-DAST-Site-Validation-${this.token}.txt`;
}, },
locationStepLabel() { locationStepLabel() {
return DAST_SITE_VALIDATION_METHODS[this.validationMethod].i18n.locationStepLabel; return DAST_SITE_VALIDATION_METHODS[this.validationMethod].i18n.locationStepLabel;
}, },
httpHeader() {
return `${DAST_SITE_VALIDATION_HTTP_HEADER_KEY}: uuid-code-${this.token}`;
},
}, },
watch: { watch: {
targetUrl() { targetUrl() {
...@@ -132,13 +155,22 @@ export default { ...@@ -132,13 +155,22 @@ export default {
}, },
}, },
created() { created() {
this.unsubscribe = this.$watch(() => this.token, this.updateValidationPath, { this.unsubscribe = this.$watch(
() => [this.token, this.validationMethod],
this.updateValidationPath,
{
immediate: true, immediate: true,
}); },
);
}, },
methods: { methods: {
updateValidationPath() { updateValidationPath() {
this.validationPath = joinPaths(stripPathTail(this.path), this.textFileName); this.validationPath = this.isTextFileValidation
? this.getTextFileValidationPath()
: this.path;
},
getTextFileValidationPath() {
return joinPaths(stripPathTail(this.path), this.textFileName);
}, },
onValidationPathInput() { onValidationPathInput() {
this.unsubscribe(); this.unsubscribe();
...@@ -189,7 +221,6 @@ export default { ...@@ -189,7 +221,6 @@ export default {
this.hasValidationError = true; this.hasValidationError = true;
}, },
}, },
validationMethodOptions: Object.values(DAST_SITE_VALIDATION_METHODS),
}; };
</script> </script>
...@@ -199,7 +230,7 @@ export default { ...@@ -199,7 +230,7 @@ export default {
{{ s__('DastProfiles|Site is not validated yet, please follow the steps.') }} {{ s__('DastProfiles|Site is not validated yet, please follow the steps.') }}
</gl-alert> </gl-alert>
<gl-form-group :label="s__('DastProfiles|Step 1 - Choose site validation method')"> <gl-form-group :label="s__('DastProfiles|Step 1 - Choose site validation method')">
<gl-form-radio-group v-model="validationMethod" :options="$options.validationMethodOptions" /> <gl-form-radio-group v-model="validationMethod" :options="validationMethodOptions" />
</gl-form-group> </gl-form-group>
<gl-form-group <gl-form-group
v-if="isTextFileValidation" v-if="isTextFileValidation"
...@@ -217,6 +248,16 @@ export default { ...@@ -217,6 +248,16 @@ export default {
{{ textFileName }} {{ textFileName }}
</gl-button> </gl-button>
</gl-form-group> </gl-form-group>
<gl-form-group
v-else-if="isHttpHeaderValidation"
:label="s__('DastProfiles|Step 2 - Add following HTTP header to your site')"
>
<code class="gl-p-3 gl-bg-black gl-text-white">{{ httpHeader }}</code>
<clipboard-button
:text="httpHeader"
:title="s__('DastProfiles|Copy HTTP header to clipboard')"
/>
</gl-form-group>
<gl-form-group :label="locationStepLabel" class="mw-460"> <gl-form-group :label="locationStepLabel" class="mw-460">
<gl-form-input-group> <gl-form-input-group>
<template #prepend> <template #prepend>
...@@ -255,7 +296,7 @@ export default { ...@@ -255,7 +296,7 @@ export default {
<gl-icon name="status_failed" /> <gl-icon name="status_failed" />
{{ {{
s__( s__(
'DastProfiles|Validation failed, please make sure that you follow the steps above with the choosen method.', 'DastProfiles|Validation failed, please make sure that you follow the steps above with the chosen method.',
) )
}} }}
</template> </template>
......
import { s__ } from '~/locale'; import { s__ } from '~/locale';
export const DAST_SITE_VALIDATION_METHOD_TEXT_FILE = 'TEXT_FILE'; export const DAST_SITE_VALIDATION_METHOD_TEXT_FILE = 'TEXT_FILE';
export const DAST_SITE_VALIDATION_METHOD_HTTP_HEADER = 'HTTP_HEADER';
export const DAST_SITE_VALIDATION_METHODS = { export const DAST_SITE_VALIDATION_METHODS = {
[DAST_SITE_VALIDATION_METHOD_TEXT_FILE]: { [DAST_SITE_VALIDATION_METHOD_TEXT_FILE]: {
value: DAST_SITE_VALIDATION_METHOD_TEXT_FILE, value: DAST_SITE_VALIDATION_METHOD_TEXT_FILE,
...@@ -9,6 +11,13 @@ export const DAST_SITE_VALIDATION_METHODS = { ...@@ -9,6 +11,13 @@ export const DAST_SITE_VALIDATION_METHODS = {
locationStepLabel: s__('DastProfiles|Step 3 - Confirm text file location and validate'), locationStepLabel: s__('DastProfiles|Step 3 - Confirm text file location and validate'),
}, },
}, },
[DAST_SITE_VALIDATION_METHOD_HTTP_HEADER]: {
value: DAST_SITE_VALIDATION_METHOD_HTTP_HEADER,
text: s__('DastProfiles|Header validation'),
i18n: {
locationStepLabel: s__('DastProfiles|Step 3 - Confirm header location and validate'),
},
},
}; };
export const DAST_SITE_VALIDATION_STATUS = { export const DAST_SITE_VALIDATION_STATUS = {
...@@ -19,3 +28,4 @@ export const DAST_SITE_VALIDATION_STATUS = { ...@@ -19,3 +28,4 @@ export const DAST_SITE_VALIDATION_STATUS = {
}; };
export const DAST_SITE_VALIDATION_POLL_INTERVAL = 1000; export const DAST_SITE_VALIDATION_POLL_INTERVAL = 1000;
export const DAST_SITE_VALIDATION_HTTP_HEADER_KEY = 'Gitlab-On-Demand-DAST';
...@@ -6,6 +6,7 @@ module Projects ...@@ -6,6 +6,7 @@ module Projects
before_action do before_action do
authorize_read_on_demand_scans! authorize_read_on_demand_scans!
push_frontend_feature_flag(:security_on_demand_scans_site_validation, @project) push_frontend_feature_flag(:security_on_demand_scans_site_validation, @project)
push_frontend_feature_flag(:security_on_demand_scans_http_header_validation, @project)
end end
feature_category :dynamic_application_security_testing feature_category :dynamic_application_security_testing
......
...@@ -51,6 +51,11 @@ module EE ...@@ -51,6 +51,11 @@ module EE
null: true, null: true,
resolver: ::Resolvers::InstanceSecurityDashboardResolver, resolver: ::Resolvers::InstanceSecurityDashboardResolver,
description: 'Fields related to Instance Security Dashboard' description: 'Fields related to Instance Security Dashboard'
field :devops_adoption_segments, ::Types::Admin::Analytics::DevopsAdoption::SegmentType.connection_type,
null: true,
description: 'Get configured DevOps adoption segments on the instance',
resolver: ::Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver
end end
def vulnerability(id:) def vulnerability(id:)
......
# frozen_string_literal: true
module Resolvers
module Admin
module Analytics
module DevopsAdoption
class SegmentsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
type Types::Admin::Analytics::DevopsAdoption::SegmentType, null: true
def resolve
authorize!
if segments_feature_available?
::Analytics::DevopsAdoption::Segment.with_groups.ordered_by_name
else
::Analytics::DevopsAdoption::Segment.none
end
end
private
def segments_feature_available?
License.feature_available?(:instance_level_devops_adoption)
end
def authorize!
admin? || raise_resource_not_available_error!
end
def admin?
context[:current_user].present? && context[:current_user].admin?
end
end
end
end
end
end
# frozen_string_literal: true
# rubocop:disable Graphql/AuthorizeTypes
module Types
module Admin
module Analytics
module DevopsAdoption
class SegmentType < BaseObject
graphql_name 'DevopsAdoptionSegment'
description 'Segment'
field :id, GraphQL::ID_TYPE, null: false,
description: "ID of the segment"
field :name, GraphQL::STRING_TYPE, null: false,
description: 'Name of the segment'
field :groups, Types::GroupType.connection_type, null: true,
description: 'Assigned groups'
end
end
end
end
end
...@@ -19,6 +19,7 @@ class License < ApplicationRecord ...@@ -19,6 +19,7 @@ class License < ApplicationRecord
group_activity_analytics group_activity_analytics
group_bulk_edit group_bulk_edit
group_webhooks group_webhooks
instance_level_devops_adoption
issuable_default_templates issuable_default_templates
issue_weights issue_weights
iterations iterations
......
---
title: Expose Devops Adoption segments via GraphQL
merge_request: 46879
author:
type: added
...@@ -3,8 +3,5 @@ ...@@ -3,8 +3,5 @@
--- ---
inherit_from: ../../../../../lib/gitlab/background_migration/.rubocop.yml inherit_from: ../../../../../lib/gitlab/background_migration/.rubocop.yml
CodeReuse/ActiveRecord:
Enabled: false
Style/Documentation: Style/Documentation:
Enabled: false Enabled: false
...@@ -14,7 +14,7 @@ export const dastSiteValidation = (status = DAST_SITE_VALIDATION_STATUS.PENDING) ...@@ -14,7 +14,7 @@ export const dastSiteValidation = (status = DAST_SITE_VALIDATION_STATUS.PENDING)
export const dastSiteValidationCreate = (errors = []) => ({ export const dastSiteValidationCreate = (errors = []) => ({
data: { data: {
dastSiteValidationCreate: { status: DAST_SITE_VALIDATION_STATUS.PASSED, id: '1', errors }, dastSiteValidationCreate: { status: DAST_SITE_VALIDATION_STATUS.PENDING, id: '1', errors },
}, },
}); });
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do
include GraphqlHelpers
let_it_be(:admin_user) { create(:user, :admin) }
let(:current_user) { admin_user }
def resolve_segments(args = {}, context = {})
resolve(described_class, args: args, ctx: context)
end
describe '#resolve' do
let_it_be(:user) { create(:user) }
let_it_be(:segment_1) { create(:devops_adoption_segment, name: 'bbb') }
let_it_be(:segment_2) { create(:devops_adoption_segment, name: 'aaa') }
subject { resolve_segments({}, { current_user: current_user }) }
before do
stub_licensed_features(instance_level_devops_adoption: true)
end
context 'when requesting project count measurements' do
context 'as an admin user' do
let(:current_user) { admin_user }
it 'returns the records, ordered by name' do
expect(subject).to eq([segment_2, segment_1])
end
end
context 'when the feature is not available' do
let(:current_user) { admin_user }
before do
stub_licensed_features(instance_level_devops_adoption: false)
end
it 'returns the records, ordered by name' do
expect(subject).to be_empty
end
end
context 'as a non-admin user' do
let(:current_user) { user }
it 'raises ResourceNotAvailable error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'as an unauthenticated user' do
let(:current_user) { nil }
it 'raises ResourceNotAvailable error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
end
end
...@@ -26,4 +26,15 @@ RSpec.describe Analytics::DevopsAdoption::Segment, type: :model do ...@@ -26,4 +26,15 @@ RSpec.describe Analytics::DevopsAdoption::Segment, type: :model do
end end
end end
end end
describe '.ordered_by_name' do
let(:segment_1) { create(:devops_adoption_segment, name: 'bbb') }
let(:segment_2) { create(:devops_adoption_segment, name: 'aaa') }
subject { described_class.ordered_by_name }
it 'orders segments by name' do
expect(subject).to eq([segment_2, segment_1])
end
end
end end
...@@ -190,7 +190,7 @@ module Gitlab ...@@ -190,7 +190,7 @@ module Gitlab
%r{\A(ee/)?vendor/} => :backend, %r{\A(ee/)?vendor/} => :backend,
%r{\A(Gemfile|Gemfile.lock|Rakefile)\z} => :backend, %r{\A(Gemfile|Gemfile.lock|Rakefile)\z} => :backend,
%r{\A[A-Z_]+_VERSION\z} => :backend, %r{\A[A-Z_]+_VERSION\z} => :backend,
%r{\A\.rubocop(_todo)?\.yml\z} => :backend, %r{\A\.rubocop((_manual)?_todo)?\.yml\z} => :backend,
%r{\Afile_hooks/} => :backend, %r{\Afile_hooks/} => :backend,
%r{\A(ee/)?qa/} => :qa, %r{\A(ee/)?qa/} => :qa,
......
...@@ -8319,6 +8319,9 @@ msgstr "" ...@@ -8319,6 +8319,9 @@ msgstr ""
msgid "DastProfiles|Authentication URL" msgid "DastProfiles|Authentication URL"
msgstr "" msgstr ""
msgid "DastProfiles|Copy HTTP header to clipboard"
msgstr ""
msgid "DastProfiles|Could not create site validation token. Please refresh the page, or try again later." msgid "DastProfiles|Could not create site validation token. Please refresh the page, or try again later."
msgstr "" msgstr ""
...@@ -8385,6 +8388,9 @@ msgstr "" ...@@ -8385,6 +8388,9 @@ msgstr ""
msgid "DastProfiles|Error Details" msgid "DastProfiles|Error Details"
msgstr "" msgstr ""
msgid "DastProfiles|Header validation"
msgstr ""
msgid "DastProfiles|Hide debug messages" msgid "DastProfiles|Hide debug messages"
msgstr "" msgstr ""
...@@ -8469,9 +8475,15 @@ msgstr "" ...@@ -8469,9 +8475,15 @@ msgstr ""
msgid "DastProfiles|Step 1 - Choose site validation method" msgid "DastProfiles|Step 1 - Choose site validation method"
msgstr "" msgstr ""
msgid "DastProfiles|Step 2 - Add following HTTP header to your site"
msgstr ""
msgid "DastProfiles|Step 2 - Add following text to the target site" msgid "DastProfiles|Step 2 - Add following text to the target site"
msgstr "" msgstr ""
msgid "DastProfiles|Step 3 - Confirm header location and validate"
msgstr ""
msgid "DastProfiles|Step 3 - Confirm text file location and validate" msgid "DastProfiles|Step 3 - Confirm text file location and validate"
msgstr "" msgstr ""
...@@ -8508,7 +8520,7 @@ msgstr "" ...@@ -8508,7 +8520,7 @@ msgstr ""
msgid "DastProfiles|Validating..." msgid "DastProfiles|Validating..."
msgstr "" msgstr ""
msgid "DastProfiles|Validation failed, please make sure that you follow the steps above with the choosen method." msgid "DastProfiles|Validation failed, please make sure that you follow the steps above with the chosen method."
msgstr "" msgstr ""
msgid "DastProfiles|Validation failed. Please try again." msgid "DastProfiles|Validation failed. Please try again."
...@@ -14507,7 +14519,7 @@ msgstr "" ...@@ -14507,7 +14519,7 @@ msgstr ""
msgid "Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira." msgid "Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira."
msgstr "" msgstr ""
msgid "Integrations|Projects using custom settings will not be impacted unless the project owner chooses to use instance-level defaults." msgid "Integrations|Projects using custom settings will not be impacted unless the project owner chooses to use parent level defaults."
msgstr "" msgstr ""
msgid "Integrations|Return to GitLab for Jira" msgid "Integrations|Return to GitLab for Jira"
......
...@@ -19,6 +19,7 @@ module QA ...@@ -19,6 +19,7 @@ module QA
autoload :Saml, 'qa/flow/saml' autoload :Saml, 'qa/flow/saml'
autoload :User, 'qa/flow/user' autoload :User, 'qa/flow/user'
autoload :MergeRequest, 'qa/flow/merge_request' autoload :MergeRequest, 'qa/flow/merge_request'
autoload :Pipeline, 'qa/flow/pipeline'
end end
## ##
......
# frozen_string_literal: true
module QA
module Flow
module Pipeline
module_function
# In some cases we don't need to wait for anything, blocked, running or pending is acceptable
# Some cases only need pipeline to finish with different condition (completion, success or replication)
def visit_latest_pipeline(pipeline_condition: nil)
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:"wait_for_latest_pipeline_#{pipeline_condition}") if pipeline_condition
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
end
end
end
end
...@@ -65,8 +65,7 @@ module QA ...@@ -65,8 +65,7 @@ module QA
) )
end.project.visit! end.project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
{ {
'test-success': :passed, 'test-success': :passed,
......
...@@ -29,7 +29,7 @@ module QA ...@@ -29,7 +29,7 @@ module QA
Flow::Login.sign_in Flow::Login.sign_in
add_ci_files add_ci_files
project.visit! project.visit!
view_the_last_pipeline Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'success')
end end
after do after do
...@@ -64,12 +64,6 @@ module QA ...@@ -64,12 +64,6 @@ module QA
end end
end end
def view_the_last_pipeline
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:wait_for_latest_pipeline_success)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
end
def parent_ci_file def parent_ci_file
{ {
file_path: '.gitlab-ci.yml', file_path: '.gitlab-ci.yml',
......
...@@ -57,8 +57,7 @@ module QA ...@@ -57,8 +57,7 @@ module QA
end end
project.visit! project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline| Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('create_package') pipeline.click_job('create_package')
......
...@@ -94,8 +94,7 @@ module QA ...@@ -94,8 +94,7 @@ module QA
end end
project.visit! project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline| Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('deploy') pipeline.click_job('deploy')
......
...@@ -61,8 +61,7 @@ module QA ...@@ -61,8 +61,7 @@ module QA
end end
project.visit! project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline| Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('deploy') pipeline.click_job('deploy')
......
...@@ -74,8 +74,7 @@ module QA ...@@ -74,8 +74,7 @@ module QA
end end
project.visit! project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline| Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('run') pipeline.click_job('run')
......
...@@ -77,8 +77,7 @@ module QA ...@@ -77,8 +77,7 @@ module QA
sha1sum = Digest::SHA1.hexdigest(gitlab_ci) sha1sum = Digest::SHA1.hexdigest(gitlab_ci)
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform(&:click_on_first_job) Page::Project::Pipeline::Show.perform(&:click_on_first_job)
Page::Project::Job::Show.perform do |job| Page::Project::Job::Show.perform do |job|
......
...@@ -27,7 +27,7 @@ module QA ...@@ -27,7 +27,7 @@ module QA
it 'parent pipelines passes if child passes', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/751' do it 'parent pipelines passes if child passes', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/751' do
add_ci_files(success_child_ci_file) add_ci_files(success_child_ci_file)
view_pipelines Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completion')
Page::Project::Pipeline::Show.perform do |parent_pipeline| Page::Project::Pipeline::Show.perform do |parent_pipeline|
expect(parent_pipeline).to have_child_pipeline expect(parent_pipeline).to have_child_pipeline
...@@ -37,7 +37,7 @@ module QA ...@@ -37,7 +37,7 @@ module QA
it 'parent pipeline fails if child fails', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/752' do it 'parent pipeline fails if child fails', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/752' do
add_ci_files(fail_child_ci_file) add_ci_files(fail_child_ci_file)
view_pipelines Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completion')
Page::Project::Pipeline::Show.perform do |parent_pipeline| Page::Project::Pipeline::Show.perform do |parent_pipeline|
expect(parent_pipeline).to have_child_pipeline expect(parent_pipeline).to have_child_pipeline
...@@ -47,12 +47,6 @@ module QA ...@@ -47,12 +47,6 @@ module QA
private private
def view_pipelines
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:wait_for_latest_pipeline_completion)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
end
def success_child_ci_file def success_child_ci_file
{ {
file_path: '.child-ci.yml', file_path: '.child-ci.yml',
......
...@@ -27,7 +27,7 @@ module QA ...@@ -27,7 +27,7 @@ module QA
it 'parent pipelines passes if child passes', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/754' do it 'parent pipelines passes if child passes', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/754' do
add_ci_files(success_child_ci_file) add_ci_files(success_child_ci_file)
view_pipelines Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completion')
Page::Project::Pipeline::Show.perform do |parent_pipeline| Page::Project::Pipeline::Show.perform do |parent_pipeline|
expect(parent_pipeline).to have_child_pipeline expect(parent_pipeline).to have_child_pipeline
...@@ -37,7 +37,7 @@ module QA ...@@ -37,7 +37,7 @@ module QA
it 'parent pipeline passes even if child fails', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/753' do it 'parent pipeline passes even if child fails', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/753' do
add_ci_files(fail_child_ci_file) add_ci_files(fail_child_ci_file)
view_pipelines Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completion')
Page::Project::Pipeline::Show.perform do |parent_pipeline| Page::Project::Pipeline::Show.perform do |parent_pipeline|
expect(parent_pipeline).to have_child_pipeline expect(parent_pipeline).to have_child_pipeline
...@@ -47,12 +47,6 @@ module QA ...@@ -47,12 +47,6 @@ module QA
private private
def view_pipelines
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:wait_for_latest_pipeline_completion)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
end
def success_child_ci_file def success_child_ci_file
{ {
file_path: '.child-ci.yml', file_path: '.child-ci.yml',
......
...@@ -54,8 +54,7 @@ module QA ...@@ -54,8 +54,7 @@ module QA
push.commit_message = 'Create Auto DevOps compatible rack application' push.commit_message = 'Create Auto DevOps compatible rack application'
end end
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline| Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('build') pipeline.click_job('build')
...@@ -119,8 +118,7 @@ module QA ...@@ -119,8 +118,7 @@ module QA
end end
it 'runs an AutoDevOps pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/444' do it 'runs an AutoDevOps pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/444' do
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline| Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to have_tag('Auto DevOps') expect(pipeline).to have_tag('Auto DevOps')
......
...@@ -53,9 +53,7 @@ module QA ...@@ -53,9 +53,7 @@ module QA
project.visit! project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |show| Page::Project::Pipeline::Show.perform do |show|
expect(show).to have_build('jenkins', status: :success, wait: 15) expect(show).to have_build('jenkins', status: :success, wait: 15)
......
...@@ -56,11 +56,7 @@ module QA ...@@ -56,11 +56,7 @@ module QA
) )
end.project.visit! end.project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completion')
Page::Project::Pipeline::Index.perform do |index|
index.wait_for_latest_pipeline_completion
index.click_on_latest_pipeline
end
Page::Project::Pipeline::Show.perform do |pipeline| Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('test-artifacts') pipeline.click_job('test-artifacts')
......
...@@ -65,11 +65,7 @@ module QA ...@@ -65,11 +65,7 @@ module QA
dashboard.go_to_project(@project.name) dashboard.go_to_project(@project.name)
end end
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'replication')
Page::Project::Pipeline::Index.perform do |index|
index.wait_for_latest_pipeline_replication
index.click_on_latest_pipeline
end
Page::Project::Pipeline::Show.perform do |pipeline| Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.wait_for_pipeline_job_replication(@pipeline_job_name) pipeline.wait_for_pipeline_job_replication(@pipeline_job_name)
...@@ -98,11 +94,7 @@ module QA ...@@ -98,11 +94,7 @@ module QA
dashboard.go_to_project(@project.name) dashboard.go_to_project(@project.name)
end end
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'replication')
Page::Project::Pipeline::Index.perform do |index|
index.wait_for_latest_pipeline_replication
index.click_on_latest_pipeline
end
Page::Project::Pipeline::Show.perform do |pipeline| Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.wait_for_pipeline_job_replication(@pipeline_job_name) pipeline.wait_for_pipeline_job_replication(@pipeline_job_name)
......
...@@ -108,8 +108,7 @@ module QA ...@@ -108,8 +108,7 @@ module QA
end end
it 'can approve and deny licenses in the pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/965' do it 'can approve and deny licenses in the pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/965' do
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline| Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_on_licenses pipeline.click_on_licenses
......
...@@ -51,8 +51,7 @@ module QA ...@@ -51,8 +51,7 @@ module QA
end end
it 'displays security reports in the pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/565', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/271547' } do it 'displays security reports in the pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/565', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/271547' } do
Page::Project::Menu.perform(&:click_ci_cd_pipelines) Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline| Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_on_security pipeline.click_on_security
......
# frozen_string_literal: true
require_relative '../../code_reuse_helpers'
module RuboCop
module Cop
module CodeReuse
# Cop that denies the use of ActiveRecord methods outside of models.
class ActiveRecord < RuboCop::Cop::Cop
include CodeReuseHelpers
MSG = 'This method can only be used inside an ActiveRecord model: ' \
'https://gitlab.com/gitlab-org/gitlab-foss/issues/49653'
# Various methods from ActiveRecord::Querying that are denied. We
# exclude some generic ones such as `any?` and `first`, as these may
# lead to too many false positives, since `Array` also supports these
# methods.
#
# The keys of this Hash are the denied method names. The values are
# booleans that indicate if the method should only be denied if any
# arguments are provided.
NOT_ALLOWED = {
average: true,
calculate: true,
count_by_sql: true,
create_with: true,
distinct: false,
eager_load: true,
exists?: true,
find_by: true,
find_by!: true,
find_by_sql: true,
find_each: true,
find_in_batches: true,
find_or_create_by: true,
find_or_create_by!: true,
find_or_initialize_by: true,
first!: false,
first_or_create: true,
first_or_create!: true,
first_or_initialize: true,
from: true,
group: true,
having: true,
ids: false,
includes: true,
joins: true,
limit: true,
lock: false,
many?: false,
offset: true,
order: true,
pluck: true,
preload: true,
readonly: false,
references: true,
reorder: true,
rewhere: true,
take: false,
take!: false,
unscope: false,
where: false,
with: true
}.freeze
# Directories that allow the use of the denied methods. These
# directories are checked relative to both . and ee/
ALLOWED_DIRECTORIES = %w[
app/models
config
danger
db
lib/backup
lib/banzai
lib/gitlab/background_migration
lib/gitlab/cycle_analytics
lib/gitlab/database
lib/gitlab/import_export
lib/gitlab/project_authorizations
lib/gitlab/sql
lib/system_check
lib/tasks
qa
rubocop
spec
].freeze
def on_send(node)
return if in_allowed_directory?(node)
receiver = node.children[0]
send_name = node.children[1]
first_arg = node.children[2]
if receiver && NOT_ALLOWED.key?(send_name)
# If the rule requires an argument to be given, but none are
# provided, we won't register an offense. This prevents us from
# adding offenses for `project.group`, while still covering
# `Project.group(:name)`.
return if NOT_ALLOWED[send_name] && !first_arg
add_offense(node, location: :selector)
end
end
# Returns true if the node resides in one of the allowed
# directories.
def in_allowed_directory?(node)
path = file_path_for_node(node)
ALLOWED_DIRECTORIES.any? do |directory|
path.start_with?(
File.join(rails_root, directory),
File.join(rails_root, 'ee', directory)
)
end
end
# We can not auto correct code like this, as it requires manual
# refactoring. Instead, we'll just allow the surrounding scope.
#
# Despite this method's presence, you should not use it. This method
# exists to make it possible to allow large chunks of offenses we
# can't fix in the short term. If you are writing new code, follow the
# code reuse guidelines, instead of allowing any new offenses.
def autocorrect(node)
scope = surrounding_scope_of(node)
indent = indentation_of(scope)
lambda do |corrector|
# This prevents us from inserting the same enable/disable comment
# for a method or block that has multiple offenses.
next if allowed_scopes.include?(scope)
corrector.insert_before(
scope.source_range,
"# rubocop: disable #{cop_name}\n#{indent}"
)
corrector.insert_after(
scope.source_range,
"\n#{indent}# rubocop: enable #{cop_name}"
)
allowed_scopes << scope
end
end
def indentation_of(node)
' ' * node.loc.expression.source_line[/\A */].length
end
def surrounding_scope_of(node)
%i[def defs block begin].each do |type|
if (found = node.each_ancestor(type).first)
return found
end
end
end
def allowed_scopes
@allowed_scopes ||= Set.new
end
end
end
end
end
...@@ -5,7 +5,7 @@ module RuboCop ...@@ -5,7 +5,7 @@ module RuboCop
def in_qa_file?(node) def in_qa_file?(node)
path = node.location.expression.source_buffer.name path = node.location.expression.source_buffer.name
path.start_with?(File.join(RuboCop::PathUtil.pwd, 'qa')) path.start_with?(File.join(Dir.pwd, 'qa'))
end end
end end
end end
# Denies the use of ActiveRecord methods outside of configured
# excluded directories
# Directories that allow the use of the denied methods.
# To start we provide a default configuration that matches
# a standard Rails app and enable.
# The default configuration can be overridden by
# providing your own Exclusion list as follows:
# CodeReuse/ActiveRecord:
# Enabled: true
# Exclude:
# - app/models/**/*.rb
# - config/**/*.rb
# - db/**/*.rb
# - lib/tasks/**/*.rb
# - spec/**/*.rb
# - lib/gitlab/**/*.rb
CodeReuse/ActiveRecord:
Exclude:
- app/models/**/*.rb
- config/**/*.rb
- db/**/*.rb
- lib/tasks/**/*.rake
- spec/**/*.rb
- danger/**/*.rb
- lib/backup/**/*.rb
- lib/banzai/**/*.rb
- lib/gitlab/background_migration/**/*.rb
- lib/gitlab/cycle_analytics/**/*.rb
- lib/gitlab/database/**/*.rb
- lib/gitlab/database_importers/common_metrics/importer.rb
- lib/gitlab/import_export/**/*.rb
- lib/gitlab/project_authorizations.rb
- lib/gitlab/sql/**/*.rb
- lib/system_check/**/*.rb
- qa/**/*.rb
- rubocop/**/*.rb
- ee/app/models/**/*.rb
- ee/spec/**/*.rb
- ee/db/fixtures/**/*.rb
- ee/lib/tasks/**/*.rake
- ee/lib/ee/gitlab/background_migration/**/*.rb
This diff is collapsed.
...@@ -34,7 +34,7 @@ describe('ConfirmationModal', () => { ...@@ -34,7 +34,7 @@ describe('ConfirmationModal', () => {
'Saving will update the default settings for all projects that are not using custom settings.', 'Saving will update the default settings for all projects that are not using custom settings.',
); );
expect(findGlModal().text()).toContain( expect(findGlModal().text()).toContain(
'Projects using custom settings will not be impacted unless the project owner chooses to use instance-level defaults.', 'Projects using custom settings will not be impacted unless the project owner chooses to use parent level defaults.',
); );
}); });
......
...@@ -9,6 +9,7 @@ import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_field ...@@ -9,6 +9,7 @@ import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_field
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue'; import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue'; import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
import DynamicField from '~/integrations/edit/components/dynamic_field.vue'; import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
import { integrationLevels } from '~/integrations/edit/constants';
describe('IntegrationForm', () => { describe('IntegrationForm', () => {
let wrapper; let wrapper;
...@@ -69,14 +70,24 @@ describe('IntegrationForm', () => { ...@@ -69,14 +70,24 @@ describe('IntegrationForm', () => {
describe('integrationLevel is instance', () => { describe('integrationLevel is instance', () => {
it('renders ConfirmationModal', () => { it('renders ConfirmationModal', () => {
createComponent({ createComponent({
integrationLevel: 'instance', integrationLevel: integrationLevels.INSTANCE,
}); });
expect(findConfirmationModal().exists()).toBe(true); expect(findConfirmationModal().exists()).toBe(true);
}); });
}); });
describe('integrationLevel is not instance', () => { describe('integrationLevel is group', () => {
it('renders ConfirmationModal', () => {
createComponent({
integrationLevel: integrationLevels.GROUP,
});
expect(findConfirmationModal().exists()).toBe(true);
});
});
describe('integrationLevel is project', () => {
it('does not render ConfirmationModal', () => { it('does not render ConfirmationModal', () => {
createComponent({ createComponent({
integrationLevel: 'project', integrationLevel: 'project',
......
...@@ -208,4 +208,27 @@ RSpec.describe PageLayoutHelper do ...@@ -208,4 +208,27 @@ RSpec.describe PageLayoutHelper do
end end
end end
end end
describe '#page_itemtype' do
subject { helper.page_itemtype(itemtype) }
context 'when itemtype is passed' do
let(:itemtype) { 'http://schema.org/Person' }
it 'stores and returns the itemtype value' do
attrs = { itemscope: true, itemtype: itemtype }
expect(subject).to eq attrs
expect(helper.page_itemtype(nil)).to eq attrs
end
end
context 'when no itemtype is provided' do
let(:itemtype) { nil }
it 'returns an empty hash' do
expect(subject).to eq({})
end
end
end
end end
This diff is collapsed.
...@@ -241,6 +241,9 @@ RSpec.describe Gitlab::Danger::Helper do ...@@ -241,6 +241,9 @@ RSpec.describe Gitlab::Danger::Helper do
'config/foo' | [:backend] 'config/foo' | [:backend]
'lib/foo' | [:backend] 'lib/foo' | [:backend]
'rubocop/foo' | [:backend] 'rubocop/foo' | [:backend]
'.rubocop.yml' | [:backend]
'.rubocop_todo.yml' | [:backend]
'.rubocop_manual_todo.yml' | [:backend]
'spec/foo' | [:backend] 'spec/foo' | [:backend]
'spec/foo/bar' | [:backend] 'spec/foo/bar' | [:backend]
......
...@@ -108,7 +108,7 @@ RSpec.describe Key, :mailer do ...@@ -108,7 +108,7 @@ RSpec.describe Key, :mailer do
expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid
end end
where(:factory, :chars, :expected_sections) do where(:factory, :characters, :expected_sections) do
[ [
[:key, ["\n", "\r\n"], 3], [:key, ["\n", "\r\n"], 3],
[:key, [' ', ' '], 3], [:key, [' ', ' '], 3],
...@@ -122,7 +122,7 @@ RSpec.describe Key, :mailer do ...@@ -122,7 +122,7 @@ RSpec.describe Key, :mailer do
let!(:original_fingerprint_sha256) { key.fingerprint_sha256 } let!(:original_fingerprint_sha256) { key.fingerprint_sha256 }
it 'accepts a key with blank space characters after stripping them' do it 'accepts a key with blank space characters after stripping them' do
modified_key = key.key.insert(100, chars.first).insert(40, chars.last) modified_key = key.key.insert(100, characters.first).insert(40, characters.last)
_, content = modified_key.split _, content = modified_key.split
key.update!(key: modified_key) key.update!(key: modified_key)
......
This diff is collapsed.
# frozen_string_literal: true # frozen_string_literal: true
require 'fast_spec_helper' require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../../rubocop/cop/rspec/be_success_matcher' require_relative '../../../../rubocop/cop/rspec/be_success_matcher'
RSpec.describe RuboCop::Cop::RSpec::BeSuccessMatcher, type: :rubocop do RSpec.describe RuboCop::Cop::RSpec::BeSuccessMatcher, type: :rubocop do
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment