Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
46fd9b1dd8
commit
374f3dee7d
|
|
@ -1775,11 +1775,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'ee/spec/services/ci_cd/github_integration_setup_service_spec.rb'
|
||||
- 'ee/spec/services/ci_cd/github_setup_service_spec.rb'
|
||||
- 'ee/spec/services/ci_cd/setup_project_spec.rb'
|
||||
- 'ee/spec/services/compliance_management/frameworks/create_service_spec.rb'
|
||||
- 'ee/spec/services/compliance_management/frameworks/destroy_service_spec.rb'
|
||||
- 'ee/spec/services/compliance_management/frameworks/update_service_spec.rb'
|
||||
- 'ee/spec/services/compliance_management/merge_requests/create_compliance_violations_service_spec.rb'
|
||||
- 'ee/spec/services/concerns/epics/related_epic_links/usage_data_helper_spec.rb'
|
||||
- 'ee/spec/services/dashboard/environments/list_service_spec.rb'
|
||||
- 'ee/spec/services/dashboard/operations/list_service_spec.rb'
|
||||
- 'ee/spec/services/dashboard/projects/create_service_spec.rb'
|
||||
|
|
@ -1793,7 +1788,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'ee/spec/services/ee/ci/job_artifacts/destroy_all_expired_service_spec.rb'
|
||||
- 'ee/spec/services/ee/ci/job_artifacts/destroy_batch_service_spec.rb'
|
||||
- 'ee/spec/services/ee/ci/pipeline_processing/atomic_processing_service_spec.rb'
|
||||
- 'ee/spec/services/ee/commits/create_service_spec.rb'
|
||||
- 'ee/spec/services/ee/deployments/update_environment_service_spec.rb'
|
||||
- 'ee/spec/services/ee/design_management/delete_designs_service_spec.rb'
|
||||
- 'ee/spec/services/ee/design_management/save_designs_service_spec.rb'
|
||||
|
|
@ -3775,7 +3769,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'spec/lib/feature/gitaly_spec.rb'
|
||||
- 'spec/lib/file_size_validator_spec.rb'
|
||||
- 'spec/lib/forever_spec.rb'
|
||||
- 'spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb'
|
||||
- 'spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb'
|
||||
- 'spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb'
|
||||
- 'spec/lib/generators/gitlab/usage_metric_generator_spec.rb'
|
||||
|
|
@ -6153,7 +6146,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'spec/models/protectable_dropdown_spec.rb'
|
||||
- 'spec/models/protected_branch/merge_access_level_spec.rb'
|
||||
- 'spec/models/protected_branch/push_access_level_spec.rb'
|
||||
- 'spec/models/protected_branch_spec.rb'
|
||||
- 'spec/models/protected_tag_spec.rb'
|
||||
- 'spec/models/push_event_payload_spec.rb'
|
||||
- 'spec/models/push_event_spec.rb'
|
||||
|
|
@ -6827,34 +6819,8 @@ RSpec/MissingFeatureCategory:
|
|||
- 'spec/services/ci/update_build_queue_service_spec.rb'
|
||||
- 'spec/services/ci/update_instance_variables_service_spec.rb'
|
||||
- 'spec/services/ci/update_pending_build_service_spec.rb'
|
||||
- 'spec/services/clusters/agent_tokens/track_usage_service_spec.rb'
|
||||
- 'spec/services/clusters/agents/create_activity_event_service_spec.rb'
|
||||
- 'spec/services/clusters/agents/create_service_spec.rb'
|
||||
- 'spec/services/clusters/agents/delete_expired_events_service_spec.rb'
|
||||
- 'spec/services/clusters/agents/delete_service_spec.rb'
|
||||
- 'spec/services/clusters/build_kubernetes_namespace_service_spec.rb'
|
||||
- 'spec/services/clusters/build_service_spec.rb'
|
||||
- 'spec/services/clusters/cleanup/project_namespace_service_spec.rb'
|
||||
- 'spec/services/clusters/cleanup/service_account_service_spec.rb'
|
||||
- 'spec/services/clusters/create_service_spec.rb'
|
||||
- 'spec/services/clusters/destroy_service_spec.rb'
|
||||
- 'spec/services/clusters/integrations/create_service_spec.rb'
|
||||
- 'spec/services/clusters/integrations/prometheus_health_check_service_spec.rb'
|
||||
- 'spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb'
|
||||
- 'spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb'
|
||||
- 'spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb'
|
||||
- 'spec/services/clusters/kubernetes_spec.rb'
|
||||
- 'spec/services/clusters/management/validate_management_project_permissions_service_spec.rb'
|
||||
- 'spec/services/clusters/update_service_spec.rb'
|
||||
- 'spec/services/cohorts_service_spec.rb'
|
||||
- 'spec/services/commits/cherry_pick_service_spec.rb'
|
||||
- 'spec/services/commits/commit_patch_service_spec.rb'
|
||||
- 'spec/services/commits/tag_service_spec.rb'
|
||||
- 'spec/services/compare_service_spec.rb'
|
||||
- 'spec/services/concerns/audit_event_save_type_spec.rb'
|
||||
- 'spec/services/concerns/exclusive_lease_guard_spec.rb'
|
||||
- 'spec/services/concerns/merge_requests/assigns_merge_params_spec.rb'
|
||||
- 'spec/services/concerns/rate_limited_service_spec.rb'
|
||||
- 'spec/services/container_expiration_policies/cleanup_service_spec.rb'
|
||||
- 'spec/services/container_expiration_policies/update_service_spec.rb'
|
||||
- 'spec/services/customer_relations/contacts/create_service_spec.rb'
|
||||
|
|
@ -7238,7 +7204,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb'
|
||||
- 'spec/services/projects/lfs_pointers/lfs_download_service_spec.rb'
|
||||
- 'spec/services/projects/lfs_pointers/lfs_import_service_spec.rb'
|
||||
- 'spec/services/projects/lfs_pointers/lfs_link_service_spec.rb'
|
||||
- 'spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb'
|
||||
- 'spec/services/projects/move_access_service_spec.rb'
|
||||
- 'spec/services/projects/move_deploy_keys_projects_service_spec.rb'
|
||||
|
|
@ -7871,7 +7836,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'spec/workers/gitlab_service_ping_worker_spec.rb'
|
||||
- 'spec/workers/gitlab_shell_worker_spec.rb'
|
||||
- 'spec/workers/google_cloud/create_cloudsql_instance_worker_spec.rb'
|
||||
- 'spec/workers/group_destroy_worker_spec.rb'
|
||||
- 'spec/workers/group_export_worker_spec.rb'
|
||||
- 'spec/workers/group_import_worker_spec.rb'
|
||||
- 'spec/workers/groups/update_statistics_worker_spec.rb'
|
||||
|
|
@ -7968,7 +7932,6 @@ RSpec/MissingFeatureCategory:
|
|||
- 'spec/workers/post_receive_spec.rb'
|
||||
- 'spec/workers/process_commit_worker_spec.rb'
|
||||
- 'spec/workers/project_cache_worker_spec.rb'
|
||||
- 'spec/workers/project_destroy_worker_spec.rb'
|
||||
- 'spec/workers/project_export_worker_spec.rb'
|
||||
- 'spec/workers/projects/after_import_worker_spec.rb'
|
||||
- 'spec/workers/projects/finalize_project_statistics_refresh_worker_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
212f0e3545668faeb671dac213eaf25be5840bc0
|
||||
c7695bd902060be80b3499ffd2bd9e86d89c4f4f
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ export default {
|
|||
<a
|
||||
ref="authorUsernameLink"
|
||||
class="author-username-link"
|
||||
:href="author.path"
|
||||
:href="authorHref"
|
||||
@mouseenter="handleUsernameMouseEnter"
|
||||
@mouseleave="handleUsernameMouseLeave"
|
||||
><span class="note-headline-light">@{{ author.username }}</span>
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export default {
|
|||
v-for="author in uniqueAuthors"
|
||||
:key="author.username"
|
||||
class="gl-mr-3 reply-author-avatar"
|
||||
:link-href="author.path"
|
||||
:link-href="author.path || author.webUrl"
|
||||
:img-alt="author.name"
|
||||
img-css-classes="gl-mr-0!"
|
||||
:img-src="author.avatar_url || author.avatarUrl"
|
||||
|
|
@ -95,7 +95,7 @@ export default {
|
|||
<gl-sprintf :message="$options.i18n.lastReplyBy">
|
||||
<template #name>
|
||||
<gl-link
|
||||
:href="lastReply.author.path"
|
||||
:href="lastReply.author.path || lastReply.author.webUrl"
|
||||
class="gl-text-body! gl-text-decoration-none! gl-mx-2"
|
||||
>
|
||||
{{ lastReply.author.name }}
|
||||
|
|
|
|||
|
|
@ -114,10 +114,18 @@ export default {
|
|||
this.workItemType
|
||||
}`;
|
||||
},
|
||||
isLockedOutOrSignedOut() {
|
||||
return !this.signedIn || !this.canUpdate;
|
||||
},
|
||||
lockedOutUserWarningInReplies() {
|
||||
return this.addPadding && this.isLockedOutOrSignedOut;
|
||||
},
|
||||
timelineEntryClass() {
|
||||
return {
|
||||
'timeline-entry gl-mb-3': true,
|
||||
'gl-p-4': this.addPadding,
|
||||
'gl-p-2 gl-pl-3 gl-mt-5': this.addPadding,
|
||||
'gl-bg-gray-10 gl-rounded-bottom-left-base gl-rounded-bottom-right-base gl-p-5! gl-mx-n3 gl-mb-n2!': this
|
||||
.lockedOutUserWarningInReplies,
|
||||
};
|
||||
},
|
||||
isProjectArchived() {
|
||||
|
|
@ -191,7 +199,7 @@ export default {
|
|||
<work-item-comment-form
|
||||
v-if="isEditing"
|
||||
:work-item-type="workItemType"
|
||||
:aria-label="__('Add a comment')"
|
||||
:aria-label="__('Add a reply')"
|
||||
:is-submitting="isSubmitting"
|
||||
:autosave-key="autosaveKey"
|
||||
@submitForm="updateWorkItem"
|
||||
|
|
@ -201,7 +209,7 @@ export default {
|
|||
v-else
|
||||
class="gl-flex-grow-1 gl-justify-content-start! gl-text-secondary!"
|
||||
@click="isEditing = true"
|
||||
>{{ __('Add a comment') }}</gl-button
|
||||
>{{ __('Add a reply') }}</gl-button
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="disabled-comments gl-mt-3">
|
||||
<div class="disabled-comment gl-text-center gl-relative gl-mt-3">
|
||||
<span
|
||||
class="issuable-note-warning gl-display-inline-block gl-w-full gl-px-5 gl-py-4 gl-rounded-base"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -53,10 +53,11 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
isExpanded: false,
|
||||
isExpanded: true,
|
||||
autofocus: false,
|
||||
isReplying: false,
|
||||
replyingText: '',
|
||||
showForm: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -70,7 +71,7 @@ export default {
|
|||
return `note_${this.note.id}`;
|
||||
},
|
||||
hasReplies() {
|
||||
return this.replies?.length;
|
||||
return Boolean(this.replies?.length);
|
||||
},
|
||||
replies() {
|
||||
if (this.discussion?.length > 1) {
|
||||
|
|
@ -81,9 +82,13 @@ export default {
|
|||
discussionId() {
|
||||
return this.discussion[0]?.discussion?.id || '';
|
||||
},
|
||||
shouldShowReplyForm() {
|
||||
return this.showForm || this.hasReplies;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showReplyForm() {
|
||||
this.showForm = true;
|
||||
this.isExpanded = true;
|
||||
this.autofocus = true;
|
||||
},
|
||||
|
|
@ -93,7 +98,6 @@ export default {
|
|||
},
|
||||
toggleDiscussion() {
|
||||
this.isExpanded = !this.isExpanded;
|
||||
this.autofocus = this.isExpanded;
|
||||
},
|
||||
threadKey(note) {
|
||||
/* eslint-disable @gitlab/require-i18n-strings */
|
||||
|
|
@ -139,8 +143,9 @@ export default {
|
|||
:is-first-note="true"
|
||||
:note="note"
|
||||
:discussion-id="discussionId"
|
||||
:has-replies="hasReplies"
|
||||
:work-item-type="workItemType"
|
||||
:class="{ 'gl-mb-5': hasReplies }"
|
||||
:class="{ 'gl-mb-4': hasReplies }"
|
||||
@startReplying="showReplyForm"
|
||||
@deleteNote="$emit('deleteNote', note)"
|
||||
@error="$emit('error', $event)"
|
||||
|
|
@ -166,6 +171,7 @@ export default {
|
|||
</template>
|
||||
<work-item-note-replying v-if="isReplying" :body="replyingText" />
|
||||
<work-item-add-note
|
||||
v-if="shouldShowReplyForm"
|
||||
:autofocus="autofocus"
|
||||
:query-variables="queryVariables"
|
||||
:full-path="fullPath"
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
hasReplies: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
workItemType: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -59,13 +64,19 @@ export default {
|
|||
},
|
||||
entryClass() {
|
||||
return {
|
||||
'note note-wrapper note-comment': true,
|
||||
'gl-p-4': !this.isFirstNote,
|
||||
'note note-wrapper note-comment gl-mb-4': true,
|
||||
'gl-p-2 gl-mt-3 gl-pl-3': !this.isFirstNote,
|
||||
};
|
||||
},
|
||||
showReply() {
|
||||
return this.note.userPermissions.createNote && this.isFirstNote;
|
||||
},
|
||||
noteHeaderClass() {
|
||||
return {
|
||||
'note-header': true,
|
||||
'gl-pt-2': !this.isFirstNote,
|
||||
};
|
||||
},
|
||||
autosaveKey() {
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
return `${this.note.id}-comment`;
|
||||
|
|
@ -142,37 +153,41 @@ export default {
|
|||
@submitForm="updateNote"
|
||||
/>
|
||||
<div v-else class="timeline-content-inner" data-testid="note-wrapper">
|
||||
<div class="note-header">
|
||||
<note-header :author="author" :created-at="note.createdAt" :note-id="note.id" />
|
||||
<note-actions
|
||||
:show-reply="showReply"
|
||||
:show-edit="hasAdminPermission"
|
||||
@startReplying="showReplyForm"
|
||||
@startEditing="startEditing"
|
||||
/>
|
||||
<!-- v-if condition should be moved to "delete" dropdown item as soon as we implement copying the link -->
|
||||
<gl-dropdown
|
||||
v-if="hasAdminPermission"
|
||||
v-gl-tooltip
|
||||
icon="ellipsis_v"
|
||||
text-sr-only
|
||||
right
|
||||
:text="$options.i18n.moreActionsText"
|
||||
:title="$options.i18n.moreActionsText"
|
||||
category="tertiary"
|
||||
no-caret
|
||||
>
|
||||
<gl-dropdown-item
|
||||
variant="danger"
|
||||
data-testid="delete-note-action"
|
||||
@click="$emit('deleteNote')"
|
||||
<div :class="noteHeaderClass">
|
||||
<note-header :author="author" :created-at="note.createdAt" :note-id="note.id">
|
||||
<span v-if="note.createdAt" class="d-none d-sm-inline">·</span>
|
||||
</note-header>
|
||||
<div class="gl-display-inline-flex">
|
||||
<note-actions
|
||||
:show-reply="showReply"
|
||||
:show-edit="hasAdminPermission"
|
||||
@startReplying="showReplyForm"
|
||||
@startEditing="startEditing"
|
||||
/>
|
||||
<!-- v-if condition should be moved to "delete" dropdown item as soon as we implement copying the link -->
|
||||
<gl-dropdown
|
||||
v-if="hasAdminPermission"
|
||||
v-gl-tooltip
|
||||
icon="ellipsis_v"
|
||||
text-sr-only
|
||||
right
|
||||
:text="$options.i18n.moreActionsText"
|
||||
:title="$options.i18n.moreActionsText"
|
||||
category="tertiary"
|
||||
no-caret
|
||||
>
|
||||
{{ $options.i18n.deleteNoteText }}
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
<gl-dropdown-item
|
||||
variant="danger"
|
||||
data-testid="delete-note-action"
|
||||
@click="$emit('deleteNote')"
|
||||
>
|
||||
{{ $options.i18n.deleteNoteText }}
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-discussion-body">
|
||||
<note-body ref="noteBody" :note="note" />
|
||||
<note-body ref="noteBody" :note="note" :has-replies="hasReplies" />
|
||||
</div>
|
||||
<edited-at
|
||||
v-if="note.lastEditedBy"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,19 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
hasReplies: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
noteBodyClass() {
|
||||
return {
|
||||
'note-body gl-pb-0!': true,
|
||||
'gl-mb-2': this.hasReplies,
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'note.bodyHtml': {
|
||||
|
|
@ -38,7 +51,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="note-body" class="note-body">
|
||||
<div ref="note-body" :class="noteBodyClass">
|
||||
<div
|
||||
v-safe-html:[$options.safeHtmlConfig]="note.bodyHtml"
|
||||
class="note-text md"
|
||||
|
|
|
|||
|
|
@ -27,5 +27,5 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div v-safe-html="signedOutText" class="disabled-comment gl-text-center"></div>
|
||||
<div v-safe-html="signedOutText" class="disabled-comment gl-text-center gl-relative"></div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -320,6 +320,11 @@ export default {
|
|||
);
|
||||
return widgetHierarchy.children.nodes;
|
||||
},
|
||||
workItemBodyClass() {
|
||||
return {
|
||||
'gl-pt-5': !this.updateError && !this.isModal,
|
||||
};
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.modalWorkItemId || this.modalWorkItemIid) {
|
||||
|
|
@ -486,246 +491,244 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<section class="gl-pt-5">
|
||||
<gl-alert
|
||||
v-if="updateError"
|
||||
class="gl-mb-3"
|
||||
variant="danger"
|
||||
@dismiss="updateError = undefined"
|
||||
>
|
||||
{{ updateError }}
|
||||
</gl-alert>
|
||||
|
||||
<div v-if="workItemLoading" class="gl-max-w-26 gl-py-5">
|
||||
<gl-skeleton-loader :height="65" :width="240">
|
||||
<rect width="240" height="20" x="5" y="0" rx="4" />
|
||||
<rect width="100" height="20" x="5" y="45" rx="4" />
|
||||
</gl-skeleton-loader>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="gl-display-flex gl-align-items-center" data-testid="work-item-body">
|
||||
<ul
|
||||
v-if="parentWorkItem"
|
||||
class="list-unstyled gl-display-flex gl-mr-auto gl-max-w-26 gl-md-max-w-50p gl-min-w-0 gl-mb-0 gl-z-index-0"
|
||||
data-testid="work-item-parent"
|
||||
>
|
||||
<li class="gl-ml-n4 gl-display-flex gl-align-items-center gl-overflow-hidden">
|
||||
<gl-button
|
||||
v-gl-tooltip.hover
|
||||
class="gl-text-truncate gl-max-w-full"
|
||||
:icon="parentWorkItemIconName"
|
||||
category="tertiary"
|
||||
:href="parentUrl"
|
||||
:title="parentWorkItemReference"
|
||||
@click="openInModal($event, parentWorkItem)"
|
||||
>{{ parentWorkItemReference }}</gl-button
|
||||
>
|
||||
<gl-icon name="chevron-right" :size="16" class="gl-flex-shrink-0" />
|
||||
</li>
|
||||
<li
|
||||
class="gl-px-4 gl-py-3 gl-line-height-0 gl-display-flex gl-align-items-center gl-overflow-hidden gl-flex-shrink-0"
|
||||
<section>
|
||||
<section v-if="updateError" class="flash-container flash-container-page sticky">
|
||||
<gl-alert class="gl-mb-3" variant="danger" @dismiss="updateError = undefined">
|
||||
{{ updateError }}
|
||||
</gl-alert>
|
||||
</section>
|
||||
<section :class="workItemBodyClass">
|
||||
<div v-if="workItemLoading" class="gl-max-w-26 gl-py-5">
|
||||
<gl-skeleton-loader :height="65" :width="240">
|
||||
<rect width="240" height="20" x="5" y="0" rx="4" />
|
||||
<rect width="100" height="20" x="5" y="45" rx="4" />
|
||||
</gl-skeleton-loader>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="gl-display-flex gl-align-items-center" data-testid="work-item-body">
|
||||
<ul
|
||||
v-if="parentWorkItem"
|
||||
class="list-unstyled gl-display-flex gl-mr-auto gl-max-w-26 gl-md-max-w-50p gl-min-w-0 gl-mb-0 gl-z-index-0"
|
||||
data-testid="work-item-parent"
|
||||
>
|
||||
<work-item-type-icon
|
||||
:work-item-icon-name="workItemIconName"
|
||||
:work-item-type="workItemType && workItemType.toUpperCase()"
|
||||
/>
|
||||
{{ workItemBreadcrumbReference }}
|
||||
</li>
|
||||
</ul>
|
||||
<work-item-type-icon
|
||||
v-else-if="!error"
|
||||
:work-item-icon-name="workItemIconName"
|
||||
:work-item-type="workItemType && workItemType.toUpperCase()"
|
||||
show-text
|
||||
class="gl-font-weight-bold gl-text-secondary gl-mr-auto"
|
||||
data-testid="work-item-type"
|
||||
/>
|
||||
<gl-loading-icon v-if="updateInProgress" :inline="true" class="gl-mr-3" />
|
||||
<gl-badge
|
||||
v-if="workItem.confidential"
|
||||
v-gl-tooltip.bottom
|
||||
:title="confidentialTooltip"
|
||||
variant="warning"
|
||||
icon="eye-slash"
|
||||
class="gl-mr-3 gl-cursor-help"
|
||||
>{{ __('Confidential') }}</gl-badge
|
||||
>
|
||||
<work-item-actions
|
||||
v-if="canUpdate || canDelete"
|
||||
<li class="gl-ml-n4 gl-display-flex gl-align-items-center gl-overflow-hidden">
|
||||
<gl-button
|
||||
v-gl-tooltip.hover
|
||||
class="gl-text-truncate gl-max-w-full"
|
||||
:icon="parentWorkItemIconName"
|
||||
category="tertiary"
|
||||
:href="parentUrl"
|
||||
:title="parentWorkItemReference"
|
||||
@click="openInModal($event, parentWorkItem)"
|
||||
>{{ parentWorkItemReference }}</gl-button
|
||||
>
|
||||
<gl-icon name="chevron-right" :size="16" class="gl-flex-shrink-0" />
|
||||
</li>
|
||||
<li
|
||||
class="gl-px-4 gl-py-3 gl-line-height-0 gl-display-flex gl-align-items-center gl-overflow-hidden gl-flex-shrink-0"
|
||||
>
|
||||
<work-item-type-icon
|
||||
:work-item-icon-name="workItemIconName"
|
||||
:work-item-type="workItemType && workItemType.toUpperCase()"
|
||||
/>
|
||||
{{ workItemBreadcrumbReference }}
|
||||
</li>
|
||||
</ul>
|
||||
<work-item-type-icon
|
||||
v-else-if="!error"
|
||||
:work-item-icon-name="workItemIconName"
|
||||
:work-item-type="workItemType && workItemType.toUpperCase()"
|
||||
show-text
|
||||
class="gl-font-weight-bold gl-text-secondary gl-mr-auto"
|
||||
data-testid="work-item-type"
|
||||
/>
|
||||
<gl-loading-icon v-if="updateInProgress" :inline="true" class="gl-mr-3" />
|
||||
<gl-badge
|
||||
v-if="workItem.confidential"
|
||||
v-gl-tooltip.bottom
|
||||
:title="confidentialTooltip"
|
||||
variant="warning"
|
||||
icon="eye-slash"
|
||||
class="gl-mr-3 gl-cursor-help"
|
||||
>{{ __('Confidential') }}</gl-badge
|
||||
>
|
||||
<work-item-actions
|
||||
v-if="canUpdate || canDelete"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-type="workItemType"
|
||||
:can-delete="canDelete"
|
||||
:can-update="canUpdate"
|
||||
:is-confidential="workItem.confidential"
|
||||
:is-parent-confidential="parentWorkItemConfidentiality"
|
||||
@deleteWorkItem="$emit('deleteWorkItem', { workItemType, workItemId: workItem.id })"
|
||||
@toggleWorkItemConfidentiality="toggleConfidentiality"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<gl-button
|
||||
v-if="isModal"
|
||||
category="tertiary"
|
||||
data-testid="work-item-close"
|
||||
icon="close"
|
||||
:aria-label="__('Close')"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
<work-item-title
|
||||
v-if="workItem.title"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-title="workItem.title"
|
||||
:work-item-type="workItemType"
|
||||
:can-delete="canDelete"
|
||||
:work-item-parent-id="workItemParentId"
|
||||
:can-update="canUpdate"
|
||||
:is-confidential="workItem.confidential"
|
||||
:is-parent-confidential="parentWorkItemConfidentiality"
|
||||
@deleteWorkItem="$emit('deleteWorkItem', { workItemType, workItemId: workItem.id })"
|
||||
@toggleWorkItemConfidentiality="toggleConfidentiality"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<gl-button
|
||||
v-if="isModal"
|
||||
category="tertiary"
|
||||
data-testid="work-item-close"
|
||||
icon="close"
|
||||
:aria-label="__('Close')"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
<work-item-title
|
||||
v-if="workItem.title"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-title="workItem.title"
|
||||
:work-item-type="workItemType"
|
||||
:work-item-parent-id="workItemParentId"
|
||||
:can-update="canUpdate"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-created-updated
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-iid="workItemIid"
|
||||
:full-path="fullPath"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
/>
|
||||
<work-item-state
|
||||
:work-item="workItem"
|
||||
:work-item-parent-id="workItemParentId"
|
||||
:can-update="canUpdate"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-assignees
|
||||
v-if="workItemAssignees"
|
||||
:can-update="canUpdate"
|
||||
:work-item-id="workItem.id"
|
||||
:assignees="workItemAssignees.assignees.nodes"
|
||||
:allows-multiple-assignees="workItemAssignees.allowsMultipleAssignees"
|
||||
:work-item-type="workItemType"
|
||||
:can-invite-members="workItemAssignees.canInviteMembers"
|
||||
:full-path="fullPath"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-labels
|
||||
v-if="workItemLabels"
|
||||
:work-item-id="workItem.id"
|
||||
:can-update="canUpdate"
|
||||
:full-path="fullPath"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-due-date
|
||||
v-if="workItemDueDate"
|
||||
:can-update="canUpdate"
|
||||
:due-date="workItemDueDate.dueDate"
|
||||
:start-date="workItemDueDate.startDate"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-type="workItemType"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-milestone
|
||||
v-if="workItemMilestone"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-milestone="workItemMilestone.milestone"
|
||||
:work-item-type="workItemType"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
:can-update="canUpdate"
|
||||
:full-path="fullPath"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-weight
|
||||
v-if="workItemWeight"
|
||||
class="gl-mb-5"
|
||||
:can-update="canUpdate"
|
||||
:weight="workItemWeight.weight"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-type="workItemType"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-progress
|
||||
v-if="workItemProgress"
|
||||
class="gl-mb-5"
|
||||
:can-update="canUpdate"
|
||||
:progress="workItemProgress.progress"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-type="workItemType"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-iteration
|
||||
v-if="workItemIteration"
|
||||
class="gl-mb-5"
|
||||
:iteration="workItemIteration.iteration"
|
||||
:can-update="canUpdate"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-type="workItemType"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
:full-path="fullPath"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-health-status
|
||||
v-if="workItemHealthStatus"
|
||||
class="gl-mb-5"
|
||||
:health-status="workItemHealthStatus.healthStatus"
|
||||
:can-update="canUpdate"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-type="workItemType"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
:full-path="fullPath"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-description
|
||||
v-if="hasDescriptionWidget"
|
||||
:work-item-id="workItem.id"
|
||||
:full-path="fullPath"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
class="gl-pt-5"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-tree
|
||||
v-if="workItemType === $options.WORK_ITEM_TYPE_VALUE_OBJECTIVE"
|
||||
:work-item-type="workItemType"
|
||||
:parent-work-item-type="workItem.workItemType.name"
|
||||
:work-item-id="workItem.id"
|
||||
:children="children"
|
||||
:can-update="canUpdate"
|
||||
:project-path="fullPath"
|
||||
:confidential="workItem.confidential"
|
||||
@addWorkItemChild="addChild"
|
||||
@removeChild="removeChild"
|
||||
@show-modal="openInModal"
|
||||
/>
|
||||
<template v-if="workItemsMvcEnabled">
|
||||
<work-item-notes
|
||||
v-if="workItemNotes"
|
||||
<work-item-created-updated
|
||||
:work-item-id="workItem.id"
|
||||
:query-variables="queryVariables"
|
||||
:work-item-iid="workItemIid"
|
||||
:full-path="fullPath"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
/>
|
||||
<work-item-state
|
||||
:work-item="workItem"
|
||||
:work-item-parent-id="workItemParentId"
|
||||
:can-update="canUpdate"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-assignees
|
||||
v-if="workItemAssignees"
|
||||
:can-update="canUpdate"
|
||||
:work-item-id="workItem.id"
|
||||
:assignees="workItemAssignees.assignees.nodes"
|
||||
:allows-multiple-assignees="workItemAssignees.allowsMultipleAssignees"
|
||||
:work-item-type="workItemType"
|
||||
:can-invite-members="workItemAssignees.canInviteMembers"
|
||||
:full-path="fullPath"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-labels
|
||||
v-if="workItemLabels"
|
||||
:work-item-id="workItem.id"
|
||||
:can-update="canUpdate"
|
||||
:full-path="fullPath"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-due-date
|
||||
v-if="workItemDueDate"
|
||||
:can-update="canUpdate"
|
||||
:due-date="workItemDueDate.dueDate"
|
||||
:start-date="workItemDueDate.startDate"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-type="workItemType"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-milestone
|
||||
v-if="workItemMilestone"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-milestone="workItemMilestone.milestone"
|
||||
:work-item-type="workItemType"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
:can-update="canUpdate"
|
||||
:full-path="fullPath"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-weight
|
||||
v-if="workItemWeight"
|
||||
class="gl-mb-5"
|
||||
:can-update="canUpdate"
|
||||
:weight="workItemWeight.weight"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-type="workItemType"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-progress
|
||||
v-if="workItemProgress"
|
||||
class="gl-mb-5"
|
||||
:can-update="canUpdate"
|
||||
:progress="workItemProgress.progress"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-type="workItemType"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-iteration
|
||||
v-if="workItemIteration"
|
||||
class="gl-mb-5"
|
||||
:iteration="workItemIteration.iteration"
|
||||
:can-update="canUpdate"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-type="workItemType"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
:full-path="fullPath"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-health-status
|
||||
v-if="workItemHealthStatus"
|
||||
class="gl-mb-5"
|
||||
:health-status="workItemHealthStatus.healthStatus"
|
||||
:can-update="canUpdate"
|
||||
:work-item-id="workItem.id"
|
||||
:work-item-type="workItemType"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
:full-path="fullPath"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-description
|
||||
v-if="hasDescriptionWidget"
|
||||
:work-item-id="workItem.id"
|
||||
:full-path="fullPath"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:query-variables="queryVariables"
|
||||
class="gl-pt-5"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-tree
|
||||
v-if="workItemType === $options.WORK_ITEM_TYPE_VALUE_OBJECTIVE"
|
||||
:work-item-type="workItemType"
|
||||
:parent-work-item-type="workItem.workItemType.name"
|
||||
:work-item-id="workItem.id"
|
||||
:children="children"
|
||||
:can-update="canUpdate"
|
||||
:project-path="fullPath"
|
||||
:confidential="workItem.confidential"
|
||||
@addWorkItemChild="addChild"
|
||||
@removeChild="removeChild"
|
||||
@show-modal="openInModal"
|
||||
/>
|
||||
<template v-if="workItemsMvcEnabled">
|
||||
<work-item-notes
|
||||
v-if="workItemNotes"
|
||||
:work-item-id="workItem.id"
|
||||
:query-variables="queryVariables"
|
||||
:full-path="fullPath"
|
||||
:fetch-by-iid="fetchByIid"
|
||||
:work-item-type="workItemType"
|
||||
class="gl-pt-5"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
</template>
|
||||
<gl-empty-state
|
||||
v-if="error"
|
||||
:title="$options.i18n.fetchErrorTitle"
|
||||
:description="error"
|
||||
:svg-path="noAccessSvgPath"
|
||||
/>
|
||||
</template>
|
||||
<gl-empty-state
|
||||
v-if="error"
|
||||
:title="$options.i18n.fetchErrorTitle"
|
||||
:description="error"
|
||||
:svg-path="noAccessSvgPath"
|
||||
<work-item-detail-modal
|
||||
v-if="!isModal"
|
||||
ref="modal"
|
||||
:work-item-id="modalWorkItemId"
|
||||
:work-item-iid="modalWorkItemIid"
|
||||
:show="true"
|
||||
@close="updateUrl"
|
||||
/>
|
||||
</template>
|
||||
<work-item-detail-modal
|
||||
v-if="!isModal"
|
||||
ref="modal"
|
||||
:work-item-id="modalWorkItemId"
|
||||
:work-item-iid="modalWorkItemIid"
|
||||
:show="true"
|
||||
@close="updateUrl"
|
||||
/>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -89,7 +89,15 @@
|
|||
|
||||
.work-item-notes {
|
||||
.discussion-notes ul.notes li.toggle-replies-widget {
|
||||
// top to be zero , we don't need extra spacing there
|
||||
// offset for .timeline-content padding + an extra 1px for border width
|
||||
margin: -5px -9px;
|
||||
margin: 0 -9px -5px;
|
||||
}
|
||||
}
|
||||
|
||||
// sticky error placement for errors in modals , by default it is 83px for full view
|
||||
#work-item-detail-modal {
|
||||
.flash-container.flash-container-page.sticky {
|
||||
top: -8px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ module Groups
|
|||
groups_with_guest_access_plus
|
||||
end
|
||||
|
||||
groups = groups.search(params[:search]) if params[:search].present?
|
||||
groups = by_search(groups)
|
||||
|
||||
sort(groups).with_route
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ require './spec/support/sidekiq_middleware'
|
|||
|
||||
Sidekiq::Testing.inline! do
|
||||
Gitlab::Seeder.quiet do
|
||||
User.not_mass_generated.sample(10).each do |user|
|
||||
User.humans.not_mass_generated.sample(10).each do |user|
|
||||
source_project = Project.not_mass_generated.public_only.sample
|
||||
|
||||
##
|
||||
|
|
|
|||
|
|
@ -0,0 +1,185 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UpdateVulnerabilityReadsTriggerToSetHasIssue < Gitlab::Database::Migration[2.1]
|
||||
enable_lock_retries!
|
||||
|
||||
def up
|
||||
execute(<<~SQL)
|
||||
CREATE OR REPLACE FUNCTION insert_or_update_vulnerability_reads()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
severity smallint;
|
||||
state smallint;
|
||||
report_type smallint;
|
||||
resolved_on_default_branch boolean;
|
||||
present_on_default_branch boolean;
|
||||
namespace_id bigint;
|
||||
has_issues boolean;
|
||||
BEGIN
|
||||
IF (NEW.vulnerability_id IS NULL AND (TG_OP = 'INSERT' OR TG_OP = 'UPDATE')) THEN
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
IF (TG_OP = 'UPDATE' AND OLD.vulnerability_id IS NOT NULL AND NEW.vulnerability_id IS NOT NULL) THEN
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
SELECT
|
||||
vulnerabilities.severity, vulnerabilities.state, vulnerabilities.report_type, vulnerabilities.resolved_on_default_branch, vulnerabilities.present_on_default_branch
|
||||
INTO
|
||||
severity, state, report_type, resolved_on_default_branch, present_on_default_branch
|
||||
FROM
|
||||
vulnerabilities
|
||||
WHERE
|
||||
vulnerabilities.id = NEW.vulnerability_id;
|
||||
|
||||
IF present_on_default_branch IS NOT true THEN
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
SELECT
|
||||
projects.namespace_id
|
||||
INTO
|
||||
namespace_id
|
||||
FROM
|
||||
projects
|
||||
WHERE
|
||||
projects.id = NEW.project_id;
|
||||
|
||||
SELECT
|
||||
EXISTS (SELECT 1 FROM vulnerability_issue_links WHERE vulnerability_issue_links.vulnerability_id = NEW.vulnerability_id)
|
||||
INTO
|
||||
has_issues;
|
||||
|
||||
INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues)
|
||||
VALUES (NEW.vulnerability_id, namespace_id, NEW.project_id, NEW.scanner_id, report_type, severity, state, resolved_on_default_branch, NEW.uuid::uuid, NEW.location->>'image', NEW.location->'kubernetes_resource'->>'agent_id', CAST(NEW.location->'kubernetes_resource'->>'agent_id' AS bigint), has_issues)
|
||||
ON CONFLICT(vulnerability_id) DO NOTHING;
|
||||
RETURN NULL;
|
||||
END
|
||||
$$
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
CREATE OR REPLACE FUNCTION insert_vulnerability_reads_from_vulnerability()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
scanner_id bigint;
|
||||
uuid uuid;
|
||||
location_image text;
|
||||
cluster_agent_id text;
|
||||
casted_cluster_agent_id bigint;
|
||||
namespace_id bigint;
|
||||
has_issues boolean;
|
||||
BEGIN
|
||||
SELECT
|
||||
v_o.scanner_id, v_o.uuid, v_o.location->>'image', v_o.location->'kubernetes_resource'->>'agent_id', CAST(v_o.location->'kubernetes_resource'->>'agent_id' AS bigint), projects.namespace_id
|
||||
INTO
|
||||
scanner_id, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, namespace_id
|
||||
FROM
|
||||
vulnerability_occurrences v_o
|
||||
INNER JOIN projects ON projects.id = v_o.project_id
|
||||
WHERE
|
||||
v_o.vulnerability_id = NEW.id
|
||||
LIMIT 1;
|
||||
|
||||
SELECT
|
||||
EXISTS (SELECT 1 FROM vulnerability_issue_links WHERE vulnerability_issue_links.vulnerability_id = NEW.id)
|
||||
INTO
|
||||
has_issues;
|
||||
|
||||
INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues)
|
||||
VALUES (NEW.id, namespace_id, NEW.project_id, scanner_id, NEW.report_type, NEW.severity, NEW.state, NEW.resolved_on_default_branch, uuid::uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues)
|
||||
ON CONFLICT(vulnerability_id) DO NOTHING;
|
||||
RETURN NULL;
|
||||
END
|
||||
$$
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
execute(<<~SQL)
|
||||
CREATE OR REPLACE FUNCTION insert_or_update_vulnerability_reads() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
severity smallint;
|
||||
state smallint;
|
||||
report_type smallint;
|
||||
resolved_on_default_branch boolean;
|
||||
present_on_default_branch boolean;
|
||||
namespace_id bigint;
|
||||
BEGIN
|
||||
IF (NEW.vulnerability_id IS NULL AND (TG_OP = 'INSERT' OR TG_OP = 'UPDATE')) THEN
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
IF (TG_OP = 'UPDATE' AND OLD.vulnerability_id IS NOT NULL AND NEW.vulnerability_id IS NOT NULL) THEN
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
SELECT
|
||||
vulnerabilities.severity, vulnerabilities.state, vulnerabilities.report_type, vulnerabilities.resolved_on_default_branch, vulnerabilities.present_on_default_branch
|
||||
INTO
|
||||
severity, state, report_type, resolved_on_default_branch, present_on_default_branch
|
||||
FROM
|
||||
vulnerabilities
|
||||
WHERE
|
||||
vulnerabilities.id = NEW.vulnerability_id;
|
||||
|
||||
IF present_on_default_branch IS NOT true THEN
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
SELECT
|
||||
projects.namespace_id
|
||||
INTO
|
||||
namespace_id
|
||||
FROM
|
||||
projects
|
||||
WHERE
|
||||
projects.id = NEW.project_id;
|
||||
|
||||
INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id)
|
||||
VALUES (NEW.vulnerability_id, namespace_id, NEW.project_id, NEW.scanner_id, report_type, severity, state, resolved_on_default_branch, NEW.uuid::uuid, NEW.location->>'image', NEW.location->'kubernetes_resource'->>'agent_id', CAST(NEW.location->'kubernetes_resource'->>'agent_id' AS bigint))
|
||||
ON CONFLICT(vulnerability_id) DO NOTHING;
|
||||
RETURN NULL;
|
||||
END
|
||||
$$;
|
||||
SQL
|
||||
|
||||
execute(<<~SQL)
|
||||
CREATE OR REPLACE FUNCTION insert_vulnerability_reads_from_vulnerability() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
scanner_id bigint;
|
||||
uuid uuid;
|
||||
location_image text;
|
||||
cluster_agent_id text;
|
||||
casted_cluster_agent_id bigint;
|
||||
namespace_id bigint;
|
||||
BEGIN
|
||||
SELECT
|
||||
v_o.scanner_id, v_o.uuid, v_o.location->>'image', v_o.location->'kubernetes_resource'->>'agent_id', CAST(v_o.location->'kubernetes_resource'->>'agent_id' AS bigint), projects.namespace_id
|
||||
INTO
|
||||
scanner_id, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, namespace_id
|
||||
FROM
|
||||
vulnerability_occurrences v_o
|
||||
INNER JOIN projects ON projects.id = v_o.project_id
|
||||
WHERE
|
||||
v_o.vulnerability_id = NEW.id
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id)
|
||||
VALUES (NEW.id, namespace_id, NEW.project_id, scanner_id, NEW.report_type, NEW.severity, NEW.state, NEW.resolved_on_default_branch, uuid::uuid, location_image, cluster_agent_id, casted_cluster_agent_id)
|
||||
ON CONFLICT(vulnerability_id) DO NOTHING;
|
||||
RETURN NULL;
|
||||
END
|
||||
$$;
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ScheduleMigrationForLinks < Gitlab::Database::Migration[2.1]
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
MIGRATION = 'MigrateLinksForVulnerabilityFindings'
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
SUB_BATCH_SIZE = 500
|
||||
BATCH_SIZE = 10000
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:vulnerability_occurrences,
|
||||
:id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(MIGRATION, :vulnerability_occurrences, :id, [])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
ce2100af8a397f9d2acfcdb9d8e4fefd82c42cecc78b1e762812738622bf76a9
|
||||
|
|
@ -0,0 +1 @@
|
|||
8b8b1a55b2f82b4dc0dcbb2b618dbc4dabdcb21d091cd98f19c68cc6fb4fa493
|
||||
|
|
@ -68,6 +68,7 @@ DECLARE
|
|||
resolved_on_default_branch boolean;
|
||||
present_on_default_branch boolean;
|
||||
namespace_id bigint;
|
||||
has_issues boolean;
|
||||
BEGIN
|
||||
IF (NEW.vulnerability_id IS NULL AND (TG_OP = 'INSERT' OR TG_OP = 'UPDATE')) THEN
|
||||
RETURN NULL;
|
||||
|
|
@ -82,7 +83,7 @@ BEGIN
|
|||
INTO
|
||||
severity, state, report_type, resolved_on_default_branch, present_on_default_branch
|
||||
FROM
|
||||
vulnerabilities
|
||||
vulnerabilities
|
||||
WHERE
|
||||
vulnerabilities.id = NEW.vulnerability_id;
|
||||
|
||||
|
|
@ -99,8 +100,13 @@ BEGIN
|
|||
WHERE
|
||||
projects.id = NEW.project_id;
|
||||
|
||||
INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id)
|
||||
VALUES (NEW.vulnerability_id, namespace_id, NEW.project_id, NEW.scanner_id, report_type, severity, state, resolved_on_default_branch, NEW.uuid::uuid, NEW.location->>'image', NEW.location->'kubernetes_resource'->>'agent_id', CAST(NEW.location->'kubernetes_resource'->>'agent_id' AS bigint))
|
||||
SELECT
|
||||
EXISTS (SELECT 1 FROM vulnerability_issue_links WHERE vulnerability_issue_links.vulnerability_id = NEW.vulnerability_id)
|
||||
INTO
|
||||
has_issues;
|
||||
|
||||
INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues)
|
||||
VALUES (NEW.vulnerability_id, namespace_id, NEW.project_id, NEW.scanner_id, report_type, severity, state, resolved_on_default_branch, NEW.uuid::uuid, NEW.location->>'image', NEW.location->'kubernetes_resource'->>'agent_id', CAST(NEW.location->'kubernetes_resource'->>'agent_id' AS bigint), has_issues)
|
||||
ON CONFLICT(vulnerability_id) DO NOTHING;
|
||||
RETURN NULL;
|
||||
END
|
||||
|
|
@ -127,6 +133,7 @@ DECLARE
|
|||
cluster_agent_id text;
|
||||
casted_cluster_agent_id bigint;
|
||||
namespace_id bigint;
|
||||
has_issues boolean;
|
||||
BEGIN
|
||||
SELECT
|
||||
v_o.scanner_id, v_o.uuid, v_o.location->>'image', v_o.location->'kubernetes_resource'->>'agent_id', CAST(v_o.location->'kubernetes_resource'->>'agent_id' AS bigint), projects.namespace_id
|
||||
|
|
@ -139,8 +146,13 @@ BEGIN
|
|||
v_o.vulnerability_id = NEW.id
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id)
|
||||
VALUES (NEW.id, namespace_id, NEW.project_id, scanner_id, NEW.report_type, NEW.severity, NEW.state, NEW.resolved_on_default_branch, uuid::uuid, location_image, cluster_agent_id, casted_cluster_agent_id)
|
||||
SELECT
|
||||
EXISTS (SELECT 1 FROM vulnerability_issue_links WHERE vulnerability_issue_links.vulnerability_id = NEW.id)
|
||||
INTO
|
||||
has_issues;
|
||||
|
||||
INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues)
|
||||
VALUES (NEW.id, namespace_id, NEW.project_id, scanner_id, NEW.report_type, NEW.severity, NEW.state, NEW.resolved_on_default_branch, uuid::uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues)
|
||||
ON CONFLICT(vulnerability_id) DO NOTHING;
|
||||
RETURN NULL;
|
||||
END
|
||||
|
|
|
|||
|
|
@ -151,7 +151,8 @@ Backups of GitLab databases and file systems are taken every 24 hours, and are k
|
|||
- You can use the project export option in:
|
||||
- [The UI](../user/project/settings/import_export.md#export-a-project-and-its-data).
|
||||
- [The API](../api/project_import_export.md#schedule-an-export).
|
||||
- [Group export](../user/group/settings/import_export.md) does *not* export the projects in it, but does export:
|
||||
- [Group export by uploading a file export](../user/group/import/index.md#migrate-groups-by-uploading-an-export-file-deprecated)
|
||||
does **not** export the projects in it, but does export:
|
||||
- Epics
|
||||
- Milestones
|
||||
- Boards
|
||||
|
|
|
|||
|
|
@ -17,21 +17,81 @@ To scale GitLab, you can configure GitLab to use multiple application databases.
|
|||
|
||||
Due to [known issues](#known-issues), configuring GitLab with multiple databases is in [**Alpha**](../../policy/alpha-beta-support.md#alpha-features).
|
||||
|
||||
After you have set up multiple databases, GitLab uses a second application database for
|
||||
[CI/CD features](../../ci/index.md), referred to as the `ci` database.
|
||||
|
||||
All tables have exactly the same structure in both the `main`, and `ci`
|
||||
databases. Some examples:
|
||||
|
||||
- When multiple databases are configured, the `ci_pipelines` table exists in
|
||||
both the `main` and `ci` databases, but GitLab reads and writes only to the
|
||||
`ci_pipelines` table in the `ci` database.
|
||||
- Similarly, the `projects` table exists in
|
||||
both the `main` and `ci` databases, but GitLab reads and writes only to the
|
||||
`projects` table in the `main` database.
|
||||
- For some tables (such as `loose_foreign_keys_deleted_records`) GitLab reads and writes to both the `main` and `ci` databases. See the
|
||||
[development documentation](../../development/database/multiple_databases.md#gitlab-schema)
|
||||
|
||||
## Known issues
|
||||
|
||||
- Migrating data from the `main` database to the `ci` database is not supported or documented yet.
|
||||
- Once data is migrated to the `ci` database, you cannot migrate it back.
|
||||
|
||||
## Migrate existing installations
|
||||
|
||||
To migrate existing data from the `main` database to the `ci` database, you can
|
||||
copy the database across.
|
||||
|
||||
### Existing source installation
|
||||
|
||||
1. Stop GitLab, except for PostgreSQL:
|
||||
|
||||
```shell
|
||||
sudo service gitlab stop
|
||||
sudo service postgresql start
|
||||
```
|
||||
|
||||
1. Dump the `main` database:
|
||||
|
||||
```shell
|
||||
sudo -u git pg_dump -f gitlabhq_production.sql gitlabhq_production
|
||||
```
|
||||
|
||||
1. Create the `ci` database, and copy the data from the previous dump:
|
||||
|
||||
```shell
|
||||
sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production_ci OWNER git;"
|
||||
sudo -u git psql -f gitlabhq_production.sql gitlabhq_production_ci
|
||||
```
|
||||
|
||||
1. Configure GitLab to [use multiple databases](#set-up-multiple-databases).
|
||||
|
||||
### Existing Omnibus installation
|
||||
|
||||
1. Stop GitLab, except for PostgreSQL:
|
||||
|
||||
```shell
|
||||
sudo gitlab-ctl stop
|
||||
sudo gitlab-ctl start postgresql
|
||||
```
|
||||
|
||||
1. Dump the `main` database:
|
||||
|
||||
```shell
|
||||
sudo -u gitlab-psql /opt/gitlab/embedded/bin/pg_dump -h /var/opt/gitlab/postgresql -f gitlabhq_production.sql gitlabhq_production
|
||||
```
|
||||
|
||||
1. Create the `ci` database, and copy the data from the previous dump:
|
||||
|
||||
```shell
|
||||
sudo -u gitlab-psql /opt/gitlab/embedded/bin/psql -h /var/opt/gitlab/postgresql -d template1 -c "CREATE DATABASE gitlabhq_production_ci OWNER gitlab;"
|
||||
sudo -u gitlab-psql /opt/gitlab/embedded/bin/psql -h /var/opt/gitlab/postgresql -f gitlabhq_production.sql gitlabhq_production_ci
|
||||
```
|
||||
|
||||
1. Configure GitLab to [use multiple databases](#set-up-multiple-databases).
|
||||
|
||||
## Set up multiple databases
|
||||
|
||||
Use the following content to set up multiple databases with a new GitLab installation.
|
||||
|
||||
There is no documentation for existing GitLab installations yet.
|
||||
|
||||
After you have set up multiple databases, GitLab uses a second application database for
|
||||
[CI/CD features](../../ci/index.md), referred to as the `ci` database. For
|
||||
example, GitLab reads and writes to the `ci_pipelines` table in the `ci`
|
||||
database.
|
||||
To configure GitLab to use multiple application databases, follow the instructions below for your installation type.
|
||||
|
||||
WARNING:
|
||||
You must stop GitLab before setting up multiple databases. This prevents
|
||||
|
|
@ -40,6 +100,9 @@ the other way around.
|
|||
|
||||
### Installations from source
|
||||
|
||||
1. For existing installations,
|
||||
[migrate the data](#migrate-existing-installations) first.
|
||||
|
||||
1. [Back up GitLab](../../raketasks/backup_restore.md)
|
||||
in case of unforeseen issues.
|
||||
|
||||
|
|
@ -70,7 +133,7 @@ the other way around.
|
|||
1. Update the service files to set the `GITLAB_ALLOW_SEPARATE_CI_DATABASE`
|
||||
environment variable to `true`.
|
||||
|
||||
1. Create the `gitlabhq_production_ci` database:
|
||||
1. For new installations only. Create the `gitlabhq_production_ci` database:
|
||||
|
||||
```shell
|
||||
sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;"
|
||||
|
|
@ -91,6 +154,9 @@ the other way around.
|
|||
|
||||
### Omnibus GitLab installations
|
||||
|
||||
1. For existing installations,
|
||||
[migrate the data](#migrate-existing-installations) first.
|
||||
|
||||
1. [Back up GitLab](../../raketasks/backup_restore.md)
|
||||
in case of unforeseen issues.
|
||||
|
||||
|
|
@ -116,7 +182,8 @@ the other way around.
|
|||
sudo gitlab-ctl reconfigure
|
||||
```
|
||||
|
||||
1. Optional. Reconfiguring GitLab should create the `gitlabhq_production_ci`. If it did not, manually create the `gitlabhq_production_ci`:
|
||||
1. Optional, for new installations only. Reconfiguring GitLab should create the
|
||||
`gitlabhq_production_ci` database if it does not exist. If the database is not created automatically, create it manually:
|
||||
|
||||
```shell
|
||||
sudo gitlab-ctl start postgresql
|
||||
|
|
|
|||
|
|
@ -37,13 +37,14 @@ idea is to have one ETL pipeline for each relation to be imported.
|
|||
|
||||
### API
|
||||
|
||||
The current [Project](../user/project/settings/import_export.md) and [Group](../user/group/settings/import_export.md) Import are file based, so they require an export
|
||||
step to generate the file to be imported.
|
||||
The current [project](../user/project/settings/import_export.md) and
|
||||
[group](../user/group/import/index.md#migrate-groups-by-uploading-an-export-file-deprecated) imports are file based, so
|
||||
they require an export step to generate the file to be imported.
|
||||
|
||||
GitLab Group migration leverages on [GitLab API](../api/rest/index.md) to speed the migration.
|
||||
Group migration by direct transfer leverages the [GitLab API](../api/rest/index.md) to speed the migration.
|
||||
|
||||
And, because we're on the road to [GraphQL](../api/graphql/index.md),
|
||||
GitLab Group Migration will be contributing towards to expand the GraphQL API coverage, which benefits both GitLab
|
||||
Group migration by direct transfer can contribute to expanding GraphQL API coverage, which benefits both GitLab
|
||||
and its users.
|
||||
|
||||
### Namespace
|
||||
|
|
|
|||
|
|
@ -4,28 +4,29 @@ group: unassigned
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Test Import Project
|
||||
# Test import project
|
||||
|
||||
For testing, we can import our own [GitLab CE](https://gitlab.com/gitlab-org/gitlab-foss/) project (named `gitlabhq` in this case) under a group named `qa-perf-testing`. Project tarballs that can be used for testing can be found over on the [performance-data](https://gitlab.com/gitlab-org/quality/performance-data) project. A different project could be used if required.
|
||||
|
||||
There are several options for importing the project into your GitLab environment. They are detailed as follows with the assumption that the recommended group `qa-perf-testing` and project `gitlabhq` are being set up.
|
||||
You can import the project into your GitLab environment in a number of ways. They are detailed as follows with the
|
||||
assumption that the recommended group `qa-perf-testing` and project `gitlabhq` are being set up.
|
||||
|
||||
## Importing the project
|
||||
|
||||
There are several ways to import a project.
|
||||
Use one of these methods to import the test project.
|
||||
|
||||
### Importing via UI
|
||||
### Import by using the UI
|
||||
|
||||
The first option is to [import the Project tarball file via the GitLab UI](../user/project/settings/import_export.md#import-a-project-and-its-data):
|
||||
The first option is to [import the project tarball file by using the GitLab UI](../user/project/settings/import_export.md#import-a-project-and-its-data):
|
||||
|
||||
1. Create the group `qa-perf-testing`
|
||||
1. Import the [GitLab FOSS project tarball](https://gitlab.com/gitlab-org/quality/performance-data/-/blob/master/projects_export/gitlabhq_export.tar.gz) into the Group.
|
||||
1. Create the group `qa-perf-testing`.
|
||||
1. Import the [GitLab FOSS project tarball](https://gitlab.com/gitlab-org/quality/performance-data/-/blob/master/projects_export/gitlabhq_export.tar.gz) into the group.
|
||||
|
||||
It should take up to 15 minutes for the project to fully import. You can head to the project's main page for the current status.
|
||||
|
||||
This method ignores all the errors silently (including the ones related to `GITALY_DISABLE_REQUEST_LIMITS`) and is used by GitLab users. For development and testing, check the other methods below.
|
||||
|
||||
### Importing via the `import-project` script
|
||||
### Import by using the `import-project` script
|
||||
|
||||
A convenient script, [`bin/import-project`](https://gitlab.com/gitlab-org/quality/performance/blob/master/bin/import-project), is provided with [performance](https://gitlab.com/gitlab-org/quality/performance) project to import the Project tarball into a GitLab environment via API from the terminal.
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ bin/import-project --help
|
|||
|
||||
The process should take up to 15 minutes for the project to import fully. The script checks the status periodically and exits after the import has completed.
|
||||
|
||||
### Importing via GitHub
|
||||
### Import by using GitHub
|
||||
|
||||
There is also an option to [import the project via GitHub](../user/project/import/github.md):
|
||||
|
||||
|
|
@ -51,7 +52,12 @@ There is also an option to [import the project via GitHub](../user/project/impor
|
|||
|
||||
This method takes longer to import than the other methods and depends on several factors. It's recommended to use the other methods.
|
||||
|
||||
### Importing via the Rails console
|
||||
### Import by using a Rake task
|
||||
|
||||
To import the test project by using a Rake task, see
|
||||
[Import large projects](../administration/raketasks/project_import_export.md#import-large-projects).
|
||||
|
||||
### Import by using the Rails console
|
||||
|
||||
The last option is to import a project using a Rails console:
|
||||
|
||||
|
|
@ -126,8 +132,9 @@ bundle exec rails r /path_to_script/script.rb project_name /path_to_extracted_p
|
|||
|
||||
## Access token setup
|
||||
|
||||
Many of the tests also require a GitLab Personal Access Token. This is due to numerous endpoints themselves requiring authentication.
|
||||
Many of the tests also require a GitLab personal access token because numerous endpoints require authentication themselves.
|
||||
|
||||
[The official GitLab docs detail how to create this token](../user/profile/personal_access_tokens.md#create-a-personal-access-token). The tests require that the token is generated by an administrator and that it has the `API` and `read_repository` permissions.
|
||||
[The GitLab documentation details how to create this token](../user/profile/personal_access_tokens.md#create-a-personal-access-token).
|
||||
The tests require that the token is generated by an administrator and that it has the `API` and `read_repository` permissions.
|
||||
|
||||
Details on how to use the Access Token with each type of test are found in their respective documentation.
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ Enable any relevant feature flag, if the spam/CAPTCHA support is behind a featur
|
|||
1. For **Site key**, use: `6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI`
|
||||
1. For **Secret key**, use: `6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe`
|
||||
1. Go to **Admin -> Settings -> Reporting** settings: `http://gdk.test:3000/admin/application_settings/reporting#js-spam-settings`
|
||||
1. Expand the **Spam and Anti-bot Protection** section.
|
||||
1. Select **Enable reCAPTCHA**. Enabling for login is not required unless you are testing that feature.
|
||||
1. Enter the **Site key** and **Secret key**.
|
||||
1. To set up Akismet:
|
||||
|
|
|
|||
|
|
@ -273,9 +273,7 @@ project or group from there:
|
|||
the backed-up instance from which you want to restore.
|
||||
1. [Restore the backup](#restore-gitlab) into this new instance, then
|
||||
export your [project](../user/project/settings/import_export.md)
|
||||
or [group](../user/group/settings/import_export.md). Be sure to read the
|
||||
**Important Notes** on either export feature's documentation to understand
|
||||
what is and isn't exported.
|
||||
or [group](../user/group/import/index.md#migrate-groups-by-uploading-an-export-file-deprecated). For more information about what is and isn't exported, see the export feature's documentation.
|
||||
1. After the export is complete, go to the old instance and then import it.
|
||||
1. After importing the projects or groups that you wanted is complete, you may
|
||||
delete the new, temporary GitLab instance.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ group: Optimize
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Value Streams Dashboard **(PREMIUM)**
|
||||
# Value Streams Dashboard **(ULTIMATE)**
|
||||
|
||||
> Introduced in GitLab 15.8 as a Closed [Beta](../../policy/alpha-beta-support.md#beta-features) feature.
|
||||
|
||||
|
|
@ -13,7 +13,6 @@ You can leave feedback on dashboard bugs or functionality in [issue 381787](http
|
|||
This feature is not ready for production use.
|
||||
|
||||
The Value Streams Dashboard is a customizable dashboard that enables decision-makers to identify trends, patterns, and opportunities for digital transformation improvements.
|
||||
The dashboard's basic functionality is available on the Premium tier, but most of the dashboard features (for example DORA metrics) are available only on the Ultimate tier.
|
||||
This page is a work in progress, and we're updating the information as we add more features.
|
||||
For more information, see the [Value Stream Management category direction page](https://about.gitlab.com/direction/plan/value_stream_management/).
|
||||
|
||||
|
|
|
|||
|
|
@ -8,20 +8,27 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
# Compliance report **(ULTIMATE)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36524) in GitLab 12.8 as Compliance Dashboard.
|
||||
> - Compliance violation drawer [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299357) in GitLab 14.1.
|
||||
> - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/299360) to compliance report in GitLab 14.2.
|
||||
> - [Replaced](https://gitlab.com/groups/gitlab-org/-/epics/5237) by merge request violations in GitLab 14.6 [with a flag](../../../administration/feature_flags.md) named `compliance_violations_report`. Disabled by default.
|
||||
> - GraphQL API [introduced](https://gitlab.com/groups/gitlab-org/-/epics/7222) in GitLab 14.9.
|
||||
> - [Generally available](https://gitlab.com/groups/gitlab-org/-/epics/5237) in GitLab 14.10. [Feature flag `compliance_violations_report`](https://gitlab.com/gitlab-org/gitlab/-/issues/346266) removed.
|
||||
|
||||
Compliance report gives you the ability to see a group's merge request activity. It provides a
|
||||
high-level view for all projects in the group. For example, code approved for merging into
|
||||
production.
|
||||
With a compliance report, you can:
|
||||
|
||||
You can use the report to get:
|
||||
- List compliance violations from all merge requests merged in projects in a group.
|
||||
- See the reason and severity of each compliance violation.
|
||||
|
||||
- A list of compliance violations from all merged merge requests within the group.
|
||||
- The reason and severity of each compliance violation.
|
||||
- A link to the merge request that caused each compliance violation.
|
||||
When you select a row in the compliance report, a drawer appears that provides:
|
||||
|
||||
- The project name and [compliance framework label](../../project/settings/index.md#add-a-compliance-framework-to-a-project),
|
||||
if the project has one assigned.
|
||||
- A link to the merge request that introduced the violation.
|
||||
- The merge request's branch path in the format `[source] into [target]`.
|
||||
- A list of users that committed changes to the merge request.
|
||||
- A list of users that commented on the merge request.
|
||||
- A list of users that approved the merge request.
|
||||
- The user that merged the merge request.
|
||||
|
||||
## View the compliance report for a group
|
||||
|
||||
|
|
@ -34,9 +41,19 @@ To view the compliance report:
|
|||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Security and Compliance > Compliance report**.
|
||||
|
||||
### Severity levels scale
|
||||
You can sort the compliance report on:
|
||||
|
||||
The following is a list of available violation severity levels, ranked from most to least severe:
|
||||
- Severity level.
|
||||
- Type of violation.
|
||||
- Merge request title.
|
||||
|
||||
Select a row to see details of the compliance violation.
|
||||
|
||||
### Severity levels
|
||||
|
||||
Each compliance violation has one of the following severities.
|
||||
|
||||
<!-- vale gitlab.SubstitutionWarning = NO -->
|
||||
|
||||
| Icon | Severity level |
|
||||
|:----------------------------------------------|:---------------|
|
||||
|
|
@ -46,49 +63,41 @@ The following is a list of available violation severity levels, ranked from most
|
|||
| **{severity-low, 18, gl-fill-orange-300}** | Low |
|
||||
| **{severity-info, 18, gl-fill-blue-400}** | Info |
|
||||
|
||||
<!-- vale gitlab.SubstitutionWarning = YES -->
|
||||
|
||||
### Violation types
|
||||
|
||||
The following is a list of violations that are either:
|
||||
From [GitLab 14.10](https://gitlab.com/groups/gitlab-org/-/epics/6870), these are the available compliance violations.
|
||||
|
||||
- Already available.
|
||||
- Aren't available, but which we are tracking in issues.
|
||||
| Violation | Severity level | Category | Description |
|
||||
|:----------------------------------|:---------------|:----------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Author approved merge request | High | [Separation of duties](#separation-of-duties) | Author of the merge request approved their own merge request. For more information, see [Prevent approval by author](../../project/merge_requests/approvals/settings.md#prevent-approval-by-author). |
|
||||
| Committers approved merge request | High | [Separation of duties](#separation-of-duties) | Committers of the merge request approved the merge request they contributed to. For more information, see [Prevent approvals by users who add commits](../../project/merge_requests/approvals/settings.md#prevent-approvals-by-users-who-add-commits). |
|
||||
| Fewer than two approvals | High | [Separation of duties](#separation-of-duties) | Merge request was merged with fewer than two approvals. For more information, see [Merge request approval rules](../../project/merge_requests/approvals/rules.md). |
|
||||
|
||||
| Violation | Severity level | Category | Description | Availability |
|
||||
|:-------------------------------------|:----------------|:---------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------|
|
||||
| Author approved merge request | High | [Separation of duties](#separation-of-duties) | The author of the merge request approved their own merge request. For more information, see [Prevent approval by author](../../project/merge_requests/approvals/settings.md#prevent-approval-by-author). | [Available in GitLab 14.10](https://gitlab.com/groups/gitlab-org/-/epics/6870) |
|
||||
| Committers approved merge request | High | [Separation of duties](#separation-of-duties) | The committers of the merge request approved the merge request they contributed to. For more information, see [Prevent approvals by users who add commits](../../project/merge_requests/approvals/settings.md#prevent-approvals-by-users-who-add-commits). | [Available in GitLab 14.10](https://gitlab.com/groups/gitlab-org/-/epics/6870) |
|
||||
| Fewer than two approvals | High | [Separation of duties](#separation-of-duties) | The merge request was merged with fewer than two approvals. For more information, see [Merge request approval rules](../../project/merge_requests/approvals/rules.md). | [Available in GitLab 14.10](https://gitlab.com/groups/gitlab-org/-/epics/6870) |
|
||||
| Pipeline failed | Medium | [Pipeline results](../../../ci/pipelines/index.md) | The merge requests pipeline failed and was merged. | [Unavailable](https://gitlab.com/gitlab-org/gitlab/-/issues/346011) |
|
||||
| Pipeline passed with warnings | Info | [Pipeline results](../../../ci/pipelines/index.md) | The merge request pipeline passed with warnings and was merged. | [Unavailable](https://gitlab.com/gitlab-org/gitlab/-/issues/346011) |
|
||||
| Code coverage down more than 10% | High | [Code coverage](../../../ci/pipelines/settings.md#merge-request-test-coverage-results) | The code coverage report for the merge request indicates a reduction in coverage of more than 10%. | [Unavailable](https://gitlab.com/gitlab-org/gitlab/-/issues/346011) |
|
||||
| Code coverage down between 5% to 10% | Medium | [Code coverage](../../../ci/pipelines/settings.md#merge-request-test-coverage-results) | The code coverage report for the merge request indicates a reduction in coverage of between 5% to 10%. | [Unavailable](https://gitlab.com/gitlab-org/gitlab/-/issues/346011) |
|
||||
| Code coverage down between 1% to 5% | Low | [Code coverage](../../../ci/pipelines/settings.md#merge-request-test-coverage-results) | The code coverage report for the merge request indicates a reduction in coverage of between 1% to 5%. | [Unavailable](https://gitlab.com/gitlab-org/gitlab/-/issues/346011) |
|
||||
| Code coverage down less than 1% | Info | [Code coverage](../../../ci/pipelines/settings.md#merge-request-test-coverage-results) | The code coverage report for the merge request indicates a reduction in coverage of less than 1%. | [Unavailable](https://gitlab.com/gitlab-org/gitlab/-/issues/346011) |
|
||||
The following are unavailable compliance violations that are tracked in [issue 346011](https://gitlab.com/gitlab-org/gitlab/-/issues/346011).
|
||||
|
||||
## Merge request drawer
|
||||
<!-- vale gitlab.SubstitutionWarning = NO -->
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299357) in GitLab 14.1.
|
||||
| Violation | Severity level | Category | Description |
|
||||
|:-------------------------------------|:---------------|:---------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------|
|
||||
| Pipeline failed | Medium | [Pipeline results](../../../ci/pipelines/index.md) | Merge requests pipeline failed and was merged. |
|
||||
| Pipeline passed with warnings | Info | [Pipeline results](../../../ci/pipelines/index.md) | Merge request pipeline passed with warnings and was merged. |
|
||||
| Code coverage down more than 10% | High | [Code coverage](../../../ci/pipelines/settings.md#merge-request-test-coverage-results) | Code coverage report for the merge request indicates a reduction in coverage of more than 10%. |
|
||||
| Code coverage down between 5% to 10% | Medium | [Code coverage](../../../ci/pipelines/settings.md#merge-request-test-coverage-results) | Code coverage report for the merge request indicates a reduction in coverage of between 5% to 10%. |
|
||||
| Code coverage down between 1% to 5% | Low | [Code coverage](../../../ci/pipelines/settings.md#merge-request-test-coverage-results) | Code coverage report for the merge request indicates a reduction in coverage of between 1% to 5%. |
|
||||
| Code coverage down less than 1% | Info | [Code coverage](../../../ci/pipelines/settings.md#merge-request-test-coverage-results) | Code coverage report for the merge request indicates a reduction in coverage of less than 1%. |
|
||||
|
||||
When you select a row, a drawer is shown that provides further details about the merge
|
||||
request:
|
||||
<!-- vale gitlab.SubstitutionWarning = YES -->
|
||||
|
||||
- Project name and [compliance framework label](../../project/settings/index.md#add-a-compliance-framework-to-a-project),
|
||||
if the project has one assigned.
|
||||
- Link to the merge request.
|
||||
- The merge request's branch path in the format `[source] into [target]`.
|
||||
- A list of users that committed changes to the merge request.
|
||||
- A list of users that commented on the merge request.
|
||||
- A list of users that approved the merge request.
|
||||
- The user that merged the merge request.
|
||||
#### Separation of duties
|
||||
|
||||
## Separation of duties
|
||||
GitLab supports a separation of duties policy between users who create and approve merge requests. Our criteria for the
|
||||
separation of duties is:
|
||||
|
||||
We support a separation of duties policy between users who create and approve merge requests.
|
||||
Our criteria for the separation of duties is as follows:
|
||||
|
||||
- [A merge request author is **not** allowed to approve their merge request](../../project/merge_requests/approvals/settings.md#prevent-approval-by-author)
|
||||
- [A merge request committer is **not** allowed to approve a merge request they have added commits to](../../project/merge_requests/approvals/settings.md#prevent-approvals-by-users-who-add-commits)
|
||||
- [The minimum number of approvals required to merge a merge request is **at least** two](../../project/merge_requests/approvals/rules.md)
|
||||
- [A merge request author is **not** allowed to approve their merge request](../../project/merge_requests/approvals/settings.md#prevent-approval-by-author).
|
||||
- [A merge request committer is **not** allowed to approve a merge request they have added commits to](../../project/merge_requests/approvals/settings.md#prevent-approvals-by-users-who-add-commits).
|
||||
- [The minimum number of approvals required to merge a merge request is **at least** two](../../project/merge_requests/approvals/rules.md).
|
||||
|
||||
## Chain of Custody report
|
||||
|
||||
|
|
@ -98,16 +107,25 @@ Our criteria for the separation of duties is as follows:
|
|||
> - Chain of Custody report includes all commits (instead of just merge commits) [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267601) in GitLab 15.9 with a flag named `all_commits_compliance_report`. Disabled by default.
|
||||
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112092) in GitLab 15.9. Feature flag `all_commits_compliance_report` removed.
|
||||
|
||||
The Chain of Custody report provides a 1 month trailing window of any commit into a project under the group.
|
||||
The Chain of Custody report provides a 1 month trailing window of all commits to a project under the group.
|
||||
|
||||
To generate the report for all commits, GitLab:
|
||||
|
||||
1. Fetches all projects under the group.
|
||||
1. For each project, fetches the last 1 month of commits. Each project is capped at 1024 commits. If there are more than 1024 commits in the 1-month window, they
|
||||
are truncated.
|
||||
1. Writes the commits to a CSV file. The file is truncated at 15 MB because the report is emailed as an attachment.
|
||||
1. For each project, fetches the last 1 month of commits. Each project is capped at 1024 commits. If there are more than
|
||||
1024 commits in the 1-month window, they are truncated.
|
||||
1. Writes the commits to a CSV file. The file is truncated at 15 MB because the report is emailed as an attachment
|
||||
(GitLab 15.5 and later).
|
||||
|
||||
The report includes:
|
||||
|
||||
- Commit SHA.
|
||||
- Commit author.
|
||||
- Committer.
|
||||
- Date committed.
|
||||
- Group.
|
||||
- Project.
|
||||
|
||||
The report includes the commit SHA, commit author, committer, date committed, the group, and the project.
|
||||
If the commit has a related merge commit, then the following are also included:
|
||||
|
||||
- Merge commit SHA.
|
||||
|
|
@ -117,39 +135,33 @@ If the commit has a related merge commit, then the following are also included:
|
|||
- Pipeline ID.
|
||||
- Merge request approvers.
|
||||
|
||||
### Generate Chain of Custody report
|
||||
|
||||
To generate the Chain of Custody report:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Security and Compliance > Compliance report**.
|
||||
1. Select **List of all merge commits**.
|
||||
|
||||
The Chain of Custody report is either:
|
||||
Depending on your version of GitLab, the Chain of Custody report is either sent through email or available for download.
|
||||
|
||||
- Available for download.
|
||||
- Sent through email. Requires GitLab 15.5 and later.
|
||||
|
||||
### Commit-specific Chain of Custody report
|
||||
### Generate commit-specific Chain of Custody report
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267629) in GitLab 13.6.
|
||||
|
||||
Authenticated group owners can generate a commit-specific Chain of Custody report for a given commit SHA, either:
|
||||
You can generate a commit-specific Chain of Custody report for a given merge commit SHA. This report provides only the
|
||||
details for the provided merge commit SHA. Issue [393446](https://gitlab.com/gitlab-org/gitlab/-/issues/393446) proposes
|
||||
to extend the commit SHA filtering to work with all commits instead of only merge commits.
|
||||
|
||||
- Using the GitLab UI:
|
||||
To generate a commit-specific Chain of Custody report:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Security and Compliance > Compliance report**.
|
||||
1. At the top of the compliance report, to the right of **List of all merge commits**, select the down arrow (**{chevron-lg-down}**).
|
||||
1. Enter the merge commit SHA, and then select **Export commit custody report**.
|
||||
SHA and then select **Export commit custody report**.
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Security and Compliance > Compliance report**.
|
||||
1. At the top of the compliance report, to the right of **List of all merge commits**, select the down arrow
|
||||
(**{chevron-lg-down}**).
|
||||
1. Enter the merge commit SHA, and then select **Export commit custody report**.
|
||||
|
||||
The Chain of Custody report is either:
|
||||
Depending on your version of GitLab, the Chain of Custody report is either sent through email or available for download.
|
||||
|
||||
- Available for download.
|
||||
- Sent through email. Requires GitLab 15.5 and later.
|
||||
|
||||
- Using a direct link: `https://gitlab.com/groups/<group-name>/-/security/merge_commit_reports.csv?commit_sha={optional_commit_sha}`, passing in an optional value to the
|
||||
`commit_sha` query parameter.
|
||||
|
||||
NOTE:
|
||||
The Chain of Custody report download is a CSV file, with a maximum size of 15 MB.
|
||||
The remaining records are truncated when this limit is reached.
|
||||
Alternatively, use a direct link: `https://gitlab.com/groups/<group-name>/-/security/merge_commit_reports.csv?commit_sha={optional_commit_sha}`,
|
||||
passing in an optional value to the `commit_sha` query parameter.
|
||||
|
|
|
|||
|
|
@ -38,10 +38,11 @@ this feature, ask an administrator to [enable the feature flag](../../../adminis
|
|||
Migrating groups by direct transfer copies the groups from one place to another. You can:
|
||||
|
||||
- Copy many groups at once.
|
||||
- Copy top-level groups to:
|
||||
- In the GitLab UI, copy top-level groups to:
|
||||
- Another top-level group.
|
||||
- The subgroup of any existing top-level group.
|
||||
- Another GitLab instance, including GitLab.com.
|
||||
- In the [API](../../../api/bulk_imports.md), copy top-level groups and subgroups to these locations.
|
||||
- Copy groups with projects (in [beta](../../../policy/alpha-beta-support.md#beta-features) and not ready for production
|
||||
use) or without projects. Copying projects with groups is available:
|
||||
- On GitLab.com by default.
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ info: "To determine the technical writer assigned to the Stage/Group associated
|
|||
Existing projects on any self-managed GitLab instance or GitLab.com can be exported to a file and
|
||||
then imported into a new GitLab instance. You can also:
|
||||
|
||||
- [Migrate groups](../../group/import/index.md) using the preferred method.
|
||||
- [Migrate groups using file exports](../../group/settings/import_export.md).
|
||||
- Migrate projects when you [migrate groups by direct transfer](../../group/import/index.md#migrate-groups-by-direct-transfer-recommended).
|
||||
- [Migrate groups by using file exports](../../group/import/index.md#migrate-groups-by-uploading-an-export-file-deprecated).
|
||||
|
||||
GitLab maps user contributions correctly when an admin access token is used to perform the import.
|
||||
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ To access a group wiki:
|
|||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53247) in GitLab 13.9.
|
||||
|
||||
Users with the Owner role in a group can
|
||||
[import and export group wikis](../../group/settings/import_export.md) when importing
|
||||
or exporting a group.
|
||||
[import or export a group wiki](../../group/import/index.md#migrate-groups-by-uploading-an-export-file-deprecated) when they
|
||||
import or export a group.
|
||||
|
||||
Content created in a group wiki is not deleted when an account is downgraded or a
|
||||
GitLab trial ends. The group wiki data is exported whenever the group owner of
|
||||
|
|
@ -48,8 +48,8 @@ the wiki is exported.
|
|||
To access the group wiki data from the export file if the feature is no longer
|
||||
available, you have to:
|
||||
|
||||
1. Extract the [export file tarball](../../group/settings/import_export.md) with
|
||||
this command, replacing `FILENAME` with your file's name:
|
||||
1. Extract the [export file tarball](../../group/import/index.md#migrate-groups-by-uploading-an-export-file-deprecated)
|
||||
with this command, replacing `FILENAME` with your file's name:
|
||||
`tar -xvzf FILENAME.tar.gz`
|
||||
1. Browse to the `repositories` directory. This directory contains a
|
||||
[Git bundle](https://git-scm.com/docs/git-bundle) with the extension `.wiki.bundle`.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# The class to migrate the link data into their own records from the json attribute
|
||||
class MigrateLinksForVulnerabilityFindings < BatchedMigrationJob
|
||||
feature_category :vulnerability_management
|
||||
operation_name :migrate_links_for_vulnerability_findings
|
||||
|
||||
# The class is mimicking Vulnerabilites::Finding
|
||||
class Finding < ApplicationRecord
|
||||
self.table_name = 'vulnerability_occurrences'
|
||||
|
||||
validates :details, json_schema: { filename: 'vulnerability_finding_details', draft: 7 }, if: false
|
||||
end
|
||||
|
||||
# The class is mimicking Vulnerabilites::FindingLink
|
||||
class Link < ApplicationRecord
|
||||
self.table_name = 'vulnerability_finding_links'
|
||||
end
|
||||
|
||||
def perform
|
||||
each_sub_batch do |sub_batch|
|
||||
migrate_remediations(sub_batch)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def migrate_remediations(sub_batch)
|
||||
sub_batch.each do |finding|
|
||||
links = extract_links(finding.raw_metadata)
|
||||
|
||||
list_of_attrs = links.map do |link|
|
||||
build_link(finding, link)
|
||||
end
|
||||
|
||||
next unless list_of_attrs.present?
|
||||
|
||||
create_links(list_of_attrs)
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
rescue StandardError => e
|
||||
logger.error(
|
||||
message: e.message,
|
||||
class: self.class.name,
|
||||
model_id: finding.id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def build_link(finding, link)
|
||||
current_time = Time.current
|
||||
{
|
||||
vulnerability_occurrence_id: finding.id,
|
||||
name: link['name'],
|
||||
url: link['url'],
|
||||
created_at: current_time,
|
||||
updated_at: current_time
|
||||
}
|
||||
end
|
||||
|
||||
def create_links(attributes)
|
||||
Link.upsert_all(attributes, returning: false)
|
||||
end
|
||||
|
||||
def extract_links(metadata)
|
||||
parsed_metadata = Gitlab::Json.parse(metadata)
|
||||
|
||||
return [] unless parsed_metadata['links']
|
||||
|
||||
parsed_metadata['links'].compact.uniq
|
||||
end
|
||||
|
||||
def logger
|
||||
@logger ||= ::Gitlab::AppLogger
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -10,6 +10,27 @@ module Gitlab
|
|||
# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||
MAX_IDENTIFIER_NAME_LENGTH = 63
|
||||
|
||||
def self.check_constraint_exists?(table, constraint_name, connection:)
|
||||
# Constraint names are unique per table in Postgres, not per schema
|
||||
# Two tables can have constraints with the same name, so we filter by
|
||||
# the table name in addition to using the constraint_name
|
||||
|
||||
check_sql = <<~SQL
|
||||
SELECT COUNT(*)
|
||||
FROM pg_catalog.pg_constraint con
|
||||
INNER JOIN pg_catalog.pg_class rel
|
||||
ON rel.oid = con.conrelid
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = con.connamespace
|
||||
WHERE con.contype = 'c'
|
||||
AND con.conname = #{connection.quote(constraint_name)}
|
||||
AND nsp.nspname = #{connection.quote(connection.current_schema)}
|
||||
AND rel.relname = #{connection.quote(table)}
|
||||
SQL
|
||||
|
||||
connection.select_value(check_sql.squish) > 0
|
||||
end
|
||||
|
||||
# Returns the name for a check constraint
|
||||
#
|
||||
# type:
|
||||
|
|
@ -29,24 +50,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def check_constraint_exists?(table, constraint_name)
|
||||
# Constraint names are unique per table in Postgres, not per schema
|
||||
# Two tables can have constraints with the same name, so we filter by
|
||||
# the table name in addition to using the constraint_name
|
||||
|
||||
check_sql = <<~SQL
|
||||
SELECT COUNT(*)
|
||||
FROM pg_catalog.pg_constraint con
|
||||
INNER JOIN pg_catalog.pg_class rel
|
||||
ON rel.oid = con.conrelid
|
||||
INNER JOIN pg_catalog.pg_namespace nsp
|
||||
ON nsp.oid = con.connamespace
|
||||
WHERE con.contype = 'c'
|
||||
AND con.conname = #{connection.quote(constraint_name)}
|
||||
AND nsp.nspname = #{connection.quote(current_schema)}
|
||||
AND rel.relname = #{connection.quote(table)}
|
||||
SQL
|
||||
|
||||
connection.select_value(check_sql) > 0
|
||||
ConstraintsHelpers.check_constraint_exists?(table, constraint_name, connection: connection)
|
||||
end
|
||||
|
||||
# Adds a check constraint to a table
|
||||
|
|
|
|||
|
|
@ -2252,9 +2252,6 @@ msgstr ""
|
|||
msgid "Add a collapsible section"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add a comment"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add a comment to this line"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -2285,6 +2282,9 @@ msgstr ""
|
|||
msgid "Add a related issue"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add a reply"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add a suffix to Service Desk email address. %{linkStart}Learn more.%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ RSpec.describe 'Merge request > User creates image diff notes', :js, feature_cat
|
|||
end
|
||||
|
||||
shared_examples 'onion skin' do
|
||||
it 'resets opacity when toggling between view modes' do
|
||||
it 'resets opacity when toggling between view modes', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/393331' do
|
||||
# Simulate dragging onion-skin slider
|
||||
drag_and_drop_by(find('.dragger'), -30, 0)
|
||||
|
||||
|
|
|
|||
|
|
@ -89,13 +89,17 @@ describe('Work Item Discussion', () => {
|
|||
});
|
||||
|
||||
it('the number of threads should be equal to the response length', async () => {
|
||||
findToggleRepliesWidget().vm.$emit('toggle');
|
||||
await nextTick();
|
||||
expect(findAllThreads()).toHaveLength(
|
||||
mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0].notes.nodes.length,
|
||||
);
|
||||
});
|
||||
|
||||
it('should collapse when we click on toggle replies widget', async () => {
|
||||
findToggleRepliesWidget().vm.$emit('toggle');
|
||||
await nextTick();
|
||||
expect(findAllThreads()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should autofocus when we click expand replies', async () => {
|
||||
const mainComment = findThreadAtIndex(0);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::MigrateLinksForVulnerabilityFindings,
|
||||
feature_category: :vulnerability_management do
|
||||
let(:vulnerability_occurrences) { table(:vulnerability_occurrences) }
|
||||
let(:vulnerability_finding_links) { table(:vulnerability_finding_links) }
|
||||
let(:link_hash) { { url: 'http://test.com' } }
|
||||
let(:namespace1) { table(:namespaces).create!(name: 'namespace 1', path: 'namespace1') }
|
||||
let(:project1) { table(:projects).create!(namespace_id: namespace1.id, project_namespace_id: namespace1.id) }
|
||||
let(:user) { table(:users).create!(email: 'test1@example.com', projects_limit: 5) }
|
||||
|
||||
let(:scanner1) do
|
||||
table(:vulnerability_scanners).create!(project_id: project1.id, external_id: 'test 1', name: 'test scanner 1')
|
||||
end
|
||||
|
||||
let(:stating_id) { vulnerability_occurrences.pluck(:id).min }
|
||||
let(:end_id) { vulnerability_occurrences.pluck(:id).max }
|
||||
|
||||
let(:migration) do
|
||||
described_class.new(
|
||||
start_id: stating_id,
|
||||
end_id: end_id,
|
||||
batch_table: :vulnerability_occurrences,
|
||||
batch_column: :id,
|
||||
sub_batch_size: 2,
|
||||
pause_ms: 2,
|
||||
connection: ApplicationRecord.connection
|
||||
)
|
||||
end
|
||||
|
||||
subject(:perform_migration) { migration.perform }
|
||||
|
||||
context 'without the presence of links key' do
|
||||
before do
|
||||
create_finding!(project1.id, scanner1.id, { other_keys: 'test' })
|
||||
end
|
||||
|
||||
it 'does not create any link' do
|
||||
expect(Gitlab::AppLogger).not_to receive(:error)
|
||||
|
||||
expect { perform_migration }.not_to change { vulnerability_finding_links.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with links equals to an array of nil element' do
|
||||
before do
|
||||
create_finding!(project1.id, scanner1.id, { links: [nil] })
|
||||
end
|
||||
|
||||
it 'does not create any link' do
|
||||
expect(Gitlab::AppLogger).not_to receive(:error)
|
||||
|
||||
expect { perform_migration }.not_to change { vulnerability_finding_links.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with links equals to an array of duplicated elements' do
|
||||
let!(:finding) do
|
||||
create_finding!(project1.id, scanner1.id, { links: [link_hash, link_hash] })
|
||||
end
|
||||
|
||||
it 'creates one new link' do
|
||||
expect(Gitlab::AppLogger).not_to receive(:error)
|
||||
|
||||
expect { perform_migration }.to change { vulnerability_finding_links.count }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with existing links within raw_metadata' do
|
||||
let!(:finding1) { create_finding!(project1.id, scanner1.id, { links: [link_hash] }) }
|
||||
let!(:finding2) { create_finding!(project1.id, scanner1.id, { links: [link_hash] }) }
|
||||
|
||||
it 'creates new link for each finding' do
|
||||
expect(Gitlab::AppLogger).not_to receive(:error)
|
||||
|
||||
expect { perform_migration }.to change { vulnerability_finding_links.count }.by(2)
|
||||
end
|
||||
|
||||
context 'when create throws exception ActiveRecord::RecordNotUnique' do
|
||||
before do
|
||||
allow(migration).to receive(:create_links).and_raise(ActiveRecord::RecordNotUnique)
|
||||
end
|
||||
|
||||
it 'does not log this error nor create new records' do
|
||||
expect(Gitlab::AppLogger).not_to receive(:error)
|
||||
|
||||
expect { perform_migration }.not_to change { vulnerability_finding_links.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when create throws exception StandardError' do
|
||||
before do
|
||||
allow(migration).to receive(:create_links).and_raise(StandardError)
|
||||
end
|
||||
|
||||
it 'logs StandardError' do
|
||||
expect(Gitlab::AppLogger).to receive(:error).with({
|
||||
class: described_class.name, message: StandardError.to_s, model_id: finding1.id
|
||||
})
|
||||
expect(Gitlab::AppLogger).to receive(:error).with({
|
||||
class: described_class.name, message: StandardError.to_s, model_id: finding2.id
|
||||
})
|
||||
expect { perform_migration }.not_to change { vulnerability_finding_links.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with existing link records' do
|
||||
let!(:finding) { create_finding!(project1.id, scanner1.id, { links: [link_hash] }) }
|
||||
|
||||
before do
|
||||
vulnerability_finding_links.create!(vulnerability_occurrence_id: finding.id, url: link_hash[:url])
|
||||
end
|
||||
|
||||
it 'does not create new link' do
|
||||
expect(Gitlab::AppLogger).not_to receive(:error)
|
||||
|
||||
expect { perform_migration }.not_to change { vulnerability_finding_links.count }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_finding!(project_id, scanner_id, raw_metadata)
|
||||
vulnerability = table(:vulnerabilities).create!(project_id: project_id, author_id: user.id, title: 'test',
|
||||
severity: 4, confidence: 4, report_type: 0)
|
||||
|
||||
identifier = table(:vulnerability_identifiers).create!(project_id: project_id, external_type: 'uuid-v5',
|
||||
external_id: 'uuid-v5', fingerprint: OpenSSL::Digest::SHA256.hexdigest(vulnerability.id.to_s),
|
||||
name: 'Identifier for UUIDv5 2 2')
|
||||
|
||||
table(:vulnerability_occurrences).create!(
|
||||
vulnerability_id: vulnerability.id, project_id: project_id, scanner_id: scanner_id,
|
||||
primary_identifier_id: identifier.id, name: 'test', severity: 4, confidence: 4, report_type: 0,
|
||||
uuid: SecureRandom.uuid, project_fingerprint: '123qweasdzxc', location: { "image" => "alpine:3.4" },
|
||||
location_fingerprint: 'test', metadata_version: 'test',
|
||||
raw_metadata: raw_metadata.to_json)
|
||||
end
|
||||
end
|
||||
|
|
@ -23,43 +23,46 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#check_constraint_exists?' do
|
||||
describe '#check_constraint_exists?', :aggregate_failures do
|
||||
before do
|
||||
ActiveRecord::Migration.connection.execute(
|
||||
'ALTER TABLE projects ADD CONSTRAINT check_1 CHECK (char_length(path) <= 5) NOT VALID'
|
||||
)
|
||||
|
||||
ActiveRecord::Migration.connection.execute(
|
||||
'CREATE SCHEMA new_test_schema'
|
||||
)
|
||||
|
||||
ActiveRecord::Migration.connection.execute(
|
||||
'CREATE TABLE new_test_schema.projects (id integer, name character varying)'
|
||||
)
|
||||
|
||||
ActiveRecord::Migration.connection.execute(
|
||||
'ALTER TABLE new_test_schema.projects ADD CONSTRAINT check_2 CHECK (char_length(name) <= 5)'
|
||||
)
|
||||
ActiveRecord::Migration.connection.execute(<<~SQL)
|
||||
ALTER TABLE projects ADD CONSTRAINT check_1 CHECK (char_length(path) <= 5) NOT VALID;
|
||||
CREATE SCHEMA new_test_schema;
|
||||
CREATE TABLE new_test_schema.projects (id integer, name character varying);
|
||||
ALTER TABLE new_test_schema.projects ADD CONSTRAINT check_2 CHECK (char_length(name) <= 5);
|
||||
SQL
|
||||
end
|
||||
|
||||
it 'returns true if a constraint exists' do
|
||||
expect(model)
|
||||
.to be_check_constraint_exists(:projects, 'check_1')
|
||||
|
||||
expect(described_class)
|
||||
.to be_check_constraint_exists(:projects, 'check_1', connection: model.connection)
|
||||
end
|
||||
|
||||
it 'returns false if a constraint does not exist' do
|
||||
expect(model)
|
||||
.not_to be_check_constraint_exists(:projects, 'this_does_not_exist')
|
||||
|
||||
expect(described_class)
|
||||
.not_to be_check_constraint_exists(:projects, 'this_does_not_exist', connection: model.connection)
|
||||
end
|
||||
|
||||
it 'returns false if a constraint with the same name exists in another table' do
|
||||
expect(model)
|
||||
.not_to be_check_constraint_exists(:users, 'check_1')
|
||||
|
||||
expect(described_class)
|
||||
.not_to be_check_constraint_exists(:users, 'check_1', connection: model.connection)
|
||||
end
|
||||
|
||||
it 'returns false if a constraint with the same name exists for the same table in another schema' do
|
||||
expect(model)
|
||||
.not_to be_check_constraint_exists(:projects, 'check_2')
|
||||
|
||||
expect(described_class)
|
||||
.not_to be_check_constraint_exists(:projects, 'check_2', connection: model.connection)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ScheduleMigrationForLinks, :migration, feature_category: :vulnerability_management do
|
||||
let(:migration) { described_class::MIGRATION }
|
||||
|
||||
describe '#up' do
|
||||
it 'schedules a batched background migration' do
|
||||
migrate!
|
||||
|
||||
expect(migration).to have_scheduled_batched_migration(
|
||||
table_name: :vulnerability_occurrences,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#down' do
|
||||
it 'deletes all batched migration records' do
|
||||
migrate!
|
||||
schema_migrate_down!
|
||||
|
||||
expect(migration).not_to have_scheduled_batched_migration
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2288,9 +2288,9 @@ RSpec.describe API::Projects, feature_category: :projects do
|
|||
end
|
||||
|
||||
describe 'GET /project/:id/share_locations' do
|
||||
let_it_be(:root_group) { create(:group, :public, name: 'root group') }
|
||||
let_it_be(:project_group1) { create(:group, :public, parent: root_group, name: 'group1') }
|
||||
let_it_be(:project_group2) { create(:group, :public, parent: root_group, name: 'group2') }
|
||||
let_it_be(:root_group) { create(:group, :public, name: 'root group', path: 'root-group-path') }
|
||||
let_it_be(:project_group1) { create(:group, :public, parent: root_group, name: 'group1', path: 'group-1-path') }
|
||||
let_it_be(:project_group2) { create(:group, :public, parent: root_group, name: 'group2', path: 'group-2-path') }
|
||||
let_it_be(:project) { create(:project, :private, group: project_group1) }
|
||||
|
||||
shared_examples_for 'successful groups response' do
|
||||
|
|
@ -2340,10 +2340,22 @@ RSpec.describe API::Projects, feature_category: :projects do
|
|||
end
|
||||
|
||||
context 'when searching by group name' do
|
||||
let(:params) { { search: 'group1' } }
|
||||
context 'searching by group name' do
|
||||
it_behaves_like 'successful groups response' do
|
||||
let(:params) { { search: 'group1' } }
|
||||
let(:expected_groups) { [project_group1] }
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'successful groups response' do
|
||||
let(:expected_groups) { [project_group1] }
|
||||
context 'searching by full group path' do
|
||||
let_it_be(:project_group2_subgroup) do
|
||||
create(:group, :public, parent: project_group2, name: 'subgroup', path: 'subgroup-path')
|
||||
end
|
||||
|
||||
it_behaves_like 'successful groups response' do
|
||||
let(:params) { { search: 'root-group-path/group-2-path/subgroup-path' } }
|
||||
let(:expected_groups) { [project_group2_subgroup] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::AgentTokens::TrackUsageService do
|
||||
RSpec.describe Clusters::AgentTokens::TrackUsageService, feature_category: :kubernetes_management do
|
||||
let_it_be(:agent) { create(:cluster_agent) }
|
||||
|
||||
describe '#execute', :clean_gitlab_redis_cache do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::Agents::CreateService do
|
||||
RSpec.describe Clusters::Agents::CreateService, feature_category: :kubernetes_management do
|
||||
subject(:service) { described_class.new(project, user) }
|
||||
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::Agents::DeleteExpiredEventsService do
|
||||
RSpec.describe Clusters::Agents::DeleteExpiredEventsService, feature_category: :kubernetes_management do
|
||||
let_it_be(:agent) { create(:cluster_agent) }
|
||||
|
||||
describe '#execute' do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::Agents::DeleteService do
|
||||
RSpec.describe Clusters::Agents::DeleteService, feature_category: :kubernetes_management do
|
||||
subject(:service) { described_class.new(container: project, current_user: user) }
|
||||
|
||||
let(:cluster_agent) { create(:cluster_agent) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::BuildKubernetesNamespaceService do
|
||||
RSpec.describe Clusters::BuildKubernetesNamespaceService, feature_category: :kubernetes_management do
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:environment) { create(:environment) }
|
||||
let(:project) { environment.project }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::BuildService do
|
||||
RSpec.describe Clusters::BuildService, feature_category: :kubernetes_management do
|
||||
describe '#execute' do
|
||||
subject { described_class.new(cluster_subject).execute }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::Cleanup::ProjectNamespaceService do
|
||||
RSpec.describe Clusters::Cleanup::ProjectNamespaceService, feature_category: :kubernetes_management do
|
||||
describe '#execute' do
|
||||
subject { service.execute }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::Cleanup::ServiceAccountService do
|
||||
RSpec.describe Clusters::Cleanup::ServiceAccountService, feature_category: :kubernetes_management do
|
||||
describe '#execute' do
|
||||
subject { service.execute }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::CreateService do
|
||||
RSpec.describe Clusters::CreateService, feature_category: :kubernetes_management do
|
||||
let(:access_token) { 'xxx' }
|
||||
let(:project) { create(:project) }
|
||||
let(:user) { create(:user) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::DestroyService do
|
||||
RSpec.describe Clusters::DestroyService, feature_category: :kubernetes_management do
|
||||
describe '#execute' do
|
||||
subject { described_class.new(cluster.user, params).execute(cluster) }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::Integrations::CreateService, '#execute' do
|
||||
RSpec.describe Clusters::Integrations::CreateService, '#execute', feature_category: :kubernetes_management do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be_with_reload(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::Integrations::PrometheusHealthCheckService, '#execute' do
|
||||
RSpec.describe Clusters::Integrations::PrometheusHealthCheckService, '#execute', feature_category: :kubernetes_management do
|
||||
let(:service) { described_class.new(cluster) }
|
||||
|
||||
subject { service.execute }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
|
||||
RSpec.describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute', feature_category: :kubernetes_management do
|
||||
include KubernetesHelpers
|
||||
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
|
||||
RSpec.describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService, feature_category: :kubernetes_management do
|
||||
include KubernetesHelpers
|
||||
|
||||
let(:api_url) { 'http://111.111.111.111' }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::Kubernetes::FetchKubernetesTokenService do
|
||||
RSpec.describe Clusters::Kubernetes::FetchKubernetesTokenService, feature_category: :kubernetes_management do
|
||||
include KubernetesHelpers
|
||||
|
||||
describe '#execute' do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::Kubernetes do
|
||||
RSpec.describe Clusters::Kubernetes, feature_category: :kubernetes_management do
|
||||
it { is_expected.to be_const_defined(:GITLAB_SERVICE_ACCOUNT_NAME) }
|
||||
it { is_expected.to be_const_defined(:GITLAB_SERVICE_ACCOUNT_NAMESPACE) }
|
||||
it { is_expected.to be_const_defined(:GITLAB_ADMIN_TOKEN_NAME) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::Management::ValidateManagementProjectPermissionsService do
|
||||
RSpec.describe Clusters::Management::ValidateManagementProjectPermissionsService, feature_category: :kubernetes_management do
|
||||
describe '#execute' do
|
||||
subject { described_class.new(user).execute(cluster, management_project_id) }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::UpdateService do
|
||||
RSpec.describe Clusters::UpdateService, feature_category: :kubernetes_management do
|
||||
include KubernetesHelpers
|
||||
|
||||
describe '#execute' do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Commits::CherryPickService do
|
||||
RSpec.describe Commits::CherryPickService, feature_category: :source_code_management do
|
||||
let(:project) { create(:project, :repository) }
|
||||
# * ddd0f15ae83993f5cb66a927a28673882e99100b (HEAD -> master, origin/master, origin/HEAD) Merge branch 'po-fix-test-en
|
||||
# |\
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Commits::CommitPatchService do
|
||||
RSpec.describe Commits::CommitPatchService, feature_category: :source_code_management do
|
||||
describe '#execute' do
|
||||
let(:patches) do
|
||||
patches_folder = Rails.root.join('spec/fixtures/patchfiles')
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Commits::TagService do
|
||||
RSpec.describe Commits::TagService, feature_category: :source_code_management do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe AuditEventSaveType do
|
||||
RSpec.describe AuditEventSaveType, feature_category: :audit_events do
|
||||
subject(:target) { Object.new.extend(described_class) }
|
||||
|
||||
describe '#should_save_database? and #should_save_stream?' do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ExclusiveLeaseGuard, :clean_gitlab_redis_shared_state do
|
||||
RSpec.describe ExclusiveLeaseGuard, :clean_gitlab_redis_shared_state, feature_category: :shared do
|
||||
subject :subject_class do
|
||||
Class.new do
|
||||
include ExclusiveLeaseGuard
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe MergeRequests::AssignsMergeParams do
|
||||
RSpec.describe MergeRequests::AssignsMergeParams, feature_category: :code_review_workflow do
|
||||
it 'raises an error when used from an instance that does not respond to #current_user' do
|
||||
define_class = -> { Class.new { include MergeRequests::AssignsMergeParams }.new }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe RateLimitedService do
|
||||
RSpec.describe RateLimitedService, feature_category: :rate_limiting do
|
||||
let(:key) { :issues_create }
|
||||
let(:scope) { [:container, :current_user] }
|
||||
let(:opts) { { scope: scope, users_allowlist: -> { [User.support_bot.username] } } }
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ RSpec.shared_examples 'work items comments' do
|
|||
let(:form_selector) { '[data-testid="work-item-add-comment"]' }
|
||||
|
||||
it 'successfully creates and shows comments' do
|
||||
click_button 'Add a comment'
|
||||
click_button 'Add a reply'
|
||||
|
||||
find(form_selector).fill_in(with: "Test comment")
|
||||
click_button "Comment"
|
||||
|
|
|
|||
Loading…
Reference in New Issue