Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-09-23 15:11:09 +00:00
parent 1ccebc7b3f
commit 2cba761741
65 changed files with 798 additions and 305 deletions

View file

@ -15,27 +15,7 @@ build-qa-image:
stage: build-images
needs: []
script:
# Tag with commit SHA by default
- export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_SHA}"
# For branches, tag with slugified branch name. For tags, use the tag directly
- export QA_IMAGE_BRANCH="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_TAG:-$CI_COMMIT_REF_SLUG}"
# Auto-deploy tag format uses first 12 letters of commit SHA. Tag with that
# reference also
- export QA_IMAGE_FOR_AUTO_DEPLOY="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_SHA:0:11}"
- echo $QA_IMAGE
- echo $QA_IMAGE_BRANCH
- echo $QA_IMAGE_FOR_AUTO_DEPLOY
- |
/kaniko/executor \
--context=${CI_PROJECT_DIR} \
--dockerfile=${CI_PROJECT_DIR}/qa/Dockerfile \
--destination=${QA_IMAGE} \
--destination=${QA_IMAGE_BRANCH} \
--destination=${QA_IMAGE_FOR_AUTO_DEPLOY} \
--build-arg=CHROME_VERSION=${CHROME_VERSION} \
--build-arg=DOCKER_VERSION=${DOCKER_VERSION} \
--build-arg=QA_BUILD_TARGET=${QA_BUILD_TARGET:-qa} \
--cache=true
- ./scripts/build_qa_image
# This image is used by:
# - The `CNG` pipelines (via the `review-build-cng` job): https://gitlab.com/gitlab-org/build/CNG/-/blob/cfc67136d711e1c8c409bf8e57427a644393da2f/.gitlab-ci.yml#L335

View file

@ -1 +1 @@
c13d9d902ef8175a0b1165ef0bc8643fb37b7897
d7181e813e602f80bf53e47089da92b6342b355f

View file

@ -281,7 +281,7 @@ export default {
'gl-z-dropdown-menu!': idState.moreActionsShown,
'is-sidebar-moved': glFeatures.movedMrSidebar,
}"
class="js-file-title file-title file-title-flex-parent"
class="js-file-title file-title file-title-flex-parent gl-border"
data-qa-selector="file_title_container"
:data-qa-file-name="filePath"
@click.self="handleToggleFile"

View file

@ -0,0 +1,40 @@
<script>
import { GlAlert } from '@gitlab/ui';
import { s__ } from '~/locale';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
export default {
name: 'NewTopLevelGroupAlert',
components: {
GlAlert,
UserCalloutDismisser,
},
i18n: {
titleText: s__("Groups|You're creating a new top-level group"),
bodyText: s__(
'Groups|Members, projects, trials, and paid subscriptions are tied to a specific top-level group. If you are already a member of a top-level group, you can create a subgroup so your new work is part of your existing top-level group. Do you want to create a subgroup instead?',
),
primaryBtnText: s__('Groups|Learn more about subgroups'),
},
subgroupsDocsPath: helpPagePath('user/group/subgroups/index'),
};
</script>
<template>
<user-callout-dismisser feature-name="new_top_level_group_alert">
<template #default="{ dismiss, shouldShowCallout }">
<gl-alert
v-if="shouldShowCallout"
ref="newTopLevelAlert"
data-testid="new-top-level-alert"
:title="$options.i18n.titleText"
:primary-button-text="$options.i18n.primaryBtnText"
:primary-button-link="$options.subgroupsDocsPath"
@dismiss="dismiss"
>
{{ $options.i18n.bodyText }}
</gl-alert>
</template>
</user-callout-dismisser>
</template>

View file

