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:
- rubocop-rspec
inherit_from:
- .rubocop_manual_todo.yml
- .rubocop_todo.yml
- ./rubocop/rubocop-migrations.yml
- ./rubocop/rubocop-usage-data.yml
- ./rubocop/rubocop-code_reuse.yml
inherit_mode:
merge:
......
This diff is collapsed.
This diff is collapsed.
......@@ -370,11 +370,7 @@ group :development, :test do
gem 'spring', '~> 2.0.0'
gem 'spring-commands-rspec', '~> 1.0.4'
gem 'gitlab-styles', '~> 4.3.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 'gitlab-styles', '~> 5.0.0', require: false
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.36.0', require: false
......
......@@ -449,12 +449,12 @@ GEM
gitlab-puma (>= 2.7, < 5)
gitlab-sidekiq-fetcher (0.5.2)
sidekiq (~> 5)
gitlab-styles (4.3.0)
rubocop (~> 0.82.0)
gitlab-styles (5.0.0)
rubocop (~> 0.89.1)
rubocop-gitlab-security (~> 0.1.0)
rubocop-performance (~> 1.5.2)
rubocop-rails (~> 2.5)
rubocop-rspec (~> 1.36)
rubocop-performance (~> 1.8.1)
rubocop-rails (~> 2.8)
rubocop-rspec (~> 1.44)
gitlab_chronic_duration (0.10.6.2)
numerizer (~> 0.2)
gitlab_omniauth-ldap (2.1.1)
......@@ -602,7 +602,6 @@ GEM
jaeger-client (1.1.0)
opentracing (~> 0.3)
thrift
jaro_winkler (1.5.4)
jira-ruby (2.0.0)
activesupport
atlassian-jwt
......@@ -834,7 +833,7 @@ GEM
rubypants (~> 0.2)
orm_adapter (0.5.0)
os (1.0.0)
parallel (1.19.1)
parallel (1.19.2)
parser (2.7.2.0)
ast (~> 2.4.1)
parslet (1.8.2)
......@@ -958,7 +957,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.8.1)
redis (>= 4, < 5)
regexp_parser (1.5.1)
regexp_parser (1.8.2)
regexp_property_values (0.3.5)
representable (3.0.4)
declarative (< 0.1.0)
......@@ -1019,24 +1018,29 @@ GEM
pg
rails
sqlite3
rubocop (0.82.0)
jaro_winkler (~> 1.5.1)
rubocop (0.89.1)
parallel (~> 1.10)
parser (>= 2.7.0.1)
parser (>= 2.7.1.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.7)
rexml
rubocop-ast (>= 0.3.0, < 1.0)
ruby-progressbar (~> 1.7)
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 (>= 0.51)
rubocop-performance (1.5.2)
rubocop (>= 0.71.0)
rubocop-rails (2.5.2)
activesupport
rubocop-performance (1.8.1)
rubocop (>= 0.87.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.8.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 0.72.0)
rubocop-rspec (1.37.0)
rubocop (>= 0.68.1)
rubocop (>= 0.87.0)
rubocop-rspec (1.44.1)
rubocop (~> 0.87)
rubocop-ast (>= 0.7.1)
ruby-enum (0.7.2)
i18n
ruby-fogbugz (0.2.1)
......@@ -1249,7 +1253,7 @@ GEM
xpath (3.2.0)
nokogiri (~> 1.8)
yajl-ruby (1.4.1)
zeitwerk (2.4.0)
zeitwerk (2.4.1)
PLATFORMS
ruby
......@@ -1353,7 +1357,7 @@ DEPENDENCIES
gitlab-puma (~> 4.3.3.gitlab.2)
gitlab-puma_worker_killer (~> 0.1.1.gitlab.1)
gitlab-sidekiq-fetcher (= 0.5.2)
gitlab-styles (~> 4.3.0)
gitlab-styles (~> 5.0.0)
gitlab_chronic_duration (~> 0.10.6.2)
gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2)
......@@ -1473,9 +1477,6 @@ DEPENDENCIES
rspec-retry (~> 0.6.1)
rspec_junit_formatter
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-prof (~> 1.3.0)
ruby-progressbar
......
......@@ -52,7 +52,7 @@ export default {
<p class="gl-mb-0">
{{
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>
......
......@@ -38,8 +38,11 @@ export default {
isJira() {
return this.propsSource.type === 'jira';
},
isInstanceLevel() {
return this.propsSource.integrationLevel === integrationLevels.INSTANCE;
isInstanceOrGroupLevel() {
return (
this.propsSource.integrationLevel === integrationLevels.INSTANCE ||
this.propsSource.integrationLevel === integrationLevels.GROUP
);
},
showJiraIssuesFields() {
return this.isJira && this.glFeatures.jiraIssuesIntegration;
......@@ -91,7 +94,7 @@ export default {
v-bind="propsSource.jiraIssuesProps"
/>
<div v-if="isEditable" class="footer-block row-content-block">
<template v-if="isInstanceLevel">
<template v-if="isInstanceOrGroupLevel">
<gl-button
v-gl-modal.confirmSaveIntegration
category="primary"
......
......@@ -108,12 +108,7 @@ export default {
:label="s__('Integrations|Comment detail:')"
data-testid="comment-detail"
>
<input
v-if="isInheriting"
name="service[comment_detail]"
type="hidden"
:value="commentDetail"
/>
<input name="service[comment_detail]" type="hidden" :value="commentDetail" />
<gl-form-radio
v-for="commentDetailOption in commentDetailOptions"
:key="commentDetailOption.value"
......
......@@ -150,6 +150,14 @@ module PageLayoutHelper
css_class.join(' ')
end
def page_itemtype(itemtype = nil)
if itemtype
@page_itemtype = { itemscope: true, itemtype: itemtype }
else
@page_itemtype || {}
end
end
private
def generic_canonical_url
......
......@@ -91,18 +91,18 @@ module UsersHelper
end
end
def work_information(user)
def work_information(user, with_schema_markup: false)
return unless user
organization = user.organization
job_title = user.job_title
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?
job_title
render_job_title(job_title, with_schema_markup: with_schema_markup)
elsif organization.present?
organization
render_organization(organization, with_schema_markup: with_schema_markup)
end
end
......@@ -151,6 +151,35 @@ module UsersHelper
items
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
UsersHelper.prepend_if_ee('EE::UsersHelper')
......@@ -4,10 +4,16 @@ class Analytics::DevopsAdoption::Segment < ApplicationRecord
ALLOWED_SEGMENT_COUNT = 20
has_many :segment_selections
has_many :groups, through: :segment_selections
validates :name, presence: true, uniqueness: true, length: { maximum: 255 }
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
def validate_segment_count
......
......@@ -70,6 +70,8 @@
= render 'shared/members/sort_dropdown'
- if vue_members_list_enabled
.js-group-members-list{ data: group_members_list_data_attributes(@group, @members) }
.loading
.spinner.spinner-md
- else
%ul.content-list.members-list{ data: { qa_selector: 'members_list' } }
= render partial: 'shared/members/member',
......@@ -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 }
- if vue_members_list_enabled
.js-group-linked-list{ data: linked_groups_list_data_attributes(@group) }
.loading
.spinner.spinner-md
- else
%ul.content-list.members-list{ data: { qa_selector: 'groups_list' } }
- @group.shared_with_group_links.each do |group_link|
......@@ -100,6 +104,8 @@
= render 'shared/members/search_field', name: 'search_invited'
- if vue_members_list_enabled
.js-group-invited-members-list{ data: group_members_list_data_attributes(@group, @invited_members) }
.loading
.spinner.spinner-md
- else
%ul.content-list.members-list
= render partial: 'shared/members/member',
......@@ -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 }
- if vue_members_list_enabled
.js-group-access-requests-list{ data: group_members_list_data_attributes(@group, @requesters) }
.loading
.spinner.spinner-md
- else
%ul.content-list.members-list
= render partial: 'shared/members/member',
......
......@@ -20,6 +20,6 @@
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
%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'
= yield
......@@ -4,6 +4,7 @@
- page_title @user.blocked? ? s_('UserProfile|Blocked user') : @user.name
- page_description @user.bio_html
- header_title @user.name, user_path(@user)
- page_itemtype 'http://schema.org/Person'
- link_classes = "flex-grow-1 mx-1 "
= content_for :meta_tags do
......@@ -35,7 +36,7 @@
.profile-header{ class: [('with-no-profile-tabs' if profile_tabs.empty?)] }
.avatar-holder
= 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?
.user-info
......@@ -44,7 +45,7 @@
= render "users/profile_basic_info"
- else
.user-info
.cover-title
.cover-title{ itemprop: 'name' }
= @user.name
- if @user.status
......@@ -54,15 +55,15 @@
= render "users/profile_basic_info"
.cover-desc.cgray.mb-1.mb-sm-2
- 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')
%span.vertical-align-middle
%span.vertical-align-middle{ itemprop: 'addressLocality' }
= @user.location
- unless work_information(@user).blank?
.profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline
= sprite_icon('work', css_class: 'vertical-align-middle fgray')
%span.vertical-align-middle
= work_information(@user)
= work_information(@user, with_schema_markup: true)
.cover-desc.cgray.mb-1.mb-sm-2
- unless @user.skype.blank?
.profile-link-holder.middle-dot-divider
......@@ -80,10 +81,10 @@
.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?
= 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?
.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?
.cover-desc.cgray
.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 {
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 {
"""
Merge base of the branch the comment was made on
......@@ -9165,6 +9240,41 @@ type Group {
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
"""
......@@ -16540,6 +16650,31 @@ type Query {
"""
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
"""
......
......@@ -1000,6 +1000,16 @@ Autogenerated return type of DestroySnippet.
| `text` | String | Text of 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
| Field | Type | Description |
......
......@@ -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
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
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`.
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
information is displayed when you view the package details:
- [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#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)
When using Maven and NPM, you can view which pipeline published the package, as well as the commit and
user who triggered it.
When using Maven and NPM, you can view which pipeline published the package, and
the commit and user who triggered it.
## Download a package
To download a package:
1. Go to **Packages & Registries > Package Registry**.
1. Click 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. Select 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
......
......@@ -11,11 +11,16 @@ import {
GlInputGroupText,
GlLoadingIcon,
} from '@gitlab/ui';
import { omit } from 'lodash';
import * as Sentry from '~/sentry/wrapper';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
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 { fetchPolicies } from '~/lib/graphql';
import {
DAST_SITE_VALIDATION_HTTP_HEADER_KEY,
DAST_SITE_VALIDATION_METHOD_HTTP_HEADER,
DAST_SITE_VALIDATION_METHOD_TEXT_FILE,
DAST_SITE_VALIDATION_METHODS,
DAST_SITE_VALIDATION_STATUS,
......@@ -27,6 +32,7 @@ import dastSiteValidationQuery from '../graphql/dast_site_validation.query.graph
export default {
name: 'DastSiteValidation',
components: {
ClipboardButton,
GlAlert,
GlButton,
GlCard,
......@@ -38,6 +44,7 @@ export default {
GlInputGroupText,
GlLoadingIcon,
},
mixins: [glFeatureFlagsMixin()],
apollo: {
dastSiteValidation: {
query: dastSiteValidationQuery,
......@@ -103,6 +110,16 @@ export default {
};
},
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() {
try {
return new URL(this.targetUrl);
......@@ -119,12 +136,18 @@ export default {
isTextFileValidation() {
return this.validationMethod === DAST_SITE_VALIDATION_METHOD_TEXT_FILE;
},
isHttpHeaderValidation() {
return this.validationMethod === DAST_SITE_VALIDATION_METHOD_HTTP_HEADER;
},
textFileName() {
return `GitLab-DAST-Site-Validation-${this.token}.txt`;
},
locationStepLabel() {
return DAST_SITE_VALIDATION_METHODS[this.validationMethod].i18n.locationStepLabel;
},
httpHeader() {
return `${DAST_SITE_VALIDATION_HTTP_HEADER_KEY}: uuid-code-${this.token}`;
},
},
watch: {
targetUrl() {
......@@ -132,13 +155,22 @@ export default {
},
},
created() {
this.unsubscribe = this.$watch(() => this.token, this.updateValidationPath, {
immediate: true,
});
this.unsubscribe = this.$watch(
() => [this.token, this.validationMethod],
this.updateValidationPath,
{
immediate: true,
},
);
},
methods: {
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() {
this.unsubscribe();
......@@ -189,7 +221,6 @@ export default {
this.hasValidationError = true;
},
},
validationMethodOptions: Object.values(DAST_SITE_VALIDATION_METHODS),
};
</script>
......@@ -199,7 +230,7 @@ export default {
{{ s__('DastProfiles|Site is not validated yet, please follow the steps.') }}
</gl-alert>
<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
v-if="isTextFileValidation"
......@@ -217,6 +248,16 @@ export default {
{{ textFileName }}
</gl-button>
</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-input-group>
<template #prepend>
......@@ -255,7 +296,7 @@ export default {
<gl-icon name="status_failed" />
{{
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>
......
import { s__ } from '~/locale';
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 = {
[DAST_SITE_VALIDATION_METHOD_TEXT_FILE]: {
value: DAST_SITE_VALIDATION_METHOD_TEXT_FILE,
......@@ -9,6 +11,13 @@ export const DAST_SITE_VALIDATION_METHODS = {
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 = {
......@@ -19,3 +28,4 @@ export const DAST_SITE_VALIDATION_STATUS = {
};
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
before_action do
authorize_read_on_demand_scans!
push_frontend_feature_flag(:security_on_demand_scans_site_validation, @project)
push_frontend_feature_flag(:security_on_demand_scans_http_header_validation, @project)
end
feature_category :dynamic_application_security_testing
......
......@@ -51,6 +51,11 @@ module EE
null: true,
resolver: ::Resolvers::InstanceSecurityDashboardResolver,
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
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
group_activity_analytics
group_bulk_edit
group_webhooks
instance_level_devops_adoption
issuable_default_templates
issue_weights
iterations
......
---
title: Expose Devops Adoption segments via GraphQL
merge_request: 46879
author:
type: added
......@@ -3,8 +3,5 @@
---
inherit_from: ../../../../../lib/gitlab/background_migration/.rubocop.yml
CodeReuse/ActiveRecord:
Enabled: false
Style/Documentation:
Enabled: false
......@@ -14,7 +14,7 @@ export const dastSiteValidation = (status = DAST_SITE_VALIDATION_STATUS.PENDING)
export const dastSiteValidationCreate = (errors = []) => ({
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
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
......@@ -190,7 +190,7 @@ module Gitlab
%r{\A(ee/)?vendor/} => :backend,
%r{\A(Gemfile|Gemfile.lock|Rakefile)\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{\A(ee/)?qa/} => :qa,
......
......@@ -8319,6 +8319,9 @@ msgstr ""
msgid "DastProfiles|Authentication URL"
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."
msgstr ""
......@@ -8385,6 +8388,9 @@ msgstr ""
msgid "DastProfiles|Error Details"
msgstr ""
msgid "DastProfiles|Header validation"
msgstr ""
msgid "DastProfiles|Hide debug messages"
msgstr ""
......@@ -8469,9 +8475,15 @@ msgstr ""
msgid "DastProfiles|Step 1 - Choose site validation method"
msgstr ""
msgid "DastProfiles|Step 2 - Add following HTTP header to your site"
msgstr ""
msgid "DastProfiles|Step 2 - Add following text to the target site"
msgstr ""
msgid "DastProfiles|Step 3 - Confirm header location and validate"
msgstr ""
msgid "DastProfiles|Step 3 - Confirm text file location and validate"
msgstr ""
......@@ -8508,7 +8520,7 @@ msgstr ""
msgid "DastProfiles|Validating..."
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 ""
msgid "DastProfiles|Validation failed. Please try again."
......@@ -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."
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 ""
msgid "Integrations|Return to GitLab for Jira"
......
......@@ -19,6 +19,7 @@ module QA
autoload :Saml, 'qa/flow/saml'
autoload :User, 'qa/flow/user'
autoload :MergeRequest, 'qa/flow/merge_request'
autoload :Pipeline, 'qa/flow/pipeline'
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
)
end.project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Flow::Pipeline.visit_latest_pipeline
{
'test-success': :passed,
......
......@@ -29,7 +29,7 @@ module QA
Flow::Login.sign_in
add_ci_files
project.visit!
view_the_last_pipeline
Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'success')
end
after do
......@@ -64,12 +64,6 @@ module QA
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
{
file_path: '.gitlab-ci.yml',
......
......@@ -57,8 +57,7 @@ module QA
end
project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('create_package')
......
......@@ -94,8 +94,7 @@ module QA
end
project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('deploy')
......
......@@ -61,8 +61,7 @@ module QA
end
project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('deploy')
......
......@@ -74,8 +74,7 @@ module QA
end
project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('run')
......
......@@ -77,8 +77,7 @@ module QA
sha1sum = Digest::SHA1.hexdigest(gitlab_ci)
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform(&:click_on_first_job)
Page::Project::Job::Show.perform do |job|
......
......@@ -27,7 +27,7 @@ module QA
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)
view_pipelines
Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completion')
Page::Project::Pipeline::Show.perform do |parent_pipeline|
expect(parent_pipeline).to have_child_pipeline
......@@ -37,7 +37,7 @@ module QA
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)
view_pipelines
Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completion')
Page::Project::Pipeline::Show.perform do |parent_pipeline|
expect(parent_pipeline).to have_child_pipeline
......@@ -47,12 +47,6 @@ module QA
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
{
file_path: '.child-ci.yml',
......
......@@ -27,7 +27,7 @@ module QA
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)
view_pipelines
Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completion')
Page::Project::Pipeline::Show.perform do |parent_pipeline|
expect(parent_pipeline).to have_child_pipeline
......@@ -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
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|
expect(parent_pipeline).to have_child_pipeline
......@@ -47,12 +47,6 @@ module QA
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
{
file_path: '.child-ci.yml',
......
......@@ -54,8 +54,7 @@ module QA
push.commit_message = 'Create Auto DevOps compatible rack application'
end
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('build')
......@@ -119,8 +118,7 @@ module QA
end
it 'runs an AutoDevOps pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/444' do
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to have_tag('Auto DevOps')
......
......@@ -53,9 +53,7 @@ module QA
project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |show|
expect(show).to have_build('jenkins', status: :success, wait: 15)
......
......@@ -56,11 +56,7 @@ module QA
)
end.project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform do |index|
index.wait_for_latest_pipeline_completion
index.click_on_latest_pipeline
end
Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completion')
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('test-artifacts')
......
......@@ -65,11 +65,7 @@ module QA
dashboard.go_to_project(@project.name)
end
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform do |index|
index.wait_for_latest_pipeline_replication
index.click_on_latest_pipeline
end
Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'replication')
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.wait_for_pipeline_job_replication(@pipeline_job_name)
......@@ -98,11 +94,7 @@ module QA
dashboard.go_to_project(@project.name)
end
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform do |index|
index.wait_for_latest_pipeline_replication
index.click_on_latest_pipeline
end
Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'replication')
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.wait_for_pipeline_job_replication(@pipeline_job_name)
......
......@@ -108,8 +108,7 @@ module QA
end
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)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_on_licenses
......
......@@ -51,8 +51,7 @@ module QA
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
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
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
def in_qa_file?(node)
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
# 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', () => {
'Saving will update the default settings for all projects that are not using custom settings.',
);
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
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
import { integrationLevels } from '~/integrations/edit/constants';
describe('IntegrationForm', () => {
let wrapper;
......@@ -69,14 +70,24 @@ describe('IntegrationForm', () => {
describe('integrationLevel is instance', () => {
it('renders ConfirmationModal', () => {
createComponent({
integrationLevel: 'instance',
integrationLevel: integrationLevels.INSTANCE,
});
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', () => {
createComponent({
integrationLevel: 'project',
......
......@@ -208,4 +208,27 @@ RSpec.describe PageLayoutHelper do
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
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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