mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-27 20:14:57 +02:00
Allow to export documents without children (#10909)
* Allow to export documents without children Signed-off-by: Artem Savchenko <armisav@gmail.com> * Fix tests Signed-off-by: Artem Savchenko <armisav@gmail.com> --------- Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
@@ -53,6 +53,8 @@
|
||||
"ExportFilterEffectiveOnly": "Pouze platné dokumenty",
|
||||
"ExportFilterSkipArchivedObsolete": "Vše kromě archivovaných a zastaralých",
|
||||
"ExportFilterAll": "Všechny dokumenty",
|
||||
"ExportChildDocuments": "Exportovat podřízené dokumenty",
|
||||
"ExportChildDocumentsDescription": "Také exportovat dokumenty připojené k vybraným",
|
||||
"ExportResultRecordTitle": "Export dokumentů z pracovního prostoru {workspace} ({date})"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"ExportFilterEffectiveOnly": "Nur wirksame Dokumente exportieren",
|
||||
"ExportFilterSkipArchivedObsolete": "Alle Dokumente außer archivierten und veralteten",
|
||||
"ExportFilterAll": "Alle Dokumente",
|
||||
"ExportChildDocuments": "Untergeordnete Dokumente exportieren",
|
||||
"ExportChildDocumentsDescription": "Auch Dokumente exportieren, die an die ausgewählten angehängt sind",
|
||||
"ExportResultRecordTitle": "Export von Dokumenten aus dem Arbeitsbereich {workspace} ({date})"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"ExportFilterEffectiveOnly": "Export only effective documents",
|
||||
"ExportFilterSkipArchivedObsolete": "All documents except archived and obsolete",
|
||||
"ExportFilterAll": "All documents",
|
||||
"ExportChildDocuments": "Export child documents",
|
||||
"ExportChildDocumentsDescription": "Also export documents attached to the selected ones",
|
||||
"ExportResultRecordTitle": "Export of documents from {workspace} workspace ({date})"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"ExportFilterEffectiveOnly": "Solo documentos efectivos",
|
||||
"ExportFilterSkipArchivedObsolete": "Todos excepto archivados y obsoletos",
|
||||
"ExportFilterAll": "Todos los documentos",
|
||||
"ExportChildDocuments": "Exportar documentos secundarios",
|
||||
"ExportChildDocumentsDescription": "Exportar también los documentos adjuntos a los seleccionados",
|
||||
"ExportResultRecordTitle": "Exportación de documentos del espacio de trabajo {workspace} ({date})"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"ExportFilterEffectiveOnly": "Seulement les documents effectifs",
|
||||
"ExportFilterSkipArchivedObsolete": "Tous sauf archivés et obsolètes",
|
||||
"ExportFilterAll": "Tous les documents",
|
||||
"ExportChildDocuments": "Exporter les documents enfants",
|
||||
"ExportChildDocumentsDescription": "Exporter aussi les documents rattachés aux éléments sélectionnés",
|
||||
"ExportResultRecordTitle": "Export de documents depuis l'espace de travail {workspace} ({date})"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"ExportFilterEffectiveOnly": "Solo documenti effettivi",
|
||||
"ExportFilterSkipArchivedObsolete": "Tutti tranne archiviati e obsoleti",
|
||||
"ExportFilterAll": "Tutti i documenti",
|
||||
"ExportChildDocuments": "Esporta documenti figlio",
|
||||
"ExportChildDocumentsDescription": "Esporta anche i documenti collegati a quelli selezionati",
|
||||
"ExportResultRecordTitle": "Esportazione di documenti dallo spazio di lavoro {workspace} ({date})"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"ExportFilterEffectiveOnly": "有効なドキュメントのみ",
|
||||
"ExportFilterSkipArchivedObsolete": "アーカイブ・廃止を除くすべて",
|
||||
"ExportFilterAll": "すべてのドキュメント",
|
||||
"ExportChildDocuments": "子ドキュメントをエクスポート",
|
||||
"ExportChildDocumentsDescription": "選択したドキュメントに添付されたドキュメントもエクスポートします",
|
||||
"ExportResultRecordTitle": "ワークスペース {workspace} からのドキュメントのエクスポート ({date})"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"ExportFilterEffectiveOnly": "유효한 문서만",
|
||||
"ExportFilterSkipArchivedObsolete": "보관/폐기 제외 전체",
|
||||
"ExportFilterAll": "모든 문서",
|
||||
"ExportChildDocuments": "하위 문서 내보내기",
|
||||
"ExportChildDocumentsDescription": "선택한 문서에 첨부된 문서도 함께 내보냅니다",
|
||||
"ExportResultRecordTitle": "{workspace} 워크스페이스 문서 내보내기 ({date})"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
"ExportFilterEffectiveOnly": "Apenas documentos efetivos",
|
||||
"ExportFilterSkipArchivedObsolete": "Todos exceto arquivados e obsoletos",
|
||||
"ExportFilterAll": "Todos os documentos",
|
||||
"ExportChildDocuments": "Exportar documentos filhos",
|
||||
"ExportChildDocumentsDescription": "Exportar também os documentos anexados aos selecionados",
|
||||
"ExportResultRecordTitle": "Exportação de documentos do espaço de trabalho {workspace} ({date})"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"ExportFilterEffectiveOnly": "Apenas documentos efetivos",
|
||||
"ExportFilterSkipArchivedObsolete": "Todos exceto arquivados e obsoletos",
|
||||
"ExportFilterAll": "Todos os documentos",
|
||||
"ExportChildDocuments": "Exportar documentos filhos",
|
||||
"ExportChildDocumentsDescription": "Exportar também os documentos anexados aos selecionados",
|
||||
"ExportResultRecordTitle": "Exportação de documentos do espaço de trabalho {workspace} ({date})"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"ExportFilterEffectiveOnly": "Только действующие документы",
|
||||
"ExportFilterSkipArchivedObsolete": "Все кроме архивных и устаревших",
|
||||
"ExportFilterAll": "Все документы",
|
||||
"ExportChildDocuments": "Экспортировать дочерние документы",
|
||||
"ExportChildDocumentsDescription": "Также экспортировать документы, прикреплённые к выбранным",
|
||||
"ExportResultRecordTitle": "Экспорт документов из пространства {workspace} ({date})"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"ExportFilterEffectiveOnly": "Yalnızca geçerli belgeler",
|
||||
"ExportFilterSkipArchivedObsolete": "Arşivlenmiş ve geçersiz hariç tümü",
|
||||
"ExportFilterAll": "Tüm belgeler",
|
||||
"ExportChildDocuments": "Alt belgeleri dışa aktar",
|
||||
"ExportChildDocumentsDescription": "Seçili belgelere ekli belgeleri de dışa aktar",
|
||||
"ExportResultRecordTitle": "{workspace} çalışma alanından belge dışa aktarma ({date})"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"ExportFilterEffectiveOnly": "仅有效文档",
|
||||
"ExportFilterSkipArchivedObsolete": "除已归档和已过时外的全部",
|
||||
"ExportFilterAll": "全部文档",
|
||||
"ExportChildDocuments": "导出子文档",
|
||||
"ExportChildDocumentsDescription": "同时导出附加到所选文档上的文档",
|
||||
"ExportResultRecordTitle": "从工作区 {workspace} 导出文档 ({date})"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
type Class
|
||||
} from '@hcengineering/core'
|
||||
import { Card, getClient, getCurrentWorkspaceUuid } from '@hcengineering/presentation'
|
||||
import { DropdownLabels, DropdownLabelsIntl, Label } from '@hcengineering/ui'
|
||||
import { DropdownLabels, DropdownLabelsIntl, Label, ToggleWithLabel } from '@hcengineering/ui'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import login from '@hcengineering/login'
|
||||
import { shouldSkipDocument, isEffectiveDocument } from '@hcengineering/export'
|
||||
@@ -52,6 +52,11 @@
|
||||
type ExportFilterMode = 'effectiveOnly' | 'skipArchivedObsolete' | 'all'
|
||||
let exportFilterMode: ExportFilterMode = 'effectiveOnly'
|
||||
|
||||
// Whether to recursively export child (collection) documents of the selected docs.
|
||||
// Hidden and forced to true when exporting an entire space — the space already
|
||||
// enumerates every document inside it.
|
||||
let includeChildren: boolean = false
|
||||
|
||||
const exportFilterItems = [
|
||||
{ id: 'effectiveOnly' as const, label: plugin.string.ExportFilterEffectiveOnly },
|
||||
{ id: 'skipArchivedObsolete' as const, label: plugin.string.ExportFilterSkipArchivedObsolete },
|
||||
@@ -142,6 +147,10 @@
|
||||
|
||||
const effectiveDocs = projectDocExport === true ? await getExportDocuments() : filteredSelectedDocs
|
||||
|
||||
// When exporting an entire space, child documents are picked up by the
|
||||
// space-wide traversal — force the flag on regardless of UI state.
|
||||
const effectiveIncludeChildren = spaceExport === true ? true : includeChildren
|
||||
|
||||
void exportToWorkspace(
|
||||
_class,
|
||||
exportQuery,
|
||||
@@ -149,7 +158,8 @@
|
||||
targetWorkspace,
|
||||
undefined,
|
||||
exportFilterMode === 'skipArchivedObsolete',
|
||||
exportFilterMode === 'effectiveOnly'
|
||||
exportFilterMode === 'effectiveOnly',
|
||||
effectiveIncludeChildren
|
||||
)
|
||||
loading = false
|
||||
dispatch('close', true)
|
||||
@@ -191,5 +201,14 @@
|
||||
<Label label={plugin.string.ExportFilterMode} />
|
||||
</span>
|
||||
<DropdownLabelsIntl items={exportFilterItems} bind:selected={exportFilterMode} kind="regular" size="large" />
|
||||
{#if spaceExport !== true}
|
||||
<div class="pl-2 py-4">
|
||||
<ToggleWithLabel
|
||||
label={plugin.string.ExportChildDocuments}
|
||||
description={plugin.string.ExportChildDocumentsDescription}
|
||||
bind:on={includeChildren}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -26,7 +26,8 @@ export async function exportToWorkspace (
|
||||
targetWorkspace: string | undefined,
|
||||
relations?: RelationDefinition[] | undefined,
|
||||
skipDeletedObsolete?: boolean,
|
||||
exportOnlyEffective?: boolean
|
||||
exportOnlyEffective?: boolean,
|
||||
includeChildren?: boolean
|
||||
): Promise<void> {
|
||||
const lang = getCurrentLanguage()
|
||||
|
||||
@@ -73,7 +74,8 @@ export async function exportToWorkspace (
|
||||
_class,
|
||||
fieldMappers,
|
||||
skipDeletedObsolete,
|
||||
exportOnlyEffective
|
||||
exportOnlyEffective,
|
||||
includeChildren
|
||||
}
|
||||
if (relations != null) {
|
||||
body.relations = relations
|
||||
|
||||
@@ -56,6 +56,8 @@ export default mergeIds(exportId, exportPlugin, {
|
||||
ExportFilterMode: '' as IntlString,
|
||||
ExportFilterEffectiveOnly: '' as IntlString,
|
||||
ExportFilterSkipArchivedObsolete: '' as IntlString,
|
||||
ExportFilterAll: '' as IntlString
|
||||
ExportFilterAll: '' as IntlString,
|
||||
ExportChildDocuments: '' as IntlString,
|
||||
ExportChildDocumentsDescription: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -127,7 +127,8 @@ describe('RelationExporter sourceClass', () => {
|
||||
hierarchy,
|
||||
lowLevel,
|
||||
expect.any(Map),
|
||||
relations
|
||||
relations,
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
@@ -178,7 +179,8 @@ describe('RelationExporter sourceClass', () => {
|
||||
hierarchy,
|
||||
lowLevel,
|
||||
expect.any(Map),
|
||||
relations
|
||||
relations,
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
@@ -219,7 +221,8 @@ describe('RelationExporter sourceClass', () => {
|
||||
hierarchy,
|
||||
lowLevel,
|
||||
expect.any(Map),
|
||||
relations
|
||||
relations,
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -541,6 +541,149 @@ describe('CrossWorkspaceExporter', () => {
|
||||
expect(attachedToArg).not.toBe(SOURCE_DOC_1)
|
||||
}
|
||||
})
|
||||
|
||||
it('should NOT export child collection documents when includeChildren is false', async () => {
|
||||
const sourceSpace = createMockSpace(SOURCE_SPACE_ID, 'Test Space')
|
||||
const parentDoc = createMockDoc(SOURCE_DOC_1, mockDocClass, SOURCE_SPACE_ID)
|
||||
const attachedDoc = createMockAttachedDoc(
|
||||
SOURCE_ATTACHED_1,
|
||||
mockAttachedDocClass,
|
||||
SOURCE_SPACE_ID,
|
||||
SOURCE_DOC_1,
|
||||
mockDocClass,
|
||||
'children',
|
||||
{ title: 'Attached Doc' }
|
||||
)
|
||||
|
||||
const hierarchy = createMockHierarchy({
|
||||
domains: new Map([
|
||||
[mockDocClass, 'test_domain'],
|
||||
[mockAttachedDocClass, 'test_domain'],
|
||||
[core.class.Space, 'space_domain']
|
||||
]),
|
||||
isDerived: new Map([[mockAttachedDocClass, true]]),
|
||||
attributes: new Map([
|
||||
[
|
||||
mockDocClass,
|
||||
new Map([['children', { type: { _class: core.class.Collection, of: mockAttachedDocClass } }]])
|
||||
],
|
||||
[
|
||||
mockAttachedDocClass,
|
||||
new Map([
|
||||
['attachedTo', { type: { _class: core.class.RefTo } }],
|
||||
['attachedToClass', { type: { _class: 'core:class:TypeString' as Ref<Class<Doc>> } }],
|
||||
['collection', { type: { _class: 'core:class:TypeString' as Ref<Class<Doc>> } }]
|
||||
])
|
||||
]
|
||||
])
|
||||
})
|
||||
|
||||
const lowLevelStorage = createMockLowLevelStorage(
|
||||
new Map([
|
||||
['test_domain', [parentDoc, attachedDoc]],
|
||||
['space_domain', [sourceSpace]]
|
||||
])
|
||||
)
|
||||
|
||||
const targetClient = createMockTxOperations([], [], hierarchy)
|
||||
const pipelineFactory = createMockPipelineFactory(hierarchy, lowLevelStorage)
|
||||
|
||||
const exporter = new CrossWorkspaceExporter(
|
||||
mockContext,
|
||||
pipelineFactory,
|
||||
targetClient,
|
||||
mockStorage,
|
||||
undefined,
|
||||
createWorkspaceIds('source-ws'),
|
||||
createWorkspaceIds('target-ws')
|
||||
)
|
||||
|
||||
// Query the parent class only — and explicitly opt OUT of child export.
|
||||
const result = await exporter.export({
|
||||
sourceWorkspace: createWorkspaceIds('source-ws'),
|
||||
targetWorkspace: createWorkspaceIds('target-ws'),
|
||||
sourceQuery: { _id: SOURCE_DOC_1 } as any,
|
||||
_class: mockDocClass,
|
||||
includeChildren: false
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
// The parent doc must be created via createDoc, but the attached collection
|
||||
// item must NOT go through addCollection because includeChildren is false.
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
expect((targetClient.addCollection as jest.Mock).mock.calls.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should export child collection documents when includeChildren is true', async () => {
|
||||
const sourceSpace = createMockSpace(SOURCE_SPACE_ID, 'Test Space')
|
||||
const parentDoc = createMockDoc(SOURCE_DOC_1, mockDocClass, SOURCE_SPACE_ID)
|
||||
const attachedDoc = createMockAttachedDoc(
|
||||
SOURCE_ATTACHED_1,
|
||||
mockAttachedDocClass,
|
||||
SOURCE_SPACE_ID,
|
||||
SOURCE_DOC_1,
|
||||
mockDocClass,
|
||||
'children',
|
||||
{ title: 'Attached Doc' }
|
||||
)
|
||||
|
||||
const hierarchy = createMockHierarchy({
|
||||
domains: new Map([
|
||||
[mockDocClass, 'test_domain'],
|
||||
[mockAttachedDocClass, 'test_domain'],
|
||||
[core.class.Space, 'space_domain']
|
||||
]),
|
||||
isDerived: new Map([[mockAttachedDocClass, true]]),
|
||||
attributes: new Map([
|
||||
[
|
||||
mockDocClass,
|
||||
new Map([['children', { type: { _class: core.class.Collection, of: mockAttachedDocClass } }]])
|
||||
],
|
||||
[
|
||||
mockAttachedDocClass,
|
||||
new Map([
|
||||
['attachedTo', { type: { _class: core.class.RefTo } }],
|
||||
['attachedToClass', { type: { _class: 'core:class:TypeString' as Ref<Class<Doc>> } }],
|
||||
['collection', { type: { _class: 'core:class:TypeString' as Ref<Class<Doc>> } }]
|
||||
])
|
||||
]
|
||||
])
|
||||
})
|
||||
|
||||
const lowLevelStorage = createMockLowLevelStorage(
|
||||
new Map([
|
||||
['test_domain', [parentDoc, attachedDoc]],
|
||||
['space_domain', [sourceSpace]]
|
||||
])
|
||||
)
|
||||
|
||||
const targetClient = createMockTxOperations([], [], hierarchy)
|
||||
const pipelineFactory = createMockPipelineFactory(hierarchy, lowLevelStorage)
|
||||
|
||||
const exporter = new CrossWorkspaceExporter(
|
||||
mockContext,
|
||||
pipelineFactory,
|
||||
targetClient,
|
||||
mockStorage,
|
||||
undefined,
|
||||
createWorkspaceIds('source-ws'),
|
||||
createWorkspaceIds('target-ws')
|
||||
)
|
||||
|
||||
// Query the parent class only — and opt IN to recursive child export.
|
||||
const result = await exporter.export({
|
||||
sourceWorkspace: createWorkspaceIds('source-ws'),
|
||||
targetWorkspace: createWorkspaceIds('target-ws'),
|
||||
sourceQuery: { _id: SOURCE_DOC_1 } as any,
|
||||
_class: mockDocClass,
|
||||
includeChildren: true
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
// With the flag on, the attached collection item must be created via addCollection.
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
expect((targetClient.addCollection as jest.Mock).mock.calls.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Forward Relations', () => {
|
||||
|
||||
@@ -414,7 +414,8 @@ export function createServer (
|
||||
relations: rawRelations,
|
||||
fieldMappers,
|
||||
skipDeletedObsolete,
|
||||
exportOnlyEffective
|
||||
exportOnlyEffective,
|
||||
includeChildren
|
||||
}: {
|
||||
targetWorkspace: WorkspaceUuid
|
||||
_class: Ref<Class<Doc>>
|
||||
@@ -427,6 +428,7 @@ export function createServer (
|
||||
fieldMappers?: Record<string, Record<string, any>>
|
||||
skipDeletedObsolete?: boolean
|
||||
exportOnlyEffective?: boolean
|
||||
includeChildren?: boolean
|
||||
} = req.body
|
||||
|
||||
// Validate required parameters
|
||||
@@ -543,6 +545,7 @@ export function createServer (
|
||||
fieldMappers,
|
||||
skipDeletedObsolete: skipDeletedObsolete ?? true,
|
||||
exportOnlyEffective: exportOnlyEffective ?? false,
|
||||
includeChildren: includeChildren ?? false,
|
||||
customHandlers: [createProductVersionHandler()]
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,8 @@ export class DocumentExporter {
|
||||
sourceHierarchy: Hierarchy,
|
||||
sourceLowLevel: LowLevelStorage,
|
||||
existingDocsMap: Map<Ref<Doc>, Doc>,
|
||||
relations: RelationDefinition[]
|
||||
relations: RelationDefinition[],
|
||||
includeChildren: boolean = false
|
||||
): Promise<boolean> {
|
||||
if (this.state.processingDocs.has(doc._id)) {
|
||||
return false
|
||||
@@ -127,7 +128,8 @@ export class DocumentExporter {
|
||||
conflictStrategy,
|
||||
includeAttachments,
|
||||
sourceHierarchy,
|
||||
sourceLowLevel
|
||||
sourceLowLevel,
|
||||
includeChildren
|
||||
)
|
||||
|
||||
// Create the document
|
||||
@@ -145,7 +147,8 @@ export class DocumentExporter {
|
||||
conflictStrategy,
|
||||
includeAttachments,
|
||||
sourceHierarchy,
|
||||
sourceLowLevel
|
||||
sourceLowLevel,
|
||||
includeChildren
|
||||
)
|
||||
await this.exportSpaceRelations(
|
||||
doc,
|
||||
@@ -154,7 +157,8 @@ export class DocumentExporter {
|
||||
includeAttachments,
|
||||
sourceHierarchy,
|
||||
sourceLowLevel,
|
||||
relations
|
||||
relations,
|
||||
includeChildren
|
||||
)
|
||||
|
||||
// Handle attachments
|
||||
@@ -165,8 +169,12 @@ export class DocumentExporter {
|
||||
// Handle collaborative content blobs (e.g., document content)
|
||||
await this.attachmentExporter.exportCollaborativeContent(doc, sourceHierarchy)
|
||||
|
||||
// Handle collections (child documents)
|
||||
await this.exportCollections(doc, targetId, sourceHierarchy, sourceLowLevel, relations)
|
||||
// Handle collections (child documents) — only when explicitly requested.
|
||||
// When disabled, only the top-level documents matched by the export query
|
||||
// are exported and their attached collection items are skipped.
|
||||
if (includeChildren) {
|
||||
await this.exportCollections(doc, targetId, sourceHierarchy, sourceLowLevel, relations, includeChildren)
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (err: any) {
|
||||
@@ -230,7 +238,8 @@ export class DocumentExporter {
|
||||
includeAttachments: boolean,
|
||||
sourceHierarchy: Hierarchy,
|
||||
sourceLowLevel: LowLevelStorage,
|
||||
relations: RelationDefinition[]
|
||||
relations: RelationDefinition[],
|
||||
includeChildren: boolean
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (this.relationExporter === undefined) {
|
||||
@@ -256,7 +265,8 @@ export class DocumentExporter {
|
||||
conflictStrategy,
|
||||
includeAttachments,
|
||||
sourceHierarchy,
|
||||
sourceLowLevel
|
||||
sourceLowLevel,
|
||||
includeChildren
|
||||
)
|
||||
} catch (err: any) {
|
||||
this.context.error(`Failed to export relations for space ${space}:`, {
|
||||
@@ -310,7 +320,8 @@ export class DocumentExporter {
|
||||
targetDocId: Ref<Doc>,
|
||||
sourceHierarchy: Hierarchy,
|
||||
sourceLowLevel: LowLevelStorage,
|
||||
relations: RelationDefinition[]
|
||||
relations: RelationDefinition[],
|
||||
includeChildren: boolean
|
||||
): Promise<void> {
|
||||
const attributes = sourceHierarchy.getAllAttributes(sourceDoc._class)
|
||||
|
||||
@@ -349,7 +360,8 @@ export class DocumentExporter {
|
||||
sourceHierarchy,
|
||||
sourceLowLevel,
|
||||
new Map(),
|
||||
relations
|
||||
relations,
|
||||
includeChildren
|
||||
)
|
||||
} catch (err: any) {
|
||||
this.context.error(`Failed to export collection item ${collectionDoc._id}:`, {
|
||||
|
||||
@@ -34,7 +34,8 @@ export type ExportDocumentFn = (
|
||||
sourceHierarchy: Hierarchy,
|
||||
sourceLowLevel: LowLevelStorage,
|
||||
existingDocsMap: Map<Ref<Doc>, Doc>,
|
||||
relations: RelationDefinition[]
|
||||
relations: RelationDefinition[],
|
||||
includeChildren: boolean
|
||||
) => Promise<boolean>
|
||||
|
||||
/**
|
||||
@@ -56,7 +57,8 @@ export class RelationExporter {
|
||||
conflictStrategy: 'skip' | 'duplicate',
|
||||
includeAttachments: boolean,
|
||||
sourceHierarchy: Hierarchy,
|
||||
sourceLowLevel: LowLevelStorage
|
||||
sourceLowLevel: LowLevelStorage,
|
||||
includeChildren: boolean = false
|
||||
): Promise<void> {
|
||||
for (const relation of relations) {
|
||||
const direction = relation.direction ?? 'forward'
|
||||
@@ -71,7 +73,8 @@ export class RelationExporter {
|
||||
includeAttachments,
|
||||
sourceHierarchy,
|
||||
sourceLowLevel,
|
||||
relations
|
||||
relations,
|
||||
includeChildren
|
||||
)
|
||||
} catch (err: any) {
|
||||
this.context.error(`Failed to export forward relation ${relation.field} for document ${doc._id}:`, {
|
||||
@@ -92,7 +95,8 @@ export class RelationExporter {
|
||||
conflictStrategy: 'skip' | 'duplicate',
|
||||
includeAttachments: boolean,
|
||||
sourceHierarchy: Hierarchy,
|
||||
sourceLowLevel: LowLevelStorage
|
||||
sourceLowLevel: LowLevelStorage,
|
||||
includeChildren: boolean = false
|
||||
): Promise<void> {
|
||||
for (const relation of relations) {
|
||||
const direction = relation.direction ?? 'forward'
|
||||
@@ -107,7 +111,8 @@ export class RelationExporter {
|
||||
includeAttachments,
|
||||
sourceHierarchy,
|
||||
sourceLowLevel,
|
||||
relations
|
||||
relations,
|
||||
includeChildren
|
||||
)
|
||||
} catch (err: any) {
|
||||
this.context.error(`Failed to export inverse relation ${relation.field} for document ${doc._id}:`, {
|
||||
@@ -125,7 +130,8 @@ export class RelationExporter {
|
||||
conflictStrategy: 'skip' | 'duplicate',
|
||||
includeAttachments: boolean,
|
||||
sourceHierarchy: Hierarchy,
|
||||
sourceLowLevel: LowLevelStorage
|
||||
sourceLowLevel: LowLevelStorage,
|
||||
includeChildren: boolean = false
|
||||
): Promise<void> {
|
||||
await this.exportForwardRelations(
|
||||
doc,
|
||||
@@ -133,7 +139,8 @@ export class RelationExporter {
|
||||
conflictStrategy,
|
||||
includeAttachments,
|
||||
sourceHierarchy,
|
||||
sourceLowLevel
|
||||
sourceLowLevel,
|
||||
includeChildren
|
||||
)
|
||||
await this.exportInverseRelations(
|
||||
doc,
|
||||
@@ -141,7 +148,8 @@ export class RelationExporter {
|
||||
conflictStrategy,
|
||||
includeAttachments,
|
||||
sourceHierarchy,
|
||||
sourceLowLevel
|
||||
sourceLowLevel,
|
||||
includeChildren
|
||||
)
|
||||
}
|
||||
|
||||
@@ -152,7 +160,8 @@ export class RelationExporter {
|
||||
includeAttachments: boolean,
|
||||
sourceHierarchy: Hierarchy,
|
||||
sourceLowLevel: LowLevelStorage,
|
||||
relations: RelationDefinition[]
|
||||
relations: RelationDefinition[],
|
||||
includeChildren: boolean
|
||||
): Promise<void> {
|
||||
if (relation.sourceClass !== undefined && !sourceHierarchy.isDerived(doc._class, relation.sourceClass)) {
|
||||
return
|
||||
@@ -185,7 +194,8 @@ export class RelationExporter {
|
||||
includeAttachments,
|
||||
sourceHierarchy,
|
||||
sourceLowLevel,
|
||||
relations
|
||||
relations,
|
||||
includeChildren
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -197,7 +207,8 @@ export class RelationExporter {
|
||||
includeAttachments: boolean,
|
||||
sourceHierarchy: Hierarchy,
|
||||
sourceLowLevel: LowLevelStorage,
|
||||
relations: RelationDefinition[]
|
||||
relations: RelationDefinition[],
|
||||
includeChildren: boolean
|
||||
): Promise<void> {
|
||||
if (relation.sourceClass !== undefined && !sourceHierarchy.isDerived(doc._class, relation.sourceClass)) {
|
||||
return
|
||||
@@ -232,7 +243,8 @@ export class RelationExporter {
|
||||
sourceHierarchy,
|
||||
sourceLowLevel,
|
||||
new Map(),
|
||||
relations
|
||||
relations,
|
||||
includeChildren
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -244,7 +256,8 @@ export class RelationExporter {
|
||||
includeAttachments: boolean,
|
||||
sourceHierarchy: Hierarchy,
|
||||
sourceLowLevel: LowLevelStorage,
|
||||
relations: RelationDefinition[]
|
||||
relations: RelationDefinition[],
|
||||
includeChildren: boolean
|
||||
): Promise<void> {
|
||||
if (this.state.idMapping.has(ref)) {
|
||||
return
|
||||
@@ -274,7 +287,8 @@ export class RelationExporter {
|
||||
sourceHierarchy,
|
||||
sourceLowLevel,
|
||||
new Map(),
|
||||
relations
|
||||
relations,
|
||||
includeChildren
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,10 @@ export interface ExportOptions {
|
||||
skipDeletedObsolete?: boolean
|
||||
// Whether to export only documents with effective status
|
||||
exportOnlyEffective?: boolean
|
||||
// Whether to recursively export child (collection) documents of each exported doc.
|
||||
// When false, only the documents matched by the top-level query are exported and
|
||||
// their attached collection items are skipped.
|
||||
includeChildren?: boolean
|
||||
// Class-specific export handlers, applied before the default flow. Useful
|
||||
// for collapsing or deduplicating documents (e.g. ProductVersion).
|
||||
customHandlers?: CustomExportHandler[]
|
||||
|
||||
@@ -98,7 +98,8 @@ export class CrossWorkspaceExporter {
|
||||
sourceHierarchy,
|
||||
sourceLowLevel,
|
||||
existingDocsMap,
|
||||
relations
|
||||
relations,
|
||||
includeChildren
|
||||
) => {
|
||||
return await this.documentExporter.exportDocument(
|
||||
doc,
|
||||
@@ -107,7 +108,8 @@ export class CrossWorkspaceExporter {
|
||||
sourceHierarchy,
|
||||
sourceLowLevel,
|
||||
existingDocsMap,
|
||||
relations
|
||||
relations,
|
||||
includeChildren
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -128,6 +130,7 @@ export class CrossWorkspaceExporter {
|
||||
fieldMappers = {},
|
||||
skipDeletedObsolete = true,
|
||||
exportOnlyEffective = false,
|
||||
includeChildren = false,
|
||||
customHandlers = []
|
||||
} = options
|
||||
|
||||
@@ -258,7 +261,8 @@ export class CrossWorkspaceExporter {
|
||||
hierarchy,
|
||||
lowLevelStorage,
|
||||
existingDocsMap,
|
||||
resolvedRelations
|
||||
resolvedRelations,
|
||||
includeChildren
|
||||
)
|
||||
if (exported) {
|
||||
result.exportedCount++
|
||||
|
||||
Reference in New Issue
Block a user