Skip to content

Commit

Permalink
Merge pull request #250 from Kentico/feat/193_migration_to_folder
Browse files Browse the repository at this point in the history
193 migration to folder
  • Loading branch information
tkrch authored Sep 23, 2024
2 parents ec8a77f + b5d8e9e commit 86f2c13
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public async Task<CommandResult> Handle(MigratePageTypesCommand request, Cancell
if (entityConfiguration.ExcludeCodeNames.Contains(ksClass.ClassName, StringComparer.InvariantCultureIgnoreCase))
{
protocol.Warning(HandbookReferences.EntityExplicitlyExcludedByCodeName(ksClass.ClassName, "PageType"), ksClass);
logger.LogWarning("CmsClass: {ClassName} was skipped => it is explicitly excluded in configuration", ksClass.ClassName);
logger.LogInformation("CmsClass: {ClassName} was skipped => it is explicitly excluded in configuration", ksClass.ClassName);
continue;
}

Expand All @@ -87,6 +87,17 @@ public async Task<CommandResult> Handle(MigratePageTypesCommand request, Cancell
continue;
}

if (ksClass.ClassName.Equals("cms.folder", StringComparison.InvariantCultureIgnoreCase))
{
if (!toolkitConfiguration.UseDeprecatedFolderPageType.GetValueOrDefault(false))
{
logger.LogInformation("Class {Class} is deprecated, skipping", Printer.GetEntityIdentityPrint(ksClass));
continue;
}

logger.LogWarning("Class {Class} is deprecated, but migration is enabled with configuration flag 'UseDeprecatedFolderPageType'", Printer.GetEntityIdentityPrint(ksClass));
}

var kxoDataClass = kxpClassFacade.GetClass(ksClass.ClassGUID);
protocol.FetchedTarget(kxoDataClass);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ public async Task<CommandResult> Handle(MigratePagesCommand request, Cancellatio
ksSite.SiteGUID,
nodeParentGuid,
cultureCodeToLanguageGuid,
targetClass.ClassFormDefinition,
targetClass?.ClassFormDefinition,
ksNodeClass.ClassFormDefinition,
migratedDocuments,
ksSite
Expand Down
203 changes: 108 additions & 95 deletions KVA/Migration.Toolkit.Source/Mappers/ContentItemMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public record CmsTreeMapperSource(
Guid SiteGuid,
Guid? NodeParentGuid,
Dictionary<string, Guid> CultureToLanguageGuid,
string TargetFormDefinition,
string? TargetFormDefinition,
string SourceFormDefinition,
List<ICmsDocument> MigratedDocuments,
ICmsSite SourceSite
Expand Down Expand Up @@ -69,14 +69,16 @@ protected override IEnumerable<IUmtModel> MapInternal(CmsTreeMapperSource source

var nodeClass = modelFacade.SelectById<ICmsClass>(cmsTree.NodeClassID) ?? throw new InvalidOperationException($"Fatal: node class is missing, class id '{cmsTree.NodeClassID}'");

bool migratedAsContentFolder = nodeClass.ClassName.Equals("cms.folder", StringComparison.InvariantCultureIgnoreCase) && !configuration.UseDeprecatedFolderPageType.GetValueOrDefault(false);

var contentItemGuid = spoiledGuidContext.EnsureNodeGuid(cmsTree.NodeGUID, cmsTree.NodeSiteID, cmsTree.NodeID);
yield return new ContentItemModel
{
ContentItemGUID = contentItemGuid,
ContentItemName = safeNodeName,
ContentItemIsReusable = false, // page is not reusable
ContentItemIsSecured = cmsTree.IsSecuredNode ?? false,
ContentItemDataClassGuid = nodeClass.ClassGUID,
ContentItemDataClassGuid = migratedAsContentFolder ? null : nodeClass.ClassGUID,
ContentItemChannelGuid = siteGuid
};

Expand Down Expand Up @@ -112,7 +114,7 @@ protected override IEnumerable<IUmtModel> MapInternal(CmsTreeMapperSource source
: null;

bool draftMigrated = false;
if (checkoutVersion is { PublishFrom: null } draftVersion)
if (checkoutVersion is { PublishFrom: null } draftVersion && !migratedAsContentFolder)
{
List<IUmtModel>? migratedDraft = null;
try
Expand Down Expand Up @@ -144,63 +146,71 @@ protected override IEnumerable<IUmtModel> MapInternal(CmsTreeMapperSource source
{ DocumentPublishedVersionHistoryID: null, DocumentCheckedOutVersionHistoryID: not null } => VersionStatus.InitialDraft,
_ => draftMigrated ? VersionStatus.Published : VersionStatus.InitialDraft
};
if (migratedAsContentFolder)
{
versionStatus = VersionStatus.Published; // folder is automatically published
}

DateTime? scheduledPublishWhen = null;
DateTime? scheduleUnpublishWhen = null;
string? contentItemCommonDataPageBuilderWidgets = null;
string? contentItemCommonDataPageTemplateConfiguration = null;

if (cmsDocument.DocumentPublishFrom is { } publishFrom)
bool ndp = false;
if (!migratedAsContentFolder)
{
var now = Service.Resolve<IDateTimeNowService>().GetDateTimeNow();
if (publishFrom > now)
{
versionStatus = VersionStatus.Unpublished;
}
else
if (cmsDocument.DocumentPublishFrom is { } publishFrom)
{
scheduledPublishWhen = publishFrom;
var now = Service.Resolve<IDateTimeNowService>().GetDateTimeNow();
if (publishFrom > now)
{
versionStatus = VersionStatus.Unpublished;
}
else
{
scheduledPublishWhen = publishFrom;
}
}
}

if (cmsDocument.DocumentPublishTo is { } publishTo)
{
var now = Service.Resolve<IDateTimeNowService>().GetDateTimeNow();
if (publishTo < now)
{
versionStatus = VersionStatus.Unpublished;
}
else
if (cmsDocument.DocumentPublishTo is { } publishTo)
{
scheduleUnpublishWhen = publishTo;
var now = Service.Resolve<IDateTimeNowService>().GetDateTimeNow();
if (publishTo < now)
{
versionStatus = VersionStatus.Unpublished;
}
else
{
scheduleUnpublishWhen = publishTo;
}
}
}

string? contentItemCommonDataPageBuilderWidgets = null;
string? contentItemCommonDataPageTemplateConfiguration = null;
switch (cmsDocument)
{
case CmsDocumentK11:
{
break;
}
case CmsDocumentK12 doc:
switch (cmsDocument)
{
contentItemCommonDataPageBuilderWidgets = doc.DocumentPageBuilderWidgets;
contentItemCommonDataPageTemplateConfiguration = doc.DocumentPageTemplateConfiguration;
break;
}
case CmsDocumentK13 doc:
{
contentItemCommonDataPageBuilderWidgets = doc.DocumentPageBuilderWidgets;
contentItemCommonDataPageTemplateConfiguration = doc.DocumentPageTemplateConfiguration;
break;
case CmsDocumentK11:
{
break;
}
case CmsDocumentK12 doc:
{
contentItemCommonDataPageBuilderWidgets = doc.DocumentPageBuilderWidgets;
contentItemCommonDataPageTemplateConfiguration = doc.DocumentPageTemplateConfiguration;
break;
}
case CmsDocumentK13 doc:
{
contentItemCommonDataPageBuilderWidgets = doc.DocumentPageBuilderWidgets;
contentItemCommonDataPageTemplateConfiguration = doc.DocumentPageTemplateConfiguration;
break;
}

default:
break;
}

default:
break;
PatchJsonDefinitions(source.CmsTree.NodeSiteID, ref contentItemCommonDataPageTemplateConfiguration, ref contentItemCommonDataPageBuilderWidgets, out ndp);
}

PatchJsonDefinitions(source.CmsTree.NodeSiteID, ref contentItemCommonDataPageTemplateConfiguration, ref contentItemCommonDataPageBuilderWidgets, out bool ndp);

var documentGuid = spoiledGuidContext.EnsureDocumentGuid(
cmsDocument.DocumentGUID ?? throw new InvalidOperationException("DocumentGUID is null"),
cmsTree.NodeSiteID,
Expand Down Expand Up @@ -228,74 +238,77 @@ protected override IEnumerable<IUmtModel> MapInternal(CmsTreeMapperSource source
);
}

var dataModel = new ContentItemDataModel { ContentItemDataGUID = commonDataModel.ContentItemCommonDataGUID, ContentItemDataCommonDataGuid = commonDataModel.ContentItemCommonDataGUID, ContentItemContentTypeName = nodeClass.ClassName };

var fi = new FormInfo(targetFormDefinition);
if (nodeClass.ClassIsCoupledClass)
if (!migratedAsContentFolder)
{
var sfi = new FormInfo(sourceFormDefinition);
string primaryKeyName = "";
foreach (var sourceFieldInfo in sfi.GetFields(true, true))
var dataModel = new ContentItemDataModel { ContentItemDataGUID = commonDataModel.ContentItemCommonDataGUID, ContentItemDataCommonDataGuid = commonDataModel.ContentItemCommonDataGUID, ContentItemContentTypeName = nodeClass.ClassName };

var fi = new FormInfo(targetFormDefinition);
if (nodeClass.ClassIsCoupledClass)
{
if (sourceFieldInfo.PrimaryKey)
var sfi = new FormInfo(sourceFormDefinition);
string primaryKeyName = "";
foreach (var sourceFieldInfo in sfi.GetFields(true, true))
{
primaryKeyName = sourceFieldInfo.Name;
if (sourceFieldInfo.PrimaryKey)
{
primaryKeyName = sourceFieldInfo.Name;
}
}
}

if (string.IsNullOrWhiteSpace(primaryKeyName))
{
throw new Exception("Error, unable to find coupled data primary key");
}

var commonFields = UnpackReusableFieldSchemas(fi.GetFields<FormSchemaInfo>()).ToArray();
var sourceColumns = commonFields
.Select(cf => ReusableSchemaService.RemoveClassPrefix(nodeClass.ClassName, cf.Name))
.Union(fi.GetColumnNames(false))
.Except([CmsClassMapper.GetLegacyDocumentName(fi, nodeClass.ClassName)])
.ToList();
if (string.IsNullOrWhiteSpace(primaryKeyName))
{
throw new Exception("Error, unable to find coupled data primary key");
}

var coupledDataRow = coupledDataService.GetSourceCoupledDataRow(nodeClass.ClassTableName, primaryKeyName, cmsDocument.DocumentForeignKeyValue);
// TODO tomas.krch: 2024-09-05 propagate async to root
MapCoupledDataFieldValues(dataModel.CustomProperties,
columnName => coupledDataRow?[columnName],
columnName => coupledDataRow?.ContainsKey(columnName) ?? false,
cmsTree, cmsDocument.DocumentID, sourceColumns, sfi, fi, false, nodeClass, sourceSite
).GetAwaiter().GetResult();
var commonFields = UnpackReusableFieldSchemas(fi.GetFields<FormSchemaInfo>()).ToArray();
var sourceColumns = commonFields
.Select(cf => ReusableSchemaService.RemoveClassPrefix(nodeClass.ClassName, cf.Name))
.Union(fi.GetColumnNames(false))
.Except([CmsClassMapper.GetLegacyDocumentName(fi, nodeClass.ClassName)])
.ToList();

var coupledDataRow = coupledDataService.GetSourceCoupledDataRow(nodeClass.ClassTableName, primaryKeyName, cmsDocument.DocumentForeignKeyValue);
// TODO tomas.krch: 2024-09-05 propagate async to root
MapCoupledDataFieldValues(dataModel.CustomProperties,
columnName => coupledDataRow?[columnName],
columnName => coupledDataRow?.ContainsKey(columnName) ?? false,
cmsTree, cmsDocument.DocumentID, sourceColumns, sfi, fi, false, nodeClass, sourceSite
).GetAwaiter().GetResult();

foreach (var formFieldInfo in commonFields)
{
string originalFieldName = ReusableSchemaService.RemoveClassPrefix(nodeClass.ClassName, formFieldInfo.Name);
if (dataModel.CustomProperties.TryGetValue(originalFieldName, out object? value))
{
commonDataModel.CustomProperties ??= [];
logger.LogTrace("Reusable schema field '{FieldName}' from schema '{SchemaGuid}' populated", formFieldInfo.Name, formFieldInfo.Properties[ReusableFieldSchemaConstants.SCHEMA_IDENTIFIER_KEY]);
commonDataModel.CustomProperties[formFieldInfo.Name] = value;
dataModel.CustomProperties.Remove(originalFieldName);
}
else
{
logger.LogTrace("Reusable schema field '{FieldName}' from schema '{SchemaGuid}' missing", formFieldInfo.Name, formFieldInfo.Properties[ReusableFieldSchemaConstants.SCHEMA_IDENTIFIER_KEY]);
}
}
}

foreach (var formFieldInfo in commonFields)
if (CmsClassMapper.GetLegacyDocumentName(fi, nodeClass.ClassName) is { } legacyDocumentNameFieldName)
{
string originalFieldName = ReusableSchemaService.RemoveClassPrefix(nodeClass.ClassName, formFieldInfo.Name);
if (dataModel.CustomProperties.TryGetValue(originalFieldName, out object? value))
if (reusableSchemaService.IsConversionToReusableFieldSchemaRequested(nodeClass.ClassName))
{
commonDataModel.CustomProperties ??= [];
logger.LogTrace("Reusable schema field '{FieldName}' from schema '{SchemaGuid}' populated", formFieldInfo.Name, formFieldInfo.Properties[ReusableFieldSchemaConstants.SCHEMA_IDENTIFIER_KEY]);
commonDataModel.CustomProperties[formFieldInfo.Name] = value;
dataModel.CustomProperties.Remove(originalFieldName);
string fieldName = ReusableSchemaService.GetUniqueFieldName(nodeClass.ClassName, legacyDocumentNameFieldName);
commonDataModel.CustomProperties.Add(fieldName, cmsDocument.DocumentName);
}
else
{
logger.LogTrace("Reusable schema field '{FieldName}' from schema '{SchemaGuid}' missing", formFieldInfo.Name, formFieldInfo.Properties[ReusableFieldSchemaConstants.SCHEMA_IDENTIFIER_KEY]);
dataModel.CustomProperties.Add(legacyDocumentNameFieldName, cmsDocument.DocumentName);
}
}
}

if (CmsClassMapper.GetLegacyDocumentName(fi, nodeClass.ClassName) is { } legacyDocumentNameFieldName)
{
if (reusableSchemaService.IsConversionToReusableFieldSchemaRequested(nodeClass.ClassName))
{
string fieldName = ReusableSchemaService.GetUniqueFieldName(nodeClass.ClassName, legacyDocumentNameFieldName);
commonDataModel.CustomProperties.Add(fieldName, cmsDocument.DocumentName);
}
else
{
dataModel.CustomProperties.Add(legacyDocumentNameFieldName, cmsDocument.DocumentName);
}
yield return commonDataModel;
yield return dataModel;
}

yield return commonDataModel;
yield return dataModel;

Guid? documentCreatedByUserGuid = null;
if (modelFacade.TrySelectGuid<ICmsUser>(cmsDocument.DocumentCreatedByUserID, out var createdByUserGuid))
{
Expand Down
5 changes: 3 additions & 2 deletions Migration.Toolkit.CLI/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@
"MigrationProtocolPath": "C:\\Logs\\protocol.txt",
"KxConnectionString": "[TODO]",
"KxCmsDirPath": "[TODO]",
"XbKDirPath": "[TODO]",
"MigrateOnlyMediaFileInfo": true,
"XbKDirPath": "[TODO]",
"XbKApiSettings": {
"ConnectionStrings": {
"CMSConnectionString": "[TODO]"
}
},
"MigrateOnlyMediaFileInfo": false,
"MigrateMediaToMediaLibrary": false,
"UseDeprecatedFolderPageType": false,
"CreateReusableFieldSchemaForClasses": "",
"OptInFeatures": {
"QuerySourceInstanceApi": {
Expand Down
1 change: 1 addition & 0 deletions Migration.Toolkit.Common/ConfigurationNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class ConfigurationNames

public const string MemberIncludeUserSystemFields = "MemberIncludeUserSystemFields";
public const string MigrateMediaToMediaLibrary = "MigrateMediaToMediaLibrary";
public const string UseDeprecatedFolderPageType = "UseDeprecatedFolderPageType";

public const string ExcludeCodeNames = "ExcludeCodeNames";
public const string ExplicitPrimaryKeyMapping = "ExplicitPrimaryKeyMapping";
Expand Down
3 changes: 3 additions & 0 deletions Migration.Toolkit.Common/ToolkitConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public class ToolkitConfiguration
[ConfigurationKeyName(ConfigurationNames.MigrateMediaToMediaLibrary)]
public bool MigrateMediaToMediaLibrary { get; set; }

[ConfigurationKeyName(ConfigurationNames.UseDeprecatedFolderPageType)]
public bool? UseDeprecatedFolderPageType { get; set; }

[ConfigurationKeyName(ConfigurationNames.CreateReusableFieldSchemaForClasses)]
public string? CreateReusableFieldSchemaForClasses { get; set; }

Expand Down

0 comments on commit 86f2c13

Please sign in to comment.