diff options
Diffstat (limited to 'app')
22 files changed, 119 insertions, 532 deletions
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 |
