@contentstack/cli-variants
Version:
Variants plugin
329 lines (328 loc) • 23 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const fs_1 = require("fs");
const values_1 = __importDefault(require("lodash/values"));
const cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
const cli_utilities_1 = require("@contentstack/cli-utilities");
const utils_1 = require("../utils");
class Experiences extends utils_1.PersonalizationAdapter {
constructor(config) {
var _a, _b, _c, _d;
const conf = {
config,
baseURL: config.modules.personalize.baseURL[config.region.name],
headers: { 'X-Project-Uid': config.modules.personalize.project_id },
cmaConfig: {
baseURL: config.region.cma + `/v3`,
headers: { api_key: config.apiKey },
},
};
super(Object.assign(config, conf));
this.config = config;
this.personalizeConfig = this.config.modules.personalize;
this.experiencesDirPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.config.data), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.dirName), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.experiences.dirName));
this.experiencesPath = (0, path_1.join)((0, cli_utilities_1.sanitizePath)(this.experiencesDirPath), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.experiences.fileName));
this.experienceConfig = this.personalizeConfig.experiences;
this.audienceConfig = this.personalizeConfig.audiences;
this.mapperDirPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.config.backupDir), 'mapper', (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.dirName));
this.expMapperDirPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.mapperDirPath), (0, cli_utilities_1.sanitizePath)(this.experienceConfig.dirName));
this.experiencesUidMapperPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.expMapperDirPath), 'uid-mapping.json');
this.cmsVariantGroupPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.expMapperDirPath), 'cms-variant-groups.json');
this.cmsVariantPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.expMapperDirPath), 'cms-variants.json');
this.audiencesMapperPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.mapperDirPath), (0, cli_utilities_1.sanitizePath)(this.audienceConfig.dirName), 'uid-mapping.json');
this.eventsMapperPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.mapperDirPath), 'events', 'uid-mapping.json');
this.failedCmsExpPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.expMapperDirPath), 'failed-cms-experience.json');
this.failedCmsExpPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.expMapperDirPath), 'failed-cms-experience.json');
this.experienceCTsPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.experiencesDirPath), 'experiences-content-types.json');
this.experienceVariantsIdsPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.config.data), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.dirName), (0, cli_utilities_1.sanitizePath)(this.experienceConfig.dirName), 'experiences-variants-ids.json');
this.variantUidMapperFilePath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.expMapperDirPath), 'variants-uid-mapping.json');
this.experiencesUidMapper = {};
this.cmsVariantGroups = {};
this.cmsVariants = {};
this.expThresholdTimer = (_b = (_a = this.experienceConfig) === null || _a === void 0 ? void 0 : _a.thresholdTimer) !== null && _b !== void 0 ? _b : 30000;
this.expCheckIntervalDuration = (_d = (_c = this.experienceConfig) === null || _c === void 0 ? void 0 : _c.checkIntervalDuration) !== null && _d !== void 0 ? _d : 5000;
this.maxValidateRetry = Math.round(this.expThresholdTimer / this.expCheckIntervalDuration);
this.pendingVariantAndVariantGrpForExperience = [];
this.cTsSuccessPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.config.backupDir), 'mapper', 'content_types', 'success.json');
this.createdCTs = [];
this.audiencesUid = utils_1.fsUtil.readFile(this.audiencesMapperPath, true) || {};
this.eventsUid = utils_1.fsUtil.readFile(this.eventsMapperPath, true) || {};
this.config.context.module = 'experiences';
}
/**
* The function asynchronously imports experiences from a JSON file and creates them in the system.
*/
import() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
yield this.init();
yield utils_1.fsUtil.makeDirectory(this.expMapperDirPath);
cli_utilities_1.log.debug(`Created mapper directory: ${this.expMapperDirPath}`, this.config.context);
if ((0, fs_1.existsSync)(this.experiencesPath)) {
cli_utilities_1.log.debug(`Loading experiences from: ${this.experiencesPath}`, this.config.context);
try {
const experiences = utils_1.fsUtil.readFile(this.experiencesPath, true);
cli_utilities_1.log.info(`Found ${experiences.length} experiences to import`, this.config.context);
for (const experience of experiences) {
const { uid } = experience, restExperienceData = __rest(experience, ["uid"]);
cli_utilities_1.log.debug(`Processing experience: ${uid}`, this.config.context);
//check whether reference audience exists or not that referenced in variations having __type equal to AudienceBasedVariation & targeting
let experienceReqObj = (0, utils_1.lookUpAudiences)(restExperienceData, this.audiencesUid);
//check whether events exists or not that referenced in metrics
experienceReqObj = (0, utils_1.lookUpEvents)(experienceReqObj, this.eventsUid);
const expRes = (yield this.createExperience(experienceReqObj));
//map old experience uid to new experience uid
this.experiencesUidMapper[uid] = (_a = expRes === null || expRes === void 0 ? void 0 : expRes.uid) !== null && _a !== void 0 ? _a : '';
cli_utilities_1.log.debug(`Created experience: ${uid} -> ${expRes === null || expRes === void 0 ? void 0 : expRes.uid}`, this.config.context);
try {
// import versions of experience
yield this.importExperienceVersions(expRes, uid);
}
catch (error) {
(0, cli_utilities_1.handleAndLogError)(error, this.config.context, `Failed to import experience versions for ${expRes.uid}`);
}
}
utils_1.fsUtil.writeFile(this.experiencesUidMapperPath, this.experiencesUidMapper);
cli_utilities_1.log.success('Experiences created successfully', this.config.context);
cli_utilities_1.log.info('Validating variant and variant group creation', this.config.context);
this.pendingVariantAndVariantGrpForExperience = (0, values_1.default)((0, cloneDeep_1.default)(this.experiencesUidMapper));
const jobRes = yield this.validateVariantGroupAndVariantsCreated();
utils_1.fsUtil.writeFile(this.cmsVariantPath, this.cmsVariants);
utils_1.fsUtil.writeFile(this.cmsVariantGroupPath, this.cmsVariantGroups);
if (jobRes) {
cli_utilities_1.log.success('Variant and variant groups created successfully', this.config.context);
}
else {
cli_utilities_1.log.error('Failed to create variants and variant groups', this.config.context);
this.personalizeConfig.importData = false;
}
if (this.personalizeConfig.importData) {
cli_utilities_1.log.info('Attaching content types to experiences', this.config.context);
yield this.attachCTsInExperience();
cli_utilities_1.log.success('Content types attached to experiences successfully', this.config.context);
}
yield this.createVariantIdMapper();
}
catch (error) {
(0, cli_utilities_1.handleAndLogError)(error, this.config.context);
}
}
else {
cli_utilities_1.log.warn(`Experiences file not found: ${this.experiencesPath}`, this.config.context);
}
});
}
/**
* function import experience versions from a JSON file and creates them in the project.
*/
importExperienceVersions(experience, oldExperienceUid) {
return __awaiter(this, void 0, void 0, function* () {
cli_utilities_1.log.debug(`Importing versions for experience: ${oldExperienceUid}`, this.config.context);
const versionsPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(this.experiencesDirPath), 'versions', `${(0, cli_utilities_1.sanitizePath)(oldExperienceUid)}.json`);
if (!(0, fs_1.existsSync)(versionsPath)) {
cli_utilities_1.log.debug(`No versions file found for experience: ${oldExperienceUid}`, this.config.context);
return;
}
const versions = utils_1.fsUtil.readFile(versionsPath, true);
cli_utilities_1.log.debug(`Found ${versions.length} versions for experience: ${oldExperienceUid}`, this.config.context);
const versionMap = {
ACTIVE: undefined,
DRAFT: undefined,
PAUSE: undefined,
};
// Process each version and map them by status
versions.forEach((version) => {
let versionReqObj = (0, utils_1.lookUpAudiences)(version, this.audiencesUid);
versionReqObj = (0, utils_1.lookUpEvents)(version, this.eventsUid);
if (versionReqObj && versionReqObj.status) {
versionMap[versionReqObj.status] = versionReqObj;
cli_utilities_1.log.debug(`Mapped version with status: ${versionReqObj.status}`, this.config.context);
}
});
// Prioritize updating or creating versions based on the order: ACTIVE -> DRAFT -> PAUSE
return yield this.handleVersionUpdateOrCreate(experience, versionMap);
});
}
// Helper method to handle version update or creation logic
handleVersionUpdateOrCreate(experience, versionMap) {
return __awaiter(this, void 0, void 0, function* () {
cli_utilities_1.log.debug(`Handling version update/create for experience: ${experience.uid}`, this.config.context);
const { ACTIVE, DRAFT, PAUSE } = versionMap;
let latestVersionUsed = false;
if (ACTIVE) {
cli_utilities_1.log.debug(`Updating experience version to ACTIVE for: ${experience.uid}`, this.config.context);
yield this.updateExperienceVersion(experience.uid, experience.latestVersion, ACTIVE);
latestVersionUsed = true;
}
if (DRAFT) {
if (latestVersionUsed) {
cli_utilities_1.log.debug(`Creating new DRAFT version for: ${experience.uid}`, this.config.context);
yield this.createExperienceVersion(experience.uid, DRAFT);
}
else {
cli_utilities_1.log.debug(`Updating experience version to DRAFT for: ${experience.uid}`, this.config.context);
yield this.updateExperienceVersion(experience.uid, experience.latestVersion, DRAFT);
latestVersionUsed = true;
}
}
if (PAUSE) {
if (latestVersionUsed) {
cli_utilities_1.log.debug(`Creating new PAUSE version for: ${experience.uid}`, this.config.context);
yield this.createExperienceVersion(experience.uid, PAUSE);
}
else {
cli_utilities_1.log.debug(`Updating experience version to PAUSE for: ${experience.uid}`, this.config.context);
yield this.updateExperienceVersion(experience.uid, experience.latestVersion, PAUSE);
}
}
});
}
/**
* function to validate if all variant groups and variants have been created using personalize background job
* store the variant groups data in mapper/personalize/experiences/cms-variant-groups.json and the variants data
* in mapper/personalize/experiences/cms-variants.json. If not, invoke validateVariantGroupAndVariantsCreated after some delay.
* @param retryCount Counter to track the number of times the function has been called
* @returns
*/
validateVariantGroupAndVariantsCreated() {
return __awaiter(this, arguments, void 0, function* (retryCount = 0) {
var _a;
cli_utilities_1.log.debug(`Validating variant groups and variants creation - attempt ${retryCount + 1}/${this.maxValidateRetry}`, this.config.context);
try {
const promises = this.pendingVariantAndVariantGrpForExperience.map((expUid) => __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g;
cli_utilities_1.log.debug(`Checking experience: ${expUid}`, this.config.context);
const expRes = yield this.getExperience(expUid);
const variants = (_b = (_a = expRes === null || expRes === void 0 ? void 0 : expRes._cms) === null || _a === void 0 ? void 0 : _a.variants) !== null && _b !== void 0 ? _b : {};
if ((expRes === null || expRes === void 0 ? void 0 : expRes._cms) && ((_c = expRes === null || expRes === void 0 ? void 0 : expRes._cms) === null || _c === void 0 ? void 0 : _c.variantGroup) && Object.keys(variants).length > 0) {
cli_utilities_1.log.debug(`Found variants and variant group for experience: ${expUid}`, this.config.context);
this.cmsVariants[expUid] = (_e = (_d = expRes._cms) === null || _d === void 0 ? void 0 : _d.variants) !== null && _e !== void 0 ? _e : {};
this.cmsVariantGroups[expUid] = (_g = (_f = expRes._cms) === null || _f === void 0 ? void 0 : _f.variantGroup) !== null && _g !== void 0 ? _g : {};
return expUid; // Return the expUid for filtering later
}
else {
cli_utilities_1.log.debug(`Variants/variant group not ready for experience: ${expUid}`, this.config.context);
}
}));
yield Promise.all(promises);
retryCount++;
if ((_a = this.pendingVariantAndVariantGrpForExperience) === null || _a === void 0 ? void 0 : _a.length) {
if (retryCount !== this.maxValidateRetry) {
cli_utilities_1.log.debug(`Waiting ${this.expCheckIntervalDuration}ms before retry`, this.config.context);
yield this.delay(this.expCheckIntervalDuration);
// Filter out the processed elements
this.pendingVariantAndVariantGrpForExperience = this.pendingVariantAndVariantGrpForExperience.filter((uid) => !this.cmsVariants[uid]);
return this.validateVariantGroupAndVariantsCreated(retryCount);
}
else {
cli_utilities_1.log.error('Personalize job failed to create variants and variant groups', this.config.context);
cli_utilities_1.log.error(`Failed experiences: ${this.pendingVariantAndVariantGrpForExperience.join(', ')}`, this.config.context);
utils_1.fsUtil.writeFile(this.failedCmsExpPath, this.pendingVariantAndVariantGrpForExperience);
return false;
}
}
else {
cli_utilities_1.log.debug('All variant groups and variants created successfully', this.config.context);
return true;
}
}
catch (error) {
(0, cli_utilities_1.handleAndLogError)(error, this.config.context);
throw error;
}
});
}
attachCTsInExperience() {
return __awaiter(this, void 0, void 0, function* () {
cli_utilities_1.log.debug('Attaching content types to experiences', this.config.context);
try {
// Read the created content types from the file
this.createdCTs = utils_1.fsUtil.readFile(this.cTsSuccessPath, true);
if (!this.createdCTs) {
cli_utilities_1.log.debug('No Content types created, skipping following process', this.config.context);
return;
}
cli_utilities_1.log.debug(`Found ${this.createdCTs.length} created content types`, this.config.context);
const experienceCTsMap = utils_1.fsUtil.readFile(this.experienceCTsPath, true);
return yield Promise.allSettled(Object.entries(this.experiencesUidMapper).map((_a) => __awaiter(this, [_a], void 0, function* ([oldExpUid, newExpUid]) {
var _b;
if ((_b = experienceCTsMap[oldExpUid]) === null || _b === void 0 ? void 0 : _b.length) {
cli_utilities_1.log.debug(`Processing content types for experience: ${oldExpUid} -> ${newExpUid}`, this.config.context);
// Filter content types that were created
const updatedContentTypes = experienceCTsMap[oldExpUid].filter((ct) => this.createdCTs.includes(ct === null || ct === void 0 ? void 0 : ct.uid) && ct.status === 'linked');
if (updatedContentTypes === null || updatedContentTypes === void 0 ? void 0 : updatedContentTypes.length) {
cli_utilities_1.log.debug(`Attaching ${updatedContentTypes.length} content types to experience: ${newExpUid}`, this.config.context);
const { variant_groups: [variantGroup] = [] } = (yield this.getVariantGroup({ experienceUid: newExpUid })) || {};
variantGroup.content_types = updatedContentTypes;
// Update content types detail in the new experience asynchronously
return yield this.updateVariantGroup(variantGroup);
}
else {
cli_utilities_1.log.debug(`No valid content types found for experience: ${newExpUid}`, this.config.context);
}
}
else {
cli_utilities_1.log.debug(`No content types mapped for experience: ${oldExpUid}`, this.config.context);
}
})));
}
catch (error) {
(0, cli_utilities_1.handleAndLogError)(error, this.config.context, 'Failed to attach content type with experience');
}
});
}
createVariantIdMapper() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
cli_utilities_1.log.debug('Creating variant ID mapper', this.config.context);
try {
const experienceVariantIds = utils_1.fsUtil.readFile(this.experienceVariantsIdsPath, true) || [];
cli_utilities_1.log.debug(`Found ${experienceVariantIds.length} experience variant IDs to process`, this.config.context);
const variantUIDMapper = {};
for (let experienceVariantId of experienceVariantIds) {
const [experienceId, variantShortId, oldVariantId] = experienceVariantId.split('-');
const latestVariantId = (_a = this.cmsVariants[this.experiencesUidMapper[experienceId]]) === null || _a === void 0 ? void 0 : _a[variantShortId];
if (latestVariantId) {
variantUIDMapper[oldVariantId] = latestVariantId;
cli_utilities_1.log.debug(`Mapped variant ID: ${oldVariantId} -> ${latestVariantId}`, this.config.context);
}
else {
cli_utilities_1.log.warn(`Could not find variant ID mapping for: ${experienceVariantId}`, this.config.context);
}
}
cli_utilities_1.log.debug(`Created ${Object.keys(variantUIDMapper).length} variant ID mappings`, this.config.context);
utils_1.fsUtil.writeFile(this.variantUidMapperFilePath, variantUIDMapper);
cli_utilities_1.log.debug(`Variant ID mapper saved to: ${this.variantUidMapperFilePath}`, this.config.context);
}
catch (error) {
(0, cli_utilities_1.handleAndLogError)(error, this.config.context, 'Failed to create variant ID mapper');
throw error;
}
});
}
}
exports.default = Experiences;