Skip to content

Commit 4dcf628

Browse files
committed
feat: add force flag to import omitted fields [#62]
1 parent ac5c3fe commit 4dcf628

File tree

8 files changed

+94
-41
lines changed

8 files changed

+94
-41
lines changed

lib/index.ts

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import UpdateRenderer from 'listr-update-renderer'
66
import VerboseRenderer from 'listr-verbose-renderer'
77
import { startCase } from 'lodash'
88
import PQueue from 'p-queue'
9-
9+
import { pipe } from 'lodash/fp'
1010
import { displayErrorLog, setupLogging, writeErrorLogFile } from 'contentful-batch-libs/dist/logging'
1111
import { wrapTask } from 'contentful-batch-libs/dist/listr'
1212

@@ -17,6 +17,9 @@ import transformSpace from './transform/transform-space'
1717
import { assertDefaultLocale, assertPayload } from './utils/validations'
1818
import parseOptions from './parseOptions'
1919
import { ContentfulMultiError, LogItem } from './utils/errors'
20+
import { transformers } from './transform/transformers'
21+
import { ContentTypeProps } from 'contentful-management'
22+
import { forceDeleteOmittedFieldTransform } from './transform/force-delete-omitted-field-transform'
2023

2124
const ONE_SECOND = 1000
2225

@@ -34,28 +37,29 @@ function createListrOptions (options) {
3437

3538
// These type definitions follow what is specified in the Readme
3639
type RunContentfulImportParams = {
37-
spaceId: string,
38-
environmentId?: string,
39-
managementToken: string,
40-
contentFile?: string,
41-
content?: object,
42-
contentModelOnly?: boolean,
43-
skipContentModel?: boolean,
44-
skipLocales?: boolean,
45-
skipContentPublishing?: boolean,
46-
uploadAssets?: boolean,
47-
assetsDirectory?: string,
48-
host?: string,
49-
proxy?: string,
50-
rawProxy?: string,
51-
rateLimit?: number,
52-
headers?: object,
53-
errorLogFile?: string,
54-
useVerboseRenderer?: boolean,
55-
// TODO These properties are not documented in the Readme
56-
timeout?: number,
57-
retryLimit?: number,
58-
config?: string,
40+
spaceId: string,
41+
environmentId?: string,
42+
managementToken: string,
43+
contentFile?: string,
44+
content?: object,
45+
contentModelOnly?: boolean,
46+
skipContentModel?: boolean,
47+
skipLocales?: boolean,
48+
skipContentPublishing?: boolean,
49+
uploadAssets?: boolean,
50+
assetsDirectory?: string,
51+
host?: string,
52+
proxy?: string,
53+
rawProxy?: string,
54+
rateLimit?: number,
55+
headers?: object,
56+
errorLogFile?: string,
57+
useVerboseRenderer?: boolean,
58+
// TODO These properties are not documented in the Readme
59+
timeout?: number,
60+
retryLimit?: number,
61+
config?: string,
62+
force?: boolean,
5963
}
6064

6165
async function runContentfulImport (params: RunContentfulImportParams) {
@@ -128,8 +132,12 @@ async function runContentfulImport (params: RunContentfulImportParams) {
128132
{
129133
title: 'Apply transformations to source data',
130134
task: wrapTask(async (ctx) => {
131-
const transformedSourceData = transformSpace(ctx.sourceDataUntransformed, ctx.destinationData)
132-
ctx.sourceData = transformedSourceData
135+
const customTransformers: Partial<typeof transformers> = {}
136+
if (options.force) {
137+
customTransformers.contentTypes = (contentType: ContentTypeProps) => pipe(transformers.contentTypes, forceDeleteOmittedFieldTransform)(contentType)
138+
}
139+
140+
ctx.sourceData = transformSpace(ctx.sourceDataUntransformed, ctx.destinationData, customTransformers)
133141
})
134142
},
135143
{
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ContentTypeProps } from 'contentful-management'
2+
3+
export function forceDeleteOmittedFieldTransform (contentType: ContentTypeProps) {
4+
const omittedFields = contentType.fields.filter(field => field.omitted)
5+
omittedFields.forEach(field => {
6+
contentType.fields = contentType.fields.filter(f => f.id !== field.id)
7+
})
8+
return contentType
9+
}

lib/transform/transform-space.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { omit, defaults } from 'lodash/object'
1+
import { omit, defaults } from 'lodash'
22

3-
import * as defaultTransformers from './transformers'
3+
import { transformers as defaultTransformers } from './transformers'
44
import sortEntries from '../utils/sort-entries'
55
import sortLocales from '../utils/sort-locales'
66
import { DestinationData, OriginalSourceData, TransformedSourceData } from '../types'
@@ -14,14 +14,16 @@ const spaceEntities = [
1414
* is a need to transform data when copying it to the destination space
1515
*/
1616
export default function (
17-
sourceData: OriginalSourceData, destinationData: DestinationData, customTransformers?: any, entities = spaceEntities
17+
sourceData: OriginalSourceData, destinationData: DestinationData, customTransformers?: Partial<typeof defaultTransformers>, entities = spaceEntities
1818
): TransformedSourceData {
1919
const transformers = defaults(customTransformers, defaultTransformers)
2020
const baseSpaceData = omit(sourceData, ...entities)
2121

2222
sourceData.locales = sortLocales(sourceData.locales)
2323
const tagsEnabled = !!destinationData.tags
2424

25+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
26+
// @ts-ignore
2527
return entities.reduce((transformedSpaceData, type) => {
2628
// tags don't contain links to other entities, don't need to be sorted
2729
const sortedEntities = (type === 'tags') ? sourceData[type] : sortEntries(sourceData[type])

lib/transform/transformers.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { ContentTypeProps, EntryProps, TagProps, WebhookProps } from 'contentful-management'
2-
import { find, omit, pick, reduce } from 'lodash'
1+
import { omit, pick, find, reduce } from 'lodash'
2+
import { AssetProps, ContentTypeProps, EntryProps, LocaleProps, TagProps, WebhookProps } from 'contentful-management'
3+
import { MetadataProps } from 'contentful-management/dist/typings/common-types'
34

45
/**
56
* Default transformer methods for each kind of entity.
@@ -8,19 +9,19 @@ import { find, omit, pick, reduce } from 'lodash'
89
* as the whole upload process needs to be followed again.
910
*/
1011

11-
export function contentTypes (contentType: ContentTypeProps) {
12+
function contentTypes (contentType: ContentTypeProps) {
1213
return contentType
1314
}
1415

15-
export function tags (tag: TagProps) {
16+
function tags (tag: TagProps) {
1617
return tag
1718
}
1819

19-
export function entries (entry: EntryProps, _, tagsEnabled = false) {
20+
function entries (entry: EntryProps, _, tagsEnabled = false) {
2021
return removeMetadataTags(entry, tagsEnabled)
2122
}
2223

23-
export function webhooks (webhook: WebhookProps) {
24+
function webhooks (webhook: WebhookProps) {
2425
// Workaround for webhooks with credentials
2526
if (webhook.httpBasicUsername) {
2627
delete webhook.httpBasicUsername
@@ -34,17 +35,21 @@ export function webhooks (webhook: WebhookProps) {
3435
return webhook
3536
}
3637

37-
export function assets (asset, _, tagsEnabled = false) {
38+
function assets (asset: AssetProps, _, tagsEnabled = false) {
3839
const transformedAsset = omit(asset, 'sys')
40+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
41+
// @ts-ignore
3942
transformedAsset.sys = pick(asset.sys, 'id')
43+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
44+
// @ts-ignore
4045
transformedAsset.fields = pick(asset.fields, 'title', 'description')
4146
transformedAsset.fields.file = reduce(
4247
asset.fields.file,
4348
(newFile, localizedFile, locale) => {
4449
newFile[locale] = pick(localizedFile, 'contentType', 'fileName')
4550
if (!localizedFile.uploadFrom) {
4651
const assetUrl = localizedFile.url || localizedFile.upload
47-
newFile[locale].upload = `${/^(http|https):\/\//i.test(assetUrl) ? '' : 'https:'}${assetUrl}`
52+
newFile[locale].upload = `${/^(http|https):\/\//i.test(assetUrl!) ? '' : 'https:'}${assetUrl}`
4853
} else {
4954
newFile[locale].uploadFrom = localizedFile.uploadFrom
5055
}
@@ -55,7 +60,7 @@ export function assets (asset, _, tagsEnabled = false) {
5560
return removeMetadataTags(transformedAsset, tagsEnabled)
5661
}
5762

58-
export function locales (locale, destinationLocales) {
63+
function locales (locale: LocaleProps, destinationLocales: Array<LocaleProps>): LocaleProps {
5964
const transformedLocale = pick(locale, 'code', 'name', 'contentManagementApi', 'contentDeliveryApi', 'fallbackCode', 'optional')
6065
const destinationLocale = find(destinationLocales, { code: locale.code })
6166
if (destinationLocale) {
@@ -66,12 +71,23 @@ export function locales (locale, destinationLocales) {
6671
transformedLocale.sys = pick(destinationLocale.sys, 'id')
6772
}
6873

74+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
75+
// @ts-ignore
6976
return transformedLocale
7077
}
7178

72-
function removeMetadataTags (entity, tagsEnabled = false) {
79+
function removeMetadataTags<T extends { metadata?: MetadataProps }> (entity: T, tagsEnabled = false): T {
7380
if (!tagsEnabled) {
7481
delete entity.metadata
7582
}
7683
return entity
7784
}
85+
86+
export const transformers = {
87+
contentTypes,
88+
tags,
89+
entries,
90+
webhooks,
91+
assets,
92+
locales
93+
}

lib/usageParams.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,10 @@ export default yargs
8484
type: 'string',
8585
describe: 'Pass an additional HTTP Header'
8686
})
87+
.option('force', {
88+
describe: 'force omitted fields to be deleted before importing',
89+
type: 'boolean',
90+
default: false
91+
})
8792
.config('config', 'An optional configuration JSON file containing all the options for a single run')
8893
.argv
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { cloneMock } from 'contentful-batch-libs/test/mocks/'
2+
import { ContentTypeProps } from 'contentful-management'
3+
import { forceDeleteOmittedFieldTransform } from '../../../lib/transform/force-delete-omitted-field-transform'
4+
5+
test('It should transform content types by dropping omitted fields', () => {
6+
const contentTypeMock = cloneMock('contentType') as ContentTypeProps
7+
contentTypeMock.fields[0].omitted = true
8+
expect(contentTypeMock.fields).toHaveLength(1)
9+
const transformedContentTypeMock = forceDeleteOmittedFieldTransform(contentTypeMock)
10+
expect(transformedContentTypeMock.fields).toHaveLength(0)
11+
})

test/unit/transform/transform-space.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ test('applies transformers to give space data', () => {
6060

6161
test('applies custom transformers to give space data', () => {
6262
const result = transformSpace(space, destinationSpace, {
63+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
64+
// @ts-ignore
6365
entries: () => 'transformed'
6466
})
6567
expect(result.entries?.[0]?.transformed).toBe('transformed')

test/unit/transform/transformers.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { cloneMock } from 'contentful-batch-libs/test/mocks/'
22

3-
import * as transformers from '../../../lib/transform/transformers'
3+
import { transformers } from '../../../lib/transform/transformers'
44

55
const _ = {}
66

@@ -60,8 +60,8 @@ test('It should transform unprocessed asset with uploadFrom', () => {
6060
const transformedAsset = transformers.assets(assetMock, _)
6161
expect(transformedAsset.fields.file['en-US'].uploadFrom).toBeTruthy()
6262
expect(transformedAsset.fields.file['de-DE'].uploadFrom).toBeTruthy()
63-
expect(transformedAsset.fields.file['en-US'].uploadFrom.sys.id).toBe(assetMock.fields.file['en-US'].uploadFrom.sys.id)
64-
expect(transformedAsset.fields.file['de-DE'].uploadFrom.sys.id).toBe(assetMock.fields.file['de-DE'].uploadFrom.sys.id)
63+
expect(transformedAsset.fields.file['en-US'].uploadFrom?.sys.id).toBe(assetMock.fields.file['en-US'].uploadFrom.sys.id)
64+
expect(transformedAsset.fields.file['de-DE'].uploadFrom?.sys.id).toBe(assetMock.fields.file['de-DE'].uploadFrom.sys.id)
6565
})
6666

6767
test('It should transform webhook with credentials to normal webhook', () => {

0 commit comments

Comments
 (0)