@ -84,8 +84,8 @@ export default {
return sprintf(text, { commitDisplay, linkStart, linkEnd }, false);
},
adaptiveAvatarSize() {
return { default: 24, md: 32 };
toggleClass() {
return this.discussion.expanded ? 'expanded' : 'collapsed';
},
},
methods: {
@ -98,16 +98,13 @@ export default {
</script>
<template>
<div class="discussion-header gl-display-flex gl-align-items-center gl-p-5">
<div
v-once
class="timeline-icon gl-align-self-start gl-flex-shrink-0 gl-flex-shrink gl-mx-3 gl-md-ml-2 gl-md-mr-5"
>
<div class="discussion-header gl-display-flex gl-align-items-center">
<div v-once class="timeline-avatar gl-align-self-start gl-flex-shrink-0 gl-flex-shrink">
<gl-avatar-link v-if="author" :href="author.path">
<gl-avatar :src="author.avatar_url" :alt="author.name" :size="adaptiveAvatarSize" />
<gl-avatar :src="author.avatar_url" :alt="author.name" :size="32" />
</gl-avatar-link>
</div>
<div class="timeline-content w-100">
<div class="timeline-content w-100 gl-ml-3" :class="toggleClass">
<note-header
:author="author"
:created-at="firstNote.created_at"
@ -123,14 +120,14 @@ export default {
:edited-at="discussion.resolved_at"
:edited-by="discussion.resolved_by"
:action-text="resolvedText"
class-name="discussion-headline-light js-discussion-headline gl-pl-2"
class-name="discussion-headline-light js-discussion-headline gl-pl-3"
/>
<note-edited-text
v-else-if="lastUpdatedAt"
:edited-at="lastUpdatedAt"
:edited-by="lastUpdatedBy"
:action-text="__('Last updated')"
class-name="discussion-headline-light js-discussion-headline gl-pl-2"
class-name="discussion-headline-light js-discussion-headline gl-pl-3"
/>
</div>
</div>

View file

@ -142,7 +142,7 @@ export default {
:edited-at="discussion.resolved_at"
:edited-by="discussion.resolved_by"
:action-text="resolvedText"
class-name="discussion-headline-light js-discussion-headline discussion-resolved-text"
class-name="discussion-headline-light js-discussion-headline discussion-resolved-text gl-mb-2"
/>
</template>
<template #avatar-badge>

View file

@ -199,9 +199,6 @@ export default {
isMRDiffView() {
return this.line && !this.isOverviewTab;
},
authorAvatarAdaptiveSize() {
return { default: 24, md: 32 };
},
},
created() {
const line = this.note.position?.line_range?.start || this.line;
@ -409,7 +406,7 @@ export default {
:class="{ ...classNameBindings, 'internal-note': note.internal }"
:data-award-url="note.toggle_award_path"
:data-note-id="note.id"
class="note note-wrapper"
class="note note-wrapper note-comment"
data-qa-selector="noteable_note_container"
>
<div
@ -427,7 +424,7 @@ export default {
</gl-sprintf>
</div>
<div v-if="isMRDiffView" class="gl-float-left gl-mt-n1 gl-mr-3">
<div v-if="isMRDiffView" class="gl-float-left gl-mt-2">
<gl-avatar-link :href="author.path">
<gl-avatar
:src="author.avatar_url"
@ -440,13 +437,13 @@ export default {
</gl-avatar-link>
</div>
<div v-else class="gl-float-left gl-pl-3 gl-md-pl-2">
<div v-else class="timeline-avatar gl-float-left">
<gl-avatar-link :href="author.path">
<gl-avatar
:src="author.avatar_url"
:entity-name="author.username"
:alt="author.name"
:size="authorAvatarAdaptiveSize"
:size="32"
/>
<slot name="avatar-badge"></slot>

View file

@ -292,7 +292,11 @@ export default {
<template #comments>
<ul id="notes-list" class="notes main-notes-list timeline">
<template v-for="discussion in allDiscussions">
<skeleton-loading-container v-if="discussion.isSkeletonNote" :key="discussion.id" />
<skeleton-loading-container
v-if="discussion.isSkeletonNote"
:key="discussion.id"
class="note-skeleton"
/>
<timeline-entry-item v-else-if="discussion.isDraft" :key="discussion.id">
<draft-note :draft="discussion" />
</timeline-entry-item>
@ -327,7 +331,7 @@ export default {
:help-page-path="helpPagePath"
/>
</template>
<discussion-filter-note v-show="commentsDisabled" />
<discussion-filter-note v-if="commentsDisabled" />
</ul>
</template>
</ordered-layout>

View file

@ -61,7 +61,7 @@ export default {
<template>
<li
:class="liClasses"
class="gl-display-flex! gl-align-items-center gl-flex-wrap gl-bg-gray-10 gl-py-3 gl-px-5 gl-border-t"
class="toggle-replies-widget gl-display-flex! gl-align-items-center gl-flex-wrap gl-bg-gray-10 gl-py-3 gl-px-5 gl-border"
>
<gl-button
ref="toggle"
@ -75,7 +75,7 @@ export default {
<user-avatar-link
v-for="author in uniqueAuthors"
:key="author.username"
class="gl-mr-3"
class="gl-mr-3 reply-author-avatar"
:link-href="author.path"
:img-alt="author.name"
img-css-classes="gl-mr-0!"

View file

@ -1,4 +1,6 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import BindInOut from '~/behaviors/bind_in_out';
import initFilePickers from '~/file_pickers';
import Group from '~/group';
@ -8,6 +10,8 @@ import NewGroupCreationApp from './components/app.vue';
import GroupPathValidator from './group_path_validator';
import initToggleInviteMembers from './toggle_invite_members';
Vue.use(VueApollo);
new GroupPathValidator(); // eslint-disable-line no-new
new Group(); // eslint-disable-line no-new
initGroupNameAndPath();
@ -31,8 +35,13 @@ function initNewGroupCreation(el) {
hasErrors: parseBoolean(hasErrors),
};
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
return new Vue({
el,
apolloProvider,
provide: {
verificationRequired: parseBoolean(verificationRequired),
verificationFormUrl,

View file

@ -50,29 +50,19 @@ export default {
renderedNote() {
return renderMarkdown(this.note.body);
},
avatarSize() {
if (this.line && !this.isOverviewTab) {
return 24;
}
return {
default: 24,
md: 32,
};
},
},
};
</script>
<template>
<timeline-entry-item class="note note-wrapper being-posted fade-in-half">
<div class="timeline-icon">
<gl-avatar-link class="gl-mr-3" :href="getUserData.path">
<timeline-entry-item class="note note-wrapper note-comment being-posted fade-in-half">
<div class="timeline-avatar gl-float-left">
<gl-avatar-link :href="getUserData.path">
<gl-avatar
:src="getUserData.avatar_url"
:entity-name="getUserData.username"
:alt="getUserData.name"
:size="avatarSize"
:size="32"
/>
</gl-avatar-link>
</div>
@ -85,8 +75,10 @@ export default {
</a>
</div>
</div>
<div class="note-body">
<div v-safe-html="renderedNote" class="note-text md"></div>
<div class="timeline-discussion-body">
<div class="note-body">
<div v-safe-html="renderedNote" class="note-text md"></div>
</div>
</div>
</div>
</timeline-entry-item>

View file

@ -141,7 +141,7 @@ export default {
variant="link"
:icon="descriptionVersionToggleIcon"
data-testid="compare-btn"
class="gl-vertical-align-text-bottom"
class="gl-vertical-align-text-bottom gl-font-sm!"
@click="toggleDescriptionVersion"
>{{ __('Compare with previous version') }}</gl-button
>
@ -150,7 +150,7 @@ export default {
:icon="showLines ? 'chevron-up' : 'chevron-down'"
variant="link"
data-testid="outdated-lines-change-btn"
class="gl-vertical-align-text-bottom"
class="gl-vertical-align-text-bottom gl-font-sm!"
@click="toggleDiff"
>
{{ __('Compare changes') }}
@ -190,7 +190,7 @@ export default {
</div>
<div
v-if="lines.length && showLines"
class="diff-content gl-border-solid gl-border-1 gl-border-gray-200 gl-mt-4 gl-rounded-small gl-overflow-hidden"
class="diff-content outdated-lines-wrapper gl-border-solid gl-border-1 gl-border-gray-200 gl-mt-4 gl-rounded-small gl-overflow-hidden"
>
<table
:class="$options.userColorSchemeClass"

View file

@ -18,15 +18,15 @@ export default {
</script>
<template>
<timeline-entry-item class="system-note note-wrapper gl-mb-6!">
<timeline-entry-item class="system-note note-wrapper">
<div class="timeline-icon">
<gl-icon :name="icon" />
</div>
<div class="timeline-content">
<div class="note-header">
<span>
<div class="note-header-info">
<slot></slot>
</span>
</div>
</div>
<div class="note-body">
<slot name="body"></slot>

View file

@ -1,11 +1,13 @@
<script>
import { GlBreadcrumb, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import NewTopLevelGroupAlert from '~/groups/components/new_top_level_group_alert.vue';
import LegacyContainer from './components/legacy_container.vue';
import WelcomePage from './components/welcome.vue';
export default {
components: {
NewTopLevelGroupAlert,
GlBreadcrumb,
GlIcon,
WelcomePage,
@ -79,6 +81,14 @@ export default {
shouldVerify() {
return this.verificationRequired && !this.verificationCompleted;
},
showNewTopLevelGroupAlert() {
if (this.activePanel.detailProps === undefined) {
return false;
}
return this.activePanel.detailProps.parentGroupName === '';
},
},
created() {
@ -130,6 +140,7 @@ export default {
<slot name="extra-description"></slot>
</div>
<div class="col-lg-9">
<new-top-level-group-alert v-if="showNewTopLevelGroupAlert" />
<gl-breadcrumb v-if="breadcrumbs" :items="breadcrumbs" />
<legacy-container :key="activePanel.name" :selector="activePanel.selector" />
</div>

View file

@ -10,7 +10,7 @@ import {
GlDropdownDivider,
GlIntersectionObserver,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { debounce, uniqueId } from 'lodash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import currentUserQuery from '~/graphql_shared/queries/current_user.query.graphql';
import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
@ -126,6 +126,9 @@ export default {
},
},
computed: {
assigneesTitleId() {
return uniqueId('assignees-title-');
},
searchUsers() {
return this.users.nodes.map((node) => addClass({ ...node, ...node.user }));
},
@ -296,12 +299,14 @@ export default {
<template>
<div class="form-row gl-mb-5 work-item-assignees gl-relative gl-flex-nowrap">
<span
:id="assigneesTitleId"
class="gl-font-weight-bold col-lg-2 col-3 gl-pt-2 min-w-fit-content gl-overflow-wrap-break"
data-testid="assignees-title"
>{{ assigneeText }}</span
>
<gl-token-selector
ref="tokenSelector"
:aria-labelledby="assigneesTitleId"
:selected-tokens="localAssignees"
:container-class="containerClass"
:class="{ 'gl-hover-border-gray-200': canUpdate }"
@ -319,7 +324,7 @@ export default {
>
<template #empty-placeholder>
<div
class="add-assignees gl-min-w-fit-content gl-display-flex gl-align-items-center gl-text-gray-300 gl-pr-4 gl-pl-2 gl-top-2"
class="add-assignees gl-min-w-fit-content gl-display-flex gl-align-items-center gl-text-secondary gl-pr-4 gl-pl-2 gl-top-2"
data-testid="empty-state"
>
<gl-icon name="profile" />

View file

@ -228,7 +228,7 @@ export default {
class="gl-ml-auto"
icon="pencil"
data-testid="edit-description"
:aria-label="__('Edit')"
:aria-label="__('Edit description')"
@click="startEditing"
/>
</div>

View file

@ -198,7 +198,7 @@ export default {
label-cols="3"
label-cols-lg="2"
>
<span v-if="isReadonlyWithNoDates" class="gl-text-gray-400 gl-ml-4">
<span v-if="isReadonlyWithNoDates" class="gl-text-secondary gl-ml-4">
{{ $options.i18n.none }}
</span>
<div v-else class="gl-display-flex gl-flex-wrap gl-gap-5">

View file

@ -1,6 +1,6 @@
<script>
import { GlTokenSelector, GlLabel, GlSkeletonLoader } from '@gitlab/ui';
import { debounce } from 'lodash';
import { debounce, uniqueId } from 'lodash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import Tracking from '~/tracking';
import labelSearchQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql';
@ -89,6 +89,9 @@ export default {
},
},
computed: {
labelsTitleId() {
return uniqueId('labels-title-');
},
tracking() {
return {
category: TRACKING_CATEGORY_SHOW,
@ -194,6 +197,7 @@ export default {
<template>
<div class="form-row gl-mb-5 work-item-labels gl-relative gl-flex-nowrap">
<span
:id="labelsTitleId"
class="gl-font-weight-bold gl-mt-2 col-lg-2 col-3 gl-pt-2 min-w-fit-content gl-overflow-wrap-break"
data-testid="labels-title"
>{{ __('Labels') }}</span
@ -201,6 +205,7 @@ export default {
<gl-token-selector
ref="tokenSelector"
v-model="localLabels"
:aria-labelledby="labelsTitleId"
:container-class="containerClass"
:dropdown-items="searchLabels"
:loading="isLoading"
@ -216,7 +221,7 @@ export default {
>
<template #empty-placeholder>
<div
class="add-labels gl-min-w-fit-content gl-display-flex gl-align-items-center gl-text-gray-400 gl-pr-4 gl-top-2"
class="add-labels gl-min-w-fit-content gl-display-flex gl-align-items-center gl-text-secondary gl-pr-4 gl-top-2"
data-testid="empty-state"
>
<span v-if="canUpdate" class="gl-ml-2">{{ __('Add labels') }}</span>

View file

@ -257,7 +257,7 @@ export default {
class="gl-display-inline-flex gl-align-items-center gl-line-height-24 gl-ml-3"
data-testid="children-count"
>
<gl-icon :name="$options.WIDGET_TYPE_TASK_ICON" class="gl-mr-2 gl-text-gray-500" />
<gl-icon :name="$options.WIDGET_TYPE_TASK_ICON" class="gl-mr-2 gl-text-secondary" />
{{ childrenCountLabel }}
</span>
</div>

View file

@ -182,7 +182,7 @@ export default {
>
<template #result="{ item }">
<div class="gl-display-flex">
<div class="gl-text-gray-400 gl-mr-4">{{ getIdFromGraphQLId(item.id) }}</div>
<div class="gl-text-secondary gl-mr-4">{{ getIdFromGraphQLId(item.id) }}</div>
<div>{{ item.title }}</div>
</div>
</template>

View file

@ -53,7 +53,7 @@ export default {
v-gl-tooltip.hover="showTooltipOnHover"
:name="iconName"
:title="workItemTooltipTitle"
class="gl-mr-2 gl-text-gray-500"
class="gl-mr-2 gl-text-secondary"
/>
<span v-if="workItemTypeName" :class="{ 'gl-sr-only': !showText }">{{ workItemTypeName }}</span>
</span>

View file

@ -1013,7 +1013,7 @@ table.code {
clear: left;
.note-body {
margin-top: 0 !important;
padding: 0 0 $gl-padding-8;
}
}
@ -1023,8 +1023,12 @@ table.code {
}
// tiny adjustment to vertical align with the note header text
.discussion-collapsible .timeline-icon {
padding-top: 2px;
.discussion-collapsible {
margin-left: 1rem;
.timeline-icon {
padding-top: 2px;
}
}
}
@ -1097,6 +1101,7 @@ table.code {
.discussion-notes {
min-height: 35px;
background-color: transparent;
&:first-child {
// First child does not have the jagged borders
@ -1121,6 +1126,17 @@ table.code {
display: none;
}
}
ul.notes {
li.toggle-replies-widget,
.discussion-reply-holder {
margin-left: 2.5rem;
.reply-author-avatar {
height: 1.5rem;
}
}
}
}
.discussion-body .image .frame {

View file

@ -28,15 +28,9 @@
.timeline-entry {
color: $gl-text-color;
// [dark-theme]: only give background color to actual notes
// in the timeline, the note form textarea has a background
// of it's own
&:not(.note-form) {
background-color: $white;
}
&:not(.note-form).internal-note {
background-color: $orange-50;
&:not(.note-form).internal-note .timeline-content,
&:not(.note-form).draft-note .timeline-content {
background-color: $orange-50 !important;
}
.timeline-entry-inner {
@ -45,23 +39,15 @@
&:target,
&.target {
background: $line-target-blue;
.timeline-content {
background: $line-target-blue !important;
}
&.system-note .note-body .note-text.system-note-commit-list::after {
background: linear-gradient(rgba($line-target-blue, 0.1) -100px, $line-target-blue 100%);
}
}
img.avatar {
margin-right: $gl-padding-12;
@include media-breakpoint-down(sm) {
width: $gl-spacing-scale-6;
height: $gl-spacing-scale-6;
margin-right: $gl-padding-8;
}
}
.controls {
padding-top: 10px;
float: right;

View file

@ -661,10 +661,10 @@ $tabs-holder-z-index: 250;
&:not(:last-child)::before {
content: '';
border-left: 1px solid var(--gray-100, $gray-100);
border-left: 2px solid var(--gray-10, $gray-10);
position: absolute;
left: 28px;
bottom: -17px;
left: calc(1rem - 1px);
height: 16px;
}
}

View file

@ -946,6 +946,7 @@
.timeline-event-note {
p {
margin-bottom: 0;
font-size: 0.875rem;
}
}
}
@ -961,7 +962,7 @@
content: '';
border-left: 2px solid $gray-50;
position: absolute;
left: 39px;
left: 20px;
height: calc(100% + #{$gl-spacing-scale-5});
top: -#{$gl-spacing-scale-5};
}
@ -990,10 +991,6 @@
}
}
.timeline-event-note-form {
padding-left: 20px;
}
.timeline-entry:not(:last-child) {
.timeline-event-border {
@include gl-pb-5;

View file

@ -1,15 +1,15 @@
$system-note-icon-size: 32px;
$system-note-svg-size: 16px;
$system-note-icon-size: 2rem;
$system-note-svg-size: 1rem;
@mixin vertical-line($left) {
&::before {
content: '';
border-left: 2px solid $gray-10;
border-left: 2px solid var(--gray-10, $gray-10);
position: absolute;
top: 0;
bottom: 0;
left: $left;
height: calc(100% - 20px);
left: calc(#{$left} - 1px);
height: calc(100% + 1.5rem);
}
}
@ -19,17 +19,10 @@ $system-note-svg-size: 16px;
border-radius: $border-radius-default;
}
.note-wrapper {
padding: $gl-padding $gl-padding-8 $gl-padding $gl-padding;
&.outlined {
@include outline-comment();
}
}
.issuable-discussion {
.main-notes-list {
@include vertical-line(35px);
.issuable-discussion:not(.incident-timeline-events),
.limited-width-notes {
.main-notes-list > li.timeline-entry:not(:last-of-type) {
@include vertical-line(1rem);
}
}
@ -41,8 +34,6 @@ $system-note-svg-size: 16px;
position: relative;
&.timeline > .timeline-entry {
border: 1px solid $border-color;
border-radius: $border-radius-default;
margin: $gl-padding 0;
&.system-note,
@ -50,6 +41,103 @@ $system-note-svg-size: 16px;
border: 0;
}
.timeline-avatar {
height: 2rem;
}
&.note-comment,
&.note-skeleton,
.draft-note {
.timeline-avatar {
margin-top: 5px;
}
.timeline-content:not(.flash-container) {
margin-left: 2.5rem;
border: 1px solid $border-color;
border-radius: $gl-border-radius-base;
background-color: $white;
padding: $gl-padding-4 $gl-padding-8;
}
.note-header-info {
min-height: 2rem;
display: flex;
align-items: center;
gap: 0 0.25rem;
flex-wrap: wrap;
}
}
&.note-discussion {
.timeline-content .discussion-wrapper {
background-color: transparent;
}
.timeline-content {
ul li {
&:first-of-type {
.timeline-avatar {
margin-top: 5px;
}
.timeline-content {
margin-left: 2.5rem;
border-left: 1px solid $border-color;
border-right: 1px solid $border-color;
border-top: 1px solid $border-color;
border-top-left-radius: $gl-border-radius-base;
border-top-right-radius: $gl-border-radius-base;
background-color: $white;
padding: $gl-padding-4 $gl-padding-8;
}
}
&:not(:first-of-type) .timeline-entry-inner {
margin-left: 2.5rem;
border-left: 1px solid $border-color;
border-right: 1px solid $border-color;
background-color: $white;
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 $gl-padding;
.timeline-discussion-body {
margin-left: 2rem;
}
}
}
.diff-content {
ul li:first-of-type {
.timeline-avatar {
margin-top: 0;
}
.timeline-content {
margin-left: 0;
border: 0;
padding: 0;
}
.timeline-entry-inner {
margin-left: 2.5rem;
border-left: 1px solid $border-color;
border-right: 1px solid $border-color;
background-color: $white;
padding: $gl-padding-8 $gl-padding-8 $gl-padding-4 $gl-padding;
.timeline-discussion-body {
margin-left: 2rem;
}
}
}
}
}
.discussion-reply-holder {
border: 1px solid $border-color;
}
}
&.note-form {
margin-left: 0;
@ -88,10 +176,14 @@ $system-note-svg-size: 16px;
.card {
margin-bottom: 0;
}
}
.timeline-discussion-body {
margin-top: -$gl-padding-8;
.note-header-info {
min-height: 2rem;
display: flex;
align-items: center;
gap: 0 0.25rem;
flex-wrap: wrap;
}
}
.discussion {
@ -116,16 +208,11 @@ $system-note-svg-size: 16px;
&.being-posted {
pointer-events: none;
opacity: 0.5;
padding: $gl-padding;
.dummy-avatar {
background-color: $gray-100;
border: 1px solid darken($gray-100, 25%);
}
.note-headline-light {
margin-left: 3px;
}
}
.editing-spinner {
@ -156,6 +243,7 @@ $system-note-svg-size: 16px;
.note-edit-form {
display: block;
margin-left: 0;
margin-top: 0.5rem;
&.current-note-edit-form + .note-awards {
display: none;
@ -164,13 +252,17 @@ $system-note-svg-size: 16px;
}
.note-body {
padding: $gl-padding-4 $gl-padding-4 $gl-padding-4 $gl-padding-8;
padding: $gl-padding-8;
overflow-x: auto;
overflow-y: hidden;
.note-text {
word-wrap: break-word;
}
.suggestions {
margin-top: 4px;
}
}
.note-awards {
@ -186,9 +278,10 @@ $system-note-svg-size: 16px;
}
.system-note {
padding: $gl-padding-4 20px;
padding: $gl-padding-8 0;
margin: $gl-padding 0;
background-color: transparent;
font-size: $gl-font-size-sm;
.note-header-info {
padding-bottom: 0;
@ -229,6 +322,15 @@ $system-note-svg-size: 16px;
.note-body {
overflow: hidden;
padding: 0;
ul {
margin: 0.5rem 0;
}
p {
margin-left: 1rem;
}
.description-version {
position: relative;
@ -305,7 +407,7 @@ $system-note-svg-size: 16px;
height: $system-note-icon-size;
border: 1px solid $gray-10;
border-radius: $system-note-icon-size;
margin: -6px 0 0;
margin: -8px 0 0;
svg {
width: $system-note-svg-size;
@ -330,8 +432,9 @@ $system-note-svg-size: 16px;
.discussion-body .diff-file {
.file-title {
cursor: default;
border-top: 1px solid $border-color;
border-top: 0;
border-radius: 0;
margin-left: 2.5rem;
@media (min-width: map-get($grid-breakpoints, md)) {
--initial-top: calc(#{$header-height} + #{$mr-tabs-height});
@ -357,6 +460,40 @@ $system-note-svg-size: 16px;
.line_content {
white-space: pre-wrap;
}
.diff-content {
margin-left: 2.5rem;
&.outdated-lines-wrapper {
margin-left: 0;
}
.line_holder td:first-of-type {
@include gl-border-l;
}
.line_holder td:last-of-type {
@include gl-border-r;
}
.discussion-notes {
margin-left: -2.5rem;
.notes {
background-color: transparent;
}
.notes-content {
border: 0;
}
.timeline-content {
border-top: 0 !important;
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
}
}
}
}
.tab-pane.notes {
@ -394,8 +531,12 @@ $system-note-svg-size: 16px;
}
.system-note {
background-color: $white;
padding: $gl-padding;
background-color: transparent;
padding: 0;
.timeline-icon {
margin-top: -2px;
}
}
}
@ -487,6 +628,19 @@ $system-note-svg-size: 16px;
.code-commit .notes-content,
.diff-viewer > .image ~ .note-container {
background-color: $white;
li.note-comment {
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 $gl-padding;
.avatar {
margin-right: 0;
}
.note-body {
padding: $gl-padding-4 0 $gl-padding-8;
margin-left: 2.5rem;
}
}
}
.diff-viewer > .image ~ .note-container form.new-note {
@ -540,9 +694,21 @@ $system-note-svg-size: 16px;
padding-bottom: 0;
}
.timeline-avatar {
margin-top: 5px;
}
.timeline-content {
overflow-x: auto;
overflow-y: hidden;
border-radius: $gl-border-radius-base;
padding: $gl-padding-8 !important;
@include gl-border;
&.expanded {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
&.note-wrapper {
@ -568,19 +734,10 @@ $system-note-svg-size: 16px;
.note {
@include notes-media('max', map-get($grid-breakpoints, sm) - 1) {
.note-header {
.note-actions {
flex-wrap: wrap;
margin-bottom: $gl-padding-12;
> :first-child {
margin-left: 0;
}
.note-actions > :first-child {
margin-left: 0;
}
}
.note-header-author-name {
display: block;
}
}
}
@ -593,11 +750,6 @@ $system-note-svg-size: 16px;
}
}
.note-header-info,
.note-actions {
padding-bottom: $gl-padding-4;
}
.system-note .note-header-info {
padding-bottom: 0;
}
@ -618,10 +770,6 @@ $system-note-svg-size: 16px;
}
.note-headline-meta {
.system-note-separator {
color: $gray-500;
}
.note-timestamp {
white-space: nowrap;
}
@ -674,7 +822,6 @@ $system-note-svg-size: 16px;
align-items: center;
margin-left: 10px;
color: $gray-400;
margin-top: -4px;
@include notes-media('max', map-get($grid-breakpoints, sm) - 1) {
float: none;
@ -719,7 +866,7 @@ $system-note-svg-size: 16px;
}
.discussion-toggle-button {
padding: 0;
padding: 0 $gl-padding-8 0 0;
background-color: transparent;
border: 0;
line-height: 20px;
@ -868,6 +1015,22 @@ $system-note-svg-size: 16px;
.note-discussion.timeline-entry {
padding-left: 0;
ul.notes li.note-wrapper {
padding: $gl-padding-8 $gl-padding-8 $gl-padding-8 $gl-padding;
}
ul.notes {
li.toggle-replies-widget {
margin-left: 0;
border-left: 0;
border-right: 0;
}
div.discussion-reply-holder {
margin-left: 0;
}
}
&:last-child {
border-bottom: 0;
}

View file

@ -11,6 +11,7 @@ module Ci
include Gitlab::Utils::StrongMemoize
self.table_name = 'ci_builds_metadata'
self.primary_key = 'id'
belongs_to :build, class_name: 'CommitStatus'
belongs_to :project

View file

@ -61,7 +61,8 @@ module Users
namespace_storage_limit_banner_alert_threshold: 57, # EE-only
namespace_storage_limit_banner_error_threshold: 58, # EE-only
project_quality_summary_feedback: 59, # EE-only
merge_request_settings_moved_callout: 60
merge_request_settings_moved_callout: 60,
new_top_level_group_alert: 61
}
validates :feature_name,

View file

@ -197,6 +197,10 @@ class WebHookService
Gitlab::WebHooks::GITLAB_EVENT_HEADER => self.class.hook_to_event(hook_name)
}
if Feature.enabled?(:webhooks_gitlab_instance_header)
headers[Gitlab::WebHooks::GITLAB_INSTANCE_HEADER] = Gitlab.config.gitlab.base_url
end
headers['X-Gitlab-Token'] = Gitlab::Utils.remove_line_breaks(hook.token) if hook.token.present?
headers.merge!(Gitlab::WebHooks::RecursionDetection.header(hook))
end

View file

@ -34,4 +34,4 @@
= f.gitlab_ui_checkbox_component :write_package_registry, 'write_package_registry', help_text: s_('DeployTokens|Allows read and write access to the package registry.'), checkbox_options: { data: { qa_selector: 'deploy_token_write_package_registry_checkbox' } }
.gl-mt-3
= f.submit s_('DeployTokens|Create deploy token'), class: 'btn gl-button btn-confirm', data: { qa_selector: 'create_deploy_token_button' }
= f.submit s_('DeployTokens|Create deploy token'), data: { qa_selector: 'create_deploy_token_button' }, pajamas_button: true

View file

@ -1,4 +1,4 @@
= form_for @label, as: :label, url: url, html: { class: 'label-form js-quick-submit js-requires-input' } do |f|
= gitlab_ui_form_for @label, as: :label, url: url, html: { class: 'label-form js-quick-submit js-requires-input' } do |f|
= form_errors(@label)
.form-group.row
@ -26,9 +26,9 @@
.gl-display-flex.gl-justify-content-space-between
%div
- if @label.persisted?
= f.submit _('Save changes'), class: 'btn gl-button btn-confirm js-save-button gl-mr-2'
= f.submit _('Save changes'), class: 'js-save-button gl-mr-2', pajamas_button: true
- else
= f.submit _('Create label'), class: 'btn gl-button btn-confirm js-save-button gl-mr-2', data: { qa_selector: 'label_create_button' }
= f.submit _('Create label'), class: 'js-save-button gl-mr-2', data: { qa_selector: 'label_create_button' }, pajamas_button: true
= link_to _('Cancel'), back_path, class: 'btn gl-button btn-default btn-cancel gl-mr-2'
- if @label.persisted?
- presented_label = @label.present

View file

@ -6,17 +6,18 @@
- note_counter = local_assigns.fetch(:note_counter, 0)
%li.timeline-entry.note-wrapper{ id: dom_id(note),
class: ["note", "note-row-#{note.id}", ('system-note' if note.system)],
class: ["note", "note-comment", "note-row-#{note.id}", ('system-note' if note.system)],
data: { author_id: note.author.id,
editable: note_editable,
note_id: note.id } }
.timeline-entry-inner
.timeline-icon
- if note.system
- if note.system
.timeline-icon
= icon_for_system_note(note)
- else
- else
.timeline-avatar.gl-float-left
%a.image-diff-avatar-link{ href: user_path(note.author) }
= image_tag avatar_icon_for_user(note.author), alt: '', class: 'avatar s40'
= image_tag avatar_icon_for_user(note.author), alt: '', class: 'avatar s32'
- if note.is_a?(DiffNote) && note.on_image?
- if show_image_comment_badge && note_counter == 0
-# Only show this for the first comment in the discussion
@ -34,9 +35,9 @@
%span.note-header-author-name.bold
= note.author.name
= user_status(note.author)
%span.note-headline-light{ data: { qa_selector: 'note_author_content' } }
%spannote-headline-light{ data: { qa_selector: 'note_author_content' } }
= note.author.to_reference
%span.note-headline-light.note-headline-meta
%span.note-headline-ligh.note-headline-meta
- if note.system
%span.system-note-message
= markdown_field(note, :note)

View file

@ -0,0 +1,8 @@
---
name: webhooks_gitlab_instance_header
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98624
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/375001
milestone: '15.5'
type: development
group: group::integrations
default_enabled: false

View file

@ -0,0 +1,7 @@
---
name: sidekiq_memory_killer_read_only_mode
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98519
milestone: '15.5'
type: ops
group: group::application performance
default_enabled: false

View file

@ -3,7 +3,7 @@ key_path: database.flavor
description: What PostgreSQL flavor is being used. Possible values are
"Amazon Aurora PostgreSQL", "PostgreSQL on Amazon RDS", "Cloud SQL for PostgreSQL",
"Azure Database for PostgreSQL - Single Server", "Azure Database for PostgreSQL - Flexible Server",
or "null".
"AlloyDB for PostgreSQL", or "null".
product_section: enablement
product_stage: enablement
product_group: database

View file

@ -0,0 +1,36 @@
# frozen_string_literal: true
class PrepareCiBuildsMetadataForPartitioningPrimaryKey < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
TABLE_NAME = 'ci_builds_metadata'
PRIMARY_KEY = 'ci_builds_metadata_pkey'
NEW_INDEX_NAME = 'index_ci_builds_metadata_on_id_partition_id_unique'
OLD_INDEX_NAME = 'index_ci_builds_metadata_on_id_unique'
def up
with_lock_retries(raise_on_exhaustion: true) do
execute("ALTER TABLE #{TABLE_NAME} DROP CONSTRAINT #{PRIMARY_KEY} CASCADE")
rename_index(TABLE_NAME, NEW_INDEX_NAME, PRIMARY_KEY)
execute("ALTER TABLE #{TABLE_NAME} ADD CONSTRAINT #{PRIMARY_KEY} " \
"PRIMARY KEY USING INDEX #{PRIMARY_KEY}")
end
end
# rolling back this migration is time consuming with the creation of these two indexes
def down
add_concurrent_index(TABLE_NAME, :id, unique: true, name: OLD_INDEX_NAME)
add_concurrent_index(TABLE_NAME, [:id, :partition_id], unique: true, name: NEW_INDEX_NAME)
with_lock_retries(raise_on_exhaustion: true) do
execute("ALTER TABLE #{TABLE_NAME} DROP CONSTRAINT #{PRIMARY_KEY} CASCADE")
rename_index(TABLE_NAME, OLD_INDEX_NAME, PRIMARY_KEY)
execute("ALTER TABLE #{TABLE_NAME} ADD CONSTRAINT #{PRIMARY_KEY} " \
"PRIMARY KEY USING INDEX #{PRIMARY_KEY}")
end
end
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddIndexOnInternalNotes < Gitlab::Database::Migration[2.0]
INDEX_NAME = 'index_notes_on_id_where_internal'
disable_ddl_transaction!
def up
add_concurrent_index :notes, :id, where: 'internal = true', name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :notes, INDEX_NAME
end
end

View file

@ -0,0 +1 @@
081480492cbe6e631f0357b181a883a2bc7f34566f23f119c0ba4df59ee363d6

View file

@ -0,0 +1 @@
368d6e417d6ac9c4ed3815b67f3247d55a6e4ec8a6e7ac255c7f9f24d3721f59

View file

@ -25045,7 +25045,7 @@ ALTER TABLE ONLY ci_build_trace_metadata
ADD CONSTRAINT ci_build_trace_metadata_pkey PRIMARY KEY (build_id);
ALTER TABLE ONLY ci_builds_metadata
ADD CONSTRAINT ci_builds_metadata_pkey PRIMARY KEY (id);
ADD CONSTRAINT ci_builds_metadata_pkey PRIMARY KEY (id, partition_id);
ALTER TABLE ONLY ci_builds
ADD CONSTRAINT ci_builds_pkey PRIMARY KEY (id);
@ -29502,6 +29502,8 @@ CREATE INDEX index_notes_on_discussion_id ON notes USING btree (discussion_id);
CREATE INDEX index_notes_on_id_where_confidential ON notes USING btree (id) WHERE (confidential = true);
CREATE INDEX index_notes_on_id_where_internal ON notes USING btree (id) WHERE (internal = true);
CREATE INDEX index_notes_on_line_code ON notes USING btree (line_code);
CREATE INDEX index_notes_on_noteable_id_and_noteable_type_and_system ON notes USING btree (noteable_id, noteable_type, system);

View file

@ -21386,6 +21386,7 @@ Name of the feature that the callout is for.
| <a id="usercalloutfeaturenameenumnamespace_storage_limit_banner_error_threshold"></a>`NAMESPACE_STORAGE_LIMIT_BANNER_ERROR_THRESHOLD` | Callout feature name for namespace_storage_limit_banner_error_threshold. |
| <a id="usercalloutfeaturenameenumnamespace_storage_limit_banner_info_threshold"></a>`NAMESPACE_STORAGE_LIMIT_BANNER_INFO_THRESHOLD` | Callout feature name for namespace_storage_limit_banner_info_threshold. |
| <a id="usercalloutfeaturenameenumnamespace_storage_limit_banner_warning_threshold"></a>`NAMESPACE_STORAGE_LIMIT_BANNER_WARNING_THRESHOLD` | Callout feature name for namespace_storage_limit_banner_warning_threshold. |
| <a id="usercalloutfeaturenameenumnew_top_level_group_alert"></a>`NEW_TOP_LEVEL_GROUP_ALERT` | Callout feature name for new_top_level_group_alert. |
| <a id="usercalloutfeaturenameenumnew_user_signups_cap_reached"></a>`NEW_USER_SIGNUPS_CAP_REACHED` | Callout feature name for new_user_signups_cap_reached. |
| <a id="usercalloutfeaturenameenumpersonal_access_token_expiry"></a>`PERSONAL_ACCESS_TOKEN_EXPIRY` | Callout feature name for personal_access_token_expiry. |
| <a id="usercalloutfeaturenameenumpersonal_project_limitations_banner"></a>`PERSONAL_PROJECT_LIMITATIONS_BANNER` | Callout feature name for personal_project_limitations_banner. |

View file

@ -1,7 +1,7 @@
---
stage: Create
group: Source Code
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
stage: Data Stores
group: Global Search
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Search API **(FREE)**
@ -10,28 +10,30 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Every API call to search must be authenticated.
## Additional scopes **(PREMIUM)**
Additional scopes are available for the [Advanced Search API](#advanced-search-api)
and [Group Search API](#group-search-api) if
[Elasticsearch is enabled](../integration/advanced_search/elasticsearch.md):
`blobs`, `commits`, `notes`, `wiki_blobs`.
## Advanced Search API
Search globally across the GitLab instance.
Search for an expression globally across the GitLab instance, in a specified scope.
The response depends on the requested scope.
```plaintext
GET /search
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------|
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
| `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. |
| `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. |
| `order_by` | string | no | Allowed values are `created_at` only. If this is not set, the results are either sorted by `created_at` in descending order for basic search, or by the most relevant documents when using advanced search.|
| `sort` | string | no | Allowed values are `asc` or `desc` only. If this is not set, the results are either sorted by `created_at` in descending order for basic search, or by the most relevant documents when using advanced search.|
Search the expression in the specified scope. These scopes are supported: `projects`, `issues`, `merge_requests`, `milestones`, `snippet_titles`, `users`.
If Elasticsearch is enabled additional scopes available are `blobs`, `wiki_blobs`, `notes`, and `commits`. Find more about [the feature](../integration/advanced_search/elasticsearch.md). **(PREMIUM)**
The response depends on the requested scope.
| Attribute | Type | Required | Description |
| ------------- | -------- | ---------- | ------------|
| `scope` | string | Yes | The scope to search in. Values include `projects`, `issues`, `merge_requests`, `milestones`, `snippet_titles`, `users`. [Additional scopes](#additional-scopes): `blobs`, `commits`, `notes`, `wiki_blobs`. |
| `search` | string | Yes | The search query. |
| `confidential` | boolean | No | Filter by confidentiality. Supports `issues` scope; other scopes are ignored. |
| `order_by` | string | No | Allowed values are `created_at` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for Advanced Search.|
| `sort` | string | No | Allowed values are `asc` or `desc` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for Advanced Search.|
| `state` | string | No | Filter by state. Supports `issues` and `merge_requests` scopes; other scopes are ignored. |
### Scope: `projects`
@ -129,7 +131,7 @@ Example response:
```
NOTE:
`assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
The `assignee` column is deprecated. It is shown as a single-sized array `assignees` to conform to the GitLab EE API.
### Scope: `merge_requests`
@ -432,27 +434,23 @@ Example response:
## Group Search API
Search in the specified group.
Search for an expression in the specified group.
If a user is not a member of a group and the group is private, a `GET` request on that group results in a `404` status code.
If a user is not a member of a group and the group is private, a `GET` request on that group results in a `404 Not Found` status code.
```plaintext
GET /groups/:id/search
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) owned by the authenticated user |
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
| `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. |
| `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. |
| `order_by` | string | no | Allowed values are `created_at` only. If this is not set, the results are either sorted by `created_at` in descending order for basic search, or by the most relevant documents when using advanced search.|
| `sort` | string | no | Allowed values are `asc` or `desc` only. If this is not set, the results are either sorted by `created_at` in descending order for basic search, or by the most relevant documents when using advanced search.|
Search the expression in the specified scope. These scopes are supported: `projects`, `issues`, `merge_requests`, `milestones`, `users`.
If Elasticsearch is enabled additional scopes available are `blobs`, `wiki_blobs`, `notes`, and `commits`. Find more about [the feature](../integration/advanced_search/elasticsearch.md). **(PREMIUM)**
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | -------------|
| `id` | integer or string | Yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `scope` | string | Yes | The scope to search in. Values include `issues`, `merge_requests`, `milestones`, `projects`, `users`. [Additional scopes](#additional-scopes): `blobs`, `commits`, `notes`, `wiki_blobs`. |
| `search` | string | Yes | The search query. |
| `confidential` | boolean | No | Filter by confidentiality. Supports only `issues` scope; other scopes are ignored. |
| `order_by` | string | No | Allowed values are `created_at` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for Advanced Search.|
| `sort` | string | No | Allowed values are `asc` or `desc` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for Advanced Search.|
| `state` | string | No | Filter by state. Supports `issues` and `merge_requests` only; other scopes are ignored. |
The response depends on the requested scope.
@ -824,7 +822,7 @@ Example response:
## Project Search API
Search in the specified project.
Search for an expression in the specified project.
If a user is not a member of a project and the project is private, a `GET` request on that project results in a `404` status code.
@ -832,18 +830,16 @@ If a user is not a member of a project and the project is private, a `GET` reque
GET /projects/:id/search
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ---------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user |
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
| `ref` | string | no | The name of a repository branch or tag to search on. The project's default branch is used by default. This is only applicable for scopes: `commits`, `blobs`, and `wiki_blobs`. |
| `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. |
| `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. |
| `order_by` | string | no | Allowed values are `created_at` only. If this is not set, the results are either sorted by `created_at` in descending order for basic search, or by the most relevant documents when using advanced search.|
| `sort` | string | no | Allowed values are `asc` or `desc` only. If this is not set, the results are either sorted by `created_at` in descending order for basic search, or by the most relevant documents when using advanced search.|
Search the expression in the specified scope. These scopes are supported: `issues`, `merge_requests`, `milestones`, `notes`, `wiki_blobs`, `commits`, `blobs`, `users`.
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ------------|
| `id` | integer or string | Yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
| `scope` | string | Yes | The scope to search in. Values include `blobs`, `commits`, `issues`, `merge_requests`, `milestones`, `notes`, `users`, and `wiki_blobs`. |
| `search` | string | Yes | The search query. |
| `confidential` | boolean | No | Filter by confidentiality. Supports `issues` scope; other scopes are ignored. |
| `ref` | string | No | The name of a repository branch or tag to search on. The project's default branch is used by default. Applicable only for scopes `blobs`, `commits`, and `wiki_blobs`. |
| `order_by` | string | No | Allowed values are `created_at` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for Advanced Search.|
| `sort` | string | No | Allowed values are `asc` or `desc` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for Advanced Search.|
| `state` | string | No | Filter by state. Supports the `issues` and `merge_requests` scopes; other scopes are ignored. |
The response depends on the requested scope.
@ -1144,9 +1140,9 @@ This scope is available only if [Elasticsearch](../integration/advanced_search/e
Filters are available for this scope:
- filename
- path
- extension
- Filename
- Path
- Extension
To use a filter, include it in your query. For example: `a query filename:some_name*`.
You may use wildcards (`*`) to use glob matching.

View file

@ -234,6 +234,23 @@ Image URLs are not rewritten if:
For more information about supported events for Webhooks, go to [Webhook events](webhook_events.md).
## Delivery headers
> `X-Gitlab-Instance` header introduced in GitLab 15.5 [with a flag](../../../administration/feature_flags.md) named `webhooks_gitlab_instance_header`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available,
ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `webhooks_gitlab_instance_header`.
The feature is not ready for production use.
Webhook requests to your endpoint include the following headers:
| Header | Description | Example |
| ------ | ------ | ------ |
| `User-Agent` | In the format `"Gitlab/<VERSION>"`. | `"GitLab/15.5.0-pre"` |
| `X-Gitlab-Event` | Name of the webhook type. Corresponds to [event types](webhook_events.md) but in the format `"<EVENT> Hook"`. | `"Push Hook"` |
| `X-Gitlab-Instance` | Hostname of the GitLab instance that sent the webhook. | `"https://gitlab.com"` |
## Troubleshoot webhooks
> **Recent events** for group webhooks [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/325642) in GitLab 15.3.

View file

@ -124,7 +124,11 @@ module Gitlab
# - https://docs.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-servers
# - https://docs.microsoft.com/en-us/azure/postgresql/concepts-servers#managing-your-server
# this database is present on both Flexible and Single server, so we should check the former first.
'Azure Database for PostgreSQL - Single Server' => { statement: "SELECT datname FROM pg_database WHERE datname = 'azure_maintenance'" }
'Azure Database for PostgreSQL - Single Server' => { statement: "SELECT datname FROM pg_database WHERE datname = 'azure_maintenance'" },
# Based on
# - https://cloud.google.com/sql/docs/postgres/flags
# running a query to detect flag names that begin with 'alloydb
'AlloyDB for PostgreSQL' => { statement: "SELECT name FROM pg_settings WHERE name LIKE 'alloydb%'" }
}.each do |flavor, conditions|
return flavor if connection.execute(conditions[:statement]).to_a.present?
rescue ActiveRecord::StatementInvalid => e

View file

@ -107,6 +107,8 @@ module Gitlab
end
def restart_sidekiq
return if Feature.enabled?(:sidekiq_memory_killer_read_only_mode, type: :ops)
# Tell Sidekiq to stop fetching new jobs
# We first SIGNAL and then wait given time
# We also monitor a number of running jobs and allow to restart early

View file

@ -3,5 +3,6 @@
module Gitlab
module WebHooks
GITLAB_EVENT_HEADER = 'X-Gitlab-Event'
GITLAB_INSTANCE_HEADER = 'X-Gitlab-Instance'
end
end

View file

@ -18703,6 +18703,9 @@ msgstr ""
msgid "GroupRoadmap|Loading epics"
msgstr ""
msgid "GroupRoadmap|New epic"
msgstr ""
msgid "GroupRoadmap|No start and end date"
msgstr ""
@ -18739,6 +18742,9 @@ msgstr ""
msgid "GroupRoadmap|To widen your search, change or remove filters; from %{startDate} to %{endDate}."
msgstr ""
msgid "GroupRoadmap|View epics list"
msgstr ""
msgid "GroupRoadmap|Within 3 years"
msgstr ""
@ -19300,6 +19306,12 @@ msgstr ""
msgid "Groups|Learn more"
msgstr ""
msgid "Groups|Learn more about subgroups"
msgstr ""
msgid "Groups|Members, projects, trials, and paid subscriptions are tied to a specific top-level group. If you are already a member of a top-level group, you can create a subgroup so your new work is part of your existing top-level group. Do you want to create a subgroup instead?"
msgstr ""
msgid "Groups|Must start with letter, digit, emoji, or underscore. Can also contain periods, dashes, spaces, and parentheses."
msgstr ""
@ -19318,6 +19330,9 @@ msgstr ""
msgid "Groups|Subgroup slug"
msgstr ""
msgid "Groups|You're creating a new top-level group"
msgstr ""
msgid "Guideline"
msgstr ""
@ -43932,9 +43947,6 @@ msgstr ""
msgid "View entire blame"
msgstr ""
msgid "View epics list"
msgstr ""
msgid "View exposed artifact"
msgid_plural "View %d exposed artifacts"
msgstr[0] ""

View file

@ -52,8 +52,8 @@
"@codesandbox/sandpack-client": "^1.2.2",
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "3.3.0",
"@gitlab/ui": "43.17.1",
"@gitlab/svgs": "3.4.0",
"@gitlab/ui": "43.18.0",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20220815034418",
"@rails/actioncable": "6.1.4-7",

32
scripts/build_qa_image Executable file
View file

@ -0,0 +1,32 @@
#!/bin/sh
QA_IMAGE_NAME="gitlab-ee-qa"
if [ "${CI_PROJECT_NAME}" == "gitlabhq" ] || [ "${CI_PROJECT_NAME}" == "gitlab-foss" ]; then
QA_IMAGE_NAME="gitlab-ce-qa"
fi
# Tag with commit SHA by default
QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/${QA_IMAGE_NAME}:${CI_COMMIT_SHA}"
# For branches, tag with slugified branch name. For tags, use the tag directly
QA_IMAGE_BRANCH="${CI_REGISTRY}/${CI_PROJECT_PATH}/${QA_IMAGE_NAME}:${CI_COMMIT_TAG:-$CI_COMMIT_REF_SLUG}"
DESTINATIONS="--destination=${QA_IMAGE} --destination=${QA_IMAGE_BRANCH}"
# Auto-deploy tag format uses first 12 letters of commit SHA. Tag with that
# reference also for EE images.
if [ "${QA_IMAGE_NAME}" == "gitlab-ee-qa" ]; then
QA_IMAGE_FOR_AUTO_DEPLOY="${CI_REGISTRY}/${CI_PROJECT_PATH}/${QA_IMAGE_NAME}:${CI_COMMIT_SHA:0:11}"
DESTINATIONS="${DESTINATIONS} --destination=$QA_IMAGE_FOR_AUTO_DEPLOY"
fi
echo "Building QA image for destinations: ${DESTINATIONS}"
/kaniko/executor \
--context="${CI_PROJECT_DIR}" \
--dockerfile="${CI_PROJECT_DIR}/qa/Dockerfile" \
--build-arg=CHROME_VERSION="${CHROME_VERSION}" \
--build-arg=DOCKER_VERSION="${DOCKER_VERSION}" \
--build-arg=QA_BUILD_TARGET="${QA_BUILD_TARGET:-qa}" \
--cache=true \
${DESTINATIONS}

View file

@ -0,0 +1,32 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'New group page', :js do
let(:user) { create(:user) }
let(:group) { create(:group) }
before do
sign_in(user)
end
describe 'new top level group alert' do
context 'when a user visits the new group page' do
it 'shows the new top level group alert' do
visit new_group_path(anchor: 'create-group-pane')
expect(page).to have_selector('[data-testid="new-top-level-alert"]')
end
end
context 'when a user visits the new sub group page' do
let(:parent_group) { create(:group) }
it 'does not show the new top level group alert' do
visit new_group_path(parent_id: parent_group.id, anchor: 'create-group-pane')
expect(page).not_to have_selector('[data-testid="new-top-level-alert"]')
end
end
end
end

View file

@ -0,0 +1,75 @@
import { shallowMount } from '@vue/test-utils';
import NewTopLevelGroupAlert from '~/groups/components/new_top_level_group_alert.vue';
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
import { helpPagePath } from '~/helpers/help_page_helper';
describe('NewTopLevelGroupAlert', () => {
let wrapper;
let userCalloutDismissSpy;
const findAlert = () => wrapper.findComponent({ ref: 'newTopLevelAlert' });
const createSubGroupPath = '/groups/new?parent_id=1#create-group-pane';
const createComponent = ({ shouldShowCallout = true } = {}) => {
userCalloutDismissSpy = jest.fn();
wrapper = shallowMount(NewTopLevelGroupAlert, {
provide: {
createSubGroupPath,
},
stubs: {
UserCalloutDismisser: makeMockUserCalloutDismisser({
dismiss: userCalloutDismissSpy,
shouldShowCallout,
}),
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('when the component is created', () => {
beforeEach(() => {
createComponent({
shouldShowCallout: true,
});
});
it('renders a button with a link to create a new sub-group', () => {
expect(findAlert().props('primaryButtonText')).toBe(
NewTopLevelGroupAlert.i18n.primaryBtnText,
);
expect(findAlert().props('primaryButtonLink')).toBe(
helpPagePath('user/group/subgroups/index'),
);
});
});
describe('dismissing the alert', () => {
beforeEach(() => {
findAlert().vm.$emit('dismiss');
});
it('calls the dismiss callback', () => {
expect(userCalloutDismissSpy).toHaveBeenCalled();
});
});
describe('when the alert has been dismissed', () => {
beforeEach(() => {
createComponent({
shouldShowCallout: false,
});
});
it('does not show the alert', () => {
expect(findAlert().exists()).toBe(false);
});
});
});

View file

@ -3,15 +3,15 @@
exports[`note_app when sort direction is asc shows skeleton notes after the loaded discussions 1`] = `
"<ul id=\\"notes-list\\" class=\\"notes main-notes-list timeline\\">
<noteable-discussion-stub discussion=\\"[object Object]\\" renderdifffile=\\"true\\" helppagepath=\\"\\" isoverviewtab=\\"true\\"></noteable-discussion-stub>
<skeleton-loading-container-stub></skeleton-loading-container-stub>
<discussion-filter-note-stub style=\\"display: none;\\"></discussion-filter-note-stub>
<skeleton-loading-container-stub class=\\"note-skeleton\\"></skeleton-loading-container-stub>
<!---->
</ul>"
`;
exports[`note_app when sort direction is desc shows skeleton notes before the loaded discussions 1`] = `
"<ul id=\\"notes-list\\" class=\\"notes main-notes-list timeline\\">
<skeleton-loading-container-stub></skeleton-loading-container-stub>
<skeleton-loading-container-stub class=\\"note-skeleton\\"></skeleton-loading-container-stub>
<noteable-discussion-stub discussion=\\"[object Object]\\" renderdifffile=\\"true\\" helppagepath=\\"\\" isoverviewtab=\\"true\\"></noteable-discussion-stub>
<discussion-filter-note-stub style=\\"display: none;\\"></discussion-filter-note-stub>
<!---->
</ul>"
`;

View file

@ -42,7 +42,7 @@ describe('diff_discussion_header component', () => {
expect(props).toMatchObject({
src: firstNoteAuthor.avatar_url,
alt: firstNoteAuthor.name,
size: { default: 24, md: 32 },
size: 32,
});
});
});

View file

@ -149,7 +149,7 @@ describe('NoteHeader component', () => {
it('renders busy status if author availability is set', () => {
createComponent({ author: { ...author, availability: AVAILABILITY_STATUS.BUSY } });
expect(wrapper.find('.js-user-link').text()).toContain('(Busy)');
expect(wrapper.find('.note-header-info').text()).toContain('(Busy)');
});
it('renders author status', () => {

View file

@ -214,7 +214,7 @@ describe('issue_note', () => {
expect(avatarProps.src).toBe(author.avatar_url);
expect(avatarProps.entityName).toBe(author.username);
expect(avatarProps.alt).toBe(author.name);
expect(avatarProps.size).toEqual({ default: 24, md: 32 });
expect(avatarProps.size).toEqual(32);
});
it('should render note header content', () => {

View file

@ -2,13 +2,12 @@
exports[`Issue placeholder note component matches snapshot 1`] = `
<timeline-entry-item-stub
class="note note-wrapper being-posted fade-in-half"
class="note note-wrapper note-comment being-posted fade-in-half"
>
<div
class="timeline-icon"
class="timeline-avatar gl-float-left"
>
<gl-avatar-link-stub
class="gl-mr-3"
href="/root"
>
<gl-avatar-stub
@ -16,7 +15,7 @@ exports[`Issue placeholder note component matches snapshot 1`] = `
entityid="0"
entityname="root"
shape="circle"
size="[object Object]"
size="32"
src="mock_path"
/>
</gl-avatar-link-stub>
@ -50,16 +49,20 @@ exports[`Issue placeholder note component matches snapshot 1`] = `
</div>
<div
class="note-body"
class="timeline-discussion-body"
>
<div
class="note-text md"
class="note-body"
>
<p>
Foo
</p>
<div
class="note-text md"
>
<p>
Foo
</p>
</div>
</div>
</div>
</div>

View file

@ -1,5 +1,4 @@
import { shallowMount } from '@vue/test-utils';
import { GlAvatar } from '@gitlab/ui';
import Vue from 'vue';
import Vuex from 'vuex';
import IssuePlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue';
@ -53,17 +52,4 @@ describe('Issue placeholder note component', () => {
expect(findNote().classes()).toContain('discussion');
});
describe('avatar size', () => {
it.each`
size | line | isOverviewTab
${{ default: 24, md: 32 }} | ${null} | ${false}
${24} | ${{ line_code: '123' }} | ${false}
${{ default: 24, md: 32 }} | ${{ line_code: '123' }} | ${true}
`('renders avatar $size for $line and $isOverviewTab', ({ size, line, isOverviewTab }) => {
createComponent(false, { line, isOverviewTab });
expect(wrapper.findComponent(GlAvatar).props('size')).toEqual(size);
});
});
});

View file

@ -2,7 +2,7 @@
exports[`History Item renders the correct markup 1`] = `
<li
class="timeline-entry system-note note-wrapper gl-mb-6!"
class="timeline-entry system-note note-wrapper"
>
<div
class="timeline-entry-inner"
@ -22,11 +22,13 @@ exports[`History Item renders the correct markup 1`] = `
<div
class="note-header"
>
<span>
<div
class="note-header-info"
>
<div
data-testid="default-slot"
/>
</span>
</div>
</div>
<div

View file

@ -157,6 +157,12 @@ describe('WorkItemAssignees component', () => {
expect(findTokenSelector().props('viewOnly')).toBe(true);
});
it('has a label', () => {
createComponent();
expect(findTokenSelector().props('ariaLabelledby')).toContain('assignees-title-');
});
describe('when clicking outside the token selector', () => {
function arrange(args) {
createComponent(args);

View file

@ -60,6 +60,12 @@ describe('WorkItemLabels component', () => {
wrapper.destroy();
});
it('has a label', () => {
createComponent();
expect(findTokenSelector().props('ariaLabelledby')).toContain('labels-title-');
});
it('focuses token selector on token selector input event', async () => {
createComponent();
findTokenSelector().vm.$emit('input', [mockLabels[0]]);

View file

@ -51,7 +51,7 @@ describe('Work Item type component', () => {
});
it('renders the icon in gray color', () => {
expect(findIcon().classes()).toContain('gl-text-gray-500');
expect(findIcon().classes()).toContain('gl-text-secondary');
});
it('shows tooltip on hover when props passed', () => {

View file

@ -314,6 +314,12 @@ RSpec.describe Gitlab::Database::Reflection do
expect(database.flavor).to eq('Azure Database for PostgreSQL - Single Server')
end
it 'recognizes AlloyDB for PostgreSQL' do
stub_statements("SELECT name FROM pg_settings WHERE name LIKE 'alloydb%'")
expect(database.flavor).to eq('AlloyDB for PostgreSQL')
end
it 'returns nil if can not recognize the flavor' do
expect(database.flavor).to be_nil
end

View file

@ -235,40 +235,56 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
subject { memory_killer.send(:restart_sidekiq) }
before do
stub_const("#{described_class}::SHUTDOWN_TIMEOUT_SECONDS", shutdown_timeout_seconds)
allow(Sidekiq).to receive(:options).and_return(timeout: 9)
allow(memory_killer).to receive(:get_rss).and_return(100)
allow(memory_killer).to receive(:get_soft_limit_rss).and_return(200)
allow(memory_killer).to receive(:get_hard_limit_rss).and_return(300)
context 'when sidekiq_memory_killer_read_only_mode is enabled' do
before do
stub_feature_flags(sidekiq_memory_killer_read_only_mode: true)
end
it 'does not send signal' do
expect(memory_killer).not_to receive(:refresh_state)
expect(memory_killer).not_to receive(:signal_and_wait)
subject
end
end
it 'send signal' do
expect(memory_killer).to receive(:refresh_state)
.with(:stop_fetching_new_jobs)
.ordered
.and_call_original
expect(memory_killer).to receive(:signal_and_wait)
.with(shutdown_timeout_seconds, 'SIGTSTP', 'stop fetching new jobs')
.ordered
context 'when sidekiq_memory_killer_read_only_mode is disabled' do
before do
stub_const("#{described_class}::SHUTDOWN_TIMEOUT_SECONDS", shutdown_timeout_seconds)
stub_feature_flags(sidekiq_memory_killer_read_only_mode: false)
allow(Sidekiq).to receive(:options).and_return(timeout: 9)
allow(memory_killer).to receive(:get_rss).and_return(100)
allow(memory_killer).to receive(:get_soft_limit_rss).and_return(200)
allow(memory_killer).to receive(:get_hard_limit_rss).and_return(300)
end
expect(memory_killer).to receive(:refresh_state)
.with(:shutting_down)
.ordered
.and_call_original
expect(memory_killer).to receive(:signal_and_wait)
.with(11, 'SIGTERM', 'gracefully shut down')
.ordered
it 'send signal' do
expect(memory_killer).to receive(:refresh_state)
.with(:stop_fetching_new_jobs)
.ordered
.and_call_original
expect(memory_killer).to receive(:signal_and_wait)
.with(shutdown_timeout_seconds, 'SIGTSTP', 'stop fetching new jobs')
.ordered
expect(memory_killer).to receive(:refresh_state)
.with(:killing_sidekiq)
.ordered
.and_call_original
expect(memory_killer).to receive(:signal_pgroup)
.with('SIGKILL', 'die')
.ordered
expect(memory_killer).to receive(:refresh_state)
.with(:shutting_down)
.ordered
.and_call_original
expect(memory_killer).to receive(:signal_and_wait)
.with(11, 'SIGTERM', 'gracefully shut down')
.ordered
subject
expect(memory_killer).to receive(:refresh_state)
.with(:killing_sidekiq)
.ordered
.and_call_original
expect(memory_killer).to receive(:signal_pgroup)
.with('SIGKILL', 'die')
.ordered
subject
end
end
end

View file

@ -75,7 +75,8 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
'Content-Type' => 'application/json',
'User-Agent' => "GitLab/#{Gitlab::VERSION}",
'X-Gitlab-Event' => 'Push Hook',
'X-Gitlab-Event-UUID' => uuid
'X-Gitlab-Event-UUID' => uuid,
'X-Gitlab-Instance' => Gitlab.config.gitlab.base_url
}
end
@ -164,7 +165,7 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
end
end
it 'POSTs the data as JSON' do
it 'POSTs the data as JSON and returns expected headers' do
stub_full_request(project_hook.url, method: :post)
service_instance.execute
@ -174,6 +175,22 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
).once
end
context 'when webhooks_gitlab_instance_header flag is disabled' do
before do
stub_feature_flags(webhooks_gitlab_instance_header: false)
end
it 'excludes the X-Gitlab-Instance header' do
stub_full_request(project_hook.url, method: :post)
service_instance.execute
expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)).with(
headers: headers.except('X-Gitlab-Instance')
).once
end
end
context 'when the data is a Gitlab::DataBuilder::Pipeline' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:data) { ::Gitlab::DataBuilder::Pipeline.new(pipeline) }

View file

@ -1059,15 +1059,15 @@
stylelint-declaration-strict-value "1.8.0"
stylelint-scss "4.2.0"
"@gitlab/svgs@3.3.0":
version "3.3.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.3.0.tgz#99b044484fcf3d5a6431281e320e2405540ff5a9"
integrity sha512-S8Hqf+ms8aNrSgmci9SVoIyj/0qQnizU5uV5vUPAOwiufMDFDyI5qfcgn4EYZ6mnju3LiO+ReSL/PPTD4qNgHA==
"@gitlab/svgs@3.4.0":
version "3.4.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.4.0.tgz#cfc8319e259e5914ad0f48ee0ab6e0eec75d03da"
integrity sha512-myCYbjViOI2k6oHGRqL1iKaMKbYvPqWL6tYZ07QkUKziVz5kYjECWk5c0Qp6yu9NsFAMWuow5PkR3oFTGBHmbg==
"@gitlab/ui@43.17.1":
version "43.17.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-43.17.1.tgz#0b667f1f2dd8010d0a178d0b231641ab5a1ff7dd"
integrity sha512-/vHNW6WbznJM19ti+1Li88+MIXUFnNjAoJjmxXV36h93BNMSzTnUSQi6mGmMbklOkSbHMsS6ilyA9hh63ggsoA==
"@gitlab/ui@43.18.0":
version "43.18.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-43.18.0.tgz#7c412eebe362052dae811502586e1fd100c28be0"
integrity sha512-hAyBjYQtOFbVWt4XhUBgth/OtNg2cTQ7KMv0JSLLTnvrrrASJu0Li1ROaat81YsT+kGWytM/Fgr283ISNzy/vw==
dependencies:
"@popperjs/core" "^2.11.2"
bootstrap-vue "2.20.1"