diff --git a/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs b/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs index 34e17aae..c791d10b 100644 --- a/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs +++ b/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs @@ -103,27 +103,30 @@ public async Task Handle(MigratePageTypesCommand request, Cancell bool hasFieldsAlready = true; foreach (var cmml in classMapping.Mappings.Where(m => m.IsTemplate).ToLookup(x => x.SourceFieldName)) { - var cmm = cmml.FirstOrDefault() ?? throw new InvalidOperationException(); - if (fieldInReusableSchemas.ContainsKey(cmm.TargetFieldName)) + foreach (var cmm in cmml) { - // part of reusable schema - continue; - } + if (fieldInReusableSchemas.ContainsKey(cmm.TargetFieldName)) + { + // part of reusable schema + continue; + } - var sc = cmsClasses.FirstOrDefault(sc => sc.ClassName.Equals(cmm.SourceClassName, StringComparison.InvariantCultureIgnoreCase)) - ?? throw new NullReferenceException($"The source class '{cmm.SourceClassName}' does not exist - wrong mapping {classMapping}"); + var sc = cmsClasses.FirstOrDefault(sc => sc.ClassName.Equals(cmm.SourceClassName, StringComparison.InvariantCultureIgnoreCase)) + ?? throw new NullReferenceException($"The source class '{cmm.SourceClassName}' does not exist - wrong mapping {classMapping}"); - var fi = new FormInfo(sc.ClassFormDefinition); - if (nfi.GetFormField(cmm.TargetFieldName) is { }) - { - } - else - { - var src = fi.GetFormField(cmm.SourceFieldName); - src.Name = cmm.TargetFieldName; - nfi.AddFormItem(src); - hasFieldsAlready = false; + var fi = new FormInfo(sc.ClassFormDefinition); + if (nfi.GetFormField(cmm.TargetFieldName) is { }) + { + } + else + { + var src = fi.GetFormField(cmm.SourceFieldName); + src.Name = cmm.TargetFieldName; + nfi.AddFormItem(src); + hasFieldsAlready = false; + } } + //var cmm = cmml.FirstOrDefault() ?? throw new InvalidOperationException(); } if (!hasFieldsAlready) @@ -242,26 +245,29 @@ public async Task Handle(MigratePageTypesCommand request, Cancell var kxoDataClass = kxpClassFacade.GetClass(ksClass.ClassGUID); protocol.FetchedTarget(kxoDataClass); - if (SaveUsingKxoApi(ksClass, kxoDataClass) is { } targetClassId) + if (SaveUsingKxoApi(ksClass, kxoDataClass) is { } targetClass) { - foreach (var cmsClassSite in modelFacade.SelectWhere("ClassID = @classId", new SqlParameter("classId", ksClass.ClassID))) + if (targetClass.ClassContentTypeType is ClassContentTypeType.WEBSITE) { - if (modelFacade.SelectById(cmsClassSite.SiteID) is { SiteGUID: var siteGuid }) + foreach (var cmsClassSite in modelFacade.SelectWhere("ClassID = @classId", new SqlParameter("classId", ksClass.ClassID))) { - if (ChannelInfoProvider.ProviderObject.Get(siteGuid) is { ChannelID: var channelId }) + if (modelFacade.SelectById(cmsClassSite.SiteID) is { SiteGUID: var siteGuid }) { - var info = new ContentTypeChannelInfo { ContentTypeChannelChannelID = channelId, ContentTypeChannelContentTypeID = targetClassId }; - ContentTypeChannelInfoProvider.ProviderObject.Set(info); + if (ChannelInfoProvider.ProviderObject.Get(siteGuid) is { ChannelID: var channelId }) + { + var info = new ContentTypeChannelInfo { ContentTypeChannelChannelID = channelId, ContentTypeChannelContentTypeID = targetClass.ClassID }; + ContentTypeChannelInfoProvider.ProviderObject.Set(info); + } + else + { + logger.LogWarning("Channel for site with SiteGUID '{SiteGuid}' not found", siteGuid); + } } else { - logger.LogWarning("Channel for site with SiteGUID '{SiteGuid}' not found", siteGuid); + logger.LogWarning("Source site with SiteID '{SiteId}' not found", cmsClassSite.SiteID); } } - else - { - logger.LogWarning("Source site with SiteID '{SiteId}' not found", cmsClassSite.SiteID); - } } } } @@ -337,7 +343,7 @@ private async Task MigratePageTemplateConfigurations() } } - private int? SaveUsingKxoApi(ICmsClass ksClass, DataClassInfo kxoDataClass) + private DataClassInfo? SaveUsingKxoApi(ICmsClass ksClass, DataClassInfo kxoDataClass) { var mapped = dataClassMapper.Map(ksClass, kxoDataClass); protocol.MappedTarget(mapped); @@ -365,7 +371,7 @@ private async Task MigratePageTemplateConfigurations() dataClassInfo.ClassID ); - return dataClassInfo.ClassID; + return dataClassInfo; } } catch (Exception ex) diff --git a/KVA/Migration.Tool.Source/Handlers/MigratePagesCommandHandler.cs b/KVA/Migration.Tool.Source/Handlers/MigratePagesCommandHandler.cs index c694cc28..aa5608ba 100644 --- a/KVA/Migration.Tool.Source/Handlers/MigratePagesCommandHandler.cs +++ b/KVA/Migration.Tool.Source/Handlers/MigratePagesCommandHandler.cs @@ -215,6 +215,11 @@ public async Task Handle(MigratePagesCommand request, Cancellatio { switch (await importer.ImportAsync(umtModel)) { + case { Success: false } result: + { + logger.LogError("Failed to import: {Exception}, {ValidationResults}", result.Exception, JsonConvert.SerializeObject(result.ModelValidationResults)); + break; + } case { Success: true, Imported: ContentItemCommonDataInfo ccid }: { commonDataInfos.Add(ccid); diff --git a/KVA/Migration.Tool.Source/Mappers/CmsClassMapper.cs b/KVA/Migration.Tool.Source/Mappers/CmsClassMapper.cs index 0413f96e..33d9ac95 100644 --- a/KVA/Migration.Tool.Source/Mappers/CmsClassMapper.cs +++ b/KVA/Migration.Tool.Source/Mappers/CmsClassMapper.cs @@ -25,7 +25,8 @@ public class CmsClassMapper( PrimaryKeyMappingContext primaryKeyMappingContext, IProtocol protocol, FieldMigrationService fieldMigrationService, - ModelFacade modelFacade + ModelFacade modelFacade, + ToolConfiguration configuration ) : EntityMapperBase(logger, primaryKeyMappingContext, protocol) @@ -188,7 +189,9 @@ protected override DataClassInfo MapInternal(ICmsClass source, DataClassInfo tar }: { target.ClassType = ClassType.CONTENT_TYPE; - target.ClassContentTypeType = ClassContentTypeType.WEBSITE; + target.ClassContentTypeType = configuration.ClassNamesConvertToContentHub.Contains(target.ClassName) + ? ClassContentTypeType.REUSABLE + : ClassContentTypeType.WEBSITE; target = PatchDataClassInfo(target, out string? oldPrimaryKeyName, out string? documentNameField); break; diff --git a/KVA/Migration.Tool.Source/Mappers/ContentItemMapper.cs b/KVA/Migration.Tool.Source/Mappers/ContentItemMapper.cs index c1ddb754..231259b6 100644 --- a/KVA/Migration.Tool.Source/Mappers/ContentItemMapper.cs +++ b/KVA/Migration.Tool.Source/Mappers/ContentItemMapper.cs @@ -76,7 +76,7 @@ protected override IEnumerable MapInternal(CmsTreeMapperSource source bool migratedAsContentFolder = sourceNodeClass.ClassName.Equals("cms.folder", StringComparison.InvariantCultureIgnoreCase) && !configuration.UseDeprecatedFolderPageType.GetValueOrDefault(false); var contentItemGuid = spoiledGuidContext.EnsureNodeGuid(cmsTree.NodeGUID, cmsTree.NodeSiteID, cmsTree.NodeID); - bool isMappedTypeReusable = targetClassInfo?.ClassContentTypeType is ClassContentTypeType.REUSABLE; + bool isMappedTypeReusable = (targetClassInfo?.ClassContentTypeType is ClassContentTypeType.REUSABLE) || configuration.ClassNamesConvertToContentHub.Contains(sourceNodeClass.ClassName); yield return new ContentItemModel { ContentItemGUID = contentItemGuid, diff --git a/Migration.Tool.Common/ConfigurationNames.cs b/Migration.Tool.Common/ConfigurationNames.cs index b1d10b0e..11e67997 100644 --- a/Migration.Tool.Common/ConfigurationNames.cs +++ b/Migration.Tool.Common/ConfigurationNames.cs @@ -22,6 +22,7 @@ public class ConfigurationNames public const string UseDeprecatedFolderPageType = "UseDeprecatedFolderPageType"; public const string ExcludeCodeNames = "ExcludeCodeNames"; + public const string ConvertClassesToContentHub = "ConvertClassesToContentHub"; public const string ExplicitPrimaryKeyMapping = "ExplicitPrimaryKeyMapping"; public const string SiteName = "SiteName"; diff --git a/Migration.Tool.Common/ToolConfiguration.cs b/Migration.Tool.Common/ToolConfiguration.cs index 98644dfe..15249bb6 100644 --- a/Migration.Tool.Common/ToolConfiguration.cs +++ b/Migration.Tool.Common/ToolConfiguration.cs @@ -49,11 +49,19 @@ public class ToolConfiguration [ConfigurationKeyName(ConfigurationNames.CreateReusableFieldSchemaForClasses)] public string? CreateReusableFieldSchemaForClasses { get; set; } + [ConfigurationKeyName(ConfigurationNames.ConvertClassesToContentHub)] + public string? ConvertClassesToContentHub { get; set; } + public IReadOnlySet ClassNamesCreateReusableSchema => classNamesCreateReusableSchema ??= new HashSet( (CreateReusableFieldSchemaForClasses?.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) ?? []).Select(x => x.Trim()), StringComparer.InvariantCultureIgnoreCase ); + public IReadOnlySet ClassNamesConvertToContentHub => classNamesConvertToContentHub ??= new HashSet( + (ConvertClassesToContentHub?.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) ?? []).Select(x => x.Trim()), + StringComparer.InvariantCultureIgnoreCase + ); + #region Opt-in features [ConfigurationKeyName(ConfigurationNames.OptInFeatures)] @@ -95,6 +103,7 @@ public void SetXbKConnectionStringIfNotEmpty(string? connectionString) #region Path to root directory of target instance + private HashSet? classNamesConvertToContentHub; private HashSet? classNamesCreateReusableSchema; private string? xbKConnectionString; diff --git a/Migration.Tool.Extensions/ClassMappings/ClassMappingSample.cs b/Migration.Tool.Extensions/ClassMappings/ClassMappingSample.cs index bdaa8cea..fa0c0026 100644 --- a/Migration.Tool.Extensions/ClassMappings/ClassMappingSample.cs +++ b/Migration.Tool.Extensions/ClassMappings/ClassMappingSample.cs @@ -34,6 +34,12 @@ public static IServiceCollection AddReusableRemodelingSample(this IServiceCollec .SetFrom(sourceClassName, "CoffeeFarm", true) .WithFieldPatch(f => f.SetPropertyValue(FormFieldPropertyEnum.FieldCaption, "Farm RM")); + // field clone sample + m + .BuildField("FarmRM_Clone") + .SetFrom(sourceClassName, "CoffeeFarm", true) + .WithFieldPatch(f => f.SetPropertyValue(FormFieldPropertyEnum.FieldCaption, "Farm RM Clone")); + m .BuildField("CoffeeCountryRM") .WithFieldPatch(f => f.Caption = "Country RM") diff --git a/Migration.Tool.Extensions/README.md b/Migration.Tool.Extensions/README.md index c6a4b3c9..6b2cf2b7 100644 --- a/Migration.Tool.Extensions/README.md +++ b/Migration.Tool.Extensions/README.md @@ -103,6 +103,10 @@ To create custom widget migration: Samples: - [Sample BannerWidget migration](./CommunityMigrations/SampleWidgetMigration.cs) +### Convert page type to reusable content item (content hub) + +demonstrated in method `AddReusableRemodelingSample`. Please note, that all information unique to page will be lost + ## Custom widget property migrations To create custom widget property migration: