diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-21 06:15:00 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-21 06:15:00 +0300 |
commit | e3ecb7dc093db47b9491e20d9f20de02b4ac2b6d (patch) | |
tree | e87e5dd85e9c33644b3eddcd47a6214e5537d2c9 /app | |
parent | 4aa6fba6d825b88d23ff37668e78c851bec102b0 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
22 files changed, 187 insertions, 26 deletions
diff --git a/app/assets/javascripts/ci/catalog/components/list/catalog_header.vue b/app/assets/javascripts/ci/catalog/components/list/catalog_header.vue index 3a9ec341789..001e3ec3720 100644 --- a/app/assets/javascripts/ci/catalog/components/list/catalog_header.vue +++ b/app/assets/javascripts/ci/catalog/components/list/catalog_header.vue @@ -1,5 +1,6 @@ <script> import { GlBanner, GlLink } from '@gitlab/ui'; +import ChatBubbleSvg from '@gitlab/svgs/dist/illustrations/chat-sm.svg?url'; import { __, s__ } from '~/locale'; import { helpPagePath } from '~/helpers/help_page_helper'; import BetaBadge from '~/vue_shared/components/badges/beta_badge.vue'; @@ -44,6 +45,7 @@ export default { learnMore: __('Learn more'), }, learnMorePath: helpPagePath('ci/components/index'), + ChatBubbleSvg, }; </script> <template> @@ -54,6 +56,7 @@ export default { :title="$options.i18n.banner.title" :button-text="$options.i18n.banner.btnText" button-link="https://gitlab.com/gitlab-org/gitlab/-/issues/407556" + :svg-path="$options.ChatBubbleSvg" @close="handleDismissBanner" > <p> diff --git a/app/assets/javascripts/content_editor/extensions/copy_paste.js b/app/assets/javascripts/content_editor/extensions/copy_paste.js index 23f2da7bc28..1164e7ead33 100644 --- a/app/assets/javascripts/content_editor/extensions/copy_paste.js +++ b/app/assets/javascripts/content_editor/extensions/copy_paste.js @@ -172,6 +172,8 @@ export default Extension.create({ return true; } + if (!textContent) return false; + const hasHTML = clipboardData.types.some((type) => type === HTML_FORMAT); const hasVsCode = clipboardData.types.some((type) => type === VS_CODE_FORMAT); const vsCodeMeta = hasVsCode ? JSON.parse(clipboardData.getData(VS_CODE_FORMAT)) : {}; diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index b11f7b1ba76..f1e46262b2f 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -3,6 +3,7 @@ import '~/lib/utils/jquery_at_who'; import { escape as lodashEscape, sortBy, template, escapeRegExp } from 'lodash'; import * as Emoji from '~/emoji'; import axios from '~/lib/utils/axios_utils'; +import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import { loadingIconForLegacyJS } from '~/loading_icon_for_legacy_js'; import { s__, __, sprintf } from '~/locale'; import { isUserBusy } from '~/set_status_modal/utils'; @@ -26,6 +27,8 @@ export const CONTACT_STATE_ACTIVE = 'active'; export const CONTACTS_ADD_COMMAND = '/add_contacts'; export const CONTACTS_REMOVE_COMMAND = '/remove_contacts'; +const useMentionsBackendFiltering = window.gon.features?.mentionAutocompleteBackendFiltering; + /** * Escapes user input before we pass it to at.js, which * renders it as HTML in the autocomplete dropdown. @@ -62,6 +65,8 @@ export function showAndHideHelper($input, alias = '') { }); } +// This should be kept in sync with the backend filtering in +// `User#gfm_autocomplete_search` and `Namespace#gfm_autocomplete_search` function createMemberSearchString(member) { return `${member.name.replace(/ /g, '')} ${member.username}`; } @@ -344,6 +349,7 @@ class GfmAutoComplete { } setupMembers($input) { + const instance = this; const fetchData = this.fetchData.bind(this); const MEMBER_COMMAND = { ASSIGN: '/assign', @@ -383,6 +389,7 @@ class GfmAutoComplete { // eslint-disable-next-line no-template-curly-in-string insertTpl: '${atwho-at}${username}', limit: 10, + delay: useMentionsBackendFiltering ? DEFAULT_DEBOUNCE_AND_THROTTLE_MS : null, searchKey: 'search', alwaysHighlightFirst: true, skipSpecialCharacterTest: true, @@ -409,16 +416,19 @@ class GfmAutoComplete { const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers); return match && match.length ? match[1] : null; }, - filter(query, data, searchKey) { - if (GfmAutoComplete.isLoading(data)) { + filter(query, data) { + if (useMentionsBackendFiltering) { + if (GfmAutoComplete.isLoading(data) || instance.previousQuery !== query) { + instance.previousQuery = query; + + fetchData(this.$inputor, this.at, query); + return data; + } + } else if (GfmAutoComplete.isLoading(data)) { fetchData(this.$inputor, this.at); return data; } - if (data === GfmAutoComplete.defaultLoadingData) { - return $.fn.atwho.default.callbacks.filter(query, data, searchKey); - } - if (command === MEMBER_COMMAND.ASSIGN) { // Only include members which are not assigned to Issuable currently return data.filter((member) => !assignees.includes(member.search)); @@ -988,6 +998,11 @@ GfmAutoComplete.atTypeMap = { }; GfmAutoComplete.typesWithBackendFiltering = ['vulnerabilities']; + +if (useMentionsBackendFiltering) { + GfmAutoComplete.typesWithBackendFiltering.push('members'); +} + GfmAutoComplete.isTypeWithBackendFiltering = (type) => GfmAutoComplete.typesWithBackendFiltering.includes(GfmAutoComplete.atTypeMap[type]); @@ -1040,6 +1055,8 @@ GfmAutoComplete.Members = { // `member.search` is a name:username string like `MargeSimpson msimpson` return member.search.toLowerCase().includes(query); }, + // This should be kept in sync with the backend sorting in + // `User#gfm_autocomplete_search` and `Namespace#gfm_autocomplete_search` sort(query, members) { const lowercaseQuery = query.toLowerCase(); const { nameOrUsernameStartsWith, nameOrUsernameIncludes } = GfmAutoComplete.Members; diff --git a/app/controllers/groups/autocomplete_sources_controller.rb b/app/controllers/groups/autocomplete_sources_controller.rb index 7a490b34511..191720f69a0 100644 --- a/app/controllers/groups/autocomplete_sources_controller.rb +++ b/app/controllers/groups/autocomplete_sources_controller.rb @@ -10,7 +10,7 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController urgency :low, [:issues, :labels, :milestones, :commands, :merge_requests, :members] def members - render json: ::Groups::ParticipantsService.new(@group, current_user).execute(target) + render json: ::Groups::ParticipantsService.new(@group, current_user, params).execute(target) end def issues diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb index ff3484d3020..dc10004c62b 100644 --- a/app/controllers/projects/autocomplete_sources_controller.rb +++ b/app/controllers/projects/autocomplete_sources_controller.rb @@ -15,7 +15,7 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController urgency :low, [:issues, :labels, :milestones, :commands, :contacts] def members - render json: ::Projects::ParticipantsService.new(@project, current_user).execute(target) + render json: ::Projects::ParticipantsService.new(@project, current_user, params).execute(target) end def issues diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index d0eabf8d837..c1de24f300b 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -71,6 +71,7 @@ class Projects::IssuesController < Projects::ApplicationController push_frontend_feature_flag(:display_work_item_epic_issue_sidebar, project) push_force_frontend_feature_flag(:linked_work_items, project.linked_work_items_feature_flag_enabled?) push_frontend_feature_flag(:notifications_todos_buttons, current_user) + push_frontend_feature_flag(:mention_autocomplete_backend_filtering, project) end around_action :allow_gitaly_ref_name_caching, only: [:discussions] diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 0899e303305..09ba6e40efd 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -46,6 +46,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:notifications_todos_buttons, current_user) push_frontend_feature_flag(:mr_request_changes, current_user) push_frontend_feature_flag(:merge_blocked_component, current_user) + push_frontend_feature_flag(:mention_autocomplete_backend_filtering, project) end around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :diffs, :discussions] diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index e3ead1b04d0..71e5ea6de48 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -18,7 +18,7 @@ class GroupMember < Member default_scope { where(source_type: SOURCE_TYPE) } # rubocop:disable Cop/DefaultScope - scope :of_groups, ->(groups) { where(source_id: groups&.select(:id)) } + scope :of_groups, ->(groups) { where(source_id: groups) } scope :of_ldap_type, -> { where(ldap: true) } scope :count_users_by_group_id, -> { group(:source_id).count } diff --git a/app/models/namespace.rb b/app/models/namespace.rb index c665c2278a5..0b6a5b3752d 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -263,6 +263,28 @@ class Namespace < ApplicationRecord end end + # This should be kept in sync with the frontend filtering in + # https://gitlab.com/gitlab-org/gitlab/-/blob/5d34e3488faa3982d30d7207773991c1e0b6368a/app/assets/javascripts/gfm_auto_complete.js#L68 and + # https://gitlab.com/gitlab-org/gitlab/-/blob/5d34e3488faa3982d30d7207773991c1e0b6368a/app/assets/javascripts/gfm_auto_complete.js#L1053 + def gfm_autocomplete_search(query) + without_project_namespaces + .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046") + .joins(:route) + .where( + "REPLACE(routes.name, ' ', '') ILIKE :pattern OR routes.path ILIKE :pattern", + pattern: "%#{sanitize_sql_like(query)}%" + ) + .order( + Arel.sql(sanitize_sql( + [ + "CASE WHEN starts_with(REPLACE(routes.name, ' ', ''), :pattern) OR starts_with(routes.path, :pattern) THEN 1 ELSE 2 END", + { pattern: query } + ] + )), + 'routes.path' + ) + end + def clean_path(path, limited_to: Namespace.all) slug = Gitlab::Slug::Path.new(path).generate path = Namespaces::RandomizedSuffixPath.new(slug) diff --git a/app/models/resource_milestone_event.rb b/app/models/resource_milestone_event.rb index d305a4ace51..2b93334f721 100644 --- a/app/models/resource_milestone_event.rb +++ b/app/models/resource_milestone_event.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ResourceMilestoneEvent < ResourceTimeboxEvent + include EachBatch + belongs_to :milestone scope :include_relations, -> { includes(:user, milestone: [:project, :group]) } diff --git a/app/models/user.rb b/app/models/user.rb index 8ae89d60b0b..3770a14bdc1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -847,6 +847,25 @@ class User < MainClusterwide::ApplicationRecord scope.reorder(order) end + # This should be kept in sync with the frontend filtering in + # https://gitlab.com/gitlab-org/gitlab/-/blob/5d34e3488faa3982d30d7207773991c1e0b6368a/app/assets/javascripts/gfm_auto_complete.js#L68 and + # https://gitlab.com/gitlab-org/gitlab/-/blob/5d34e3488faa3982d30d7207773991c1e0b6368a/app/assets/javascripts/gfm_auto_complete.js#L1053 + def gfm_autocomplete_search(query) + where( + "REPLACE(users.name, ' ', '') ILIKE :pattern OR users.username ILIKE :pattern", + pattern: "%#{sanitize_sql_like(query)}%" + ).order( + Arel.sql(sanitize_sql( + [ + "CASE WHEN starts_with(REPLACE(users.name, ' ', ''), :pattern) OR starts_with(users.username, :pattern) THEN 1 ELSE 2 END", + { pattern: query } + ] + )), + :username, + :id + ) + end + # Limits the result set to users _not_ in the given query/list of IDs. # # users - The list of users to ignore. This can be an diff --git a/app/services/concerns/users/participable_service.rb b/app/services/concerns/users/participable_service.rb index a54c4947b0b..f84793d869c 100644 --- a/app/services/concerns/users/participable_service.rb +++ b/app/services/concerns/users/participable_service.rb @@ -3,6 +3,9 @@ module Users module ParticipableService extend ActiveSupport::Concern + include Gitlab::Utils::StrongMemoize + + SEARCH_LIMIT = 10 included do attr_reader :noteable @@ -25,6 +28,16 @@ module Users sorted(users) end + def filter_and_sort_users(users_relation) + if params[:search] + users_relation.gfm_autocomplete_search(params[:search]).limit(SEARCH_LIMIT).tap do |users| + preload_status(users) + end + else + sorted(users_relation) + end + end + def sorted(users) users.uniq.to_a.compact.sort_by(&:username).tap do |users| preload_status(users) @@ -34,8 +47,15 @@ module Users def groups return [] unless current_user - current_user.authorized_groups.with_route.sort_by(&:full_path) + relation = current_user.authorized_groups + + if params[:search] + relation.gfm_autocomplete_search(params[:search]).limit(SEARCH_LIMIT).to_a + else + relation.with_route.sort_by(&:full_path) + end end + strong_memoize_attr :groups def render_participants_as_hash(participants) participants.map { |participant| participant_as_hash(participant) } @@ -74,11 +94,14 @@ module Users end def group_counts - @group_counts ||= GroupMember - .of_groups(current_user.authorized_groups) + groups_for_count = params[:search] ? groups : current_user.authorized_groups + + GroupMember + .of_groups(groups_for_count) .non_request .count_users_by_group_id end + strong_memoize_attr :group_counts def preload_status(users) users.each { |u| lazy_user_availability(u) } diff --git a/app/services/groups/participants_service.rb b/app/services/groups/participants_service.rb index 7b68b435f14..ae1a917f022 100644 --- a/app/services/groups/participants_service.rb +++ b/app/services/groups/participants_service.rb @@ -29,7 +29,9 @@ module Groups def group_hierarchy_users return [] unless group - sorted(Autocomplete::GroupUsersFinder.new(group: group).execute) + relation = Autocomplete::GroupUsersFinder.new(group: group).execute + + filter_and_sort_users(relation) end end end diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb index a96bfd74cd0..e28c58794a9 100644 --- a/app/services/import/github_service.rb +++ b/app/services/import/github_service.rb @@ -139,7 +139,8 @@ module Import .new(project) .write( timeout_strategy: params[:timeout_strategy] || ProjectImportData::PESSIMISTIC_TIMEOUT, - optional_stages: params[:optional_stages] + optional_stages: params[:optional_stages], + extended_events: Feature.enabled?(:github_import_extended_events, current_user) ) end end diff --git a/app/services/milestones/promote_service.rb b/app/services/milestones/promote_service.rb index 4417f17f33e..d657b8b3255 100644 --- a/app/services/milestones/promote_service.rb +++ b/app/services/milestones/promote_service.rb @@ -63,9 +63,12 @@ module Milestones def update_children(group_milestone, milestone_ids) issues = Issue.where(project_id: group_project_ids, milestone_id: milestone_ids) merge_requests = MergeRequest.where(source_project_id: group_project_ids, milestone_id: milestone_ids) + milestone_events = ResourceMilestoneEvent.where(milestone_id: milestone_ids) - [issues, merge_requests].each do |issuable_collection| - issuable_collection.update_all(milestone_id: group_milestone.id) + [issues, merge_requests, milestone_events].each do |collection| + collection.each_batch do |batch| + batch.update_all(milestone_id: group_milestone.id) + end end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index fe19d1f051d..188f12a287b 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -18,13 +18,17 @@ module Projects end def project_members - @project_members ||= sorted(project.authorized_users) + filter_and_sort_users(project_members_relation) end def all_members return [] if Feature.enabled?(:disable_all_mention) - [{ username: "all", name: "All Project and Group Members", count: project_members.count }] + [{ username: "all", name: "All Project and Group Members", count: project_members_relation.count }] + end + + def project_members_relation + project.authorized_users end end end diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index 97acafe24d0..0f56ae92557 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -12,4 +12,4 @@ cancel_path: cancel_import_github_path, details_path: details_import_github_path, status_import_github_group_path: status_import_github_group_path(format: :json), - optional_stages: Gitlab::GithubImport::Settings.stages_array + optional_stages: Gitlab::GithubImport::Settings.stages_array(current_user) diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index ec5156bb1d0..e4329ae2a8b 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -1353,6 +1353,15 @@ :weight: 1 :idempotent: false :tags: [] +- :name: github_importer:github_import_replay_events + :worker_name: Gitlab::GithubImport::ReplayEventsWorker + :feature_category: :importers + :has_external_dependencies: true + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: github_importer:github_import_stage_finish_import :worker_name: Gitlab::GithubImport::Stage::FinishImportWorker :feature_category: :importers diff --git a/app/workers/gitlab/github_import/advance_stage_worker.rb b/app/workers/gitlab/github_import/advance_stage_worker.rb index 417b8598547..805252927ff 100644 --- a/app/workers/gitlab/github_import/advance_stage_worker.rb +++ b/app/workers/gitlab/github_import/advance_stage_worker.rb @@ -22,14 +22,20 @@ module Gitlab sidekiq_options dead: false # The known importer stages and their corresponding Sidekiq workers. + # + # Note: AdvanceStageWorker is not used for the repository, base_data, and pull_requests stages. + # They are included in the list for us to easily see all stage workers and the order in which they are executed. STAGES = { + repository: Stage::ImportRepositoryWorker, + base_data: Stage::ImportBaseDataWorker, + pull_requests: Stage::ImportPullRequestsWorker, collaborators: Stage::ImportCollaboratorsWorker, - pull_requests_merged_by: Stage::ImportPullRequestsMergedByWorker, - pull_request_review_requests: Stage::ImportPullRequestsReviewRequestsWorker, - pull_request_reviews: Stage::ImportPullRequestsReviewsWorker, + pull_requests_merged_by: Stage::ImportPullRequestsMergedByWorker, # Skipped on extended_events + pull_request_review_requests: Stage::ImportPullRequestsReviewRequestsWorker, # Skipped on extended_events + pull_request_reviews: Stage::ImportPullRequestsReviewsWorker, # Skipped on extended_events issues_and_diff_notes: Stage::ImportIssuesAndDiffNotesWorker, issue_events: Stage::ImportIssueEventsWorker, - notes: Stage::ImportNotesWorker, + notes: Stage::ImportNotesWorker, # Skipped on extended_events attachments: Stage::ImportAttachmentsWorker, protected_branches: Stage::ImportProtectedBranchesWorker, lfs_objects: Stage::ImportLfsObjectsWorker, diff --git a/app/workers/gitlab/github_import/replay_events_worker.rb b/app/workers/gitlab/github_import/replay_events_worker.rb new file mode 100644 index 00000000000..680d5ec2d7d --- /dev/null +++ b/app/workers/gitlab/github_import/replay_events_worker.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Gitlab + module GithubImport + class ReplayEventsWorker + include ObjectImporter + + idempotent! + + def representation_class + Representation::ReplayEvent + end + + def importer_class + Importer::ReplayEventsImporter + end + + def object_type + :replay_event + end + + def increment_object_counter?(_object) + false + end + end + end +end diff --git a/app/workers/gitlab/github_import/stage/import_collaborators_worker.rb b/app/workers/gitlab/github_import/stage/import_collaborators_worker.rb index b5b1601e3ed..38e1fd52889 100644 --- a/app/workers/gitlab/github_import/stage/import_collaborators_worker.rb +++ b/app/workers/gitlab/github_import/stage/import_collaborators_worker.rb @@ -42,9 +42,15 @@ module Gitlab def move_to_next_stage(project, waiters = {}) AdvanceStageWorker.perform_async( - project.id, waiters.deep_stringify_keys, 'pull_requests_merged_by' + project.id, waiters.deep_stringify_keys, next_stage(project) ) end + + def next_stage(project) + return 'issues_and_diff_notes' if import_settings(project).extended_events? + + 'pull_requests_merged_by' + end end end end diff --git a/app/workers/gitlab/github_import/stage/import_issue_events_worker.rb b/app/workers/gitlab/github_import/stage/import_issue_events_worker.rb index 27d14a1a108..9618500604a 100644 --- a/app/workers/gitlab/github_import/stage/import_issue_events_worker.rb +++ b/app/workers/gitlab/github_import/stage/import_issue_events_worker.rb @@ -15,7 +15,7 @@ module Gitlab # client - An instance of Gitlab::GithubImport::Client. # project - An instance of Project. def import(client, project) - return skip_to_next_stage(project) if import_settings(project).disabled?(:single_endpoint_issue_events_import) + return skip_to_next_stage(project) if skip_to_next_stage?(project) importer = ::Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter info(project.id, message: "starting importer", importer: importer.name) @@ -25,13 +25,26 @@ module Gitlab private + def skip_to_next_stage?(project) + # This stage is mandatory when using extended_events + return false if import_settings(project).extended_events? + + import_settings(project).disabled?(:single_endpoint_issue_events_import) + end + def skip_to_next_stage(project) info(project.id, message: "skipping importer", importer: "IssueEventsImporter") move_to_next_stage(project) end def move_to_next_stage(project, waiters = {}) - AdvanceStageWorker.perform_async(project.id, waiters.deep_stringify_keys, 'notes') + AdvanceStageWorker.perform_async(project.id, waiters.deep_stringify_keys, next_stage(project)) + end + + def next_stage(project) + return "attachments" if import_settings(project).extended_events? + + "notes" end end end |