Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-04-23 18:10:18 +00:00
parent 7e1e45d40a
commit bc62085601
71 changed files with 683 additions and 621 deletions

View File

@ -1 +1 @@
fee093e7c786e4b1132cb87ab13175aef07265dc
b19cafa222fd7a999167d3f9f8562c2d74b62bfd

View File

@ -204,27 +204,32 @@ export default {
<template v-if="line.left && line.left.type !== $options.CONFLICT_MARKER">
<div
:class="classNameMapCellLeft"
data-testid="leftLineNumber"
data-testid="left-line-number"
class="diff-td diff-line-num"
>
<template v-if="!isLeftConflictMarker">
<span
v-if="shouldRenderCommentButton && !line.hasDiscussionsLeft"
v-gl-tooltip
data-testid="leftCommentButton"
class="add-diff-note tooltip-wrapper"
:title="addCommentTooltipLeft"
>
<button
:draggable="glFeatures.dragCommentSelection"
<div
data-testid="left-comment-button"
role="button"
tabindex="0"
:draggable="!line.left.commentsDisabled && glFeatures.dragCommentSelection"
type="button"
class="add-diff-note unified-diff-components-diff-note-button note-button js-add-diff-note-button qa-diff-comment"
data-qa-selector="diff_comment_button"
:class="{ 'gl-cursor-grab': dragging }"
:disabled="line.left.commentsDisabled"
@click="handleCommentButton(line.left)"
@dragstart="onDragStart({ ...line.left, index })"
></button>
:aria-disabled="line.left.commentsDisabled"
@click="!line.left.commentsDisabled && handleCommentButton(line.left)"
@keydown.enter="!line.left.commentsDisabled && handleCommentButton(line.left)"
@keydown.space="!line.left.commentsDisabled && handleCommentButton(line.left)"
@dragstart="!line.left.commentsDisabled && onDragStart({ ...line.left, index })"
></div>
</span>
</template>
<a
@ -238,7 +243,7 @@ export default {
v-if="line.hasDiscussionsLeft"
:discussions="line.left.discussions"
:discussions-expanded="line.left.discussionsExpanded"
data-testid="leftDiscussions"
data-testid="left-discussions"
@toggleLineDiscussions="
toggleLineDiscussions({
lineCode: line.left.line_code,
@ -268,7 +273,7 @@ export default {
:key="line.left.line_code"
:class="[parallelViewLeftLineType, { parallel: !inline }]"
class="diff-td line_content with-coverage left-side"
data-testid="leftContent"
data-testid="left-content"
@mousedown="handleParallelLineMouseDown"
>
<strong v-if="isLeftConflictMarker">{{ conflictText(line.left) }}</strong>
@ -277,7 +282,7 @@ export default {
</template>
<template v-else-if="!inline || (line.left && line.left.type === $options.CONFLICT_MARKER)">
<div
data-testid="leftEmptyCell"
data-testid="left-empty-cell"
class="diff-td diff-line-num old_line empty-cell"
:class="emptyCellLeftClassMap"
>
@ -313,19 +318,24 @@ export default {
<span
v-if="shouldRenderCommentButton && !line.hasDiscussionsRight"
v-gl-tooltip
data-testid="rightCommentButton"
class="add-diff-note tooltip-wrapper"
:title="addCommentTooltipRight"
>
<button
:draggable="glFeatures.dragCommentSelection"
<div
data-testid="right-comment-button"
role="button"
tabindex="0"
:draggable="!line.right.commentsDisabled && glFeatures.dragCommentSelection"
type="button"
class="add-diff-note unified-diff-components-diff-note-button note-button js-add-diff-note-button qa-diff-comment"
:class="{ 'gl-cursor-grab': dragging }"
:disabled="line.right.commentsDisabled"
@click="handleCommentButton(line.right)"
@dragstart="onDragStart({ ...line.right, index })"
></button>
:aria-disabled="line.right.commentsDisabled"
@click="!line.right.commentsDisabled && handleCommentButton(line.right)"
@keydown.enter="!line.right.commentsDisabled && handleCommentButton(line.right)"
@keydown.space="!line.right.commentsDisabled && handleCommentButton(line.right)"
@dragstart="!line.right.commentsDisabled && onDragStart({ ...line.right, index })"
></div>
</span>
</template>
<a
@ -339,7 +349,7 @@ export default {
v-if="line.hasDiscussionsRight"
:discussions="line.right.discussions"
:discussions-expanded="line.right.discussionsExpanded"
data-testid="rightDiscussions"
data-testid="right-discussions"
@toggleLineDiscussions="
toggleLineDiscussions({
lineCode: line.right.line_code,
@ -381,7 +391,7 @@ export default {
</template>
<template v-else>
<div
data-testid="rightEmptyCell"
data-testid="right-empty-cell"
class="diff-td diff-line-num old_line empty-cell"
:class="emptyCellRightClassMap"
></div>

View File

@ -1,7 +1,6 @@
<script>
import { GlLink, GlTable, GlAlert } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import ManageViaMR from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
import {
REPORT_TYPE_SAST,
REPORT_TYPE_DAST,
@ -12,8 +11,8 @@ import {
REPORT_TYPE_API_FUZZING,
REPORT_TYPE_LICENSE_COMPLIANCE,
} from '~/vue_shared/security_reports/constants';
import { scanners } from './constants';
import ManageSast from './manage_sast.vue';
import { scanners } from './scanners_constants';
import Upgrade from './upgrade.vue';
const borderClasses = 'gl-border-b-1! gl-border-b-solid! gl-border-gray-100!';
@ -41,7 +40,7 @@ export default {
},
getComponentForItem(item) {
const COMPONENTS = {
[REPORT_TYPE_SAST]: ManageViaMR,
[REPORT_TYPE_SAST]: ManageSast,
[REPORT_TYPE_DAST]: Upgrade,
[REPORT_TYPE_DAST_PROFILES]: Upgrade,
[REPORT_TYPE_DEPENDENCY_SCANNING]: Upgrade,
@ -50,6 +49,7 @@ export default {
[REPORT_TYPE_API_FUZZING]: Upgrade,
[REPORT_TYPE_LICENSE_COMPLIANCE]: Upgrade,
};
return COMPONENTS[item.type];
},
},
@ -95,12 +95,7 @@ export default {
</template>
<template #cell(manage)="{ item }">
<component
:is="getComponentForItem(item)"
:feature="item"
:data-testid="item.type"
@error="onError"
/>
<component :is="getComponentForItem(item)" :data-testid="item.type" @error="onError" />
</template>
</gl-table>
</div>

View File

@ -0,0 +1,59 @@
<script>
import { GlButton } from '@gitlab/ui';
import { redirectTo } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import configureSastMutation from '~/security_configuration/graphql/configure_sast.mutation.graphql';
export default {
components: {
GlButton,
},
inject: {
projectPath: {
from: 'projectPath',
default: '',
},
},
data() {
return {
isLoading: false,
};
},
methods: {
async mutate() {
this.isLoading = true;
try {
const { data } = await this.$apollo.mutate({
mutation: configureSastMutation,
variables: {
input: {
projectPath: this.projectPath,
configuration: { global: [], pipeline: [], analyzers: [] },
},
},
});
const { errors, successPath } = data.configureSast;
if (errors.length > 0) {
throw new Error(errors[0]);
}
if (!successPath) {
throw new Error(s__('SecurityConfiguration|SAST merge request creation mutation failed'));
}
redirectTo(successPath);
} catch (e) {
this.$emit('error', e.message);
this.isLoading = false;
}
},
},
};
</script>
<template>
<gl-button :loading="isLoading" variant="success" category="secondary" @click="mutate">{{
s__('SecurityConfiguration|Configure via merge request')
}}</gl-button>
</template>

View File

@ -1,7 +1,6 @@
import { helpPagePath } from '~/helpers/help_page_helper';
import { __, s__ } from '~/locale';
import configureSastMutation from '~/security_configuration/graphql/configure_sast.mutation.graphql';
import {
REPORT_TYPE_SAST,
REPORT_TYPE_DAST,
@ -135,18 +134,3 @@ export const scanners = [
type: REPORT_TYPE_LICENSE_COMPLIANCE,
},
];
export const featureToMutationMap = {
[REPORT_TYPE_SAST]: {
mutationId: 'configureSast',
getMutationPayload: (projectPath) => ({
mutation: configureSastMutation,
variables: {
input: {
projectPath,
configuration: { global: [], pipeline: [], analyzers: [] },
},
},
}),
},
};

View File

@ -1,6 +1,6 @@
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { UPGRADE_CTA } from './constants';
import { UPGRADE_CTA } from './scanners_constants';
export default {
components: {

View File

@ -1,73 +0,0 @@
<script>
import { GlButton } from '@gitlab/ui';
import { featureToMutationMap } from 'ee_else_ce/security_configuration/components/constants';
import { redirectTo } from '~/lib/utils/url_utility';
import { sprintf, s__ } from '~/locale';
import apolloProvider from '../provider';
export default {
apolloProvider,
components: {
GlButton,
},
inject: ['projectPath'],
props: {
feature: {
type: Object,
required: true,
},
},
data() {
return {
isLoading: false,
};
},
computed: {
featureSettings() {
return featureToMutationMap[this.feature.type];
},
},
methods: {
async mutate() {
this.isLoading = true;
try {
const mutation = this.featureSettings;
const { data } = await this.$apollo.mutate(mutation.getMutationPayload(this.projectPath));
const { errors, successPath } = data[mutation.mutationId];
if (errors.length > 0) {
throw new Error(errors[0]);
}
if (!successPath) {
throw new Error(
sprintf(this.$options.i18n.noSuccessPathError, { featureName: this.feature.name }),
);
}
redirectTo(successPath);
} catch (e) {
this.$emit('error', e.message);
this.isLoading = false;
}
},
},
i18n: {
buttonLabel: s__('SecurityConfiguration|Configure via Merge Request'),
noSuccessPathError: s__(
'SecurityConfiguration|%{featureName} merge request creation mutation failed',
),
},
};
</script>
<template>
<gl-button
v-if="!feature.configured"
:loading="isLoading"
variant="success"
category="secondary"
@click="mutate"
>{{ $options.i18n.buttonLabel }}</gl-button
>
</template>

View File

@ -1,9 +0,0 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
export default new VueApollo({
defaultClient: createDefaultClient(),
});

View File

@ -85,7 +85,7 @@ module CascadingNamespaceSettingAttribute
next self[attribute] unless self.class.cascading_settings_feature_enabled?
next self[attribute] if will_save_change_to_attribute?(attribute)
next locked_value(attribute) if cascading_attribute_locked?(attribute)
next locked_value(attribute) if cascading_attribute_locked?(attribute, include_self: false)
next self[attribute] unless self[attribute].nil?
cascaded_value = cascaded_ancestor_value(attribute)
@ -115,8 +115,8 @@ module CascadingNamespaceSettingAttribute
end
def define_lock_methods(attribute)
define_method("#{attribute}_locked?") do
cascading_attribute_locked?(attribute)
define_method("#{attribute}_locked?") do |include_self: false|
cascading_attribute_locked?(attribute, include_self: include_self)
end
define_method("#{attribute}_locked_by_ancestor?") do
@ -144,7 +144,7 @@ module CascadingNamespaceSettingAttribute
def define_validator_methods(attribute)
define_method("#{attribute}_changeable?") do
return unless cascading_attribute_changed?(attribute)
return unless cascading_attribute_locked?(attribute)
return unless cascading_attribute_locked?(attribute, include_self: false)
errors.add(attribute, s_('CascadingSettings|cannot be changed because it is locked by an ancestor'))
end
@ -152,7 +152,7 @@ module CascadingNamespaceSettingAttribute
define_method("lock_#{attribute}_changeable?") do
return unless cascading_attribute_changed?("lock_#{attribute}")
if cascading_attribute_locked?(attribute)
if cascading_attribute_locked?(attribute, include_self: false)
return errors.add(:"lock_#{attribute}", s_('CascadingSettings|cannot be changed because it is locked by an ancestor'))
end
@ -213,8 +213,9 @@ module CascadingNamespaceSettingAttribute
Gitlab::CurrentSettings.public_send("lock_#{attribute}") # rubocop:disable GitlabSecurity/PublicSend
end
def cascading_attribute_locked?(attribute)
locked_by_ancestor?(attribute) || locked_by_application_setting?(attribute)
def cascading_attribute_locked?(attribute, include_self:)
locked_by_self = include_self ? public_send("lock_#{attribute}?") : false # rubocop:disable GitlabSecurity/PublicSend
locked_by_self || locked_by_ancestor?(attribute) || locked_by_application_setting?(attribute)
end
def cascading_attribute_changed?(attribute)

View File

@ -36,12 +36,22 @@ module Suggestions
.track_apply_suggestion_action(user: current_user)
end
def author
authors = suggestion_set.authors
return unless authors.one?
Gitlab::Git::User.from_gitlab(authors.first)
end
def multi_service
params = {
commit_message: commit_message,
branch_name: suggestion_set.branch,
start_branch: suggestion_set.branch,
actions: suggestion_set.actions
actions: suggestion_set.actions,
author_name: author&.name,
author_email: author&.email
}
::Files::MultiService.new(suggestion_set.project, current_user, params)

View File

@ -1,4 +1,4 @@
- parsed_with_gfm = "Content parsed with #{link_to('GitLab Flavored Markdown', help_page_path('user/markdown'), target: '_blank')}.".html_safe
- parsed_with_gfm = (_("Content parsed with %{link}.") % { link: link_to('GitLab Flavored Markdown', help_page_path('user/markdown'), target: '_blank') }).html_safe
= form_for @appearance, url: admin_appearances_path, html: { class: 'gl-mt-3' } do |f|
= form_errors(@appearance)
@ -6,22 +6,22 @@
.row
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0 Navigation bar
%h4.gl-mt-0= _('Navigation bar')
.col-lg-8
.form-group
= f.label :header_logo, 'Header logo', class: 'col-form-label label-bold pt-0'
= f.label :header_logo, _('Header logo'), class: 'col-form-label label-bold pt-0'
%p
- if @appearance.header_logo?
= image_tag @appearance.header_logo_path, class: 'appearance-light-logo-preview'
- if @appearance.persisted?
%br
= link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm"
= link_to _('Remove header logo'), header_logos_admin_appearances_path, data: { confirm: _("Header logo will be removed. Are you sure?") }, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm"
%hr
= f.hidden_field :header_logo_cache
= f.file_field :header_logo, class: "", accept: 'image/*'
.hint
Maximum file size is 1MB. Pages are optimized for a 28px tall header logo
= _('Maximum file size is 1MB. Pages are optimized for a 28px tall header logo')
%hr
.row
.col-lg-4.profile-settings-sidebar
@ -29,27 +29,27 @@
.col-lg-8
.form-group
= f.label :favicon, 'Favicon', class: 'col-form-label label-bold pt-0'
= f.label :favicon, _('Favicon'), class: 'col-form-label label-bold pt-0'
%p
- if @appearance.favicon?
= image_tag @appearance.favicon_path, class: 'appearance-light-logo-preview'
- if @appearance.persisted?
%br
= link_to 'Remove favicon', favicon_admin_appearances_path, data: { confirm: "Favicon will be removed. Are you sure?"}, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm"
= link_to _('Remove favicon'), favicon_admin_appearances_path, data: { confirm: _("Favicon will be removed. Are you sure?") }, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm"
%hr
= f.hidden_field :favicon_cache
= f.file_field :favicon, class: '', accept: 'image/*'
.hint
Maximum file size is 1MB. Image size must be 32x32px. Allowed image formats are #{favicon_extension_whitelist}.
= _("Maximum file size is 1MB. Image size must be 32x32px. Allowed image formats are %{favicon_extension_whitelist}.") % { favicon_extension_whitelist: favicon_extension_whitelist }
%br
Images with incorrect dimensions are not resized automatically, and may result in unexpected behavior.
= _("Images with incorrect dimensions are not resized automatically, and may result in unexpected behavior.")
= render partial: 'admin/appearances/system_header_footer_form', locals: { form: f }
%hr
.row
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0 Sign in/Sign up pages
%h4.gl-mt-0= _('Sign in/Sign up pages')
.col-lg-8
.form-group
@ -67,17 +67,17 @@
= image_tag @appearance.logo_path, class: 'appearance-logo-preview'
- if @appearance.persisted?
%br
= link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm remove-logo"
= link_to _('Remove logo'), logo_admin_appearances_path, data: { confirm: _("Logo will be removed. Are you sure?") }, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm remove-logo"
%hr
= f.hidden_field :logo_cache
= f.file_field :logo, class: "", accept: 'image/*'
.hint
Maximum file size is 1MB. Pages are optimized for a 640x360 px logo.
= _('Maximum file size is 1MB. Pages are optimized for a 640x360 px logo.')
%hr
.row
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0 New project pages
%h4.gl-mt-0= _('New project pages')
.col-lg-8
.form-group
@ -90,7 +90,7 @@
%hr
.row
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0 Profile image guideline
%h4.gl-mt-0= _('Profile image guideline')
.col-lg-8
.form-group
@ -101,13 +101,13 @@
= parsed_with_gfm
.gl-mt-3.gl-mb-3
= f.submit 'Update appearance settings', class: 'btn gl-button btn-confirm'
= f.submit _('Update appearance settings'), class: 'btn gl-button btn-confirm'
- if @appearance.persisted? || @appearance.updated_at
.mt-4
- if @appearance.persisted?
Preview last save:
= link_to 'Sign-in page', preview_sign_in_admin_appearances_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer'
= link_to 'New project page', new_project_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer'
= link_to _('Sign-in page'), preview_sign_in_admin_appearances_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer'
= link_to _('New project page'), new_project_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer'
- if @appearance.updated_at
%span.float-right

View File

@ -0,0 +1,5 @@
---
title: Add VulnerabiltyFindingEvidenceResponse model
merge_request: 59563
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Fix multiline comment dragging in Firefox
merge_request: 58692
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Externalize strings in appearances/_form.html.haml
merge_request: 58135
author: nuwe1
type: other

View File

@ -0,0 +1,5 @@
---
title: Add ability to include self in cascading setting lock check
merge_request: 60031
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Improve dropdown and search in the tags page
merge_request: 60145
author:
type: changed

View File

@ -0,0 +1,6 @@
---
title: Make Sentry processors for GitLab-internal error tracking compatible with new
version of Sentry gem
merge_request: 59565
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Commit author for suggestions is note author
merge_request: 39940
author:
type: added

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/327055
milestone: '13.11'
type: development
group: group::continuous integration
default_enabled: false
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: sentry_processors_before_send
introduced_by_url:
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/849#processors
milestone: '13.11'
type: development
group: team::Scalability
default_enabled: false

View File

@ -1,5 +1,5 @@
---
name: project_sidebar_refactor
name: sidebar_refactor
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58638
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326111
milestone: '13.11'

View File

@ -16,4 +16,3 @@ tier:
- free
- premium
- ultimate
skip_validation: true

View File

@ -16,4 +16,3 @@ tier:
- free
- premium
- ultimate
skip_validation: true

View File

@ -16,4 +16,3 @@ tier:
- free
- premium
- ultimate
skip_validation: true

View File

@ -16,4 +16,3 @@ tier:
- free
- premium
- ultimate
skip_validation: true

View File

@ -16,4 +16,3 @@ tier:
- free
- premium
- ultimate
skip_validation: true

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class CreateVulnerabilityFindingEvidenceResponses < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
create_table_with_constraints :vulnerability_finding_evidence_responses do |t|
t.timestamps_with_timezone null: false
t.references :vulnerability_finding_evidence, index: { name: 'finding_evidence_responses_on_finding_evidences_id' }, null: false, foreign_key: { on_delete: :cascade }
t.integer :status_code
t.text :reason_phrase
t.text_limit :reason_phrase, 2048
end
end
def down
with_lock_retries do
drop_table :vulnerability_finding_evidence_responses
end
end
end

View File

@ -0,0 +1 @@
26f3978600808eae8396e0d5292bae95feca52ff3e44a019c04bd9708f27cc84

View File

@ -18601,6 +18601,25 @@ CREATE SEQUENCE vulnerability_finding_evidence_requests_id_seq
ALTER SEQUENCE vulnerability_finding_evidence_requests_id_seq OWNED BY vulnerability_finding_evidence_requests.id;
CREATE TABLE vulnerability_finding_evidence_responses (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
vulnerability_finding_evidence_id bigint NOT NULL,
status_code integer,
reason_phrase text,
CONSTRAINT check_58b124ab48 CHECK ((char_length(reason_phrase) <= 2048))
);
CREATE SEQUENCE vulnerability_finding_evidence_responses_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE vulnerability_finding_evidence_responses_id_seq OWNED BY vulnerability_finding_evidence_responses.id;
CREATE TABLE vulnerability_finding_evidences (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@ -19908,6 +19927,8 @@ ALTER TABLE ONLY vulnerability_feedback ALTER COLUMN id SET DEFAULT nextval('vul
ALTER TABLE ONLY vulnerability_finding_evidence_requests ALTER COLUMN id SET DEFAULT nextval('vulnerability_finding_evidence_requests_id_seq'::regclass);
ALTER TABLE ONLY vulnerability_finding_evidence_responses ALTER COLUMN id SET DEFAULT nextval('vulnerability_finding_evidence_responses_id_seq'::regclass);
ALTER TABLE ONLY vulnerability_finding_evidences ALTER COLUMN id SET DEFAULT nextval('vulnerability_finding_evidences_id_seq'::regclass);
ALTER TABLE ONLY vulnerability_finding_links ALTER COLUMN id SET DEFAULT nextval('vulnerability_finding_links_id_seq'::regclass);
@ -21541,6 +21562,9 @@ ALTER TABLE ONLY vulnerability_feedback
ALTER TABLE ONLY vulnerability_finding_evidence_requests
ADD CONSTRAINT vulnerability_finding_evidence_requests_pkey PRIMARY KEY (id);
ALTER TABLE ONLY vulnerability_finding_evidence_responses
ADD CONSTRAINT vulnerability_finding_evidence_responses_pkey PRIMARY KEY (id);
ALTER TABLE ONLY vulnerability_finding_evidences
ADD CONSTRAINT vulnerability_finding_evidences_pkey PRIMARY KEY (id);
@ -21779,6 +21803,8 @@ CREATE INDEX expired_artifacts_temp_index ON ci_job_artifacts USING btree (id, c
CREATE INDEX finding_evidence_requests_on_finding_evidence_id ON vulnerability_finding_evidence_requests USING btree (vulnerability_finding_evidence_id);
CREATE INDEX finding_evidence_responses_on_finding_evidences_id ON vulnerability_finding_evidence_responses USING btree (vulnerability_finding_evidence_id);
CREATE INDEX finding_evidences_on_vulnerability_occurrence_id ON vulnerability_finding_evidences USING btree (vulnerability_occurrence_id);
CREATE INDEX finding_links_on_vulnerability_occurrence_id ON vulnerability_finding_links USING btree (vulnerability_occurrence_id);
@ -25691,6 +25717,9 @@ ALTER TABLE ONLY service_desk_settings
ALTER TABLE ONLY saml_group_links
ADD CONSTRAINT fk_rails_22e312c530 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerability_finding_evidence_responses
ADD CONSTRAINT fk_rails_2390a09723 FOREIGN KEY (vulnerability_finding_evidence_id) REFERENCES vulnerability_finding_evidences(id) ON DELETE CASCADE;
ALTER TABLE ONLY dast_profiles
ADD CONSTRAINT fk_rails_23cae5abe1 FOREIGN KEY (dast_scanner_profile_id) REFERENCES dast_scanner_profiles(id) ON DELETE CASCADE;

View File

@ -242,6 +242,8 @@ GET /groups/:id/issues?state=opened
| `due_date` | string | no | Return issues that have no due date, are overdue, or whose due date is this week, this month, or between two weeks ago and next month. Accepts: `0` (no due date), `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`. _(Introduced in [GitLab 13.3](https://gitlab.com/gitlab-org/gitlab/-/issues/233420))_ |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `iids[]` | integer array | no | Return only the issues having the given `iid` |
| `iteration_id` **(PREMIUM)** | integer | no | Return issues assigned to the given iteration ID. `None` returns issues that do not belong to an iteration. `Any` returns issues that belong to an iteration. Mutually exclusive with `iteration_title`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.6)_ |
| `iteration_title` **(PREMIUM)** | string | no | Return issues assigned to the iteration with the given title. Similar to `iteration_id` and mutually exclusive with `iteration_id`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.6)_ |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/14016) in GitLab 10.0)_ |
@ -419,6 +421,8 @@ GET /projects/:id/issues?state=opened
| `due_date` | string | no | Return issues that have no due date, are overdue, or whose due date is this week, this month, or between two weeks ago and next month. Accepts: `0` (no due date), `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`. _(Introduced in [GitLab 13.3](https://gitlab.com/gitlab-org/gitlab/-/issues/233420))_ |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `iids[]` | integer array | no | Return only the issues having the given `iid` |
| `iteration_id` **(PREMIUM)** | integer | no | Return issues assigned to the given iteration ID. `None` returns issues that do not belong to an iteration. `Any` returns issues that belong to an iteration. Mutually exclusive with `iteration_title`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.6)_ |
| `iteration_title` **(PREMIUM)** | string | no | Return issues assigned to the iteration with the given title. Similar to `iteration_id` and mutually exclusive with `iteration_id`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.6)_ |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/14016) in GitLab 10.0)_ |

View File

@ -82,7 +82,9 @@ cascading_attr :delayed_project_removal
- `delayed_project_removal_locked_by_ancestor?`
- `delayed_project_removal_locked_by_application_setting?`
- `delayed_project_removal?` (Boolean attributes only)
- `delayed_project_removal_locked_ancestor` - (Returns locked namespace settings object [namespace_id])
- `delayed_project_removal_locked_ancestor` (Returns locked namespace settings object [namespace_id])
### Attribute reader method (`delayed_project_removal`)
The attribute reader method (`delayed_project_removal`) returns the correct
cascaded value using the following criteria:
@ -94,3 +96,12 @@ cascaded value using the following criteria:
1. Return this namespace's attribute, if not nil.
1. Return value from nearest ancestor where value is not nil.
1. Return instance-level application setting.
### `_locked?` method
By default, the `_locked?` method (`delayed_project_removal_locked?`) returns
`true` if an ancestor of the group or application setting locks the attribute.
It returns `false` when called from the group that locked the attribute.
When `include_self: true` is specified, it returns `true` when called from the group that locked the attribute.
This would be relevant, for example, when checking if an attribute is locked from a project.

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -221,7 +221,7 @@ to set the status for each alert:
By default, the list doesn't display resolved or dismissed alerts. To show these alerts, clear the
checkbox **Hide dismissed alerts**.
![Policy Alert List](img/threat_monitoring_policy_alert_list_v13_11.png)
![Policy Alert List](img/threat_monitoring_policy_alert_list_v13_12.png)
Clicking an alert's name takes the user to the [alert details page](../../../operations/incident_management/alerts.md#alert-details-page).

View File

@ -515,6 +515,9 @@ to your branch to address your reviewers' requests.
![A code change suggestion displayed, with the button to apply the batch of suggestions highlighted.](img/apply_batch_of_suggestions_v13_1.jpg "Apply a batch of suggestions")
WARNING:
Suggestions applied from multiple authors creates a commit authored by the user applying the suggestions.
## Start a thread by replying to a standard comment
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30299) in GitLab 11.9

View File

@ -8,13 +8,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Import groups from another instance of GitLab **(FREE)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/249160) in GitLab 13.7.
> - It's [deployed behind a feature flag](../../feature_flags.md), disabled by default.
> - It's enabled on GitLab.com.
## Overview
> - [Deployed behind a feature flag](../../feature_flags.md), disabled by default.
> - Enabled on GitLab.com.
WARNING:
This feature is [under construction](https://gitlab.com/groups/gitlab-org/-/epics/2771) and currently migrates only some of the Group data. Please see below for the full list of what is included in the migration at this time.
This feature is [under construction](https://gitlab.com/groups/gitlab-org/-/epics/2771), and migrates only some of the group data. Refer to the following information for the list of what's included in the migration.
Using GitLab Group Migration, you can migrate existing top-level groups from GitLab.com or a self-managed instance. Groups can be migrated to a target instance, as a top-level group, or as a subgroup of any existing top-level group.

View File

@ -17,11 +17,6 @@ module Gitlab
MergeRequest
end
def timestamp_projection
Arel::Nodes::NamedFunction.new('COALESCE', column_list)
end
override :column_list
def column_list
[
issue_metrics_table[:first_mentioned_in_commit_at],

View File

@ -17,8 +17,8 @@ module Gitlab
Issue
end
def timestamp_projection
issue_table[:created_at]
def column_list
[issue_table[:created_at]]
end
end
end

View File

@ -17,13 +17,8 @@ module Gitlab
Issue
end
def timestamp_projection
mr_metrics_table[:first_deployed_to_production_at]
end
override :column_list
def column_list
[timestamp_projection]
[mr_metrics_table[:first_deployed_to_production_at]]
end
# rubocop: disable CodeReuse/ActiveRecord

View File

@ -17,8 +17,8 @@ module Gitlab
Issue
end
def timestamp_projection
issue_metrics_table[:first_mentioned_in_commit_at]
def column_list
[issue_metrics_table[:first_mentioned_in_commit_at]]
end
end
end

View File

@ -17,11 +17,6 @@ module Gitlab
Issue
end
def timestamp_projection
Arel::Nodes::NamedFunction.new('COALESCE', column_list)
end
override :column_list
def column_list
[
issue_metrics_table[:first_associated_with_milestone_at],

View File

@ -17,8 +17,8 @@ module Gitlab
MergeRequest
end
def timestamp_projection
mr_table[:created_at]
def column_list
[mr_table[:created_at]]
end
end
end

View File

@ -17,8 +17,8 @@ module Gitlab
MergeRequest
end
def timestamp_projection
mr_metrics_table[:first_deployed_to_production_at]
def column_list
[mr_metrics_table[:first_deployed_to_production_at]]
end
# rubocop: disable CodeReuse/ActiveRecord

View File

@ -17,8 +17,8 @@ module Gitlab
MergeRequest
end
def timestamp_projection
mr_metrics_table[:latest_build_finished_at]
def column_list
[mr_metrics_table[:latest_build_finished_at]]
end
end
end

View File

@ -17,8 +17,8 @@ module Gitlab
MergeRequest
end
def timestamp_projection
mr_metrics_table[:latest_build_started_at]
def column_list
[mr_metrics_table[:latest_build_started_at]]
end
end
end

View File

@ -17,8 +17,8 @@ module Gitlab
MergeRequest
end
def timestamp_projection
mr_metrics_table[:merged_at]
def column_list
[mr_metrics_table[:merged_at]]
end
end
end

View File

@ -11,7 +11,6 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
override :column_list
def column_list
[timestamp_projection]
end

View File

@ -17,11 +17,6 @@ module Gitlab
Issue
end
def timestamp_projection
Arel::Nodes::NamedFunction.new('COALESCE', column_list)
end
override :column_list
def column_list
[
issue_metrics_table[:first_associated_with_milestone_at],

View File

@ -34,14 +34,16 @@ module Gitlab
# Each StageEvent must expose a timestamp or a timestamp like expression in order to build a range query.
# Example: get me all the Issue records between start event end end event
def timestamp_projection
raise NotImplementedError
columns = column_list
columns.one? ? columns.first : Arel::Nodes::NamedFunction.new('COALESCE', columns)
end
# List of columns that are referenced in the `timestamp_projection` expression
# Example timestamp projection: COALESCE(issue_metrics.created_at, issue_metrics.updated_at)
# Expected column list: issue_metrics.created_at, issue_metrics.updated_at
def column_list
[]
raise NotImplementedError
end
# Optionally a StageEvent may apply additional filtering or join other tables on the base query.

View File

@ -1031,7 +1031,7 @@ module Gitlab
[column, temporary_name]
end
batched_migration = queue_batched_background_migration(
queue_batched_background_migration(
'CopyColumnUsingBackgroundMigrationJob',
table,
primary_key,
@ -1040,12 +1040,6 @@ module Gitlab
job_interval: interval,
batch_size: batch_size,
sub_batch_size: sub_batch_size)
if perform_background_migration_inline?
# To ensure the schema is up to date immediately we perform the
# migration inline in dev / test environments.
Gitlab::Database::BackgroundMigration::BatchedMigrationRunner.new.run_entire_migration(batched_migration)
end
end
# Performs a concurrent column rename when using PostgreSQL.

View File

@ -31,9 +31,6 @@ module Gitlab
# Sanitize fields based on those sanitized from Rails.
config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
config.processors << ::Gitlab::ErrorTracking::Processor::SidekiqProcessor
config.processors << ::Gitlab::ErrorTracking::Processor::GrpcErrorProcessor
config.processors << ::Gitlab::ErrorTracking::Processor::ContextPayloadProcessor
# Sanitize authentication headers
config.sanitize_http_headers = %w[Authorization Private-Token]

View File

@ -3,21 +3,12 @@
module Gitlab
module ErrorTracking
module Processor
class ContextPayloadProcessor < ::Raven::Processor
module ContextPayloadProcessor
# This processor is added to inject application context into Sentry
# events generated by Sentry built-in integrations. When the
# integrations are re-implemented and use Gitlab::ErrorTracking, this
# processor should be removed.
def process(payload)
return payload if ::Feature.enabled?(:sentry_processors_before_send, default_enabled: :yaml)
context_payload = Gitlab::ErrorTracking::ContextPayloadGenerator.generate(nil, {})
payload.deep_merge!(context_payload)
end
def self.call(event)
return event unless ::Feature.enabled?(:sentry_processors_before_send, default_enabled: :yaml)
Gitlab::ErrorTracking::ContextPayloadGenerator.generate(nil, {}).each do |key, value|
event.public_send(key).deep_merge!(value) # rubocop:disable GitlabSecurity/PublicSend
end

View File

@ -3,22 +3,11 @@
module Gitlab
module ErrorTracking
module Processor
class GrpcErrorProcessor < ::Raven::Processor
module GrpcErrorProcessor
DEBUG_ERROR_STRING_REGEX = RE2('(.*) debug_error_string:(.*)')
def process(payload)
return payload if ::Feature.enabled?(:sentry_processors_before_send, default_enabled: :yaml)
self.class.process_first_exception_value(payload)
self.class.process_custom_fingerprint(payload)
payload
end
class << self
def call(event)
return event unless ::Feature.enabled?(:sentry_processors_before_send, default_enabled: :yaml)
process_first_exception_value(event)
process_custom_fingerprint(event)
@ -27,8 +16,9 @@ module Gitlab
# Sentry can report multiple exceptions in an event. Sanitize
# only the first one since that's what is used for grouping.
def process_first_exception_value(event_or_payload)
exceptions = exceptions(event_or_payload)
def process_first_exception_value(event)
# Better in new version, will be event.exception.values
exceptions = event.instance_variable_get(:@interfaces)[:exception]&.values
return unless exceptions.is_a?(Array)
@ -36,18 +26,21 @@ module Gitlab
return unless valid_exception?(exception)
exception_type, raw_message = type_and_value(exception)
raw_message = exception.value
return unless exception_type&.start_with?('GRPC::')
return unless exception.type&.start_with?('GRPC::')
return unless raw_message.present?
message, debug_str = split_debug_error_string(raw_message)
set_new_values!(event_or_payload, exception, message, debug_str)
# Worse in new version, no setter! Have to poke at the
# instance variable
exception.value = message if message
event.extra[:grpc_debug_error_string] = debug_str if debug_str
end
def process_custom_fingerprint(event)
fingerprint = fingerprint(event)
fingerprint = event.fingerprint
return event unless custom_grpc_fingerprint?(fingerprint)
@ -71,61 +64,14 @@ module Gitlab
[match[1], match[2]]
end
# The below methods can be removed once we remove the
# sentry_processors_before_send feature flag, and we can
# assume we always have an Event object
def exceptions(event_or_payload)
case event_or_payload
when Raven::Event
# Better in new version, will be event_or_payload.exception.values
event_or_payload.instance_variable_get(:@interfaces)[:exception]&.values
when Hash
event_or_payload.dig(:exception, :values)
end
end
def valid_exception?(exception)
case exception
when Raven::SingleExceptionInterface
exception&.value
when Hash
true
else
false
end
end
def type_and_value(exception)
case exception
when Raven::SingleExceptionInterface
[exception.type, exception.value]
when Hash
exception.values_at(:type, :value)
end
end
def set_new_values!(event_or_payload, exception, message, debug_str)
case event_or_payload
when Raven::Event
# Worse in new version, no setter! Have to poke at the
# instance variable
exception.value = message if message
event_or_payload.extra[:grpc_debug_error_string] = debug_str if debug_str
when Hash
exception[:value] = message if message
extra = event_or_payload[:extra] || {}
extra[:grpc_debug_error_string] = debug_str if debug_str
end
end
def fingerprint(event_or_payload)
case event_or_payload
when Raven::Event
event_or_payload.fingerprint
when Hash
event_or_payload[:fingerprint]
end
end
end
end
end

View File

@ -5,7 +5,7 @@ require 'set'
module Gitlab
module ErrorTracking
module Processor
class SidekiqProcessor < ::Raven::Processor
module SidekiqProcessor
FILTERED_STRING = '[FILTERED]'
class << self
@ -42,8 +42,6 @@ module Gitlab
end
def call(event)
return event unless ::Feature.enabled?(:sentry_processors_before_send, default_enabled: :yaml)
sidekiq = event&.extra&.dig(:sidekiq)
return event unless sidekiq
@ -64,29 +62,6 @@ module Gitlab
event
end
end
def process(value, key = nil)
return value if ::Feature.enabled?(:sentry_processors_before_send, default_enabled: :yaml)
sidekiq = value.dig(:extra, :sidekiq)
return value unless sidekiq
sidekiq = sidekiq.deep_dup
sidekiq.delete(:jobstr)
# 'args' in this hash => from Gitlab::ErrorTracking.track_*
# 'args' in :job => from default error handler
job_holder = sidekiq.key?('args') ? sidekiq : sidekiq[:job]
if job_holder['args']
job_holder['args'] = self.class.filter_arguments(job_holder['args'], job_holder['class']).to_a
end
value[:extra][:sidekiq] = sidekiq
value
end
end
end
end

View File

@ -39,6 +39,10 @@ module Gitlab
@file_paths ||= suggestions.map(&:file_path).uniq
end
def authors
suggestions.map { |suggestion| suggestion.note.author }.uniq
end
private
def first_suggestion

View File

@ -248,5 +248,19 @@ namespace :gitlab do
ActiveRecord::Base.clear_cache!
ActiveRecord::Migration.verbose = verbose_was
end
desc 'Run all pending batched migrations'
task execute_batched_migrations: :environment do
Gitlab::Database::BackgroundMigration::BatchedMigration.active.queue_order.each do |migration|
Gitlab::AppLogger.info("Executing batched migration #{migration.id} inline")
Gitlab::Database::BackgroundMigration::BatchedMigrationRunner.new.run_entire_migration(migration)
end
end
# Only for development environments,
# we execute pending data migrations inline for convenience.
Rake::Task['db:migrate'].enhance do
Rake::Task['gitlab:db:execute_batched_migrations'].invoke if Rails.env.development?
end
end
end

View File

@ -8791,6 +8791,9 @@ msgstr ""
msgid "Contains %{count} blobs of images (%{size})"
msgstr ""
msgid "Content parsed with %{link}."
msgstr ""
msgid "ContentEditor|You have to provide a renderMarkdown function or a custom serializer"
msgstr ""
@ -13478,9 +13481,15 @@ msgstr ""
msgid "Faster releases. Better code. Less pain."
msgstr ""
msgid "Favicon"
msgstr ""
msgid "Favicon was successfully removed."
msgstr ""
msgid "Favicon will be removed. Are you sure?"
msgstr ""
msgid "Feature Flags"
msgstr ""
@ -15931,9 +15940,15 @@ msgstr ""
msgid "Hashed storage can't be disabled anymore for new projects"
msgstr ""
msgid "Header logo"
msgstr ""
msgid "Header logo was successfully removed."
msgstr ""
msgid "Header logo will be removed. Are you sure?"
msgstr ""
msgid "Header message"
msgstr ""
@ -16373,6 +16388,9 @@ msgstr ""
msgid "ImageViewerDimensions|W"
msgstr ""
msgid "Images with incorrect dimensions are not resized automatically, and may result in unexpected behavior."
msgstr ""
msgid "Impersonate"
msgstr ""
@ -19409,6 +19427,9 @@ msgstr ""
msgid "Logo was successfully removed."
msgstr ""
msgid "Logo will be removed. Are you sure?"
msgstr ""
msgid "Logs"
msgstr ""
@ -19784,6 +19805,15 @@ msgstr ""
msgid "Maximum file size indexed (KiB)"
msgstr ""
msgid "Maximum file size is 1MB. Image size must be 32x32px. Allowed image formats are %{favicon_extension_whitelist}."
msgstr ""
msgid "Maximum file size is 1MB. Pages are optimized for a 28px tall header logo"
msgstr ""
msgid "Maximum file size is 1MB. Pages are optimized for a 640x360 px logo."
msgstr ""
msgid "Maximum file size is 2MB. Please select a smaller file."
msgstr ""
@ -21116,6 +21146,9 @@ msgstr ""
msgid "Navigate to the project to close the milestone."
msgstr ""
msgid "Navigation bar"
msgstr ""
msgid "Nav|Help"
msgstr ""
@ -21538,6 +21571,12 @@ msgstr ""
msgid "New project"
msgstr ""
msgid "New project page"
msgstr ""
msgid "New project pages"
msgstr ""
msgid "New project/repository"
msgstr ""
@ -24347,6 +24386,9 @@ msgstr ""
msgid "Profile Settings"
msgstr ""
msgid "Profile image guideline"
msgstr ""
msgid "ProfileSession|on"
msgstr ""
@ -26574,6 +26616,9 @@ msgstr ""
msgid "Remove due date"
msgstr ""
msgid "Remove favicon"
msgstr ""
msgid "Remove file"
msgstr ""
@ -26589,6 +26634,9 @@ msgstr ""
msgid "Remove group"
msgstr ""
msgid "Remove header logo"
msgstr ""
msgid "Remove iteration"
msgstr ""
@ -26604,6 +26652,9 @@ msgstr ""
msgid "Remove log"
msgstr ""
msgid "Remove logo"
msgstr ""
msgid "Remove member"
msgstr ""
@ -28169,6 +28220,9 @@ msgstr ""
msgid "SecurityConfiguration|Configure via Merge Request"
msgstr ""
msgid "SecurityConfiguration|Configure via merge request"
msgstr ""
msgid "SecurityConfiguration|Could not retrieve configuration data. Please refresh the page, or try again later."
msgstr ""
@ -28205,6 +28259,9 @@ msgstr ""
msgid "SecurityConfiguration|SAST Configuration"
msgstr ""
msgid "SecurityConfiguration|SAST merge request creation mutation failed"
msgstr ""
msgid "SecurityConfiguration|Security Control"
msgstr ""
@ -29388,6 +29445,9 @@ msgstr ""
msgid "Sign in with smart card"
msgstr ""
msgid "Sign in/Sign up pages"
msgstr ""
msgid "Sign out"
msgstr ""
@ -29400,6 +29460,9 @@ msgstr ""
msgid "Sign up was successful! Please confirm your email to sign in."
msgstr ""
msgid "Sign-in page"
msgstr ""
msgid "Sign-in restrictions"
msgstr ""
@ -32730,6 +32793,9 @@ msgstr ""
msgid "ThreatMonitoring|In review"
msgstr ""
msgid "ThreatMonitoring|Incident"
msgstr ""
msgid "ThreatMonitoring|Name"
msgstr ""
@ -34011,6 +34077,9 @@ msgstr ""
msgid "Update all"
msgstr ""
msgid "Update appearance settings"
msgstr ""
msgid "Update approval rule"
msgstr ""

View File

@ -1,5 +1,6 @@
import { getByTestId, fireEvent } from '@testing-library/dom';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import DiffRow from '~/diffs/components/diff_row.vue';
import { mapParallel } from '~/diffs/components/diff_row_utils';
@ -28,12 +29,12 @@ describe('DiffRow', () => {
},
];
const createWrapper = ({ props, state, isLoggedIn = true }) => {
const localVue = createLocalVue();
localVue.use(Vuex);
const createWrapper = ({ props, state, actions, isLoggedIn = true }) => {
Vue.use(Vuex);
const diffs = diffsModule();
diffs.state = { ...diffs.state, ...state };
diffs.actions = { ...diffs.actions, ...actions };
const getters = { isLoggedIn: () => isLoggedIn };
@ -54,7 +55,7 @@ describe('DiffRow', () => {
glFeatures: { dragCommentSelection: true },
};
return shallowMount(DiffRow, { propsData, localVue, store, provide });
return shallowMount(DiffRow, { propsData, store, provide });
};
it('isHighlighted returns true given line.left', () => {
@ -95,6 +96,9 @@ describe('DiffRow', () => {
expect(wrapper.vm.isHighlighted).toBe(false);
});
const getCommentButton = (wrapper, side) =>
wrapper.find(`[data-testid="${side}-comment-button"]`);
describe.each`
side
${'left'}
@ -102,18 +106,59 @@ describe('DiffRow', () => {
`('$side side', ({ side }) => {
it(`renders empty cells if ${side} is unavailable`, () => {
const wrapper = createWrapper({ props: { line: testLines[2], inline: false } });
expect(wrapper.find(`[data-testid="${side}LineNumber"]`).exists()).toBe(false);
expect(wrapper.find(`[data-testid="${side}EmptyCell"]`).exists()).toBe(true);
expect(wrapper.find(`[data-testid="${side}-line-number"]`).exists()).toBe(false);
expect(wrapper.find(`[data-testid="${side}-empty-cell"]`).exists()).toBe(true);
});
it('renders comment button', () => {
const wrapper = createWrapper({ props: { line: testLines[3], inline: false } });
expect(wrapper.find(`[data-testid="${side}CommentButton"]`).exists()).toBe(true);
describe('comment button', () => {
const showCommentForm = jest.fn();
let line;
beforeEach(() => {
showCommentForm.mockReset();
// https://eslint.org/docs/rules/prefer-destructuring#when-not-to-use-it
// eslint-disable-next-line prefer-destructuring
line = testLines[3];
});
it('renders', () => {
const wrapper = createWrapper({ props: { line, inline: false } });
expect(getCommentButton(wrapper, side).exists()).toBe(true);
});
it('responds to click and keyboard events', async () => {
const wrapper = createWrapper({
props: { line, inline: false },
actions: { showCommentForm },
});
const commentButton = getCommentButton(wrapper, side);
await commentButton.trigger('click');
await commentButton.trigger('keydown.enter');
await commentButton.trigger('keydown.space');
expect(showCommentForm).toHaveBeenCalledTimes(3);
});
it('ignores click and keyboard events when comments are disabled', async () => {
line[side].commentsDisabled = true;
const wrapper = createWrapper({
props: { line, inline: false },
actions: { showCommentForm },
});
const commentButton = getCommentButton(wrapper, side);
await commentButton.trigger('click');
await commentButton.trigger('keydown.enter');
await commentButton.trigger('keydown.space');
expect(showCommentForm).not.toHaveBeenCalled();
});
});
it('renders avatars', () => {
const wrapper = createWrapper({ props: { line: testLines[0], inline: false } });
expect(wrapper.find(`[data-testid="${side}Discussions"]`).exists()).toBe(true);
expect(wrapper.find(`[data-testid="${side}-discussions"]`).exists()).toBe(true);
});
});

View File

@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ConfigurationTable from '~/security_configuration/components/configuration_table.vue';
import { scanners, UPGRADE_CTA } from '~/security_configuration/components/constants';
import { scanners, UPGRADE_CTA } from '~/security_configuration/components/scanners_constants';
import {
REPORT_TYPE_SAST,
@ -12,13 +12,7 @@ describe('Configuration Table Component', () => {
let wrapper;
const createComponent = () => {
wrapper = extendedWrapper(
mount(ConfigurationTable, {
provide: {
projectPath: 'testProjectPath',
},
}),
);
wrapper = extendedWrapper(mount(ConfigurationTable, {}));
};
const findHelpLinks = () => wrapper.findAll('[data-testid="help-link"]');
@ -36,10 +30,8 @@ describe('Configuration Table Component', () => {
expect(wrapper.text()).toContain(scanner.name);
expect(wrapper.text()).toContain(scanner.description);
if (scanner.type === REPORT_TYPE_SAST) {
expect(wrapper.findByTestId(scanner.type).text()).toBe('Configure via Merge Request');
} else if (scanner.type === REPORT_TYPE_SECRET_DETECTION) {
expect(wrapper.findByTestId(scanner.type).exists()).toBe(false);
} else {
expect(wrapper.findByTestId(scanner.type).text()).toBe('Configure via merge request');
} else if (scanner.type !== REPORT_TYPE_SECRET_DETECTION) {
expect(wrapper.findByTestId(scanner.type).text()).toMatchInterpolatedText(UPGRADE_CTA);
}
});

View File

@ -0,0 +1,136 @@
import { GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { redirectTo } from '~/lib/utils/url_utility';
import ManageSast from '~/security_configuration/components/manage_sast.vue';
import configureSastMutation from '~/security_configuration/graphql/configure_sast.mutation.graphql';
jest.mock('~/lib/utils/url_utility', () => ({
redirectTo: jest.fn(),
}));
Vue.use(VueApollo);
describe('Manage Sast Component', () => {
let wrapper;
const findButton = () => wrapper.findComponent(GlButton);
const successHandler = async () => {
return {
data: {
configureSast: {
successPath: 'testSuccessPath',
errors: [],
__typename: 'ConfigureSastPayload',
},
},
};
};
const noSuccessPathHandler = async () => {
return {
data: {
configureSast: {
successPath: '',
errors: [],
__typename: 'ConfigureSastPayload',
},
},
};
};
const errorHandler = async () => {
return {
data: {
configureSast: {
successPath: 'testSuccessPath',
errors: ['foo'],
__typename: 'ConfigureSastPayload',
},
},
};
};
const pendingHandler = () => new Promise(() => {});
function createMockApolloProvider(handler) {
const requestHandlers = [[configureSastMutation, handler]];
return createMockApollo(requestHandlers);
}
function createComponent(options = {}) {
const { mockApollo } = options;
wrapper = extendedWrapper(
mount(ManageSast, {
apolloProvider: mockApollo,
}),
);
}
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('should render Button with correct text', () => {
createComponent();
expect(findButton().text()).toContain('Configure via merge request');
});
describe('given a successful response', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(successHandler);
createComponent({ mockApollo });
});
it('should call redirect helper with correct value', async () => {
await wrapper.trigger('click');
await waitForPromises();
expect(redirectTo).toHaveBeenCalledTimes(1);
expect(redirectTo).toHaveBeenCalledWith('testSuccessPath');
// This is done for UX reasons. If the loading prop is set to false
// on success, then there's a period where the button is clickable
// again. Instead, we want the button to display a loading indicator
// for the remainder of the lifetime of the page (i.e., until the
// browser can start painting the new page it's been redirected to).
expect(findButton().props().loading).toBe(true);
});
});
describe('given a pending response', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(pendingHandler);
createComponent({ mockApollo });
});
it('renders spinner correctly', async () => {
expect(findButton().props('loading')).toBe(false);
await wrapper.trigger('click');
await waitForPromises();
expect(findButton().props('loading')).toBe(true);
});
});
describe.each`
handler | message
${noSuccessPathHandler} | ${'SAST merge request creation mutation failed'}
${errorHandler} | ${'foo'}
`('given an error response', ({ handler, message }) => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(handler);
createComponent({ mockApollo });
});
it('should catch and emit error', async () => {
await wrapper.trigger('click');
await waitForPromises();
expect(wrapper.emitted('error')).toEqual([[message]]);
expect(findButton().props('loading')).toBe(false);
});
});
});

View File

@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
import { UPGRADE_CTA } from '~/security_configuration/components/constants';
import { UPGRADE_CTA } from '~/security_configuration/components/scanners_constants';
import Upgrade from '~/security_configuration/components/upgrade.vue';
const TEST_URL = 'http://www.example.test';

View File

@ -1,12 +0,0 @@
export const buildConfigureSecurityFeatureMockFactory = (mutationType) => ({
successPath = 'testSuccessPath',
errors = [],
} = {}) => ({
data: {
[mutationType]: {
successPath,
errors,
__typename: `${mutationType}Payload`,
},
},
});

View File

@ -1,145 +0,0 @@
import { GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import configureDependencyScanningMutation from 'ee/security_configuration/graphql/configure_dependency_scanning.mutation.graphql';
import configureSecretDetectionMutation from 'ee/security_configuration/graphql/configure_secret_detection.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { redirectTo } from '~/lib/utils/url_utility';
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
import {
REPORT_TYPE_DEPENDENCY_SCANNING,
REPORT_TYPE_SECRET_DETECTION,
} from '~/vue_shared/security_reports/constants';
import { buildConfigureSecurityFeatureMockFactory } from './apollo_mocks';
jest.mock('~/lib/utils/url_utility');
Vue.use(VueApollo);
describe('ManageViaMr component', () => {
let wrapper;
const findButton = () => wrapper.findComponent(GlButton);
describe.each`
featureName | featureType | mutation | mutationId
${'Dependency Scanning'} | ${REPORT_TYPE_DEPENDENCY_SCANNING} | ${configureDependencyScanningMutation} | ${'configureDependencyScanning'}
${'Secret Detection'} | ${REPORT_TYPE_SECRET_DETECTION} | ${configureSecretDetectionMutation} | ${'configureSecretDetection'}
`('$featureType', ({ featureName, mutation, featureType, mutationId }) => {
const buildConfigureSecurityFeatureMock = buildConfigureSecurityFeatureMockFactory(mutationId);
const successHandler = async () => buildConfigureSecurityFeatureMock();
const noSuccessPathHandler = async () =>
buildConfigureSecurityFeatureMock({
successPath: '',
});
const errorHandler = async () =>
buildConfigureSecurityFeatureMock({
errors: ['foo'],
});
const pendingHandler = () => new Promise(() => {});
function createMockApolloProvider(handler) {
const requestHandlers = [[mutation, handler]];
return createMockApollo(requestHandlers);
}
function createComponent({ mockApollo, isFeatureConfigured = false } = {}) {
wrapper = extendedWrapper(
mount(ManageViaMr, {
apolloProvider: mockApollo,
provide: {
projectPath: 'testProjectPath',
},
propsData: {
feature: {
name: featureName,
type: featureType,
configured: isFeatureConfigured,
},
},
}),
);
}
afterEach(() => {
wrapper.destroy();
});
describe('when feature is configured', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(successHandler);
createComponent({ mockApollo, isFeatureConfigured: true });
});
it('it does not render a button', () => {
expect(findButton().exists()).toBe(false);
});
});
describe('when feature is not configured', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(successHandler);
createComponent({ mockApollo, isFeatureConfigured: false });
});
it('it does render a button', () => {
expect(findButton().exists()).toBe(true);
});
});
describe('given a pending response', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(pendingHandler);
createComponent({ mockApollo });
});
it('renders spinner correctly', async () => {
const button = findButton();
expect(button.props('loading')).toBe(false);
await button.trigger('click');
expect(button.props('loading')).toBe(true);
});
});
describe('given a successful response', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(successHandler);
createComponent({ mockApollo });
});
it('should call redirect helper with correct value', async () => {
await wrapper.trigger('click');
await waitForPromises();
expect(redirectTo).toHaveBeenCalledTimes(1);
expect(redirectTo).toHaveBeenCalledWith('testSuccessPath');
// This is done for UX reasons. If the loading prop is set to false
// on success, then there's a period where the button is clickable
// again. Instead, we want the button to display a loading indicator
// for the remainder of the lifetime of the page (i.e., until the
// browser can start painting the new page it's been redirected to).
expect(findButton().props().loading).toBe(true);
});
});
describe.each`
handler | message
${noSuccessPathHandler} | ${`${featureName} merge request creation mutation failed`}
${errorHandler} | ${'foo'}
`('given an error response', ({ handler, message }) => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(handler);
createComponent({ mockApollo });
});
it('should catch and emit error', async () => {
await wrapper.trigger('click');
await waitForPromises();
expect(wrapper.emitted('error')).toEqual([[message]]);
expect(findButton().props('loading')).toBe(false);
});
});
});
});

View File

@ -1902,22 +1902,6 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
)
end
end
context 'when the migration should be performed inline' do
let(:columns) { column }
it 'calls the runner to run the entire migration' do
expect(model).to receive(:perform_background_migration_inline?).and_return(true)
expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |scheduler|
expect(scheduler).to receive(:run_entire_migration) do |batched_migration|
expect(batched_migration).to eq(migration_relation.last)
end
end
model.backfill_conversion_of_integer_to_bigint(table, column, batch_size: 2, sub_batch_size: 1)
end
end
end
end

View File

@ -3,7 +3,10 @@
require 'spec_helper'
RSpec.describe Gitlab::ErrorTracking::Processor::ContextPayloadProcessor do
shared_examples 'processing an exception' do
describe '.call' do
let(:event) { Raven::Event.new(payload) }
let(:result_hash) { described_class.call(event).to_hash }
before do
allow_next_instance_of(Gitlab::ErrorTracking::ContextPayloadGenerator) do |generator|
allow(generator).to receive(:generate).and_return(
@ -37,30 +40,4 @@ RSpec.describe Gitlab::ErrorTracking::Processor::ContextPayloadProcessor do
sidekiq: { class: 'SomeWorker', args: ['[FILTERED]', 1, 2] })
end
end
describe '.call' do
let(:event) { Raven::Event.new(payload) }
let(:result_hash) { described_class.call(event).to_hash }
it_behaves_like 'processing an exception'
context 'when followed by #process' do
let(:result_hash) { described_class.new.process(described_class.call(event).to_hash) }
it_behaves_like 'processing an exception'
end
end
describe '#process' do
let(:event) { Raven::Event.new(payload) }
let(:result_hash) { described_class.new.process(event.to_hash) }
context 'with sentry_processors_before_send disabled' do
before do
stub_feature_flags(sentry_processors_before_send: false)
end
it_behaves_like 'processing an exception'
end
end
end

View File

@ -3,7 +3,10 @@
require 'spec_helper'
RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
shared_examples 'processing an exception' do
describe '.call' do
let(:event) { Raven::Event.from_exception(exception, data) }
let(:result_hash) { described_class.call(event).to_hash }
context 'when there is no GRPC exception' do
let(:exception) { RuntimeError.new }
let(:data) { { fingerprint: ['ArgumentError', 'Missing arguments'] } }
@ -56,30 +59,4 @@ RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
end
end
end
describe '.call' do
let(:event) { Raven::Event.from_exception(exception, data) }
let(:result_hash) { described_class.call(event).to_hash }
it_behaves_like 'processing an exception'
context 'when followed by #process' do
let(:result_hash) { described_class.new.process(described_class.call(event).to_hash) }
it_behaves_like 'processing an exception'
end
end
describe '#process' do
let(:event) { Raven::Event.from_exception(exception, data) }
let(:result_hash) { described_class.new.process(event.to_hash) }
context 'with sentry_processors_before_send disabled' do
before do
stub_feature_flags(sentry_processors_before_send: false)
end
it_behaves_like 'processing an exception'
end
end
end

View File

@ -94,7 +94,10 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
end
end
shared_examples 'processing an exception' do
describe '.call' do
let(:event) { Raven::Event.new(wrapped_value) }
let(:result_hash) { described_class.call(event).to_hash }
context 'when there is Sidekiq data' do
let(:wrapped_value) { { extra: { sidekiq: value } } }
@ -168,30 +171,4 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
end
end
end
describe '.call' do
let(:event) { Raven::Event.new(wrapped_value) }
let(:result_hash) { described_class.call(event).to_hash }
it_behaves_like 'processing an exception'
context 'when followed by #process' do
let(:result_hash) { described_class.new.process(described_class.call(event).to_hash) }
it_behaves_like 'processing an exception'
end
end
describe '#process' do
let(:event) { Raven::Event.new(wrapped_value) }
let(:result_hash) { described_class.new.process(event.to_hash) }
context 'with sentry_processors_before_send disabled' do
before do
stub_feature_flags(sentry_processors_before_send: false)
end
it_behaves_like 'processing an exception'
end
end
end

View File

@ -217,7 +217,7 @@ RSpec.describe Gitlab::ErrorTracking do
end
end
shared_examples 'event processors' do
context 'event processors' do
subject(:track_exception) { described_class.track_exception(exception, extra) }
before do
@ -312,20 +312,4 @@ RSpec.describe Gitlab::ErrorTracking do
end
end
end
context 'with sentry_processors_before_send enabled' do
before do
stub_feature_flags(sentry_processors_before_send: true)
end
include_examples 'event processors'
end
context 'with sentry_processors_before_send disabled' do
before do
stub_feature_flags(sentry_processors_before_send: false)
end
include_examples 'event processors'
end
end

View File

@ -202,6 +202,20 @@ RSpec.describe NamespaceSetting, 'CascadingNamespaceSettingAttribute' do
it_behaves_like 'not locked'
end
context 'when attribute is locked by self' do
before do
subgroup_settings.update!(lock_delayed_project_removal: true)
end
it 'is not locked by default' do
expect(subgroup_settings.delayed_project_removal_locked?).to eq(false)
end
it 'is locked when including self' do
expect(subgroup_settings.delayed_project_removal_locked?(include_self: true)).to eq(true)
end
end
context 'when parent does not lock the attribute' do
it_behaves_like 'not locked'
end

View File

@ -67,11 +67,13 @@ RSpec.describe Suggestions::ApplyService do
apply(suggestions)
commit = project.repository.commit
author = suggestions.first.note.author
expect(user.commit_email).not_to eq(user.email)
expect(commit.author_email).to eq(user.commit_email)
expect(commit.author_email).to eq(author.commit_email)
expect(commit.committer_email).to eq(user.commit_email)
expect(commit.author_name).to eq(user.name)
expect(commit.author_name).to eq(author.name)
expect(commit.committer_name).to eq(user.name)
end
it 'tracks apply suggestion event' do
@ -319,6 +321,73 @@ RSpec.describe Suggestions::ApplyService do
end
end
context 'single suggestion' do
let(:author) { suggestions.first.note.author }
let(:commit) { project.repository.commit }
context 'author of suggestion applies suggestion' do
before do
suggestion.note.update!(author_id: user.id)
apply(suggestions)
end
it 'created commit by same author and committer' do
expect(user.commit_email).to eq(author.commit_email)
expect(author).to eq(user)
expect(commit.author_email).to eq(author.commit_email)
expect(commit.committer_email).to eq(user.commit_email)
expect(commit.author_name).to eq(author.name)
expect(commit.committer_name).to eq(user.name)
end
end
context 'another user applies suggestion' do
before do
apply(suggestions)
end
it 'created commit has authors info and commiters info' do
expect(user.commit_email).not_to eq(user.email)
expect(author).not_to eq(user)
expect(commit.author_email).to eq(author.commit_email)
expect(commit.committer_email).to eq(user.commit_email)
expect(commit.author_name).to eq(author.name)
expect(commit.committer_name).to eq(user.name)
end
end
end
context 'multiple suggestions' do
let(:author_emails) { suggestions.map {|s| s.note.author.commit_email } }
let(:first_author) { suggestion.note.author }
let(:commit) { project.repository.commit }
context 'when all the same author' do
before do
apply(suggestions)
end
it 'uses first authors information' do
expect(author_emails).to include(first_author.commit_email).exactly(3)
expect(commit.author_email).to eq(first_author.commit_email)
end
end
context 'when all different authors' do
before do
suggestion2.note.update!(author_id: create(:user).id)
suggestion3.note.update!(author_id: create(:user).id)
apply(suggestions)
end
it 'uses committers information' do
expect(commit.author_email).to eq(user.commit_email)
expect(commit.committer_email).to eq(user.commit_email)
end
end
end
context 'multiple suggestions applied sequentially' do
def apply_suggestion(suggestion)
suggestion.reload

View File

@ -348,6 +348,26 @@ RSpec.describe 'gitlab:db namespace rake task' do
end
end
describe '#execute_batched_migrations' do
subject { run_rake_task('gitlab:db:execute_batched_migrations') }
let(:migrations) { create_list(:batched_background_migration, 2) }
let(:runner) { instance_double('Gitlab::Database::BackgroundMigration::BatchedMigrationRunner') }
before do
allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive_message_chain(:active, :queue_order).and_return(migrations)
allow(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner).to receive(:new).and_return(runner)
end
it 'executes all migrations' do
migrations.each do |migration|
expect(runner).to receive(:run_entire_migration).with(migration)
end
subject
end
end
def run_rake_task(task_name, arguments = '')
Rake::Task[task_name].reenable
Rake.application.invoke_task("#{task_name}#{arguments}")