Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-03-02 09:08:22 +00:00
parent 46fd9b1dd8
commit 374f3dee7d
65 changed files with 1097 additions and 496 deletions

View File

@ -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'

View File

@ -1 +1 @@
212f0e3545668faeb671dac213eaf25be5840bc0
c7695bd902060be80b3499ffd2bd9e86d89c4f4f

View File

@ -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>

View File

@ -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 }}

View File

@ -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>

View File

@ -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"
>

View File

@ -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"

View File

@ -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">&middot;</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"

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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

View File

@ -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
##

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
ce2100af8a397f9d2acfcdb9d8e4fefd82c42cecc78b1e762812738622bf76a9

View File

@ -0,0 +1 @@
8b8b1a55b2f82b4dc0dcbb2b618dbc4dabdcb21d091cd98f19c68cc6fb4fa493

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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:

View File

@ -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.

View File

@ -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/).

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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`.

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -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)

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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) }

View File

@ -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 }

View File

@ -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 }

View File

@ -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 }

View File

@ -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 }

View File

@ -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) }

View File

@ -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) }

View File

@ -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]) }

View File

@ -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 }

View File

@ -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) }

View File

@ -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' }

View File

@ -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

View File

@ -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) }

View File

@ -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) }

View File

@ -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

View File

@ -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
# |\

View File

@ -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')

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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] } } }

View File

@ -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"