Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
072dbf4b87
commit
468bcfb9c6
|
|
@ -1 +1 @@
|
|||
08c42adfc68c06661cf52363dacf52e2c347adff
|
||||
87a19146fdc01d0132a278b661017705514207cf
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -310,7 +310,7 @@ gem 'pg_query', '~> 2.1'
|
|||
gem 'premailer-rails', '~> 1.10.3'
|
||||
|
||||
# LabKit: Tracing and Correlation
|
||||
gem 'gitlab-labkit', '~> 0.21.3'
|
||||
gem 'gitlab-labkit', '~> 0.22.0'
|
||||
# Thrift is a dependency of gitlab-labkit, we want a version higher than 0.14.0
|
||||
# because of https://gitlab.com/gitlab-org/gitlab/-/issues/321900
|
||||
gem 'thrift', '>= 0.14.0'
|
||||
|
|
|
|||
|
|
@ -470,11 +470,11 @@ GEM
|
|||
fog-json (~> 1.2.0)
|
||||
mime-types
|
||||
ms_rest_azure (~> 0.12.0)
|
||||
gitlab-labkit (0.21.3)
|
||||
gitlab-labkit (0.22.0)
|
||||
actionpack (>= 5.0.0, < 7.0.0)
|
||||
activesupport (>= 5.0.0, < 7.0.0)
|
||||
grpc (>= 1.37)
|
||||
jaeger-client (~> 1.1)
|
||||
jaeger-client (~> 1.1.0)
|
||||
opentracing (~> 0.4)
|
||||
pg_query (~> 2.1)
|
||||
redis (> 3.0.0, < 5.0.0)
|
||||
|
|
@ -1470,7 +1470,7 @@ DEPENDENCIES
|
|||
gitlab-dangerfiles (~> 2.8.0)
|
||||
gitlab-experiment (~> 0.7.0)
|
||||
gitlab-fog-azure-rm (~> 1.2.0)
|
||||
gitlab-labkit (~> 0.21.3)
|
||||
gitlab-labkit (~> 0.22.0)
|
||||
gitlab-license (~> 2.1.0)
|
||||
gitlab-license_finder (~> 6.0)
|
||||
gitlab-mail_room (~> 0.0.9)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import Store from 'ee_else_ce/sidebar/stores/sidebar_store';
|
||||
import Store from '~/sidebar/stores/sidebar_store';
|
||||
import createFlash from '~/flash';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import toast from '~/vue_shared/plugins/global_toast';
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
<script>
|
||||
import { GlDatepicker } from '@gitlab/ui';
|
||||
import { pikadayToString } from '~/lib/utils/datetime_utility';
|
||||
|
||||
export default {
|
||||
name: 'DatePicker',
|
||||
components: {
|
||||
GlDatepicker,
|
||||
},
|
||||
props: {
|
||||
selectedDate: {
|
||||
type: Date,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
minDate: {
|
||||
type: Date,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
maxDate: {
|
||||
type: Date,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
selected(date) {
|
||||
this.$emit('newDateSelected', pikadayToString(date));
|
||||
},
|
||||
toggled() {
|
||||
this.$emit('hidePicker');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-datepicker
|
||||
:value="selectedDate"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
start-opened
|
||||
@close="toggled"
|
||||
@click="toggled"
|
||||
@input="selected"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
<script>
|
||||
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
name: 'CollapsedCalendarIcon',
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
components: {
|
||||
GlIcon,
|
||||
},
|
||||
props: {
|
||||
containerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
tooltipText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.$emit('click');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-gl-tooltip.left.viewport="tooltipText" :class="containerClass" @click="click">
|
||||
<gl-icon v-if="showIcon" name="calendar" />
|
||||
<slot>
|
||||
<span> {{ text }} </span>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
<script>
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import { dateInWords } from '../../../lib/utils/datetime_utility';
|
||||
import datePicker from '../pikaday.vue';
|
||||
import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
|
||||
import toggleSidebar from './toggle_sidebar.vue';
|
||||
|
||||
export default {
|
||||
name: 'SidebarDatePicker',
|
||||
components: {
|
||||
datePicker,
|
||||
toggleSidebar,
|
||||
collapsedCalendarIcon,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
blockClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
showToggleSidebar: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: __('Date picker'),
|
||||
},
|
||||
selectedDate: {
|
||||
type: Date,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
minDate: {
|
||||
type: Date,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
maxDate: {
|
||||
type: Date,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editing: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedAndEditable() {
|
||||
return this.selectedDate && this.editable;
|
||||
},
|
||||
selectedDateWords() {
|
||||
return dateInWords(this.selectedDate, true);
|
||||
},
|
||||
collapsedText() {
|
||||
return this.selectedDateWords ? this.selectedDateWords : __('None');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
stopEditing() {
|
||||
this.editing = false;
|
||||
},
|
||||
toggleDatePicker() {
|
||||
this.editing = !this.editing;
|
||||
},
|
||||
newDateSelected(date = null) {
|
||||
this.date = date;
|
||||
this.editing = false;
|
||||
this.$emit('saveDate', date);
|
||||
},
|
||||
toggleSidebar() {
|
||||
this.$emit('toggleCollapse');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="blockClass" class="block">
|
||||
<div class="issuable-sidebar-header">
|
||||
<toggle-sidebar :collapsed="collapsed" @toggle="toggleSidebar" />
|
||||
</div>
|
||||
<collapsed-calendar-icon :text="collapsedText" class="sidebar-collapsed-icon" />
|
||||
<div class="title">
|
||||
{{ label }}
|
||||
<gl-loading-icon v-if="isLoading" size="sm" :inline="true" />
|
||||
<div class="float-right">
|
||||
<button
|
||||
v-if="editable && !editing"
|
||||
type="button"
|
||||
class="btn-blank btn-link btn-primary-hover-link btn-sidebar-action"
|
||||
@click="toggleDatePicker"
|
||||
>
|
||||
{{ __('Edit') }}
|
||||
</button>
|
||||
<toggle-sidebar v-if="showToggleSidebar" :collapsed="collapsed" @toggle="toggleSidebar" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="value">
|
||||
<date-picker
|
||||
v-if="editing"
|
||||
:selected-date="selectedDate"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:label="label"
|
||||
@newDateSelected="newDateSelected"
|
||||
@hidePicker="stopEditing"
|
||||
/>
|
||||
<span v-else class="value-content">
|
||||
<template v-if="selectedDate">
|
||||
<strong>{{ selectedDateWords }}</strong>
|
||||
<span v-if="selectedAndEditable" class="no-value">
|
||||
-
|
||||
<button
|
||||
type="button"
|
||||
class="btn-blank btn-link btn-secondary-hover-link"
|
||||
@click="newDateSelected(null)"
|
||||
>
|
||||
{{ __('remove') }}
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
<span v-else class="no-value">{{ __('None') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownForm, GlDropdownDivider } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdownForm,
|
||||
GlDropdown,
|
||||
GlDropdownDivider,
|
||||
},
|
||||
props: {
|
||||
headerText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-dropdown class="show" :text="text" @toggle="$emit('toggle')">
|
||||
<template #header>
|
||||
<p class="gl-font-weight-bold gl-text-center gl-mt-2 gl-mb-4">{{ headerText }}</p>
|
||||
<gl-dropdown-divider />
|
||||
<slot name="search"></slot>
|
||||
</template>
|
||||
<gl-dropdown-form>
|
||||
<slot name="items"></slot>
|
||||
</gl-dropdown-form>
|
||||
<template #footer>
|
||||
<slot name="footer"></slot>
|
||||
</template>
|
||||
</gl-dropdown>
|
||||
</template>
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
colors: {
|
||||
type: Array,
|
||||
required: true,
|
||||
validator(value) {
|
||||
return value.length === 2;
|
||||
},
|
||||
},
|
||||
opacity: {
|
||||
type: Array,
|
||||
required: true,
|
||||
validator(value) {
|
||||
return value.length === 2;
|
||||
},
|
||||
},
|
||||
identifierName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<svg height="0" width="0">
|
||||
<defs>
|
||||
<linearGradient :id="identifierName">
|
||||
<stop :stop-color="colors[0]" :stop-opacity="opacity[0]" offset="0%" />
|
||||
<stop :stop-color="colors[1]" :stop-opacity="opacity[1]" offset="100%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -366,29 +366,6 @@
|
|||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&.btn-secondary-hover-link,
|
||||
&.btn-default-hover-link {
|
||||
color: $gl-text-color-secondary;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $blue-600;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-primary-hover-link {
|
||||
color: inherit;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $blue-600;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The .btn-svg class is available for legacy icon buttons to
|
||||
|
|
|
|||
|
|
@ -415,49 +415,6 @@ $top-nav-hover-bg: var(--indigo-900-alpha-008, $indigo-900-alpha-008) !important
|
|||
}
|
||||
}
|
||||
|
||||
.title-container,
|
||||
.navbar-nav {
|
||||
.badge.badge-pill:not(.gl-badge) {
|
||||
position: inherit;
|
||||
font-weight: $gl-font-weight-normal;
|
||||
margin-left: -6px;
|
||||
font-size: 11px;
|
||||
color: var(--gray-950, $white);
|
||||
padding: 0 5px;
|
||||
line-height: 12px;
|
||||
border-radius: 7px;
|
||||
box-shadow: 0 1px 0 rgba($gl-header-color, 0.2);
|
||||
|
||||
&.green-badge {
|
||||
background-color: var(--green-400, $green-400);
|
||||
}
|
||||
|
||||
&.merge-requests-count {
|
||||
background-color: var(--orange-400, $orange-400);
|
||||
}
|
||||
|
||||
&.todos-count {
|
||||
background-color: var(--blue-400, $blue-400);
|
||||
}
|
||||
}
|
||||
|
||||
.canary-badge {
|
||||
.badge {
|
||||
font-size: $gl-font-size-small;
|
||||
line-height: $gl-line-height;
|
||||
padding: 0 $grid-size;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
|
||||
.badge {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
.navbar-gitlab .container-fluid {
|
||||
font-size: 18px;
|
||||
|
|
|
|||
|
|
@ -42,12 +42,6 @@ $status-box-line-height: 26px;
|
|||
}
|
||||
|
||||
.milestone-content {
|
||||
.issues-count {
|
||||
margin-right: 17px;
|
||||
float: right;
|
||||
width: 105px;
|
||||
}
|
||||
|
||||
.issuable-row {
|
||||
span {
|
||||
a {
|
||||
|
|
|
|||
|
|
@ -274,16 +274,10 @@
|
|||
font-weight: $gl-font-weight-normal;
|
||||
}
|
||||
|
||||
.no-value,
|
||||
.btn-default-hover-link,
|
||||
.btn-secondary-hover-link {
|
||||
.no-value {
|
||||
color: $gl-text-color-secondary;
|
||||
}
|
||||
|
||||
.btn-secondary-hover-link:hover {
|
||||
color: $blue-600;
|
||||
}
|
||||
|
||||
.sidebar-collapsed-icon {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,8 @@ body.gl-dark {
|
|||
--gray-100: #404040;
|
||||
--gray-600: #bfbfbf;
|
||||
--gray-900: #fafafa;
|
||||
--gray-950: #fff;
|
||||
--green-100: #0d532a;
|
||||
--green-400: #108548;
|
||||
--green-700: #91d4a8;
|
||||
--blue-400: #1f75cb;
|
||||
--orange-400: #ab6100;
|
||||
--indigo-900-alpha-008: rgba(235, 235, 250, 0.08);
|
||||
--gl-text-color: #fafafa;
|
||||
--border-color: #4f4f4f;
|
||||
|
|
@ -314,10 +310,18 @@ h1 {
|
|||
padding-left: 0.6em;
|
||||
border-radius: 10rem;
|
||||
}
|
||||
.badge-success {
|
||||
color: #fff;
|
||||
background-color: #2da160;
|
||||
}
|
||||
.badge-info {
|
||||
color: #fff;
|
||||
background-color: #428fdc;
|
||||
}
|
||||
.badge-warning {
|
||||
color: #fff;
|
||||
background-color: #c17d10;
|
||||
}
|
||||
.bg-transparent {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
|
@ -394,6 +398,34 @@ a.gl-badge.badge-info:active {
|
|||
0 0 0 1px rgba(51, 51, 51, 0.4), 0 0 0 4px rgba(66, 143, 220, 0.48);
|
||||
outline: none;
|
||||
}
|
||||
.gl-badge.badge-success {
|
||||
background-color: #0d532a;
|
||||
color: #91d4a8;
|
||||
}
|
||||
a.gl-badge.badge-success.active,
|
||||
a.gl-badge.badge-success:active {
|
||||
color: #ecf4ee;
|
||||
background-color: #24663b;
|
||||
}
|
||||
a.gl-badge.badge-success:active {
|
||||
box-shadow: inset 0 0 0 1px rgba(51, 51, 51, 0.8),
|
||||
0 0 0 1px rgba(51, 51, 51, 0.4), 0 0 0 4px rgba(66, 143, 220, 0.48);
|
||||
outline: none;
|
||||
}
|
||||
.gl-badge.badge-warning {
|
||||
background-color: #703800;
|
||||
color: #e9be74;
|
||||
}
|
||||
a.gl-badge.badge-warning.active,
|
||||
a.gl-badge.badge-warning:active {
|
||||
color: #fdf1dd;
|
||||
background-color: #8f4700;
|
||||
}
|
||||
a.gl-badge.badge-warning:active {
|
||||
box-shadow: inset 0 0 0 1px rgba(51, 51, 51, 0.8),
|
||||
0 0 0 1px rgba(51, 51, 51, 0.4), 0 0 0 4px rgba(66, 143, 220, 0.48);
|
||||
outline: none;
|
||||
}
|
||||
.gl-button .gl-badge {
|
||||
top: 0;
|
||||
}
|
||||
|
|
@ -920,36 +952,6 @@ input {
|
|||
line-height: 18px;
|
||||
margin: 4px 0 4px 2px;
|
||||
}
|
||||
.title-container .badge.badge-pill:not(.gl-badge),
|
||||
.navbar-nav .badge.badge-pill:not(.gl-badge) {
|
||||
position: inherit;
|
||||
font-weight: 400;
|
||||
margin-left: -6px;
|
||||
font-size: 11px;
|
||||
color: var(--gray-950, #333);
|
||||
padding: 0 5px;
|
||||
line-height: 12px;
|
||||
border-radius: 7px;
|
||||
box-shadow: 0 1px 0 rgba(76, 78, 84, 0.2);
|
||||
}
|
||||
.title-container .badge.badge-pill:not(.gl-badge).green-badge,
|
||||
.navbar-nav .badge.badge-pill:not(.gl-badge).green-badge {
|
||||
background-color: var(--green-400, #108548);
|
||||
}
|
||||
.title-container .badge.badge-pill:not(.gl-badge).merge-requests-count,
|
||||
.navbar-nav .badge.badge-pill:not(.gl-badge).merge-requests-count {
|
||||
background-color: var(--orange-400, #ab6100);
|
||||
}
|
||||
.title-container .badge.badge-pill:not(.gl-badge).todos-count,
|
||||
.navbar-nav .badge.badge-pill:not(.gl-badge).todos-count {
|
||||
background-color: var(--blue-400, #1f75cb);
|
||||
}
|
||||
.title-container .canary-badge .badge,
|
||||
.navbar-nav .canary-badge .badge {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
.navbar-gitlab .container-fluid {
|
||||
font-size: 18px;
|
||||
|
|
@ -2022,18 +2024,9 @@ body.gl-dark {
|
|||
white-space: nowrap;
|
||||
width: 1px;
|
||||
}
|
||||
.gl-bg-green-500 {
|
||||
background-color: #2da160;
|
||||
}
|
||||
.gl-border-none\! {
|
||||
border-style: none !important;
|
||||
}
|
||||
.gl-rounded-pill {
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
.gl-text-white {
|
||||
color: #333;
|
||||
}
|
||||
.gl-display-none {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -2055,9 +2048,8 @@ body.gl-dark {
|
|||
.gl-pr-2 {
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
.gl-py-1 {
|
||||
padding-top: 0.125rem;
|
||||
padding-bottom: 0.125rem;
|
||||
.gl-ml-n2 {
|
||||
margin-left: -0.25rem;
|
||||
}
|
||||
.gl-ml-3 {
|
||||
margin-left: 0.5rem;
|
||||
|
|
|
|||
|
|
@ -295,10 +295,18 @@ h1 {
|
|||
padding-left: 0.6em;
|
||||
border-radius: 10rem;
|
||||
}
|
||||
.badge-success {
|
||||
color: #fff;
|
||||
background-color: #108548;
|
||||
}
|
||||
.badge-info {
|
||||
color: #fff;
|
||||
background-color: #1f75cb;
|
||||
}
|
||||
.badge-warning {
|
||||
color: #fff;
|
||||
background-color: #ab6100;
|
||||
}
|
||||
.bg-transparent {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
|
@ -375,6 +383,34 @@ a.gl-badge.badge-info:active {
|
|||
0 0 0 1px rgba(255, 255, 255, 0.4), 0 0 0 4px rgba(31, 117, 203, 0.48);
|
||||
outline: none;
|
||||
}
|
||||
.gl-badge.badge-success {
|
||||
background-color: #c3e6cd;
|
||||
color: #24663b;
|
||||
}
|
||||
a.gl-badge.badge-success.active,
|
||||
a.gl-badge.badge-success:active {
|
||||
color: #0a4020;
|
||||
background-color: #91d4a8;
|
||||
}
|
||||
a.gl-badge.badge-success:active {
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.8),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.4), 0 0 0 4px rgba(31, 117, 203, 0.48);
|
||||
outline: none;
|
||||
}
|
||||
.gl-badge.badge-warning {
|
||||
background-color: #f5d9a8;
|
||||
color: #8f4700;
|
||||
}
|
||||
a.gl-badge.badge-warning.active,
|
||||
a.gl-badge.badge-warning:active {
|
||||
color: #5c2900;
|
||||
background-color: #e9be74;
|
||||
}
|
||||
a.gl-badge.badge-warning:active {
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.8),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.4), 0 0 0 4px rgba(31, 117, 203, 0.48);
|
||||
outline: none;
|
||||
}
|
||||
.gl-button .gl-badge {
|
||||
top: 0;
|
||||
}
|
||||
|
|
@ -901,36 +937,6 @@ input {
|
|||
line-height: 18px;
|
||||
margin: 4px 0 4px 2px;
|
||||
}
|
||||
.title-container .badge.badge-pill:not(.gl-badge),
|
||||
.navbar-nav .badge.badge-pill:not(.gl-badge) {
|
||||
position: inherit;
|
||||
font-weight: 400;
|
||||
margin-left: -6px;
|
||||
font-size: 11px;
|
||||
color: var(--gray-950, #fff);
|
||||
padding: 0 5px;
|
||||
line-height: 12px;
|
||||
border-radius: 7px;
|
||||
box-shadow: 0 1px 0 rgba(76, 78, 84, 0.2);
|
||||
}
|
||||
.title-container .badge.badge-pill:not(.gl-badge).green-badge,
|
||||
.navbar-nav .badge.badge-pill:not(.gl-badge).green-badge {
|
||||
background-color: var(--green-400, #2da160);
|
||||
}
|
||||
.title-container .badge.badge-pill:not(.gl-badge).merge-requests-count,
|
||||
.navbar-nav .badge.badge-pill:not(.gl-badge).merge-requests-count {
|
||||
background-color: var(--orange-400, #c17d10);
|
||||
}
|
||||
.title-container .badge.badge-pill:not(.gl-badge).todos-count,
|
||||
.navbar-nav .badge.badge-pill:not(.gl-badge).todos-count {
|
||||
background-color: var(--blue-400, #428fdc);
|
||||
}
|
||||
.title-container .canary-badge .badge,
|
||||
.navbar-nav .canary-badge .badge {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
.navbar-gitlab .container-fluid {
|
||||
font-size: 18px;
|
||||
|
|
@ -1691,18 +1697,9 @@ svg.s16 {
|
|||
white-space: nowrap;
|
||||
width: 1px;
|
||||
}
|
||||
.gl-bg-green-500 {
|
||||
background-color: #108548;
|
||||
}
|
||||
.gl-border-none\! {
|
||||
border-style: none !important;
|
||||
}
|
||||
.gl-rounded-pill {
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
.gl-text-white {
|
||||
color: #fff;
|
||||
}
|
||||
.gl-display-none {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -1724,9 +1721,8 @@ svg.s16 {
|
|||
.gl-pr-2 {
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
.gl-py-1 {
|
||||
padding-top: 0.125rem;
|
||||
padding-bottom: 0.125rem;
|
||||
.gl-ml-n2 {
|
||||
margin-left: -0.25rem;
|
||||
}
|
||||
.gl-ml-3 {
|
||||
margin-left: 0.5rem;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class Admin::UsersController < Admin::ApplicationController
|
|||
return redirect_to admin_cohorts_path if params[:tab] == 'cohorts'
|
||||
|
||||
@users = User.filter_items(params[:filter]).order_name_asc
|
||||
@users = @users.search_with_secondary_emails(params[:search_query]) if params[:search_query].present?
|
||||
@users = @users.search(params[:search_query], with_private_emails: true) if params[:search_query].present?
|
||||
@users = users_with_included_associations(@users)
|
||||
@users = @users.sort_by_attribute(@sort = params[:sort])
|
||||
@users = @users.page(params[:page])
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ module Autocomplete
|
|||
find_users
|
||||
.active
|
||||
.reorder_by_name
|
||||
.optionally_search(search)
|
||||
.optionally_search(search, use_minimum_char_limit: use_minimum_char_limit)
|
||||
.where_not_in(skip_users)
|
||||
.limit_to_todo_authors(
|
||||
user: current_user,
|
||||
|
|
@ -99,6 +99,12 @@ module Autocomplete
|
|||
ActiveRecord::Associations::Preloader.new.preload(items, :status)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def use_minimum_char_limit
|
||||
return if project.blank? && group.blank? # We return nil so that we use the default defined in the User model
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module Mutations
|
|||
|
||||
ADMIN_MESSAGE = 'You must be an admin to use this mutation'
|
||||
|
||||
::Gitlab::ApplicationContext::KNOWN_KEYS.each do |key|
|
||||
::Gitlab::ApplicationContext.known_keys.each do |key|
|
||||
argument key,
|
||||
GraphQL::Types::String,
|
||||
required: false,
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ class Member < ApplicationRecord
|
|||
|
||||
class << self
|
||||
def search(query)
|
||||
joins(:user).merge(User.search(query))
|
||||
joins(:user).merge(User.search(query, use_minimum_char_limit: false))
|
||||
end
|
||||
|
||||
def search_invite_email(query)
|
||||
|
|
|
|||
|
|
@ -668,7 +668,8 @@ class User < ApplicationRecord
|
|||
|
||||
sanitized_order_sql = Arel.sql(sanitize_sql_array([order, query: query]))
|
||||
|
||||
scope = options[:with_private_emails] ? search_with_secondary_emails(query) : search_with_public_emails(query)
|
||||
scope = options[:with_private_emails] ? with_primary_or_secondary_email(query) : with_public_email(query)
|
||||
scope = scope.or(search_by_name_or_username(query, use_minimum_char_limit: options[:use_minimum_char_limit]))
|
||||
|
||||
scope.reorder(sanitized_order_sql, :name)
|
||||
end
|
||||
|
|
@ -685,50 +686,32 @@ class User < ApplicationRecord
|
|||
reorder(:name)
|
||||
end
|
||||
|
||||
def search_with_public_emails(query)
|
||||
return none if query.blank?
|
||||
|
||||
query = query.downcase
|
||||
|
||||
where(
|
||||
fuzzy_arel_match(:name, query, use_minimum_char_limit: user_search_minimum_char_limit)
|
||||
.or(fuzzy_arel_match(:username, query, use_minimum_char_limit: user_search_minimum_char_limit))
|
||||
.or(arel_table[:public_email].eq(query))
|
||||
)
|
||||
end
|
||||
|
||||
def search_without_secondary_emails(query)
|
||||
return none if query.blank?
|
||||
|
||||
query = query.downcase
|
||||
|
||||
where(
|
||||
fuzzy_arel_match(:name, query, lower_exact_match: true)
|
||||
.or(fuzzy_arel_match(:username, query, lower_exact_match: true))
|
||||
.or(arel_table[:email].eq(query))
|
||||
)
|
||||
end
|
||||
|
||||
# searches user by given pattern
|
||||
# it compares name, email, username fields and user's secondary emails with given pattern
|
||||
# it compares name and username fields with given pattern
|
||||
# This method uses ILIKE on PostgreSQL.
|
||||
def search_by_name_or_username(query, use_minimum_char_limit: nil)
|
||||
use_minimum_char_limit = user_search_minimum_char_limit if use_minimum_char_limit.nil?
|
||||
|
||||
def search_with_secondary_emails(query)
|
||||
return none if query.blank?
|
||||
where(
|
||||
fuzzy_arel_match(:name, query, use_minimum_char_limit: use_minimum_char_limit)
|
||||
.or(fuzzy_arel_match(:username, query, use_minimum_char_limit: use_minimum_char_limit))
|
||||
)
|
||||
end
|
||||
|
||||
query = query.downcase
|
||||
def with_public_email(email_address)
|
||||
where(public_email: email_address)
|
||||
end
|
||||
|
||||
def with_primary_or_secondary_email(email_address)
|
||||
email_table = Email.arel_table
|
||||
matched_by_email_user_id = email_table
|
||||
.project(email_table[:user_id])
|
||||
.where(email_table[:email].eq(query))
|
||||
.where(email_table[:email].eq(email_address))
|
||||
.take(1) # at most 1 record as there is a unique constraint
|
||||
|
||||
where(
|
||||
fuzzy_arel_match(:name, query, use_minimum_char_limit: user_search_minimum_char_limit)
|
||||
.or(fuzzy_arel_match(:username, query, use_minimum_char_limit: user_search_minimum_char_limit))
|
||||
.or(arel_table[:email].eq(query))
|
||||
.or(arel_table[:id].eq(matched_by_email_user_id))
|
||||
arel_table[:email].eq(email_address)
|
||||
.or(arel_table[:id].eq(matched_by_email_user_id))
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class UsersStarProject < ApplicationRecord
|
|||
end
|
||||
|
||||
def search(query)
|
||||
joins(:user).merge(User.search(query))
|
||||
joins(:user).merge(User.search(query, use_minimum_char_limit: false))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -70,7 +70,9 @@ module Ci
|
|||
def check_assignable_runners!(build); end
|
||||
|
||||
def clone_build(build)
|
||||
project.builds.new(build_attributes(build))
|
||||
project.builds.new(build_attributes(build)).tap do |new_build|
|
||||
yield(new_build) if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
def build_attributes(build)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module MergeRequests
|
||||
class RebaseService < MergeRequests::BaseService
|
||||
REBASE_ERROR = 'Rebase failed. Please rebase locally'
|
||||
REBASE_ERROR = 'Rebase failed: Rebase locally, resolve all conflicts, then push the branch.'
|
||||
|
||||
attr_reader :merge_request, :rebase_error
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ module MergeRequests
|
|||
def set_rebase_error(exception)
|
||||
@rebase_error =
|
||||
if exception.is_a?(Gitlab::Git::PreReceiveError)
|
||||
"Something went wrong during the rebase pre-receive hook: #{exception.message}."
|
||||
"The rebase pre-receive hook failed: #{exception.message}."
|
||||
else
|
||||
REBASE_ERROR
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ module MergeRequests
|
|||
return success(squash_sha: merge_request.diff_head_sha)
|
||||
end
|
||||
|
||||
return error(s_('MergeRequests|This project does not allow squashing commits when merge requests are accepted.')) if squash_forbidden?
|
||||
return error(s_("MergeRequests|Squashing not allowed: This project doesn't allow you to squash commits when merging.")) if squash_forbidden?
|
||||
|
||||
squash! || error(s_('MergeRequests|Failed to squash. Should be done manually.'))
|
||||
squash! || error(s_('MergeRequests|Squashing failed: Squash the commits locally, resolve any conflicts, then push the branch.'))
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
= logo_text
|
||||
- if Gitlab.com_and_canary?
|
||||
= link_to Gitlab::Saas.canary_toggle_com_url, class: 'canary-badge bg-transparent', data: { qa_selector: 'canary_badge_link' }, target: :_blank, rel: 'noopener noreferrer' do
|
||||
%span.gl-badge.gl-bg-green-500.gl-text-white.gl-rounded-pill.gl-font-weight-bold.gl-py-1
|
||||
= gl_badge_tag({ variant: :success }) do
|
||||
= _('Next')
|
||||
|
||||
- if current_user
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
container: 'body' } do
|
||||
= sprite_icon('issues')
|
||||
- issues_count = assigned_issuables_count(:issues)
|
||||
%span.badge.badge-pill.issues-count.green-badge{ class: ('hidden' if issues_count == 0) }
|
||||
= gl_badge_tag({ size: :sm, variant: :success }, { class: "gl-ml-n2 #{(' gl-display-none' if issues_count == 0)}", "aria-label": n_("%d assigned issue", "%d assigned issues", issues_count) % issues_count }) do
|
||||
= number_with_delimiter(issues_count)
|
||||
- if header_link?(:merge_requests)
|
||||
= nav_link(path: 'dashboard#merge_requests', html_options: { class: "user-counter dropdown" }) do
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
track_property: 'navigation',
|
||||
container: 'body' } do
|
||||
= sprite_icon('git-merge')
|
||||
%span.badge.badge-pill.merge-requests-count.js-merge-requests-count{ class: ('hidden' if user_merge_requests_counts[:total] == 0) }
|
||||
= gl_badge_tag({ size: :sm, variant: :warning }, { class: "js-merge-requests-count gl-ml-n2#{(' gl-display-none' if user_merge_requests_counts[:total] == 0)}", "aria-label": n_("%d merge request", "%d merge requests", user_merge_requests_counts[:total]) % user_merge_requests_counts[:total] }) do
|
||||
= number_with_delimiter(user_merge_requests_counts[:total])
|
||||
= sprite_icon('chevron-down', css_class: 'caret-down gl-mx-0!')
|
||||
.dropdown-menu.dropdown-menu-right
|
||||
|
|
@ -87,12 +87,12 @@
|
|||
%li
|
||||
= link_to assigned_mrs_dashboard_path, class: 'gl-display-flex! gl-align-items-center js-prefetch-document' do
|
||||
= _('Assigned to you')
|
||||
%span.badge.gl-badge.badge-pill.badge-muted.merge-request-badge.gl-ml-auto.js-assigned-mr-count{ class: "" }
|
||||
= gl_badge_tag({ variant: :neutral, size: :sm }, { class: "js-assigned-mr-count gl-ml-auto" }) do
|
||||
= user_merge_requests_counts[:assigned]
|
||||
%li
|
||||
= link_to reviewer_mrs_dashboard_path, class: 'gl-display-flex! gl-align-items-center js-prefetch-document' do
|
||||
= _('Review requests for you')
|
||||
%span.badge.gl-badge.badge-pill.badge-muted.merge-request-badge.gl-ml-auto.js-reviewer-mr-count{ class: "" }
|
||||
= gl_badge_tag({ variant: :neutral, size: :sm }, { class: "js-reviewer-mr-count gl-ml-auto" }) do
|
||||
= user_merge_requests_counts[:review_requested]
|
||||
- if header_link?(:todos)
|
||||
= nav_link(controller: 'dashboard/todos', html_options: { class: "user-counter" }) do
|
||||
|
|
@ -103,7 +103,9 @@
|
|||
track_property: 'navigation',
|
||||
container: 'body' } do
|
||||
= sprite_icon('todo-done')
|
||||
%span.badge.badge-pill.todos-count.js-todos-count{ class: ('hidden' if todos_pending_count == 0) }
|
||||
-# The todos' counter badge's visibility is being toggled by adding or removing the .hidden class in Js.
|
||||
-# We'll eventually migrate to .gl-display-none: https://gitlab.com/gitlab-org/gitlab/-/issues/351792.
|
||||
= gl_badge_tag({ size: :sm, variant: :info }, { class: "js-todos-count gl-ml-n2#{(' hidden' if todos_pending_count == 0)}", "aria-label": _("Todos count") }) do
|
||||
= todos_count_format(todos_pending_count)
|
||||
%li.nav-item.header-help.dropdown.d-none.d-md-block{ **tracking_attrs('main_navigation', 'click_question_mark_link', 'navigation') }
|
||||
= link_to help_path, class: 'header-help-dropdown-toggle gl-relative', data: { toggle: "dropdown" } do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: dast_sharded_cloned_ci_builds
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78164
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351980
|
||||
milestone: '14.8'
|
||||
type: development
|
||||
group: group::dynamic analysis
|
||||
default_enabled: true
|
||||
|
|
@ -35,8 +35,12 @@
|
|||
- 1
|
||||
- - analytics_usage_trends_counter_job
|
||||
- 1
|
||||
- - app_sec_dast_scanner_profiles_builds_consistency
|
||||
- 1
|
||||
- - app_sec_dast_scans_consistency
|
||||
- 1
|
||||
- - app_sec_dast_site_profiles_builds_consistency
|
||||
- 1
|
||||
- - approval_rules_external_approval_rule_payload
|
||||
- 1
|
||||
- - approve_blocked_pending_approval_users
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
SCALABILITY_REVIEW_MESSAGE = <<~MSG
|
||||
## Sidekiq queue changes
|
||||
|
||||
This merge request contains changes to Sidekiq queues. Please follow the [documentation on changing a queue's urgency](https://docs.gitlab.com/ee/development/sidekiq_style_guide.html#changing-a-queues-urgency).
|
||||
This merge request contains changes to Sidekiq queues. Please follow the [documentation on changing a queue's urgency](https://docs.gitlab.com/ee/development/sidekiq/worker_attributes.html#job-urgency).
|
||||
MSG
|
||||
|
||||
ADDED_QUEUES_MESSAGE = <<~MSG
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -8810,6 +8810,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="boardepicancestorsauthorusername"></a>`authorUsername` | [`String`](#string) | Filter epics by author. |
|
||||
| <a id="boardepicancestorsconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter epics by given confidentiality. |
|
||||
| <a id="boardepicancestorscreatedafter"></a>`createdAfter` | [`Time`](#time) | Epics created after this date. |
|
||||
| <a id="boardepicancestorscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Epics created before this date. |
|
||||
| <a id="boardepicancestorsenddate"></a>`endDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.end. |
|
||||
| <a id="boardepicancestorsiid"></a>`iid` | [`ID`](#id) | IID of the epic, e.g., "1". |
|
||||
| <a id="boardepicancestorsiidstartswith"></a>`iidStartsWith` | [`String`](#string) | Filter epics by IID for autocomplete. |
|
||||
|
|
@ -8826,6 +8828,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="boardepicancestorsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
|
||||
| <a id="boardepicancestorsstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
|
||||
| <a id="boardepicancestorstimeframe"></a>`timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. |
|
||||
| <a id="boardepicancestorsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Epics updated after this date. |
|
||||
| <a id="boardepicancestorsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Epics updated before this date. |
|
||||
|
||||
##### `BoardEpic.children`
|
||||
|
||||
|
|
@ -8843,6 +8847,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="boardepicchildrenauthorusername"></a>`authorUsername` | [`String`](#string) | Filter epics by author. |
|
||||
| <a id="boardepicchildrenconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter epics by given confidentiality. |
|
||||
| <a id="boardepicchildrencreatedafter"></a>`createdAfter` | [`Time`](#time) | Epics created after this date. |
|
||||
| <a id="boardepicchildrencreatedbefore"></a>`createdBefore` | [`Time`](#time) | Epics created before this date. |
|
||||
| <a id="boardepicchildrenenddate"></a>`endDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.end. |
|
||||
| <a id="boardepicchildreniid"></a>`iid` | [`ID`](#id) | IID of the epic, e.g., "1". |
|
||||
| <a id="boardepicchildreniidstartswith"></a>`iidStartsWith` | [`String`](#string) | Filter epics by IID for autocomplete. |
|
||||
|
|
@ -8859,6 +8865,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="boardepicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
|
||||
| <a id="boardepicchildrenstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
|
||||
| <a id="boardepicchildrentimeframe"></a>`timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. |
|
||||
| <a id="boardepicchildrenupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Epics updated after this date. |
|
||||
| <a id="boardepicchildrenupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Epics updated before this date. |
|
||||
|
||||
##### `BoardEpic.currentUserTodos`
|
||||
|
||||
|
|
@ -10326,6 +10334,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="epicancestorsauthorusername"></a>`authorUsername` | [`String`](#string) | Filter epics by author. |
|
||||
| <a id="epicancestorsconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter epics by given confidentiality. |
|
||||
| <a id="epicancestorscreatedafter"></a>`createdAfter` | [`Time`](#time) | Epics created after this date. |
|
||||
| <a id="epicancestorscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Epics created before this date. |
|
||||
| <a id="epicancestorsenddate"></a>`endDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.end. |
|
||||
| <a id="epicancestorsiid"></a>`iid` | [`ID`](#id) | IID of the epic, e.g., "1". |
|
||||
| <a id="epicancestorsiidstartswith"></a>`iidStartsWith` | [`String`](#string) | Filter epics by IID for autocomplete. |
|
||||
|
|
@ -10342,6 +10352,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="epicancestorsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
|
||||
| <a id="epicancestorsstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
|
||||
| <a id="epicancestorstimeframe"></a>`timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. |
|
||||
| <a id="epicancestorsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Epics updated after this date. |
|
||||
| <a id="epicancestorsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Epics updated before this date. |
|
||||
|
||||
##### `Epic.children`
|
||||
|
||||
|
|
@ -10359,6 +10371,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="epicchildrenauthorusername"></a>`authorUsername` | [`String`](#string) | Filter epics by author. |
|
||||
| <a id="epicchildrenconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter epics by given confidentiality. |
|
||||
| <a id="epicchildrencreatedafter"></a>`createdAfter` | [`Time`](#time) | Epics created after this date. |
|
||||
| <a id="epicchildrencreatedbefore"></a>`createdBefore` | [`Time`](#time) | Epics created before this date. |
|
||||
| <a id="epicchildrenenddate"></a>`endDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.end. |
|
||||
| <a id="epicchildreniid"></a>`iid` | [`ID`](#id) | IID of the epic, e.g., "1". |
|
||||
| <a id="epicchildreniidstartswith"></a>`iidStartsWith` | [`String`](#string) | Filter epics by IID for autocomplete. |
|
||||
|
|
@ -10375,6 +10389,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="epicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
|
||||
| <a id="epicchildrenstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
|
||||
| <a id="epicchildrentimeframe"></a>`timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. |
|
||||
| <a id="epicchildrenupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Epics updated after this date. |
|
||||
| <a id="epicchildrenupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Epics updated before this date. |
|
||||
|
||||
##### `Epic.currentUserTodos`
|
||||
|
||||
|
|
@ -11049,6 +11065,8 @@ Returns [`Epic`](#epic).
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="groupepicauthorusername"></a>`authorUsername` | [`String`](#string) | Filter epics by author. |
|
||||
| <a id="groupepicconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter epics by given confidentiality. |
|
||||
| <a id="groupepiccreatedafter"></a>`createdAfter` | [`Time`](#time) | Epics created after this date. |
|
||||
| <a id="groupepiccreatedbefore"></a>`createdBefore` | [`Time`](#time) | Epics created before this date. |
|
||||
| <a id="groupepicenddate"></a>`endDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.end. |
|
||||
| <a id="groupepiciid"></a>`iid` | [`ID`](#id) | IID of the epic, e.g., "1". |
|
||||
| <a id="groupepiciidstartswith"></a>`iidStartsWith` | [`String`](#string) | Filter epics by IID for autocomplete. |
|
||||
|
|
@ -11065,6 +11083,8 @@ Returns [`Epic`](#epic).
|
|||
| <a id="groupepicstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
|
||||
| <a id="groupepicstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
|
||||
| <a id="groupepictimeframe"></a>`timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. |
|
||||
| <a id="groupepicupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Epics updated after this date. |
|
||||
| <a id="groupepicupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Epics updated before this date. |
|
||||
|
||||
##### `Group.epicBoard`
|
||||
|
||||
|
|
@ -11094,6 +11114,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="groupepicsauthorusername"></a>`authorUsername` | [`String`](#string) | Filter epics by author. |
|
||||
| <a id="groupepicsconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter epics by given confidentiality. |
|
||||
| <a id="groupepicscreatedafter"></a>`createdAfter` | [`Time`](#time) | Epics created after this date. |
|
||||
| <a id="groupepicscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Epics created before this date. |
|
||||
| <a id="groupepicsenddate"></a>`endDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.end. |
|
||||
| <a id="groupepicsiid"></a>`iid` | [`ID`](#id) | IID of the epic, e.g., "1". |
|
||||
| <a id="groupepicsiidstartswith"></a>`iidStartsWith` | [`String`](#string) | Filter epics by IID for autocomplete. |
|
||||
|
|
@ -11110,6 +11132,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="groupepicsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
|
||||
| <a id="groupepicsstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
|
||||
| <a id="groupepicstimeframe"></a>`timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. |
|
||||
| <a id="groupepicsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Epics updated after this date. |
|
||||
| <a id="groupepicsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Epics updated before this date. |
|
||||
|
||||
##### `Group.groupMembers`
|
||||
|
||||
|
|
@ -17041,12 +17065,16 @@ Roadmap sort values.
|
|||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="epicsortcreated_at_asc"></a>`CREATED_AT_ASC` | Sort by created_at by ascending order. |
|
||||
| <a id="epicsortcreated_at_desc"></a>`CREATED_AT_DESC` | Sort by created_at by descending order. |
|
||||
| <a id="epicsortend_date_asc"></a>`END_DATE_ASC` | Sort by end date in ascending order. |
|
||||
| <a id="epicsortend_date_desc"></a>`END_DATE_DESC` | Sort by end date in descending order. |
|
||||
| <a id="epicsortstart_date_asc"></a>`START_DATE_ASC` | Sort by start date in ascending order. |
|
||||
| <a id="epicsortstart_date_desc"></a>`START_DATE_DESC` | Sort by start date in descending order. |
|
||||
| <a id="epicsorttitle_asc"></a>`TITLE_ASC` | Sort by title in ascending order. |
|
||||
| <a id="epicsorttitle_desc"></a>`TITLE_DESC` | Sort by title in descending order. |
|
||||
| <a id="epicsortupdated_at_asc"></a>`UPDATED_AT_ASC` | Sort by updated_at by ascending order. |
|
||||
| <a id="epicsortupdated_at_desc"></a>`UPDATED_AT_DESC` | Sort by updated_at by descending order. |
|
||||
| <a id="epicsortend_date_asc"></a>`end_date_asc` **{warning-solid}** | **Deprecated** in 13.11. Use END_DATE_ASC. |
|
||||
| <a id="epicsortend_date_desc"></a>`end_date_desc` **{warning-solid}** | **Deprecated** in 13.11. Use END_DATE_DESC. |
|
||||
| <a id="epicsortstart_date_asc"></a>`start_date_asc` **{warning-solid}** | **Deprecated** in 13.11. Use START_DATE_ASC. |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -243,8 +243,8 @@ curl --request POST \
|
|||
}
|
||||
```
|
||||
|
||||
The `ContentType` header must return a valid number. The maximum file size is 10 gigabytes.
|
||||
The `ContentLength` header must be `application/gzip`.
|
||||
The `Content-Length` header must return a valid number. The maximum file size is 10 gigabytes.
|
||||
The `Content-Type` header must be `application/gzip`.
|
||||
|
||||
## Import status
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
type: reference, api
|
||||
---
|
||||
|
||||
# Visual Review discussions API **(PREMIUM)**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Configure
|
||||
group: Configure
|
||||
stage: Verify
|
||||
group: Pipeline Authoring
|
||||
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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Configure
|
||||
group: Configure
|
||||
stage: Verify
|
||||
group: Pipeline Authoring
|
||||
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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
author: Vincent Tunru
|
||||
author_gitlab: Vinnl
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
type: reference
|
||||
---
|
||||
|
||||
# Metrics Reports **(PREMIUM)**
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
disqus_identifier: 'https://docs.gitlab.com/ee/user/project/pipelines/job_artifacts.html'
|
||||
type: reference, howto
|
||||
---
|
||||
|
||||
# Job artifacts **(FREE)**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
type: reference, howto
|
||||
---
|
||||
|
||||
# Pipeline artifacts **(FREE)**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
type: reference
|
||||
---
|
||||
|
||||
# Review Apps **(FREE)**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
type: reference
|
||||
---
|
||||
|
||||
# Unit test reports **(FREE)**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -560,10 +560,6 @@ sudo -u git -H cp config/puma.rb.example config/puma.rb
|
|||
# cores you have available. You can get that number via the `nproc` command.
|
||||
sudo -u git -H editor config/puma.rb
|
||||
|
||||
# Configure Git global settings for git user
|
||||
# 'autocrlf' is needed for the web editor
|
||||
sudo -u git -H git config --global core.autocrlf input
|
||||
|
||||
# Disable 'git gc --auto' because GitLab already runs 'git gc' when needed
|
||||
sudo -u git -H git config --global gc.auto 0
|
||||
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ any Kubernetes-specific commands from the authorized project.
|
|||
|
||||
First, configure your Agent:
|
||||
|
||||
1. Go to you your Agent's configuration repository.
|
||||
1. Go to your Agent's configuration repository.
|
||||
1. Edit your Agent's `config.yaml` file authorizing the [project](#authorize-projects-to-use-an-agent) or [group](#authorize-groups-to-use-an-agent) you want to run Kubernetes commands from.
|
||||
|
||||
Then, configure the other project:
|
||||
|
|
@ -231,6 +231,14 @@ To get the list of available contexts:
|
|||
1. Open your terminal and connect to your cluster.
|
||||
1. Run `kubectl config get-contexts`.
|
||||
|
||||
### `kubectl` commands not supported
|
||||
|
||||
The commands `kubectl exec`, `kubectl cp`, and `kubectl attach` are not supported by the CI/CD tunnel.
|
||||
Anything else that uses the same API endpoints does not work either as they use the deprecated
|
||||
SPDY protocol.
|
||||
We [plan to add support for these features](https://gitlab.com/gitlab-org/gitlab/-/issues/346248)
|
||||
in a future version of GitLab.
|
||||
|
||||
## Use impersonation to restrict project and group access **(PREMIUM)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345014) in GitLab 14.5.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
type: reference
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
type: reference, howto
|
||||
---
|
||||
|
||||
# Accessibility testing **(FREE)**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
type: reference, howto
|
||||
---
|
||||
|
||||
# Browser Performance Testing **(PREMIUM)**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
type: reference, howto
|
||||
---
|
||||
|
||||
# Fail Fast Testing **(PREMIUM)**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
type: reference, howto
|
||||
---
|
||||
|
||||
# Load Performance Testing **(PREMIUM)**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
type: reference, howto
|
||||
---
|
||||
|
||||
# Test coverage visualization **(FREE)**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
---
|
||||
stage: Verify
|
||||
group: Testing
|
||||
group: Pipeline Insights
|
||||
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
|
||||
type: index
|
||||
description: "Test your code and display reports in merge requests"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ module API
|
|||
def retrieve_members(source, params:, deep: false)
|
||||
members = deep ? find_all_members(source) : source_members(source).connected_to_user
|
||||
members = members.includes(:user)
|
||||
members = members.references(:user).merge(User.search(params[:query])) if params[:query].present?
|
||||
members = members.references(:user).merge(User.search(params[:query], use_minimum_char_limit: false)) if params[:query].present?
|
||||
members = members.where(user_id: params[:user_ids]) if params[:user_ids].present?
|
||||
members
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,7 +9,17 @@ module Gitlab
|
|||
Attribute = Struct.new(:name, :type)
|
||||
|
||||
LOG_KEY = Labkit::Context::LOG_KEY
|
||||
KNOWN_KEYS = Labkit::Context::KNOWN_KEYS
|
||||
KNOWN_KEYS = [
|
||||
:user,
|
||||
:project,
|
||||
:root_namespace,
|
||||
:client_id,
|
||||
:caller_id,
|
||||
:remote_ip,
|
||||
:related_class,
|
||||
:feature_category
|
||||
].freeze
|
||||
private_constant :KNOWN_KEYS
|
||||
|
||||
APPLICATION_ATTRIBUTES = [
|
||||
Attribute.new(:project, Project),
|
||||
|
|
@ -22,6 +32,10 @@ module Gitlab
|
|||
Attribute.new(:feature_category, String)
|
||||
].freeze
|
||||
|
||||
def self.known_keys
|
||||
KNOWN_KEYS
|
||||
end
|
||||
|
||||
def self.with_context(args, &block)
|
||||
application_context = new(**args)
|
||||
application_context.use(&block)
|
||||
|
|
|
|||
|
|
@ -95,10 +95,6 @@ module Gitlab
|
|||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
def git_bin_path
|
||||
Gitlab.config.git.bin_path
|
||||
end
|
||||
|
||||
def copy_files(source, destination)
|
||||
# if we are copying files, create the destination folder
|
||||
destination_folder = File.file?(source) ? File.dirname(destination) : destination
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module Gitlab
|
|||
InvalidQueueError = Class.new(StandardError)
|
||||
|
||||
WORKER_KEY = 'worker_class'
|
||||
ALLOWED_KEYS = Gitlab::ApplicationContext::KNOWN_KEYS + [WORKER_KEY]
|
||||
ALLOWED_KEYS = Gitlab::ApplicationContext.known_keys.map(&:to_s) + [WORKER_KEY]
|
||||
|
||||
attr_reader :queue_name
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ module Gitlab
|
|||
private
|
||||
|
||||
def transform_key(key)
|
||||
if Gitlab::ApplicationContext::KNOWN_KEYS.include?(key)
|
||||
if Gitlab::ApplicationContext.known_keys.include?(key.to_sym)
|
||||
"meta.#{key}"
|
||||
elsif key == WORKER_KEY
|
||||
'class'
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SystemCheck
|
||||
module App
|
||||
class GitConfigCheck < SystemCheck::BaseCheck
|
||||
OPTIONS = {
|
||||
'core.autocrlf' => 'input'
|
||||
}.freeze
|
||||
|
||||
set_name 'Git configured correctly?'
|
||||
|
||||
def check?
|
||||
correct_options = OPTIONS.map do |name, value|
|
||||
run_command(%W(#{Gitlab.config.git.bin_path} config --global --get #{name})).try(:squish) == value
|
||||
end
|
||||
|
||||
correct_options.all?
|
||||
end
|
||||
|
||||
# Tries to configure git itself
|
||||
#
|
||||
# Returns true if all subcommands were successful (according to their exit code)
|
||||
# Returns false if any or all subcommands failed.
|
||||
def repair!
|
||||
return false unless gitlab_user?
|
||||
|
||||
command_success = OPTIONS.map do |name, value|
|
||||
system(*%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value}))
|
||||
end
|
||||
|
||||
command_success.all?
|
||||
end
|
||||
|
||||
def show_error
|
||||
try_fixing_it(
|
||||
sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{OPTIONS['core.autocrlf']}\"")
|
||||
)
|
||||
for_more_information(
|
||||
see_installation_guide_section('GitLab')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SystemCheck
|
||||
module App
|
||||
class GitVersionCheck < SystemCheck::BaseCheck
|
||||
set_name -> { "Git version >= #{self.required_version} ?" }
|
||||
set_check_pass -> { "yes (#{self.current_version})" }
|
||||
|
||||
def self.required_version
|
||||
@required_version ||= Gitlab::VersionInfo.parse('2.33.0')
|
||||
end
|
||||
|
||||
def self.current_version
|
||||
@current_version ||= Gitlab::VersionInfo.parse(Gitlab::TaskHelpers.run_command(%W(#{Gitlab.config.git.bin_path} --version)))
|
||||
end
|
||||
|
||||
def check?
|
||||
self.class.current_version.valid? && self.class.required_version <= self.class.current_version
|
||||
end
|
||||
|
||||
def show_error
|
||||
$stdout.puts "Your git bin path is \"#{Gitlab.config.git.bin_path}\""
|
||||
|
||||
try_fixing_it(
|
||||
"Update your git to a version >= #{self.class.required_version} from #{self.class.current_version}"
|
||||
)
|
||||
fix_and_rerun
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -12,7 +12,6 @@ module SystemCheck
|
|||
|
||||
def self.checks
|
||||
[
|
||||
SystemCheck::App::GitConfigCheck,
|
||||
SystemCheck::App::DatabaseConfigExistsCheck,
|
||||
SystemCheck::App::MigrationsAreUpCheck,
|
||||
SystemCheck::App::OrphanedGroupMembersCheck,
|
||||
|
|
@ -28,7 +27,6 @@ module SystemCheck
|
|||
SystemCheck::App::ProjectsHaveNamespaceCheck,
|
||||
SystemCheck::App::RedisVersionCheck,
|
||||
SystemCheck::App::RubyVersionCheck,
|
||||
SystemCheck::App::GitVersionCheck,
|
||||
SystemCheck::App::GitUserDefaultSSHConfigCheck,
|
||||
SystemCheck::App::ActiveUsersCheck,
|
||||
SystemCheck::App::AuthorizedKeysPermissionCheck,
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ namespace :gitlab do
|
|||
proxies = Gitlab::Proxy.detect_proxy.map {|k, v| "#{k}: #{v}"}.join("\n\t\t")
|
||||
end
|
||||
|
||||
# check Git version
|
||||
git_version = run_and_match([Gitlab.config.git.bin_path, '--version'], /git version ([\d\.]+)/).to_a
|
||||
# check Go version
|
||||
go_version = run_and_match(%w(go version), /go version (.+)/).to_a
|
||||
|
||||
|
|
@ -43,7 +41,6 @@ namespace :gitlab do
|
|||
puts "Bundler Version:#{bunder_version || "unknown".color(:red)}"
|
||||
puts "Rake Version:\t#{rake_version || "unknown".color(:red)}"
|
||||
puts "Redis Version:\t#{redis_version[1] || "unknown".color(:red)}"
|
||||
puts "Git Version:\t#{git_version[1] || "unknown".color(:red)}"
|
||||
puts "Sidekiq Version:#{Sidekiq::VERSION}"
|
||||
puts "Go Version:\t#{go_version[1] || "unknown".color(:red)}"
|
||||
|
||||
|
|
@ -95,7 +92,6 @@ namespace :gitlab do
|
|||
end
|
||||
end
|
||||
puts "GitLab Shell path:\t\t#{Gitlab.config.gitlab_shell.path}"
|
||||
puts "Git:\t\t#{Gitlab.config.git.bin_path}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -146,6 +146,11 @@ msgid_plural "%d approvers (you've approved)"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d assigned issue"
|
||||
msgid_plural "%d assigned issues"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d changed file"
|
||||
msgid_plural "%d changed files"
|
||||
msgstr[0] ""
|
||||
|
|
@ -11382,9 +11387,6 @@ msgstr ""
|
|||
msgid "Date merged"
|
||||
msgstr ""
|
||||
|
||||
msgid "Date picker"
|
||||
msgstr ""
|
||||
|
||||
msgid "Date range"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -14123,9 +14125,6 @@ msgstr ""
|
|||
msgid "Error occurred while updating the issue status"
|
||||
msgstr ""
|
||||
|
||||
msgid "Error occurred while updating the issue weight"
|
||||
msgstr ""
|
||||
|
||||
msgid "Error occurred. A blocked user cannot be deactivated"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -22680,13 +22679,13 @@ msgstr ""
|
|||
msgid "MergeRequests|Create issue to resolve thread"
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequests|Failed to squash. Should be done manually."
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequests|Saving the comment failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted."
|
||||
msgid "MergeRequests|Squashing failed: Squash the commits locally, resolve any conflicts, then push the branch."
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequests|Squashing not allowed: This project doesn't allow you to squash commits when merging."
|
||||
msgstr ""
|
||||
|
||||
msgid "MergeRequests|Thread stays resolved"
|
||||
|
|
@ -27082,6 +27081,18 @@ msgstr ""
|
|||
msgid "Policy project doesn't exist"
|
||||
msgstr ""
|
||||
|
||||
msgid "PolicyRuleMultiSelect|%{firstLabel} +%{numberOfAdditionalLabels} more"
|
||||
msgstr ""
|
||||
|
||||
msgid "PolicyRuleMultiSelect|All %{itemTypeName}"
|
||||
msgstr ""
|
||||
|
||||
msgid "PolicyRuleMultiSelect|Select %{itemTypeName}"
|
||||
msgstr ""
|
||||
|
||||
msgid "PolicyRuleMultiSelect|Select all"
|
||||
msgstr ""
|
||||
|
||||
msgid "Polling interval multiplier"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -33402,12 +33413,6 @@ msgstr ""
|
|||
msgid "Sidebar|None"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sidebar|Only numeral characters allowed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sidebar|Weight"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sidekiq job compression threshold (bytes)"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -37796,6 +37801,9 @@ msgstr ""
|
|||
msgid "Today"
|
||||
msgstr ""
|
||||
|
||||
msgid "Todos count"
|
||||
msgstr ""
|
||||
|
||||
msgid "Todos|Are you looking for things to do? Take a look at %{strongStart}%{openIssuesLinkStart}open issues%{openIssuesLinkEnd}%{strongEnd}, contribute to %{strongStart}%{mergeRequestLinkStart}a merge request%{mergeRequestLinkEnd}%{mergeRequestLinkEnd}%{strongEnd}, or mention someone in a comment to automatically assign them a new to-do item."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -100,8 +100,7 @@ module QA
|
|||
|
||||
def exists?
|
||||
api_get
|
||||
# TODO: remove 'InternalServerError' from below line after https://gitlab.com/gitlab-org/gitlab/-/issues/349337 has been resolved
|
||||
rescue ResourceNotFoundError, InternalServerError
|
||||
rescue ResourceNotFoundError
|
||||
false
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Manage', :requires_admin, quarantine: {
|
||||
only: { subdomain: :staging },
|
||||
type: :investigating,
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/350965'
|
||||
} do
|
||||
RSpec.describe 'Manage', :requires_admin do
|
||||
describe 'Gitlab migration' do
|
||||
let(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } }
|
||||
let(:admin_api_client) { Runtime::API::Client.as_admin }
|
||||
|
|
|
|||
|
|
@ -3,11 +3,7 @@
|
|||
require_relative 'gitlab_project_migration_common'
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Manage', :requires_admin, quarantine: {
|
||||
only: { subdomain: :staging },
|
||||
type: :investigating,
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/351596'
|
||||
} do
|
||||
RSpec.describe 'Manage' do
|
||||
describe 'Gitlab migration' do
|
||||
include_context 'with gitlab project migration'
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require_relative 'gitlab_project_migration_common'
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Manage', :requires_admin do
|
||||
RSpec.describe 'Manage' do
|
||||
describe 'Gitlab migration' do
|
||||
include_context 'with gitlab project migration'
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require_relative 'gitlab_project_migration_common'
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Manage', :requires_admin do
|
||||
RSpec.describe 'Manage' do
|
||||
describe 'Gitlab migration' do
|
||||
include_context 'with gitlab project migration'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.shared_context 'with gitlab project migration', quarantine: {
|
||||
# Disable on staging until bulk_import_projects toggle is on by default
|
||||
# Otherwise tests running in parallel can disable feature in the middle of other test
|
||||
RSpec.shared_context 'with gitlab project migration', :requires_admin, except: { subdomain: :staging }, quarantine: {
|
||||
only: { job: 'praefect' },
|
||||
type: :investigating,
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/348999'
|
||||
|
|
|
|||
|
|
@ -64,7 +64,9 @@ module QA
|
|||
Page::Profile::Accounts::Show.perform do |show|
|
||||
show.delete_account(user.password)
|
||||
end
|
||||
Support::Waiter.wait_until { !user.exists? }
|
||||
|
||||
# TODO: Remove retry_on_exception once https://gitlab.com/gitlab-org/gitlab/-/issues/24294 is resolved
|
||||
Support::Waiter.wait_until(retry_on_exception: true, sleep_interval: 3) { !user.exists? }
|
||||
end
|
||||
|
||||
it 'allows recreating with same credentials', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347868' do
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching d
|
|||
it 'reflects dashboard issues count' do
|
||||
visit issues_path
|
||||
|
||||
expect_counters('issues', '1')
|
||||
expect_counters('issues', '1', n_("%d assigned issue", "%d assigned issues", 1) % 1)
|
||||
|
||||
issue.assignees = []
|
||||
|
||||
|
|
@ -26,14 +26,14 @@ RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching d
|
|||
travel_to(3.minutes.from_now) do
|
||||
visit issues_path
|
||||
|
||||
expect_counters('issues', '0')
|
||||
expect_counters('issues', '0', n_("%d assigned issue", "%d assigned issues", 0) % 0)
|
||||
end
|
||||
end
|
||||
|
||||
it 'reflects dashboard merge requests count' do
|
||||
visit merge_requests_path
|
||||
|
||||
expect_counters('merge_requests', '1')
|
||||
expect_counters('merge_requests', '1', n_("%d merge request", "%d merge requests", 1) % 1)
|
||||
|
||||
merge_request.update!(assignees: [])
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching d
|
|||
travel_to(3.minutes.from_now) do
|
||||
visit merge_requests_path
|
||||
|
||||
expect_counters('merge_requests', '0')
|
||||
expect_counters('merge_requests', '0', n_("%d merge request", "%d merge requests", 0) % 0)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -54,13 +54,14 @@ RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching d
|
|||
merge_requests_dashboard_path(assignee_username: user.username)
|
||||
end
|
||||
|
||||
def expect_counters(issuable_type, count)
|
||||
def expect_counters(issuable_type, count, badge_label)
|
||||
dashboard_count = find('.gl-tabs-nav li a.active')
|
||||
nav_count = find(".dashboard-shortcuts-#{issuable_type}")
|
||||
header_count = find(".header-content .#{issuable_type.tr('_', '-')}-count")
|
||||
|
||||
expect(dashboard_count).to have_content(count)
|
||||
expect(nav_count).to have_content(count)
|
||||
expect(header_count).to have_content(count)
|
||||
within("span[aria-label='#{badge_label}']") do
|
||||
expect(page).to have_content(count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -112,7 +112,9 @@ RSpec.describe 'Dashboard Merge Requests' do
|
|||
end
|
||||
|
||||
it 'includes assigned and reviewers in badge' do
|
||||
expect(find('.merge-requests-count')).to have_content('3')
|
||||
within("span[aria-label='#{n_("%d merge request", "%d merge requests", 3) % 3}']") do
|
||||
expect(page).to have_content('3')
|
||||
end
|
||||
expect(find('.js-assigned-mr-count')).to have_content('2')
|
||||
expect(find('.js-reviewer-mr-count')).to have_content('1')
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ RSpec.describe 'Manually create a todo item from issue', :js do
|
|||
expect(page).to have_content 'Mark as done'
|
||||
end
|
||||
|
||||
page.within '.header-content .todos-count' do
|
||||
page.within ".header-content span[aria-label='#{_('Todos count')}']" do
|
||||
expect(page).to have_content '1'
|
||||
end
|
||||
|
||||
visit project_issue_path(project, issue)
|
||||
|
||||
page.within '.header-content .todos-count' do
|
||||
page.within ".header-content span[aria-label='#{_('Todos count')}']" do
|
||||
expect(page).to have_content '1'
|
||||
end
|
||||
end
|
||||
|
|
@ -36,10 +36,10 @@ RSpec.describe 'Manually create a todo item from issue', :js do
|
|||
click_button 'Mark as done'
|
||||
end
|
||||
|
||||
expect(page).to have_selector('.todos-count', visible: false)
|
||||
expect(page).to have_selector(".header-content span[aria-label='#{_('Todos count')}']", visible: false)
|
||||
|
||||
visit project_issue_path(project, issue)
|
||||
|
||||
expect(page).to have_selector('.todos-count', visible: false)
|
||||
expect(page).to have_selector(".header-content span[aria-label='#{_('Todos count')}']", visible: false)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,15 +7,16 @@ RSpec.describe Autocomplete::UsersFinder do
|
|||
# https://gitlab.com/gitlab-org/gitlab/-/issues/21432
|
||||
|
||||
describe '#execute' do
|
||||
let!(:user1) { create(:user, username: 'johndoe') }
|
||||
let!(:user2) { create(:user, :blocked, username: 'notsorandom') }
|
||||
let!(:external_user) { create(:user, :external) }
|
||||
let!(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
|
||||
let_it_be(:user1) { create(:user, name: 'zzzzzname', username: 'johndoe') }
|
||||
let_it_be(:user2) { create(:user, :blocked, username: 'notsorandom') }
|
||||
let_it_be(:external_user) { create(:user, :external) }
|
||||
let_it_be(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
|
||||
|
||||
let(:current_user) { create(:user) }
|
||||
let(:params) { {} }
|
||||
|
||||
let(:project) { nil }
|
||||
let(:group) { nil }
|
||||
let_it_be(:project) { nil }
|
||||
let_it_be(:group) { nil }
|
||||
|
||||
subject { described_class.new(params: params, current_user: current_user, project: project, group: group).execute.to_a }
|
||||
|
||||
|
|
@ -26,7 +27,7 @@ RSpec.describe Autocomplete::UsersFinder do
|
|||
end
|
||||
|
||||
context 'when project passed' do
|
||||
let(:project) { create(:project) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
it { is_expected.to match_array([project.first_owner]) }
|
||||
|
||||
|
|
@ -43,16 +44,36 @@ RSpec.describe Autocomplete::UsersFinder do
|
|||
it { is_expected.to match_array([project.first_owner]) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'searching with less than 3 characters' do
|
||||
let(:params) { { search: 'zz' } }
|
||||
|
||||
before do
|
||||
project.add_guest(user1)
|
||||
end
|
||||
|
||||
it 'allows partial matches' do
|
||||
expect(subject).to contain_exactly(user1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when group passed and project not passed' do
|
||||
let(:group) { create(:group, :public) }
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
|
||||
before do
|
||||
before_all do
|
||||
group.add_users([user1], GroupMember::DEVELOPER)
|
||||
end
|
||||
|
||||
it { is_expected.to match_array([user1]) }
|
||||
|
||||
context 'searching with less than 3 characters' do
|
||||
let(:params) { { search: 'zz' } }
|
||||
|
||||
it 'allows partial matches' do
|
||||
expect(subject).to contain_exactly(user1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when passed a subgroup' do
|
||||
|
|
@ -76,6 +97,14 @@ RSpec.describe Autocomplete::UsersFinder do
|
|||
let(:params) { { search: 'johndoe' } }
|
||||
|
||||
it { is_expected.to match_array([user1]) }
|
||||
|
||||
context 'searching with less than 3 characters' do
|
||||
let(:params) { { search: 'zz' } }
|
||||
|
||||
it 'does not allow partial matches' do
|
||||
expect(subject).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when filtered by skip_users' do
|
||||
|
|
|
|||
|
|
@ -1,152 +1,140 @@
|
|||
{
|
||||
"type": "object",
|
||||
"properties" : {
|
||||
"properties" : {
|
||||
"id": { "type": "integer" },
|
||||
"iid": { "type": "integer" },
|
||||
"project_id": { "type": "integer" },
|
||||
"title": { "type": "string" },
|
||||
"description": { "type": ["string", "null"] },
|
||||
"state": { "type": "string" },
|
||||
"merged_by": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"username": { "type": "string" },
|
||||
"id": { "type": "integer" },
|
||||
"state": { "type": "string" },
|
||||
"avatar_url": { "type": "uri" },
|
||||
"web_url": { "type": "uri" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
"id": { "type": "integer" },
|
||||
"iid": { "type": "integer" },
|
||||
"project_id": { "type": "integer" },
|
||||
"title": { "type": "string" },
|
||||
"description": { "type": ["string", "null"] },
|
||||
"state": { "type": "string" },
|
||||
"merged_by": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"username": { "type": "string" },
|
||||
"id": { "type": "integer" },
|
||||
"state": { "type": "string" },
|
||||
"avatar_url": { "type": "uri" },
|
||||
"web_url": { "type": "uri" }
|
||||
},
|
||||
"merge_user": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"username": { "type": "string" },
|
||||
"id": { "type": "integer" },
|
||||
"state": { "type": "string" },
|
||||
"avatar_url": { "type": "uri" },
|
||||
"web_url": { "type": "uri" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
"additionalProperties": false
|
||||
},
|
||||
"merge_user": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"username": { "type": "string" },
|
||||
"id": { "type": "integer" },
|
||||
"state": { "type": "string" },
|
||||
"avatar_url": { "type": "uri" },
|
||||
"web_url": { "type": "uri" }
|
||||
},
|
||||
"merged_at": { "type": ["string", "null"] },
|
||||
"closed_by": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"username": { "type": "string" },
|
||||
"id": { "type": "integer" },
|
||||
"state": { "type": "string" },
|
||||
"avatar_url": { "type": "uri" },
|
||||
"web_url": { "type": "uri" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
"additionalProperties": false
|
||||
},
|
||||
"merged_at": { "type": ["string", "null"] },
|
||||
"closed_by": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"username": { "type": "string" },
|
||||
"id": { "type": "integer" },
|
||||
"state": { "type": "string" },
|
||||
"avatar_url": { "type": "uri" },
|
||||
"web_url": { "type": "uri" }
|
||||
},
|
||||
"closed_at": { "type": ["string", "null"], "format": "date-time" },
|
||||
"created_at": { "type": "string", "format": "date-time" },
|
||||
"updated_at": { "type": "string", "format": "date-time" },
|
||||
"target_branch": { "type": "string" },
|
||||
"source_branch": { "type": "string" },
|
||||
"upvotes": { "type": "integer" },
|
||||
"downvotes": { "type": "integer" },
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"username": { "type": "string" },
|
||||
"id": { "type": "integer" },
|
||||
"state": { "type": "string" },
|
||||
"avatar_url": { "type": "uri" },
|
||||
"web_url": { "type": "uri" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
"additionalProperties": false
|
||||
},
|
||||
"closed_at": { "type": ["string", "null"], "format": "date-time" },
|
||||
"created_at": { "type": "string", "format": "date-time" },
|
||||
"updated_at": { "type": "string", "format": "date-time" },
|
||||
"target_branch": { "type": "string" },
|
||||
"source_branch": { "type": "string" },
|
||||
"upvotes": { "type": "integer" },
|
||||
"downvotes": { "type": "integer" },
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"username": { "type": "string" },
|
||||
"id": { "type": "integer" },
|
||||
"state": { "type": "string" },
|
||||
"avatar_url": { "type": "uri" },
|
||||
"web_url": { "type": "uri" }
|
||||
},
|
||||
"assignee": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"username": { "type": "string" },
|
||||
"id": { "type": "integer" },
|
||||
"state": { "type": "string" },
|
||||
"avatar_url": { "type": "uri" },
|
||||
"web_url": { "type": "uri" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
"additionalProperties": false
|
||||
},
|
||||
"assignee": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"username": { "type": "string" },
|
||||
"id": { "type": "integer" },
|
||||
"state": { "type": "string" },
|
||||
"avatar_url": { "type": "uri" },
|
||||
"web_url": { "type": "uri" }
|
||||
},
|
||||
"assignees": {
|
||||
"items": {
|
||||
"$ref": "./merge_request.json"
|
||||
}
|
||||
},
|
||||
"source_project_id": { "type": "integer" },
|
||||
"target_project_id": { "type": "integer" },
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"work_in_progress": { "type": "boolean" },
|
||||
"milestone": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"iid": { "type": "integer" },
|
||||
"project_id": { "type": ["integer", "null"] },
|
||||
"group_id": { "type": ["integer", "null"] },
|
||||
"title": { "type": "string" },
|
||||
"description": { "type": ["string", "null"] },
|
||||
"state": { "type": "string" },
|
||||
"created_at": { "type": "string", "format": "date-time" },
|
||||
"updated_at": { "type": "string", "format": "date-time" },
|
||||
"due_date": { "type": "string", "format": "date-time" },
|
||||
"start_date": { "type": "string", "format": "date-time" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"merge_when_pipeline_succeeds": { "type": "boolean" },
|
||||
"merge_status": { "type": "string" },
|
||||
"sha": { "type": "string" },
|
||||
"merge_commit_sha": { "type": ["string", "null"] },
|
||||
"user_notes_count": { "type": "integer" },
|
||||
"changes_count": { "type": "string" },
|
||||
"should_remove_source_branch": { "type": ["boolean", "null"] },
|
||||
"force_remove_source_branch": { "type": ["boolean", "null"] },
|
||||
"discussion_locked": { "type": ["boolean", "null"] },
|
||||
"web_url": { "type": "uri" },
|
||||
"squash": { "type": "boolean" },
|
||||
"time_stats": {
|
||||
"time_estimate": { "type": "integer" },
|
||||
"total_time_spent": { "type": "integer" },
|
||||
"human_time_estimate": { "type": ["string", "null"] },
|
||||
"human_total_time_spent": { "type": ["string", "null"] }
|
||||
},
|
||||
"allow_collaboration": { "type": ["boolean", "null"] },
|
||||
"allow_maintainer_to_push": { "type": ["boolean", "null"] },
|
||||
"references": {
|
||||
"short": {"type": "string"},
|
||||
"relative": {"type": "string"},
|
||||
"full": {"type": "string"}
|
||||
"additionalProperties": false
|
||||
},
|
||||
"assignees": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "user/basic.json"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id", "iid", "project_id", "title", "description",
|
||||
"state", "created_at", "updated_at", "target_branch",
|
||||
"source_branch", "upvotes", "downvotes", "author",
|
||||
"assignee", "source_project_id", "target_project_id",
|
||||
"labels", "work_in_progress", "milestone", "merge_when_pipeline_succeeds",
|
||||
"merge_status", "sha", "merge_commit_sha", "user_notes_count",
|
||||
"should_remove_source_branch", "force_remove_source_branch",
|
||||
"web_url", "squash"
|
||||
],
|
||||
"head_pipeline": {
|
||||
"source_project_id": { "type": "integer" },
|
||||
"target_project_id": { "type": "integer" },
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"work_in_progress": { "type": "boolean" },
|
||||
"milestone": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "$ref": "pipeline/detail.json" }
|
||||
{ "$ref": "milestone.json" }
|
||||
]
|
||||
},
|
||||
"merge_when_pipeline_succeeds": { "type": "boolean" },
|
||||
"merge_status": { "type": "string" },
|
||||
"sha": { "type": "string" },
|
||||
"merge_commit_sha": { "type": ["string", "null"] },
|
||||
"user_notes_count": { "type": "integer" },
|
||||
"changes_count": { "type": "string" },
|
||||
"should_remove_source_branch": { "type": ["boolean", "null"] },
|
||||
"force_remove_source_branch": { "type": ["boolean", "null"] },
|
||||
"discussion_locked": { "type": ["boolean", "null"] },
|
||||
"web_url": { "type": "uri" },
|
||||
"squash": { "type": "boolean" },
|
||||
"time_stats": {
|
||||
"time_estimate": { "type": "integer" },
|
||||
"total_time_spent": { "type": "integer" },
|
||||
"human_time_estimate": { "type": ["string", "null"] },
|
||||
"human_total_time_spent": { "type": ["string", "null"] }
|
||||
},
|
||||
"allow_collaboration": { "type": ["boolean", "null"] },
|
||||
"allow_maintainer_to_push": { "type": ["boolean", "null"] },
|
||||
"references": {
|
||||
"short": {"type": "string"},
|
||||
"relative": {"type": "string"},
|
||||
"full": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id", "iid", "project_id", "title", "description",
|
||||
"state", "created_at", "updated_at", "target_branch",
|
||||
"source_branch", "upvotes", "downvotes", "author",
|
||||
"assignee", "source_project_id", "target_project_id",
|
||||
"labels", "work_in_progress", "milestone", "merge_when_pipeline_succeeds",
|
||||
"merge_status", "sha", "merge_commit_sha", "user_notes_count",
|
||||
"should_remove_source_branch", "force_remove_source_branch",
|
||||
"web_url", "squash"
|
||||
],
|
||||
"head_pipeline": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "$ref": "pipeline/detail.json" }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
import { GlDropdown } from '@gitlab/ui';
|
||||
import { getByText } from '@testing-library/dom';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dropdown.vue';
|
||||
|
||||
describe('MultiSelectDropdown Component', () => {
|
||||
it('renders items slot', () => {
|
||||
const wrapper = shallowMount(MultiSelectDropdown, {
|
||||
propsData: {
|
||||
text: '',
|
||||
headerText: '',
|
||||
},
|
||||
slots: {
|
||||
items: '<p>Test</p>',
|
||||
},
|
||||
});
|
||||
expect(getByText(wrapper.element, 'Test')).toBeDefined();
|
||||
});
|
||||
|
||||
it('renders search slot', () => {
|
||||
const wrapper = shallowMount(MultiSelectDropdown, {
|
||||
propsData: {
|
||||
text: '',
|
||||
headerText: '',
|
||||
},
|
||||
slots: {
|
||||
search: '<p>Search</p>',
|
||||
},
|
||||
stubs: {
|
||||
GlDropdown,
|
||||
},
|
||||
});
|
||||
expect(getByText(wrapper.element, 'Search')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
import { GlDatepicker } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import datePicker from '~/vue_shared/components/pikaday.vue';
|
||||
|
||||
describe('datePicker', () => {
|
||||
let wrapper;
|
||||
|
||||
const buildWrapper = (propsData = {}) => {
|
||||
wrapper = shallowMount(datePicker, {
|
||||
propsData,
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
it('should emit newDateSelected when GlDatePicker emits the input event', () => {
|
||||
const minDate = new Date();
|
||||
const maxDate = new Date();
|
||||
const selectedDate = new Date();
|
||||
const theDate = selectedDate.toISOString().slice(0, 10);
|
||||
|
||||
buildWrapper({ minDate, maxDate, selectedDate });
|
||||
|
||||
expect(wrapper.find(GlDatepicker).props()).toMatchObject({
|
||||
minDate,
|
||||
maxDate,
|
||||
value: selectedDate,
|
||||
});
|
||||
wrapper.find(GlDatepicker).vm.$emit('input', selectedDate);
|
||||
expect(wrapper.emitted('newDateSelected')[0][0]).toBe(theDate);
|
||||
});
|
||||
it('should emit the hidePicker event when GlDatePicker emits the close event', () => {
|
||||
buildWrapper();
|
||||
|
||||
wrapper.find(GlDatepicker).vm.$emit('close');
|
||||
|
||||
expect(wrapper.emitted('hidePicker')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import { nextTick } from 'vue';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
|
||||
import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
|
||||
|
||||
describe('CollapsedCalendarIcon', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
containerClass: 'test-class',
|
||||
text: 'text',
|
||||
tooltipText: 'tooltip text',
|
||||
showIcon: false,
|
||||
};
|
||||
|
||||
const createComponent = ({ props = {} } = {}) => {
|
||||
wrapper = shallowMount(CollapsedCalendarIcon, {
|
||||
propsData: { ...defaultProps, ...props },
|
||||
directives: {
|
||||
GlTooltip: createMockDirective(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const findGlIcon = () => wrapper.findComponent(GlIcon);
|
||||
const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip');
|
||||
|
||||
it('adds class to container', () => {
|
||||
expect(wrapper.classes()).toContain(defaultProps.containerClass);
|
||||
});
|
||||
|
||||
it('does not render calendar icon when showIcon is false', () => {
|
||||
expect(findGlIcon().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders calendar icon when showIcon is true', () => {
|
||||
createComponent({
|
||||
props: { showIcon: true },
|
||||
});
|
||||
|
||||
expect(findGlIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders text', () => {
|
||||
expect(wrapper.text()).toBe(defaultProps.text);
|
||||
});
|
||||
|
||||
it('renders tooltipText as tooltip', () => {
|
||||
expect(getTooltip().value).toBe(defaultProps.tooltipText);
|
||||
});
|
||||
|
||||
it('emits click event when container is clicked', async () => {
|
||||
wrapper.trigger('click');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.emitted('click')[0]).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import DatePicker from '~/vue_shared/components/pikaday.vue';
|
||||
import SidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
|
||||
|
||||
describe('SidebarDatePicker', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = (propsData = {}, data = {}) => {
|
||||
wrapper = mount(SidebarDatePicker, {
|
||||
propsData,
|
||||
data: () => data,
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const findDatePicker = () => wrapper.findComponent(DatePicker);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findEditButton = () => wrapper.find('.title .btn-blank');
|
||||
const findRemoveButton = () => wrapper.find('.value-content .btn-blank');
|
||||
const findSidebarToggle = () => wrapper.find('.title .gutter-toggle');
|
||||
const findValueContent = () => wrapper.find('.value-content');
|
||||
|
||||
it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => {
|
||||
createComponent();
|
||||
|
||||
wrapper.find('.issuable-sidebar-header .gutter-toggle').trigger('click');
|
||||
|
||||
expect(wrapper.emitted('toggleCollapse')).toEqual([[]]);
|
||||
});
|
||||
|
||||
it('should render collapsed-calendar-icon', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.find('.sidebar-collapsed-icon').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render value when not editing', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findValueContent().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render None if there is no selectedDate', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findValueContent().text()).toBe('None');
|
||||
});
|
||||
|
||||
it('should render date-picker when editing', () => {
|
||||
createComponent({}, { editing: true });
|
||||
|
||||
expect(findDatePicker().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render label', () => {
|
||||
const label = 'label';
|
||||
createComponent({ label });
|
||||
expect(wrapper.find('.title').text()).toBe(label);
|
||||
});
|
||||
|
||||
it('should render loading-icon when isLoading', () => {
|
||||
createComponent({ isLoading: true });
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('editable', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ editable: true });
|
||||
});
|
||||
|
||||
it('should render edit button', () => {
|
||||
expect(findEditButton().text()).toBe('Edit');
|
||||
});
|
||||
|
||||
it('should enable editing when edit button is clicked', async () => {
|
||||
findEditButton().trigger('click');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.editing).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render date if selectedDate', () => {
|
||||
createComponent({ selectedDate: new Date('07/07/2017') });
|
||||
|
||||
expect(wrapper.find('.value-content strong').text()).toBe('Jul 7, 2017');
|
||||
});
|
||||
|
||||
describe('selectedDate and editable', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ selectedDate: new Date('07/07/2017'), editable: true });
|
||||
});
|
||||
|
||||
it('should render remove button if selectedDate and editable', () => {
|
||||
expect(findRemoveButton().text()).toBe('remove');
|
||||
});
|
||||
|
||||
it('should emit saveDate with null when remove button is clicked', () => {
|
||||
findRemoveButton().trigger('click');
|
||||
|
||||
expect(wrapper.emitted('saveDate')).toEqual([[null]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('showToggleSidebar', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ showToggleSidebar: true });
|
||||
});
|
||||
|
||||
it('should render toggle-sidebar when showToggleSidebar', () => {
|
||||
expect(findSidebarToggle().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should emit toggleCollapse when toggle sidebar is clicked', () => {
|
||||
findSidebarToggle().trigger('click');
|
||||
|
||||
expect(wrapper.emitted('toggleCollapse')).toEqual([[]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -125,6 +125,17 @@ RSpec.describe Gitlab::ApplicationContext do
|
|||
.to include(project: project.full_path, root_namespace: project.full_path_components.first)
|
||||
end
|
||||
|
||||
it 'contains known keys' do
|
||||
context = described_class.new(project: project)
|
||||
|
||||
# Make sure all possible keys would be included
|
||||
allow(context).to receive_message_chain(:set_values, :include?).and_return(true)
|
||||
|
||||
# If a newly added key is added to the context hash, we need to list it in
|
||||
# the known keys constant. This spec ensures that we do.
|
||||
expect(context.to_lazy_hash.keys).to contain_exactly(*described_class.known_keys)
|
||||
end
|
||||
|
||||
describe 'setting the client' do
|
||||
let_it_be(:remote_ip) { '127.0.0.1' }
|
||||
let_it_be(:runner) { create(:ci_runner) }
|
||||
|
|
|
|||
|
|
@ -2612,6 +2612,12 @@ RSpec.describe User do
|
|||
it 'returns users with a exact matching name shorter than 3 chars regardless of the casing' do
|
||||
expect(described_class.search(user3.name.upcase)).to eq([user3])
|
||||
end
|
||||
|
||||
context 'when use_minimum_char_limit is false' do
|
||||
it 'returns users with a partially matching name' do
|
||||
expect(described_class.search('u', use_minimum_char_limit: false)).to eq([user3, user, user2])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'email matching' do
|
||||
|
|
@ -2671,6 +2677,12 @@ RSpec.describe User do
|
|||
it 'returns users with a exact matching username shorter than 3 chars regardless of the casing' do
|
||||
expect(described_class.search(user3.username.upcase)).to eq([user3])
|
||||
end
|
||||
|
||||
context 'when use_minimum_char_limit is false' do
|
||||
it 'returns users with a partially matching username' do
|
||||
expect(described_class.search('se', use_minimum_char_limit: false)).to eq([user3, user, user2])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns no matches for an empty string' do
|
||||
|
|
@ -2682,196 +2694,6 @@ RSpec.describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.search_without_secondary_emails' do
|
||||
let_it_be(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'someone.1@example.com' ) }
|
||||
let_it_be(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'another.2@example.com' ) }
|
||||
let_it_be(:email) { create(:email, user: another_user, email: 'alias@example.com') }
|
||||
|
||||
it 'returns users with a matching name' do
|
||||
expect(described_class.search_without_secondary_emails(user.name)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a partially matching name' do
|
||||
expect(described_class.search_without_secondary_emails(user.name[0..2])).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching name regardless of the casing' do
|
||||
expect(described_class.search_without_secondary_emails(user.name.upcase)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching email' do
|
||||
expect(described_class.search_without_secondary_emails(user.email)).to eq([user])
|
||||
end
|
||||
|
||||
it 'does not return users with a partially matching email' do
|
||||
expect(described_class.search_without_secondary_emails(user.email[1...-1])).to be_empty
|
||||
end
|
||||
|
||||
it 'returns users with a matching email regardless of the casing' do
|
||||
expect(described_class.search_without_secondary_emails(user.email.upcase)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching username' do
|
||||
expect(described_class.search_without_secondary_emails(user.username)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a partially matching username' do
|
||||
expect(described_class.search_without_secondary_emails(user.username[0..2])).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching username regardless of the casing' do
|
||||
expect(described_class.search_without_secondary_emails(user.username.upcase)).to eq([user])
|
||||
end
|
||||
|
||||
it 'does not return users with a matching whole secondary email' do
|
||||
expect(described_class.search_without_secondary_emails(email.email)).not_to include(email.user)
|
||||
end
|
||||
|
||||
it 'does not return users with a matching part of secondary email' do
|
||||
expect(described_class.search_without_secondary_emails(email.email[1...-1])).to be_empty
|
||||
end
|
||||
|
||||
it 'returns no matches for an empty string' do
|
||||
expect(described_class.search_without_secondary_emails('')).to be_empty
|
||||
end
|
||||
|
||||
it 'returns no matches for nil' do
|
||||
expect(described_class.search_without_secondary_emails(nil)).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '.search_with_public_emails' do
|
||||
let_it_be(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'someone.1@example.com' ) }
|
||||
let_it_be(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'another.2@example.com' ) }
|
||||
let_it_be(:public_email) do
|
||||
create(:email, :confirmed, user: another_user, email: 'alias@example.com').tap do |email|
|
||||
another_user.update!(public_email: email.email)
|
||||
end
|
||||
end
|
||||
|
||||
let_it_be(:secondary_email) do
|
||||
create(:email, :confirmed, user: another_user, email: 'secondary@example.com')
|
||||
end
|
||||
|
||||
it 'returns users with a matching name' do
|
||||
expect(described_class.search_with_public_emails(user.name)).to match_array([user])
|
||||
end
|
||||
|
||||
it 'returns users with a partially matching name' do
|
||||
expect(described_class.search_with_public_emails(user.name[0..2])).to match_array([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching name regardless of the casing' do
|
||||
expect(described_class.search_with_public_emails(user.name.upcase)).to match_array([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching public email' do
|
||||
expect(described_class.search_with_public_emails(another_user.public_email)).to match_array([another_user])
|
||||
end
|
||||
|
||||
it 'does not return users with a partially matching email' do
|
||||
expect(described_class.search_with_public_emails(another_user.public_email[1...-1])).to be_empty
|
||||
end
|
||||
|
||||
it 'returns users with a matching email regardless of the casing' do
|
||||
expect(described_class.search_with_public_emails(another_user.public_email.upcase)).to match_array([another_user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching username' do
|
||||
expect(described_class.search_with_public_emails(user.username)).to match_array([user])
|
||||
end
|
||||
|
||||
it 'returns users with a partially matching username' do
|
||||
expect(described_class.search_with_public_emails(user.username[0..2])).to match_array([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching username regardless of the casing' do
|
||||
expect(described_class.search_with_public_emails(user.username.upcase)).to match_array([user])
|
||||
end
|
||||
|
||||
it 'does not return users with a matching whole private email' do
|
||||
expect(described_class.search_with_public_emails(user.email)).not_to include(user)
|
||||
end
|
||||
|
||||
it 'does not return users with a matching whole private email' do
|
||||
expect(described_class.search_with_public_emails(secondary_email.email)).to be_empty
|
||||
end
|
||||
|
||||
it 'does not return users with a matching part of secondary email' do
|
||||
expect(described_class.search_with_public_emails(secondary_email.email[1...-1])).to be_empty
|
||||
end
|
||||
|
||||
it 'does not return users with a matching part of private email' do
|
||||
expect(described_class.search_with_public_emails(user.email[1...-1])).to be_empty
|
||||
end
|
||||
|
||||
it 'returns no matches for an empty string' do
|
||||
expect(described_class.search_with_public_emails('')).to be_empty
|
||||
end
|
||||
|
||||
it 'returns no matches for nil' do
|
||||
expect(described_class.search_with_public_emails(nil)).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '.search_with_secondary_emails' do
|
||||
let_it_be(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'someone.1@example.com' ) }
|
||||
let_it_be(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'another.2@example.com' ) }
|
||||
let_it_be(:email) { create(:email, user: another_user, email: 'alias@example.com') }
|
||||
|
||||
it 'returns users with a matching name' do
|
||||
expect(described_class.search_with_secondary_emails(user.name)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a partially matching name' do
|
||||
expect(described_class.search_with_secondary_emails(user.name[0..2])).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching name regardless of the casing' do
|
||||
expect(described_class.search_with_secondary_emails(user.name.upcase)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching email' do
|
||||
expect(described_class.search_with_secondary_emails(user.email)).to eq([user])
|
||||
end
|
||||
|
||||
it 'does not return users with a partially matching email' do
|
||||
expect(described_class.search_with_secondary_emails(user.email[1...-1])).to be_empty
|
||||
end
|
||||
|
||||
it 'returns users with a matching email regardless of the casing' do
|
||||
expect(described_class.search_with_secondary_emails(user.email.upcase)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching username' do
|
||||
expect(described_class.search_with_secondary_emails(user.username)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a partially matching username' do
|
||||
expect(described_class.search_with_secondary_emails(user.username[0..2])).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching username regardless of the casing' do
|
||||
expect(described_class.search_with_secondary_emails(user.username.upcase)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching whole secondary email' do
|
||||
expect(described_class.search_with_secondary_emails(email.email)).to eq([email.user])
|
||||
end
|
||||
|
||||
it 'does not return users with a matching part of secondary email' do
|
||||
expect(described_class.search_with_secondary_emails(email.email[1...-1])).to be_empty
|
||||
end
|
||||
|
||||
it 'returns no matches for an empty string' do
|
||||
expect(described_class.search_with_secondary_emails('')).to be_empty
|
||||
end
|
||||
|
||||
it 'returns no matches for nil' do
|
||||
expect(described_class.search_with_secondary_emails(nil)).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '.user_search_minimum_char_limit' do
|
||||
it 'returns true' do
|
||||
expect(described_class.user_search_minimum_char_limit).to be(true)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@ RSpec.describe Ci::RetryBuildService do
|
|||
artifacts_file artifacts_metadata artifacts_size commands
|
||||
resource resource_group_id processed security_scans author
|
||||
pipeline_id report_results pending_state pages_deployments
|
||||
queuing_entry runtime_metadata trace_metadata].freeze
|
||||
queuing_entry runtime_metadata trace_metadata
|
||||
dast_site_profile dast_scanner_profile].freeze
|
||||
|
||||
shared_examples 'build duplication' do
|
||||
let_it_be(:another_pipeline) { create(:ci_empty_pipeline, project: project) }
|
||||
|
|
|
|||
|
|
@ -388,7 +388,7 @@ RSpec.describe MergeRequests::MergeService do
|
|||
end
|
||||
|
||||
it 'logs and saves error if there is an error when squashing' do
|
||||
error_message = 'Failed to squash. Should be done manually'
|
||||
error_message = 'Squashing failed: Squash the commits locally, resolve any conflicts, then push the branch.'
|
||||
|
||||
allow_any_instance_of(MergeRequests::SquashService).to receive(:squash!).and_return(nil)
|
||||
merge_request.update!(squash: true)
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ RSpec.describe MergeRequests::RebaseService do
|
|||
|
||||
context 'with a pre-receive failure' do
|
||||
let(:pre_receive_error) { "Commit message does not follow the pattern 'ACME'" }
|
||||
let(:merge_error) { "Something went wrong during the rebase pre-receive hook: #{pre_receive_error}." }
|
||||
let(:merge_error) { "The rebase pre-receive hook failed: #{pre_receive_error}." }
|
||||
|
||||
before do
|
||||
allow(repository).to receive(:gitaly_operation_client).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{pre_receive_error}")
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ RSpec.describe MergeRequests::SquashService do
|
|||
it 'raises a squash error' do
|
||||
expect(service.execute).to match(
|
||||
status: :error,
|
||||
message: a_string_including('does not allow squashing commits when merge requests are accepted'))
|
||||
message: a_string_including('allow you to squash commits when merging'))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -205,7 +205,7 @@ RSpec.describe MergeRequests::SquashService do
|
|||
end
|
||||
|
||||
it 'returns an error' do
|
||||
expect(service.execute).to match(status: :error, message: a_string_including('squash'))
|
||||
expect(service.execute).to match(status: :error, message: a_string_including('Squash'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -232,7 +232,7 @@ RSpec.describe MergeRequests::SquashService do
|
|||
end
|
||||
|
||||
it 'returns an error' do
|
||||
expect(service.execute).to match(status: :error, message: a_string_including('squash'))
|
||||
expect(service.execute).to match(status: :error, message: a_string_including('Squash'))
|
||||
end
|
||||
|
||||
it 'cleans up the temporary directory' do
|
||||
|
|
|
|||
|
|
@ -36,12 +36,38 @@ end
|
|||
|
||||
RSpec::Matchers.define :match_response_schema do |schema, dir: nil, **options|
|
||||
match do |response|
|
||||
schema_path = Pathname.new(SchemaPath.expand(schema, dir))
|
||||
validator = SchemaPath.validator(schema_path)
|
||||
@schema_path = Pathname.new(SchemaPath.expand(schema, dir))
|
||||
validator = SchemaPath.validator(@schema_path)
|
||||
|
||||
data = Gitlab::Json.parse(response.body)
|
||||
@data = Gitlab::Json.parse(response.body)
|
||||
|
||||
validator.valid?(data)
|
||||
@schema_errors = validator.validate(@data)
|
||||
@schema_errors.none?
|
||||
end
|
||||
|
||||
failure_message do |actual|
|
||||
message = []
|
||||
|
||||
message << <<~MESSAGE
|
||||
expected JSON response to match schema #{@schema_path.inspect}.
|
||||
|
||||
JSON input: #{Gitlab::Json.pretty_generate(@data).indent(2)}
|
||||
|
||||
Schema errors:
|
||||
MESSAGE
|
||||
|
||||
@schema_errors.each do |error|
|
||||
property_name, actual_value = error.values_at('data_pointer', 'data')
|
||||
property_name = 'root' if property_name.empty?
|
||||
|
||||
message << <<~MESSAGE
|
||||
Property: #{property_name}
|
||||
Actual value: #{Gitlab::Json.pretty_generate(actual_value).indent(2)}
|
||||
Error: #{JSONSchemer::Errors.pretty(error)}
|
||||
MESSAGE
|
||||
end
|
||||
|
||||
message.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rake_helper'
|
||||
|
||||
RSpec.describe 'gitlab:env:info', :silence_stdout do
|
||||
before do
|
||||
Rake.application.rake_require 'tasks/gitlab/info'
|
||||
|
||||
stub_warn_user_is_not_gitlab
|
||||
allow(Gitlab::Popen).to receive(:popen)
|
||||
end
|
||||
|
||||
describe 'git version' do
|
||||
before do
|
||||
allow(Gitlab::Popen).to receive(:popen).with([Gitlab.config.git.bin_path, '--version'])
|
||||
.and_return(git_version)
|
||||
end
|
||||
|
||||
context 'when git installed' do
|
||||
let(:git_version) { 'git version 2.10.0' }
|
||||
|
||||
it 'prints git version' do
|
||||
run_rake_task('gitlab:env:info')
|
||||
|
||||
expect($stdout.string).to match(/Git Version:(.*)2.10.0/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when git not installed' do
|
||||
let(:git_version) { '' }
|
||||
|
||||
it 'prints unknown' do
|
||||
run_rake_task('gitlab:env:info')
|
||||
|
||||
expect($stdout.string).to match(/Git Version:(.*)unknown/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue