-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathaudiences.ts
More file actions
168 lines (147 loc) · 7.39 KB
/
audiences.ts
File metadata and controls
168 lines (147 loc) · 7.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import { resolve } from 'path';
import { existsSync } from 'fs';
import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities';
import { APIConfig, AudienceStruct, ImportConfig } from '../types';
import { PersonalizationAdapter, fsUtil, lookUpAttributes } from '../utils';
import { PROCESS_NAMES, MODULE_CONTEXTS, IMPORT_PROCESS_STATUS } from '../utils/constants';
export default class Audiences extends PersonalizationAdapter<ImportConfig> {
private mapperDirPath: string;
private audienceMapperDirPath: string;
private attributesMapperPath: string;
private audiencesUidMapperPath: string;
private audiencesUidMapper: Record<string, unknown>;
private personalizeConfig: ImportConfig['modules']['personalize'];
private audienceConfig: ImportConfig['modules']['personalize']['audiences'];
public attributeConfig: ImportConfig['modules']['personalize']['attributes'];
private audiences: AudienceStruct[];
constructor(public readonly config: ImportConfig) {
const conf: APIConfig = {
config,
baseURL: config.modules.personalize.baseURL[config.region.name],
headers: { 'X-Project-Uid': config.modules.personalize.project_id },
};
super(Object.assign(config, conf));
this.personalizeConfig = this.config.modules.personalize;
this.audienceConfig = this.personalizeConfig.audiences;
this.attributeConfig = this.personalizeConfig.attributes;
this.mapperDirPath = resolve(
sanitizePath(this.config.backupDir),
'mapper',
sanitizePath(this.personalizeConfig.dirName),
);
this.audienceMapperDirPath = resolve(sanitizePath(this.mapperDirPath), sanitizePath(this.audienceConfig.dirName));
this.audiencesUidMapperPath = resolve(sanitizePath(this.audienceMapperDirPath), 'uid-mapping.json');
this.attributesMapperPath = resolve(
sanitizePath(this.mapperDirPath),
sanitizePath(this.attributeConfig.dirName),
'uid-mapping.json',
);
this.audiencesUidMapper = {};
this.config.context.module = MODULE_CONTEXTS.AUDIENCES;
this.audiences = [];
}
/**
* The function asynchronously imports audiences from a JSON file and creates them in the system.
*/
async import() {
try {
log.debug('Starting audiences import...', this.config.context);
const [canImport, audiencesCount] = await this.analyzeAudiences();
if (!canImport) {
log.info('No audiences found to import', this.config.context);
// Still need to mark as complete for parent progress
if (this.parentProgressManager) {
this.parentProgressManager.tick(true, 'audiences module (no data)', null, PROCESS_NAMES.AUDIENCES);
}
return;
}
// Don't create own progress manager if we have a parent
let progress;
if (this.parentProgressManager) {
progress = this.parentProgressManager;
log.debug('Using parent progress manager for audiences import', this.config.context);
this.parentProgressManager.updateProcessTotal(PROCESS_NAMES.AUDIENCES, audiencesCount);
} else {
progress = this.createSimpleProgress(PROCESS_NAMES.AUDIENCES, audiencesCount);
log.debug('Created standalone progress manager for audiences import', this.config.context);
}
await this.init();
await fsUtil.makeDirectory(this.audienceMapperDirPath);
log.debug(`Created mapper directory: ${this.audienceMapperDirPath}`, this.config.context);
const attributesUid = (fsUtil.readFile(this.attributesMapperPath, true) as Record<string, string>) || {};
log.debug(
`Loaded ${Object.keys(attributesUid).length} attribute mappings for audience processing`,
this.config.context,
);
for (const audience of this.audiences) {
let { name, definition, description, uid } = audience;
if (!this.parentProgressManager) {
progress.updateStatus(IMPORT_PROCESS_STATUS[PROCESS_NAMES.AUDIENCES].CREATING);
}
log.debug(`Processing audience: ${name} (${uid})`, this.config.context);
// Skip Lytics audiences - they cannot be created via API (synced from Lytics)
if ((audience as any).source?.toUpperCase() === 'LYTICS') {
log.debug(`Skipping Lytics audience: ${name} (${uid})`, this.config.context);
this.updateProgress(true, `audience: ${name} (skipped - Lytics)`, undefined, PROCESS_NAMES.AUDIENCES);
continue;
}
try {
//check whether reference attributes exists or not
if (definition?.rules?.length) {
log.debug(
`Processing ${definition.rules.length} definition rules for audience: ${name}`,
this.config.context,
);
definition.rules = lookUpAttributes(definition.rules, attributesUid);
log.debug(`Processed definition rules, remaining rules: ${definition.rules.length}`, this.config.context);
} else {
log.debug(`No definition rules found for audience: ${name}`, this.config.context);
}
log.debug(`Creating audience: ${name}`, this.config.context);
const audienceRes = await this.createAudience({ definition, name, description });
//map old audience uid to new audience uid
//mapper file is used to check whether audience created or not before creating experience
this.audiencesUidMapper[uid] = audienceRes?.uid ?? '';
this.updateProgress(true, `audience: ${name}`, undefined, PROCESS_NAMES.AUDIENCES);
log.debug(`Created audience: ${uid} -> ${audienceRes?.uid}`, this.config.context);
} catch (error) {
this.updateProgress(false, `audience: ${name}`, (error as any)?.message, PROCESS_NAMES.AUDIENCES);
handleAndLogError(error, this.config.context, `Failed to create audience: ${name} (${uid})`);
}
}
fsUtil.writeFile(this.audiencesUidMapperPath, this.audiencesUidMapper);
log.debug(`Saved ${Object.keys(this.audiencesUidMapper).length} audience mappings`, this.config.context);
// Only complete progress if we own the progress manager (no parent)
if (!this.parentProgressManager) {
this.completeProgress(true);
}
log.success(`Audiences imported successfully! Total audiences: ${audiencesCount}`, this.config.context);
} catch (error) {
if (!this.parentProgressManager) {
this.completeProgress(false, (error as any)?.message || 'Audiences import failed');
}
handleAndLogError(error, this.config.context);
throw error;
}
}
private async analyzeAudiences(): Promise<[boolean, number]> {
return this.withLoadingSpinner('AUDIENCES: Analyzing import data...', async () => {
const { dirName, fileName } = this.audienceConfig;
const audiencesPath = resolve(
sanitizePath(this.config.contentDir),
sanitizePath(this.personalizeConfig.dirName),
sanitizePath(dirName),
sanitizePath(fileName),
);
log.debug(`Checking for audiences file: ${audiencesPath}`, this.config.context);
if (!existsSync(audiencesPath)) {
log.warn(`Audiences file not found: ${audiencesPath}`, this.config.context);
return [false, 0];
}
this.audiences = fsUtil.readFile(audiencesPath, true) as AudienceStruct[];
const audiencesCount = this.audiences?.length || 0;
log.debug(`Found ${audiencesCount} audiences to import`, this.config.context);
return [audiencesCount > 0, audiencesCount];
});
}
}