@contentstack/cli-variants 1.3.3 → 2.0.0-beta
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 +51 -23
- package/lib/export/audiences.d.ts +2 -2
- package/lib/export/audiences.js +50 -24
- package/lib/export/events.d.ts +2 -2
- package/lib/export/events.js +52 -24
- package/lib/export/experiences.js +87 -54
- package/lib/export/projects.d.ts +3 -2
- package/lib/export/projects.js +55 -63
- package/lib/export/variant-entries.d.ts +19 -0
- package/lib/export/variant-entries.js +76 -1
- package/lib/import/attribute.d.ts +2 -0
- package/lib/import/attribute.js +83 -37
- package/lib/import/audiences.d.ts +2 -0
- package/lib/import/audiences.js +85 -41
- package/lib/import/events.d.ts +3 -1
- package/lib/import/events.js +86 -30
- package/lib/import/experiences.d.ts +2 -0
- package/lib/import/experiences.js +93 -39
- package/lib/import/project.d.ts +2 -0
- package/lib/import/project.js +81 -22
- package/lib/import/variant-entries.d.ts +10 -0
- package/lib/import/variant-entries.js +132 -47
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/types/export-config.d.ts +0 -2
- package/lib/types/import-config.d.ts +0 -1
- package/lib/types/utils.d.ts +1 -1
- package/lib/utils/constants.d.ts +91 -0
- package/lib/utils/constants.js +93 -0
- package/lib/utils/personalization-api-adapter.d.ts +34 -1
- package/lib/utils/personalization-api-adapter.js +171 -44
- package/lib/utils/variant-api-adapter.d.ts +28 -1
- package/lib/utils/variant-api-adapter.js +75 -0
- package/package.json +1 -1
- package/src/export/attributes.ts +84 -34
- package/src/export/audiences.ts +87 -41
- package/src/export/events.ts +84 -41
- package/src/export/experiences.ts +155 -83
- package/src/export/projects.ts +71 -39
- package/src/export/variant-entries.ts +136 -12
- package/src/import/attribute.ts +105 -49
- package/src/import/audiences.ts +110 -54
- package/src/import/events.ts +104 -41
- package/src/import/experiences.ts +140 -62
- package/src/import/project.ts +108 -38
- package/src/import/variant-entries.ts +179 -65
- package/src/index.ts +2 -1
- package/src/types/export-config.ts +0 -2
- package/src/types/import-config.ts +0 -1
- package/src/types/utils.ts +1 -1
- package/src/utils/constants.ts +98 -0
- package/src/utils/personalization-api-adapter.ts +202 -66
- package/src/utils/variant-api-adapter.ts +82 -1
- package/tsconfig.json +1 -1
package/src/export/projects.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { sanitizePath, log } from '@contentstack/cli-utilities';
|
|
3
|
-
import { ExportConfig,
|
|
4
|
-
import {
|
|
1
|
+
import { resolve as pResolve } from 'node:path';
|
|
2
|
+
import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities';
|
|
3
|
+
import { PersonalizeConfig, ExportConfig, ProjectStruct } from '../types';
|
|
4
|
+
import { fsUtil, PersonalizationAdapter } from '../utils';
|
|
5
|
+
import { PROCESS_NAMES, MODULE_CONTEXTS, EXPORT_PROCESS_STATUS } from '../utils/constants';
|
|
5
6
|
|
|
6
7
|
export default class ExportProjects extends PersonalizationAdapter<ExportConfig> {
|
|
7
|
-
private
|
|
8
|
+
private projectsFolderPath: string;
|
|
9
|
+
private projectsData: ProjectStruct[];
|
|
8
10
|
public exportConfig: ExportConfig;
|
|
9
11
|
public personalizeConfig: PersonalizeConfig;
|
|
12
|
+
|
|
10
13
|
constructor(exportConfig: ExportConfig) {
|
|
11
14
|
super({
|
|
12
15
|
config: exportConfig,
|
|
@@ -15,57 +18,86 @@ export default class ExportProjects extends PersonalizationAdapter<ExportConfig>
|
|
|
15
18
|
});
|
|
16
19
|
this.exportConfig = exportConfig;
|
|
17
20
|
this.personalizeConfig = exportConfig.modules.personalize;
|
|
18
|
-
this.
|
|
21
|
+
this.projectsFolderPath = pResolve(
|
|
19
22
|
sanitizePath(exportConfig.data),
|
|
20
23
|
sanitizePath(exportConfig.branchName || ''),
|
|
21
24
|
sanitizePath(this.personalizeConfig.dirName),
|
|
22
25
|
'projects',
|
|
23
26
|
);
|
|
24
|
-
this.
|
|
27
|
+
this.projectsData = [];
|
|
28
|
+
this.exportConfig.context.module = MODULE_CONTEXTS.PROJECTS;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
async start() {
|
|
28
32
|
try {
|
|
29
33
|
log.debug('Starting projects export process...', this.exportConfig.context);
|
|
30
|
-
log.info(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
await this.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
log.info('Starting projects export', this.exportConfig.context);
|
|
35
|
+
|
|
36
|
+
// Initial setup with loading spinner
|
|
37
|
+
await this.withLoadingSpinner('PROJECTS: Initializing export and fetching data...', async () => {
|
|
38
|
+
log.debug('Initializing personalization adapter...', this.exportConfig.context);
|
|
39
|
+
await this.init();
|
|
40
|
+
log.debug('Personalization adapter initialized successfully', this.exportConfig.context);
|
|
41
|
+
|
|
42
|
+
log.debug(`Creating projects directory at: ${this.projectsFolderPath}`, this.exportConfig.context);
|
|
43
|
+
await fsUtil.makeDirectory(this.projectsFolderPath);
|
|
44
|
+
log.debug('Projects directory created successfully', this.exportConfig.context);
|
|
45
|
+
|
|
46
|
+
log.debug('Fetching projects from personalization API...', this.exportConfig.context);
|
|
47
|
+
// talisman-ignore-line
|
|
48
|
+
this.projectsData = (await this.projects({ connectedStackApiKey: this.exportConfig.apiKey })) || [];
|
|
49
|
+
log.debug(`Fetched ${this.projectsData?.length || 0} projects`, this.exportConfig.context);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (!this.projectsData?.length) {
|
|
45
53
|
log.debug('No projects found, disabling personalization', this.exportConfig.context);
|
|
46
|
-
log.info(
|
|
54
|
+
log.info('No Personalize Project connected with the given stack', this.exportConfig.context);
|
|
47
55
|
this.exportConfig.personalizationEnabled = false;
|
|
48
56
|
return;
|
|
49
57
|
}
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
|
|
59
|
+
// Enable personalization and set project config
|
|
60
|
+
log.debug(`Found ${this.projectsData.length} projects, enabling personalization`, this.exportConfig.context);
|
|
52
61
|
this.exportConfig.personalizationEnabled = true;
|
|
53
|
-
this.exportConfig.project_id =
|
|
54
|
-
log.debug(`Set project ID: ${
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
} catch (error) {
|
|
63
|
-
if (error !== 'Forbidden') {
|
|
64
|
-
log.debug(`Error occurred during projects export: ${error}`, this.exportConfig.context);
|
|
65
|
-
log.error('Failed to export projects!', this.exportConfig.context);
|
|
62
|
+
this.exportConfig.project_id = this.projectsData[0]?.uid;
|
|
63
|
+
log.debug(`Set project ID: ${this.projectsData[0]?.uid}`, this.exportConfig.context);
|
|
64
|
+
|
|
65
|
+
let progress: any;
|
|
66
|
+
if (this.parentProgressManager) {
|
|
67
|
+
progress = this.parentProgressManager;
|
|
68
|
+
this.progressManager = this.parentProgressManager;
|
|
69
|
+
// Parent already has correct count, just update status
|
|
70
|
+
progress.updateStatus(EXPORT_PROCESS_STATUS[PROCESS_NAMES.PROJECTS].EXPORTING, PROCESS_NAMES.PROJECTS);
|
|
66
71
|
} else {
|
|
67
|
-
|
|
72
|
+
progress = this.createNestedProgress(PROCESS_NAMES.PROJECTS);
|
|
73
|
+
progress.addProcess(PROCESS_NAMES.PROJECTS, this.projectsData?.length);
|
|
74
|
+
progress
|
|
75
|
+
.startProcess(PROCESS_NAMES.PROJECTS)
|
|
76
|
+
.updateStatus(EXPORT_PROCESS_STATUS[PROCESS_NAMES.PROJECTS].EXPORTING, PROCESS_NAMES.PROJECTS);
|
|
68
77
|
}
|
|
78
|
+
|
|
79
|
+
const projectsFilePath = pResolve(sanitizePath(this.projectsFolderPath), 'projects.json');
|
|
80
|
+
log.debug(`Writing projects to: ${projectsFilePath}`, this.exportConfig.context);
|
|
81
|
+
fsUtil.writeFile(projectsFilePath, this.projectsData);
|
|
82
|
+
log.debug('Projects export completed successfully', this.exportConfig.context);
|
|
83
|
+
|
|
84
|
+
const processName = PROCESS_NAMES.PROJECTS;
|
|
85
|
+
this.updateProgress(true, 'project export', undefined, processName);
|
|
86
|
+
|
|
87
|
+
// Complete process only if we're managing our own progress
|
|
88
|
+
if (!this.parentProgressManager) {
|
|
89
|
+
progress.completeProcess(PROCESS_NAMES.PROJECTS, true);
|
|
90
|
+
this.completeProgress(true);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
log.success(
|
|
94
|
+
`Projects exported successfully! Total projects: ${this.projectsData.length} - personalization enabled`,
|
|
95
|
+
this.exportConfig.context,
|
|
96
|
+
);
|
|
97
|
+
} catch (error: any) {
|
|
98
|
+
log.debug(`Error occurred during projects export: ${error}`, this.exportConfig.context);
|
|
99
|
+
this.completeProgress(false, error?.message || 'Projects export failed');
|
|
100
|
+
handleAndLogError(error, { ...this.exportConfig.context });
|
|
69
101
|
throw error;
|
|
70
102
|
}
|
|
71
103
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync } from 'fs';
|
|
2
2
|
import { join, resolve } from 'path';
|
|
3
|
-
import { FsUtility, sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities';
|
|
3
|
+
import { FsUtility, sanitizePath, log, handleAndLogError, CLIProgressManager } from '@contentstack/cli-utilities';
|
|
4
|
+
import { PROCESS_NAMES, EXPORT_PROCESS_STATUS } from '../utils/constants';
|
|
4
5
|
|
|
5
6
|
import { APIConfig, AdapterType, ExportConfig } from '../types';
|
|
6
7
|
import VariantAdapter, { VariantHttpClient } from '../utils/variant-api-adapter';
|
|
@@ -8,6 +9,12 @@ import VariantAdapter, { VariantHttpClient } from '../utils/variant-api-adapter'
|
|
|
8
9
|
export default class VariantEntries extends VariantAdapter<VariantHttpClient<ExportConfig>> {
|
|
9
10
|
public entriesDirPath: string;
|
|
10
11
|
public variantEntryBasePath!: string;
|
|
12
|
+
protected progressManager: CLIProgressManager | null = null;
|
|
13
|
+
protected parentProgressManager: CLIProgressManager | null = null;
|
|
14
|
+
public progress: any;
|
|
15
|
+
private processInitialized: boolean = false;
|
|
16
|
+
private totalVariantCount: number = 0;
|
|
17
|
+
private processedVariantCount: number = 0;
|
|
11
18
|
|
|
12
19
|
constructor(readonly config: ExportConfig) {
|
|
13
20
|
const conf: APIConfig & AdapterType<VariantHttpClient<ExportConfig>, APIConfig> = {
|
|
@@ -33,6 +40,39 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Exp
|
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Set parent progress manager for integration with entries module
|
|
45
|
+
*/
|
|
46
|
+
public setParentProgressManager(parentProgress: CLIProgressManager): void {
|
|
47
|
+
this.parentProgressManager = parentProgress;
|
|
48
|
+
this.progressManager = parentProgress;
|
|
49
|
+
this.progress = parentProgress;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Update progress for a specific item
|
|
54
|
+
*/
|
|
55
|
+
protected updateProgress(success: boolean, itemName: string, error?: string, processName?: string): void {
|
|
56
|
+
if (this.progress) {
|
|
57
|
+
this.progress.tick(success, itemName, error, processName);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Complete the variant entries export process
|
|
63
|
+
*/
|
|
64
|
+
public completeExport(): void {
|
|
65
|
+
if (this.processInitialized && this.progress) {
|
|
66
|
+
this.progress.completeProcess(PROCESS_NAMES.VARIANT_ENTRIES, true);
|
|
67
|
+
log.success(
|
|
68
|
+
`Completed export of ${this.totalVariantCount} variant entries across all content types and locales`,
|
|
69
|
+
this.config.context,
|
|
70
|
+
);
|
|
71
|
+
} else if (this.totalVariantCount === 0) {
|
|
72
|
+
log.info(`No variant entries found for export`, this.config.context);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
36
76
|
/**
|
|
37
77
|
* This function exports variant entries for a specific content type and locale.
|
|
38
78
|
* @param options - The `exportVariantEntry` function takes in an `options` object with the following
|
|
@@ -41,18 +81,27 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Exp
|
|
|
41
81
|
async exportVariantEntry(options: { locale: string; contentTypeUid: string; entries: Record<string, any>[] }) {
|
|
42
82
|
const variantEntry = this.config.modules.variantEntry;
|
|
43
83
|
const { entries, locale, contentTypeUid: content_type_uid } = options;
|
|
44
|
-
|
|
45
|
-
log.debug(
|
|
84
|
+
|
|
85
|
+
log.debug(
|
|
86
|
+
`Starting variant entries export for content type: ${content_type_uid}, locale: ${locale}`,
|
|
87
|
+
this.config.context,
|
|
88
|
+
);
|
|
46
89
|
log.debug(`Processing ${entries.length} entries for variant export`, this.config.context);
|
|
47
|
-
|
|
90
|
+
|
|
48
91
|
log.debug('Initializing variant instance...', this.config.context);
|
|
49
92
|
await this.variantInstance.init();
|
|
50
93
|
log.debug('Variant instance initialized successfully', this.config.context);
|
|
51
|
-
|
|
94
|
+
|
|
95
|
+
let localVariantCount = 0; // Track variants found in this specific call
|
|
96
|
+
let processedEntries = 0;
|
|
97
|
+
|
|
52
98
|
for (let index = 0; index < entries.length; index++) {
|
|
53
99
|
const entry = entries[index];
|
|
54
|
-
log.debug(
|
|
55
|
-
|
|
100
|
+
log.debug(
|
|
101
|
+
`Processing variant entries for entry: ${entry.title} (${entry.uid}) - ${index + 1}/${entries.length}`,
|
|
102
|
+
this.config.context,
|
|
103
|
+
);
|
|
104
|
+
|
|
56
105
|
const variantEntryBasePath = join(
|
|
57
106
|
sanitizePath(this.entriesDirPath),
|
|
58
107
|
sanitizePath(content_type_uid),
|
|
@@ -61,7 +110,7 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Exp
|
|
|
61
110
|
sanitizePath(entry.uid),
|
|
62
111
|
);
|
|
63
112
|
log.debug(`Variant entry base path: ${variantEntryBasePath}`, this.config.context);
|
|
64
|
-
|
|
113
|
+
|
|
65
114
|
const variantEntriesFs = new FsUtility({
|
|
66
115
|
isArray: true,
|
|
67
116
|
keepMetadata: false,
|
|
@@ -73,9 +122,38 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Exp
|
|
|
73
122
|
});
|
|
74
123
|
log.debug('Initialized FsUtility for variant entries', this.config.context);
|
|
75
124
|
|
|
125
|
+
let entryHasVariants = false;
|
|
126
|
+
let variantCount = 0;
|
|
127
|
+
|
|
76
128
|
const callback = (variantEntries: Record<string, any>[]) => {
|
|
77
|
-
log.debug(
|
|
129
|
+
log.debug(
|
|
130
|
+
`Callback received ${variantEntries?.length || 0} variant entries for entry: ${entry.uid}`,
|
|
131
|
+
this.config.context,
|
|
132
|
+
);
|
|
78
133
|
if (variantEntries?.length) {
|
|
134
|
+
log.info(`Fetched ${variantEntries.length} variant entries for entry: ${entry.uid}`, this.config.context);
|
|
135
|
+
entryHasVariants = true;
|
|
136
|
+
variantCount = variantEntries.length;
|
|
137
|
+
localVariantCount += variantCount;
|
|
138
|
+
this.totalVariantCount += variantCount;
|
|
139
|
+
|
|
140
|
+
// Initialize progress ONLY when we find the first variants globally (lazy initialization)
|
|
141
|
+
if (!this.processInitialized && this.progress) {
|
|
142
|
+
this.progress.addProcess(PROCESS_NAMES.VARIANT_ENTRIES, variantCount);
|
|
143
|
+
this.progress.startProcess(PROCESS_NAMES.VARIANT_ENTRIES);
|
|
144
|
+
this.processInitialized = true;
|
|
145
|
+
log.debug(
|
|
146
|
+
`Initialized variant entries progress with first batch of ${variantCount} variants`,
|
|
147
|
+
this.config.context,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Update total as we discover more variants globally
|
|
152
|
+
if (this.processInitialized && this.progress) {
|
|
153
|
+
this.progress.updateProcessTotal(PROCESS_NAMES.VARIANT_ENTRIES, this.totalVariantCount);
|
|
154
|
+
log.debug(`Updated progress total to: ${this.totalVariantCount}`, this.config.context);
|
|
155
|
+
}
|
|
156
|
+
|
|
79
157
|
if (!existsSync(variantEntryBasePath)) {
|
|
80
158
|
log.debug(`Creating directory: ${variantEntryBasePath}`, this.config.context);
|
|
81
159
|
mkdirSync(variantEntryBasePath, { recursive: true });
|
|
@@ -96,7 +174,7 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Exp
|
|
|
96
174
|
entry_uid: entry.uid,
|
|
97
175
|
locale,
|
|
98
176
|
});
|
|
99
|
-
|
|
177
|
+
|
|
100
178
|
if (existsSync(variantEntryBasePath)) {
|
|
101
179
|
log.debug(`Completing file for entry: ${entry.uid}`, this.config.context);
|
|
102
180
|
variantEntriesFs.completeFile(true);
|
|
@@ -107,8 +185,39 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Exp
|
|
|
107
185
|
} else {
|
|
108
186
|
log.debug(`No variant entries directory created for entry: ${entry.uid}`, this.config.context);
|
|
109
187
|
}
|
|
188
|
+
|
|
189
|
+
// After processing this entry, update progress for variants found
|
|
190
|
+
if (entryHasVariants && this.processInitialized) {
|
|
191
|
+
// Tick progress for each variant found in this entry
|
|
192
|
+
for (let i = 0; i < variantCount; i++) {
|
|
193
|
+
this.processedVariantCount++;
|
|
194
|
+
this.updateProgress(
|
|
195
|
+
true,
|
|
196
|
+
`Exported variant ${this.processedVariantCount}/${this.totalVariantCount} from ${entry.title || entry.uid}`,
|
|
197
|
+
undefined,
|
|
198
|
+
PROCESS_NAMES.VARIANT_ENTRIES,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
log.debug(
|
|
202
|
+
`Processed ${variantCount} variants for entry: ${entry.uid}, total processed: ${this.processedVariantCount}/${this.totalVariantCount}`,
|
|
203
|
+
this.config.context,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
processedEntries++;
|
|
110
208
|
} catch (error) {
|
|
111
209
|
log.debug(`Error occurred while exporting variant entries for entry: ${entry.uid}`, this.config.context);
|
|
210
|
+
|
|
211
|
+
// Track progress for failed entry
|
|
212
|
+
if (this.processInitialized) {
|
|
213
|
+
this.updateProgress(
|
|
214
|
+
false,
|
|
215
|
+
`Failed to export variants for entry: ${entry.title || entry.uid}`,
|
|
216
|
+
(error as any)?.message || 'Unknown error',
|
|
217
|
+
PROCESS_NAMES.VARIANT_ENTRIES,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
112
221
|
handleAndLogError(
|
|
113
222
|
error,
|
|
114
223
|
{ ...this.config.context },
|
|
@@ -116,7 +225,22 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Exp
|
|
|
116
225
|
);
|
|
117
226
|
}
|
|
118
227
|
}
|
|
119
|
-
|
|
120
|
-
|
|
228
|
+
|
|
229
|
+
if (localVariantCount > 0) {
|
|
230
|
+
log.success(
|
|
231
|
+
`Exported ${localVariantCount} variant entries across ${processedEntries} entries for ${content_type_uid}/${locale}`,
|
|
232
|
+
this.config.context,
|
|
233
|
+
);
|
|
234
|
+
} else {
|
|
235
|
+
log.info(
|
|
236
|
+
`No variant entries found for content type: ${content_type_uid}, locale: ${locale}`,
|
|
237
|
+
this.config.context,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
log.debug(
|
|
242
|
+
`Completed variant entries export for content type: ${content_type_uid}, locale: ${locale}. Local variants: ${localVariantCount}, Total variants so far: ${this.totalVariantCount}, Processed entries: ${processedEntries}`,
|
|
243
|
+
this.config.context,
|
|
244
|
+
);
|
|
121
245
|
}
|
|
122
246
|
}
|
package/src/import/attribute.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync } from 'fs';
|
|
|
3
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
|
+
import { PROCESS_NAMES, MODULE_CONTEXTS, IMPORT_PROCESS_STATUS } from '../utils/constants';
|
|
6
7
|
|
|
7
8
|
export default class Attribute extends PersonalizationAdapter<ImportConfig> {
|
|
8
9
|
private mapperDirPath: string;
|
|
@@ -11,15 +12,16 @@ export default class Attribute extends PersonalizationAdapter<ImportConfig> {
|
|
|
11
12
|
private attributesUidMapper: Record<string, unknown>;
|
|
12
13
|
private personalizeConfig: ImportConfig['modules']['personalize'];
|
|
13
14
|
private attributeConfig: ImportConfig['modules']['personalize']['attributes'];
|
|
15
|
+
private attributeData: AttributeStruct[];
|
|
14
16
|
|
|
15
|
-
constructor(public readonly config: ImportConfig) {
|
|
17
|
+
constructor(public readonly config: ImportConfig) {
|
|
16
18
|
const conf: APIConfig = {
|
|
17
19
|
config,
|
|
18
20
|
baseURL: config.modules.personalize.baseURL[config.region.name],
|
|
19
21
|
headers: { 'X-Project-Uid': config.modules.personalize.project_id },
|
|
20
22
|
};
|
|
21
23
|
super(Object.assign(config, conf));
|
|
22
|
-
|
|
24
|
+
|
|
23
25
|
this.personalizeConfig = this.config.modules.personalize;
|
|
24
26
|
this.attributeConfig = this.personalizeConfig.attributes;
|
|
25
27
|
this.mapperDirPath = resolve(
|
|
@@ -30,62 +32,116 @@ export default class Attribute extends PersonalizationAdapter<ImportConfig> {
|
|
|
30
32
|
this.attrMapperDirPath = resolve(sanitizePath(this.mapperDirPath), sanitizePath(this.attributeConfig.dirName));
|
|
31
33
|
this.attributesUidMapperPath = resolve(sanitizePath(this.attrMapperDirPath), 'uid-mapping.json');
|
|
32
34
|
this.attributesUidMapper = {};
|
|
33
|
-
this.config.context.module =
|
|
35
|
+
this.config.context.module = MODULE_CONTEXTS.ATTRIBUTES;
|
|
36
|
+
this.attributeData = [];
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
40
|
* The function asynchronously imports attributes from a JSON file and creates them in the system.
|
|
38
41
|
*/
|
|
39
|
-
async import() {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
async import() {
|
|
43
|
+
try {
|
|
44
|
+
log.debug('Starting attributes import...', this.config.context);
|
|
45
|
+
|
|
46
|
+
const [canImport, attributesCount] = await this.analyzeAttributes();
|
|
47
|
+
if (!canImport) {
|
|
48
|
+
log.info('No attributes found to import', this.config.context);
|
|
49
|
+
// Still need to mark as complete for parent progress
|
|
50
|
+
if (this.parentProgressManager) {
|
|
51
|
+
this.parentProgressManager.tick(true, 'attributes module (no data)', null, PROCESS_NAMES.ATTRIBUTES);
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Don't create own progress manager if we have a parent
|
|
57
|
+
let progress;
|
|
58
|
+
if (this.parentProgressManager) {
|
|
59
|
+
progress = this.parentProgressManager;
|
|
60
|
+
log.debug('Using parent progress manager for attributes import', this.config.context);
|
|
61
|
+
this.parentProgressManager.updateProcessTotal(PROCESS_NAMES.ATTRIBUTES, attributesCount);
|
|
62
|
+
|
|
63
|
+
} else {
|
|
64
|
+
progress = this.createSimpleProgress(PROCESS_NAMES.ATTRIBUTES, attributesCount);
|
|
65
|
+
log.debug('Created standalone progress manager for attributes import', this.config.context);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await this.init();
|
|
69
|
+
await fsUtil.makeDirectory(this.attrMapperDirPath);
|
|
70
|
+
log.debug(`Created mapper directory: ${this.attrMapperDirPath}`, this.config.context);
|
|
71
|
+
|
|
72
|
+
const { dirName, fileName } = this.attributeConfig;
|
|
73
|
+
log.info(`Processing ${attributesCount} attributes`, this.config.context);
|
|
74
|
+
|
|
75
|
+
for (const attribute of this.attributeData) {
|
|
76
|
+
const { key, name, description, uid } = attribute;
|
|
77
|
+
if (!this.parentProgressManager) {
|
|
78
|
+
progress.updateStatus(IMPORT_PROCESS_STATUS[PROCESS_NAMES.ATTRIBUTES].CREATING);
|
|
79
|
+
}
|
|
80
|
+
log.debug(`Processing attribute: ${name} - ${attribute.__type}`, this.config.context);
|
|
51
81
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
log.info(`Found ${attributes.length} attributes to import`, this.config.context);
|
|
58
|
-
|
|
59
|
-
for (const attribute of attributes) {
|
|
60
|
-
const { key, name, description, uid } = attribute;
|
|
61
|
-
log.debug(`Processing attribute: ${name} - ${attribute.__type}`, this.config.context);
|
|
62
|
-
|
|
63
|
-
// skip creating preset attributes, as they are already present in the system
|
|
64
|
-
if (attribute.__type === 'PRESET') {
|
|
65
|
-
log.debug(`Skipping preset attribute: ${name}`, this.config.context);
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
log.debug(`Creating custom attribute: ${name}`, this.config.context);
|
|
71
|
-
const attributeRes = await this.createAttribute({ key, name, description });
|
|
72
|
-
//map old attribute uid to new attribute uid
|
|
73
|
-
//mapper file is used to check whether attribute created or not before creating audience
|
|
74
|
-
this.attributesUidMapper[uid] = attributeRes?.uid ?? '';
|
|
75
|
-
log.debug(`Created attribute: ${uid} -> ${attributeRes?.uid}`, this.config.context);
|
|
76
|
-
} catch (error) {
|
|
77
|
-
handleAndLogError(error, this.config.context, `Failed to create attribute: ${name}`);
|
|
78
|
-
}
|
|
82
|
+
// skip creating preset attributes, as they are already present in the system
|
|
83
|
+
if (attribute.__type === 'PRESET') {
|
|
84
|
+
log.debug(`Skipping preset attribute: ${name}`, this.config.context);
|
|
85
|
+
this.updateProgress(true, `attribute: ${name} (preset - skipped)`, undefined, PROCESS_NAMES.ATTRIBUTES);
|
|
86
|
+
continue;
|
|
79
87
|
}
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
try {
|
|
90
|
+
log.debug(`Creating custom attribute: ${name}`, this.config.context);
|
|
91
|
+
const attributeRes = await this.createAttribute({ key, name, description });
|
|
92
|
+
//map old attribute uid to new attribute uid
|
|
93
|
+
//mapper file is used to check whether attribute created or not before creating audience
|
|
94
|
+
this.attributesUidMapper[uid] = attributeRes?.uid ?? '';
|
|
95
|
+
|
|
96
|
+
this.updateProgress(true, `attribute: ${name}`, undefined, PROCESS_NAMES.ATTRIBUTES);
|
|
97
|
+
log.debug(`Created attribute: ${uid} -> ${attributeRes?.uid}`, this.config.context);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
this.updateProgress(false, `attribute: ${name}`, (error as any)?.message, PROCESS_NAMES.ATTRIBUTES);
|
|
100
|
+
handleAndLogError(error, this.config.context, `Failed to create attribute: ${name}`);
|
|
101
|
+
}
|
|
86
102
|
}
|
|
87
|
-
|
|
88
|
-
|
|
103
|
+
|
|
104
|
+
fsUtil.writeFile(this.attributesUidMapperPath, this.attributesUidMapper);
|
|
105
|
+
log.debug(`Saved ${Object.keys(this.attributesUidMapper).length} attribute mappings`, this.config.context);
|
|
106
|
+
|
|
107
|
+
if (!this.parentProgressManager) {
|
|
108
|
+
this.completeProgress(true);
|
|
109
|
+
}
|
|
110
|
+
log.success(
|
|
111
|
+
`Attributes imported successfully! Total attributes: ${attributesCount} - personalization enabled`,
|
|
112
|
+
this.config.context,
|
|
113
|
+
);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (!this.parentProgressManager) {
|
|
116
|
+
this.completeProgress(false, (error as any)?.message || 'Attributes import failed');
|
|
117
|
+
}
|
|
118
|
+
handleAndLogError(error, this.config.context);
|
|
119
|
+
throw error;
|
|
89
120
|
}
|
|
90
121
|
}
|
|
122
|
+
|
|
123
|
+
private async analyzeAttributes(): Promise<[boolean, number]> {
|
|
124
|
+
return this.withLoadingSpinner('ATTRIBUTES: Analyzing import data...', async () => {
|
|
125
|
+
const { dirName, fileName } = this.attributeConfig;
|
|
126
|
+
const attributesPath = resolve(
|
|
127
|
+
sanitizePath(this.config.data),
|
|
128
|
+
sanitizePath(this.personalizeConfig.dirName),
|
|
129
|
+
sanitizePath(dirName),
|
|
130
|
+
sanitizePath(fileName),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
log.debug(`Checking for attributes file: ${attributesPath}`, this.config.context);
|
|
134
|
+
|
|
135
|
+
if (!existsSync(attributesPath)) {
|
|
136
|
+
log.warn(`Attributes file not found: ${attributesPath}`, this.config.context);
|
|
137
|
+
return [false, 0];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this.attributeData = fsUtil.readFile(attributesPath, true) as AttributeStruct[];
|
|
141
|
+
const attributesCount = this.attributeData?.length || 0;
|
|
142
|
+
|
|
143
|
+
log.debug(`Found ${attributesCount} attributes to import`, this.config.context);
|
|
144
|
+
return [attributesCount > 0, attributesCount];
|
|
145
|
+
});
|
|
146
|
+
}
|
|
91
147
|
}
|