@contentstack/cli-variants 0.0.1-alpha → 1.1.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.d.ts +2 -2
- package/lib/export/attributes.js +5 -4
- package/lib/export/audiences.d.ts +2 -2
- package/lib/export/audiences.js +5 -4
- package/lib/export/events.d.ts +2 -2
- package/lib/export/events.js +5 -4
- package/lib/export/experiences.d.ts +2 -2
- package/lib/export/experiences.js +20 -5
- package/lib/export/projects.d.ts +2 -2
- package/lib/export/projects.js +9 -6
- package/lib/export/variant-entries.js +1 -1
- package/lib/import/attribute.d.ts +1 -1
- package/lib/import/attribute.js +21 -10
- package/lib/import/audiences.d.ts +2 -2
- package/lib/import/audiences.js +21 -14
- package/lib/import/events.d.ts +1 -1
- package/lib/import/events.js +15 -8
- package/lib/import/experiences.d.ts +12 -5
- package/lib/import/experiences.js +86 -21
- package/lib/import/project.js +12 -11
- package/lib/import/variant-entries.d.ts +1 -1
- package/lib/import/variant-entries.js +28 -22
- package/lib/messages/index.d.ts +1 -1
- package/lib/messages/index.js +3 -2
- package/lib/types/export-config.d.ts +3 -3
- package/lib/types/import-config.d.ts +1 -1
- package/lib/types/personalization-api-adapter.d.ts +14 -1
- package/lib/types/variant-api-adapter.d.ts +3 -2
- package/lib/types/variant-entry.d.ts +2 -3
- package/lib/utils/attributes-helper.js +2 -2
- package/lib/utils/audiences-helper.js +14 -3
- package/lib/utils/error-helper.js +6 -6
- package/lib/utils/logger.js +5 -4
- package/lib/utils/personalization-api-adapter.d.ts +6 -2
- package/lib/utils/personalization-api-adapter.js +90 -23
- package/lib/utils/variant-api-adapter.d.ts +5 -4
- package/lib/utils/variant-api-adapter.js +29 -10
- package/package.json +2 -2
- package/src/export/attributes.ts +11 -7
- package/src/export/audiences.ts +7 -6
- package/src/export/events.ts +7 -6
- package/src/export/experiences.ts +24 -7
- package/src/export/projects.ts +11 -8
- package/src/export/variant-entries.ts +1 -2
- package/src/import/attribute.ts +31 -13
- package/src/import/audiences.ts +37 -19
- package/src/import/events.ts +25 -11
- package/src/import/experiences.ts +120 -30
- package/src/import/project.ts +13 -13
- package/src/import/variant-entries.ts +70 -37
- package/src/messages/index.ts +3 -2
- package/src/types/export-config.ts +3 -3
- package/src/types/import-config.ts +1 -1
- package/src/types/personalization-api-adapter.ts +14 -1
- package/src/types/variant-api-adapter.ts +3 -1
- package/src/types/variant-entry.ts +2 -3
- package/src/utils/attributes-helper.ts +2 -2
- package/src/utils/audiences-helper.ts +12 -2
- package/src/utils/error-helper.ts +6 -6
- package/src/utils/logger.ts +5 -4
- package/src/utils/personalization-api-adapter.ts +71 -18
- package/src/utils/variant-api-adapter.ts +21 -7
package/src/export/projects.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import { sanitizePath } from '@contentstack/cli-utilities';
|
|
3
|
-
import { ExportConfig,
|
|
3
|
+
import { ExportConfig, PersonalizeConfig } from '../types';
|
|
4
4
|
import { PersonalizationAdapter, log, fsUtil, formatError } from '../utils';
|
|
5
5
|
|
|
6
6
|
export default class ExportProjects extends PersonalizationAdapter<ExportConfig> {
|
|
7
7
|
private projectFolderPath: string;
|
|
8
8
|
public exportConfig: ExportConfig;
|
|
9
|
-
public
|
|
9
|
+
public personalizeConfig: PersonalizeConfig;
|
|
10
10
|
constructor(exportConfig: ExportConfig) {
|
|
11
11
|
super({
|
|
12
12
|
config: exportConfig,
|
|
13
|
-
baseURL: exportConfig.modules.
|
|
14
|
-
headers: {
|
|
13
|
+
baseURL: exportConfig.modules.personalize.baseURL[exportConfig.region.name],
|
|
14
|
+
headers: { organization_uid: exportConfig.org_uid },
|
|
15
15
|
});
|
|
16
16
|
this.exportConfig = exportConfig;
|
|
17
|
-
this.
|
|
17
|
+
this.personalizeConfig = exportConfig.modules.personalize;
|
|
18
18
|
this.projectFolderPath = path.resolve(
|
|
19
19
|
sanitizePath(exportConfig.data),
|
|
20
20
|
sanitizePath(exportConfig.branchName || ''),
|
|
21
|
-
sanitizePath(this.
|
|
21
|
+
sanitizePath(this.personalizeConfig.dirName),
|
|
22
22
|
'projects',
|
|
23
23
|
);
|
|
24
24
|
}
|
|
@@ -26,10 +26,11 @@ export default class ExportProjects extends PersonalizationAdapter<ExportConfig>
|
|
|
26
26
|
async start() {
|
|
27
27
|
try {
|
|
28
28
|
log(this.exportConfig, 'Starting projects export', 'info');
|
|
29
|
+
await this.init();
|
|
29
30
|
await fsUtil.makeDirectory(this.projectFolderPath);
|
|
30
31
|
const project = await this.projects({ connectedStackApiKey: this.exportConfig.apiKey });
|
|
31
32
|
if (!project || project?.length < 1) {
|
|
32
|
-
log(this.exportConfig, 'No
|
|
33
|
+
log(this.exportConfig, 'No Personalize Project connected with the given stack', 'info');
|
|
33
34
|
this.exportConfig.personalizationEnabled = false;
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
@@ -38,7 +39,9 @@ export default class ExportProjects extends PersonalizationAdapter<ExportConfig>
|
|
|
38
39
|
fsUtil.writeFile(path.resolve(sanitizePath(this.projectFolderPath), 'projects.json'), project);
|
|
39
40
|
log(this.exportConfig, 'Project exported successfully!', 'success');
|
|
40
41
|
} catch (error) {
|
|
41
|
-
|
|
42
|
+
if (error !== 'Forbidden') {
|
|
43
|
+
log(this.exportConfig, `Failed to export projects!`, 'error');
|
|
44
|
+
}
|
|
42
45
|
throw error;
|
|
43
46
|
}
|
|
44
47
|
}
|
|
@@ -19,7 +19,6 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Exp
|
|
|
19
19
|
headers: {
|
|
20
20
|
api_key: config.apiKey,
|
|
21
21
|
branch: config.branchName,
|
|
22
|
-
authtoken: config.auth_token,
|
|
23
22
|
organization_uid: config.org_uid,
|
|
24
23
|
'X-Project-Uid': config.project_id,
|
|
25
24
|
},
|
|
@@ -36,7 +35,7 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Exp
|
|
|
36
35
|
async exportVariantEntry(options: { locale: string; contentTypeUid: string; entries: Record<string, any>[] }) {
|
|
37
36
|
const variantEntry = this.config.modules.variantEntry;
|
|
38
37
|
const { entries, locale, contentTypeUid: content_type_uid } = options;
|
|
39
|
-
|
|
38
|
+
await this.variantInstance.init();
|
|
40
39
|
for (let index = 0; index < entries.length; index++) {
|
|
41
40
|
const entry = entries[index];
|
|
42
41
|
const variantEntryBasePath = join(sanitizePath(this.entriesDirPath), sanitizePath(content_type_uid), sanitizePath(locale), sanitizePath(variantEntry.dirName), sanitizePath(entry.uid));
|
package/src/import/attribute.ts
CHANGED
|
@@ -9,19 +9,23 @@ export default class Attribute extends PersonalizationAdapter<ImportConfig> {
|
|
|
9
9
|
private attrMapperDirPath: string;
|
|
10
10
|
private attributesUidMapperPath: string;
|
|
11
11
|
private attributesUidMapper: Record<string, unknown>;
|
|
12
|
-
private
|
|
13
|
-
private attributeConfig: ImportConfig['modules']['
|
|
12
|
+
private personalizeConfig: ImportConfig['modules']['personalize'];
|
|
13
|
+
private attributeConfig: ImportConfig['modules']['personalize']['attributes'];
|
|
14
14
|
|
|
15
15
|
constructor(public readonly config: ImportConfig, private readonly log: LogType = console.log) {
|
|
16
16
|
const conf: APIConfig = {
|
|
17
17
|
config,
|
|
18
|
-
baseURL: config.modules.
|
|
19
|
-
headers: { 'X-Project-Uid': config.modules.
|
|
18
|
+
baseURL: config.modules.personalize.baseURL[config.region.name],
|
|
19
|
+
headers: { 'X-Project-Uid': config.modules.personalize.project_id },
|
|
20
20
|
};
|
|
21
21
|
super(Object.assign(config, conf));
|
|
22
|
-
this.
|
|
23
|
-
this.attributeConfig = this.
|
|
24
|
-
this.mapperDirPath = resolve(
|
|
22
|
+
this.personalizeConfig = this.config.modules.personalize;
|
|
23
|
+
this.attributeConfig = this.personalizeConfig.attributes;
|
|
24
|
+
this.mapperDirPath = resolve(
|
|
25
|
+
sanitizePath(this.config.backupDir),
|
|
26
|
+
'mapper',
|
|
27
|
+
sanitizePath(this.personalizeConfig.dirName),
|
|
28
|
+
);
|
|
25
29
|
this.attrMapperDirPath = resolve(sanitizePath(this.mapperDirPath), sanitizePath(this.attributeConfig.dirName));
|
|
26
30
|
this.attributesUidMapperPath = resolve(sanitizePath(this.attrMapperDirPath), 'uid-mapping.json');
|
|
27
31
|
this.attributesUidMapper = {};
|
|
@@ -32,10 +36,15 @@ export default class Attribute extends PersonalizationAdapter<ImportConfig> {
|
|
|
32
36
|
*/
|
|
33
37
|
async import() {
|
|
34
38
|
this.log(this.config, this.$t(this.messages.IMPORT_MSG, { module: 'Attributes' }), 'info');
|
|
35
|
-
|
|
39
|
+
await this.init();
|
|
36
40
|
await fsUtil.makeDirectory(this.attrMapperDirPath);
|
|
37
41
|
const { dirName, fileName } = this.attributeConfig;
|
|
38
|
-
const attributesPath = resolve(
|
|
42
|
+
const attributesPath = resolve(
|
|
43
|
+
sanitizePath(this.config.data),
|
|
44
|
+
sanitizePath(this.personalizeConfig.dirName),
|
|
45
|
+
sanitizePath(dirName),
|
|
46
|
+
sanitizePath(fileName),
|
|
47
|
+
);
|
|
39
48
|
|
|
40
49
|
if (existsSync(attributesPath)) {
|
|
41
50
|
try {
|
|
@@ -43,10 +52,19 @@ export default class Attribute extends PersonalizationAdapter<ImportConfig> {
|
|
|
43
52
|
|
|
44
53
|
for (const attribute of attributes) {
|
|
45
54
|
const { key, name, description, uid } = attribute;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
// skip creating preset attributes, as they are already present in the system
|
|
56
|
+
if (attribute.__type === 'PRESET') {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const attributeRes = await this.createAttribute({ key, name, description });
|
|
61
|
+
//map old attribute uid to new attribute uid
|
|
62
|
+
//mapper file is used to check whether attribute created or not before creating audience
|
|
63
|
+
this.attributesUidMapper[uid] = attributeRes?.uid ?? '';
|
|
64
|
+
} catch (error) {
|
|
65
|
+
this.log(this.config, `Failed to create attribute ${name}!`, 'error');
|
|
66
|
+
this.log(this.config, error, 'error');
|
|
67
|
+
}
|
|
50
68
|
}
|
|
51
69
|
|
|
52
70
|
fsUtil.writeFile(this.attributesUidMapperPath, this.attributesUidMapper);
|
package/src/import/audiences.ts
CHANGED
|
@@ -10,24 +10,32 @@ export default class Audiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
10
10
|
private attributesMapperPath: string;
|
|
11
11
|
private audiencesUidMapperPath: string;
|
|
12
12
|
private audiencesUidMapper: Record<string, unknown>;
|
|
13
|
-
private
|
|
14
|
-
private audienceConfig: ImportConfig['modules']['
|
|
15
|
-
public attributeConfig: ImportConfig['modules']['
|
|
13
|
+
private personalizeConfig: ImportConfig['modules']['personalize'];
|
|
14
|
+
private audienceConfig: ImportConfig['modules']['personalize']['audiences'];
|
|
15
|
+
public attributeConfig: ImportConfig['modules']['personalize']['attributes'];
|
|
16
16
|
|
|
17
17
|
constructor(public readonly config: ImportConfig, private readonly log: LogType = console.log) {
|
|
18
18
|
const conf: APIConfig = {
|
|
19
19
|
config,
|
|
20
|
-
baseURL: config.modules.
|
|
21
|
-
headers: { 'X-Project-Uid': config.modules.
|
|
20
|
+
baseURL: config.modules.personalize.baseURL[config.region.name],
|
|
21
|
+
headers: { 'X-Project-Uid': config.modules.personalize.project_id },
|
|
22
22
|
};
|
|
23
23
|
super(Object.assign(config, conf));
|
|
24
|
-
this.
|
|
25
|
-
this.audienceConfig = this.
|
|
26
|
-
this.attributeConfig = this.
|
|
27
|
-
this.mapperDirPath = resolve(
|
|
24
|
+
this.personalizeConfig = this.config.modules.personalize;
|
|
25
|
+
this.audienceConfig = this.personalizeConfig.audiences;
|
|
26
|
+
this.attributeConfig = this.personalizeConfig.attributes;
|
|
27
|
+
this.mapperDirPath = resolve(
|
|
28
|
+
sanitizePath(this.config.backupDir),
|
|
29
|
+
'mapper',
|
|
30
|
+
sanitizePath(this.personalizeConfig.dirName),
|
|
31
|
+
);
|
|
28
32
|
this.audienceMapperDirPath = resolve(sanitizePath(this.mapperDirPath), sanitizePath(this.audienceConfig.dirName));
|
|
29
33
|
this.audiencesUidMapperPath = resolve(sanitizePath(this.audienceMapperDirPath), 'uid-mapping.json');
|
|
30
|
-
this.attributesMapperPath = resolve(
|
|
34
|
+
this.attributesMapperPath = resolve(
|
|
35
|
+
sanitizePath(this.mapperDirPath),
|
|
36
|
+
sanitizePath(this.attributeConfig.dirName),
|
|
37
|
+
'uid-mapping.json',
|
|
38
|
+
);
|
|
31
39
|
this.audiencesUidMapper = {};
|
|
32
40
|
}
|
|
33
41
|
|
|
@@ -36,10 +44,15 @@ export default class Audiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
36
44
|
*/
|
|
37
45
|
async import() {
|
|
38
46
|
this.log(this.config, this.$t(this.messages.IMPORT_MSG, { module: 'Audiences' }), 'info');
|
|
39
|
-
|
|
47
|
+
await this.init();
|
|
40
48
|
await fsUtil.makeDirectory(this.audienceMapperDirPath);
|
|
41
49
|
const { dirName, fileName } = this.audienceConfig;
|
|
42
|
-
const audiencesPath = resolve(
|
|
50
|
+
const audiencesPath = resolve(
|
|
51
|
+
sanitizePath(this.config.data),
|
|
52
|
+
sanitizePath(this.personalizeConfig.dirName),
|
|
53
|
+
sanitizePath(dirName),
|
|
54
|
+
sanitizePath(fileName),
|
|
55
|
+
);
|
|
43
56
|
|
|
44
57
|
if (existsSync(audiencesPath)) {
|
|
45
58
|
try {
|
|
@@ -48,14 +61,19 @@ export default class Audiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
48
61
|
|
|
49
62
|
for (const audience of audiences) {
|
|
50
63
|
let { name, definition, description, uid } = audience;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
try {
|
|
65
|
+
//check whether reference attributes exists or not
|
|
66
|
+
if (definition.rules?.length) {
|
|
67
|
+
definition.rules = lookUpAttributes(definition.rules, attributesUid);
|
|
68
|
+
}
|
|
69
|
+
const audienceRes = await this.createAudience({ definition, name, description });
|
|
70
|
+
//map old audience uid to new audience uid
|
|
71
|
+
//mapper file is used to check whether audience created or not before creating experience
|
|
72
|
+
this.audiencesUidMapper[uid] = audienceRes?.uid ?? '';
|
|
73
|
+
} catch (error) {
|
|
74
|
+
this.log(this.config, `Failed to create audience uid: ${uid}, name: ${name}`, 'error');
|
|
75
|
+
this.log(this.config, error, 'error');
|
|
54
76
|
}
|
|
55
|
-
const audienceRes = await this.createAudience({ definition, name, description });
|
|
56
|
-
//map old audience uid to new audience uid
|
|
57
|
-
//mapper file is used to check whether audience created or not before creating experience
|
|
58
|
-
this.audiencesUidMapper[uid] = audienceRes?.uid ?? '';
|
|
59
77
|
}
|
|
60
78
|
|
|
61
79
|
fsUtil.writeFile(this.audiencesUidMapperPath, this.audiencesUidMapper);
|
package/src/import/events.ts
CHANGED
|
@@ -9,19 +9,23 @@ export default class Events extends PersonalizationAdapter<ImportConfig> {
|
|
|
9
9
|
private eventMapperDirPath: string;
|
|
10
10
|
private eventsUidMapperPath: string;
|
|
11
11
|
private eventsUidMapper: Record<string, unknown>;
|
|
12
|
-
private
|
|
13
|
-
private eventsConfig: ImportConfig['modules']['
|
|
12
|
+
private personalizeConfig: ImportConfig['modules']['personalize'];
|
|
13
|
+
private eventsConfig: ImportConfig['modules']['personalize']['events'];
|
|
14
14
|
|
|
15
15
|
constructor(public readonly config: ImportConfig, private readonly log: LogType = console.log) {
|
|
16
16
|
const conf: APIConfig = {
|
|
17
17
|
config,
|
|
18
|
-
baseURL: config.modules.
|
|
19
|
-
headers: { 'X-Project-Uid': config.modules.
|
|
18
|
+
baseURL: config.modules.personalize.baseURL[config.region.name],
|
|
19
|
+
headers: { 'X-Project-Uid': config.modules.personalize.project_id },
|
|
20
20
|
};
|
|
21
21
|
super(Object.assign(config, conf));
|
|
22
|
-
this.
|
|
23
|
-
this.eventsConfig = this.
|
|
24
|
-
this.mapperDirPath = resolve(
|
|
22
|
+
this.personalizeConfig = this.config.modules.personalize;
|
|
23
|
+
this.eventsConfig = this.personalizeConfig.events;
|
|
24
|
+
this.mapperDirPath = resolve(
|
|
25
|
+
sanitizePath(this.config.backupDir),
|
|
26
|
+
'mapper',
|
|
27
|
+
sanitizePath(this.personalizeConfig.dirName),
|
|
28
|
+
);
|
|
25
29
|
this.eventMapperDirPath = resolve(sanitizePath(this.mapperDirPath), sanitizePath(this.eventsConfig.dirName));
|
|
26
30
|
this.eventsUidMapperPath = resolve(sanitizePath(this.eventMapperDirPath), 'uid-mapping.json');
|
|
27
31
|
this.eventsUidMapper = {};
|
|
@@ -32,10 +36,15 @@ export default class Events extends PersonalizationAdapter<ImportConfig> {
|
|
|
32
36
|
*/
|
|
33
37
|
async import() {
|
|
34
38
|
this.log(this.config, this.$t(this.messages.IMPORT_MSG, { module: 'Events' }), 'info');
|
|
35
|
-
|
|
39
|
+
await this.init();
|
|
36
40
|
await fsUtil.makeDirectory(this.eventMapperDirPath);
|
|
37
41
|
const { dirName, fileName } = this.eventsConfig;
|
|
38
|
-
const eventsPath = resolve(
|
|
42
|
+
const eventsPath = resolve(
|
|
43
|
+
sanitizePath(this.config.data),
|
|
44
|
+
sanitizePath(this.personalizeConfig.dirName),
|
|
45
|
+
sanitizePath(dirName),
|
|
46
|
+
sanitizePath(fileName),
|
|
47
|
+
);
|
|
39
48
|
|
|
40
49
|
if (existsSync(eventsPath)) {
|
|
41
50
|
try {
|
|
@@ -43,8 +52,13 @@ export default class Events extends PersonalizationAdapter<ImportConfig> {
|
|
|
43
52
|
|
|
44
53
|
for (const event of events) {
|
|
45
54
|
const { key, description, uid } = event;
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
try {
|
|
56
|
+
const eventsResponse = await this.createEvents({ key, description });
|
|
57
|
+
this.eventsUidMapper[uid] = eventsResponse?.uid ?? '';
|
|
58
|
+
} catch (error) {
|
|
59
|
+
this.log(this.config, `failed to create event uid: ${uid}`, 'error');
|
|
60
|
+
this.log(this.config, error, 'error');
|
|
61
|
+
}
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
fsUtil.writeFile(this.eventsUidMapperPath, this.eventsUidMapper);
|
|
@@ -4,8 +4,14 @@ import values from 'lodash/values';
|
|
|
4
4
|
import cloneDeep from 'lodash/cloneDeep';
|
|
5
5
|
import { sanitizePath } from '@contentstack/cli-utilities';
|
|
6
6
|
import { PersonalizationAdapter, fsUtil, lookUpAudiences, lookUpEvents } from '../utils';
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
APIConfig,
|
|
9
|
+
ImportConfig,
|
|
10
|
+
ExperienceStruct,
|
|
11
|
+
CreateExperienceInput,
|
|
12
|
+
LogType,
|
|
13
|
+
CreateExperienceVersionInput,
|
|
14
|
+
} from '../types';
|
|
9
15
|
export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
10
16
|
private createdCTs: string[];
|
|
11
17
|
private mapperDirPath: string;
|
|
@@ -29,43 +35,56 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
29
35
|
private cmsVariantGroups: Record<string, unknown>;
|
|
30
36
|
private experiencesUidMapper: Record<string, string>;
|
|
31
37
|
private pendingVariantAndVariantGrpForExperience: string[];
|
|
32
|
-
private
|
|
33
|
-
private
|
|
34
|
-
private
|
|
38
|
+
private audiencesUid: Record<string, string>;
|
|
39
|
+
private eventsUid: Record<string, string>;
|
|
40
|
+
private personalizeConfig: ImportConfig['modules']['personalize'];
|
|
41
|
+
private audienceConfig: ImportConfig['modules']['personalize']['audiences'];
|
|
42
|
+
private experienceConfig: ImportConfig['modules']['personalize']['experiences'];
|
|
35
43
|
|
|
36
44
|
constructor(public readonly config: ImportConfig, private readonly log: LogType = console.log) {
|
|
37
45
|
const conf: APIConfig = {
|
|
38
46
|
config,
|
|
39
|
-
baseURL: config.modules.
|
|
40
|
-
headers: { 'X-Project-Uid': config.modules.
|
|
47
|
+
baseURL: config.modules.personalize.baseURL[config.region.name],
|
|
48
|
+
headers: { 'X-Project-Uid': config.modules.personalize.project_id},
|
|
41
49
|
cmaConfig: {
|
|
42
50
|
baseURL: config.region.cma + `/v3`,
|
|
43
|
-
headers: {
|
|
51
|
+
headers: { api_key: config.apiKey },
|
|
44
52
|
},
|
|
45
53
|
};
|
|
46
54
|
super(Object.assign(config, conf));
|
|
47
|
-
this.
|
|
55
|
+
this.personalizeConfig = this.config.modules.personalize;
|
|
48
56
|
this.experiencesDirPath = resolve(
|
|
49
57
|
sanitizePath(this.config.data),
|
|
50
|
-
sanitizePath(this.
|
|
51
|
-
sanitizePath(this.
|
|
58
|
+
sanitizePath(this.personalizeConfig.dirName),
|
|
59
|
+
sanitizePath(this.personalizeConfig.experiences.dirName),
|
|
60
|
+
);
|
|
61
|
+
this.experiencesPath = join(
|
|
62
|
+
sanitizePath(this.experiencesDirPath),
|
|
63
|
+
sanitizePath(this.personalizeConfig.experiences.fileName),
|
|
64
|
+
);
|
|
65
|
+
this.experienceConfig = this.personalizeConfig.experiences;
|
|
66
|
+
this.audienceConfig = this.personalizeConfig.audiences;
|
|
67
|
+
this.mapperDirPath = resolve(
|
|
68
|
+
sanitizePath(this.config.backupDir),
|
|
69
|
+
'mapper',
|
|
70
|
+
sanitizePath(this.personalizeConfig.dirName),
|
|
52
71
|
);
|
|
53
|
-
this.experiencesPath = join(sanitizePath(this.experiencesDirPath), sanitizePath(this.personalizationConfig.experiences.fileName));
|
|
54
|
-
this.experienceConfig = this.personalizationConfig.experiences;
|
|
55
|
-
this.audienceConfig = this.personalizationConfig.audiences;
|
|
56
|
-
this.mapperDirPath = resolve(sanitizePath(this.config.backupDir), 'mapper', sanitizePath(this.personalizationConfig.dirName));
|
|
57
72
|
this.expMapperDirPath = resolve(sanitizePath(this.mapperDirPath), sanitizePath(this.experienceConfig.dirName));
|
|
58
73
|
this.experiencesUidMapperPath = resolve(sanitizePath(this.expMapperDirPath), 'uid-mapping.json');
|
|
59
74
|
this.cmsVariantGroupPath = resolve(sanitizePath(this.expMapperDirPath), 'cms-variant-groups.json');
|
|
60
75
|
this.cmsVariantPath = resolve(sanitizePath(this.expMapperDirPath), 'cms-variants.json');
|
|
61
|
-
this.audiencesMapperPath = resolve(
|
|
76
|
+
this.audiencesMapperPath = resolve(
|
|
77
|
+
sanitizePath(this.mapperDirPath),
|
|
78
|
+
sanitizePath(this.audienceConfig.dirName),
|
|
79
|
+
'uid-mapping.json',
|
|
80
|
+
);
|
|
62
81
|
this.eventsMapperPath = resolve(sanitizePath(this.mapperDirPath), 'events', 'uid-mapping.json');
|
|
63
82
|
this.failedCmsExpPath = resolve(sanitizePath(this.expMapperDirPath), 'failed-cms-experience.json');
|
|
64
83
|
this.failedCmsExpPath = resolve(sanitizePath(this.expMapperDirPath), 'failed-cms-experience.json');
|
|
65
84
|
this.experienceCTsPath = resolve(sanitizePath(this.experiencesDirPath), 'experiences-content-types.json');
|
|
66
85
|
this.experienceVariantsIdsPath = resolve(
|
|
67
86
|
sanitizePath(this.config.data),
|
|
68
|
-
sanitizePath(this.
|
|
87
|
+
sanitizePath(this.personalizeConfig.dirName),
|
|
69
88
|
sanitizePath(this.experienceConfig.dirName),
|
|
70
89
|
'experiences-variants-ids.json',
|
|
71
90
|
);
|
|
@@ -79,6 +98,8 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
79
98
|
this.pendingVariantAndVariantGrpForExperience = [];
|
|
80
99
|
this.cTsSuccessPath = resolve(sanitizePath(this.config.backupDir), 'mapper', 'content_types', 'success.json');
|
|
81
100
|
this.createdCTs = [];
|
|
101
|
+
this.audiencesUid = (fsUtil.readFile(this.audiencesMapperPath, true) as Record<string, string>) || {};
|
|
102
|
+
this.eventsUid = (fsUtil.readFile(this.eventsMapperPath, true) as Record<string, string>) || {};
|
|
82
103
|
}
|
|
83
104
|
|
|
84
105
|
/**
|
|
@@ -86,25 +107,31 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
86
107
|
*/
|
|
87
108
|
async import() {
|
|
88
109
|
this.log(this.config, this.$t(this.messages.IMPORT_MSG, { module: 'Experiences' }), 'info');
|
|
89
|
-
|
|
110
|
+
await this.init();
|
|
90
111
|
await fsUtil.makeDirectory(this.expMapperDirPath);
|
|
91
112
|
|
|
92
113
|
if (existsSync(this.experiencesPath)) {
|
|
93
114
|
try {
|
|
94
115
|
const experiences = fsUtil.readFile(this.experiencesPath, true) as ExperienceStruct[];
|
|
95
|
-
const audiencesUid = (fsUtil.readFile(this.audiencesMapperPath, true) as Record<string, string>) || {};
|
|
96
|
-
const eventsUid = (fsUtil.readFile(this.eventsMapperPath, true) as Record<string, string>) || {};
|
|
97
116
|
|
|
98
117
|
for (const experience of experiences) {
|
|
99
118
|
const { uid, ...restExperienceData } = experience;
|
|
100
119
|
//check whether reference audience exists or not that referenced in variations having __type equal to AudienceBasedVariation & targeting
|
|
101
|
-
let experienceReqObj: CreateExperienceInput = lookUpAudiences(restExperienceData, audiencesUid);
|
|
120
|
+
let experienceReqObj: CreateExperienceInput = lookUpAudiences(restExperienceData, this.audiencesUid);
|
|
102
121
|
//check whether events exists or not that referenced in metrics
|
|
103
|
-
experienceReqObj = lookUpEvents(experienceReqObj, eventsUid);
|
|
122
|
+
experienceReqObj = lookUpEvents(experienceReqObj, this.eventsUid);
|
|
104
123
|
|
|
105
|
-
const expRes = await this.createExperience(experienceReqObj);
|
|
124
|
+
const expRes = (await this.createExperience(experienceReqObj)) as ExperienceStruct;
|
|
106
125
|
//map old experience uid to new experience uid
|
|
107
126
|
this.experiencesUidMapper[uid] = expRes?.uid ?? '';
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
// import versions of experience
|
|
130
|
+
await this.importExperienceVersions(expRes, uid);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
this.log(this.config, `Error while importing experience versions of ${expRes.uid}`, 'error');
|
|
133
|
+
this.log(this.config, error, 'error');
|
|
134
|
+
}
|
|
108
135
|
}
|
|
109
136
|
fsUtil.writeFile(this.experiencesUidMapperPath, this.experiencesUidMapper);
|
|
110
137
|
this.log(this.config, this.$t(this.messages.CREATE_SUCCESS, { module: 'Experiences' }), 'info');
|
|
@@ -117,9 +144,10 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
117
144
|
if (jobRes)
|
|
118
145
|
this.log(this.config, this.$t(this.messages.CREATE_SUCCESS, { module: 'Variant & Variant groups' }), 'info');
|
|
119
146
|
|
|
120
|
-
if (this.
|
|
147
|
+
if (this.personalizeConfig.importData) {
|
|
121
148
|
this.log(this.config, this.messages.UPDATING_CT_IN_EXP, 'info');
|
|
122
149
|
await this.attachCTsInExperience();
|
|
150
|
+
this.log(this.config, this.messages.UPDATED_CT_IN_EXP, 'info');
|
|
123
151
|
}
|
|
124
152
|
|
|
125
153
|
await this.createVariantIdMapper();
|
|
@@ -131,9 +159,71 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
131
159
|
}
|
|
132
160
|
|
|
133
161
|
/**
|
|
134
|
-
* function
|
|
135
|
-
|
|
136
|
-
|
|
162
|
+
* function import experience versions from a JSON file and creates them in the project.
|
|
163
|
+
*/
|
|
164
|
+
async importExperienceVersions(experience: ExperienceStruct, oldExperienceUid: string) {
|
|
165
|
+
const versionsPath = resolve(sanitizePath(this.experiencesDirPath), 'versions', `${sanitizePath(oldExperienceUid)}.json`);
|
|
166
|
+
|
|
167
|
+
if (!existsSync(versionsPath)) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const versions = fsUtil.readFile(versionsPath, true) as ExperienceStruct[];
|
|
172
|
+
const versionMap: Record<string, CreateExperienceVersionInput | undefined> = {
|
|
173
|
+
ACTIVE: undefined,
|
|
174
|
+
DRAFT: undefined,
|
|
175
|
+
PAUSE: undefined,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Process each version and map them by status
|
|
179
|
+
versions.forEach((version) => {
|
|
180
|
+
let versionReqObj = lookUpAudiences(version, this.audiencesUid) as CreateExperienceVersionInput;
|
|
181
|
+
versionReqObj = lookUpEvents(version, this.eventsUid) as CreateExperienceVersionInput;
|
|
182
|
+
|
|
183
|
+
if (versionReqObj && versionReqObj.status) {
|
|
184
|
+
versionMap[versionReqObj.status] = versionReqObj;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Prioritize updating or creating versions based on the order: ACTIVE -> DRAFT -> PAUSE
|
|
189
|
+
return await this.handleVersionUpdateOrCreate(experience, versionMap);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Helper method to handle version update or creation logic
|
|
193
|
+
private async handleVersionUpdateOrCreate(
|
|
194
|
+
experience: ExperienceStruct,
|
|
195
|
+
versionMap: Record<string, CreateExperienceVersionInput | undefined>,
|
|
196
|
+
) {
|
|
197
|
+
const { ACTIVE, DRAFT, PAUSE } = versionMap;
|
|
198
|
+
let latestVersionUsed = false;
|
|
199
|
+
|
|
200
|
+
if (ACTIVE) {
|
|
201
|
+
await this.updateExperienceVersion(experience.uid, experience.latestVersion, ACTIVE);
|
|
202
|
+
latestVersionUsed = true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (DRAFT) {
|
|
206
|
+
if (latestVersionUsed) {
|
|
207
|
+
await this.createExperienceVersion(experience.uid, DRAFT);
|
|
208
|
+
} else {
|
|
209
|
+
await this.updateExperienceVersion(experience.uid, experience.latestVersion, DRAFT);
|
|
210
|
+
latestVersionUsed = true;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (PAUSE) {
|
|
215
|
+
if (latestVersionUsed) {
|
|
216
|
+
await this.createExperienceVersion(experience.uid, PAUSE);
|
|
217
|
+
} else {
|
|
218
|
+
await this.updateExperienceVersion(experience.uid, experience.latestVersion, PAUSE);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* function to validate if all variant groups and variants have been created using personalize background job
|
|
225
|
+
* store the variant groups data in mapper/personalize/experiences/cms-variant-groups.json and the variants data
|
|
226
|
+
* in mapper/personalize/experiences/cms-variants.json. If not, invoke validateVariantGroupAndVariantsCreated after some delay.
|
|
137
227
|
* @param retryCount Counter to track the number of times the function has been called
|
|
138
228
|
* @returns
|
|
139
229
|
*/
|
|
@@ -160,7 +250,7 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
160
250
|
);
|
|
161
251
|
return this.validateVariantGroupAndVariantsCreated(retryCount);
|
|
162
252
|
} else {
|
|
163
|
-
this.log(this.config, this.messages.
|
|
253
|
+
this.log(this.config, this.messages.PERSONALIZE_JOB_FAILURE, 'error');
|
|
164
254
|
fsUtil.writeFile(this.failedCmsExpPath, this.pendingVariantAndVariantGrpForExperience);
|
|
165
255
|
return false;
|
|
166
256
|
}
|
|
@@ -185,8 +275,8 @@ export default class Experiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
185
275
|
Object.entries(this.experiencesUidMapper).map(async ([oldExpUid, newExpUid]) => {
|
|
186
276
|
if (experienceCTsMap[oldExpUid]?.length) {
|
|
187
277
|
// Filter content types that were created
|
|
188
|
-
const updatedContentTypes = experienceCTsMap[oldExpUid].filter(
|
|
189
|
-
this.createdCTs.includes(ct?.uid),
|
|
278
|
+
const updatedContentTypes = experienceCTsMap[oldExpUid].filter(
|
|
279
|
+
(ct: any) => this.createdCTs.includes(ct?.uid) && ct.status === 'linked',
|
|
190
280
|
);
|
|
191
281
|
if (updatedContentTypes?.length) {
|
|
192
282
|
const { variant_groups: [variantGroup] = [] } =
|
package/src/import/project.ts
CHANGED
|
@@ -9,14 +9,14 @@ export default class Project extends PersonalizationAdapter<ImportConfig> {
|
|
|
9
9
|
constructor(public readonly config: ImportConfig, private readonly log: LogType = console.log) {
|
|
10
10
|
const conf: APIConfig = {
|
|
11
11
|
config,
|
|
12
|
-
baseURL: config.modules.
|
|
13
|
-
headers: { organization_uid: config.org_uid
|
|
12
|
+
baseURL: config.modules.personalize.baseURL[config.region.name],
|
|
13
|
+
headers: { organization_uid: config.org_uid },
|
|
14
14
|
};
|
|
15
15
|
super(Object.assign(config, conf));
|
|
16
16
|
this.projectMapperFolderPath = pResolve(
|
|
17
17
|
sanitizePath(this.config.backupDir),
|
|
18
18
|
'mapper',
|
|
19
|
-
sanitizePath(this.config.modules.
|
|
19
|
+
sanitizePath(this.config.modules.personalize.dirName),
|
|
20
20
|
'projects',
|
|
21
21
|
);
|
|
22
22
|
}
|
|
@@ -26,19 +26,19 @@ export default class Project extends PersonalizationAdapter<ImportConfig> {
|
|
|
26
26
|
* data.
|
|
27
27
|
*/
|
|
28
28
|
async import() {
|
|
29
|
-
const
|
|
30
|
-
const { dirName, fileName } =
|
|
31
|
-
const projectPath = join(sanitizePath(this.config.data), sanitizePath(
|
|
32
|
-
|
|
29
|
+
const personalize = this.config.modules.personalize;
|
|
30
|
+
const { dirName, fileName } = personalize.projects;
|
|
31
|
+
const projectPath = join(sanitizePath(this.config.data), sanitizePath(personalize.dirName), sanitizePath(dirName), sanitizePath(fileName));
|
|
32
|
+
|
|
33
33
|
if (existsSync(projectPath)) {
|
|
34
34
|
const projects = JSON.parse(readFileSync(projectPath, 'utf8')) as CreateProjectInput[];
|
|
35
35
|
|
|
36
36
|
if (!projects || projects.length < 1) {
|
|
37
|
-
this.config.modules.
|
|
37
|
+
this.config.modules.personalize.importData = false; // Stop personalize import if stack not connected to any project
|
|
38
38
|
this.log(this.config, 'No project found!', 'info');
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
await this.init();
|
|
42
42
|
for (const project of projects) {
|
|
43
43
|
const createProject = async (newName: void | string): Promise<ProjectStruct> => {
|
|
44
44
|
return await this.createProject({
|
|
@@ -46,7 +46,7 @@ export default class Project extends PersonalizationAdapter<ImportConfig> {
|
|
|
46
46
|
description: project.description,
|
|
47
47
|
connectedStackApiKey: this.config.apiKey,
|
|
48
48
|
}).catch(async (error) => {
|
|
49
|
-
if (error
|
|
49
|
+
if (error.includes('personalization.PROJECTS.DUPLICATE_NAME') || error.includes('personalize.PROJECTS.DUPLICATE_NAME')) {
|
|
50
50
|
const projectName = await askProjectName('Copy Of ' + (newName || project.name));
|
|
51
51
|
return await createProject(projectName);
|
|
52
52
|
}
|
|
@@ -56,15 +56,15 @@ export default class Project extends PersonalizationAdapter<ImportConfig> {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
const projectRes = await createProject(this.config.personalizeProjectName);
|
|
59
|
-
this.config.modules.
|
|
60
|
-
this.config.modules.
|
|
59
|
+
this.config.modules.personalize.project_id = projectRes.uid;
|
|
60
|
+
this.config.modules.personalize.importData = true;
|
|
61
61
|
|
|
62
62
|
await fsUtil.makeDirectory(this.projectMapperFolderPath);
|
|
63
63
|
fsUtil.writeFile(pResolve(sanitizePath(this.projectMapperFolderPath), 'projects.json'), projectRes);
|
|
64
64
|
this.log(this.config, `Project Created Successfully: ${projectRes.uid}`, 'info');
|
|
65
65
|
}
|
|
66
66
|
} else {
|
|
67
|
-
this.config.modules.
|
|
67
|
+
this.config.modules.personalize.importData = false; // Stop personalize import if stack not connected to any project
|
|
68
68
|
this.log(this.config, 'No project found!', 'info');
|
|
69
69
|
}
|
|
70
70
|
}
|