@contentstack/cli-variants 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/export/attributes.js +27 -10
- package/lib/export/audiences.js +28 -10
- package/lib/export/events.js +28 -10
- package/lib/export/experiences.js +48 -13
- package/lib/export/projects.js +24 -6
- package/lib/export/variant-entries.js +25 -4
- package/lib/import/attribute.d.ts +2 -3
- package/lib/import/attribute.js +16 -8
- package/lib/import/audiences.d.ts +2 -3
- package/lib/import/audiences.js +21 -8
- package/lib/import/events.d.ts +3 -4
- package/lib/import/events.js +16 -9
- package/lib/import/experiences.d.ts +2 -3
- package/lib/import/experiences.js +60 -17
- package/lib/import/project.d.ts +2 -3
- package/lib/import/project.js +11 -6
- package/lib/import/variant-entries.js +62 -25
- package/lib/types/export-config.d.ts +2 -1
- package/lib/types/utils.d.ts +11 -0
- package/lib/utils/attributes-helper.js +17 -1
- package/lib/utils/audiences-helper.js +37 -6
- package/lib/utils/events-helper.js +17 -4
- package/lib/utils/personalization-api-adapter.d.ts +2 -1
- package/lib/utils/personalization-api-adapter.js +119 -27
- package/lib/utils/variant-api-adapter.d.ts +4 -1
- package/lib/utils/variant-api-adapter.js +91 -17
- package/package.json +8 -5
- package/src/export/attributes.ts +34 -10
- package/src/export/audiences.ts +35 -7
- package/src/export/events.ts +35 -7
- package/src/export/experiences.ts +74 -24
- package/src/export/projects.ts +31 -7
- package/src/export/variant-entries.ts +47 -12
- package/src/import/attribute.ts +22 -9
- package/src/import/audiences.ts +28 -10
- package/src/import/events.ts +21 -10
- package/src/import/experiences.ts +74 -20
- package/src/import/project.ts +22 -8
- package/src/import/variant-entries.ts +116 -40
- package/src/types/export-config.ts +2 -1
- package/src/types/utils.ts +12 -0
- package/src/utils/attributes-helper.ts +21 -2
- package/src/utils/audiences-helper.ts +41 -1
- package/src/utils/events-helper.ts +19 -1
- package/src/utils/personalization-api-adapter.ts +95 -19
- package/src/utils/variant-api-adapter.ts +79 -8
package/src/import/attribute.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolve } from 'path';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
|
-
import { sanitizePath } from '@contentstack/cli-utilities';
|
|
3
|
+
import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities';
|
|
4
4
|
import { PersonalizationAdapter, fsUtil } from '../utils';
|
|
5
5
|
import { APIConfig, AttributeStruct, ImportConfig, LogType } from '../types';
|
|
6
6
|
|
|
@@ -12,13 +12,14 @@ export default class Attribute extends PersonalizationAdapter<ImportConfig> {
|
|
|
12
12
|
private personalizeConfig: ImportConfig['modules']['personalize'];
|
|
13
13
|
private attributeConfig: ImportConfig['modules']['personalize']['attributes'];
|
|
14
14
|
|
|
15
|
-
constructor(public readonly config: ImportConfig
|
|
15
|
+
constructor(public readonly config: ImportConfig) {
|
|
16
16
|
const conf: APIConfig = {
|
|
17
17
|
config,
|
|
18
18
|
baseURL: config.modules.personalize.baseURL[config.region.name],
|
|
19
19
|
headers: { 'X-Project-Uid': config.modules.personalize.project_id },
|
|
20
20
|
};
|
|
21
21
|
super(Object.assign(config, conf));
|
|
22
|
+
|
|
22
23
|
this.personalizeConfig = this.config.modules.personalize;
|
|
23
24
|
this.attributeConfig = this.personalizeConfig.attributes;
|
|
24
25
|
this.mapperDirPath = resolve(
|
|
@@ -29,15 +30,17 @@ export default class Attribute extends PersonalizationAdapter<ImportConfig> {
|
|
|
29
30
|
this.attrMapperDirPath = resolve(sanitizePath(this.mapperDirPath), sanitizePath(this.attributeConfig.dirName));
|
|
30
31
|
this.attributesUidMapperPath = resolve(sanitizePath(this.attrMapperDirPath), 'uid-mapping.json');
|
|
31
32
|
this.attributesUidMapper = {};
|
|
33
|
+
this.config.context.module = 'attributes';
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* The function asynchronously imports attributes from a JSON file and creates them in the system.
|
|
36
38
|
*/
|
|
37
|
-
async import() {
|
|
38
|
-
this.log(this.config, this.$t(this.messages.IMPORT_MSG, { module: 'Attributes' }), 'info');
|
|
39
|
+
async import() {
|
|
39
40
|
await this.init();
|
|
40
41
|
await fsUtil.makeDirectory(this.attrMapperDirPath);
|
|
42
|
+
log.debug(`Created mapper directory: ${this.attrMapperDirPath}`, this.config.context);
|
|
43
|
+
|
|
41
44
|
const { dirName, fileName } = this.attributeConfig;
|
|
42
45
|
const attributesPath = resolve(
|
|
43
46
|
sanitizePath(this.config.data),
|
|
@@ -46,33 +49,43 @@ export default class Attribute extends PersonalizationAdapter<ImportConfig> {
|
|
|
46
49
|
sanitizePath(fileName),
|
|
47
50
|
);
|
|
48
51
|
|
|
52
|
+
log.debug(`Checking for attributes file: ${attributesPath}`, this.config.context);
|
|
53
|
+
|
|
49
54
|
if (existsSync(attributesPath)) {
|
|
50
55
|
try {
|
|
51
56
|
const attributes = fsUtil.readFile(attributesPath, true) as AttributeStruct[];
|
|
57
|
+
log.info(`Found ${attributes.length} attributes to import`, this.config.context);
|
|
52
58
|
|
|
53
59
|
for (const attribute of attributes) {
|
|
54
60
|
const { key, name, description, uid } = attribute;
|
|
61
|
+
log.debug(`Processing attribute: ${name} - ${attribute.__type}`, this.config.context);
|
|
62
|
+
|
|
55
63
|
// skip creating preset attributes, as they are already present in the system
|
|
56
64
|
if (attribute.__type === 'PRESET') {
|
|
65
|
+
log.debug(`Skipping preset attribute: ${name}`, this.config.context);
|
|
57
66
|
continue;
|
|
58
67
|
}
|
|
68
|
+
|
|
59
69
|
try {
|
|
70
|
+
log.debug(`Creating custom attribute: ${name}`, this.config.context);
|
|
60
71
|
const attributeRes = await this.createAttribute({ key, name, description });
|
|
61
72
|
//map old attribute uid to new attribute uid
|
|
62
73
|
//mapper file is used to check whether attribute created or not before creating audience
|
|
63
74
|
this.attributesUidMapper[uid] = attributeRes?.uid ?? '';
|
|
75
|
+
log.debug(`Created attribute: ${uid} -> ${attributeRes?.uid}`, this.config.context);
|
|
64
76
|
} catch (error) {
|
|
65
|
-
|
|
66
|
-
this.log(this.config, error, 'error');
|
|
77
|
+
handleAndLogError(error, this.config.context, `Failed to create attribute: ${name}`);
|
|
67
78
|
}
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
fsUtil.writeFile(this.attributesUidMapperPath, this.attributesUidMapper);
|
|
71
|
-
|
|
82
|
+
log.debug(`Saved ${Object.keys(this.attributesUidMapper).length} attribute mappings to: ${this.attributesUidMapperPath}`, this.config.context);
|
|
83
|
+
log.success('Attributes imported successfully', this.config.context);
|
|
72
84
|
} catch (error) {
|
|
73
|
-
|
|
74
|
-
this.log(this.config, error, 'error');
|
|
85
|
+
handleAndLogError(error, this.config.context);
|
|
75
86
|
}
|
|
87
|
+
} else {
|
|
88
|
+
log.warn(`Attributes file not found: ${attributesPath}`, this.config.context);
|
|
76
89
|
}
|
|
77
90
|
}
|
|
78
91
|
}
|
package/src/import/audiences.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolve } from 'path';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
|
-
import { sanitizePath } from '@contentstack/cli-utilities';
|
|
4
|
-
import { APIConfig, AudienceStruct, ImportConfig
|
|
3
|
+
import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities';
|
|
4
|
+
import { APIConfig, AudienceStruct, ImportConfig } from '../types';
|
|
5
5
|
import { PersonalizationAdapter, fsUtil, lookUpAttributes } from '../utils';
|
|
6
6
|
|
|
7
7
|
export default class Audiences extends PersonalizationAdapter<ImportConfig> {
|
|
@@ -14,13 +14,14 @@ export default class Audiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
14
14
|
private audienceConfig: ImportConfig['modules']['personalize']['audiences'];
|
|
15
15
|
public attributeConfig: ImportConfig['modules']['personalize']['attributes'];
|
|
16
16
|
|
|
17
|
-
constructor(public readonly config: ImportConfig
|
|
17
|
+
constructor(public readonly config: ImportConfig ) {
|
|
18
18
|
const conf: APIConfig = {
|
|
19
19
|
config,
|
|
20
20
|
baseURL: config.modules.personalize.baseURL[config.region.name],
|
|
21
21
|
headers: { 'X-Project-Uid': config.modules.personalize.project_id },
|
|
22
22
|
};
|
|
23
23
|
super(Object.assign(config, conf));
|
|
24
|
+
|
|
24
25
|
this.personalizeConfig = this.config.modules.personalize;
|
|
25
26
|
this.audienceConfig = this.personalizeConfig.audiences;
|
|
26
27
|
this.attributeConfig = this.personalizeConfig.attributes;
|
|
@@ -37,15 +38,17 @@ export default class Audiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
37
38
|
'uid-mapping.json',
|
|
38
39
|
);
|
|
39
40
|
this.audiencesUidMapper = {};
|
|
41
|
+
this.config.context.module = 'audiences';
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/**
|
|
43
45
|
* The function asynchronously imports audiences from a JSON file and creates them in the system.
|
|
44
46
|
*/
|
|
45
|
-
async import() {
|
|
46
|
-
this.log(this.config, this.$t(this.messages.IMPORT_MSG, { module: 'Audiences' }), 'info');
|
|
47
|
+
async import() {
|
|
47
48
|
await this.init();
|
|
48
49
|
await fsUtil.makeDirectory(this.audienceMapperDirPath);
|
|
50
|
+
log.debug(`Created mapper directory: ${this.audienceMapperDirPath}`, this.config.context);
|
|
51
|
+
|
|
49
52
|
const { dirName, fileName } = this.audienceConfig;
|
|
50
53
|
const audiencesPath = resolve(
|
|
51
54
|
sanitizePath(this.config.data),
|
|
@@ -54,34 +57,49 @@ export default class Audiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
54
57
|
sanitizePath(fileName),
|
|
55
58
|
);
|
|
56
59
|
|
|
60
|
+
log.debug(`Checking for audiences file: ${audiencesPath}`, this.config.context);
|
|
61
|
+
|
|
57
62
|
if (existsSync(audiencesPath)) {
|
|
58
63
|
try {
|
|
59
64
|
const audiences = fsUtil.readFile(audiencesPath, true) as AudienceStruct[];
|
|
65
|
+
log.info(`Found ${audiences.length} audiences to import`, this.config.context);
|
|
66
|
+
|
|
60
67
|
const attributesUid = (fsUtil.readFile(this.attributesMapperPath, true) as Record<string, string>) || {};
|
|
68
|
+
log.debug(`Loaded ${Object.keys(attributesUid).length} attribute mappings for audience processing`, this.config.context);
|
|
61
69
|
|
|
62
70
|
for (const audience of audiences) {
|
|
63
71
|
let { name, definition, description, uid } = audience;
|
|
72
|
+
log.debug(`Processing audience: ${name} (${uid})`, this.config.context);
|
|
73
|
+
|
|
64
74
|
try {
|
|
65
75
|
//check whether reference attributes exists or not
|
|
66
76
|
if (definition.rules?.length) {
|
|
77
|
+
log.debug(`Processing ${definition.rules.length} definition rules for audience: ${name}`, this.config.context);
|
|
67
78
|
definition.rules = lookUpAttributes(definition.rules, attributesUid);
|
|
79
|
+
log.debug(`Processed definition rules, remaining rules: ${definition.rules.length}`, this.config.context);
|
|
80
|
+
} else {
|
|
81
|
+
log.debug(`No definition rules found for audience: ${name}`, this.config.context);
|
|
68
82
|
}
|
|
83
|
+
|
|
84
|
+
log.debug(`Creating audience: ${name}`, this.config.context);
|
|
69
85
|
const audienceRes = await this.createAudience({ definition, name, description });
|
|
70
86
|
//map old audience uid to new audience uid
|
|
71
87
|
//mapper file is used to check whether audience created or not before creating experience
|
|
72
88
|
this.audiencesUidMapper[uid] = audienceRes?.uid ?? '';
|
|
89
|
+
log.debug(`Created audience: ${uid} -> ${audienceRes?.uid}`, this.config.context);
|
|
73
90
|
} catch (error) {
|
|
74
|
-
|
|
75
|
-
this.log(this.config, error, 'error');
|
|
91
|
+
handleAndLogError(error, this.config.context, `Failed to create audience: ${name} (${uid})`);
|
|
76
92
|
}
|
|
77
93
|
}
|
|
78
94
|
|
|
79
95
|
fsUtil.writeFile(this.audiencesUidMapperPath, this.audiencesUidMapper);
|
|
80
|
-
|
|
96
|
+
log.debug(`Saved ${Object.keys(this.audiencesUidMapper).length} audience mappings to: ${this.audiencesUidMapperPath}`, this.config.context);
|
|
97
|
+
log.success('Audiences imported successfully', this.config.context);
|
|
81
98
|
} catch (error) {
|
|
82
|
-
|
|
83
|
-
this.log(this.config, error, 'error');
|
|
99
|
+
handleAndLogError(error, this.config.context);
|
|
84
100
|
}
|
|
101
|
+
} else {
|
|
102
|
+
log.warn(`Audiences file not found: ${audiencesPath}`, this.config.context);
|
|
85
103
|
}
|
|
86
104
|
}
|
|
87
105
|
}
|
package/src/import/events.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { resolve } from 'path';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
|
-
import { sanitizePath } from '@contentstack/cli-utilities';
|
|
3
|
+
import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities';
|
|
4
4
|
import { PersonalizationAdapter, fsUtil } from '../utils';
|
|
5
|
-
import { APIConfig, EventStruct, ImportConfig
|
|
5
|
+
import { APIConfig, EventStruct, ImportConfig } from '../types';
|
|
6
6
|
|
|
7
7
|
export default class Events extends PersonalizationAdapter<ImportConfig> {
|
|
8
8
|
private mapperDirPath: string;
|
|
@@ -12,13 +12,14 @@ export default class Events extends PersonalizationAdapter<ImportConfig> {
|
|
|
12
12
|
private personalizeConfig: ImportConfig['modules']['personalize'];
|
|
13
13
|
private eventsConfig: ImportConfig['modules']['personalize']['events'];
|
|
14
14
|
|
|
15
|
-
constructor(public readonly config: ImportConfig
|
|
15
|
+
constructor(public readonly config: ImportConfig) {
|
|
16
16
|
const conf: APIConfig = {
|
|
17
17
|
config,
|
|
18
18
|
baseURL: config.modules.personalize.baseURL[config.region.name],
|
|
19
19
|
headers: { 'X-Project-Uid': config.modules.personalize.project_id },
|
|
20
20
|
};
|
|
21
21
|
super(Object.assign(config, conf));
|
|
22
|
+
|
|
22
23
|
this.personalizeConfig = this.config.modules.personalize;
|
|
23
24
|
this.eventsConfig = this.personalizeConfig.events;
|
|
24
25
|
this.mapperDirPath = resolve(
|
|
@@ -29,15 +30,17 @@ export default class Events extends PersonalizationAdapter<ImportConfig> {
|
|
|
29
30
|
this.eventMapperDirPath = resolve(sanitizePath(this.mapperDirPath), sanitizePath(this.eventsConfig.dirName));
|
|
30
31
|
this.eventsUidMapperPath = resolve(sanitizePath(this.eventMapperDirPath), 'uid-mapping.json');
|
|
31
32
|
this.eventsUidMapper = {};
|
|
33
|
+
this.config.context.module = 'events';
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
|
-
* The function asynchronously imports
|
|
37
|
+
* The function asynchronously imports events from a JSON file and creates them in the system.
|
|
36
38
|
*/
|
|
37
39
|
async import() {
|
|
38
|
-
this.log(this.config, this.$t(this.messages.IMPORT_MSG, { module: 'Events' }), 'info');
|
|
39
40
|
await this.init();
|
|
40
41
|
await fsUtil.makeDirectory(this.eventMapperDirPath);
|
|
42
|
+
log.debug(`Created mapper directory: ${this.eventMapperDirPath}`, this.config.context);
|
|
43
|
+
|
|
41
44
|
const { dirName, fileName } = this.eventsConfig;
|
|
42
45
|
const eventsPath = resolve(
|
|
43
46
|
sanitizePath(this.config.data),
|
|
@@ -46,27 +49,35 @@ export default class Events extends PersonalizationAdapter<ImportConfig> {
|
|
|
46
49
|
sanitizePath(fileName),
|
|
47
50
|
);
|
|
48
51
|
|
|
52
|
+
log.debug(`Checking for events file: ${eventsPath}`, this.config.context);
|
|
53
|
+
|
|
49
54
|
if (existsSync(eventsPath)) {
|
|
50
55
|
try {
|
|
51
56
|
const events = fsUtil.readFile(eventsPath, true) as EventStruct[];
|
|
57
|
+
log.info(`Found ${events.length} events to import`, this.config.context);
|
|
52
58
|
|
|
53
59
|
for (const event of events) {
|
|
54
60
|
const { key, description, uid } = event;
|
|
61
|
+
log.debug(`Processing event: ${key} (${uid})`, this.config.context);
|
|
62
|
+
|
|
55
63
|
try {
|
|
64
|
+
log.debug(`Creating event: ${key}`, this.config.context);
|
|
56
65
|
const eventsResponse = await this.createEvents({ key, description });
|
|
57
66
|
this.eventsUidMapper[uid] = eventsResponse?.uid ?? '';
|
|
67
|
+
log.debug(`Created event: ${uid} -> ${eventsResponse?.uid}`, this.config.context);
|
|
58
68
|
} catch (error) {
|
|
59
|
-
|
|
60
|
-
this.log(this.config, error, 'error');
|
|
69
|
+
handleAndLogError(error, this.config.context, `Failed to create event: ${key} (${uid})`);
|
|
61
70
|
}
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
fsUtil.writeFile(this.eventsUidMapperPath, this.eventsUidMapper);
|
|
65
|
-
|
|
74
|
+
log.debug(`Saved ${Object.keys(this.eventsUidMapper).length} event mappings to: ${this.eventsUidMapperPath}`, this.config.context);
|
|
75
|
+
log.success('Events imported successfully', this.config.context);
|
|
66
76
|
} catch (error) {
|
|
67
|
-
|
|
68
|
-
this.log(this.config, error, 'error');
|
|
77
|
+
handleAndLogError(error, this.config.context);
|
|
69
78
|
}
|
|
79
|
+
} else {
|
|
80
|
+
log.warn(`Events file not found: ${eventsPath}`, this.config.context);
|
|
70
81
|
}
|
|
71
82
|
}
|
|
72
83
|
}
|
|
@@ -2,16 +2,16 @@ import { join, resolve } from 'path';
|
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
3
|
import values from 'lodash/values';
|
|
4
4
|
import cloneDeep from 'lodash/cloneDeep';
|
|
5
|
-
import { sanitizePath } from '@contentstack/cli-utilities';
|
|
5
|
+
import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities';
|
|
6
6
|
import { PersonalizationAdapter, fsUtil, lookUpAudiences, lookUpEvents } from '../utils';
|
|
7
7
|
import {
|
|
8
8
|
APIConfig,
|
|
9
9
|
ImportConfig,
|
|
10
10
|
ExperienceStruct,
|
|
11
11
|
CreateExperienceInput,
|
|
12
|
-
LogType,
|
|
13
12
|
CreateExperienceVersionInput,
|
|
14
13
|
} from '../types';
|
|
14
|
+
|
|
15
15
|
export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
16
16
|
private createdCTs: string[];
|
|
17
17
|
private mapperDirPath: string;
|
|
@@ -41,8 +41,8 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
41
41
|
private audienceConfig: ImportConfig['modules']['personalize']['audiences'];
|
|
42
42
|
private experienceConfig: ImportConfig['modules']['personalize']['experiences'];
|
|
43
43
|
|
|
44
|
-
constructor(public readonly config: ImportConfig
|
|
45
|
-
|
|
44
|
+
constructor(public readonly config: ImportConfig) {
|
|
45
|
+
const conf: APIConfig = {
|
|
46
46
|
config,
|
|
47
47
|
baseURL: config.modules.personalize.baseURL[config.region.name],
|
|
48
48
|
headers: { 'X-Project-Uid': config.modules.personalize.project_id },
|
|
@@ -52,6 +52,7 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
52
52
|
},
|
|
53
53
|
};
|
|
54
54
|
super(Object.assign(config, conf));
|
|
55
|
+
|
|
55
56
|
this.personalizeConfig = this.config.modules.personalize;
|
|
56
57
|
this.experiencesDirPath = resolve(
|
|
57
58
|
sanitizePath(this.config.data),
|
|
@@ -100,22 +101,28 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
100
101
|
this.createdCTs = [];
|
|
101
102
|
this.audiencesUid = (fsUtil.readFile(this.audiencesMapperPath, true) as Record<string, string>) || {};
|
|
102
103
|
this.eventsUid = (fsUtil.readFile(this.eventsMapperPath, true) as Record<string, string>) || {};
|
|
104
|
+
this.config.context.module = 'experiences';
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
/**
|
|
106
108
|
* The function asynchronously imports experiences from a JSON file and creates them in the system.
|
|
107
109
|
*/
|
|
108
|
-
async import() {
|
|
109
|
-
this.log(this.config, this.$t(this.messages.IMPORT_MSG, { module: 'Experiences' }), 'info');
|
|
110
|
+
async import() {
|
|
110
111
|
await this.init();
|
|
111
112
|
await fsUtil.makeDirectory(this.expMapperDirPath);
|
|
113
|
+
log.debug(`Created mapper directory: ${this.expMapperDirPath}`, this.config.context);
|
|
112
114
|
|
|
113
115
|
if (existsSync(this.experiencesPath)) {
|
|
116
|
+
log.debug(`Loading experiences from: ${this.experiencesPath}`, this.config.context);
|
|
117
|
+
|
|
114
118
|
try {
|
|
115
119
|
const experiences = fsUtil.readFile(this.experiencesPath, true) as ExperienceStruct[];
|
|
120
|
+
log.info(`Found ${experiences.length} experiences to import`, this.config.context);
|
|
116
121
|
|
|
117
122
|
for (const experience of experiences) {
|
|
118
123
|
const { uid, ...restExperienceData } = experience;
|
|
124
|
+
log.debug(`Processing experience: ${uid}`, this.config.context);
|
|
125
|
+
|
|
119
126
|
//check whether reference audience exists or not that referenced in variations having __type equal to AudienceBasedVariation & targeting
|
|
120
127
|
let experienceReqObj: CreateExperienceInput = lookUpAudiences(restExperienceData, this.audiencesUid);
|
|
121
128
|
//check whether events exists or not that referenced in metrics
|
|
@@ -124,40 +131,44 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
124
131
|
const expRes = (await this.createExperience(experienceReqObj)) as ExperienceStruct;
|
|
125
132
|
//map old experience uid to new experience uid
|
|
126
133
|
this.experiencesUidMapper[uid] = expRes?.uid ?? '';
|
|
134
|
+
log.debug(`Created experience: ${uid} -> ${expRes?.uid}`, this.config.context);
|
|
127
135
|
|
|
128
136
|
try {
|
|
129
137
|
// import versions of experience
|
|
130
138
|
await this.importExperienceVersions(expRes, uid);
|
|
131
139
|
} catch (error) {
|
|
132
|
-
|
|
133
|
-
this.log(this.config, error, 'error');
|
|
140
|
+
handleAndLogError(error, this.config.context, `Failed to import experience versions for ${expRes.uid}`);
|
|
134
141
|
}
|
|
135
142
|
}
|
|
143
|
+
|
|
136
144
|
fsUtil.writeFile(this.experiencesUidMapperPath, this.experiencesUidMapper);
|
|
137
|
-
|
|
145
|
+
log.success('Experiences created successfully', this.config.context);
|
|
138
146
|
|
|
139
|
-
|
|
147
|
+
log.info('Validating variant and variant group creation',this.config.context);
|
|
140
148
|
this.pendingVariantAndVariantGrpForExperience = values(cloneDeep(this.experiencesUidMapper));
|
|
141
149
|
const jobRes = await this.validateVariantGroupAndVariantsCreated();
|
|
142
150
|
fsUtil.writeFile(this.cmsVariantPath, this.cmsVariants);
|
|
143
151
|
fsUtil.writeFile(this.cmsVariantGroupPath, this.cmsVariantGroups);
|
|
152
|
+
|
|
144
153
|
if (jobRes) {
|
|
145
|
-
|
|
154
|
+
log.success('Variant and variant groups created successfully', this.config.context);
|
|
146
155
|
} else {
|
|
156
|
+
log.error('Failed to create variants and variant groups', this.config.context);
|
|
147
157
|
this.personalizeConfig.importData = false;
|
|
148
158
|
}
|
|
149
159
|
|
|
150
160
|
if (this.personalizeConfig.importData) {
|
|
151
|
-
|
|
161
|
+
log.info('Attaching content types to experiences', this.config.context);
|
|
152
162
|
await this.attachCTsInExperience();
|
|
153
|
-
|
|
163
|
+
log.success('Content types attached to experiences successfully', this.config.context);
|
|
154
164
|
}
|
|
155
165
|
|
|
156
166
|
await this.createVariantIdMapper();
|
|
157
167
|
} catch (error) {
|
|
158
|
-
|
|
159
|
-
this.log(this.config, error, 'error');
|
|
168
|
+
handleAndLogError(error, this.config.context);
|
|
160
169
|
}
|
|
170
|
+
} else {
|
|
171
|
+
log.warn(`Experiences file not found: ${this.experiencesPath}`, this.config.context);
|
|
161
172
|
}
|
|
162
173
|
}
|
|
163
174
|
|
|
@@ -165,6 +176,8 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
165
176
|
* function import experience versions from a JSON file and creates them in the project.
|
|
166
177
|
*/
|
|
167
178
|
async importExperienceVersions(experience: ExperienceStruct, oldExperienceUid: string) {
|
|
179
|
+
log.debug(`Importing versions for experience: ${oldExperienceUid}`, this.config.context);
|
|
180
|
+
|
|
168
181
|
const versionsPath = resolve(
|
|
169
182
|
sanitizePath(this.experiencesDirPath),
|
|
170
183
|
'versions',
|
|
@@ -172,10 +185,13 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
172
185
|
);
|
|
173
186
|
|
|
174
187
|
if (!existsSync(versionsPath)) {
|
|
188
|
+
log.debug(`No versions file found for experience: ${oldExperienceUid}`, this.config.context);
|
|
175
189
|
return;
|
|
176
190
|
}
|
|
177
191
|
|
|
178
192
|
const versions = fsUtil.readFile(versionsPath, true) as ExperienceStruct[];
|
|
193
|
+
log.debug(`Found ${versions.length} versions for experience: ${oldExperienceUid}`, this.config.context);
|
|
194
|
+
|
|
179
195
|
const versionMap: Record<string, CreateExperienceVersionInput | undefined> = {
|
|
180
196
|
ACTIVE: undefined,
|
|
181
197
|
DRAFT: undefined,
|
|
@@ -189,6 +205,7 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
189
205
|
|
|
190
206
|
if (versionReqObj && versionReqObj.status) {
|
|
191
207
|
versionMap[versionReqObj.status] = versionReqObj;
|
|
208
|
+
log.debug(`Mapped version with status: ${versionReqObj.status}`, this.config.context);
|
|
192
209
|
}
|
|
193
210
|
});
|
|
194
211
|
|
|
@@ -201,18 +218,22 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
201
218
|
experience: ExperienceStruct,
|
|
202
219
|
versionMap: Record<string, CreateExperienceVersionInput | undefined>,
|
|
203
220
|
) {
|
|
221
|
+
log.debug(`Handling version update/create for experience: ${experience.uid}`, this.config.context);
|
|
204
222
|
const { ACTIVE, DRAFT, PAUSE } = versionMap;
|
|
205
223
|
let latestVersionUsed = false;
|
|
206
224
|
|
|
207
225
|
if (ACTIVE) {
|
|
226
|
+
log.debug(`Updating experience version to ACTIVE for: ${experience.uid}`, this.config.context);
|
|
208
227
|
await this.updateExperienceVersion(experience.uid, experience.latestVersion, ACTIVE);
|
|
209
228
|
latestVersionUsed = true;
|
|
210
229
|
}
|
|
211
230
|
|
|
212
231
|
if (DRAFT) {
|
|
213
232
|
if (latestVersionUsed) {
|
|
233
|
+
log.debug(`Creating new DRAFT version for: ${experience.uid}`, this.config.context);
|
|
214
234
|
await this.createExperienceVersion(experience.uid, DRAFT);
|
|
215
235
|
} else {
|
|
236
|
+
log.debug(`Updating experience version to DRAFT for: ${experience.uid}`, this.config.context);
|
|
216
237
|
await this.updateExperienceVersion(experience.uid, experience.latestVersion, DRAFT);
|
|
217
238
|
latestVersionUsed = true;
|
|
218
239
|
}
|
|
@@ -220,8 +241,10 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
220
241
|
|
|
221
242
|
if (PAUSE) {
|
|
222
243
|
if (latestVersionUsed) {
|
|
244
|
+
log.debug(`Creating new PAUSE version for: ${experience.uid}`, this.config.context);
|
|
223
245
|
await this.createExperienceVersion(experience.uid, PAUSE);
|
|
224
246
|
} else {
|
|
247
|
+
log.debug(`Updating experience version to PAUSE for: ${experience.uid}`, this.config.context);
|
|
225
248
|
await this.updateExperienceVersion(experience.uid, experience.latestVersion, PAUSE);
|
|
226
249
|
}
|
|
227
250
|
}
|
|
@@ -235,14 +258,20 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
235
258
|
* @returns
|
|
236
259
|
*/
|
|
237
260
|
async validateVariantGroupAndVariantsCreated(retryCount = 0): Promise<any> {
|
|
261
|
+
log.debug(`Validating variant groups and variants creation - attempt ${retryCount + 1}/${this.maxValidateRetry}`, this.config.context);
|
|
262
|
+
|
|
238
263
|
try {
|
|
239
264
|
const promises = this.pendingVariantAndVariantGrpForExperience.map(async (expUid) => {
|
|
265
|
+
log.debug(`Checking experience: ${expUid}`, this.config.context);
|
|
240
266
|
const expRes = await this.getExperience(expUid);
|
|
241
267
|
const variants = expRes?._cms?.variants ?? {};
|
|
242
268
|
if (expRes?._cms && expRes?._cms?.variantGroup && Object.keys(variants).length > 0) {
|
|
269
|
+
log.debug(`Found variants and variant group for experience: ${expUid}`, this.config.context);
|
|
243
270
|
this.cmsVariants[expUid] = expRes._cms?.variants ?? {};
|
|
244
271
|
this.cmsVariantGroups[expUid] = expRes._cms?.variantGroup ?? {};
|
|
245
272
|
return expUid; // Return the expUid for filtering later
|
|
273
|
+
} else {
|
|
274
|
+
log.debug(`Variants/variant group not ready for experience: ${expUid}`, this.config.context);
|
|
246
275
|
}
|
|
247
276
|
});
|
|
248
277
|
|
|
@@ -251,6 +280,7 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
251
280
|
|
|
252
281
|
if (this.pendingVariantAndVariantGrpForExperience?.length) {
|
|
253
282
|
if (retryCount !== this.maxValidateRetry) {
|
|
283
|
+
log.debug(`Waiting ${this.expCheckIntervalDuration}ms before retry`, this.config.context);
|
|
254
284
|
await this.delay(this.expCheckIntervalDuration);
|
|
255
285
|
// Filter out the processed elements
|
|
256
286
|
this.pendingVariantAndVariantGrpForExperience = this.pendingVariantAndVariantGrpForExperience.filter(
|
|
@@ -258,65 +288,89 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
258
288
|
);
|
|
259
289
|
return this.validateVariantGroupAndVariantsCreated(retryCount);
|
|
260
290
|
} else {
|
|
261
|
-
|
|
291
|
+
log.error('Personalize job failed to create variants and variant groups', this.config.context);
|
|
292
|
+
log.error(`Failed experiences: ${this.pendingVariantAndVariantGrpForExperience.join(', ')}`, this.config.context);
|
|
262
293
|
fsUtil.writeFile(this.failedCmsExpPath, this.pendingVariantAndVariantGrpForExperience);
|
|
263
294
|
return false;
|
|
264
295
|
}
|
|
265
296
|
} else {
|
|
297
|
+
log.debug('All variant groups and variants created successfully', this.config.context);
|
|
266
298
|
return true;
|
|
267
299
|
}
|
|
268
300
|
} catch (error) {
|
|
269
|
-
|
|
301
|
+
handleAndLogError(error, this.config.context);
|
|
270
302
|
throw error;
|
|
271
303
|
}
|
|
272
304
|
}
|
|
273
305
|
|
|
274
306
|
async attachCTsInExperience() {
|
|
307
|
+
log.debug('Attaching content types to experiences', this.config.context);
|
|
308
|
+
|
|
275
309
|
try {
|
|
276
310
|
// Read the created content types from the file
|
|
277
311
|
this.createdCTs = fsUtil.readFile(this.cTsSuccessPath, true) as any;
|
|
278
312
|
if (!this.createdCTs) {
|
|
279
|
-
|
|
313
|
+
log.debug('No Content types created, skipping following process', this.config.context);
|
|
280
314
|
return;
|
|
281
315
|
}
|
|
316
|
+
|
|
317
|
+
log.debug(`Found ${this.createdCTs.length} created content types`, this.config.context);
|
|
282
318
|
const experienceCTsMap = fsUtil.readFile(this.experienceCTsPath, true) as Record<string, string[]>;
|
|
319
|
+
|
|
283
320
|
return await Promise.allSettled(
|
|
284
321
|
Object.entries(this.experiencesUidMapper).map(async ([oldExpUid, newExpUid]) => {
|
|
285
322
|
if (experienceCTsMap[oldExpUid]?.length) {
|
|
323
|
+
log.debug(`Processing content types for experience: ${oldExpUid} -> ${newExpUid}`, this.config.context);
|
|
324
|
+
|
|
286
325
|
// Filter content types that were created
|
|
287
326
|
const updatedContentTypes = experienceCTsMap[oldExpUid].filter(
|
|
288
327
|
(ct: any) => this.createdCTs.includes(ct?.uid) && ct.status === 'linked',
|
|
289
328
|
);
|
|
329
|
+
|
|
290
330
|
if (updatedContentTypes?.length) {
|
|
331
|
+
log.debug(`Attaching ${updatedContentTypes.length} content types to experience: ${newExpUid}`, this.config.context);
|
|
291
332
|
const { variant_groups: [variantGroup] = [] } =
|
|
292
333
|
(await this.getVariantGroup({ experienceUid: newExpUid })) || {};
|
|
293
334
|
variantGroup.content_types = updatedContentTypes;
|
|
294
335
|
// Update content types detail in the new experience asynchronously
|
|
295
336
|
return await this.updateVariantGroup(variantGroup);
|
|
337
|
+
} else {
|
|
338
|
+
log.debug(`No valid content types found for experience: ${newExpUid}`, this.config.context);
|
|
296
339
|
}
|
|
340
|
+
} else {
|
|
341
|
+
log.debug(`No content types mapped for experience: ${oldExpUid}`, this.config.context);
|
|
297
342
|
}
|
|
298
343
|
}),
|
|
299
344
|
);
|
|
300
345
|
} catch (error) {
|
|
301
|
-
|
|
302
|
-
this.log(this.config, error, 'error');
|
|
346
|
+
handleAndLogError(error, this.config.context, 'Failed to attach content type with experience');
|
|
303
347
|
}
|
|
304
348
|
}
|
|
305
349
|
|
|
306
350
|
async createVariantIdMapper() {
|
|
351
|
+
log.debug('Creating variant ID mapper', this.config.context);
|
|
352
|
+
|
|
307
353
|
try {
|
|
308
354
|
const experienceVariantIds: any = fsUtil.readFile(this.experienceVariantsIdsPath, true) || [];
|
|
355
|
+
log.debug(`Found ${experienceVariantIds.length} experience variant IDs to process`, this.config.context);
|
|
356
|
+
|
|
309
357
|
const variantUIDMapper: Record<string, string> = {};
|
|
310
358
|
for (let experienceVariantId of experienceVariantIds) {
|
|
311
359
|
const [experienceId, variantShortId, oldVariantId] = experienceVariantId.split('-');
|
|
312
360
|
const latestVariantId = this.cmsVariants[this.experiencesUidMapper[experienceId]]?.[variantShortId];
|
|
313
361
|
if (latestVariantId) {
|
|
314
362
|
variantUIDMapper[oldVariantId] = latestVariantId;
|
|
363
|
+
log.debug(`Mapped variant ID: ${oldVariantId} -> ${latestVariantId}`, this.config.context);
|
|
364
|
+
} else {
|
|
365
|
+
log.warn(`Could not find variant ID mapping for: ${experienceVariantId}`, this.config.context);
|
|
315
366
|
}
|
|
316
367
|
}
|
|
317
368
|
|
|
369
|
+
log.debug(`Created ${Object.keys(variantUIDMapper).length} variant ID mappings`, this.config.context);
|
|
318
370
|
fsUtil.writeFile(this.variantUidMapperFilePath, variantUIDMapper);
|
|
371
|
+
log.debug(`Variant ID mapper saved to: ${this.variantUidMapperFilePath}`, this.config.context);
|
|
319
372
|
} catch (error) {
|
|
373
|
+
handleAndLogError(error, this.config.context, 'Failed to create variant ID mapper');
|
|
320
374
|
throw error;
|
|
321
375
|
}
|
|
322
376
|
}
|