UNPKG

@contentstack/cli-variants

Version:

Variants plugin

329 lines (328 loc) 23 kB
"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;