diff --git a/app/assets/javascripts/graphql_shared/issuable_client.js b/app/assets/javascripts/graphql_shared/issuable_client.js
index 7a596434195..97830c24029 100644
--- a/app/assets/javascripts/graphql_shared/issuable_client.js
+++ b/app/assets/javascripts/graphql_shared/issuable_client.js
@@ -137,7 +137,11 @@ export const config = {
}
// we want to concat next page of children work items within Hierarchy widget to the existing ones
- if (incomingWidget?.type === WIDGET_TYPE_HIERARCHY && context.variables.endCursor) {
+ if (
+ incomingWidget?.type === WIDGET_TYPE_HIERARCHY &&
+ context.variables.endCursor &&
+ incomingWidget.children?.nodes
+ ) {
// concatPagination won't work because we were placing new widget here so we have to do this manually
return {
...incomingWidget,
diff --git a/app/assets/javascripts/integrations/beyond_identity/components/exclusions_tabs.vue b/app/assets/javascripts/integrations/beyond_identity/components/exclusions_tabs.vue
index 8dd4becee6c..43653ae565b 100644
--- a/app/assets/javascripts/integrations/beyond_identity/components/exclusions_tabs.vue
+++ b/app/assets/javascripts/integrations/beyond_identity/components/exclusions_tabs.vue
@@ -1,12 +1,12 @@
-
- {{
- $options.i18n.settingsTabTitle
- }}
-
-
-
-
- {{ $options.i18n.projectsTabTitle }}
-
-
+
+
diff --git a/app/assets/javascripts/integrations/overrides/components/integration_tabs.vue b/app/assets/javascripts/integrations/overrides/components/integration_tabs.vue
index a59399d9b51..9b1b96c4496 100644
--- a/app/assets/javascripts/integrations/overrides/components/integration_tabs.vue
+++ b/app/assets/javascripts/integrations/overrides/components/integration_tabs.vue
@@ -1,11 +1,11 @@
-
- {{
- $options.i18n.settingsTabTitle
- }}
-
+
diff --git a/app/assets/javascripts/members/index.js b/app/assets/javascripts/members/index.js
index 80f35ae35d1..0a64e93aa1c 100644
--- a/app/assets/javascripts/members/index.js
+++ b/app/assets/javascripts/members/index.js
@@ -37,6 +37,7 @@ export const initMembersApp = (el, context, options) => {
canApproveAccessRequests,
namespaceUserLimit,
availableRoles,
+ reassignmentCsvPath,
...vuexStoreAttributes
} = parseDataAttributes(el);
@@ -76,6 +77,7 @@ export const initMembersApp = (el, context, options) => {
namespaceUserLimit,
availableRoles,
context,
+ reassignmentCsvPath,
group: {
name: groupName,
path: groupPath,
diff --git a/app/assets/javascripts/members/placeholders/components/csv_upload_modal.vue b/app/assets/javascripts/members/placeholders/components/csv_upload_modal.vue
index 990a5c8da5a..457d451595b 100644
--- a/app/assets/javascripts/members/placeholders/components/csv_upload_modal.vue
+++ b/app/assets/javascripts/members/placeholders/components/csv_upload_modal.vue
@@ -12,7 +12,7 @@ export default {
GlAlert,
},
inject: {
- reassignmentCsvDownloadPath: {
+ reassignmentCsvPath: {
default: '',
},
},
@@ -56,7 +56,7 @@ export default {
-
target?.classList.contains('task-list-item-checkbox');
@@ -25,7 +24,6 @@ export default {
components: {
GlButton,
},
- mixins: [glFeatureFlagMixin()],
props: {
disableTruncation: {
type: Boolean,
@@ -74,7 +72,7 @@ export default {
return this.descriptionHtml?.trim() === '';
},
isTruncated() {
- return this.truncated && !this.disableTruncation && this.glFeatures.workItemsBeta;
+ return this.truncated && !this.disableTruncation;
},
},
watch: {
diff --git a/app/graphql/mutations/ml/models/edit.rb b/app/graphql/mutations/ml/models/edit.rb
new file mode 100644
index 00000000000..9c05fe1e7e6
--- /dev/null
+++ b/app/graphql/mutations/ml/models/edit.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Ml
+ module Models
+ class Edit < Base
+ graphql_name 'MlModelEdit'
+
+ include FindsProject
+
+ argument :model_id, GraphQL::Types::Int,
+ required: false,
+ description: 'Id of the model.'
+
+ argument :name, GraphQL::Types::String,
+ required: true,
+ description: 'Name of the model.'
+
+ argument :description, GraphQL::Types::String,
+ required: false,
+ description: 'Description of the model.'
+
+ def resolve(project_path:, name:, model_id:, description:)
+ project = authorized_find!(project_path)
+ model = ::Ml::FindModelService.new(project, name, model_id).execute
+ service_response = ::Ml::UpdateModelService.new(model, description).execute
+
+ if service_response.success?
+ {
+ model: service_response.payload,
+ errors: []
+ }
+ else
+ {
+ model: nil,
+ errors: service_response.errors
+ }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 7449dbf630e..1ee9f8fa275 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -219,6 +219,7 @@ module Types
mount_mutation Mutations::WorkItems::Subscribe, alpha: { milestone: '16.3' }
mount_mutation Mutations::Admin::AbuseReportLabels::Create, alpha: { milestone: '16.4' }
mount_mutation Mutations::Ml::Models::Create, alpha: { milestone: '16.8' }
+ mount_mutation Mutations::Ml::Models::Edit, alpha: { milestone: '17.3' }
mount_mutation Mutations::Ml::Models::Destroy, alpha: { milestone: '16.10' }
mount_mutation Mutations::Ml::Models::Delete, alpha: { milestone: '17.0' }
mount_mutation Mutations::Ml::ModelVersions::Create, alpha: { milestone: '17.1' }
diff --git a/app/helpers/groups/group_members_helper.rb b/app/helpers/groups/group_members_helper.rb
index ef9164ab162..e96da5694b0 100644
--- a/app/helpers/groups/group_members_helper.rb
+++ b/app/helpers/groups/group_members_helper.rb
@@ -29,7 +29,8 @@ module Groups::GroupMembersHelper
group_path: group.full_path,
can_approve_access_requests: true, # true for CE, overridden in EE
placeholder: placeholder_users,
- available_roles: available_group_roles(group)
+ available_roles: available_group_roles(group),
+ reassignment_csv_path: bulk_reassignment_file_group_group_members_path(group)
}
end
# rubocop:enable Metrics/ParameterLists
diff --git a/app/helpers/projects/ml/model_registry_helper.rb b/app/helpers/projects/ml/model_registry_helper.rb
index 9ee6a47bbe6..3faf1b8232c 100644
--- a/app/helpers/projects/ml/model_registry_helper.rb
+++ b/app/helpers/projects/ml/model_registry_helper.rb
@@ -29,7 +29,8 @@ module Projects
model_id: model.id,
model_name: model.name,
max_allowed_file_size: max_allowed_file_size(project),
- latest_version: model.latest_version&.version
+ latest_version: model.latest_version&.version,
+ markdown_preview_path: ::Gitlab::Routing.url_helpers.project_ml_preview_markdown_url(project)
}
to_json(data)
diff --git a/app/models/import/source_user.rb b/app/models/import/source_user.rb
index ccaaa1b8565..a52e971a0ab 100644
--- a/app/models/import/source_user.rb
+++ b/app/models/import/source_user.rb
@@ -109,6 +109,10 @@ module Import
accepted_status? ? reassign_to_user : placeholder_user
end
+ def mapped_user_id
+ accepted_status? ? reassign_to_user_id : placeholder_user_id
+ end
+
def accepted_status?
STATUSES.slice(*ACCEPTED_STATUSES).value?(status)
end
diff --git a/app/models/virtual_registries/packages/maven/upstream.rb b/app/models/virtual_registries/packages/maven/upstream.rb
index 55f2d24a102..07bd665cefc 100644
--- a/app/models/virtual_registries/packages/maven/upstream.rb
+++ b/app/models/virtual_registries/packages/maven/upstream.rb
@@ -34,6 +34,8 @@ module VirtualRegistries
after_validation :reset_credentials, if: -> { persisted? && url_changed? }
before_save :write_credentials
+ prevent_from_serialization(:username, :password) if respond_to?(:prevent_from_serialization)
+
def url_for(path)
full_url = File.join(url, path)
Addressable::URI.parse(full_url).to_s
diff --git a/app/services/ml/find_model_service.rb b/app/services/ml/find_model_service.rb
index 23ca0266629..cd376daa31c 100644
--- a/app/services/ml/find_model_service.rb
+++ b/app/services/ml/find_model_service.rb
@@ -2,12 +2,24 @@
module Ml
class FindModelService
- def initialize(project, name)
+ def initialize(project, name = nil, model_id = nil)
@project = project
@name = name
+ @model_id = model_id
end
def execute
+ return find_by_model_id if @model_id
+ return find_by_project_and_name if @name
+
+ nil
+ end
+
+ def find_by_model_id
+ Ml::Model.by_project_id_and_id(@project.id, @model_id)
+ end
+
+ def find_by_project_and_name
Ml::Model.by_project_id_and_name(@project.id, @name)
end
end
diff --git a/app/services/ml/update_model_service.rb b/app/services/ml/update_model_service.rb
index dade6c72588..c40872a4f35 100644
--- a/app/services/ml/update_model_service.rb
+++ b/app/services/ml/update_model_service.rb
@@ -8,9 +8,18 @@ module Ml
end
def execute
- @model.update!(description: @description)
+ return error('Model not found') unless @model
- @model
+ @model.update!(description: @description)
+ success(@model)
+ end
+
+ def success(model)
+ ServiceResponse.success(payload: model)
+ end
+
+ def error(reason)
+ ServiceResponse.error(message: reason)
end
end
end
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 0bf1aa0336b..164ea77e959 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -1,7 +1,7 @@
- page_title _("Groups")
+= render_dashboard_ultimate_trial(current_user)
%div{ data: { testid: 'groups-page' } }
- = render_dashboard_ultimate_trial(current_user)
- if current_user.groups.exists?
= render 'dashboard/groups_head'
= render 'groups'
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index f22c7be06e9..0c32975fc42 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -1,25 +1,16 @@
- if @related_branches.any?
- - if @related_branches.any?
- = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card' }, header_options: { class: 'gl-new-card-header' } , body_options: { class: 'gl-new-card-body' }) do |c|
- - c.with_header do
- .gl-new-card-title-wrapper
- %h3.gl-new-card-title
- = link_to "", "#related-branches", class: "gl-link anchor position-absolute gl-text-decoration-none", "aria-hidden": true
- = _('Related branches')
- .gl-new-card-count
- = sprite_icon('branch', css_class: "gl-mr-2 gl-text-gray-500 gl-icon")
- = @related_branches.size
- - c.with_body do
- .gl-new-card-content
- %ul.related-merge-requests.content-list
- - @related_branches.each do |branch|
- %li.list-item{ class: "gl-py-0! gl-border-0!" }
- .item-body.gl-display-flex.gl-align-items-center.gl-px-3.gl-pr-2.-gl-mx-2
- .item-contents.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-flex-grow-1.gl-min-h-7
- .item-title.gl-display-flex.mb-xl-0.gl-min-w-0.gl-align-items-center
- - if branch[:pipeline_status].present?
- %span.-gl-mt-2.-gl-mb-2.gl-mr-3
- = render 'ci/status/icon', status: branch[:pipeline_status]
- %span.related-branch-info
- %strong
- = link_to branch[:name], branch[:link], class: "ref-name"
+ = render ::Layouts::CrudComponent.new(_('Related branches'),
+ icon: 'branch',
+ count: @related_branches.size,
+ options: { class: 'gl-mt-5' },
+ body_options: { class: 'gl-p-3' }) do |c|
+ - c.with_body do
+ %ul.related-merge-requests.content-list
+ - @related_branches.each do |branch|
+ %li{ class: '!gl-p-0 !gl-border-b-0' }
+ .item-body.gl-p-3
+ .gl-flex.gl-items-center.gl-gap-3
+ - if branch[:pipeline_status].present?
+ %span.-gl-my-2
+ = render 'ci/status/icon', status: branch[:pipeline_status]
+ = link_to branch[:name], branch[:link], class: 'ref-name'
diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml
index c9b95a8e494..86eb9e093dc 100644
--- a/app/views/shared/milestones/_labels_tab.html.haml
+++ b/app/views/shared/milestones/_labels_tab.html.haml
@@ -1,5 +1,9 @@
- if labels.any?
- = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card' }, body_options: { class: 'gl-new-card-body gl-px-0' }) do |c|
+ = render ::Layouts::CrudComponent.new(s_('Labels'),
+ icon: 'label',
+ count: labels.size,
+ options: { class: 'gl-mt-5' },
+ body_options: { class: '!gl-m-0' }) do |c|
- c.with_body do
%ul.manage-labels-list.gl-px-0.gl-mb-0
- labels.each do |label|
diff --git a/db/docs/batched_background_migrations/backfill_partition_id_ci_daily_build_group_report_result.yml b/db/docs/batched_background_migrations/backfill_partition_id_ci_daily_build_group_report_result.yml
index ec2d71ed494..052ea4e701d 100644
--- a/db/docs/batched_background_migrations/backfill_partition_id_ci_daily_build_group_report_result.yml
+++ b/db/docs/batched_background_migrations/backfill_partition_id_ci_daily_build_group_report_result.yml
@@ -6,4 +6,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/153236
milestone: '17.3'
queued_migration_version: 20240717093514
finalize_after: '2024-08-01'
-finalized_by:
+finalized_by: 20240812010237
diff --git a/db/docs/batched_background_migrations/backfill_upstream_pipeline_partition_id_on_p_ci_builds.yml b/db/docs/batched_background_migrations/backfill_upstream_pipeline_partition_id_on_p_ci_builds.yml
index d6567c319eb..5a98515699c 100644
--- a/db/docs/batched_background_migrations/backfill_upstream_pipeline_partition_id_on_p_ci_builds.yml
+++ b/db/docs/batched_background_migrations/backfill_upstream_pipeline_partition_id_on_p_ci_builds.yml
@@ -7,4 +7,4 @@ milestone: '17.3'
queued_migration_version: 20240708105034
# Replace with the approximate date you think it's best to ensure the completion of this BBM.
finalize_after: '2024-08-22'
-finalized_by: # version of the migration that finalized this BBM
+finalized_by: 20240812084505
diff --git a/db/docs/batched_background_migrations/queue_backfill_autocancel_partition_id_on_ci_pipelines.yml b/db/docs/batched_background_migrations/queue_backfill_autocancel_partition_id_on_ci_pipelines.yml
index 49e97b5d026..f04c404ac74 100644
--- a/db/docs/batched_background_migrations/queue_backfill_autocancel_partition_id_on_ci_pipelines.yml
+++ b/db/docs/batched_background_migrations/queue_backfill_autocancel_partition_id_on_ci_pipelines.yml
@@ -7,4 +7,4 @@ milestone: '17.2'
queued_migration_version: 20240627122810
# Replace with the approximate date you think it's best to ensure the completion of this BBM.
finalize_after: '2024-07-22'
-finalized_by: # version of the migration that finalized this BBM
+finalized_by: 20240812062443
diff --git a/db/migrate/20240807143914_remove_crm_contacts_widget_from_work_item_types.rb b/db/migrate/20240807143914_remove_crm_contacts_widget_from_work_item_types.rb
new file mode 100644
index 00000000000..f332d7bfedf
--- /dev/null
+++ b/db/migrate/20240807143914_remove_crm_contacts_widget_from_work_item_types.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+class RemoveCrmContactsWidgetFromWorkItemTypes < Gitlab::Database::Migration[2.2]
+ class WorkItemType < MigrationRecord
+ self.table_name = 'work_item_types'
+ end
+
+ class WidgetDefinition < MigrationRecord
+ self.table_name = 'work_item_widget_definitions'
+ end
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+ disable_ddl_transaction!
+ milestone '17.4'
+
+ WIDGET_NAME = 'CrmContacts'
+ WIDGET_ENUM_VALUE = 24
+ WORK_ITEM_TYPES = %w[
+ Epic
+ Task
+ ].freeze
+
+ def up
+ WORK_ITEM_TYPES.each do |type_name|
+ type = WorkItemType.find_by_name_and_namespace_id(type_name, nil)
+ next unless type
+
+ WidgetDefinition.where(name: WIDGET_NAME, work_item_type_id: type.id).delete_all
+ end
+ end
+
+ def down
+ widgets = []
+
+ WORK_ITEM_TYPES.each do |type_name|
+ type = WorkItemType.find_by_name_and_namespace_id(type_name, nil)
+
+ unless type
+ Gitlab::AppLogger.warn("type #{type_name} is missing, not adding widget")
+
+ next
+ end
+
+ widgets << {
+ work_item_type_id: type.id,
+ name: WIDGET_NAME,
+ widget_type: WIDGET_ENUM_VALUE
+ }
+ end
+
+ return if widgets.empty?
+
+ WidgetDefinition.upsert_all(
+ widgets,
+ unique_by: :index_work_item_widget_definitions_on_default_witype_and_name
+ )
+ end
+end
diff --git a/db/post_migrate/20240812010237_finalize_backfill_partition_id_ci_daily_build_group_report_result_attempt2.rb b/db/post_migrate/20240812010237_finalize_backfill_partition_id_ci_daily_build_group_report_result_attempt2.rb
new file mode 100644
index 00000000000..46726c314b7
--- /dev/null
+++ b/db/post_migrate/20240812010237_finalize_backfill_partition_id_ci_daily_build_group_report_result_attempt2.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class FinalizeBackfillPartitionIdCiDailyBuildGroupReportResultAttempt2 < Gitlab::Database::Migration[2.2]
+ milestone '17.4'
+ disable_ddl_transaction!
+ restrict_gitlab_migration gitlab_schema: :gitlab_ci
+
+ MIGRATION = 'BackfillPartitionIdCiDailyBuildGroupReportResult'
+
+ def up
+ ensure_batched_background_migration_is_finished(
+ job_class_name: MIGRATION,
+ table_name: :ci_daily_build_group_report_results,
+ column_name: :id,
+ job_arguments: [],
+ finalize: true
+ )
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20240812024133_partitioned_fk_to_ci_pipelines_from_ci_daily_build_group_report_results.rb b/db/post_migrate/20240812024133_partitioned_fk_to_ci_pipelines_from_ci_daily_build_group_report_results.rb
new file mode 100644
index 00000000000..725521cb319
--- /dev/null
+++ b/db/post_migrate/20240812024133_partitioned_fk_to_ci_pipelines_from_ci_daily_build_group_report_results.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class PartitionedFkToCiPipelinesFromCiDailyBuildGroupReportResults < Gitlab::Database::Migration[2.2]
+ milestone '17.4'
+
+ SOURCE_TABLE_NAME = :ci_daily_build_group_report_results
+ COLUMN = :last_pipeline_id
+ PARTITION_COLUMN = :partition_id
+ FK_NAME = :fk_rails_ee072d13b3_p
+
+ def up
+ validate_foreign_key(
+ SOURCE_TABLE_NAME, [PARTITION_COLUMN, COLUMN],
+ name: FK_NAME
+ )
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20240812062443_finalize_backfill_auto_canceled_by_partition_id_for_pipelines.rb b/db/post_migrate/20240812062443_finalize_backfill_auto_canceled_by_partition_id_for_pipelines.rb
new file mode 100644
index 00000000000..445fabf21d4
--- /dev/null
+++ b/db/post_migrate/20240812062443_finalize_backfill_auto_canceled_by_partition_id_for_pipelines.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class FinalizeBackfillAutoCanceledByPartitionIdForPipelines < Gitlab::Database::Migration[2.2]
+ milestone '17.4'
+ disable_ddl_transaction!
+ restrict_gitlab_migration gitlab_schema: :gitlab_ci
+
+ MIGRATION = 'QueueBackfillAutocancelPartitionIdOnCiPipelines'
+ TABLE_NAME = :ci_pipelines
+ COLUMN_NAME = :id
+
+ def up
+ ensure_batched_background_migration_is_finished(
+ job_class_name: MIGRATION,
+ table_name: TABLE_NAME,
+ column_name: COLUMN_NAME,
+ job_arguments: [],
+ finalize: true
+ )
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20240812062522_validate_fk_auto_canceled_by_id_for_ci_pipelines.rb b/db/post_migrate/20240812062522_validate_fk_auto_canceled_by_id_for_ci_pipelines.rb
new file mode 100644
index 00000000000..c74b5abea82
--- /dev/null
+++ b/db/post_migrate/20240812062522_validate_fk_auto_canceled_by_id_for_ci_pipelines.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class ValidateFkAutoCanceledByIdForCiPipelines < Gitlab::Database::Migration[2.2]
+ milestone '17.4'
+
+ SOURCE_TABLE_NAME = :ci_pipelines
+ COLUMN = :auto_canceled_by_id
+ PARTITION_COLUMN = :auto_canceled_by_partition_id
+ FK_NAME = :fk_262d4c2d19_p
+
+ def up
+ validate_foreign_key(
+ SOURCE_TABLE_NAME, [PARTITION_COLUMN, COLUMN],
+ name: FK_NAME
+ )
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20240812084505_finalize_backfill_upstream_pipeline_partition_id_for_builds.rb b/db/post_migrate/20240812084505_finalize_backfill_upstream_pipeline_partition_id_for_builds.rb
new file mode 100644
index 00000000000..ddfc89f4cfa
--- /dev/null
+++ b/db/post_migrate/20240812084505_finalize_backfill_upstream_pipeline_partition_id_for_builds.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class FinalizeBackfillUpstreamPipelinePartitionIdForBuilds < Gitlab::Database::Migration[2.2]
+ milestone '17.4'
+ disable_ddl_transaction!
+ restrict_gitlab_migration gitlab_schema: :gitlab_ci
+
+ MIGRATION = 'BackfillUpstreamPipelinePartitionIdOnPCiBuilds'
+
+ def up
+ ensure_batched_background_migration_is_finished(
+ job_class_name: MIGRATION,
+ table_name: :p_ci_builds,
+ column_name: :upstream_pipeline_id,
+ job_arguments: [],
+ finalize: true
+ )
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20240812084612_add_fk_upstream_pipeline_id_for_p_ci_builds.rb b/db/post_migrate/20240812084612_add_fk_upstream_pipeline_id_for_p_ci_builds.rb
new file mode 100644
index 00000000000..9bd88bda812
--- /dev/null
+++ b/db/post_migrate/20240812084612_add_fk_upstream_pipeline_id_for_p_ci_builds.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+class AddFkUpstreamPipelineIdForPCiBuilds < Gitlab::Database::Migration[2.2]
+ include Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers
+
+ milestone '17.4'
+ disable_ddl_transaction!
+
+ SOURCE_TABLE_NAME = :p_ci_builds
+ TARGET_TABLE_NAME = :ci_pipelines
+ COLUMN = :upstream_pipeline_id
+ PARTITION_COLUMN = :upstream_pipeline_partition_id
+ TARGET_COLUMN = :id
+ TARGET_PARTITION_COLUMN = :partition_id
+ FK_NAME = :fk_87f4cefcda_p
+
+ def up
+ add_concurrent_partitioned_foreign_key(
+ SOURCE_TABLE_NAME,
+ TARGET_TABLE_NAME,
+ column: [PARTITION_COLUMN, COLUMN],
+ target_column: [TARGET_PARTITION_COLUMN, TARGET_COLUMN],
+ validate: true,
+ reverse_lock_order: true,
+ on_update: :cascade,
+ on_delete: :cascade,
+ name: FK_NAME
+ )
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key_if_exists(
+ SOURCE_TABLE_NAME,
+ TARGET_TABLE_NAME,
+ name: FK_NAME,
+ reverse_lock_order: true
+ )
+ end
+
+ add_concurrent_partitioned_foreign_key(
+ SOURCE_TABLE_NAME,
+ TARGET_TABLE_NAME,
+ column: [PARTITION_COLUMN, COLUMN],
+ target_column: [TARGET_PARTITION_COLUMN, TARGET_COLUMN],
+ validate: false,
+ reverse_lock_order: true,
+ on_update: :cascade,
+ on_delete: :cascade,
+ name: FK_NAME
+ )
+ end
+end
diff --git a/db/schema_migrations/20240807143914 b/db/schema_migrations/20240807143914
new file mode 100644
index 00000000000..33d04fa7e99
--- /dev/null
+++ b/db/schema_migrations/20240807143914
@@ -0,0 +1 @@
+074508a89bf7e84826fe6c94093a8ba8c3022a88be8f583d9043e2208e1a934a
\ No newline at end of file
diff --git a/db/schema_migrations/20240812010237 b/db/schema_migrations/20240812010237
new file mode 100644
index 00000000000..8e42dc5561e
--- /dev/null
+++ b/db/schema_migrations/20240812010237
@@ -0,0 +1 @@
+7eafb011d36e7a20e6ca47951f96beb512997abda4dda0a4de12b80bd406f451
\ No newline at end of file
diff --git a/db/schema_migrations/20240812024133 b/db/schema_migrations/20240812024133
new file mode 100644
index 00000000000..39be18ec735
--- /dev/null
+++ b/db/schema_migrations/20240812024133
@@ -0,0 +1 @@
+4fe56834e4bcb4750cab7a95f9baa452761262721250617d15760e3e3d64f19e
\ No newline at end of file
diff --git a/db/schema_migrations/20240812062443 b/db/schema_migrations/20240812062443
new file mode 100644
index 00000000000..b80b89969e3
--- /dev/null
+++ b/db/schema_migrations/20240812062443
@@ -0,0 +1 @@
+ed4ecaa771a6b6f3a233d08a209d0e6749159c50f91c671c2cd6ec14a4826f39
\ No newline at end of file
diff --git a/db/schema_migrations/20240812062522 b/db/schema_migrations/20240812062522
new file mode 100644
index 00000000000..27edda5d608
--- /dev/null
+++ b/db/schema_migrations/20240812062522
@@ -0,0 +1 @@
+10011db577bc89ecc877a22f7088038eeb564ea2d013a36d7522306aa1b04239
\ No newline at end of file
diff --git a/db/schema_migrations/20240812084505 b/db/schema_migrations/20240812084505
new file mode 100644
index 00000000000..2840811bd7f
--- /dev/null
+++ b/db/schema_migrations/20240812084505
@@ -0,0 +1 @@
+7725aa4f55c8e94d3b89f95b2a8760dc21e2d679e476c08a1117077ae58a88d2
\ No newline at end of file
diff --git a/db/schema_migrations/20240812084612 b/db/schema_migrations/20240812084612
new file mode 100644
index 00000000000..3133548bcff
--- /dev/null
+++ b/db/schema_migrations/20240812084612
@@ -0,0 +1 @@
+9a962866caa0a9101bc5e9a992b5f28a7383599108464d009d7bff9ada44fc44
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 02180120398..17b9085171e 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -32630,7 +32630,7 @@ ALTER TABLE ONLY ci_pipelines
ADD CONSTRAINT fk_262d4c2d19 FOREIGN KEY (auto_canceled_by_id) REFERENCES ci_pipelines(id) ON DELETE SET NULL;
ALTER TABLE ONLY ci_pipelines
- ADD CONSTRAINT fk_262d4c2d19_p FOREIGN KEY (auto_canceled_by_partition_id, auto_canceled_by_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE SET NULL NOT VALID;
+ ADD CONSTRAINT fk_262d4c2d19_p FOREIGN KEY (auto_canceled_by_partition_id, auto_canceled_by_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE SET NULL;
ALTER TABLE ONLY user_namespace_callouts
ADD CONSTRAINT fk_27a69fd1bd FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
@@ -33154,8 +33154,8 @@ ALTER TABLE ONLY packages_package_files
ALTER TABLE p_ci_builds
ADD CONSTRAINT fk_87f4cefcda FOREIGN KEY (upstream_pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
-ALTER TABLE ONLY ci_builds
- ADD CONSTRAINT fk_87f4cefcda_p FOREIGN KEY (upstream_pipeline_partition_id, upstream_pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ALTER TABLE p_ci_builds
+ ADD CONSTRAINT fk_87f4cefcda_p FOREIGN KEY (upstream_pipeline_partition_id, upstream_pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY approval_group_rules_users
ADD CONSTRAINT fk_888a0df3b7 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
@@ -35726,7 +35726,7 @@ ALTER TABLE ONLY ci_daily_build_group_report_results
ADD CONSTRAINT fk_rails_ee072d13b3 FOREIGN KEY (last_pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_daily_build_group_report_results
- ADD CONSTRAINT fk_rails_ee072d13b3_p FOREIGN KEY (partition_id, last_pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_rails_ee072d13b3_p FOREIGN KEY (partition_id, last_pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY import_source_users
ADD CONSTRAINT fk_rails_ee30e569be FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md
index 4597444176a..674be5d7e11 100644
--- a/doc/administration/incoming_email.md
+++ b/doc/administration/incoming_email.md
@@ -1033,3 +1033,19 @@ gitlab-ctl restart mailroom
```
::EndTabs
+
+### Incoming emails are rejected by providers with email address limit
+
+Your GitLab instance might not receive incoming emails, because some email providers impose a
+64-character limit on the local part of the email address (before the `@`).
+All emails from addresses that exceed this limit are rejected emails.
+
+As a workaround, maintain a shorter path:
+
+- Ensure that the local part configured before `%{key}` in `incoming_email_address` is as short as
+ possible, and not longer than 31 characters.
+- Place the designated projects at a higher group hierarchy.
+- Rename [groups](../user/group/manage.md#change-a-groups-path) and
+ [project](../user/project/working_with_projects.md#rename-a-repository) to shorter names.
+
+Track this feature in [issue 460206](https://gitlab.com/gitlab-org/gitlab/-/issues/460206).
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 2a111547f58..6fa9aa092d9 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -7048,6 +7048,32 @@ Input type: `MlModelDestroyInput`
| `message` | [`String`](#string) | Model deletion result message. |
| `model` | [`MlModel`](#mlmodel) | Model after mutation. |
+### `Mutation.mlModelEdit`
+
+DETAILS:
+**Introduced** in GitLab 17.3.
+**Status**: Experiment.
+
+Input type: `MlModelEditInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `description` | [`String`](#string) | Description of the model. |
+| `modelId` | [`Int`](#int) | Id of the model. |
+| `name` | [`String!`](#string) | Name of the model. |
+| `projectPath` | [`ID!`](#id) | Project the model to mutate is in. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+| `model` | [`MlModel`](#mlmodel) | Model after mutation. |
+
### `Mutation.mlModelVersionCreate`
DETAILS:
diff --git a/doc/api/project_container_registry_protection_rules.md b/doc/api/project_container_registry_protection_rules.md
index 280771af6b4..a63cb04b393 100644
--- a/doc/api/project_container_registry_protection_rules.md
+++ b/doc/api/project_container_registry_protection_rules.md
@@ -161,7 +161,7 @@ curl --request PATCH \
## Delete a container registry protection rule
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/457518) in GitLab 17.3.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/457518) in GitLab 17.4.
Deletes a container registry protection rule from a project.
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 934f84615e2..7f3a378f24a 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -273,8 +273,8 @@ this limit. Repository limits apply to both public and private projects.
The [import sources](../project/import/index.md#supported-import-sources) that are available to you by default depend on
which GitLab you use:
-- GitLab.com: all available import sources are enabled by default.
-- GitLab self-managed: no import sources are enabled by default and must be
+- GitLab.com: All available import sources are enabled by default.
+- GitLab self-managed: No import sources are enabled by default and must be
[enabled](../../administration/settings/import_and_export_settings.md#configure-allowed-import-sources).
## IP range
@@ -286,7 +286,7 @@ from those IPs and allow them.
GitLab.com is fronted by Cloudflare. For incoming connections to GitLab.com, you might need to allow CIDR blocks of Cloudflare ([IPv4](https://www.cloudflare.com/ips-v4/) and [IPv6](https://www.cloudflare.com/ips-v6/)).
For outgoing connections from CI/CD runners, we are not providing static IP addresses.
-All GitLab.com instance runners are deployed into Google Cloud Platform (GCP) in `us-east1`.
+Most GitLab.com instance runners are deployed into Google Cloud Platform (GCP) in `us-east1`, except _Linux GPU-enabled_ and _Linux Arm64_, hosted in `us-central1`.
Any IP-based firewall can be configured by looking up
[IP address ranges or CIDR blocks for GCP](https://cloud.google.com/compute/docs/faq#find_ip_range).
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 0131185316c..7a053700ce3 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -87,13 +87,13 @@ The following features are not found in standard Markdown:
The following features are extended from standard Markdown:
-| Standard Markdown | Extended Markdown in GitLab |
-|---------------------------------------|---------------------------------------------------------------------------------------|
-| [Blockquotes](#blockquotes) | [Multiline blockquotes](#multiline-blockquote) |
-| [Code blocks](#code-spans-and-blocks) | [Colored code and syntax highlighting](#syntax-highlighting) |
-| [Headings](#headings) | [Linkable heading IDs](#heading-ids-and-links) |
-| [Images](#images) | [Embedded videos](#videos) and [audio](#audio) |
-| [Links](#links) | [Automatically linking URLs](#url-auto-linking) |
+| Standard Markdown | Extended Markdown in GitLab |
+|---------------------------------------|-----------------------------|
+| [Blockquotes](#blockquotes) | [Multiline blockquotes](#multiline-blockquote) |
+| [Code blocks](#code-spans-and-blocks) | [Colored code and syntax highlighting](#syntax-highlighting) |
+| [Headings](#headings) | [Linkable heading IDs](#heading-ids-and-links) |
+| [Images](#images) | [Embedded videos](#videos) and [audio](#audio) |
+| [Links](#links) | [Automatically linking URLs](#url-auto-linking) |
## Markdown and accessibility
@@ -1015,13 +1015,13 @@ The number of math blocks is also limited based on render time. If the limit is
GitLab renders the excess math instances as text. Wiki and repository files do not have
these limits.
-Math written between dollar signs with backticks (``$`...`$``) or single dollar signs (`$...$`)
+Math written between dollar signs with backticks (``` $`...`$ ```) or single dollar signs (`$...$`)
is rendered inline with the text.
Math written between double dollar signs (`$$...$$`) or in a [code block](#code-spans-and-blocks) with
the language declared as `math` is rendered on a separate line:
-````markdown
+`````markdown
This math is inline: $`a^2+b^2=c^2`$.
This math is on a separate line using a ```` ```math ```` block:
@@ -1037,7 +1037,7 @@ This math is on a separate line using a `$$...$$` block:
$$
a^2+b^2=c^2
$$
-````
+`````

@@ -1049,16 +1049,12 @@ When creating tables:
- The first line contains the headers, separated by "pipes" (`|`).
- The second line separates the headers from the cells.
- - The cells can contain only empty spaces, hyphens, and
- (optionally) colons for horizontal alignment.
- - Each cell must contain at least one hyphen, but adding more hyphens to a
- cell does not change the cell's rendering.
+ - The cells can contain only empty spaces, hyphens, and (optionally) colons for horizontal alignment.
+ - Each cell must contain at least one hyphen, but adding more hyphens to a cell does not change the cell's rendering.
- Any content other than hyphens, whitespace, or colons is not allowed
- The third, and any following lines, contain the cell values.
- - You **can't** have cells separated over many lines in the Markdown, they must be kept to single lines,
- but they can be very long. You can also include HTML `
` tags to force newlines if needed.
- - The cell sizes **don't** have to match each other. They are flexible, but must be separated
- by pipes (`|`).
+ - You **can't** have cells separated over many lines in the Markdown, they must be kept to single lines, but they can be very long. You can also include HTML `
` tags to force newlines if needed.
+ - The cell sizes **don't** have to match each other. They are flexible, but must be separated by pipes (`|`).
- You **can** have blank cells.
- Column widths are calculated dynamically based on the content of the cells.
- To use the pipe character (`|`) in the text and not as table delimiter, you must [escape](#escape-characters) it with a backslash (`\|`).
diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md
index 66588578cdb..63054f1478d 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -85,10 +85,10 @@ difficult, but several tools exist including:
DETAILS:
**Status:** Experiment
-> - [Introduced to migration by using direct transfer](https://gitlab.com/gitlab-org/gitlab/-/issues/443557) in GitLab 17.3 [with a flag](../../../administration/feature_flags.md) named `importer_user_mapping`. Disabled by default. This feature is an [experiment](../../../policy/experiment-beta-support.md).
+> - [Introduced to migration by using direct transfer](https://gitlab.com/gitlab-org/gitlab/-/issues/443557) in GitLab 17.4 [with flags](../../../administration/feature_flags.md) named `importer_user_mapping` and `bulk_import_importer_user_mapping`. Disabled by default. This feature is an [experiment](../../../policy/experiment-beta-support.md).
FLAG:
-The availability of this feature is controlled by a feature flag.
+The availability of this feature is controlled by feature flags.
For more information, see the history.
This feature is available for internal testing only, it is not ready for production use.
diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md
index f6a0884d6c0..443cf161447 100644
--- a/doc/user/project/issues/managing_issues.md
+++ b/doc/user/project/issues/managing_issues.md
@@ -263,6 +263,20 @@ it inherits the issue's milestone and labels.
For performance reasons, automatic issue closing is disabled for the very first
push from an existing repository.
+#### User responsibility when merging
+
+When you merge a merge request, it's your responsibility to check that it's appropriate for any targeted issues
+to close. Users can include issue closing patterns in the merge request description, and also in the body
+of a commit message. Closing messages in commit messages are easy to miss. In both cases, the merge request widget
+shows information about the issue to close on merge:
+
+
+
+When you merge a merge request, GitLab checks that you have permission to close the targeted issues.
+In public repositories, this check is important, because external users can create both merge requests
+and commits that contain closing patterns. When you are the user who merges, it's important
+that you are aware of the effects the merge has on both the code and issues in your project.
+
#### Default closing pattern
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/465391) work item (task, objective, or key result) references in GitLab 17.3.
diff --git a/doc/user/project/merge_requests/auto_merge.md b/doc/user/project/merge_requests/auto_merge.md
index 1415173d2e0..ee27cc273cd 100644
--- a/doc/user/project/merge_requests/auto_merge.md
+++ b/doc/user/project/merge_requests/auto_merge.md
@@ -65,6 +65,10 @@ To do this from the GitLab user interface:
1. Scroll to the merge request reports section.
1. Optional. Select your desired merge options, such as **Delete source branch**,
**Squash commits**, or **Edit commit message**.
+1. Review the contents of the merge request widget. If it contains an
+ [issue closing pattern](../issues/managing_issues.md#closing-issues-automatically), confirm
+ that the issue should close when this work merges:
+ 
1. Select **Auto-merge**.
Commenting on a merge request after you select **Auto-merge**,
diff --git a/doc/user/project/merge_requests/img/closing_pattern_v17_4.png b/doc/user/project/merge_requests/img/closing_pattern_v17_4.png
new file mode 100644
index 00000000000..552217fa256
Binary files /dev/null and b/doc/user/project/merge_requests/img/closing_pattern_v17_4.png differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index dca94724d2f..b37e5d7f3f1 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -173,6 +173,13 @@ a merge request, or:
GitLab adds the merge request to the user's **Assigned merge requests** page.
+## Merge a merge request
+
+During the merge request [review process](reviews/index.md), reviewers provide feedback on your merge request.
+When a reviewer decides that the contents of your merge request are acceptable, the reviewer can set
+[auto-merge](auto_merge.md) on it, even if some merge checks are failing. When a merge request is set to auto-merge,
+it merges after all merge checks pass, without more action from you.
+
## Close a merge request
If you decide to permanently stop work on a merge request, close it rather than
diff --git a/lib/api/concerns/virtual_registries/packages/endpoint.rb b/lib/api/concerns/virtual_registries/packages/endpoint.rb
index 8a58ac1a8d3..3350d11658b 100644
--- a/lib/api/concerns/virtual_registries/packages/endpoint.rb
+++ b/lib/api/concerns/virtual_registries/packages/endpoint.rb
@@ -22,11 +22,6 @@ module API
}.freeze
included do
- include ::API::Helpers::Authentication
-
- feature_category :virtual_registry
- urgency :low
-
helpers do
def require_non_web_browser!
browser = ::Browser.new(request.user_agent)
@@ -79,12 +74,7 @@ module API
end
after_validation do
- not_found! unless Feature.enabled?(:virtual_registry_maven, current_user)
-
require_non_web_browser!
- require_dependency_proxy_enabled!
-
- authenticate!
end
end
end
diff --git a/lib/api/concerns/virtual_registries/packages/maven/upstream_endpoints.rb b/lib/api/concerns/virtual_registries/packages/maven/upstream_endpoints.rb
new file mode 100644
index 00000000000..0ee20de681c
--- /dev/null
+++ b/lib/api/concerns/virtual_registries/packages/maven/upstream_endpoints.rb
@@ -0,0 +1,151 @@
+# frozen_string_literal: true
+
+module API
+ module Concerns
+ module VirtualRegistries
+ module Packages
+ module Maven
+ module UpstreamEndpoints
+ extend ActiveSupport::Concern
+
+ included do
+ desc 'List all maven virtual registry upstreams' do
+ detail 'This feature was introduced in GitLab 17.3. \
+ This feature is currently in experiment state. \
+ This feature behind the `virtual_registry_maven` feature flag.'
+ success code: 200
+ failure [
+ { code: 400, message: 'Bad Request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not found' }
+ ]
+ tags %w[maven_virtual_registries]
+ hidden true
+ end
+ get do
+ authorize! :read_virtual_registry, registry
+
+ present [upstream].compact, with: Entities::VirtualRegistries::Packages::Maven::Upstream
+ end
+
+ desc 'Add a maven virtual registry upstream' do
+ detail 'This feature was introduced in GitLab 17.3. \
+ This feature is currently in experiment state. \
+ This feature behind the `virtual_registry_maven` feature flag.'
+ success code: 201
+ failure [
+ { code: 400, message: 'Bad Request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not found' },
+ { code: 409, message: 'Conflict' }
+ ]
+ tags %w[maven_virtual_registries]
+ hidden true
+ end
+ params do
+ requires :url, type: String, desc: 'The URL of the maven virtual registry upstream', allow_blank: false
+ optional :username, type: String, desc: 'The username of the maven virtual registry upstream'
+ optional :password, type: String, desc: 'The password of the maven virtual registry upstream'
+ all_or_none_of :username, :password
+ end
+ post do
+ authorize! :create_virtual_registry, registry
+
+ conflict!(_('Upstream already exists')) if upstream
+
+ registry.build_upstream(declared_params.merge(group: group))
+ registry_upstream.group = group
+
+ ApplicationRecord.transaction do
+ render_validation_error!(upstream) unless upstream.save
+ render_validation_error!(registry_upstream) unless registry_upstream.save
+ end
+
+ created!
+ end
+
+ route_param :upstream_id, type: Integer, desc: 'The ID of the maven virtual registry upstream' do
+ desc 'Get a specific maven virtual registry upstream' do
+ detail 'This feature was introduced in GitLab 17.3. \
+ This feature is currently in experiment state. \
+ This feature behind the `virtual_registry_maven` feature flag.'
+ success code: 200
+ failure [
+ { code: 400, message: 'Bad Request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not found' }
+ ]
+ tags %w[maven_virtual_registries]
+ hidden true
+ end
+ get do
+ authorize! :read_virtual_registry, registry
+
+ not_found! if upstream&.id != params[:upstream_id]
+
+ present upstream, with: Entities::VirtualRegistries::Packages::Maven::Upstream
+ end
+
+ desc 'Update a maven virtual registry upstream' do
+ detail 'This feature was introduced in GitLab 17.3. \
+ This feature is currently in experiment state. \
+ This feature behind the `virtual_registry_maven` feature flag.'
+ success code: 200
+ failure [
+ { code: 400, message: 'Bad Request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not found' }
+ ]
+ tags %w[maven_virtual_registries]
+ hidden true
+ end
+ params do
+ optional :url, type: String, desc: 'The URL of the maven virtual registry upstream',
+ allow_blank: false
+ optional :username, type: String, desc: 'The username of the maven virtual registry upstream',
+ allow_blank: false
+ optional :password, type: String, desc: 'The password of the maven virtual registry upstream',
+ allow_blank: false
+ at_least_one_of :url, :username, :password
+ end
+ patch do
+ authorize! :update_virtual_registry, registry
+
+ render_validation_error!(upstream) unless upstream.update(declared_params(include_missing: false))
+
+ status :ok
+ end
+
+ desc 'Delete a maven virtual registry upstream' do
+ detail 'This feature was introduced in GitLab 17.3. \
+ This feature is currently in experiment state. \
+ This feature behind the `virtual_registry_maven` feature flag.'
+ success code: 204
+ failure [
+ { code: 400, message: 'Bad Request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not found' }
+ ]
+ tags %w[maven_virtual_registries]
+ hidden true
+ end
+ delete do
+ authorize! :destroy_virtual_registry, registry
+
+ not_found! if upstream&.id != params[:upstream_id]
+
+ destroy_conditionally!(upstream)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/virtual_registries/packages/maven/upstream.rb b/lib/api/entities/virtual_registries/packages/maven/upstream.rb
new file mode 100644
index 00000000000..9a9f336ee37
--- /dev/null
+++ b/lib/api/entities/virtual_registries/packages/maven/upstream.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module VirtualRegistries
+ module Packages
+ module Maven
+ class Upstream < Grape::Entity
+ expose :id, :group_id, :url, :created_at, :updated_at
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/ml/mlflow/registered_models.rb b/lib/api/ml/mlflow/registered_models.rb
index 4059d6ec0d1..295d6c73b42 100644
--- a/lib/api/ml/mlflow/registered_models.rb
+++ b/lib/api/ml/mlflow/registered_models.rb
@@ -78,7 +78,9 @@ module API
desc: 'Optional description for registered model.'
end
patch 'update', urgency: :low do
- present ::Ml::UpdateModelService.new(find_model(user_project, params[:name]), params[:description]).execute,
+ present ::Ml::UpdateModelService.new(
+ find_model(user_project, params[:name]), params[:description]
+ ).execute.payload,
with: Entities::Ml::Mlflow::RegisteredModel, root: :registered_model
end
diff --git a/lib/api/virtual_registries/packages/maven.rb b/lib/api/virtual_registries/packages/maven.rb
index 3cd6b3be428..63acb4670d0 100644
--- a/lib/api/virtual_registries/packages/maven.rb
+++ b/lib/api/virtual_registries/packages/maven.rb
@@ -4,8 +4,12 @@ module API
module VirtualRegistries
module Packages
class Maven < ::API::Base
+ include ::API::Helpers::Authentication
include ::API::Concerns::VirtualRegistries::Packages::Endpoint
+ feature_category :virtual_registry
+ urgency :low
+
authenticate_with do |accept|
accept.token_types(:personal_access_token).sent_through(:http_private_token_header)
accept.token_types(:deploy_token).sent_through(:http_deploy_token_header)
@@ -21,47 +25,71 @@ module API
helpers do
include ::Gitlab::Utils::StrongMemoize
+ delegate :group, :upstream, :registry_upstream, to: :registry
+
+ def require_dependency_proxy_enabled!
+ not_found! unless ::Gitlab.config.dependency_proxy.enabled
+ end
+
def registry
- ::VirtualRegistries::Packages::Maven::Registry.find(declared_params[:id])
+ ::VirtualRegistries::Packages::Maven::Registry.find(params[:id])
end
strong_memoize_attr :registry
end
- desc 'Download endpoint of the Maven virtual registry.' do
- detail 'This feature was introduced in GitLab 17.3. \
- This feature is currently in experiment state. \
- This feature behind the `virtual_registry_maven` feature flag.'
- success [
- { code: 200 }
- ]
- failure [
- { code: 400, message: 'Bad request' },
- { code: 401, message: 'Unauthorized' },
- { code: 403, message: 'Forbidden' },
- { code: 404, message: 'Not Found' }
- ]
- tags %w[maven_virtual_registries]
- hidden true
- end
- params do
- requires :id,
- type: Integer,
- desc: 'The ID of the Maven virtual registry'
- requires :path,
- type: String,
- file_path: true,
- desc: 'Package path',
- documentation: { example: 'foo/bar/mypkg/1.0-SNAPSHOT/mypkg-1.0-SNAPSHOT.jar' }
- end
- get 'virtual_registries/packages/maven/:id/*path', format: false do
- service_response = ::VirtualRegistries::Packages::Maven::HandleFileRequestService.new(
- registry: registry,
- current_user: current_user,
- params: { path: declared_params[:path] }
- ).execute
+ after_validation do
+ not_found! unless Feature.enabled?(:virtual_registry_maven, current_user)
- send_error_response_from!(service_response: service_response) if service_response.error?
- send_successful_response_from(service_response: service_response)
+ require_dependency_proxy_enabled!
+
+ authenticate!
+ end
+
+ namespace 'virtual_registries/packages/maven' do
+ namespace :registries do
+ route_param :id, type: Integer, desc: 'The ID of the maven virtual registry' do
+ namespace :upstreams do
+ include ::API::Concerns::VirtualRegistries::Packages::Maven::UpstreamEndpoints
+ end
+ end
+ end
+
+ desc 'Download endpoint of the Maven virtual registry.' do
+ detail 'This feature was introduced in GitLab 17.3. \
+ This feature is currently in experiment state. \
+ This feature behind the `virtual_registry_maven` feature flag.'
+ success [
+ { code: 200 }
+ ]
+ failure [
+ { code: 400, message: 'Bad request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not Found' }
+ ]
+ tags %w[maven_virtual_registries]
+ hidden true
+ end
+ params do
+ requires :id,
+ type: Integer,
+ desc: 'The ID of the Maven virtual registry'
+ requires :path,
+ type: String,
+ file_path: true,
+ desc: 'Package path',
+ documentation: { example: 'foo/bar/mypkg/1.0-SNAPSHOT/mypkg-1.0-SNAPSHOT.jar' }
+ end
+ get ':id/*path', format: false do
+ service_response = ::VirtualRegistries::Packages::Maven::HandleFileRequestService.new(
+ registry: registry,
+ current_user: current_user,
+ params: { path: declared_params[:path] }
+ ).execute
+
+ send_error_response_from!(service_response: service_response) if service_response.error?
+ send_successful_response_from(service_response: service_response)
+ end
end
end
end
diff --git a/lib/bulk_imports/common/graphql/get_members_query.rb b/lib/bulk_imports/common/graphql/get_members_query.rb
index 4f7533ee25f..9fcd1da7013 100644
--- a/lib/bulk_imports/common/graphql/get_members_query.rb
+++ b/lib/bulk_imports/common/graphql/get_members_query.rb
@@ -30,6 +30,7 @@ module BulkImports
user_gid: id
public_email: publicEmail
username: username
+ name: name
}
}
}
diff --git a/lib/bulk_imports/common/pipelines/members_pipeline.rb b/lib/bulk_imports/common/pipelines/members_pipeline.rb
index dbaae8f6ef2..15512c0bbc7 100644
--- a/lib/bulk_imports/common/pipelines/members_pipeline.rb
+++ b/lib/bulk_imports/common/pipelines/members_pipeline.rb
@@ -10,7 +10,10 @@ module BulkImports
PROJECT_MEMBER_RELATIONS = %i[direct inherited invited_groups shared_into_ancestors].freeze
transformer Common::Transformers::ProhibitedAttributesTransformer
+ # The transformer is skipped when bulk_import_importer_user_mapping is enabled
transformer Common::Transformers::MemberAttributesTransformer
+ # The transformer is skipped when bulk_import_importer_user_mapping is disabled
+ transformer Import::BulkImports::Common::Transformers::SourceUserMemberAttributesTransformer
def extract(context)
graphql_extractor.extract(context)
diff --git a/lib/bulk_imports/common/transformers/member_attributes_transformer.rb b/lib/bulk_imports/common/transformers/member_attributes_transformer.rb
index 4cd87ec2b59..54f8be08a9a 100644
--- a/lib/bulk_imports/common/transformers/member_attributes_transformer.rb
+++ b/lib/bulk_imports/common/transformers/member_attributes_transformer.rb
@@ -5,6 +5,8 @@ module BulkImports
module Transformers
class MemberAttributesTransformer
def transform(context, data)
+ return data if context.importer_user_mapping_enabled?
+
user = find_user(data&.dig('user', 'public_email'))
access_level = data&.dig('access_level', 'integer_value')
diff --git a/lib/import/bulk_imports/common/transformers/source_user_member_attributes_transformer.rb b/lib/import/bulk_imports/common/transformers/source_user_member_attributes_transformer.rb
new file mode 100644
index 00000000000..aa7cdb16e6c
--- /dev/null
+++ b/lib/import/bulk_imports/common/transformers/source_user_member_attributes_transformer.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Import
+ module BulkImports
+ module Common
+ module Transformers
+ class SourceUserMemberAttributesTransformer
+ def transform(context, data)
+ return data if !context.importer_user_mapping_enabled? || data.nil?
+
+ # Create source_user and placeholder user if they do not exists so
+ # they can be mapped to contributions in subsequent pipelines
+ source_user = find_or_create_source_user(context, data)
+
+ # For now, members are only created on subsequent imports for users
+ # that have accepted the reassignment.
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/477845 will handle
+ # membership for source_users mapped to placeholder users
+ return unless source_user.accepted_status?
+
+ access_level = data.dig('access_level', 'integer_value')
+ return unless valid_access_level?(access_level)
+
+ {
+ user_id: source_user.mapped_user_id,
+ access_level: access_level,
+ created_at: data['created_at'],
+ updated_at: data['updated_at'],
+ expires_at: data['expires_at'],
+ created_by_id: context.current_user.id
+ }
+ end
+
+ private
+
+ def valid_access_level?(access_level)
+ Gitlab::Access.options_with_owner.value?(access_level)
+ end
+
+ def find_or_create_source_user(context, data)
+ gid = data.dig('user', 'user_gid')
+
+ source_user_id = GlobalID.parse(gid).model_id
+ source_name = data.dig('user', 'name')
+ source_username = data.dig('user', 'username')
+
+ context.source_user_mapper.find_or_create_source_user(
+ source_user_identifier: source_user_id,
+ source_name: source_name,
+ source_username: source_username
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3c82de05f3f..d927e064645 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1025,9 +1025,6 @@ msgstr ""
msgid "%{linkStart}Advanced search%{linkEnd} is enabled."
msgstr ""
-msgid "%{link_start}Add a start date and due date%{link_end} to view a burndown chart."
-msgstr ""
-
msgid "%{listToShow}, and %{awardsListLength} more"
msgstr ""
@@ -3119,6 +3116,9 @@ msgstr ""
msgid "Add Zoom meeting"
msgstr ""
+msgid "Add a %{link_start}start date and due date%{link_end} to view a burndown chart."
+msgstr ""
+
msgid "Add a GCP region"
msgstr ""
@@ -19725,6 +19725,27 @@ msgstr ""
msgid "DuoCodeReview|I have encountered some issues while I was reviewing. Please try again later."
msgstr ""
+msgid "DuoEnterpriseTrial|Congratulations, your free GitLab Duo Enterprise trial is activated and will expire on %{exp_date}. The new license might take a minute to show on the page. To give members access to new GitLab Duo Enterprise features, %{assign_link_start}assign them%{assign_link_end} to GitLab Duo Enterprise seats."
+msgstr ""
+
+msgid "DuoEnterpriseTrial|Enhance security and remediate vulnerabilities efficiently"
+msgstr ""
+
+msgid "DuoEnterpriseTrial|Gain deeper insights into GitLab Duo usage patterns"
+msgstr ""
+
+msgid "DuoEnterpriseTrial|GitLab Duo Enterprise is is your end-to-end AI partner for faster, more secure software development."
+msgstr ""
+
+msgid "DuoEnterpriseTrial|GitLab Duo Enterprise is only available for purchase for Ultimate customers."
+msgstr ""
+
+msgid "DuoEnterpriseTrial|Maintain control and keep your data safe"
+msgstr ""
+
+msgid "DuoEnterpriseTrial|Quickly remedy broken pipelines to deliver products faster"
+msgstr ""
+
msgid "DuoEnterpriseTrial|Start your free Duo Enterprise trial"
msgstr ""
@@ -19734,6 +19755,9 @@ msgstr ""
msgid "DuoEnterpriseTrial|Start your free GitLab Duo Enterprise trial on %{group_name}"
msgstr ""
+msgid "DuoEnterpriseTrial|Stay on top of regulatory requirements with self-hosted model deployment"
+msgstr ""
+
msgid "DuoEnterpriseTrial|We just need some additional information to activate your trial."
msgstr ""
@@ -57232,6 +57256,9 @@ msgstr ""
msgid "Upstream Gitaly has been exhausted. Try again later"
msgstr ""
+msgid "Upstream already exists"
+msgstr ""
+
msgid "Upvotes"
msgstr ""
diff --git a/package.json b/package.json
index 5c0cb1c5b3b..a45d67d771d 100644
--- a/package.json
+++ b/package.json
@@ -84,41 +84,41 @@
"@snowplow/browser-plugin-timezone": "^3.24.2",
"@snowplow/browser-tracker": "^3.24.2",
"@sourcegraph/code-host-integration": "0.0.95",
- "@tiptap/core": "^2.5.9",
- "@tiptap/extension-blockquote": "^2.5.9",
- "@tiptap/extension-bold": "^2.5.9",
- "@tiptap/extension-bubble-menu": "^2.5.9",
- "@tiptap/extension-bullet-list": "^2.5.9",
- "@tiptap/extension-code": "^2.5.9",
- "@tiptap/extension-code-block": "^2.5.9",
- "@tiptap/extension-code-block-lowlight": "^2.5.9",
- "@tiptap/extension-document": "^2.5.9",
- "@tiptap/extension-dropcursor": "^2.5.9",
- "@tiptap/extension-gapcursor": "^2.5.9",
- "@tiptap/extension-hard-break": "^2.5.9",
- "@tiptap/extension-heading": "^2.5.9",
- "@tiptap/extension-highlight": "^2.5.9",
- "@tiptap/extension-history": "^2.5.9",
- "@tiptap/extension-horizontal-rule": "^2.5.9",
- "@tiptap/extension-image": "^2.5.9",
- "@tiptap/extension-italic": "^2.5.9",
- "@tiptap/extension-link": "^2.5.9",
- "@tiptap/extension-list-item": "^2.5.9",
- "@tiptap/extension-ordered-list": "^2.5.9",
- "@tiptap/extension-paragraph": "^2.5.9",
- "@tiptap/extension-strike": "^2.5.9",
- "@tiptap/extension-subscript": "^2.5.9",
- "@tiptap/extension-superscript": "^2.5.9",
- "@tiptap/extension-table": "^2.5.9",
- "@tiptap/extension-table-cell": "^2.5.9",
- "@tiptap/extension-table-header": "^2.5.9",
- "@tiptap/extension-table-row": "^2.5.9",
- "@tiptap/extension-task-item": "^2.5.9",
- "@tiptap/extension-task-list": "^2.5.9",
- "@tiptap/extension-text": "^2.5.9",
- "@tiptap/pm": "^2.5.9",
- "@tiptap/suggestion": "^2.5.9",
- "@tiptap/vue-2": "^2.5.9",
+ "@tiptap/core": "^2.6.0",
+ "@tiptap/extension-blockquote": "^2.6.0",
+ "@tiptap/extension-bold": "^2.6.0",
+ "@tiptap/extension-bubble-menu": "^2.6.0",
+ "@tiptap/extension-bullet-list": "^2.6.0",
+ "@tiptap/extension-code": "^2.6.0",
+ "@tiptap/extension-code-block": "^2.6.0",
+ "@tiptap/extension-code-block-lowlight": "^2.6.0",
+ "@tiptap/extension-document": "^2.6.0",
+ "@tiptap/extension-dropcursor": "^2.6.0",
+ "@tiptap/extension-gapcursor": "^2.6.0",
+ "@tiptap/extension-hard-break": "^2.6.0",
+ "@tiptap/extension-heading": "^2.6.0",
+ "@tiptap/extension-highlight": "^2.6.0",
+ "@tiptap/extension-history": "^2.6.0",
+ "@tiptap/extension-horizontal-rule": "^2.6.0",
+ "@tiptap/extension-image": "^2.6.0",
+ "@tiptap/extension-italic": "^2.6.0",
+ "@tiptap/extension-link": "^2.6.0",
+ "@tiptap/extension-list-item": "^2.6.0",
+ "@tiptap/extension-ordered-list": "^2.6.0",
+ "@tiptap/extension-paragraph": "^2.6.0",
+ "@tiptap/extension-strike": "^2.6.0",
+ "@tiptap/extension-subscript": "^2.6.0",
+ "@tiptap/extension-superscript": "^2.6.0",
+ "@tiptap/extension-table": "^2.6.0",
+ "@tiptap/extension-table-cell": "^2.6.0",
+ "@tiptap/extension-table-header": "^2.6.0",
+ "@tiptap/extension-table-row": "^2.6.0",
+ "@tiptap/extension-task-item": "^2.6.0",
+ "@tiptap/extension-task-list": "^2.6.0",
+ "@tiptap/extension-text": "^2.6.0",
+ "@tiptap/pm": "^2.6.0",
+ "@tiptap/suggestion": "^2.6.0",
+ "@tiptap/vue-2": "^2.6.0",
"@vue/apollo-components": "^4.0.0-beta.4",
"@vue/apollo-option": "^4.0.0-beta.4",
"apollo-upload-client": "15.0.0",
diff --git a/spec/features/projects/work_items/work_item_children_spec.rb b/spec/features/projects/work_items/work_item_children_spec.rb
index b4a674d8ee7..48a37dd86c4 100644
--- a/spec/features/projects/work_items/work_item_children_spec.rb
+++ b/spec/features/projects/work_items/work_item_children_spec.rb
@@ -58,7 +58,8 @@ RSpec.describe 'Work item children', :js, feature_category: :team_planning do
end
end
- it 'adds a new child task', :aggregate_failures do
+ it 'adds a new child task', :aggregate_failures,
+ quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/461666' do
allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(108)
within_testid('work-item-links') do
diff --git a/spec/frontend/integrations/beyond_identity/components/exclusions_tabs_spec.js b/spec/frontend/integrations/beyond_identity/components/exclusions_tabs_spec.js
index 271122cd00f..11a872181d5 100644
--- a/spec/frontend/integrations/beyond_identity/components/exclusions_tabs_spec.js
+++ b/spec/frontend/integrations/beyond_identity/components/exclusions_tabs_spec.js
@@ -1,14 +1,20 @@
-import { GlNavItem, GlTabs, GlTab } from '@gitlab/ui';
+import { GlTabs, GlTab } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+
+import { visitUrl } from '~/lib/utils/url_utility';
import ExclusionsTabs from '~/integrations/beyond_identity/components/exclusions_tabs.vue';
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ visitUrl: jest.fn(),
+}));
+
describe('ExclusionsTabs component', () => {
let wrapper;
const editPath = 'path/to/edit';
const findTabs = () => wrapper.findComponent(GlTabs);
- const findNavItem = () => wrapper.findComponent(GlNavItem);
- const findTab = () => wrapper.findComponent(GlTab);
+ const findAllTabs = () => wrapper.findAllComponents(GlTab);
const createComponent = () =>
shallowMountExtended(ExclusionsTabs, {
@@ -28,12 +34,21 @@ describe('ExclusionsTabs component', () => {
});
it('renders a nav item for Settings', () => {
- expect(findNavItem().text()).toBe('Settings');
- expect(findNavItem().attributes('href')).toBe(editPath);
+ const tab = findAllTabs().at(0);
+ expect(tab.attributes('title')).toBe('Settings');
});
it('renders a tab for Exclusions', () => {
- expect(findTab().text()).toBe('Exclusions');
+ const tab = findAllTabs().at(1);
+ expect(tab.attributes('title')).toBe('Exclusions');
+ });
+
+ it('redirects to editPath when the settings tab is clicked', async () => {
+ const tab = findAllTabs().at(0);
+
+ await tab.vm.$emit('click');
+
+ expect(visitUrl).toHaveBeenCalledWith(editPath);
});
});
});
diff --git a/spec/frontend/integrations/overrides/components/integration_tabs_spec.js b/spec/frontend/integrations/overrides/components/integration_tabs_spec.js
index b35a40d69c1..a8392770119 100644
--- a/spec/frontend/integrations/overrides/components/integration_tabs_spec.js
+++ b/spec/frontend/integrations/overrides/components/integration_tabs_spec.js
@@ -1,16 +1,22 @@
-import { mount, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import { GlBadge, GlTab } from '@gitlab/ui';
+import { visitUrl } from '~/lib/utils/url_utility';
import IntegrationTabs from '~/integrations/overrides/components/integration_tabs.vue';
import { settingsTabTitle, overridesTabTitle } from '~/integrations/constants';
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ visitUrl: jest.fn(),
+}));
+
describe('IntegrationTabs', () => {
let wrapper;
const editPath = 'mock/edit';
- const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => {
- wrapper = mountFn(IntegrationTabs, {
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(IntegrationTabs, {
propsData: props,
provide: {
editPath,
@@ -22,28 +28,37 @@ describe('IntegrationTabs', () => {
};
const findGlBadge = () => wrapper.findComponent(GlBadge);
- const findGlTab = () => wrapper.findComponent(GlTab);
- const findSettingsLink = () => wrapper.find('a');
+ const findAllTabs = () => wrapper.findAllComponents(GlTab);
describe('template', () => {
- it('renders "Settings" tab as a link', () => {
- createComponent({ mountFn: mount });
+ it('renders "Settings" tab', () => {
+ createComponent();
- expect(findSettingsLink().text()).toMatchInterpolatedText(settingsTabTitle);
- expect(findSettingsLink().attributes('href')).toBe(editPath);
+ const tab = findAllTabs().at(0);
+
+ expect(tab.exists()).toBe(true);
+ expect(tab.attributes('title')).toBe(settingsTabTitle);
+ });
+
+ it('redirects to editPath when the settings tab is clicked', async () => {
+ createComponent();
+
+ const tab = findAllTabs().at(0);
+
+ await tab.vm.$emit('click');
+
+ expect(visitUrl).toHaveBeenCalledWith(editPath);
});
it('renders "Projects using custom settings" tab as active', () => {
const projectOverridesCount = '1';
- createComponent({
- props: { projectOverridesCount },
- });
+ createComponent({ projectOverridesCount });
- expect(findGlTab().exists()).toBe(true);
- expect(findGlTab().text()).toMatchInterpolatedText(
- `${overridesTabTitle} ${projectOverridesCount}`,
- );
+ const tab = findAllTabs().at(1);
+
+ expect(tab.exists()).toBe(true);
+ expect(tab.text()).toMatchInterpolatedText(`${overridesTabTitle} ${projectOverridesCount}`);
expect(findGlBadge().text()).toBe(projectOverridesCount);
});
@@ -51,8 +66,10 @@ describe('IntegrationTabs', () => {
it('renders "Projects using custom settings" tab without count', () => {
createComponent();
- expect(findGlTab().exists()).toBe(true);
- expect(findGlTab().text()).toMatchInterpolatedText(overridesTabTitle);
+ const tab = findAllTabs().at(1);
+
+ expect(tab.exists()).toBe(true);
+ expect(tab.text()).toMatchInterpolatedText(overridesTabTitle);
expect(findGlBadge().exists()).toBe(false);
});
});
diff --git a/spec/frontend/members/placeholders/components/app_spec.js b/spec/frontend/members/placeholders/components/app_spec.js
index 6adb1f849d2..60a3d7449a6 100644
--- a/spec/frontend/members/placeholders/components/app_spec.js
+++ b/spec/frontend/members/placeholders/components/app_spec.js
@@ -59,7 +59,7 @@ describe('PlaceholdersTabApp', () => {
apolloProvider: mockApollo,
store,
provide: {
- reassignmentCsvDownloadPath: 'foo/bar',
+ reassignmentCsvPath: 'foo/bar',
group: mockGroup,
...provide,
},
diff --git a/spec/frontend/members/placeholders/components/csv_upload_modal_spec.js b/spec/frontend/members/placeholders/components/csv_upload_modal_spec.js
index 26f9c6275ac..e072ec43add 100644
--- a/spec/frontend/members/placeholders/components/csv_upload_modal_spec.js
+++ b/spec/frontend/members/placeholders/components/csv_upload_modal_spec.js
@@ -5,7 +5,7 @@ describe('CsvUploadModal', () => {
let wrapper;
const defaultInjectedAttributes = {
- reassignmentCsvDownloadPath: 'foo/bar',
+ reassignmentCsvPath: 'foo/bar',
};
const findDownloadLink = () => wrapper.findByTestId('csv-download-button');
@@ -29,8 +29,6 @@ describe('CsvUploadModal', () => {
const downloadLink = findDownloadLink();
expect(downloadLink.exists()).toBe(true);
- expect(downloadLink.attributes('href')).toBe(
- defaultInjectedAttributes.reassignmentCsvDownloadPath,
- );
+ expect(downloadLink.attributes('href')).toBe(defaultInjectedAttributes.reassignmentCsvPath);
});
});
diff --git a/spec/frontend/work_items/components/work_item_description_rendered_spec.js b/spec/frontend/work_items/components/work_item_description_rendered_spec.js
index f503f66e21d..23671602986 100644
--- a/spec/frontend/work_items/components/work_item_description_rendered_spec.js
+++ b/spec/frontend/work_items/components/work_item_description_rendered_spec.js
@@ -56,7 +56,6 @@ describe('WorkItemDescriptionRendered', () => {
return true;
},
},
- hasWorkItemsBeta: true,
});
expect(wrapper.find('[data-test-id="description-read-more"]').exists()).toBe(true);
@@ -75,7 +74,6 @@ describe('WorkItemDescriptionRendered', () => {
return false;
},
},
- hasWorkItemsBeta: true,
});
expect(wrapper.find('[data-test-id="description-read-more"]').exists()).toBe(false);
diff --git a/spec/helpers/projects/ml/model_registry_helper_spec.rb b/spec/helpers/projects/ml/model_registry_helper_spec.rb
index 79267a0757b..90a7014e276 100644
--- a/spec/helpers/projects/ml/model_registry_helper_spec.rb
+++ b/spec/helpers/projects/ml/model_registry_helper_spec.rb
@@ -57,7 +57,8 @@ RSpec.describe Projects::Ml::ModelRegistryHelper, feature_category: :mlops do
'mlflowTrackingUrl' => "http://localhost/api/v4/projects/#{project.id}/ml/mlflow/",
'modelId' => model.id,
'modelName' => 'cool_model',
- 'latestVersion' => model.latest_version.version
+ 'latestVersion' => model.latest_version.version,
+ "markdownPreviewPath" => "http://localhost/#{project.full_path}/-/ml/preview_markdown"
})
end
diff --git a/spec/lib/api/entities/virtual_registries/packages/maven/upstream_spec.rb b/spec/lib/api/entities/virtual_registries/packages/maven/upstream_spec.rb
new file mode 100644
index 00000000000..cc99a89389f
--- /dev/null
+++ b/spec/lib/api/entities/virtual_registries/packages/maven/upstream_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::VirtualRegistries::Packages::Maven::Upstream, feature_category: :virtual_registry do
+ let(:upstream) { build_stubbed(:virtual_registries_packages_maven_upstream) }
+
+ subject { described_class.new(upstream).as_json }
+
+ it { is_expected.to include(:id, :group_id, :url, :created_at, :updated_at) }
+end
diff --git a/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb b/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb
index 39ccdeb344d..0fb3e8df182 100644
--- a/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb
@@ -3,8 +3,9 @@
require 'spec_helper'
RSpec.describe BulkImports::Common::Pipelines::MembersPipeline, feature_category: :importers do
+ let_it_be(:default_organization) { create(:organization, :default) }
let_it_be(:user) { create(:user) }
- let_it_be(:bulk_import) { create(:bulk_import, user: user) }
+ let_it_be(:bulk_import) { create(:bulk_import, :with_configuration, user: user) }
let_it_be(:member_user1) { create(:user, email: 'email1@email.com') }
let_it_be(:member_user2) { create(:user, email: 'email2@email.com') }
let_it_be(:member_data) do
@@ -29,7 +30,7 @@ RSpec.describe BulkImports::Common::Pipelines::MembersPipeline, feature_category
allow(pipeline).to receive(:set_source_objects_counter)
end
- def extracted_data(email:, has_next_page: false)
+ def extracted_data(email: '', id: 1, has_next_page: false)
data = {
'created_at' => '2020-01-01T00:00:00Z',
'updated_at' => '2020-01-02T00:00:00Z',
@@ -38,7 +39,10 @@ RSpec.describe BulkImports::Common::Pipelines::MembersPipeline, feature_category
'integer_value' => 30
},
'user' => {
- 'public_email' => email
+ 'user_gid' => "gid://gitlab/User/#{id}",
+ 'public_email' => email,
+ 'name' => 'source_name',
+ 'username' => 'source_username'
}
}
@@ -71,6 +75,56 @@ RSpec.describe BulkImports::Common::Pipelines::MembersPipeline, feature_category
{ user_id: member_user2.id, access_level: 30 }
)
end
+
+ context 'when importer_user_mapping is enabled' do
+ let!(:import_source_user) do
+ create(:import_source_user,
+ namespace: context.portable.root_ancestor,
+ source_hostname: bulk_import.configuration.source_hostname,
+ import_type: Import::SOURCE_DIRECT_TRANSFER,
+ source_user_identifier: '101'
+ )
+ end
+
+ let!(:reassigned_import_source_user) do
+ create(:import_source_user,
+ :completed,
+ namespace: context.portable.root_ancestor,
+ source_hostname: bulk_import.configuration.source_hostname,
+ import_type: Import::SOURCE_DIRECT_TRANSFER,
+ source_user_identifier: '102'
+ )
+ end
+
+ before do
+ allow(context).to receive(:importer_user_mapping_enabled?).and_return(true)
+
+ first_page = extracted_data(id: import_source_user.source_user_identifier, has_next_page: true)
+ second_page = extracted_data(id: reassigned_import_source_user.source_user_identifier, has_next_page: true)
+ last_page = extracted_data(id: 103)
+
+ allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
+ allow(extractor).to receive(:extract).and_return(first_page, second_page, last_page)
+ end
+ end
+
+ it 'finds and creates source users and creates membership for the reassigned users' do
+ expect { pipeline.run }.to change { portable.members.count }.by(1).and(
+ change { Import::SourceUser.count }.by(1)
+ )
+
+ expect(Import::SourceUser.last).to have_attributes(
+ source_user_identifier: '103',
+ namespace: context.portable.root_ancestor,
+ source_hostname: bulk_import.configuration.source_hostname,
+ import_type: Import::SOURCE_DIRECT_TRANSFER.to_s
+ )
+
+ expect(members).to contain_exactly(
+ { user_id: reassigned_import_source_user.reassign_to_user.id, access_level: 30 }
+ )
+ end
+ end
end
describe '#load' do
diff --git a/spec/lib/bulk_imports/common/transformers/member_attributes_transformer_spec.rb b/spec/lib/bulk_imports/common/transformers/member_attributes_transformer_spec.rb
index 4565de32c70..90bebe3d4f0 100644
--- a/spec/lib/bulk_imports/common/transformers/member_attributes_transformer_spec.rb
+++ b/spec/lib/bulk_imports/common/transformers/member_attributes_transformer_spec.rb
@@ -140,6 +140,16 @@ RSpec.describe BulkImports::Common::Transformers::MemberAttributesTransformer, f
end
end
end
+
+ context 'when importer_user_mapping is enabled' do
+ before do
+ allow(context).to receive(:importer_user_mapping_enabled?).and_return(true)
+ end
+
+ it 'does not transform the data' do
+ expect(subject.transform(context, { 'id' => 1 })).to eq({ 'id' => 1 })
+ end
+ end
end
context 'with a project' do
diff --git a/spec/lib/import/bulk_imports/common/transformers/source_user_member_attributes_transformer_spec.rb b/spec/lib/import/bulk_imports/common/transformers/source_user_member_attributes_transformer_spec.rb
new file mode 100644
index 00000000000..b84f2def379
--- /dev/null
+++ b/spec/lib/import/bulk_imports/common/transformers/source_user_member_attributes_transformer_spec.rb
@@ -0,0 +1,159 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Import::BulkImports::Common::Transformers::SourceUserMemberAttributesTransformer,
+ :with_current_organization, feature_category: :importers do
+ let_it_be(:default_organization) { create(:organization, :default) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:bulk_import) { create(:bulk_import, :with_configuration, user: user) }
+
+ shared_examples 'import source user members attribute transformer' do
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
+
+ let_it_be(:import_source_user) do
+ create(:import_source_user,
+ namespace: context.portable.root_ancestor,
+ source_hostname: bulk_import.configuration.source_hostname,
+ import_type: Import::SOURCE_DIRECT_TRANSFER,
+ source_user_identifier: '101'
+ )
+ end
+
+ let_it_be(:reassigned_import_source_user) do
+ create(:import_source_user, :completed,
+ namespace: context.portable.root_ancestor,
+ source_hostname: bulk_import.configuration.source_hostname,
+ import_type: Import::SOURCE_DIRECT_TRANSFER,
+ source_user_identifier: '102'
+ )
+ end
+
+ let(:importer_user_mapping_enabled) { true }
+
+ before do
+ allow(context).to receive(:importer_user_mapping_enabled?).and_return(importer_user_mapping_enabled)
+ end
+
+ context 'when an import source user exists and is mapped to a user' do
+ let(:data) { member_data(source_user_id: reassigned_import_source_user.source_user_identifier) }
+
+ it 'does not create an import source user' do
+ expect { subject.transform(context, data) }.not_to change { Import::SourceUser.count }
+ end
+
+ it 'returns member hash with the reassigned_to_user_id' do
+ expect(subject.transform(context, data)).to eq(
+ access_level: 30,
+ user_id: reassigned_import_source_user.reassign_to_user_id,
+ created_by_id: user.id,
+ created_at: '2020-01-01T00:00:00Z',
+ updated_at: '2020-01-01T00:00:00Z',
+ expires_at: nil
+ )
+ end
+
+ context 'when access level is invalid' do
+ let(:data) do
+ member_data(access_level: 999, source_user_id: reassigned_import_source_user.source_user_identifier)
+ end
+
+ it 'ignores record' do
+ expect(subject.transform(context, data)).to eq(nil)
+ end
+ end
+
+ context 'when importer_user_mapping is disabled' do
+ let(:importer_user_mapping_enabled) { false }
+
+ it 'does not create an import source user' do
+ expect { subject.transform(context, data) }.not_to change { Import::SourceUser.count }
+ end
+
+ it 'does not transform the data' do
+ expect(subject.transform(context, { id: 1 })).to eq({ id: 1 })
+ end
+ end
+ end
+
+ context 'when an import source user does not exist' do
+ let(:data) { member_data(source_user_id: 999) }
+
+ it 'creates an import source user' do
+ expect { subject.transform(context, data) }.to change { Import::SourceUser.count }.by(1)
+
+ expect(Import::SourceUser.last).to have_attributes(
+ source_user_identifier: '999',
+ source_username: 'source_username',
+ source_name: 'source_name',
+ import_type: Import::SOURCE_DIRECT_TRANSFER.to_s
+ )
+ end
+
+ it 'retuns nil' do
+ expect(subject.transform(context, data)).to eq(nil)
+ end
+
+ context 'when importer_user_mapping is disabled' do
+ let(:importer_user_mapping_enabled) { false }
+
+ it 'does not create an import source user' do
+ expect { subject.transform(context, data) }.not_to change { Import::SourceUser.count }
+ end
+
+ it 'does not transform the data' do
+ expect(subject.transform(context, { id: 1 })).to eq({ id: 1 })
+ end
+ end
+ end
+
+ context 'when an import source user exists and is mapped to placeholder user' do
+ let(:data) { member_data(source_user_id: import_source_user.source_user_identifier) }
+
+ it 'does not create an import source user' do
+ expect { subject.transform(context, data) }.not_to change { Import::SourceUser.count }
+ end
+
+ it 'retuns nil' do
+ expect(subject.transform(context, data)).to eq(nil)
+ end
+ end
+
+ context 'when data is nil' do
+ it 'returns nil' do
+ expect(subject.transform(context, nil)).to eq(nil)
+ end
+ end
+ end
+
+ context 'with a project' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:entity) { create(:bulk_import_entity, :project_entity, bulk_import: bulk_import, project: project) }
+
+ include_examples 'import source user members attribute transformer'
+ end
+
+ context 'with a group' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:entity) { create(:bulk_import_entity, bulk_import: bulk_import, group: group) }
+
+ include_examples 'import source user members attribute transformer'
+ end
+
+ def member_data(source_user_id:, access_level: 30)
+ {
+ 'created_at' => '2020-01-01T00:00:00Z',
+ 'updated_at' => '2020-01-01T00:00:00Z',
+ 'expires_at' => nil,
+ 'access_level' => {
+ 'integer_value' => access_level
+ },
+ 'user' => {
+ 'user_gid' => "gid://gitlab/User/#{source_user_id}",
+ 'username' => 'source_username',
+ 'name' => 'source_name'
+ }
+ }
+ end
+end
diff --git a/spec/migrations/20240807143914_remove_crm_contacts_widget_from_work_item_types_spec.rb b/spec/migrations/20240807143914_remove_crm_contacts_widget_from_work_item_types_spec.rb
new file mode 100644
index 00000000000..b9f7af71453
--- /dev/null
+++ b/spec/migrations/20240807143914_remove_crm_contacts_widget_from_work_item_types_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe RemoveCrmContactsWidgetFromWorkItemTypes, :migration, feature_category: :team_planning do
+ let(:widget_name) { described_class::WIDGET_NAME }
+ let(:work_item_types) { described_class::WORK_ITEM_TYPES }
+
+ let(:migration) { described_class.new }
+ let(:work_item_definitions) { table(:work_item_widget_definitions) }
+ let(:work_item_type_count) { work_item_types.size }
+ let(:find_method_name) { :find_by_name_and_namespace_id }
+
+ describe '#up', :migration_with_transaction do
+ it "removes definitions for widget" do
+ migration.down
+
+ expect { migrate! }.to change { work_item_definitions.count }.by(-work_item_type_count)
+ end
+ end
+
+ describe '#down', :migration_with_transaction do
+ before do
+ migrate!
+ end
+
+ it "creates widget definition in all types" do
+ work_item_definitions.where(name: widget_name).delete_all
+
+ expect { migration.down }.to change { work_item_definitions.count }.by(work_item_type_count)
+ expect(work_item_definitions.all.pluck(:name)).to include(widget_name)
+ end
+
+ it 'logs a warning if the type is missing' do
+ type_name = work_item_types.first
+
+ allow(described_class::WorkItemType).to receive(find_method_name).and_call_original
+ allow(described_class::WorkItemType).to receive(find_method_name)
+ .with(type_name, nil).and_return(nil)
+
+ expect(Gitlab::AppLogger).to receive(:warn).with("type #{type_name} is missing, not adding widget")
+ migration.down
+ end
+
+ context 'when the widget already exists' do
+ let(:work_item_types_table) { table(:work_item_types) }
+
+ before do
+ work_item_types.each do |type_name|
+ type = work_item_types_table.find_by_name(type_name)
+ work_item_definitions.create!(
+ name: widget_name,
+ work_item_type_id: type.id,
+ widget_type: described_class::WIDGET_ENUM_VALUE
+ )
+ end
+ end
+
+ it 'upserts the widget definitions and raises no error' do
+ expect { migration.down }.to not_change {
+ work_item_definitions.where(name: widget_name).count
+ }
+ end
+ end
+ end
+end
diff --git a/spec/models/import/source_user_spec.rb b/spec/models/import/source_user_spec.rb
index 4476afb069e..c402f609dc2 100644
--- a/spec/models/import/source_user_spec.rb
+++ b/spec/models/import/source_user_spec.rb
@@ -253,6 +253,28 @@ RSpec.describe Import::SourceUser, type: :model, feature_category: :importers do
end
end
+ describe '#mapped_user_id' do
+ let_it_be(:source_user) { build(:import_source_user, :with_reassign_to_user) }
+
+ subject(:mapped_user_id) { source_user.mapped_user_id }
+
+ before do
+ allow(source_user).to receive(:accepted_status?).and_return(accepted)
+ end
+
+ context 'when accepted' do
+ let(:accepted) { true }
+
+ it { is_expected.to eq(source_user.reassign_to_user_id) }
+ end
+
+ context 'when not accepted' do
+ let(:accepted) { false }
+
+ it { is_expected.to eq(source_user.placeholder_user_id) }
+ end
+ end
+
describe '#reassignable_status?' do
reassignable_statuses = [:pending_reassignment, :rejected]
all_states = described_class.state_machines[:status].states
diff --git a/spec/models/virtual_registries/packages/maven/upstream_spec.rb b/spec/models/virtual_registries/packages/maven/upstream_spec.rb
index fb220446d69..f70fa4108c2 100644
--- a/spec/models/virtual_registries/packages/maven/upstream_spec.rb
+++ b/spec/models/virtual_registries/packages/maven/upstream_spec.rb
@@ -174,4 +174,10 @@ RSpec.describe VirtualRegistries::Packages::Maven::Upstream, type: :model, featu
it { is_expected.to eq(expected_headers) }
end
end
+
+ describe '#as_json' do
+ subject { upstream.as_json }
+
+ it { is_expected.not_to include('username', 'password') }
+ end
end
diff --git a/spec/requests/api/graphql/mutations/ml/models/edit_spec.rb b/spec/requests/api/graphql/mutations/ml/models/edit_spec.rb
new file mode 100644
index 00000000000..02935a23571
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/ml/models/edit_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Editing of a machine learning model', feature_category: :mlops do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:current_user) { project.owner }
+ let_it_be(:guest) { create(:user) }
+
+ let(:name) { 'some_name' }
+ let(:description) { 'A description' }
+
+ let(:input) { { project_path: project.full_path, name: name, description: description } }
+
+ let(:model) { create(:ml_models, project: project, name: name, description: description) }
+
+ let(:new_description) { 'A new description' }
+ let(:edit_input) { { project_path: project.full_path, name: name, description: new_description, model_id: model.id } }
+
+ let(:mutation) { graphql_mutation(:ml_model_edit, edit_input, nil, ['version']) }
+ let(:mutation_response) { graphql_mutation_response(:ml_model_edit) }
+
+ context 'when user is not allowed write changes' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?)
+ .with(current_user, :write_model_registry, project)
+ .and_return(false)
+ end
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when the user is not part of the project' do
+ it 'returns an error' do
+ post_graphql_mutation(mutation, current_user: guest)
+ expect { mutation }.to not_change { ::Ml::Model.count }
+ expect(mutation_response).to be_nil
+ end
+ end
+
+ context 'when the user is authenticated' do
+ context 'when the model does not exist' do
+ it 'returns an error' do
+ edit_input[:model_id] = 0
+
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['errors']).not_to be_empty
+ expect(mutation_response['errors']).to match_array(['Model not found'])
+ end
+ end
+
+ context 'when the model exists' do
+ before do
+ model
+ end
+
+ it 'updates the model description' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['errors']).to be_empty
+
+ model.reload
+ expect(model.name).to eq(name)
+ expect(model.description).to eq(new_description)
+ end
+ end
+
+ context 'when the model is not part of the project' do
+ let(:model) { create(:ml_models, name: name, description: description) }
+
+ it 'returns an error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['errors']).not_to be_empty
+ expect(mutation_response['errors']).to match_array(['Model not found'])
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/virtual_registries/packages/maven_spec.rb b/spec/requests/api/virtual_registries/packages/maven_spec.rb
index 877c9253201..885fab5c836 100644
--- a/spec/requests/api/virtual_registries/packages/maven_spec.rb
+++ b/spec/requests/api/virtual_registries/packages/maven_spec.rb
@@ -7,11 +7,480 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu
include WorkhorseHelpers
include HttpBasicAuthHelpers
- let_it_be(:registry) { create(:virtual_registries_packages_maven_registry, :with_upstream) }
- let_it_be(:project) { create(:project, namespace: registry.group) }
+ let_it_be_with_reload(:registry) { create(:virtual_registries_packages_maven_registry) }
+ let_it_be(:upstream) { create(:virtual_registries_packages_maven_upstream, registry: registry) }
+ let_it_be(:group) { registry.group }
+ let_it_be(:project) { create(:project, namespace: group) }
let_it_be(:user) { project.creator }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:job) { create(:ci_build, :running, user: user, project: project) }
+ let_it_be(:deploy_token) do
+ create(:deploy_token, :group, groups: [group], read_virtual_registry: true)
+ end
- let(:upstream) { registry.upstream }
+ let_it_be(:headers) { user_basic_auth_header(user, personal_access_token) }
+
+ shared_examples 'disabled feature flag' do
+ before do
+ stub_feature_flags(virtual_registry_maven: false)
+ end
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ shared_examples 'disabled dependency proxy' do
+ before do
+ stub_config(dependency_proxy: { enabled: false })
+ end
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ shared_examples 'not authenticated user' do
+ let(:headers) { {} }
+
+ it_behaves_like 'returning response status', :unauthorized
+ end
+
+ before do
+ stub_config(dependency_proxy: { enabled: true })
+ end
+
+ describe 'GET /api/v4/virtual_registries/packages/maven/registries/:id/upstreams' do
+ let(:registry_id) { registry.id }
+ let(:url) { "/virtual_registries/packages/maven/registries/#{registry_id}/upstreams" }
+
+ subject(:api_request) { get api(url), headers: headers }
+
+ shared_examples 'successful response' do
+ it 'returns a successful response' do
+ api_request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(Gitlab::Json.parse(response.body)).to contain_exactly(registry.upstream.as_json)
+ end
+ end
+
+ it { is_expected.to have_request_urgency(:low) }
+
+ it_behaves_like 'disabled feature flag'
+ it_behaves_like 'disabled dependency proxy'
+ it_behaves_like 'not authenticated user'
+
+ context 'with valid registry' do
+ it_behaves_like 'successful response'
+ end
+
+ context 'with invalid registry' do
+ where(:registry_id, :status) do
+ non_existing_record_id | :not_found
+ 'foo' | :bad_request
+ '' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'with a non member user' do
+ let_it_be(:user) { create(:user) }
+
+ where(:group_access_level, :status) do
+ 'PUBLIC' | :ok
+ 'INTERNAL' | :ok
+ 'PRIVATE' | :forbidden
+ end
+
+ with_them do
+ before do
+ group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_access_level, false))
+ end
+
+ if params[:status] == :ok
+ it_behaves_like 'successful response'
+ else
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+
+ context 'for authentication' do
+ where(:token, :sent_as, :status) do
+ :personal_access_token | :header | :ok
+ :personal_access_token | :basic_auth | :ok
+ :deploy_token | :header | :ok
+ :deploy_token | :basic_auth | :ok
+ :job_token | :header | :ok
+ :job_token | :basic_auth | :ok
+ end
+
+ with_them do
+ let(:headers) do
+ case sent_as
+ when :header
+ token_header(token)
+ when :basic_auth
+ token_basic_auth(token)
+ end
+ end
+
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+
+ describe 'POST /api/v4/virtual_registries/packages/maven/registries/:id/upstreams' do
+ let(:registry_id) { registry.id }
+ let(:url) { "/virtual_registries/packages/maven/registries/#{registry_id}/upstreams" }
+ let(:params) { { url: 'http://example.com' } }
+
+ subject(:api_request) { post api(url), headers: headers, params: params }
+
+ shared_examples 'successful response' do
+ it 'returns a successful response' do
+ expect { api_request }.to change { ::VirtualRegistries::Packages::Maven::Upstream.count }.by(1)
+ .and change { ::VirtualRegistries::Packages::Maven::RegistryUpstream.count }.by(1)
+ end
+ end
+
+ it { is_expected.to have_request_urgency(:low) }
+
+ it_behaves_like 'disabled feature flag'
+ it_behaves_like 'disabled dependency proxy'
+ it_behaves_like 'not authenticated user'
+
+ context 'with valid params' do
+ where(:user_role, :status) do
+ :owner | :created
+ :maintainer | :created
+ :developer | :forbidden
+ :reporter | :forbidden
+ :guest | :forbidden
+ end
+
+ with_them do
+ before do
+ registry.upstream&.destroy!
+ group.send(:"add_#{user_role}", user)
+ end
+
+ if params[:status] == :created
+ it_behaves_like 'successful response'
+ else
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+
+ context 'with invalid registry' do
+ where(:registry_id, :status) do
+ non_existing_record_id | :not_found
+ 'foo' | :bad_request
+ '' | :not_found
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'for params' do
+ where(:params, :status) do
+ { url: 'http://example.com', username: 'test', password: 'test' } | :created
+ { url: '', username: 'test', password: 'test' } | :bad_request
+ { url: 'http://example.com', username: 'test' } | :bad_request
+ {} | :bad_request
+ end
+
+ before do
+ registry.upstream&.destroy!
+ end
+
+ before_all do
+ group.add_maintainer(user)
+ end
+
+ with_them do
+ if params[:status] == :created
+ it_behaves_like 'successful response'
+ else
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+
+ context 'with existing upstream' do
+ before_all do
+ group.add_maintainer(user)
+ create(:virtual_registries_packages_maven_upstream, registry: registry)
+ end
+
+ it_behaves_like 'returning response status', :conflict
+ end
+
+ context 'for authentication' do
+ before_all do
+ group.add_maintainer(user)
+ end
+
+ before do
+ registry.upstream&.destroy!
+ end
+
+ where(:token, :sent_as, :status) do
+ :personal_access_token | :header | :created
+ :personal_access_token | :basic_auth | :created
+ :deploy_token | :header | :forbidden
+ :deploy_token | :basic_auth | :forbidden
+ :job_token | :header | :created
+ :job_token | :basic_auth | :created
+ end
+
+ with_them do
+ let(:headers) do
+ case sent_as
+ when :header
+ token_header(token)
+ when :basic_auth
+ token_basic_auth(token)
+ end
+ end
+
+ if params[:status] == :created
+ it_behaves_like 'successful response'
+ else
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+ end
+
+ describe 'GET /api/v4/virtual_registries/packages/maven/registries/:id/upstreams/:upstream_id' do
+ let(:url) { "/virtual_registries/packages/maven/registries/#{registry.id}/upstreams/#{upstream.id}" }
+
+ subject(:api_request) { get api(url), headers: headers }
+
+ shared_examples 'successful response' do
+ it 'returns a successful response' do
+ api_request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(Gitlab::Json.parse(response.body)).to eq(registry.upstream.as_json)
+ end
+ end
+
+ it { is_expected.to have_request_urgency(:low) }
+
+ it_behaves_like 'disabled feature flag'
+ it_behaves_like 'disabled dependency proxy'
+ it_behaves_like 'not authenticated user'
+
+ context 'with valid params' do
+ it_behaves_like 'successful response'
+ end
+
+ context 'with a non member user' do
+ let_it_be(:user) { build_stubbed(:user) }
+
+ where(:group_access_level, :status) do
+ 'PUBLIC' | :ok
+ 'INTERNAL' | :ok
+ 'PRIVATE' | :forbidden
+ end
+
+ with_them do
+ before do
+ group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_access_level, false))
+ end
+
+ if params[:status] == :ok
+ it_behaves_like 'successful response'
+ else
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+
+ context 'for authentication' do
+ where(:token, :sent_as, :status) do
+ :personal_access_token | :header | :ok
+ :personal_access_token | :basic_auth | :ok
+ :deploy_token | :header | :ok
+ :deploy_token | :basic_auth | :ok
+ :job_token | :header | :ok
+ :job_token | :basic_auth | :ok
+ end
+
+ with_them do
+ let(:headers) do
+ case sent_as
+ when :header
+ token_header(token)
+ when :basic_auth
+ token_basic_auth(token)
+ end
+ end
+
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+
+ describe 'PATCH /api/v4/virtual_registries/packages/maven/registries/:id/upstreams/:upstream_id' do
+ let(:url) { "/virtual_registries/packages/maven/registries/#{registry.id}/upstreams/#{upstream.id}" }
+
+ subject(:api_request) { patch api(url), params: params, headers: headers }
+
+ context 'with valid params' do
+ let(:params) { { url: 'http://example.com', username: 'test', password: 'test' } }
+
+ it { is_expected.to have_request_urgency(:low) }
+
+ it_behaves_like 'disabled feature flag'
+ it_behaves_like 'disabled dependency proxy'
+ it_behaves_like 'not authenticated user'
+
+ where(:user_role, :status) do
+ :owner | :ok
+ :maintainer | :ok
+ :developer | :forbidden
+ :reporter | :forbidden
+ :guest | :forbidden
+ end
+
+ with_them do
+ before do
+ group.send(:"add_#{user_role}", user)
+ end
+
+ it_behaves_like 'returning response status', params[:status]
+ end
+
+ context 'for authentication' do
+ before_all do
+ group.add_maintainer(user)
+ end
+
+ where(:token, :sent_as, :status) do
+ :personal_access_token | :header | :ok
+ :personal_access_token | :basic_auth | :ok
+ :deploy_token | :header | :forbidden
+ :deploy_token | :basic_auth | :forbidden
+ :job_token | :header | :ok
+ :job_token | :basic_auth | :ok
+ end
+
+ with_them do
+ let(:headers) do
+ case sent_as
+ when :header
+ token_header(token)
+ when :basic_auth
+ token_basic_auth(token)
+ end
+ end
+
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+
+ context 'for params' do
+ before_all do
+ group.add_maintainer(user)
+ end
+
+ where(:param_url, :username, :password, :status) do
+ nil | 'test' | 'test' | :ok
+ 'http://example.com' | nil | 'test' | :ok
+ 'http://example.com' | 'test' | nil | :ok
+ '' | 'test' | 'test' | :bad_request
+ 'http://example.com' | '' | 'test' | :bad_request
+ 'http://example.com' | 'test' | '' | :bad_request
+ nil | nil | nil | :bad_request
+ end
+
+ with_them do
+ let(:params) { { url: param_url, username: username, password: password }.compact }
+
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+
+ describe 'DELETE /api/v4/virtual_registries/packages/maven/registries/:id/upstreams/:upstream_id' do
+ let(:url) { "/virtual_registries/packages/maven/registries/#{registry.id}/upstreams/#{upstream.id}" }
+
+ subject(:api_request) { delete api(url), headers: headers }
+
+ shared_examples 'successful response' do
+ it 'returns a successful response' do
+ expect { api_request }.to change { ::VirtualRegistries::Packages::Maven::Upstream.count }.by(-1)
+ .and change { ::VirtualRegistries::Packages::Maven::RegistryUpstream.count }.by(-1)
+ end
+ end
+
+ it { is_expected.to have_request_urgency(:low) }
+
+ it_behaves_like 'disabled feature flag'
+ it_behaves_like 'disabled dependency proxy'
+ it_behaves_like 'not authenticated user'
+
+ context 'for different user roles' do
+ where(:user_role, :status) do
+ :owner | :no_content
+ :maintainer | :no_content
+ :developer | :forbidden
+ :reporter | :forbidden
+ :guest | :forbidden
+ end
+
+ with_them do
+ before do
+ group.send(:"add_#{user_role}", user)
+ end
+
+ if params[:status] == :no_content
+ it_behaves_like 'successful response'
+ else
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+
+ context 'for authentication' do
+ before_all do
+ group.add_maintainer(user)
+ end
+
+ where(:token, :sent_as, :status) do
+ :personal_access_token | :header | :no_content
+ :personal_access_token | :basic_auth | :no_content
+ :deploy_token | :header | :forbidden
+ :deploy_token | :basic_auth | :forbidden
+ :job_token | :header | :no_content
+ :job_token | :basic_auth | :no_content
+ end
+
+ with_them do
+ let(:headers) do
+ case sent_as
+ when :header
+ token_header(token)
+ when :basic_auth
+ token_basic_auth(token)
+ end
+ end
+
+ if params[:status] == :no_content
+ it_behaves_like 'successful response'
+ else
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+ end
describe 'GET /api/v4/virtual_registries/packages/maven/:id/*path' do
let(:path) { 'com/test/package/1.2.3/package-1.2.3.pom' }
@@ -32,7 +501,6 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu
.to receive(:new)
.with(registry: registry, current_user: user, params: { path: path })
.and_return(service_double)
- stub_config(dependency_proxy: { enabled: true }) # not enabled by default
end
subject(:request) do
@@ -152,13 +620,7 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu
end
end
- context 'with feature flag virtual_registry_maven disabled' do
- before do
- stub_feature_flags(virtual_registry_maven: false)
- end
-
- it_behaves_like 'returning response status', :not_found
- end
+ it_behaves_like 'disabled feature flag'
context 'with a web browser' do
described_class::MAJOR_BROWSERS.each do |browser|
@@ -179,19 +641,30 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, feature_category: :virtu
end
end
- context 'with the dependency proxy disabled' do
- before do
- stub_config(dependency_proxy: { enabled: false })
- end
+ it_behaves_like 'disabled dependency proxy'
+ it_behaves_like 'not authenticated user'
+ end
+ end
- it_behaves_like 'returning response status', :not_found
- end
+ def token_header(token)
+ case token
+ when :personal_access_token
+ { 'PRIVATE-TOKEN' => personal_access_token.token }
+ when :deploy_token
+ { 'Deploy-Token' => deploy_token.token }
+ when :job_token
+ { 'Job-Token' => job.token }
+ end
+ end
- context 'as anonymous' do
- let(:headers) { {} }
-
- it_behaves_like 'returning response status', :unauthorized
- end
+ def token_basic_auth(token)
+ case token
+ when :personal_access_token
+ user_basic_auth_header(user, personal_access_token)
+ when :deploy_token
+ deploy_token_basic_auth_header(deploy_token)
+ when :job_token
+ job_basic_auth_header(job)
end
end
end
diff --git a/spec/services/ml/find_model_service_spec.rb b/spec/services/ml/find_model_service_spec.rb
index 027298d979a..6fb3aa024cf 100644
--- a/spec/services/ml/find_model_service_spec.rb
+++ b/spec/services/ml/find_model_service_spec.rb
@@ -5,24 +5,54 @@ require 'spec_helper'
RSpec.describe ::Ml::FindModelService, feature_category: :mlops do
let_it_be(:user) { create(:user) }
let_it_be(:existing_model) { create(:ml_models) }
- let(:finder) { described_class.new(project, name) }
+ let(:finder) { described_class.new(project, name, model_id) }
+ let(:model_id) { nil }
describe '#execute' do
- context 'when model name does not exist in the project' do
- let(:name) { 'new_model' }
+ context 'when model_id is provided' do
let(:project) { existing_model.project }
+ let(:name) { nil }
+ let(:model_id) { existing_model.id }
- it 'reutrns nil' do
- expect(finder.execute).to be nil
+ it 'returns the model with the given model_id' do
+ expect(finder.execute).to eq(existing_model)
+ end
+
+ context 'when model_id does not exist' do
+ let(:model_id) { non_existing_record_id }
+
+ it 'returns nil' do
+ expect(finder.execute).to be_nil
+ end
end
end
- context 'when model with name exists' do
- let(:name) { existing_model.name }
+ context 'when neither model_id nor name is provided' do
let(:project) { existing_model.project }
+ let(:name) { nil }
- it 'returns the existing model' do
- expect(finder.execute).to eq(existing_model)
+ it 'returns nil' do
+ expect(finder.execute).to be_nil
+ end
+ end
+
+ context 'when model_id is not provided' do
+ context 'when model name does not exist in the project' do
+ let(:name) { 'new_model' }
+ let(:project) { existing_model.project }
+
+ it 'returns nil' do
+ expect(finder.execute).to be_nil
+ end
+ end
+
+ context 'when model with name exists' do
+ let(:name) { existing_model.name }
+ let(:project) { existing_model.project }
+
+ it 'returns the existing model' do
+ expect(finder.execute).to eq(existing_model)
+ end
end
end
end
diff --git a/spec/services/ml/update_model_service_spec.rb b/spec/services/ml/update_model_service_spec.rb
index 35df62559e6..f43d020f9fd 100644
--- a/spec/services/ml/update_model_service_spec.rb
+++ b/spec/services/ml/update_model_service_spec.rb
@@ -4,23 +4,27 @@ require 'spec_helper'
RSpec.describe ::Ml::UpdateModelService, feature_category: :mlops do
let_it_be(:model) { create(:ml_models) }
- let_it_be(:description) { 'updated model description' }
- let(:service) { described_class.new(model, description) }
+ let_it_be(:new_description) { 'updated model description' }
+ let(:service) { described_class.new(model, new_description) }
describe '#execute' do
+ subject(:service_result) { service.execute }
+
context 'when supplied with a non-model object' do
let(:model) { nil }
- it 'returns nil' do
- expect { service.execute }.to raise_error(NoMethodError)
+ it 'returns an error' do
+ expect(service_result).to be_error
end
end
context 'with an existing model' do
+ it 'description is initially blank' do
+ expect(model.description).to eq(nil)
+ end
+
it 'updates the description' do
- updated = service.execute
- expect(updated.class).to be(Ml::Model)
- expect(updated.description).to eq(description)
+ expect(service_result.payload.description).to eq(new_description)
end
end
end
diff --git a/spec/support/helpers/http_basic_auth_helpers.rb b/spec/support/helpers/http_basic_auth_helpers.rb
index bc34e073f9f..530007a9a7b 100644
--- a/spec/support/helpers/http_basic_auth_helpers.rb
+++ b/spec/support/helpers/http_basic_auth_helpers.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
module HttpBasicAuthHelpers
- def user_basic_auth_header(user)
- access_token = create(:personal_access_token, user: user)
+ def user_basic_auth_header(user, access_token = nil)
+ access_token ||= create(:personal_access_token, user: user)
basic_auth_header(user.username, access_token.token)
end
@@ -11,6 +11,10 @@ module HttpBasicAuthHelpers
basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token)
end
+ def deploy_token_basic_auth_header(deploy_token)
+ basic_auth_header(deploy_token.username, deploy_token.token)
+ end
+
def client_basic_auth_header(client)
basic_auth_header(client.uid, client.secret)
end
diff --git a/spec/views/projects/issues/_related_branches.html.haml_spec.rb b/spec/views/projects/issues/_related_branches.html.haml_spec.rb
index d37c8c762a3..c0acd717bbb 100644
--- a/spec/views/projects/issues/_related_branches.html.haml_spec.rb
+++ b/spec/views/projects/issues/_related_branches.html.haml_spec.rb
@@ -26,6 +26,6 @@ RSpec.describe 'projects/issues/_related_branches' do
expect(rendered).to have_link(href: 'link-to-feature')
expect(rendered).to have_link(href: 'link-to-other')
expect(rendered).to have_css('[data-testid="ci-icon"]')
- expect(rendered).to have_css('.related-branch-info')
+ expect(rendered).to have_css('.ref-name')
end
end
diff --git a/yarn.lock b/yarn.lock
index 2fad0c8925f..e873ba62d11 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2619,181 +2619,181 @@
dom-accessibility-api "^0.5.1"
pretty-format "^26.4.2"
-"@tiptap/core@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.5.9.tgz#1deb0b7c748e24ec32613263e0af8d55a3b3c2ca"
- integrity sha512-PPUR+0tbr+wX2G8RG4FEps4qhbnAPEeXK1FUtirLXSRh8vm+TDgafu3sms7wBc4fAyw9zTO/KNNZ90GBe04guA==
+"@tiptap/core@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.6.0.tgz#c222f53043abfa69d8d41a081bb3dfc1181fc841"
+ integrity sha512-MG2OXwpMVaiacGuipqZ3VXi36gMvtV3i3ncrtXufsMducWvniENMi8fEonj/Q7nkeVi59OcoW8vD6kqdKkh2Gw==
-"@tiptap/extension-blockquote@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.5.9.tgz#d873a8496fcf572c69aaac2a7a341e035fdbae22"
- integrity sha512-LhGyigmd/v1OjYPeoVK8UvFHbH6ffh175ZuNvseZY4PsBd7kZhrSUiuMG8xYdNX8FxamsxAzr2YpsYnOzu3W7A==
+"@tiptap/extension-blockquote@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.6.0.tgz#3f0a80ebd82e1f5d174c24f9794e22ac3184c072"
+ integrity sha512-/rYTYnn7fk/LBixLv7R1fEee3JDrDo3VHEJ+gBGuyLXcNaYtzWuRfqdeIraNSOuhzHffWQ5dg1oxpVFrVrd6AQ==
-"@tiptap/extension-bold@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.5.9.tgz#00c9b7b5211048b1e1c5d67e355935b9c92e3532"
- integrity sha512-XUJdzFb31t0+bwiRquJf0btBpqOB3axQNHTKM9XADuL4S+Z6OBPj0I5rYINeElw/Q7muvdWrHWHh/ovNJA1/5A==
+"@tiptap/extension-bold@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.6.0.tgz#eea678778fce751d6f771b6a18c15513f1bbd8ff"
+ integrity sha512-supo1j/lZFXkQ7aQxYTduncSF4aKiDomceJbqQN2HnsRmf1b1SimsGugjhcqT0g10bYb0zuscNPnk15xG8gVTA==
-"@tiptap/extension-bubble-menu@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.5.9.tgz#d600bbcaa1d98a99f32b3b8b8c3d35752161200c"
- integrity sha512-NddZ8Qn5dgPPa1W4yk0jdhF4tDBh0FwzBpbnDu2Xz/0TUHrA36ugB2CvR5xS1we4zUKckgpVqOqgdelrmqqFVg==
+"@tiptap/extension-bubble-menu@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.6.0.tgz#91fb19abd94a4dcbd8816c89285d8b3a0dd6c098"
+ integrity sha512-d8LiaWVRCa3jc26OOCGTGLtJhbTlylokAJvKK1U0IGoX/jjzwWIypg6FZawYB2Fj0JANdTD100e7xDfLTOLLKg==
dependencies:
tippy.js "^6.3.7"
-"@tiptap/extension-bullet-list@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.5.9.tgz#2852aba9a1dacbf2c673cda6a4994b1f3c33cd5c"
- integrity sha512-hJTv1x4omFgaID4LMRT5tOZb/VKmi8Kc6jsf4JNq4Grxd2sANmr9qpmKtBZvviK+XD5PpTXHvL+1c8C1SQtuHQ==
+"@tiptap/extension-bullet-list@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.6.0.tgz#2a0458701c46295ee2e600807312ac0f76c9ecdb"
+ integrity sha512-zf46le3It6OO/wrLk/FTckLRh+EPEBCLKiff4n0N/YwIpSGj0VNEjzPjN5X8KSSiDfP50hWsLVFU0h8A2c8keQ==
-"@tiptap/extension-code-block-lowlight@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.5.9.tgz#ccd6569422d98b11813df3e0dbd09b1dcf957def"
- integrity sha512-taIXxXQ/Lka9CegHFHQS+nx6cX9i9Ws63ZFMPbrXLMSJRhXk8+m4UAoGZQJH9CGGb5/Rv0p3Z8I59AGiyUHLEw==
+"@tiptap/extension-code-block-lowlight@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.6.0.tgz#680ce93869639fb06faccb689622149ff58d3830"
+ integrity sha512-d1JFRmR75HjHeARP4B+ay4wUdeSBDe1Q5+hGLXMvNIrB1pGsuOUERrfL4QPxC51vi2EUbtxsUJh+h2V+ecGd+A==
-"@tiptap/extension-code-block@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.5.9.tgz#8cd99515b286fc62ad1215a411aea5da9a7d9701"
- integrity sha512-+MUwp0VFFv2aFiZ/qN6q10vfIc6VhLoFFpfuETX10eIRks0Xuj2nGiqCDj7ca0/M44bRg2VvW8+tg/ZEHFNl9g==
+"@tiptap/extension-code-block@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.6.0.tgz#62a7327d0d96adfd91ca10d921abc2d16eede8ba"
+ integrity sha512-sDQhuxetwgCAqbD5hAjz6bqpvnPbS8cuv0kWmkHd29filzTn3CiwIRkXIz715POadN/jkZv7CDw5WsJcHiUjng==
-"@tiptap/extension-code@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.5.9.tgz#93c4433eca8b2aa239ea7f408b90f152b7fc4603"
- integrity sha512-Q1PL3DUXiEe5eYUwOug1haRjSaB0doAKwx7KFVI+kSGbDwCV6BdkKAeYf3us/O2pMP9D0im8RWX4dbSnatgwBA==
+"@tiptap/extension-code@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.6.0.tgz#6fac1eafcfbbb7ab6c8d2ce88864305a1cd10a8f"
+ integrity sha512-xs7VhgnpIncVORP/a5K8Ad7ASOQ0P/JaF2nKnK4Sy6mFU2f8PgwOXbgbAaLtUfv2e5nLrqS3hGegePQ3y3R8+Q==
-"@tiptap/extension-document@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.5.9.tgz#13a22b2d3bdc1463844872b1f1c926633df431a8"
- integrity sha512-VdNZYDyCzC3W430UdeRXR9IZzPeODSbi5Xz/JEdV93THVp8AC9CrZR7/qjqdBTgbTB54VP8Yr6bKfCoIAF0BeQ==
+"@tiptap/extension-document@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.6.0.tgz#5c21fcb40dcbabdfedc54bd17f964ec2a28164c8"
+ integrity sha512-aKyKCaErlZSOav2mttktvbyYhpgNfQcEvrupkDuRYRsdy1CH5h66Of1Vc2F9z/NgZvkyJLWWv2T7fwyAYnMnYw==
-"@tiptap/extension-dropcursor@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.5.9.tgz#648f683f929056a0526620f530f73e6b052c1481"
- integrity sha512-nEOb37UryG6bsU9JAs/HojE6Jg43LupNTAMISbnuB1CPAeAqNsFMwORd9eEPkyEwnQT7MkhsMOSJM44GoPGIFA==
+"@tiptap/extension-dropcursor@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.6.0.tgz#0f2ed1ecb6eade94a629689ff4fd20300c5ac86f"
+ integrity sha512-l0u+T5EDICm7KKC1HBCKYyPKfoSINsG01QXAYBcogbFBsJRQJk0m9e0Bsf3kczbndV8+Sn/SOb+S1wbBoXcq7Q==
-"@tiptap/extension-floating-menu@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.5.9.tgz#b970905f3c1af49a916dcbd477a4302086187974"
- integrity sha512-MWJIQQT6e5MgqHny8neeH2Dx926nVPF7sv4p84nX4E0dnkRbEYUP8mCsWYhSUvxxIif6e+yY+4654f2Q9qTx1w==
+"@tiptap/extension-floating-menu@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.6.0.tgz#6dd656ce00e80d5312dacc7fe472d3233ccddad7"
+ integrity sha512-eFmDaJJgGC+PBdRwfWSTzNfJR5Z0udPjHUf4pJosz2PcIg6wzeAxJyELzprk8qWNKbrxDm0CeJ0nz4uXxECdVw==
dependencies:
tippy.js "^6.3.7"
-"@tiptap/extension-gapcursor@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.5.9.tgz#68b9e227cd7876aac353a8ac029995b4c092a763"
- integrity sha512-yW7V2ebezsa7mWEDWCg4A1ZGsmSV5bEHKse9wzHCDkb7TutSVhLZxGo72U6hNN9PnAksv+FJQk03NuZNYvNyRQ==
+"@tiptap/extension-gapcursor@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.6.0.tgz#a38d426f6f7691e3cb8bb038542557733bf8ec8b"
+ integrity sha512-mfkoMQuqLlnjamFJobWgXQb3g8S9P0Y8XuuyxK7MLTUdDgQdmw9/c2mF0LiaoJ825xjRRHELNuHZIPnyRWoP9Q==
-"@tiptap/extension-hard-break@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.5.9.tgz#4f38f06dbeb5fb3e58ff7fc0c48b9db9c4ee4ecd"
- integrity sha512-8hQ63SgZRG4BqHOeSfeaowG2eMr2beced018pOGbpHbE3XSYoISkMVuFz4Z8UEVR3W9dTbKo4wxNufSTducocQ==
+"@tiptap/extension-hard-break@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.6.0.tgz#6a475ca5422a53c2739c0b54eaf488098972e6cc"
+ integrity sha512-RX5Xmj2svS7T3QkDs0X8aTekkcDxpnJ3hqHarzR1gK0GdVsrpowVKYhuEAc1zAAUirVa/IBimKXAIkXjDvuvNA==
-"@tiptap/extension-heading@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.5.9.tgz#b9ec3b3b48dea939606d06eff56c4bdc7bed0662"
- integrity sha512-HHowAlGUbFn1qvmY02ydM7qiPPMTGhAJn2A46enDRjNHW5UoqeMfkMpTEYaioOexyguRFSfDT3gpK68IHkQORQ==
+"@tiptap/extension-heading@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.6.0.tgz#347e99128de6f368b6942d07f8b1ba99fdb0da17"
+ integrity sha512-dq3o9DoiqqzITKIeFrJSRuExrukrP1fNEwPEsrGx8vcwz3c0HIvQ9h/0jKzmjVD9UT+qhM/6DOiYlFnqDlTzqg==
-"@tiptap/extension-highlight@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.5.9.tgz#290426538abcbb2299809d3e1274ba5af1ba9f68"
- integrity sha512-tRaSIIbCI7aBlvlmgUgBI5lVBqnMy49lc++UVAx1Pjey1j2KW031vUyvZfEwf6wk8Y7W3kVSkN0mW9IYCcOAOQ==
+"@tiptap/extension-highlight@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.6.0.tgz#082f01e3e66d069f9c49e25a9d9e9c410ff9c52e"
+ integrity sha512-k25Krl4KwqY3kaldFm3rp9evm5tJZyJmNQeHVfcjn20TqWP+g1OTAFYvai/Gwy5UtPpk03i6SyzvVNl6hwLxrQ==
-"@tiptap/extension-history@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.5.9.tgz#f48f64ff95407f0ce27bcdd020762e49d0dd60d1"
- integrity sha512-hGPtJgoZSwnVVqi/xipC2ET/9X2G2UI/Y+M3IYV1ZlM0tCYsv4spNi3uXlZqnXRwYcBXLk5u6e/dmsy5QFbL8g==
+"@tiptap/extension-history@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.6.0.tgz#a52ec849834ec86035805fcbead9d13f2b4edd02"
+ integrity sha512-uFQU5Eo5pKboJmI8paqZLe+c3eV9RQ0RLvQ8PsJsKRTJnlq69l6phl6ATvNJC9GmVD2OwxHoroByPB5jS0o+HA==
-"@tiptap/extension-horizontal-rule@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.5.9.tgz#9f91a17b80700670e53e241fcee40365c57aa994"
- integrity sha512-/ES5NdxCndBmZAgIXSpCJH8YzENcpxR0S8w34coSWyv+iW0Sq7rW/mksQw8ZIVsj8a7ntpoY5OoRFpSlqcvyGw==
+"@tiptap/extension-horizontal-rule@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.6.0.tgz#00039d78f8e44844de7234279ab76bd85af79c6d"
+ integrity sha512-CJ2ma8Wxww+E0SdSp8t/V2Gvv7q99fPplSSEWSgDWXnMDfcfV3vg637kvnLZpGX+qiRmgYAvjRsyNRQxTvJGgQ==
-"@tiptap/extension-image@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.5.9.tgz#2f2be205d889f6688ef85c76071d2199baab03e4"
- integrity sha512-v4WZISCvbriac6HE6v7kYYY7KX+v9rJaIZC3gbYGtqnBWfaAwZiVVu8Z03xSrqYhoc+KHuI+oQ4VubcvZ/i7OQ==
+"@tiptap/extension-image@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.6.0.tgz#0515bd949e58efe499546ee9d5965fd0ae4520cd"
+ integrity sha512-j50e6P+L344iDxaPVV9svERBJv8MF+N3bKYJWPIu5GhHpoT1yO2L46rnXB8AHz2k5NHs4ZOj9SnSivMKDf/o7w==
-"@tiptap/extension-italic@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.5.9.tgz#8ea0e19e650f0f1d6fc30425ec28291511143dda"
- integrity sha512-Bw+P139L4cy+B56zpUiRjP8BZSaAUl3JFMnr/FO+FG55QhCxFMXIc6XrC3vslNy5ef3B3zv4gCttS3ee8ByMiw==
+"@tiptap/extension-italic@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.6.0.tgz#00768b7c8916cb0098c2b85be6647e5a4289d8f5"
+ integrity sha512-ZILwDaTbFv5GVuxAga72GDmx8VHYqFOK8rkg/GCtW6OfAQVrlC17pzo2uRphzHpYTmOvNW5e63PC0fRbA1qXsg==
-"@tiptap/extension-link@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.5.9.tgz#6cb323d36b82700963ad2b9d189a7d07c81c7d6e"
- integrity sha512-7v9yRsX7NuiY8DPslIsPIlFqcD8aGBMLqfEGXltJDvuG6kykdr+khEZeWcJ8ihHIL4yWR3/MAgeT2W72Z/nxiQ==
+"@tiptap/extension-link@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.6.0.tgz#2a3f65742fcb2776973541e9f2cea3169ce4a204"
+ integrity sha512-16ckFDnwqt0tYtQRgnVhooAUTLSk7liaqcGgc3NJ35gqWZho8hSnaTOl+72dlyJqUjqwt8D24NHFJsSqB9PSxA==
dependencies:
linkifyjs "^4.1.0"
-"@tiptap/extension-list-item@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.5.9.tgz#0805b7216371b8b54649abe5ab29bd2c2155f05f"
- integrity sha512-d9Eo+vBz74SMxP0r25aqiErV256C+lGz+VWMjOoqJa6xWLM1keYy12JtGQWJi8UDVZrDskJKCHq81A0uLt27WA==
+"@tiptap/extension-list-item@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.6.0.tgz#5b960a8c6bceea1fe5f7c730cb3547b920fe0706"
+ integrity sha512-vgrLi3lYe1YHBPCjEFLBfUyeS2DueU/w/L8uNuM0SP38k0wuGZRbYr2mc5oZmVRrCsBdoY9abvjkJHLDVvzwTA==
-"@tiptap/extension-ordered-list@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.5.9.tgz#44aab6ec3e19429a8e3b73e42c04156f2b0bc730"
- integrity sha512-9MsWpvVvzILuEOd/GdroF7RI7uDuE1M6at9rzsaVGvCPVHZBvu1XR3MSVK5OdiJbbJuPGttlzEFLaN/rQdCGFg==
+"@tiptap/extension-ordered-list@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.6.0.tgz#ca163a9237dad834b258bf2e159cb78a794f333a"
+ integrity sha512-eRYux5eRQFCUApOc0L4+R8uAa2tdiCCYqboe/Hadm35u5Z9VsO9V828BWeVVqtkUwSYJY3dz3tXplBkZ+ot+7A==
-"@tiptap/extension-paragraph@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.5.9.tgz#05210b6e7a9940b1acc09fdd4ec769fc6406da2b"
- integrity sha512-HDXGiHTJ/V85dbDMjcFj4XfqyTQZqry6V21ucMzgBZYX60X3gIn7VpQTQnnRjvULSgtfOASSJP6BELc5TyiK0w==
+"@tiptap/extension-paragraph@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.6.0.tgz#721921477e3785c0a0abf132ea109e67bcea9fae"
+ integrity sha512-ztmgfCSaCGFCR4VudA91WJDEQrpXH2FirQrJSaoOOwk3OfrNSSvcbqAhTmxJHl8e7aFwuR5eH8wbIxKw9In3Kg==
-"@tiptap/extension-strike@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.5.9.tgz#f2d54161d24ee37dc8a41b5077c553048ed69f99"
- integrity sha512-QezkOZpczpl09S8lp5JL7sRkwREoPY16Y/lTvBcFKm3TZbVzYZZ/KwS0zpwK9HXTfXr8os4L9AGjQf0tHonX+w==
+"@tiptap/extension-strike@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.6.0.tgz#1bff3ab3b91f7cdbb39bd7a860ad30e60e1750bd"
+ integrity sha512-+gt750H4ioTpwIQXYV5K4BScvAMBuWHvW/f4197LYE/fy6uNqUf62XRz6Ew12z13rEbWGzMHRdpYIguTHcqvzA==
-"@tiptap/extension-subscript@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-subscript/-/extension-subscript-2.5.9.tgz#b6201139faf9cf4b8be013a21161806af660c666"
- integrity sha512-SY1VCf/zlsBLowZaayXGl7dkIGPMNieCO0P1luFBjsiEXCRff0WYVpxi24wzgMeWE6q28SXmd3eD5BsGBudx9g==
+"@tiptap/extension-subscript@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-subscript/-/extension-subscript-2.6.0.tgz#124ea99a960c9af399d65388d2b6b81ab5478d13"
+ integrity sha512-FSkUVNg2kIzJAP4sgoEulLk/7T1qhaDAFCMB3i6/ppftlpEtr+HaWUx3F3qaBCeHmRpMGOF/nje72Y9+SrKZMg==
-"@tiptap/extension-superscript@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-superscript/-/extension-superscript-2.5.9.tgz#da9549e767d7686c2ca716d5eeb46d26814950f7"
- integrity sha512-ggpVx/oJk6tBDq7mNxrJNXhitHVqkJVbeOKaac5FACCoA3ZzHgZ75JooxZqn5wRoCaqShmbljLb8ZqefdX/PRg==
+"@tiptap/extension-superscript@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-superscript/-/extension-superscript-2.6.0.tgz#15f74fbc522c7cbc69f54c4f4cc1549e07acc219"
+ integrity sha512-zs3C1xt0qXAfE20HbgKq49P7t2q+3w9YzEUXy3ky/d2pNCHrrZU1nddpn8+cPf3KnG7kLarYb6cv/1JZmDsrEQ==
-"@tiptap/extension-table-cell@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.5.9.tgz#dffcd23cb11d3ca1239c7290297c9caf0d3b8434"
- integrity sha512-83zg+d8iY7FLZDC2qJVvHFEIwqB9e/zGPyfRMglYH7YxHeJSycG2K969DQhwkSq+z0PhHEO2XHDcD9aFnXRQNQ==
+"@tiptap/extension-table-cell@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.6.0.tgz#8b6c01ddafed7a914f06349cd72cc2b316e9673f"
+ integrity sha512-+zOR3c9fzSr9TRFl7p2KxY1WOLit/+0kgIpbsidCAvVH1rigV7x/l/Lo3zOVP+lCzUoAVvaJX293PY9y61lDQQ==
-"@tiptap/extension-table-header@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.5.9.tgz#405da76163ec4899b1d6b81f2fbbdb7c0f71bf59"
- integrity sha512-+FKfxpEO8RnsHUhcWFeSpsI3ZRaDtgcX2c4kBBXZGJBMWHxw71VK9gkRM+JtxCl70hNyZR13qpOw1RmByf2kdw==
+"@tiptap/extension-table-header@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.6.0.tgz#790d51971b19c1ab5a1042a1a6d389fd62dbcf43"
+ integrity sha512-mkh9RPA2iueMDsQjbs7YwExxS02EY+fYy/mA2bIqhu/4eZXLgHuZADmbW6VfPjkEZ0nRoToQCyDvP8NHS5WpWA==
-"@tiptap/extension-table-row@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.5.9.tgz#62e5e329b38daf6142b39d7c17de045dfc6918be"
- integrity sha512-VPmkzraT9m7g2/88qsTF9EQdFvkcwhvOD+WWZTflN92LMsRHddJt3peJ/fpuf0QKnwwn5qIB3fXWja4VcZe3wQ==
+"@tiptap/extension-table-row@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.6.0.tgz#f1629be948c58d8f9fc0bee01b52ca05b92f7331"
+ integrity sha512-2rJCjK6cHPPA81YtrBew+hfSAUclvaz1nSV2WBvoWiY8UQHdaRhESVmmpkEhUSuX7JaevN93mZhpQ4Tj6Q7B6w==
-"@tiptap/extension-table@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.5.9.tgz#a7b80a668a683c3abe59725812091123b2c12b88"
- integrity sha512-kLZdYBO0Ug4sNjzyDfa3W29qL4HdRK/IaMxcmcEbyKSt42qiMJlIelbGzVENzxe9AbcNTeiWje70Nhk4dbb8Ag==
+"@tiptap/extension-table@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.6.0.tgz#9866f5dfff14f9038d57e3f5e5615bd2bcc43bc0"
+ integrity sha512-dTOvgX7a9EnzqfqUZMlk6MUolb0nFN3JQG8foppxVPnO04H5Apwyhq39n87JHWQorFR5+dbbPg0FQk+3qNzP5g==
-"@tiptap/extension-task-item@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.5.9.tgz#623590d549aa0e21ccd1d34396ebbeeea45886f5"
- integrity sha512-g4HK3r3yNE0RcXQOkJHs94Ws/fhhTqa1L5iAy4gwYKNNFFnIQl8BpE6nn9d5h33kWDN9jjY+PZmq+0PvxCLODQ==
+"@tiptap/extension-task-item@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.6.0.tgz#93a064cd7d3b83a33d885092a964acf6f22ca6dd"
+ integrity sha512-IWJgtyKPU1N+Vb3YpwU2k7m8APl/YVnH2OMFV6PohV9eAA0BoH4bGC8fLaPO4Kq7Jl7er4darHDI8touTSQyVg==
-"@tiptap/extension-task-list@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.5.9.tgz#9934b3ae84dbfc27804d0d54e64aeb9e8fcaf418"
- integrity sha512-OylVo5cAh0117PzhyM8MGaUIrCskGiF7v7x6/zAHMFIqVdcbKsq+hMueVPnABfOyLcIH5Zojo3NzNOJeKeblCg==
+"@tiptap/extension-task-list@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.6.0.tgz#7f720a398cd8ad0163841481dfcff676d00b3350"
+ integrity sha512-bH0yQN4U3RhW/jlnXExfsZYPFHUjU1YxhbM2UvKY6w1IrqEDxIN+zjvaQ/anu9+4Z4zuzL6qNaJw7O5CdzqkqA==
-"@tiptap/extension-text@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.5.9.tgz#a5bef0b9c5324511dbc2804a3a5ac8b9b5d5dc4c"
- integrity sha512-W0pfiQUPsMkwaV5Y/wKW4cFsyXAIkyOFt7uN5u6LrZ/iW9KZ/IsDODPJDikWp0aeQnXzT9NNQULTpCjbHzzS6g==
+"@tiptap/extension-text@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.6.0.tgz#395cd44456059c11fb5f3e2215093a5c32ba59a3"
+ integrity sha512-Bg/FXGzj6WGvqgCVvFKzTWOpgg8Nl4X2a/bUT7nEmBafpUNAiN6enomMWnniylGTFEqZrywr5KRcvbgl8ubeDQ==
-"@tiptap/pm@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.5.9.tgz#f97889210374993a1ce78e9ecb23461d0e4644bf"
- integrity sha512-YSUaEQVtvZnGzGjif2Tl2o9utE+6tR2Djhz0EqFUcAUEVhOMk7UYUO+r/aPfcCRraIoKKuDQzyCpjKmJicjCUA==
+"@tiptap/pm@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.6.0.tgz#b2ef8aba2f5a04ac3f5051e3d891ffe26f37967b"
+ integrity sha512-Oj912Jfowuq/K+oEkqx+pADjuVwBCXroC2hdrRMo4w18bb2gI2Nhse6St1KQ9kWWGF36O81RpDbpLe7L1YQhzQ==
dependencies:
prosemirror-changeset "^2.2.1"
prosemirror-collab "^1.3.1"
@@ -2814,18 +2814,18 @@
prosemirror-transform "^1.9.0"
prosemirror-view "^1.33.9"
-"@tiptap/suggestion@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.5.9.tgz#af54bd41c2146454619fcadf15129a499bb6d05d"
- integrity sha512-s7UU0j2IRreVXrMMxsFvsNjJnZeTS1SAwsjLkN2YX+/ZQss92s0BLP3HsxEr2oFHlFye8E0qR9xjWZ4vSc9asw==
+"@tiptap/suggestion@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.6.0.tgz#457ae6a7f50ec47cf8fa7c2a3d1c88a8dfb0ed6d"
+ integrity sha512-NAsXzAbHa3epnvnK4mqzRYhvjBYndu/lnC4SVOFvgBbCqQ/V698DwYDZ8iKLBeEzckzd7G6RON223AyvCF+9lw==
-"@tiptap/vue-2@^2.5.9":
- version "2.5.9"
- resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.5.9.tgz#a8bd3854ffff25ce09b1843d4f0b663d643b84b6"
- integrity sha512-gqwwUfRy1WT9uHaZEpTWjEavLrsDfpjwzSQ/w1NjX/broyDMKc9WR/G2qlBCruYLBuypJSX9Z3nCMABD7Zhyaw==
+"@tiptap/vue-2@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.6.0.tgz#df7113e33b773a5faaf99f056430ff2ad257c831"
+ integrity sha512-o6yw06XACSzFFDBdKTl8o0B/R2HVcfRKyTPQTakKcCwlHMKpsgdWNNf5F4BV8n9nSry4g7jSIhrD0WS5Eu7klg==
dependencies:
- "@tiptap/extension-bubble-menu" "^2.5.9"
- "@tiptap/extension-floating-menu" "^2.5.9"
+ "@tiptap/extension-bubble-menu" "^2.6.0"
+ "@tiptap/extension-floating-menu" "^2.6.0"
vue-ts-types "^1.6.0"
"@tootallnate/once@2":