diff options
84 files changed, 486 insertions, 1366 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 8bcb41edfe9..d4cc38bf846 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -08c42adfc68c06661cf52363dacf52e2c347adff +87a19146fdc01d0132a278b661017705514207cf @@ -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' diff --git a/Gemfile.lock b/Gemfile.lock index eb19d981a4a..edfa7ac3679 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js index 25468d4a697..4664bb56958 100644 --- a/app/assets/javascripts/sidebar/sidebar_mediator.js +++ b/app/assets/javascripts/sidebar/sidebar_mediator.js @@ -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'; diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue deleted file mode 100644 index 3c0ac32e512..00000000000 --- a/app/assets/javascripts/vue_shared/components/pikaday.vue +++ /dev/null @@ -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> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue deleted file mode 100644 index 460a10e08ed..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue +++ /dev/null @@ -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> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue deleted file mode 100644 index 4531fafbf72..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue +++ /dev/null @@ -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> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/multiselect_dropdown.vue b/app/assets/javascripts/vue_shared/components/sidebar/multiselect_dropdown.vue deleted file mode 100644 index 17904f20341..00000000000 --- a/app/assets/javascripts/vue_shared/components/sidebar/multiselect_dropdown.vue +++ /dev/null @@ -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> diff --git a/app/assets/javascripts/vue_shared/components/svg_gradient.vue b/app/assets/javascripts/vue_shared/components/svg_gradient.vue deleted file mode 100644 index 5ce45d492f9..00000000000 --- a/app/assets/javascripts/vue_shared/components/svg_gradient.vue +++ /dev/null @@ -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> diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 1d7457e3c56..9cebd4f49a4 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -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 diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 89d59587cba..1004383cfd3 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -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; diff --git a/app/assets/stylesheets/page_bundles/milestone.scss b/app/assets/stylesheets/page_bundles/milestone.scss index 08d9d24d246..989219552a6 100644 --- a/app/assets/stylesheets/page_bundles/milestone.scss +++ b/app/assets/stylesheets/page_bundles/milestone.scss @@ -42,12 +42,6 @@ $status-box-line-height: 26px; } .milestone-content { - .issues-count { - margin-right: 17px; - float: right; - width: 105px; - } - .issuable-row { span { a { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index ad05cbd1f0e..d2a3d17e1bb 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -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; } diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss index f14298c2cf7..e4a1a6dc016 100644 --- a/app/assets/stylesheets/startup/startup-dark.scss +++ b/app/assets/stylesheets/startup/startup-dark.scss @@ -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; diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss index 7eced0f3b07..fb4ca0dc736 100644 --- a/app/assets/stylesheets/startup/startup-general.scss +++ b/app/assets/stylesheets/startup/startup-general.scss @@ -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; diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index b40e2affcee..cd3f65cf044 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -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]) diff --git a/app/finders/autocomplete/users_finder.rb b/app/finders/autocomplete/users_finder.rb index a9fffd3f411..33d9a8a3dbc 100644 --- a/app/finders/autocomplete/users_finder.rb +++ b/app/finders/autocomplete/users_finder.rb @@ -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 diff --git a/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb b/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb index c4f91d0c15c..b1db355aa40 100644 --- a/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb +++ b/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb @@ -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, diff --git a/app/models/member.rb b/app/models/member.rb index 1c1b603b4c7..b46a497dd69 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -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) diff --git a/app/models/user.rb b/app/models/user.rb index 65061305c02..313c1726429 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 + # searches user by 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? 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)) + 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 - 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)) - ) + def with_public_email(email_address) + where(public_email: email_address) end - # searches user by given pattern - # it compares name, email, username fields and user's secondary emails with given pattern - # This method uses ILIKE on PostgreSQL. - - def search_with_secondary_emails(query) - return none if query.blank? - - query = query.downcase - + 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 diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb index c633e2d8b3d..1549c099a64 100644 --- a/app/models/users_star_project.rb +++ b/app/models/users_star_project.rb @@ -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 diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb index 2371b3615ee..71588f714e8 100644 --- a/app/services/ci/retry_build_service.rb +++ b/app/services/ci/retry_build_service.rb @@ -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) diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb index d1f45b4b49c..1c4e1784b34 100644 --- a/app/services/merge_requests/rebase_service.rb +++ b/app/services/merge_requests/rebase_service.rb @@ -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 diff --git a/app/services/merge_requests/squash_service.rb b/app/services/merge_requests/squash_service.rb index 69b9740c2a5..f04682bf08a 100644 --- a/app/services/merge_requests/squash_service.rb +++ b/app/services/merge_requests/squash_service.rb @@ -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 diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 246a31f86c9..0a3cdb5b5cd 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -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 diff --git a/config/feature_flags/development/dast_sharded_cloned_ci_builds.yml b/config/feature_flags/development/dast_sharded_cloned_ci_builds.yml new file mode 100644 index 00000000000..f7d3f57403f --- /dev/null +++ b/config/feature_flags/development/dast_sharded_cloned_ci_builds.yml @@ -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 diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index c197e77fbf7..d52659d80e7 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -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 diff --git a/danger/sidekiq_queues/Dangerfile b/danger/sidekiq_queues/Dangerfile index b40be7a486e..de98af3cab2 100644 --- a/danger/sidekiq_queues/Dangerfile +++ b/danger/sidekiq_queues/Dangerfile @@ -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 diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index 62c403bfe43..9d6f646e984 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -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 --- diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 03bc80c5b83..d7b878fb1a9 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -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. | diff --git a/doc/api/job_artifacts.md b/doc/api/job_artifacts.md index 01ffd479acc..d272f259ddf 100644 --- a/doc/api/job_artifacts.md +++ b/doc/api/job_artifacts.md @@ -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 --- diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md index 6e9e9f0a81f..218036b1ee0 100644 --- a/doc/api/project_import_export.md +++ b/doc/api/project_import_export.md @@ -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 diff --git a/doc/api/visual_review_discussions.md b/doc/api/visual_review_discussions.md index 5b7aec3785b..7f91b829061 100644 --- a/doc/api/visual_review_discussions.md +++ b/doc/api/visual_review_discussions.md @@ -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)** diff --git a/doc/ci/cloud_services/aws/index.md b/doc/ci/cloud_services/aws/index.md index 51aa08fecbe..aa38562c866 100644 --- a/doc/ci/cloud_services/aws/index.md +++ b/doc/ci/cloud_services/aws/index.md @@ -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 --- diff --git a/doc/ci/cloud_services/index.md b/doc/ci/cloud_services/index.md index a0395730680..a80231a04c2 100644 --- a/doc/ci/cloud_services/index.md +++ b/doc/ci/cloud_services/index.md @@ -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 --- diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/index.md b/doc/ci/examples/end_to_end_testing_webdriverio/index.md index 9881c9657bc..4d247a4ff74 100644 --- a/doc/ci/examples/end_to_end_testing_webdriverio/index.md +++ b/doc/ci/examples/end_to_end_testing_webdriverio/index.md @@ -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 diff --git a/doc/ci/metrics_reports.md b/doc/ci/metrics_reports.md index eb302b9ed7f..5b472eec7b5 100644 --- a/doc/ci/metrics_reports.md +++ b/doc/ci/metrics_reports.md @@ -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)** diff --git a/doc/ci/pipelines/job_artifacts.md b/doc/ci/pipelines/job_artifacts.md index 8afe6243460..fa8041671dc 100644 --- a/doc/ci/pipelines/job_artifacts.md +++ b/doc/ci/pipelines/job_artifacts.md @@ -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)** diff --git a/doc/ci/pipelines/pipeline_artifacts.md b/doc/ci/pipelines/pipeline_artifacts.md index b174b6af9f9..e9dd1b2a942 100644 --- a/doc/ci/pipelines/pipeline_artifacts.md +++ b/doc/ci/pipelines/pipeline_artifacts.md @@ -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)** diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md index 2450939b12b..ed29b33bc3a 100644 --- a/doc/ci/review_apps/index.md +++ b/doc/ci/review_apps/index.md @@ -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)** diff --git a/doc/ci/unit_test_reports.md b/doc/ci/unit_test_reports.md index c2b285bb017..4e43d8792fb 100644 --- a/doc/ci/unit_test_reports.md +++ b/doc/ci/unit_test_reports.md @@ -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)** diff --git a/doc/ci/yaml/artifacts_reports.md b/doc/ci/yaml/artifacts_reports.md index 25fb861cfb7..e010dd21b9e 100644 --- a/doc/ci/yaml/artifacts_reports.md +++ b/doc/ci/yaml/artifacts_reports.md @@ -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 --- diff --git a/doc/install/installation.md b/doc/install/installation.md index 8217be02f45..878e9e3416c 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -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 diff --git a/doc/user/clusters/agent/repository.md b/doc/user/clusters/agent/repository.md index d8d30f2e681..8c1a4450598 100644 --- a/doc/user/clusters/agent/repository.md +++ b/doc/user/clusters/agent/repository.md @@ -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. diff --git a/doc/user/group/repositories_analytics/index.md b/doc/user/group/repositories_analytics/index.md index 2487ab188e8..6e3ea7d6c0f 100644 --- a/doc/user/group/repositories_analytics/index.md +++ b/doc/user/group/repositories_analytics/index.md @@ -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 --- diff --git a/doc/user/project/merge_requests/accessibility_testing.md b/doc/user/project/merge_requests/accessibility_testing.md index e67af8dc936..612f499bb65 100644 --- a/doc/user/project/merge_requests/accessibility_testing.md +++ b/doc/user/project/merge_requests/accessibility_testing.md @@ -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)** diff --git a/doc/user/project/merge_requests/browser_performance_testing.md b/doc/user/project/merge_requests/browser_performance_testing.md index a1caea39d30..9c7d9e2bf19 100644 --- a/doc/user/project/merge_requests/browser_performance_testing.md +++ b/doc/user/project/merge_requests/browser_performance_testing.md @@ -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)** diff --git a/doc/user/project/merge_requests/fail_fast_testing.md b/doc/user/project/merge_requests/fail_fast_testing.md index 20ef1746ca3..355661516a7 100644 --- a/doc/user/project/merge_requests/fail_fast_testing.md +++ b/doc/user/project/merge_requests/fail_fast_testing.md @@ -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)** diff --git a/doc/user/project/merge_requests/load_performance_testing.md b/doc/user/project/merge_requests/load_performance_testing.md index ba120080ef8..cc4ad186771 100644 --- a/doc/user/project/merge_requests/load_performance_testing.md +++ b/doc/user/project/merge_requests/load_performance_testing.md @@ -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)** diff --git a/doc/user/project/merge_requests/test_coverage_visualization.md b/doc/user/project/merge_requests/test_coverage_visualization.md index 728d28d293c..bd54eda42f5 100644 --- a/doc/user/project/merge_requests/test_coverage_visualization.md +++ b/doc/user/project/merge_requests/test_coverage_visualization.md @@ -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)** diff --git a/doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md b/doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md index 3e68c92e6f0..741ac325cca 100644 --- a/doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md +++ b/doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md @@ -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" --- diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index 6c20993431d..f26ac1318b1 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -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 diff --git a/lib/gitlab/application_context.rb b/lib/gitlab/application_context.rb index c3415c45b28..d93067c7e2f 100644 --- a/lib/gitlab/application_context.rb +++ b/lib/gitlab/application_context.rb @@ -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) diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb index f9ebefdd974..e520cade517 100644 --- a/lib/gitlab/import_export/command_line_util.rb +++ b/lib/gitlab/import_export/command_line_util.rb @@ -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 diff --git a/lib/gitlab/sidekiq_queue.rb b/lib/gitlab/sidekiq_queue.rb index 67a9d8120d8..e22f09b4976 100644 --- a/lib/gitlab/sidekiq_queue.rb +++ b/lib/gitlab/sidekiq_queue.rb @@ -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' diff --git a/lib/system_check/app/git_config_check.rb b/lib/system_check/app/git_config_check.rb deleted file mode 100644 index d0b64b8bfeb..00000000000 --- a/lib/system_check/app/git_config_check.rb +++ /dev/null @@ -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 diff --git a/lib/system_check/app/git_version_check.rb b/lib/system_check/app/git_version_check.rb deleted file mode 100644 index 6512b142969..00000000000 --- a/lib/system_check/app/git_version_check.rb +++ /dev/null @@ -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 diff --git a/lib/system_check/rake_task/app_task.rb b/lib/system_check/rake_task/app_task.rb index 892417d67ec..1eb7a35b40a 100644 --- a/lib/system_check/rake_task/app_task.rb +++ b/lib/system_check/rake_task/app_task.rb @@ -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, diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake index 02764b5d46f..6f42bf8c946 100644 --- a/lib/tasks/gitlab/info.rake +++ b/lib/tasks/gitlab/info.rake @@ -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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0d87a7df42b..0f766d48a35 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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." +msgid "MergeRequests|Saving the comment failed" msgstr "" -msgid "MergeRequests|Saving the comment failed" +msgid "MergeRequests|Squashing failed: Squash the commits locally, resolve any conflicts, then push the branch." msgstr "" -msgid "MergeRequests|This project does not allow squashing commits when merge requests are accepted." +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 "" diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index cf8e0564041..eab428a61e7 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -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 diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb index 847b9b7ff71..a6655471591 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb @@ -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 } diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb index dc981ebceab..bb4b0472398 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb @@ -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' diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb index 332133b6a4e..d656ea4dea5 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb @@ -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' diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb index 07ae8ae1372..421dbe56a99 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb @@ -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' diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb index fea68114ae0..b7f0a10c525 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb @@ -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' diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb index 098c0b3ba63..5487ecff028 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb @@ -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 diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb index 8e938fef155..6700ec07765 100644 --- a/spec/features/dashboard/issuables_counter_spec.rb +++ b/spec/features/dashboard/issuables_counter_spec.rb @@ -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 diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index 6239702edde..7507ef4e453 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -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 diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index 315d1c911a2..d63d21353e5 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -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 diff --git a/spec/finders/autocomplete/users_finder_spec.rb b/spec/finders/autocomplete/users_finder_spec.rb index df7a14d440e..9b3421d1b4f 100644 --- a/spec/finders/autocomplete/users_finder_spec.rb +++ b/spec/finders/autocomplete/users_finder_spec.rb @@ -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 diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_request.json b/spec/fixtures/api/schemas/public_api/v4/merge_request.json index a55c4b8974b..66f894e9c5c 100644 --- a/spec/fixtures/api/schemas/public_api/v4/merge_request.json +++ b/spec/fixtures/api/schemas/public_api/v4/merge_request.json @@ -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 - }, - "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 - }, - "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 - }, - "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 - }, - "assignees": { - "items": { - "$ref": "./merge_request.json" - } + "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" } }, - "source_project_id": { "type": "integer" }, - "target_project_id": { "type": "integer" }, - "labels": { - "type": "array", - "items": { - "type": "string" - } + "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" } }, - "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 + "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" } }, - "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"] } + "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" } }, - "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" + } + }, + "source_project_id": { "type": "integer" }, + "target_project_id": { "type": "integer" }, + "labels": { + "type": "array", + "items": { + "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": { + "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" } + ] } } diff --git a/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js b/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js deleted file mode 100644 index 566ca1817f2..00000000000 --- a/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js +++ /dev/null @@ -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(); - }); -}); diff --git a/spec/frontend/vue_shared/components/pikaday_spec.js b/spec/frontend/vue_shared/components/pikaday_spec.js deleted file mode 100644 index fed4ce5e696..00000000000 --- a/spec/frontend/vue_shared/components/pikaday_spec.js +++ /dev/null @@ -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); - }); -}); diff --git a/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js b/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js deleted file mode 100644 index d4b43caa7b2..00000000000 --- a/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js +++ /dev/null @@ -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(); - }); -}); diff --git a/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js deleted file mode 100644 index ae05d85f74f..00000000000 --- a/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js +++ /dev/null @@ -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([[]]); - }); - }); -}); diff --git a/spec/lib/gitlab/application_context_spec.rb b/spec/lib/gitlab/application_context_spec.rb index 5ecec978017..55f5ae7d7dc 100644 --- a/spec/lib/gitlab/application_context_spec.rb +++ b/spec/lib/gitlab/application_context_spec.rb @@ -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) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 31bcb3e2bbf..58f58232d52 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -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,204 +2677,20 @@ 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 - end - - it 'returns no matches for an empty string' do - expect(described_class.search('')).to be_empty - end - - it 'returns no matches for nil' do - expect(described_class.search(nil)).to be_empty - 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) + 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 - 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 + expect(described_class.search('')).to be_empty end it 'returns no matches for nil' do - expect(described_class.search_with_secondary_emails(nil)).to be_empty + expect(described_class.search(nil)).to be_empty end end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 0f24883bd7b..2421fd56c47 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -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) } diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 127c94763d9..ecb856bd1a4 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -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) diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb index e671bbf2cd6..a47e626666b 100644 --- a/spec/services/merge_requests/rebase_service_spec.rb +++ b/spec/services/merge_requests/rebase_service_spec.rb @@ -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}") diff --git a/spec/services/merge_requests/squash_service_spec.rb b/spec/services/merge_requests/squash_service_spec.rb index e5bea0e7b14..387be8471b5 100644 --- a/spec/services/merge_requests/squash_service_spec.rb +++ b/spec/services/merge_requests/squash_service_spec.rb @@ -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 diff --git a/spec/support/matchers/schema_matcher.rb b/spec/support/matchers/schema_matcher.rb index 5e08e96f4e1..d2f32b60464 100644 --- a/spec/support/matchers/schema_matcher.rb +++ b/spec/support/matchers/schema_matcher.rb @@ -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 diff --git a/spec/tasks/gitlab/info_rake_spec.rb b/spec/tasks/gitlab/info_rake_spec.rb deleted file mode 100644 index 19ed43723e2..00000000000 --- a/spec/tasks/gitlab/info_rake_spec.rb +++ /dev/null @@ -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 |