Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
7e1e45d40a
commit
bc62085601
|
|
@ -1 +1 @@
|
|||
fee093e7c786e4b1132cb87ab13175aef07265dc
|
||||
b19cafa222fd7a999167d3f9f8562c2d74b62bfd
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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: [] },
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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(),
|
||||
});
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add VulnerabiltyFindingEvidenceResponse model
|
||||
merge_request: 59563
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix multiline comment dragging in Firefox
|
||||
merge_request: 58692
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Externalize strings in appearances/_form.html.haml
|
||||
merge_request: 58135
|
||||
author: nuwe1
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add ability to include self in cascading setting lock check
|
||||
merge_request: 60031
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Improve dropdown and search in the tags page
|
||||
merge_request: 60145
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Commit author for suggestions is note author
|
||||
merge_request: 39940
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
@ -16,4 +16,3 @@ tier:
|
|||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
skip_validation: true
|
||||
|
|
|
|||
|
|
@ -16,4 +16,3 @@ tier:
|
|||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
skip_validation: true
|
||||
|
|
|
|||
|
|
@ -16,4 +16,3 @@ tier:
|
|||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
skip_validation: true
|
||||
|
|
|
|||
|
|
@ -16,4 +16,3 @@ tier:
|
|||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
skip_validation: true
|
||||
|
|
|
|||
|
|
@ -16,4 +16,3 @@ tier:
|
|||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
skip_validation: true
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
26f3978600808eae8396e0d5292bae95feca52ff3e44a019c04bd9708f27cc84
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)_ |
|
||||
|
|
|
|||
|
|
@ -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.
|
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
|
|
@ -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**.
|
||||
|
||||

|
||||

|
||||
|
||||
Clicking an alert's name takes the user to the [alert details page](../../../operations/incident_management/alerts.md#alert-details-page).
|
||||
|
||||
|
|
|
|||
|
|
@ -515,6 +515,9 @@ to your branch to address your reviewers' requests.
|
|||
|
||||

|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ module Gitlab
|
|||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
override :column_list
|
||||
def column_list
|
||||
[timestamp_projection]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
export const buildConfigureSecurityFeatureMockFactory = (mutationType) => ({
|
||||
successPath = 'testSuccessPath',
|
||||
errors = [],
|
||||
} = {}) => ({
|
||||
data: {
|
||||
[mutationType]: {
|
||||
successPath,
|
||||
errors,
|
||||
__typename: `${mutationType}Payload`,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
Loading…
Reference in New Issue