From 9bfb2db562c72821cf74511e3aa073f2338981c0 Mon Sep 17 00:00:00 2001 From: "d.svitak" Date: Mon, 13 Jul 2026 14:14:45 +0200 Subject: [PATCH 1/3] MIG-562 Base Template definition and deployment - Initial support - Document preview refactor --- .../example/common/mapping/AreasExport.groovy | 63 +------------------ .../common/mapping/TablesExport.groovy | 9 +-- .../common/mapping/TablesImport.groovy | 3 +- .../src/test/groovy/AreasExportTest.groovy | 2 +- .../groovy/TablesMappingExportTest.groovy | 8 +-- .../groovy/TablesMappingImportTest.groovy | 7 +-- .../src/test/groovy/Utils.groovy | 4 ++ .../com/quadient/migration/api/Migration.kt | 3 + .../migration/api/dto/migrationmodel/Ref.kt | 19 ++++++ .../migrationmodel/documentcontent/Area.kt | 7 ++- .../migrationmodel/documentcontent/Barcode.kt | 7 +++ .../documentcontent/ColumnLayout.kt | 3 + .../documentcontent/DocumentContent.kt | 4 ++ .../documentcontent/FirstMatch.kt | 5 +- .../documentcontent/Paragraph.kt | 11 ++++ .../documentcontent/RepeatedContent.kt | 11 +++- .../documentcontent/SelectByLanguage.kt | 5 +- .../migrationmodel/documentcontent/Shape.kt | 5 ++ .../migrationmodel/documentcontent/Table.kt | 43 ++++++++++++- .../migrationmodel/MappingEntity.kt | 3 +- .../migration/service/PreviewProvider.kt | 58 +++++++++++++++++ .../quadient/migration/service/TableUtil.kt | 54 ---------------- .../documentcontent/TableTest.kt | 44 +++++++++++++ .../migrationmodel/MappingEntityTest.kt | 9 ++- .../migration/service/TableUtilTest.kt | 25 +------- 25 files changed, 245 insertions(+), 167 deletions(-) create mode 100644 migration-library/src/main/kotlin/com/quadient/migration/service/PreviewProvider.kt create mode 100644 migration-library/src/test/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/TableTest.kt diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasExport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasExport.groovy index d6d91522..8685dc59 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasExport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasExport.groovy @@ -103,66 +103,7 @@ static String buildArea(Migration migration, Number idx, Area area, DocumentObje builder.append(Csv.serialize(area.position.width) + ",") builder.append(Csv.serialize(area.position.height) + ",") - builder.append(Csv.serialize(buildContentPreview(migration, area.content))) - return builder.toString() -} - -static String buildContentPreview(Migration migration, List content) { - def documentObjectIdsToNames = getDocumentObjectIdsToNames(migration, content) - def imageIdsToNames = getImageIdsToNames(migration, content) - - def previewParts = content.collect { - switch (it) { - case DocumentObjectRef: - def name = documentObjectIdsToNames.get(it.id) - return name ? "DocObjRef($name)" : "DocObjRef(${it.id})" - case ImageRef: - def name = imageIdsToNames.get(it.id) - return name ? "ImageRef($name)" : "ImageRef(${it.id})" - default: - return it.toString() - } - } - - if (previewParts.size() > 3) { - previewParts = previewParts.take(3) + ("(+${previewParts.size() - 3} more)" as String) - } - - return previewParts.join(";").replace(",", " ") -} - -static HashMap getDocumentObjectIdsToNames(Migration migration, List content) { - def idsToNames = new HashMap() - - def refIds = content.findAll { it instanceof DocumentObjectRef }.collect { (it as Ref).id } - refIds.each { - def documentObject = migration.documentObjectRepository.find(it) - if (documentObject == null) { - throw new IllegalStateException("Document object '$it' not found.") - } + builder.append(Csv.serialize(migration.previewProvider.buildDocumentContentListPreview(area.content))) - if (documentObject.name != null) { - idsToNames[it] = documentObject.name - } - } - - return idsToNames -} - -static HashMap getImageIdsToNames(Migration migration, List content) { - def idsToNames = new HashMap() - - def refIds = content.findAll { it instanceof ImageRef }.collect { (it as Ref).id } - refIds.each { - def image = migration.imageRepository.find(it) - if (image == null) { - throw new IllegalStateException("Image '$it' not found.") - } - - if (image.name != null) { - idsToNames[it] = image.name - } - } - - return idsToNames + return builder.toString() } diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TablesExport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TablesExport.groovy index 763fa3d4..e56c290b 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TablesExport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TablesExport.groovy @@ -11,12 +11,13 @@ import com.quadient.migration.api.repository.DocumentObjectRepository import com.quadient.migration.example.common.util.Csv import com.quadient.migration.example.common.util.Mapping import com.quadient.migration.service.DocumentTable +import com.quadient.migration.service.PreviewProvider import groovy.transform.Field import java.nio.file.Path import static com.quadient.migration.example.common.util.InitMigration.initMigration -import static com.quadient.migration.service.TableUtilKt.* +import static com.quadient.migration.service.TableUtilKt.collectDocumentTables @Field Migration migration = initMigration(this.binding) @@ -47,13 +48,13 @@ static void run(Migration migration, Path path) { documentObjects.each { docObj -> def documentTables = collectDocumentTables(docObj.content) documentTables.each { docTable -> - writer.writeLine(buildRow(docObj.id, docObj.name, docTable)) + writer.writeLine(buildRow(docObj.id, docObj.name, docTable, migration.previewProvider)) } } } } -static String buildRow(String docObjId, String docObjName, DocumentTable docTable) { +static String buildRow(String docObjId, String docObjName, DocumentTable docTable, PreviewProvider previewProvider) { def contentPath = docTable.contentPath def table = docTable.table @@ -61,7 +62,7 @@ static String buildRow(String docObjId, String docObjName, DocumentTable docTabl builder.append(Csv.serialize(docObjId) + ",") builder.append(Csv.serialize(docObjName) + ",") builder.append(Csv.serialize(contentPath) + ",") - builder.append(Csv.serialize(buildContentPreview(table)) + ",") + builder.append(Csv.serialize(previewProvider.getPreview(table)) + ",") builder.append(Csv.serialize(table.name) + ",") builder.append(Csv.serialize(table.pdfTaggingRule) + ",") builder.append(Csv.serialize(table.pdfAlternateText) + ",") diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TablesImport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TablesImport.groovy index 58b75bf2..a8e5a758 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TablesImport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/TablesImport.groovy @@ -20,7 +20,6 @@ import java.nio.file.Path import static com.quadient.migration.example.common.util.InitMigration.initMigration import static com.quadient.migration.service.TableUtilKt.collectDocumentTables -import static com.quadient.migration.service.TableUtilKt.computeFingerprint @Field static Logger log = LoggerFactory.getLogger(this.class.name) @@ -70,7 +69,7 @@ static void run(Migration migration, Path path) { continue } - def fingerprint = computeFingerprint(docTable.table) + def fingerprint = docTable.table.computeFingerprint() def entry = new MappingItem.Table.TableEntry( contentPath, diff --git a/migration-examples/src/test/groovy/AreasExportTest.groovy b/migration-examples/src/test/groovy/AreasExportTest.groovy index 7c1472f3..69f2d736 100644 --- a/migration-examples/src/test/groovy/AreasExportTest.groovy +++ b/migration-examples/src/test/groovy/AreasExportTest.groovy @@ -101,7 +101,7 @@ class AreasExportTest { def expected = """\ templateId,templateName (read-only),pageId,pageName (read-only),pageWidth (read-only),pageHeight (read-only),interactiveFlowName,flowToNextPage,x (read-only),y (read-only),width (read-only),height (read-only),contentPreview (read-only) - ,,page with preview,,,,test flow,false,0mm,0mm,0mm,0mm,DocObjRef(Block One);ImageRef(Image One);DocObjRef(Block Two);(+2 more) + ,,page with preview,,,,test flow,false,0mm,0mm,0mm,0mm,docRef: Block One;imageRef: Image One;docRef: Block Two;(+2 more) """.stripIndent() Assertions.assertEquals(expected, mappingFile.toFile().text.replaceAll("\\r\\n|\\r", "\n")) } diff --git a/migration-examples/src/test/groovy/TablesMappingExportTest.groovy b/migration-examples/src/test/groovy/TablesMappingExportTest.groovy index 5c035cf2..823ebdc3 100644 --- a/migration-examples/src/test/groovy/TablesMappingExportTest.groovy +++ b/migration-examples/src/test/groovy/TablesMappingExportTest.groovy @@ -34,8 +34,8 @@ class TablesMappingExportTest { def expected = """\ documentObjectId,documentObjectName (read-only),tableId,contentPreview (read-only),tableName,pdfTaggingRule,pdfAlternateText,action - doc1,My Doc,table:0,0 cols | 0 body rows,First,Default,,Keep - doc1,My Doc,table:1,0 cols | 0 body rows,Second,Table,Alt text,Flatten + doc1,My Doc,table:0,table: 0 cols | 0 body rows,First,Default,,Keep + doc1,My Doc,table:1,table: 0 cols | 0 body rows,Second,Table,Alt text,Flatten """.stripIndent() Assertions.assertEquals(expected, mappingFile.toFile().text.replaceAll("\\r\\n|\\r", "\n")) } @@ -57,8 +57,8 @@ class TablesMappingExportTest { def expected = """\ documentObjectId,documentObjectName (read-only),tableId,contentPreview (read-only),tableName,pdfTaggingRule,pdfAlternateText,action - doc1,First Doc,table:0,0 cols | 0 body rows,T1,Default,,Keep - doc2,Second Doc,table:0,0 cols | 0 body rows,T2,Default,,Keep + doc1,First Doc,table:0,table: 0 cols | 0 body rows,T1,Default,,Keep + doc2,Second Doc,table:0,table: 0 cols | 0 body rows,T2,Default,,Keep """.stripIndent() Assertions.assertEquals(expected, mappingFile.toFile().text.replaceAll("\\r\\n|\\r", "\n")) } diff --git a/migration-examples/src/test/groovy/TablesMappingImportTest.groovy b/migration-examples/src/test/groovy/TablesMappingImportTest.groovy index 07a708a5..6c8d245f 100644 --- a/migration-examples/src/test/groovy/TablesMappingImportTest.groovy +++ b/migration-examples/src/test/groovy/TablesMappingImportTest.groovy @@ -11,7 +11,6 @@ import org.junit.jupiter.api.io.TempDir import java.nio.file.Path import java.nio.file.Paths -import static com.quadient.migration.service.TableUtilKt.computeFingerprint import static org.mockito.Mockito.verify import static org.mockito.Mockito.when @@ -40,11 +39,11 @@ class TablesMappingImportTest { verify(migration.mappingRepository).upsertBatch([ "doc1": new MappingItem.Table(null, [ - new MappingItem.Table.TableEntry("table:0", TableAction.Keep, TablePdfTaggingRule.Table, "Alt text", computeFingerprint(table10), "TableOne"), - new MappingItem.Table.TableEntry("table:1", TableAction.Keep, null, null, computeFingerprint(table11), null) + new MappingItem.Table.TableEntry("table:0", TableAction.Keep, TablePdfTaggingRule.Table, "Alt text", table10.computeFingerprint(), "TableOne"), + new MappingItem.Table.TableEntry("table:1", TableAction.Keep, null, null, table11.computeFingerprint(), null) ]), "doc2": new MappingItem.Table(null, [ - new MappingItem.Table.TableEntry("table:0", TableAction.Flatten, TablePdfTaggingRule.Table, "Alt", computeFingerprint(table20), "TableTwo") + new MappingItem.Table.TableEntry("table:0", TableAction.Flatten, TablePdfTaggingRule.Table, "Alt", table20.computeFingerprint(), "TableTwo") ]) ]) } diff --git a/migration-examples/src/test/groovy/Utils.groovy b/migration-examples/src/test/groovy/Utils.groovy index 9e975ec4..aada7c8d 100644 --- a/migration-examples/src/test/groovy/Utils.groovy +++ b/migration-examples/src/test/groovy/Utils.groovy @@ -11,6 +11,7 @@ import com.quadient.migration.api.repository.StatusTrackingRepository import com.quadient.migration.api.repository.TextStyleRepository import com.quadient.migration.api.repository.VariableRepository import com.quadient.migration.api.repository.VariableStructureRepository +import com.quadient.migration.service.PreviewProvider import static org.mockito.Mockito.mock import static org.mockito.Mockito.when @@ -45,5 +46,8 @@ static Migration mockMigration() { when(migration.getMappingRepository()).thenReturn(mappingRepo) when(migration.getDisplayRuleRepository()).thenReturn(displayRuleRepo) + def previewProvider = new PreviewProvider(docObjectRepo, imageRepo, attachmentRepo, varRepo) + when(migration.getPreviewProvider()).thenReturn(previewProvider) + return migration } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/Migration.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/Migration.kt index 7c124a24..555a0c9f 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/Migration.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/Migration.kt @@ -22,6 +22,7 @@ import com.quadient.migration.service.IcmDataCache import com.quadient.migration.service.inspirebuilder.InteractiveDocumentObjectBuilder import com.quadient.migration.service.InteractiveIcmDataCache import com.quadient.migration.service.InteractiveResourcePathProvider +import com.quadient.migration.service.PreviewProvider import com.quadient.migration.service.ResourcePathProvider import com.quadient.migration.service.deploy.utility.ConflictDetectorImpl import com.quadient.migration.service.deploy.utility.ProgressReporterImpl @@ -110,6 +111,7 @@ class Migration(val config: MigConfig, val projectConfig: ProjectConfig) { singleOf(::ReferenceValidator) singleOf(::StylesValidator) singleOf(::RefCollector) + singleOf(::PreviewProvider) } private val koinApp: KoinApplication = koinApplication { @@ -137,6 +139,7 @@ class Migration(val config: MigConfig, val projectConfig: ProjectConfig) { val stylesValidator: StylesValidator by lazy { koin.get() } val referenceCollector: RefCollector by lazy { koin.get() } val pathProvider: ResourcePathProvider by lazy { koin.get() } + val previewProvider: PreviewProvider by lazy { koin.get() } val storage: Storage by lazy { koin.get() } private val ipsService: IpsService by lazy { koin.get() } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/Ref.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/Ref.kt index 0222e3ca..d1bd3fec 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/Ref.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/Ref.kt @@ -49,6 +49,10 @@ sealed interface TextContent { data class DocumentObjectRef(override val id: String, val displayRuleRef: DisplayRuleRef? = null) : Ref(id), DocumentContent, TextContent, RefValidatable { + override val pathName = "docRef" + + override fun toPreview(nameResolver: (DocumentContent) -> String?): String = + "$pathName: ${nameResolver(this) ?: id}" constructor(id: String) : this(id, null) @@ -65,6 +69,9 @@ data class DocumentObjectRef(override val id: String, val displayRuleRef: Displa } data class VariableRef(override val id: String) : Ref(id), VariableStringContent, RefValidatable { + override val pathName = "varRef" + override fun toPreview(nameResolver: (DocumentContent) -> String?): String = + "$${nameResolver(this) ?: id}$" override fun collectRefs(): List = listOf(this) companion object { @@ -104,6 +111,10 @@ sealed class ResourceRef(override val id: String) : Ref(id), DocumentContent, Te } data class ImageRef(override val id: String) : ResourceRef(id) { + override val pathName = "imageRef" + override fun toPreview(nameResolver: (DocumentContent) -> String?): String = + "imageRef: ${nameResolver(this) ?: id}" + companion object { fun fromDb(entity: ImageEntityRef) = ImageRef(entity.id) } @@ -112,6 +123,10 @@ data class ImageRef(override val id: String) : ResourceRef(id) { } data class AttachmentRef(override val id: String) : ResourceRef(id) { + override val pathName = "attachRef" + override fun toPreview(nameResolver: (DocumentContent) -> String?): String = + "$pathName: ${nameResolver(this) ?: id}" + companion object { fun fromDb(entity: AttachmentEntityRef) = AttachmentRef(entity.id) } @@ -124,6 +139,10 @@ data class VariableStructureRef(override val id: String) : Ref(id) { } data class StringValue(val value: String) : VariableStringContent { + override val pathName = "str" + override fun toPreview(nameResolver: (DocumentContent) -> String?): String = + value.replace('\r', ' ').replace('\n', ' ').take(30) + companion object { fun fromDb(entity: StringEntity) = StringValue(entity.value) } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Area.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Area.kt index 8f3ca5bc..e4f761af 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Area.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Area.kt @@ -5,8 +5,11 @@ import com.quadient.migration.shared.Position data class Area(var content: List, var position: Position?, var interactiveFlowName: String?, var flowToNextPage: Boolean = false) : DocumentContent, RefValidatable { - val pathName: String get() = "area" - + override val pathName = "area" + + override fun toPreview(nameResolver: (DocumentContent) -> String?): String = + "$pathName: ${content.size} items" + constructor(content: List, position: Position?, interactiveFlowName: String?) : this(content, position, interactiveFlowName, false) override fun collectRefs(): List { return content.flatMap { diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Barcode.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Barcode.kt index e6d879af..2bc54763 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Barcode.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Barcode.kt @@ -16,6 +16,11 @@ sealed interface Barcode : DocumentContent, TextContent { val backgroundFill: Color? val variableRef: VariableRef? + override fun toPreview(nameResolver: (DocumentContent) -> String?): String { + val label = data?.take(30) ?: variableRef?.toPreview(nameResolver) + return if (label != null) "$pathName: $label" else pathName + } + companion object { fun fromDb(entity: BarcodeEntity): Barcode = when (entity) { is QrCodeEntity -> QrCode.fromDb(entity) @@ -37,6 +42,7 @@ data class QrCode( val moduleWidth: Size, val quiteZone: Size, ): Barcode { + override val pathName = "qrCode" companion object { fun fromDb(entity: QrCodeEntity): QrCode = QrCode( @@ -83,6 +89,7 @@ data class Code39Barcode( val firstBarSpace: Size, val secondBarSpace: Size, ) : Barcode { + override val pathName = "code39Barcode" companion object { fun fromDb(entity: Code39BarcodeEntity): Code39Barcode = Code39Barcode( diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/ColumnLayout.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/ColumnLayout.kt index 97c75b54..013cd19e 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/ColumnLayout.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/ColumnLayout.kt @@ -11,6 +11,9 @@ data class ColumnLayout( val balancingType: ColumnBalancingType? = null, val applyTo: ColumnApplyTo? = null, ) : DocumentContent, TextContent { + override val pathName = "columnLayout" + + override fun toPreview(nameResolver: (DocumentContent) -> String?): String = "$pathName: $numberOfColumns cols" companion object { fun fromDb(entity: ColumnLayoutEntity): ColumnLayout = ColumnLayout( diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/DocumentContent.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/DocumentContent.kt index 92023eff..8e1b194f 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/DocumentContent.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/DocumentContent.kt @@ -16,6 +16,10 @@ import com.quadient.migration.persistence.migrationmodel.RepeatedContentEntity import com.quadient.migration.persistence.migrationmodel.VariableStringContentEntity sealed interface DocumentContent { + val pathName: String + fun toPreview(nameResolver: (DocumentContent) -> String?): String + fun toPreview(): String = toPreview { null } + companion object { fun fromDbContent(entity: DocumentContentEntity): DocumentContent = when (entity) { is TableEntity -> Table.fromDb(entity) diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/FirstMatch.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/FirstMatch.kt index 304aa06c..bab3fe20 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/FirstMatch.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/FirstMatch.kt @@ -3,7 +3,10 @@ package com.quadient.migration.api.dto.migrationmodel import com.quadient.migration.persistence.migrationmodel.FirstMatchEntity data class FirstMatch(val cases: List, val default: List) : DocumentContent, TextContent, RefValidatable { - val pathName: String get() = "firstMatch" + override val pathName = "firstMatch" + + override fun toPreview(nameResolver: (DocumentContent) -> String?): String = "$pathName: ${cases.size} cases" + override fun collectRefs(): List { return cases.flatMap { it.collectRefs() } + default.flatMap { when (it) { diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Paragraph.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Paragraph.kt index d9c64f85..cd2fab43 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Paragraph.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Paragraph.kt @@ -8,6 +8,17 @@ data class Paragraph( val styleRef: ParagraphStyleRef?, val displayRuleRef: DisplayRuleRef? ) : DocumentContent, RefValidatable { + override val pathName = "par" + override fun toPreview(nameResolver: (DocumentContent) -> String?): String { + val preview = content.flatMap { it.content }.joinToString("") { + when (it) { + is DocumentContent -> it.toPreview(nameResolver) + is Hyperlink -> it.displayText ?: it.url + } + }.replace('\r', ' ').replace('\n', ' ').take(50) + return "$pathName: $preview" + } + override fun collectRefs(): List { return content.flatMap { it.collectRefs() } + listOfNotNull(styleRef, displayRuleRef) } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/RepeatedContent.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/RepeatedContent.kt index 98a37148..4e8b9d74 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/RepeatedContent.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/RepeatedContent.kt @@ -1,6 +1,7 @@ package com.quadient.migration.api.dto.migrationmodel import com.quadient.migration.persistence.migrationmodel.RepeatedContentEntity +import com.quadient.migration.shared.LiteralPath import com.quadient.migration.shared.VariablePath import com.quadient.migration.shared.VariableRefPath @@ -8,7 +9,15 @@ data class RepeatedContent( val variablePath: VariablePath, val content: List, ) : DocumentContent, RefValidatable { - val pathName: String get() = "repeatedContent" + override val pathName = "repeatedContent" + + override fun toPreview(nameResolver: (DocumentContent) -> String?): String { + val variable = when (variablePath) { + is VariableRefPath -> VariableRef(variablePath.variableId).toPreview(nameResolver) + is LiteralPath -> variablePath.path + } + return "$pathName: $variable | ${content.size} items" + } override fun collectRefs(): List { val contentRefs = content.flatMap { diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/SelectByLanguage.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/SelectByLanguage.kt index a6c29719..a2842068 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/SelectByLanguage.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/SelectByLanguage.kt @@ -3,7 +3,10 @@ package com.quadient.migration.api.dto.migrationmodel import com.quadient.migration.persistence.migrationmodel.SelectByLanguageEntity data class SelectByLanguage(val cases: List) : DocumentContent, RefValidatable { - val pathName: String get() = "selectByLanguage" + override val pathName = "selectByLanguage" + + override fun toPreview(nameResolver: (DocumentContent) -> String?): String = "$pathName: ${cases.size} cases" + override fun collectRefs(): List { return cases.flatMap { it.collectRefs() } } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Shape.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Shape.kt index 7ad1c02a..b82db32d 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Shape.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Shape.kt @@ -14,6 +14,11 @@ data class Shape( val lineFill: Color?, val lineWidth: Size, ) : DocumentContent { + override val pathName = "shape" + + override fun toPreview(nameResolver: (DocumentContent) -> String?): String = + if (name != null) "$pathName: $name" else pathName + companion object { fun fromDb(entity: ShapeEntity): Shape = Shape( name = entity.name, diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Table.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Table.kt index 05fa861d..db04aa09 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Table.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Table.kt @@ -5,6 +5,7 @@ import com.quadient.migration.shared.BorderOptions import com.quadient.migration.shared.CellAlignment import com.quadient.migration.shared.CellHeight import com.quadient.migration.shared.CellOverflow +import com.quadient.migration.shared.LiteralPath import com.quadient.migration.shared.VariablePath import com.quadient.migration.shared.Size import com.quadient.migration.shared.TableAction @@ -35,7 +36,47 @@ data class Table( val action: TableAction = TableAction.Keep, val name: String? = null, ) : DocumentContent, TextContent, RefValidatable { - val pathName: String get() = "table" + override val pathName = "table" + + override fun toPreview(nameResolver: (DocumentContent) -> String?): String = buildString { + append("$pathName: ${columnWidths.size} cols") + val repeatedVar = repeatedVariablePreview(nameResolver) + if (repeatedVar != null) append(" | repeatedBy: $repeatedVar") + else append(" | ${rows.count { it is Row }} body rows") + firstRowPreview(" | ", nameResolver)?.let { + if (firstHeader.isNotEmpty()) append(" | firstHeader: $it") + else append(" | row0: $it") + } + } + + fun computeFingerprint(): String = buildString { + append("${columnWidths.size}cols") + val singleRowCount = (header + firstHeader + rows + footer + lastFooter).count { it is Row } + append("|${singleRowCount}rows") + repeatedVariableId()?.let { append("|repeatedBy:$it") } + firstRowPreview()?.let { append("|$it") } + } + + private fun repeatedRow(): RepeatedRow? = rows.filterIsInstance().firstOrNull() + + private fun repeatedVariablePreview(nameResolver: (DocumentContent) -> String?): String? = + repeatedRow()?.variable?.let { variable -> + when (variable) { + is VariableRefPath -> VariableRef(variable.variableId).toPreview(nameResolver) + is LiteralPath -> variable.path + } + } + + private fun repeatedVariableId(): String? = + repeatedRow()?.variable?.let { if (it is VariableRefPath) it.variableId else it.toString() } + + private fun firstRowPreview(separator: String = "|", nameResolver: (DocumentContent) -> String? = { null }): String? { + val firstRow = (firstHeader.firstOrNull() ?: rows.filterIsInstance().firstOrNull()) as? Row ?: return null + return firstRow.cells.joinToString(separator) { cell -> + cell.content.joinToString("") { it.toPreview(nameResolver) }.replace('\r', ' ').replace('\n', ' ').take(30) + }.take(100) + } + override fun collectRefs(): List { return (rows + header + firstHeader + footer + lastFooter).flatMap { it.collectRefs() } } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntity.kt b/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntity.kt index 5388a69a..353934da 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntity.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntity.kt @@ -3,7 +3,6 @@ package com.quadient.migration.persistence.migrationmodel import com.quadient.migration.api.dto.migrationmodel.* import com.quadient.migration.persistence.table.MappingTable import com.quadient.migration.service.collectDocumentTables -import com.quadient.migration.service.computeFingerprint import com.quadient.migration.shared.* import org.slf4j.LoggerFactory import com.quadient.migration.shared.LineSpacing.Additional @@ -328,7 +327,7 @@ sealed class MappingItemEntity { return@forEach } - val currentFingerprint = computeFingerprint(docTable.table) + val currentFingerprint = docTable.table.computeFingerprint() if (currentFingerprint != entry.fingerprint) { val message = "Table mapping for '${item.id}' at [${entry.contentPath}]: fingerprint mismatch. Stored: '${entry.fingerprint}', current: '$currentFingerprint'. Content may have changed since last export. Skipping." logger.error(message) diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/PreviewProvider.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/PreviewProvider.kt new file mode 100644 index 00000000..6bdef5b8 --- /dev/null +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/PreviewProvider.kt @@ -0,0 +1,58 @@ +package com.quadient.migration.service + +import com.quadient.migration.api.dto.migrationmodel.Area +import com.quadient.migration.api.dto.migrationmodel.AttachmentRef +import com.quadient.migration.api.dto.migrationmodel.Barcode +import com.quadient.migration.api.dto.migrationmodel.Code39Barcode +import com.quadient.migration.api.dto.migrationmodel.ColumnLayout +import com.quadient.migration.api.dto.migrationmodel.DocumentContent +import com.quadient.migration.api.dto.migrationmodel.DocumentObjectRef +import com.quadient.migration.api.dto.migrationmodel.FirstMatch +import com.quadient.migration.api.dto.migrationmodel.ImageRef +import com.quadient.migration.api.dto.migrationmodel.Paragraph +import com.quadient.migration.api.dto.migrationmodel.QrCode +import com.quadient.migration.api.dto.migrationmodel.RepeatedContent +import com.quadient.migration.api.dto.migrationmodel.SelectByLanguage +import com.quadient.migration.api.dto.migrationmodel.Shape +import com.quadient.migration.api.dto.migrationmodel.StringValue +import com.quadient.migration.api.dto.migrationmodel.Table +import com.quadient.migration.api.dto.migrationmodel.VariableRef +import com.quadient.migration.api.repository.AttachmentRepository +import com.quadient.migration.api.repository.DocumentObjectRepository +import com.quadient.migration.api.repository.ImageRepository +import com.quadient.migration.api.repository.VariableRepository + +class PreviewProvider( + private val documentObjectRepository: DocumentObjectRepository, + private val imageRepository: ImageRepository, + private val attachmentRepository: AttachmentRepository, + private val variableRepository: VariableRepository, +) { + fun getPreview(content: DocumentContent): String = content.toPreview(::resolveName) + + @JvmOverloads + fun buildDocumentContentListPreview(content: List, limit: Int = 3): String { + val parts = content.map { getPreview(it) } + val taken = parts.take(limit) + val suffix = if (parts.size > limit) listOf("(+${parts.size - limit} more)") else emptyList() + return (taken + suffix).joinToString(";").replace(",", " ") + } + + private fun resolveName(content: DocumentContent): String? = when (content) { + is DocumentObjectRef -> documentObjectRepository.find(content.id)?.name + is ImageRef -> imageRepository.find(content.id)?.name + is AttachmentRef -> attachmentRepository.find(content.id)?.name + is VariableRef -> variableRepository.find(content.id)?.name + is StringValue -> null + is Table -> null + is Paragraph -> null + is Area -> null + is FirstMatch -> null + is SelectByLanguage -> null + is RepeatedContent -> null + is ColumnLayout -> null + is Shape -> null + is QrCode -> null + is Code39Barcode -> null + } +} diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/TableUtil.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/TableUtil.kt index 5864fef8..1629ee0c 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/TableUtil.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/TableUtil.kt @@ -3,14 +3,9 @@ package com.quadient.migration.service import com.quadient.migration.api.dto.migrationmodel.Area import com.quadient.migration.api.dto.migrationmodel.DocumentContent import com.quadient.migration.api.dto.migrationmodel.FirstMatch -import com.quadient.migration.api.dto.migrationmodel.Paragraph import com.quadient.migration.api.dto.migrationmodel.RepeatedContent import com.quadient.migration.api.dto.migrationmodel.SelectByLanguage -import com.quadient.migration.api.dto.migrationmodel.StringValue import com.quadient.migration.api.dto.migrationmodel.Table -import com.quadient.migration.api.dto.migrationmodel.VariableRef -import com.quadient.migration.api.dto.migrationmodel.VariableStringContent -import com.quadient.migration.shared.VariableRefPath data class DocumentTable(val contentPath: String, val table: Table) @@ -45,52 +40,3 @@ private fun MutableMap.next(name: String): String { val idx = getOrDefault(name, 0).also { this[name] = it + 1 } return "$name:$idx" } - -fun computeFingerprint(table: Table): String = buildString { - append("${table.columnWidths.size}cols") - tableRepeatedVariable(table)?.let { append("|repeatedBy:$it") } - tableFirstRowPreview(table)?.let { append("|$it") } -} - -fun buildContentPreview(table: Table): String { - val colCount = table.columnWidths.size - val bodyRowCount = table.rows.count { it is Table.Row } - val repeatedVariable = tableRepeatedVariable(table) - val firstRowPreview = tableFirstRowPreview(table, " | ") - - return buildString { - append("$colCount cols") - if (repeatedVariable != null) append(" | repeatedBy: $repeatedVariable") - else append(" | $bodyRowCount body rows") - if (table.firstHeader.isNotEmpty()) append(" | firstHeader: $firstRowPreview") - else if (firstRowPreview != null) append(" | row0: $firstRowPreview") - } -} - -fun tableRepeatedVariable(table: Table): String? = table.rows.filterIsInstance() - .firstOrNull()?.variable?.let { if (it is VariableRefPath) it.variableId else it.toString() } - -fun tableFirstRowPreview(table: Table, separator: String = "|"): String? { - val firstRow = - (table.firstHeader.firstOrNull() ?: table.header.firstOrNull() ?: table.rows.filterIsInstance() - .firstOrNull()) as? Table.Row ?: return null - - return firstRow.cells.joinToString(separator) { cell -> extractCellText(cell).replace('\r', ' ').replace('\n', ' ').take(30) }.take(100) -} - -private fun extractCellText(cell: Table.Cell): String = - cell.content.asSequence() - .flatMap { item -> - when (item) { - is Paragraph -> item.content.asSequence().flatMap { it.content.asSequence() } - is VariableStringContent -> sequenceOf(item) - else -> emptySequence() - } - } - .joinToString("") { textContent -> - when (textContent) { - is StringValue -> textContent.value - is VariableRef -> "var:${textContent.id}" - else -> "" - } - } diff --git a/migration-library/src/test/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/TableTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/TableTest.kt new file mode 100644 index 00000000..d58194b6 --- /dev/null +++ b/migration-library/src/test/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/TableTest.kt @@ -0,0 +1,44 @@ +package com.quadient.migration.api.dto.migrationmodel.documentcontent + +import com.quadient.migration.api.dto.migrationmodel.DocumentContent +import com.quadient.migration.api.dto.migrationmodel.VariableRef +import com.quadient.migration.api.dto.migrationmodel.builder.TableBuilder +import com.quadient.migration.shared.VariableRefPath +import com.quadient.migration.shared.millimeters +import com.quadient.migration.tools.shouldBeEqualTo +import org.junit.jupiter.api.Test + +class TableTest { + private val richTable = TableBuilder() + .addColumnWidth(10.millimeters(), 50.0) + .addColumnWidth(10.millimeters(), 50.0) + .addRepeatedRow(VariableRefPath("itemVar")) { + addRow { + addCell { string("Item A") } + addCell { string("Item B") } + } + } + .addFirstHeaderRow { + addCell { string("Alpha") } + addCell { string("Beta") } + } + .build() + + @Test + fun `computeFingerprint encodes column count, row count, repeated variable and first header row preview`() { + richTable.computeFingerprint().shouldBeEqualTo("2cols|1rows|repeatedBy:itemVar|Alpha|Beta") + } + + @Test + fun `toPreview formats column count, repeated variable label and first header row label with preview`() { + richTable.toPreview().shouldBeEqualTo($$"table: 2 cols | repeatedBy: $itemVar$ | firstHeader: Alpha | Beta") + } + + @Test + fun `toPreview resolves repeated variable name via nameResolver`() { + val nameResolver: (DocumentContent) -> String? = { if (it is VariableRef) "Item Variable" else null } + + richTable.toPreview(nameResolver) + .shouldBeEqualTo($$"table: 2 cols | repeatedBy: $Item Variable$ | firstHeader: Alpha | Beta") + } +} diff --git a/migration-library/src/test/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntityTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntityTest.kt index 1d103e8b..6c37f950 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntityTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/persistence/migrationmodel/MappingEntityTest.kt @@ -9,7 +9,6 @@ import com.quadient.migration.api.dto.migrationmodel.builder.ParagraphStyleBuild import com.quadient.migration.api.dto.migrationmodel.builder.TableBuilder import com.quadient.migration.api.dto.migrationmodel.builder.TextStyleBuilder import com.quadient.migration.api.dto.migrationmodel.builder.documentcontent.AreaBuilder -import com.quadient.migration.service.computeFingerprint import com.quadient.migration.shared.Alignment import com.quadient.migration.shared.Color import com.quadient.migration.shared.DataType @@ -399,7 +398,7 @@ class MappingEntityTest { action = TableAction.Flatten, pdfTaggingRule = TablePdfTaggingRule.Table, pdfAlternateText = "New alt text", - fingerprint = computeFingerprint(docObj.content[0] as TableModel), + fingerprint = (docObj.content[0] as TableModel).computeFingerprint(), tableName = "Renamed Table", ) )) @@ -433,7 +432,7 @@ class MappingEntityTest { mapping.apply(docObj) { errors.add(it) } errors.size.shouldBeEqualTo(1) - errors[0].shouldBeEqualTo("Table mapping for 'doc1' at [table:0]: fingerprint mismatch. Stored: 'wrong-fingerprint', current: '0cols'. Content may have changed since last export. Skipping.") + errors[0].shouldBeEqualTo("Table mapping for 'doc1' at [table:0]: fingerprint mismatch. Stored: 'wrong-fingerprint', current: '0cols|0rows'. Content may have changed since last export. Skipping.") } @Test @@ -468,7 +467,7 @@ class MappingEntityTest { action = TableAction.Flatten, pdfTaggingRule = null, pdfAlternateText = null, - fingerprint = computeFingerprint(tableOne), + fingerprint = tableOne.computeFingerprint(), tableName = "Renamed", ), MappingItemEntity.Table.TableEntry( @@ -489,7 +488,7 @@ class MappingEntityTest { tables[0].action.shouldBeEqualTo(TableAction.Flatten) tables[1].action.shouldBeEqualTo(TableAction.Keep) errors.size.shouldBeEqualTo(1) - errors[0].shouldBeEqualTo("Table mapping for 'doc1' at [table:1]: fingerprint mismatch. Stored: 'wrong-fingerprint', current: '0cols'. Content may have changed since last export. Skipping.") + errors[0].shouldBeEqualTo("Table mapping for 'doc1' at [table:1]: fingerprint mismatch. Stored: 'wrong-fingerprint', current: '0cols|0rows'. Content may have changed since last export. Skipping.") } } } diff --git a/migration-library/src/test/kotlin/com/quadient/migration/service/TableUtilTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/TableUtilTest.kt index c309ed1f..eb79a2b1 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/TableUtilTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/TableUtilTest.kt @@ -4,8 +4,6 @@ import com.quadient.migration.api.dto.migrationmodel.DocumentContent import com.quadient.migration.api.dto.migrationmodel.builder.FirstMatchBuilder import com.quadient.migration.api.dto.migrationmodel.builder.TableBuilder import com.quadient.migration.api.dto.migrationmodel.builder.documentcontent.AreaBuilder -import com.quadient.migration.shared.VariableRefPath -import com.quadient.migration.shared.millimeters import com.quadient.migration.tools.shouldBeEqualTo import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -18,19 +16,8 @@ class TableUtilTest { FirstMatchBuilder().defaultTable { }.build(), ) - private val richTable = TableBuilder().addColumnWidth(10.millimeters(), 50.0).addColumnWidth(10.millimeters(), 50.0) - .addRepeatedRow(VariableRefPath("itemVar")) { - addRow { - addCell { string("Item A") } - addCell { string("Item B") } - } - }.addFirstHeaderRow { - addCell { string("Alpha") } - addCell { string("Beta") } - }.build() - @Test - fun `collectDocumentTables collects direct table, area-child table and FM-child table, but skips table nested inside FM inside area`() { + fun `collectDocumentTables collects direct table, one level nested table, but skips table nested two levels deep`() { val result = collectDocumentTables(mixedContent) assertEquals(3, result.size) @@ -38,14 +25,4 @@ class TableUtilTest { result[1].contentPath.shouldBeEqualTo("area:1/table:0") result[2].contentPath.shouldBeEqualTo("firstMatch:0/table:0") } - - @Test - fun `computeFingerprint encodes column count, repeated variable and first header row preview`() { - computeFingerprint(richTable).shouldBeEqualTo("2cols|repeatedBy:itemVar|Alpha|Beta") - } - - @Test - fun `buildContentPreview formats column count, repeated variable label and first header row label with preview`() { - buildContentPreview(richTable).shouldBeEqualTo("2 cols | repeatedBy: itemVar | firstHeader: Alpha | Beta") - } } From 6b6898a14ec0fda7d37fffd8adfab3de025ead35 Mon Sep 17 00:00:00 2001 From: "d.svitak" Date: Tue, 14 Jul 2026 09:51:38 +0200 Subject: [PATCH 2/3] MIG-562 Base Template definition and deployment - Initial support - more test coverage --- .../documentcontent/ParagraphTest.kt | 50 ++++++++ .../documentcontent/TableTest.kt | 43 +++++++ .../migration/service/PreviewProviderTest.kt | 116 ++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 migration-library/src/test/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/ParagraphTest.kt create mode 100644 migration-library/src/test/kotlin/com/quadient/migration/service/PreviewProviderTest.kt diff --git a/migration-library/src/test/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/ParagraphTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/ParagraphTest.kt new file mode 100644 index 00000000..d3823a85 --- /dev/null +++ b/migration-library/src/test/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/ParagraphTest.kt @@ -0,0 +1,50 @@ +package com.quadient.migration.api.dto.migrationmodel.documentcontent + +import com.quadient.migration.api.dto.migrationmodel.DocumentContent +import com.quadient.migration.api.dto.migrationmodel.VariableRef +import com.quadient.migration.api.dto.migrationmodel.builder.ParagraphBuilder +import com.quadient.migration.tools.shouldBeEqualTo +import org.junit.jupiter.api.Test + +class ParagraphTest { + + @Test + fun `toPreview concatenates strings and variable refs`() { + val paragraph = ParagraphBuilder() + .string("Dear ") + .variableRef("firstName") + .string(" ") + .variableRef("lastName") + .build() + + paragraph.toPreview().shouldBeEqualTo($$"par: Dear $firstName$ $lastName$") + } + + @Test + fun `toPreview resolves variable names via nameResolver`() { + val nameResolver: (DocumentContent) -> String? = { content -> + when (content) { + is VariableRef if content.id == "firstName" -> "First Name" + is VariableRef if content.id == "lastName" -> "Last Name" + else -> null + } + } + val paragraph = ParagraphBuilder() + .string("Dear ") + .variableRef("firstName") + .string(" ") + .variableRef("lastName") + .build() + + paragraph.toPreview(nameResolver).shouldBeEqualTo($$"par: Dear $First Name$ $Last Name$") + } + + @Test + fun `toPreview truncates long concatenated content`() { + val paragraph = ParagraphBuilder() + .string("This is a very long string that exceeds the preview limit and should be truncated at some point") + .build() + + paragraph.toPreview().shouldBeEqualTo("par: This is a very long string tha") + } +} diff --git a/migration-library/src/test/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/TableTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/TableTest.kt index d58194b6..605c9eb6 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/TableTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/TableTest.kt @@ -41,4 +41,47 @@ class TableTest { richTable.toPreview(nameResolver) .shouldBeEqualTo($$"table: 2 cols | repeatedBy: $Item Variable$ | firstHeader: Alpha | Beta") } + + @Test + fun `toPreview and fingerprint shows first row with mixed string and variable ref content`() { + val table = TableBuilder() + .addColumnWidth(10.millimeters(), 50.0) + .addColumnWidth(10.millimeters(), 50.0) + .addRow { + addCell { + string("Hello ") + variableRef("name") + } + addCell { string("static text") } + } + .build() + + table.toPreview().shouldBeEqualTo($$"table: 2 cols | 1 body rows | row0: Hello $name$ | static text") + table.computeFingerprint().shouldBeEqualTo($$"2cols|1rows|Hello $name$|static text") + } + + @Test + fun `toPreview and fingerprint handles imageRef, selectByLanguage and paragraph content types in cells`() { + val table = TableBuilder() + .addColumnWidth(10.millimeters(), 33.0) + .addColumnWidth(10.millimeters(), 33.0) + .addColumnWidth(10.millimeters(), 34.0) + .addRow { + addCell { imageRef("photo1") } + addCell { + selectByLanguage { + case { language("en").string("Hello") } + case { language("de").string("Hallo") } + } + } + addCell { + paragraph { string("caption text") } + } + } + .build() + + table.toPreview() + .shouldBeEqualTo("table: 3 cols | 1 body rows | row0: imageRef: photo1 | selectByLanguage: 2 cases | par: caption text") + table.computeFingerprint().shouldBeEqualTo("3cols|1rows|imageRef: photo1|selectByLanguage: 2 cases|par: caption text") + } } diff --git a/migration-library/src/test/kotlin/com/quadient/migration/service/PreviewProviderTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/PreviewProviderTest.kt new file mode 100644 index 00000000..5148b289 --- /dev/null +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/PreviewProviderTest.kt @@ -0,0 +1,116 @@ +package com.quadient.migration.service + +import com.quadient.migration.api.dto.migrationmodel.AttachmentRef +import com.quadient.migration.api.dto.migrationmodel.DocumentObjectRef +import com.quadient.migration.api.dto.migrationmodel.ImageRef +import com.quadient.migration.api.dto.migrationmodel.StringValue +import com.quadient.migration.api.dto.migrationmodel.VariableRef +import com.quadient.migration.api.dto.migrationmodel.builder.AttachmentBuilder +import com.quadient.migration.api.dto.migrationmodel.builder.DocumentObjectBuilder +import com.quadient.migration.api.dto.migrationmodel.builder.ImageBuilder +import com.quadient.migration.api.dto.migrationmodel.builder.VariableBuilder +import com.quadient.migration.api.repository.AttachmentRepository +import com.quadient.migration.api.repository.DocumentObjectRepository +import com.quadient.migration.api.repository.ImageRepository +import com.quadient.migration.api.repository.VariableRepository +import com.quadient.migration.shared.DataType +import com.quadient.migration.shared.DocumentObjectType +import com.quadient.migration.tools.shouldBeEqualTo +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.Test + +class PreviewProviderTest { + private val docObjectRepo = mockk() + private val imageRepo = mockk() + private val attachmentRepo = mockk() + private val variableRepo = mockk() + + private val provider = PreviewProvider(docObjectRepo, imageRepo, attachmentRepo, variableRepo) + + @Test + fun `getPreview resolves DocumentObjectRef name`() { + every { docObjectRepo.find("doc-1") } returns DocumentObjectBuilder("doc-1", DocumentObjectType.Block).name("My Block").build() + + provider.getPreview(DocumentObjectRef("doc-1")).shouldBeEqualTo("docRef: My Block") + } + + @Test + fun `getPreview falls back to id when DocumentObjectRef has no name`() { + every { docObjectRepo.find("doc-1") } returns DocumentObjectBuilder("doc-1", DocumentObjectType.Block).build() + + provider.getPreview(DocumentObjectRef("doc-1")).shouldBeEqualTo("docRef: doc-1") + } + + @Test + fun `getPreview falls back to id when DocumentObjectRef not found`() { + every { docObjectRepo.find("doc-1") } returns null + + provider.getPreview(DocumentObjectRef("doc-1")).shouldBeEqualTo("docRef: doc-1") + } + + @Test + fun `getPreview resolves ImageRef name`() { + every { imageRepo.find("img-1") } returns ImageBuilder("img-1").name("My Image").build() + + provider.getPreview(ImageRef("img-1")).shouldBeEqualTo("imageRef: My Image") + } + + @Test + fun `getPreview resolves AttachmentRef name`() { + every { attachmentRepo.find("att-1") } returns AttachmentBuilder("att-1").name("My Attachment").build() + + provider.getPreview(AttachmentRef("att-1")).shouldBeEqualTo("attachRef: My Attachment") + } + + @Test + fun `getPreview resolves VariableRef name`() { + every { variableRepo.find("var-1") } returns VariableBuilder("var-1").name("My Variable").dataType(DataType.String).build() + + provider.getPreview(VariableRef("var-1")).shouldBeEqualTo($$"$My Variable$") + } + + @Test + fun `getPreview falls back to id for unresolvable VariableRef`() { + every { variableRepo.find("var-1") } returns null + + provider.getPreview(VariableRef("var-1")).shouldBeEqualTo($$"$var-1$") + } + + @Test + fun `getPreview returns plain value for StringValue`() { + provider.getPreview(StringValue("hello world")).shouldBeEqualTo("hello world") + } + + @Test + fun `buildDocumentContentListPreview joins previews with semicolon`() { + every { docObjectRepo.find(any()) } returns null + + val result = provider.buildDocumentContentListPreview( + listOf(DocumentObjectRef("a"), DocumentObjectRef("b"), DocumentObjectRef("c")) + ) + + result.shouldBeEqualTo("docRef: a;docRef: b;docRef: c") + } + + @Test + fun `buildDocumentContentListPreview truncates after limit and appends count`() { + every { docObjectRepo.find(any()) } returns null + + val content = listOf( + DocumentObjectRef("a"), DocumentObjectRef("b"), DocumentObjectRef("c"), + DocumentObjectRef("d"), DocumentObjectRef("e") + ) + + provider.buildDocumentContentListPreview(content, limit = 3) + .shouldBeEqualTo("docRef: a;docRef: b;docRef: c;(+2 more)") + } + + @Test + fun `buildDocumentContentListPreview strips commas from previews`() { + every { docObjectRepo.find("x") } returns DocumentObjectBuilder("x", DocumentObjectType.Block).name("Foo, Bar").build() + + provider.buildDocumentContentListPreview(listOf(DocumentObjectRef("x"))) + .shouldBeEqualTo("docRef: Foo Bar") + } +} From 38af18545f22af54e7602d257c6fbb24da7b400b Mon Sep 17 00:00:00 2001 From: "d.svitak" Date: Tue, 14 Jul 2026 10:47:30 +0200 Subject: [PATCH 3/3] MIG-562 Base Template definition and deployment - Initial support - changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6848da9a..95e67ec5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) ### Changed +- Cleaned, standardized and extended content preview in mapping/report files +- More robust table fingerprint used for mapping + ### Fixed ## [17.0.26] - 2026-07-09