diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-21 21:10:53 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-21 21:10:53 +0300 |
commit | d5ff0674315196e88f48dc0838486b44cd005628 (patch) | |
tree | 654721afd199b913a4845b6a92a20dd230482d4c /app | |
parent | f1c788bb1836083e4f5ed91c1c7494244e6e13ee (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
27 files changed, 424 insertions, 169 deletions
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index 8c1cab20ece..c9fd25171aa 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -419,6 +419,7 @@ export default { <div :id="`diff-content-${file.file_hash}`" :class="hasBodyClasses.contentByHash" + class="diff-content" data-testid="content-area" > <gl-alert diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue index 756585683c8..87021db739a 100644 --- a/app/assets/javascripts/issues/show/components/app.vue +++ b/app/assets/javascripts/issues/show/components/app.vue @@ -22,6 +22,8 @@ import PinnedLinks from './pinned_links.vue'; import StickyHeader from './sticky_header.vue'; import TitleComponent from './title.vue'; +const STICKY_HEADER_VISIBLE_CLASS = 'issuable-sticky-header-visible'; + export default { components: { HeaderActions, @@ -322,6 +324,7 @@ export default { eventHub.$off('close.form', this.closeForm); eventHub.$off('open.form', this.openForm); window.removeEventListener('beforeunload', this.handleBeforeUnloadEvent); + this.hideStickyHeader(); }, methods: { handleBeforeUnloadEvent(e) { @@ -472,6 +475,8 @@ export default { hideStickyHeader() { this.isStickyHeaderShowing = false; + + document.body.classList?.remove(STICKY_HEADER_VISIBLE_CLASS); }, showStickyHeader() { @@ -479,6 +484,8 @@ export default { if (this.$refs.title.$el.offsetTop < window.pageYOffset) { this.isStickyHeaderShowing = true; } + + document.body.classList?.add(STICKY_HEADER_VISIBLE_CLASS); }, handleSaveDescription(description) { diff --git a/app/assets/javascripts/super_sidebar/components/counter.vue b/app/assets/javascripts/super_sidebar/components/counter.vue index 49efc5ab5b9..4c49aabcf93 100644 --- a/app/assets/javascripts/super_sidebar/components/counter.vue +++ b/app/assets/javascripts/super_sidebar/components/counter.vue @@ -48,7 +48,7 @@ export default { :is="component" :aria-label="ariaLabel" :href="href" - class="counter gl-display-block gl-flex-grow-1 gl-text-center gl-py-3 gl-bg-gray-10 gl-rounded-base gl-text-gray-900 gl-border-none gl-inset-border-1-gray-a-08 gl-line-height-1 gl-font-sm gl-hover-text-gray-900 gl-hover-text-decoration-none gl-focus--focus" + class="counter gl-display-block gl-flex-grow-1 gl-text-center gl-py-3 gl-rounded-base gl-border-none gl-inset-border-1-gray-a-08 gl-line-height-1 gl-font-sm gl-hover-text-decoration-none gl-focus--focus" > <gl-icon aria-hidden="true" :name="icon" /> <span v-if="count" aria-hidden="true" class="gl-ml-1">{{ formattedCount }}</span> diff --git a/app/assets/javascripts/super_sidebar/components/help_center.vue b/app/assets/javascripts/super_sidebar/components/help_center.vue index 069987d4006..752f077ca02 100644 --- a/app/assets/javascripts/super_sidebar/components/help_center.vue +++ b/app/assets/javascripts/super_sidebar/components/help_center.vue @@ -215,7 +215,11 @@ export default { @hidden="trackDropdownToggle(false)" > <template #toggle> - <gl-button category="tertiary" icon="question-o" class="btn-with-notification"> + <gl-button + category="tertiary" + icon="question-o" + class="super-sidebar-help-center-toggle btn-with-notification" + > <span v-if="showWhatsNewNotification" data-testid="notification-dot" diff --git a/app/assets/javascripts/super_sidebar/components/nav_item.vue b/app/assets/javascripts/super_sidebar/components/nav_item.vue index 3ae33bf8b37..f08cf7e5de3 100644 --- a/app/assets/javascripts/super_sidebar/components/nav_item.vue +++ b/app/assets/javascripts/super_sidebar/components/nav_item.vue @@ -229,7 +229,7 @@ export default { > <div :class="[isActive ? 'gl-opacity-10' : 'gl-opacity-0']" - class="active-indicator gl-bg-blue-500 gl-absolute gl-left-2 gl-top-2 gl-bottom-2 gl-transition-slow" + class="active-indicator gl-absolute gl-left-2 gl-top-2 gl-bottom-2 gl-transition-slow" aria-hidden="true" :style="activeIndicatorStyle" data-testid="active-indicator" diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 24211833026..e80f5c7f092 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -361,7 +361,7 @@ export default { <template> <div ref="gl-form" - class="js-vue-markdown-field md-area position-relative gfm-form gl-overflow-hidden" + class="js-vue-markdown-field md-area position-relative gfm-form" :data-uploads-path="uploadsPath" > <markdown-header diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index cc3c95a047b..5ee19c80592 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -254,7 +254,10 @@ export default { </script> <template> - <div class="md-header gl-border-b gl-border-gray-100 gl-px-3"> + <div + class="md-header gl-bg-white gl-border-b gl-border-gray-100 gl-rounded-lg gl-rounded-bottom-left-none gl-rounded-bottom-right-none gl-px-3" + :class="{ 'md-header-preview': previewMarkdown }" + > <div class="gl-display-flex gl-align-items-center gl-flex-wrap"> <div data-testid="md-header-toolbar" diff --git a/app/assets/stylesheets/framework/brand_logo.scss b/app/assets/stylesheets/framework/brand_logo.scss index 1bc1ef797a7..95dcb26c0c5 100644 --- a/app/assets/stylesheets/framework/brand_logo.scss +++ b/app/assets/stylesheets/framework/brand_logo.scss @@ -1,6 +1,3 @@ -$brand-logo-light-background: #e0dfe5; -$brand-logo-dark-background: #53515b; - .brand-logo { display: inline-block; @include gl-rounded-base; @@ -16,14 +13,4 @@ $brand-logo-dark-background: #53515b; &:active { @include gl-focus; } - - &:hover, - &:focus, - &:active { - background-color: $brand-logo-light-background; - - .gl-dark & { - background-color: $brand-logo-dark-background; - } - } } diff --git a/app/assets/stylesheets/framework/diffs.scss b/app/assets/stylesheets/framework/diffs.scss index 8f07ef73554..fff42c0973c 100644 --- a/app/assets/stylesheets/framework/diffs.scss +++ b/app/assets/stylesheets/framework/diffs.scss @@ -1,3 +1,5 @@ +$diff-file-header: 41px; + // Common .diff-file { margin-bottom: $gl-padding; @@ -38,6 +40,10 @@ &.is-sidebar-moved { top: calc(#{$calc-application-header-height} + #{$mr-sticky-header-height} - #{$gl-border-size-1}); + + + .diff-content .md-header-preview { + top: calc(#{$calc-application-header-height} + #{$mr-sticky-header-height} + #{$diff-file-header} - #{$gl-border-size-1}); + } } &::before { diff --git a/app/assets/stylesheets/framework/super_sidebar.scss b/app/assets/stylesheets/framework/super_sidebar.scss index fbf9d8c8ca6..add5758090f 100644 --- a/app/assets/stylesheets/framework/super_sidebar.scss +++ b/app/assets/stylesheets/framework/super_sidebar.scss @@ -1,5 +1,5 @@ -@mixin active-toggle { - background-color: $gray-50 !important; +@mixin active-toggle($background-color: var(--super-sidebar-user-bar-button-hover-bg)) { + background-color: $background-color !important; mix-blend-mode: multiply; .gl-dark & { @@ -12,7 +12,7 @@ $super-sidebar-transition-hint-duration: $super-sidebar-transition-duration / 4; @mixin notification-dot($color, $size, $top, $left) { background-color: $color; - border: 2px solid $gray-10; // Same as the sidebar's background color. + border: 2px solid var(--super-sidebar-bg); position: absolute; height: $size; width: $size; @@ -29,13 +29,25 @@ $super-sidebar-transition-hint-duration: $super-sidebar-transition-duration / 4; } .super-sidebar { + --super-sidebar-bg: var(--gray-10, #{$gray-10}); + --super-sidebar-primary: var(--blue-500, #{$blue-500}); + --super-sidebar-notification-dot: var(--blue-500, #{$blue-500}); + --super-sidebar-user-bar-bg: #{$t-gray-a-04}; + --super-sidebar-user-bar-button-bg: var(--gray-10, #{$gray-10}); + --super-sidebar-user-bar-button-hover-bg: var(--gray-50, #{$gray-50}); + --super-sidebar-user-bar-button-color: var(--gray-900, #{$gray-900}); + --super-sidebar-user-bar-button-hover-color: var(--gray-900, #{$gray-900}); + // Separate values provided to use `---gray-600` in dark mode + --super-sidebar-user-bar-button-icon-color: var(--gray-600, #{$gray-500}); + --super-sidebar-user-bar-button-icon-hover-color: var(--gray-700, #{$gray-700}); + display: flex; flex-direction: column; position: fixed; top: $calc-application-bars-height; bottom: $calc-application-footer-height; left: 0; - background-color: var(--gray-10, $gray-10); + background-color: var(--super-sidebar-bg); border-right: 1px solid $t-gray-a-08; transform: translate3d(0, 0, 0); width: $super-sidebar-width; @@ -55,7 +67,7 @@ $super-sidebar-transition-hint-duration: $super-sidebar-transition-duration / 4; } .user-bar { - background-color: $t-gray-a-04; + background-color: var(--super-sidebar-user-bar-bg); .user-bar-item { @include gl-rounded-base; @@ -76,39 +88,77 @@ $super-sidebar-transition-hint-duration: $super-sidebar-transition-duration / 4; @include active-toggle; } } - } - .counter .gl-icon, - .item-icon { - color: var(--gray-600, $gray-500); - } + .brand-logo { + &:hover, + &:focus { + background-color: var(--super-sidebar-user-bar-button-hover-bg); + mix-blend-mode: multiply; + + .gl-dark & { + mix-blend-mode: screen; + } + } + } - .counter:hover, - .counter:focus, - .counter[aria-expanded='true'] { - background-color: $gray-50; - border-color: transparent; - mix-blend-mode: multiply; + .btn-default-tertiary, + .counter { + color: var(--super-sidebar-user-bar-button-color); - .gl-dark & { - mix-blend-mode: screen; + .gl-icon { + color: var(--super-sidebar-user-bar-button-icon-color) !important; + } + + &:hover, + &:focus { + background-color: var(--super-sidebar-user-bar-button-hover-bg) !important; + color: var(--super-sidebar-user-bar-button-hover-color); + } + } + + .counter { + background-color: var(--super-sidebar-user-bar-button-bg); + + &:hover, + &:focus, + &[aria-expanded='true'] { + background-color: var(--super-sidebar-user-bar-button-hover-bg); + border-color: transparent; + mix-blend-mode: multiply; + + .gl-icon { + color: var(--super-sidebar-user-bar-button-icon-hover-color); + } + + .gl-dark & { + mix-blend-mode: screen; + } + } + + &:hover, + &[aria-expanded='true'] { + box-shadow: none; + } } + } + + .item-icon { + color: $gray-500; - .gl-icon { - color: var(--gray-700, $gray-700); + .gl-dark & { + color: $gray-600; } } - .counter:hover, - .counter[aria-expanded='true'] { - box-shadow: none; + .active-indicator { + background-color: var(--super-sidebar-primary); } .btn-with-notification { position: relative; .notification-dot-info { - @include notification-dot($blue-500, 9px, 5px, 22px); + @include notification-dot(var(--super-sidebar-notification-dot), 9px, 5px, 22px); } .notification-dot-warning { @@ -118,7 +168,7 @@ $super-sidebar-transition-hint-duration: $super-sidebar-transition-duration / 4; &:hover, &:focus { .notification { - border-color: $gray-50; // Same as the button's hover background color. + background-color: var(--super-sidebar-user-bar-button-hover-bg); } } } @@ -137,6 +187,10 @@ $super-sidebar-transition-hint-duration: $super-sidebar-transition-duration / 4; } } + .super-sidebar-help-center-toggle[aria-expanded='true'] { + @include active-toggle($gray-50); + } + #trial-status-sidebar-widget:hover { text-decoration: none; @include gl-text-contrast-light; diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index a908d49ae38..5422fc0ec0a 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -1126,6 +1126,10 @@ $tabs-holder-z-index: 250; .submit-review-dropdown { margin-left: $grid-size; + + .md-header { + top: -$gl-spacing-scale-2; + } } } @@ -1214,3 +1218,7 @@ $tabs-holder-z-index: 250; @include gl-rounded-top-right-none; } } + +.merge-request-overview .md-header { + top: calc(#{$calc-application-header-height} + #{$mr-sticky-header-height}); +} diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index e82a689fe5d..052e3326318 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -268,3 +268,25 @@ ul.related-merge-requests > li gl-emoji { .issuable-header-slide-leave-to { transform: translateY(-100%); } + +.issuable-sticky-header-visible { + --issuable-sticky-header-height: 40px; +} + +.md-header-preview { + z-index: 1; + position: sticky; + top: calc(#{$calc-application-header-height} + var(--issuable-sticky-header-height, 0px)); +} + +.detail-page-description .md-header { + top: $calc-application-header-height; +} + +.gl-drawer .md-header { + top: 0; +} + +.gl-modal .md-header { + top: -$gl-padding-8; +} diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index ec9bdc35337..eb2061081ae 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -264,11 +264,6 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio; display: block; position: relative; - .timeline-discussion-body { - overflow-x: auto; - overflow-y: hidden; - } - .diff-content { overflow: visible; padding: 0; @@ -330,8 +325,6 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio; .note-body { padding: 0 $gl-padding-8 $gl-padding-8; - overflow-x: auto; - overflow-y: hidden; .note-text { word-wrap: break-word; @@ -747,8 +740,6 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio; } .timeline-content { - overflow-x: auto; - overflow-y: hidden; border-radius: $gl-border-radius-base; padding: $gl-padding-8 !important; @include gl-border; diff --git a/app/assets/stylesheets/themes/theme_helper.scss b/app/assets/stylesheets/themes/theme_helper.scss index 9d4f6cabcd9..6839d236cdc 100644 --- a/app/assets/stylesheets/themes/theme_helper.scss +++ b/app/assets/stylesheets/themes/theme_helper.scss @@ -273,61 +273,32 @@ $theme-color, $theme-color-darkest, ) { - --sidebar-background: #{mix(white, $theme-color-lightest, 50%)}; - --transparent-white-16: rgba(255, 255, 255, 0.16); - --transparent-white-24: rgba(255, 255, 255, 0.24); - .super-sidebar { - background-color: var(--sidebar-background); - } - - .super-sidebar .user-bar { - background-color: $theme-color; - - .counter { - background-color: var(--transparent-white-16) !important; - } - - .brand-logo, - .btn-default-tertiary, - .counter { - color: $theme-color-lightest; - mix-blend-mode: normal; - - &:hover, - &:focus { - background-color: var(--transparent-white-24) !important; - color: $white; - } - - .gl-icon { - color: $theme-color-light; + --super-sidebar-bg: #{mix(white, $theme-color-lightest, 50%)}; + --super-sidebar-user-bar-bg: #{$theme-color}; + --super-sidebar-user-bar-button-bg: rgba(255, 255, 255, 0.16); + --super-sidebar-user-bar-button-hover-bg: rgba(255, 255, 255, 0.24); + --super-sidebar-user-bar-button-color: #{$theme-color-lightest}; + --super-sidebar-user-bar-button-hover-color: #{$white}; + --super-sidebar-user-bar-button-icon-color: #{$theme-color-light}; + --super-sidebar-user-bar-button-icon-hover-color: #{$theme-color-light}; + --super-sidebar-primary: #{$theme-color}; + --super-sidebar-notification-dot: #{$theme-color-darkest}; + + .user-bar { + .brand-logo, + .btn-default-tertiary, + .counter { + mix-blend-mode: normal; } } - } - .super-sidebar hr { - mix-blend-mode: multiply; - } - - .btn-with-notification { - &:hover, - &:focus { + hr { mix-blend-mode: multiply; } - .notification-dot-info { - background-color: $theme-color-darkest; - border-color: $theme-color-lightest; - + .super-sidebar-context-header { + color: var(--super-sidebar-primary); } } - - .active-indicator { - background-color: $theme-color; - } - - .super-sidebar-context-header { - color: $theme-color; - } } diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb index 163e741d990..a990837826d 100644 --- a/app/models/audit_event.rb +++ b/app/models/audit_event.rb @@ -45,6 +45,10 @@ class AuditEvent < ApplicationRecord # https://gitlab.com/groups/gitlab-org/-/epics/2765 after_validation :parallel_persist + def self.supported_keyset_orderings + { id: [:desc] } + end + def self.order_by(method) case method.to_s when 'created_asc' diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 4b548405bb9..6d8b2711734 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -217,6 +217,10 @@ module Ci job_variables_attributes resource_group scheduling_type ci_stage partition_id id_tokens].freeze end + + def supported_keyset_orderings + { id: [:desc] } + end end state_machine :status do diff --git a/app/models/ci/sources/pipeline.rb b/app/models/ci/sources/pipeline.rb index 475d57ee4c8..3c6a5563846 100644 --- a/app/models/ci/sources/pipeline.rb +++ b/app/models/ci/sources/pipeline.rb @@ -6,11 +6,6 @@ module Ci include Ci::Partitionable include Ci::NamespacedModelName include SafelyChangeColumnDefault - include IgnorableColumns - - ignore_columns [ - :pipeline_id_convert_to_bigint, :source_pipeline_id_convert_to_bigint - ], remove_with: '16.6', remove_after: '2023-10-22' columns_changing_default :partition_id, :source_partition_id diff --git a/app/models/concerns/ci/partitionable.rb b/app/models/concerns/ci/partitionable.rb index 447603c1635..03cce1edf74 100644 --- a/app/models/concerns/ci/partitionable.rb +++ b/app/models/concerns/ci/partitionable.rb @@ -19,42 +19,6 @@ module Ci extend ActiveSupport::Concern include ::Gitlab::Utils::StrongMemoize - module Testing - InclusionError = Class.new(StandardError) - - PARTITIONABLE_MODELS = %w[ - CommitStatus - Ci::BuildMetadata - Ci::BuildNeed - Ci::BuildReportResult - Ci::BuildRunnerSession - Ci::BuildTraceChunk - Ci::BuildTraceMetadata - Ci::BuildPendingState - Ci::JobAnnotation - Ci::JobArtifact - Ci::JobVariable - Ci::Pipeline - Ci::PendingBuild - Ci::RunningBuild - Ci::RunnerManagerBuild - Ci::PipelineVariable - Ci::Sources::Pipeline - Ci::Stage - Ci::UnitTestFailure - ].freeze - - def self.check_inclusion(klass) - return if PARTITIONABLE_MODELS.include?(klass.name) - - raise Partitionable::Testing::InclusionError, - "#{klass} must be included in PARTITIONABLE_MODELS" - - rescue InclusionError => e - Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e) - end - end - included do Partitionable::Testing.check_inclusion(self) diff --git a/app/models/concerns/ci/partitionable/testing.rb b/app/models/concerns/ci/partitionable/testing.rb new file mode 100644 index 00000000000..b961d72db94 --- /dev/null +++ b/app/models/concerns/ci/partitionable/testing.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Ci + module Partitionable + module Testing + InclusionError = Class.new(StandardError) + + PARTITIONABLE_MODELS = %w[ + CommitStatus + Ci::BuildMetadata + Ci::BuildNeed + Ci::BuildReportResult + Ci::BuildRunnerSession + Ci::BuildTraceChunk + Ci::BuildTraceMetadata + Ci::BuildPendingState + Ci::JobAnnotation + Ci::JobArtifact + Ci::JobVariable + Ci::Pipeline + Ci::PendingBuild + Ci::RunningBuild + Ci::RunnerManagerBuild + Ci::PipelineVariable + Ci::Sources::Pipeline + Ci::Stage + Ci::UnitTestFailure + ].freeze + + def self.check_inclusion(klass) + return if partitionable_models.include?(klass.name) + + raise Partitionable::Testing::InclusionError, + "#{klass} must be included in PARTITIONABLE_MODELS" + + rescue InclusionError => e + Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e) + end + + def self.partitionable_models + PARTITIONABLE_MODELS + end + end + end +end + +Ci::Partitionable::Testing.prepend_mod diff --git a/app/models/group.rb b/app/models/group.rb index 90b4067b037..59383b51bb9 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -29,6 +29,10 @@ class Group < Namespace 'Group' end + def self.supported_keyset_orderings + { name: [:asc] } + end + has_many :all_group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent has_many :group_members, -> { where(requested_at: nil).where.not(members: { access_level: Gitlab::Access::MINIMAL_ACCESS }) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent has_many :namespace_members, -> { where(requested_at: nil).where.not(members: { access_level: Gitlab::Access::MINIMAL_ACCESS }).unscope(where: %i[source_id source_type]) }, diff --git a/app/models/packages/build_info.rb b/app/models/packages/build_info.rb index 61e2194006b..6b200302aae 100644 --- a/app/models/packages/build_info.rb +++ b/app/models/packages/build_info.rb @@ -9,4 +9,8 @@ class Packages::BuildInfo < ApplicationRecord scope :order_by_pipeline_id, -> (direction) { order(pipeline_id: direction) } scope :with_pipeline_id_less_than, -> (pipeline_id) { where("#{table_name}.pipeline_id < ?", pipeline_id) } scope :with_pipeline_id_greater_than, -> (pipeline_id) { where("#{table_name}.pipeline_id > ?", pipeline_id) } + + def self.supported_keyset_orderings + { id: [:desc] } + end end diff --git a/app/models/pages/lookup_path.rb b/app/models/pages/lookup_path.rb index e5e23c3bb84..d79e4a5c12e 100644 --- a/app/models/pages/lookup_path.rb +++ b/app/models/pages/lookup_path.rb @@ -4,10 +4,11 @@ module Pages class LookupPath include Gitlab::Utils::StrongMemoize - def initialize(project, trim_prefix: nil, domain: nil) - @project = project + def initialize(deployment:, domain: nil, trim_prefix: nil) + @deployment = deployment + @project = deployment.project @domain = domain - @trim_prefix = trim_prefix || project.full_path + @trim_prefix = trim_prefix || @project.full_path end def project_id @@ -45,11 +46,7 @@ module Pages strong_memoize_attr :source def prefix - if url_builder.namespace_pages? - '/' - else - "#{project.full_path.delete_prefix(trim_prefix)}/" - end + ensure_leading_and_trailing_slash(prefix_value) end strong_memoize_attr :prefix @@ -73,23 +70,24 @@ module Pages private - attr_reader :project, :trim_prefix, :domain - - # project.active_pages_deployments is already loaded from the database, - # so selecting from the array to avoid N+1 - # this will change with when serving multiple versions on - # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133261 - def deployment - project - .active_pages_deployments - .to_a - .find { |deployment| deployment.path_prefix.blank? } - end - strong_memoize_attr :deployment + attr_reader :project, :deployment, :trim_prefix, :domain def url_builder Gitlab::Pages::UrlBuilder.new(project) end strong_memoize_attr :url_builder + + def prefix_value + return deployment.path_prefix if url_builder.namespace_pages? + + [project.full_path.delete_prefix(trim_prefix), deployment.path_prefix].compact.join('/') + end + + def ensure_leading_and_trailing_slash(value) + value + .to_s + .then { |s| s.start_with?("/") ? s : "/#{s}" } + .then { |s| s.end_with?("/") ? s : "#{s}/" } + end end end diff --git a/app/models/pages/virtual_domain.rb b/app/models/pages/virtual_domain.rb index 0a64e91bf60..94ad1491889 100644 --- a/app/models/pages/virtual_domain.rb +++ b/app/models/pages/virtual_domain.rb @@ -17,11 +17,7 @@ module Pages end def lookup_paths - projects - .map { |project| lookup_paths_for(project) } - .select(&:source) # TODO: remove in https://gitlab.com/gitlab-org/gitlab/-/issues/328715 - .sort_by(&:prefix) - .reverse + projects.flat_map { |project| lookup_paths_for(project) } end private @@ -29,7 +25,26 @@ module Pages attr_reader :projects, :trim_prefix, :domain def lookup_paths_for(project) - Pages::LookupPath.new(project, trim_prefix: trim_prefix, domain: domain) + deployments_for(project).map do |deployment| + Pages::LookupPath.new( + deployment: deployment, + trim_prefix: trim_prefix, + domain: domain) + end + end + + def deployments_for(project) + if ::Gitlab::Pages.multiple_versions_enabled_for?(project) + project.active_pages_deployments + else + # project.active_pages_deployments is already loaded from the database, + # so finding from the array to avoid N+1 + project + .active_pages_deployments + .to_a + .find { |deployment| deployment.path_prefix.blank? } + .then { |deployment| [deployment] } + end end end end diff --git a/app/models/user.rb b/app/models/user.rb index e6b2d3e1342..bb3b0100591 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -603,6 +603,16 @@ class User < MainClusterwide::ApplicationRecord .trusted_with_spam) end + def self.supported_keyset_orderings + { + id: [:asc, :desc], + name: [:asc, :desc], + username: [:asc, :desc], + created_at: [:asc, :desc], + updated_at: [:asc, :desc] + } + end + strip_attributes! :name def preferred_language diff --git a/app/services/merge_requests/mergeability/detailed_merge_status_service.rb b/app/services/merge_requests/mergeability/detailed_merge_status_service.rb index 06a68466e87..2e28ffc4363 100644 --- a/app/services/merge_requests/mergeability/detailed_merge_status_service.rb +++ b/app/services/merge_requests/mergeability/detailed_merge_status_service.rb @@ -61,7 +61,7 @@ module MergeRequests end def ci_check_failed_check - if merge_request.actual_head_pipeline&.running? + if merge_request.actual_head_pipeline&.active? :ci_still_running else check_ci_results.payload.fetch(:identifier) diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 0bb88efe183..d30ff5ce4e6 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -2649,6 +2649,15 @@ :weight: 1 :idempotent: true :tags: [] +- :name: bulk_imports_transform_references + :worker_name: BulkImports::TransformReferencesWorker + :feature_category: :importers + :has_external_dependencies: false + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: chat_notification :worker_name: ChatNotificationWorker :feature_category: :integrations diff --git a/app/workers/bulk_imports/transform_references_worker.rb b/app/workers/bulk_imports/transform_references_worker.rb new file mode 100644 index 00000000000..383ad2fd733 --- /dev/null +++ b/app/workers/bulk_imports/transform_references_worker.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +module BulkImports + class TransformReferencesWorker + include ApplicationWorker + + idempotent! + data_consistency :delayed + sidekiq_options retry: 3, dead: false + feature_category :importers + + # rubocop: disable CodeReuse/ActiveRecord + def perform(object_ids, klass, tracker_id) + @tracker = BulkImports::Tracker.find_by_id(tracker_id) + + return unless tracker + + project = tracker.entity.project + + klass.constantize.where(id: object_ids, project: project).find_each do |object| + transform_and_save(object) + end + end + # rubocop: enable CodeReuse/ActiveRecord + + attr_reader :tracker + + private + + def transform_and_save(object) + body = object_body(object).dup + + return if body.blank? + + object.refresh_markdown_cache! + + body.gsub!(username_regex(mapped_usernames), mapped_usernames) + + if object_has_reference?(body) + matching_urls(object).each do |old_url, new_url| + body.gsub!(old_url, new_url) if body.include?(old_url) + end + end + + object.assign_attributes(body_field(object) => body) + object.save!(touch: false) if object_body_changed?(object) + + object + rescue StandardError => e + log_and_fail(e) + end + + def object_body(object) + call_object_method(object) + end + + def object_body_changed?(object) + call_object_method(object, suffix: '_changed?') + end + + def call_object_method(object, suffix: nil) + method = body_field(object) + method = "#{method}#{suffix}" if suffix.present? + + object.public_send(method) # rubocop:disable GitlabSecurity/PublicSend -- the method being called is dependent on several factors + end + + def body_field(object) + object.is_a?(Note) ? 'note' : 'description' + end + + def mapped_usernames + @mapped_usernames ||= ::BulkImports::UsersMapper.new(context: context) + .map_usernames.transform_keys { |key| "@#{key}" } + .transform_values { |value| "@#{value}" } + end + + def username_regex(mapped_usernames) + @username_regex ||= Regexp.new(mapped_usernames.keys.sort_by(&:length) + .reverse.map { |x| Regexp.escape(x) }.join('|')) + end + + def matching_urls(object) + URI.extract(object_body(object), %w[http https]).each_with_object([]) do |url, array| + parsed_url = URI.parse(url) + + next unless source_host == parsed_url.host + next unless parsed_url.path&.start_with?("/#{source_full_path}") + + array << [url, new_url(object, parsed_url)] + end + end + + def new_url(object, parsed_old_url) + parsed_old_url.host = ::Gitlab.config.gitlab.host + parsed_old_url.port = ::Gitlab.config.gitlab.port + parsed_old_url.scheme = ::Gitlab.config.gitlab.https ? 'https' : 'http' + parsed_old_url.to_s.gsub!(source_full_path, full_path(object)) + end + + def source_host + @source_host ||= URI.parse(context.configuration.url).host + end + + def source_full_path + @source_full_path ||= context.entity.source_full_path + end + + def full_path(object) + object.project.full_path + end + + def object_has_reference?(body) + body.include?(source_full_path) + end + + def log_and_fail(exception) + Gitlab::ErrorTracking.track_exception(exception, log_params) + BulkImports::Failure.create(failure_attributes(exception)) + end + + def log_params + { + message: 'Failed to update references', + bulk_import_id: context.bulk_import_id, + bulk_import_entity_id: tracker.bulk_import_entity_id, + source_full_path: context.entity.source_full_path, + source_version: context.bulk_import.source_version, + importer: 'gitlab_migration' + } + end + + def failure_attributes(exception) + { + bulk_import_entity_id: context.entity.id, + pipeline_class: 'ReferencesPipeline', + exception_class: exception.class.to_s, + exception_message: exception.message.truncate(255), + correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id + } + end + + def context + @context ||= BulkImports::Pipeline::Context.new(tracker) + end + end +end |