Skip to content

Commit

Permalink
Merge pull request #313 from Kentico/rls/v1.6.0
Browse files Browse the repository at this point in the history
Rls/v1.6.0
  • Loading branch information
tkrch authored Dec 9, 2024
2 parents 0e949b3 + e57fb23 commit ee4a74a
Show file tree
Hide file tree
Showing 63 changed files with 2,134 additions and 1,556 deletions.
11 changes: 7 additions & 4 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
---
name: Bug report
about: Create a report to help us improve

---

### Brief bug description

What went wrong?

## Output logs

Please include the command line output log file and migration protocol generated for your `Migration.Tool.CLI.exe migrate` command.

### Repro steps

1. Go to '...'
Expand All @@ -21,9 +24,9 @@ What is the correct behavior?

### Test environment

- Platform/OS: [e.g. .NET Core 2.1, iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
- Platform/OS: [e.g. .NET Core 2.1, iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]

### Additional context

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

namespace Migration.Tool.Source.Behaviors;

public class XbKApiContextBehavior<TRequest, TResponse>(
ILogger<XbKApiContextBehavior<TRequest, TResponse>> logger,
public class XbyKApiContextBehavior<TRequest, TResponse>(
ILogger<XbyKApiContextBehavior<TRequest, TResponse>> logger,
IMigrationProtocol protocol,
KxpApiInitializer initializer)
: IPipelineBehavior<TRequest, TResponse>
Expand All @@ -28,9 +28,9 @@ public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TRe
{
protocol.Append(HandbookReferences
.MissingRequiredDependency<UserInfo>()
.WithMessage($"Target XbK doesn't contain default administrator account ('{UserInfoProvider.DEFAULT_ADMIN_USERNAME}'). Default administrator account is required for migration.")
.WithMessage($"Target XbyK doesn't contain default administrator account ('{UserInfoProvider.DEFAULT_ADMIN_USERNAME}'). Default administrator account is required for migration.")
);
throw new InvalidOperationException($"Target XbK doesn't contain default administrator account ('{UserInfoProvider.DEFAULT_ADMIN_USERNAME}')");
throw new InvalidOperationException($"Target XbyK doesn't contain default administrator account ('{UserInfoProvider.DEFAULT_ADMIN_USERNAME}')");
}

using (new CMSActionContext(defaultAdmin) { User = defaultAdmin, UseGlobalAdminContext = true })
Expand Down
2 changes: 2 additions & 0 deletions KVA/Migration.Tool.Source/Contexts/SourceObjectContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
namespace Migration.Tool.Source.Contexts;

public record DocumentSourceObjectContext(ICmsTree CmsTree, ICmsClass NodeClass, ICmsSite Site, FormInfo OldFormInfo, FormInfo NewFormInfo, int? DocumentId) : ISourceObjectContext;

public record CustomTableSourceObjectContext : ISourceObjectContext;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Collections;

using System.Diagnostics;
using CMS.ContentEngine;
using CMS.ContentEngine.Internal;
using CMS.DataEngine;
Expand All @@ -12,12 +12,14 @@

using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Logging;

using Migration.Tool.Common;
using Migration.Tool.Common.Abstractions;
using Migration.Tool.Common.Builders;
using Migration.Tool.Common.Helpers;
using Migration.Tool.KXP.Api;
using Migration.Tool.Source.Mappers;
using Migration.Tool.Source.Model;
using Migration.Tool.Source.Providers;
using Migration.Tool.Source.Services;

using Newtonsoft.Json;
Expand All @@ -30,7 +32,9 @@ public class MigrateCategoriesCommandHandler(
IImporter importer,
ReusableSchemaService reusableSchemaService,
IUmtMapper<TagModelSource> tagModelMapper,
SpoiledGuidContext spoiledGuidContext
SpoiledGuidContext spoiledGuidContext,
KxpClassFacade kxpClassFacade,
ClassMappingProvider classMappingProvider
) : IRequestHandler<MigrateCategoriesCommand, CommandResult>
{
public async Task<CommandResult> Handle(MigrateCategoriesCommand request, CancellationToken cancellationToken)
Expand All @@ -48,26 +52,42 @@ public async Task<CommandResult> Handle(MigrateCategoriesCommand request, Cancel
if (result.Imported is TaxonomyInfo taxonomy)
{
string query = """
SELECT C.ClassName, C.ClassGuid, C.ClassID
SELECT C.ClassName, C.ClassGuid, C.ClassID, CC.CategoryID
FROM View_CMS_Tree_Joined [TJ]
JOIN dbo.CMS_DocumentCategory [CDC] on [TJ].DocumentID = [CDC].DocumentID
JOIN CMS_Class [C] ON TJ.NodeClassID = [C].ClassID
JOIN dbo.CMS_Category CC on CDC.CategoryID = CC.CategoryID AND CC.CategoryUserID IS NULL
GROUP BY C.ClassName, C.ClassGuid, C.ClassID
GROUP BY C.ClassName, C.ClassGuid, C.ClassID, CC.CategoryID
""";

var classesWithCategories = modelFacade.Select(query, (reader, version) => new { ClassName = reader.Unbox<string>("ClassName"), ClassGuid = reader.Unbox<Guid>("ClassGuid"), ClassID = reader.Unbox<int>("ClassID") });
var classesWithCategories = modelFacade.Select(query, (reader, version) => new { ClassName = reader.Unbox<string>("ClassName"), ClassGuid = reader.Unbox<Guid>("ClassGuid"), ClassID = reader.Unbox<int>("ClassID"), CategoryID = reader.Unbox<int>("CategoryID") })
.GroupBy(x => x.ClassGuid)
.Select(x => new { ClassGuid = x.Key, x.First().ClassName, x.First().ClassID, Categories = x.Select(row => row.CategoryID) });

// For each source instance class whose documents have some categories assigned, include taxonomy-storage reusable schema in the target class
#region Ensure reusable schema
var skippedClasses = new List<int>();
var schemaGuid = Guid.Empty;
string categoryFieldName = "Category_Legacy";
foreach (var classWithCategoryUsage in classesWithCategories)
{
var targetDataClass = DataClassInfoProvider.ProviderObject.Get(classWithCategoryUsage.ClassGuid);
if (targetDataClass == null)
if (targetDataClass is null)
{
skippedClasses.Add(classWithCategoryUsage.ClassID);
logger.LogWarning("Data class not found by ClassGuid {Guid}", classWithCategoryUsage.ClassGuid);
// No direct-mapped target class found. Try to identify custom-mapped target class
var classMapping = classMappingProvider.GetMapping(classWithCategoryUsage.ClassName);
if (classMapping is not null)
{
if (classWithCategoryUsage.Categories.Any(cat => classMapping.IsCategoryMapped(classWithCategoryUsage.ClassName, cat)))
{
targetDataClass = kxpClassFacade.GetClass(classMapping.TargetClassName);
}
}
}

if (targetDataClass is null)
{
logger.LogWarning($"Class(ClassGuid {{Guid}}) has documents with categories, but no directly-mapped data class nor custom-mapped class that would receive the categories (declared via {nameof(IClassMapping.IsCategoryMapped)}) was found", classWithCategoryUsage.ClassGuid);
continue;
}

Expand All @@ -82,6 +102,8 @@ FROM View_CMS_Tree_Joined [TJ]
DataClassInfoProvider.SetDataClassInfo(targetDataClass);
}
}
#endregion


var categories = modelFacade.Select<ICmsCategory>(
"CategoryEnabled = 1 AND CategoryUserID IS NULL",
Expand All @@ -95,23 +117,24 @@ FROM View_CMS_Tree_Joined [TJ]
categoryId2Guid.Add(cmsCategory.CategoryID, cmsCategory.CategoryGUID);
// CategorySiteID - not migrated, Taxonomies are global!

var mapped = tagModelMapper.Map(new TagModelSource(
var tagUMTModels = tagModelMapper.Map(new TagModelSource(
taxonomy.TaxonomyGUID,
cmsCategory,
categoryId2Guid
));

foreach (var umtModel in mapped)
foreach (var tagUMTModel in tagUMTModels)
{
if (await importer
.ImportAsync(umtModel)
.ImportAsync(tagUMTModel)
.AssertSuccess<TagInfo>(logger) is { Success: true, Info: { } tag })
{
query = """
SELECT TJ.DocumentGUID, TJ.NodeSiteID, TJ.NodeID, TJ.DocumentID, CDC.CategoryID, TJ.DocumentCheckedOutVersionHistoryID, TJ.NodeClassID
SELECT TJ.DocumentGUID, TJ.NodeSiteID, TJ.NodeID, TJ.DocumentID, CDC.CategoryID, TJ.DocumentCheckedOutVersionHistoryID, TJ.NodeClassID, C.ClassName
FROM View_CMS_Tree_Joined [TJ]
JOIN dbo.CMS_DocumentCategory [CDC] on [TJ].DocumentID = [CDC].DocumentID
JOIN dbo.CMS_Category CC on CDC.CategoryID = CC.CategoryID AND CC.CategoryUserID IS NULL
JOIN CMS_Class [C] ON TJ.NodeClassID = [C].ClassID
WHERE CDC.CategoryID = @categoryId
""";

Expand All @@ -120,6 +143,7 @@ FROM View_CMS_Tree_Joined [TJ]
CategoryID = reader.Unbox<int?>("CategoryID"),
DocumentCheckedOutVersionHistoryID = reader.Unbox<int?>("DocumentCheckedOutVersionHistoryID"),
NodeClassID = reader.Unbox<int>("NodeClassID"),
NodeClassName = reader.Unbox<string>("ClassName"),
NodeSiteID = reader.Unbox<int>("NodeSiteID"),
DocumentGUID = spoiledGuidContext.EnsureDocumentGuid(
reader.Unbox<Guid>("DocumentGUID"),
Expand All @@ -137,13 +161,23 @@ FROM View_CMS_Tree_Joined [TJ]
continue;
}

var classMapping = classMappingProvider.GetMapping(dwc.NodeClassName);
if (classMapping is not null)
{
Debug.Assert(dwc.CategoryID.HasValue, "dwc.CategoryID should have value, otherwise the row would not be included in the query due to inner join");
if (!classMapping.IsCategoryMapped(dwc.NodeClassName, dwc.CategoryID.Value))
{
continue;
}
}

var commonData = ContentItemCommonDataInfo.Provider.Get()
.WhereEquals(nameof(ContentItemCommonDataInfo.ContentItemCommonDataGUID), dwc.DocumentGUID)
.FirstOrDefault();

if (commonData is null)
{
logger.LogWarning("ContentItemCommonDataInfo not found by guid {Guid}, taxonomy cannot be migrated", dwc.DocumentGUID);
logger.LogWarning("ContentItemCommonDataInfo not found by Guid {Guid}. Taxonomy cannot be migrated", dwc.DocumentGUID);
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ private async Task MigrateClasses(EntityConfiguration entityConfiguration, Cance
}
}

// special case - member migration (CMS_User splits into CMS_User and CMS_Member in XbK)
// special case - member migration (CMS_User splits into CMS_User and CMS_Member in XbyK)
await MigrateMemberClass(cancellationToken);
}

Expand Down
Loading

0 comments on commit ee4a74a

Please sign in to comment.