import { getProjectPath, getProjectNotificationConfigs, getProjectNotificationDisplayConfigs, getProjectOutput, loadVariables, } from '../../../helpers/cluserHelper'; import fs from 'fs'; import path from 'path'; import { parse, stringify } from 'yaml'; import chalk from 'chalk'; // Import chalk import { v4 as uuidv4 } from 'uuid'; // Import uuid import pullerConfigYaml from './components/Entity_NotSynced_Puller.yaml'; import taskConfigYaml from './components/Entity_NotSynced_Task.yaml'; import tableConfigYaml from './components/Entity_NotSynced_Table.yaml'; import trelloPusherConfigYaml from './components/Entity_Trello_NotSynced_Pusher.yaml'; import detailTransformationConfigYaml from './components/Entity_NotSynced_NestedSQL_Transformation.yaml'; import nestedSQLErrorTransformationConfigYaml from './components/Entity_NotSynced_NestedSQL_ErrorMessage_Transformation.yaml'; import scribanErrorTransformationConfigYaml from './components/Entity_NotSynced_NestedSQL_ErrorMessage_Transformation.yaml'; import { getAssociation, getTask, getTaskColumn, normalizeName, } from '../../../helpers/profileGeneratorHelper'; import { globSync } from 'glob'; import crypto from 'crypto'; import { appendVariables } from '../../../helpers/variablesHelper'; import { generateConnection } from './connections/generateConnectionHelper'; // Removed the module declaration for "*.yaml" async function generatePullerNotification( targetPath: string, notificationConfig: any, profileTemplate: any, ) { var taskIdVariable = notificationConfig.taskId; let variables = loadVariables(); var taskId = variables[taskIdVariable]; var task = profileTemplate.objects?.find((task: any) => task.id === taskId); if (!task) { console.error(chalk.red(`Task with id ${taskId} not found`)); return; } var taskKey = notificationConfig.taskKey; var taskName = notificationConfig.taskName; var tableName = task.valueTableName; // var pullerConfig = parse(pullerConfigYaml); // 1. replace **Entity_ID_Field** to taskKey in pullerConfig var pullerYaml = pullerConfigYaml.replace( /\*\*Entity_ID_Field\*\*/g, taskKey, ); // 2. replace **Entity_Table_Name** to tableName in pullerConfig pullerYaml = pullerYaml.replace(/\*\*Entity_Table_Name\*\*/g, tableName); // 3. Capitalize all first letters of the taskName, and remove special characters: Sales +)_() Order => SalesOrder var formattedTaskName = taskName .replace(/[^a-zA-Z0-9 ]/g, '') .replace(/\b\w/g, (char) => char.toUpperCase()) .replace(/\s+/g, ''); var auditFileName = `${notificationConfig.source}_${formattedTaskName}_NotSyncedNotification_Puller`; var pullerFilePath = path.join(targetPath, `${auditFileName}.yaml`); var idVariable = `**${notificationConfig.source}_${formattedTaskName}_NotSyncedNotification_Puller_Id**`; if (fs.existsSync(pullerFilePath)) { console.error( chalk.red( `Puller with name ${auditFileName} already exists. Skipping creating a puller...`, ), ); return idVariable; } // 4. "**Entity_NotSyncedNotification_Puller_Id**": the "Entity_" prefix will be replaced by #3 pullerYaml = pullerYaml.replace( /\*\*Entity_NotSyncedNotification_Puller_Id\*\*/g, `**${notificationConfig.source}_${formattedTaskName}_NotSyncedNotification_Puller_Id**`, ); variables = appendVariables(variables, idVariable, notificationConfig.id); // 5. "**Entity_NotSyncedNotification_Puller_Name**": the "Entity_" prefix will be replaced by #3 pullerYaml = pullerYaml.replace( /\*\*Entity_NotSyncedNotification_Puller_Name\*\*/g, `**${notificationConfig.source}_${formattedTaskName}_NotSyncedNotification_Puller_Name**`, ); variables = appendVariables( variables, `**${notificationConfig.source}_${formattedTaskName}_NotSyncedNotification_Puller_Name**`, notificationConfig.name, ); // 5. "**Entity_NotSyncedNotification_Puller_Name**": the "Entity_" prefix will be replaced by #3 pullerYaml = pullerYaml.replace( /\*\*Entity_NotSyncedNotification_Puller_Description\*\*/g, notificationConfig.description, ); var projectPath = getProjectPath(); fs.writeFileSync(pullerFilePath, stringify({ pullers: [parse(pullerYaml)] })); // 9. Save to variables.yaml var variablePath = path.join(projectPath, 'variables.yaml'); fs.writeFileSync( variablePath, stringify( { variables: variables, }, { keepSourceTokens: true, lineWidth: 0 }, ), ); return idVariable; } async function generateTaskNotification( targetPath: string, targetOutput: string, notificationConfig: any, ) { console.log(chalk.green('Generating Task Notification...')); let variables = loadVariables(); var taskName = notificationConfig.taskName; var formattedTaskName = taskName .replace(/[^a-zA-Z0-9 ]/g, '') .replace(/\b\w/g, (char) => char.toUpperCase()) .replace(/\s+/g, ''); var idVariable = `**${notificationConfig.source}_${targetOutput}_${formattedTaskName}_NotSyncedNotification_Task_Id**`; var taskYaml = taskConfigYaml.replace( /\*\*Entity_NotSyncedNotification/g, `**${notificationConfig.source}_${targetOutput}_${formattedTaskName}_NotSyncedNotification`, ); variables = appendVariables(variables, idVariable, uuidv4()); variables = appendVariables( variables, `**${notificationConfig.source}_${targetOutput}_${formattedTaskName}_NotSyncedNotification_Task_Name**`, notificationConfig.name, ); variables = appendVariables( variables, `**${notificationConfig.source}_${targetOutput}_${formattedTaskName}_NotSyncedNotification_Task_Description**`, notificationConfig.description, ); variables = appendVariables( variables, `**${notificationConfig.source}_${targetOutput}_${formattedTaskName}_NotSyncedNotification_Table_Name**`, notificationConfig.notificationTableName, ); var projectPath = getProjectPath(); var auditFileName = `${notificationConfig.source}_${targetOutput}_${formattedTaskName}_NotSyncedNotification_Task`; fs.writeFileSync( path.join(targetPath, `${auditFileName}.yaml`), stringify({ objects: [parse(taskYaml)] }), ); // 9. Save to variables.yaml var variablePath = path.join(projectPath, 'variables.yaml'); fs.writeFileSync( variablePath, stringify( { variables: variables, }, { keepSourceTokens: true, lineWidth: 0 }, ), ); return idVariable; } async function generateTableNotification( targetPath: string, targetOutput: string, notificationConfig: any, ) { console.log(chalk.green('Generating Table Notification...')); let variables = loadVariables(); var taskName = notificationConfig.taskName; var formattedTaskName = taskName .replace(/[^a-zA-Z0-9 ]/g, '') .replace(/\b\w/g, (char) => char.toUpperCase()) .replace(/\s+/g, ''); var idVariable = `**${notificationConfig.source}_${targetOutput}_${formattedTaskName}_NotSyncedNotification_Table_Id**`; var tableYaml = tableConfigYaml.replace( /\*\*Entity_NotSyncedNotification/g, `**${notificationConfig.source}_${targetOutput}_${formattedTaskName}_NotSyncedNotification`, ); variables = appendVariables(variables, idVariable, uuidv4()); var projectPath = getProjectPath(); var auditFileName = `${notificationConfig.source}_${targetOutput}_${formattedTaskName}_NotSyncedNotification_Table`; fs.writeFileSync( path.join(targetPath, `${auditFileName}.yaml`), stringify({ tables: [parse(tableYaml)] }), ); // 9. Save to variables.yaml var projectPath = getProjectPath(); var variablePath = path.join(projectPath, 'variables.yaml'); fs.writeFileSync( variablePath, stringify( { variables: variables, }, { keepSourceTokens: true, lineWidth: 0 }, ), ); return idVariable; } async function generateDetailTransformations( targetPath: string, targetOutput: string, task: any, notificationConfig: any, ) { console.log( chalk.green('Generating Detail Transformation for Notification...'), ); let variables = loadVariables(); var withDetails = notificationConfig.withDetails || []; var taskName = notificationConfig.taskName; var formattedTaskName = taskName .replace(/[^a-zA-Z0-9 ]+/g, '') .replace(/\b\w/g, (char) => char.toUpperCase()) .replace(/\s+/g, ''); var taskTableNameVariable = Object.keys(variables).find( (key) => variables[key] === task.valueTableName, ) || task.valueTableName; var taskIdVariable = Object.keys(variables).find((key) => variables[key] === task.id) || task.id; for (var detail of withDetails.sort( (a: any, b: any) => (a.index || 0) - (b.index || 0), )) { var relatedTaskIdVariable = detail.taskId; var relatedTask = getTask(relatedTaskIdVariable); console.log( chalk.green( `Generating Detail Transformation ${relatedTaskIdVariable} for ${taskIdVariable} Notification...`, ), ); if (!relatedTask) { console.error( chalk.red(`Task with id ${relatedTaskIdVariable} not found`), ); return; } var relatedTaskTableNameVariable = Object.keys(variables).find( (key) => variables[key] === relatedTask.valueTableName, ); if (!relatedTaskTableNameVariable) { relatedTaskTableNameVariable = relatedTask.valueTableName; } let transformationYaml = detailTransformationConfigYaml; if (relatedTaskTableNameVariable == taskTableNameVariable) { var transformationConfig = parse(transformationYaml); transformationConfig.metadata.sql = `SELECT [RelatedDetailEntityFieldNameFields] FROM [EntityTable] v WHERE v.__id = @hss_id::uuid`; transformationYaml = stringify(transformationConfig); } var relatedTaskName = detail.name; // 1. Replace any special characters and whitespaces in detail.name to 1 whitespace; // 1.1 Capitalize all first letters of the taskName, and remove special characters: Sales +)_() Order => SalesOrder var capitalizedRelatedTaskName = relatedTaskName .replace(/[^a-zA-Z0-9 ]+/g, ' ') .replace(/\b\w/g, (char) => char.toUpperCase()); var formattedRelatedTaskName = capitalizedRelatedTaskName.replace( /\s+/g, '', ); // 1.2 convert #1 to slug (snake_case) format var slugRelatedTaskName = relatedTaskName .replace(/[^a-zA-Z0-9 ]+/g, ' ') .replace(/\s+/g, '_') .toLowerCase(); // 2. Replace **Entity_NotSyncedNotification to **{source}_{formattedTaskName}_NotSyncedNotification transformationYaml = transformationYaml.replace( /\*\*Entity_NotSyncedNotification/g, `**${notificationConfig.source}_${targetOutput}_${formattedTaskName}_NotSyncedNotification`, ); // 3. Replace [RelatedDetailEntity] to formattedRelatedTaskName in transformationYaml transformationYaml = transformationYaml.replace( /\[RelatedDetailEntity\]/g, formattedRelatedTaskName, ); // 4. Replace [RelatedDetailEntityFieldName] to slugRelatedTaskName in transformationYaml /* relatedTaskTableNameVariable == taskTableNameVariable ? `main_${slugRelatedTaskName}` : slugRelatedTaskName */ transformationYaml = transformationYaml.replace( /\[RelatedDetailEntityFieldName\]/g, slugRelatedTaskName, ); // 5. Replace [EntityTable] to taskTableNameVariable in transformationYaml transformationYaml = transformationYaml.replace( /\[EntityTable\]/g, taskTableNameVariable, ); // 6. Replace [RelatedEntityTable] to relatedTaskTableNameVariable in transformationYaml transformationYaml = transformationYaml.replace( /\[RelatedDetailEntityTable\]/g, relatedTaskTableNameVariable, ); var detailRelationships = detail.relationships || {}; var relationships = Object.keys(detailRelationships) .map((key: any) => { return `v.${key} = s.${detailRelationships[key]}`; }) .join(' and '); // 7. Replace [Relationships] to relationships in transformationYaml transformationYaml = transformationYaml.replace( /\[Relationships\]/g, relationships, ); var columns = (getTaskColumn(relatedTask) || []).filter( (c) => c.visibility === 1 || c.visibility === 'Visible', ); var displays = getProjectNotificationDisplayConfigs(); var relatedEntityDisplayColumns = displays[relatedTaskIdVariable] || []; relatedEntityDisplayColumns = [ ...relatedEntityDisplayColumns, ...columns.map((col: any) => col.name), ]; relatedEntityDisplayColumns = Array.from( new Set(relatedEntityDisplayColumns), ); // Ensure the array is distinct relatedEntityDisplayColumns = relatedEntityDisplayColumns .map((col: any) => `v.${col}`) .join(', '); // 8. replace [RelatedDetailEntityFieldNameFields] to relatedEntityDisplayColumns in transformationYaml transformationYaml = transformationYaml.replace( /\[RelatedDetailEntityFieldNameFields\]/g, relatedEntityDisplayColumns, ); var auditVarialbe = `${notificationConfig.source}_${targetOutput}_${formattedTaskName}_NotSyncedNotification_${formattedRelatedTaskName}_Detail_Transformation`; var auditFileName = `${auditVarialbe}.yaml`; fs.writeFileSync( path.join(targetPath, `${auditFileName}.yaml`), stringify({ transformations: { [taskIdVariable]: [parse(transformationYaml)] }, }), ); variables = appendVariables(variables, `**${auditVarialbe}_Id**`, uuidv4()); variables = appendVariables( variables, `**${auditVarialbe}_Index**`, detail.index || 0, ); variables = appendVariables( variables, `**${auditVarialbe}_Name**`, `${notificationConfig.name} - ${capitalizedRelatedTaskName} Detail Transformation`, ); } var projectPath = getProjectPath(); var variablePath = path.join(projectPath, 'variables.yaml'); fs.writeFileSync( variablePath, stringify( { variables: variables, }, { keepSourceTokens: true, lineWidth: 0 }, ), ); } async function generateErrorMessageTransformations( targetPath: string, targetOutput: string, task: any, notificationConfig: any, profileTemplate: any, ) { // console.log(chalk.green("Generating Error Message Transformation for Notification...")); let variables = loadVariables(); var taskId = variables[task.id] || task.id; var validations = (profileTemplate.validations || {})[taskId] || []; var legacyDependencies = Object.keys(task.dependencies || {}).map((key: any) => { var result = task.dependencies[key]; result.name = key; return result; }) || []; var dependencies = (profileTemplate.dependencies || []).filter( (dep: any) => dep.objectId === taskId, ) || []; var transformations = [ ...validations.map((v: any) => { v.__transformation_type = 'VALIDATIONS'; return v; }), ...legacyDependencies.map((l: any) => { l.__transformation_type = 'DEPENDENCIES'; return l; }), ...dependencies.map((d: any) => { d.__transformation_type = 'DEPENDENCIES'; return d; }), ]; var errorMessagePath = path.join(targetPath, '__error_messages'); if (!fs.existsSync(errorMessagePath)) { fs.mkdirSync(errorMessagePath); } var notificationTaskName = normalizeName(notificationConfig.taskName); var projectPath = getProjectPath(); var variablePath = path.join(projectPath, 'variables.yaml'); for (var transformation of transformations) { var transformationYML = nestedSQLErrorTransformationConfigYaml; // defaults to DEPENDENCIES transformation switch (transformation.__transformation_type) { case 'VALIDATIONS': transformationYML = scribanErrorTransformationConfigYaml; break; default: break; } var transformationName = normalizeName(transformation.name); var transformationType = normalizeName( transformation.__transformation_type, ); var transformationFileNamePrefix = `${notificationConfig.source}_${targetOutput}_${notificationTaskName.camelCaseName}_${transformationName.camelCaseName}`; console.log( chalk.green( `Generating Error Message Transformation ${transformationName} for ${task.id} Notification...`, ), ); // replace [Entity] with {notification.source}_{targetOutput}_{notificationTaskName}_{formattedTaskName} transformationYML = transformationYML.replace( /\[Entity\]/g, transformationFileNamePrefix, ); // md5 this `${notificationConfig.source}_${targetOutput}_${notificationTaskName}_${transformationName.camelCaseName}` var md5 = crypto .createHash('md5') .update(transformationFileNamePrefix) .digest('hex'); // replace [md5] to md5 transformationYML = transformationYML.replace(/\[md5\]/g, md5); var transformationConfig = parse(transformationYML); transformationConfig.__notes = stringify(transformation); variables = appendVariables( variables, `**${transformationFileNamePrefix}_ErrorMessage_Transformation_Id**`, uuidv4(), ); variables = appendVariables( variables, `**${transformationFileNamePrefix}_ErrorMessage_Transformation_Index**`, 0, ); variables = appendVariables( variables, `**${transformationFileNamePrefix}_ErrorMessage_Transformation_Name**`, `${notificationConfig.name} - ${transformationName.displayName} ${transformationType.displayName} Error Message Transformation`, ); fs.writeFileSync( variablePath, stringify( { variables: variables, }, { keepSourceTokens: true, lineWidth: 0 }, ), ); var auditPath = path.join( errorMessagePath, `${transformationFileNamePrefix}_ErrorMessage_Transformation.yaml`, ); if (fs.existsSync(auditPath)) { console.error( chalk.red( `The Error Message Transformation for ${transformationName.camelCaseName} already exists. Skipping creating a transformation...`, ), ); continue; } console.log( chalk.green(`Writing Error Message Transformation to ${auditPath}`), ); transformationYML = stringify({ transformations: { [task.id]: [transformationConfig] }, }); fs.writeFileSync(auditPath, transformationYML); } } async function generatePusherNotification( targetPath: string, targetOutput: 'trello' | 'lark' | 'email', task: any, notificationConfig: any, profileTemplate: any, ) { var pusherYaml = trelloPusherConfigYaml; switch (targetOutput) { case 'trello': pusherYaml = trelloPusherConfigYaml; break; default: console.log(chalk.red(`Output ${targetOutput} not supported`)); return; } var outputName = normalizeName(targetOutput); var taskName = normalizeName(notificationConfig.taskName); var pusherFileNamePrefix = `${notificationConfig.source}_${outputName.camelCaseName}_${taskName.camelCaseName}`; var idVariable = `**${pusherFileNamePrefix}_NotSyncedNotification_Pusher_Id**`; // replace [Entity] with pusherFileNamePrefix pusherYaml = pusherYaml.replace(/\[Entity\]/g, pusherFileNamePrefix); // replace [EntityName] with taskName.camelCaseName pusherYaml = pusherYaml.replace(/\[EntityName\]/g, taskName.camelCaseName); var variables = loadVariables(); variables = appendVariables( variables, `**${pusherFileNamePrefix}_NotSyncedNotification_Pusher_Id**`, uuidv4(), ); variables = appendVariables( variables, `**${pusherFileNamePrefix}_NotSyncedNotification_Pusher_Name**`, `${notificationConfig.name} - Trello Pusher`, ); var projectPath = getProjectPath(); var variablePath = path.join(projectPath, 'variables.yaml'); fs.writeFileSync( variablePath, stringify( { variables: variables, }, { keepSourceTokens: true, lineWidth: 0 }, ), ); var fileName = `${pusherFileNamePrefix}_NotSyncedNotification_Pusher.yaml`; var auditPath = path.join(targetPath, fileName); fs.writeFileSync(auditPath, pusherYaml); return idVariable; } async function generateNotification( output: 'trello' | 'lark' | 'email', targetPath: string, notificationConfig: any, profileTemplate: any, ) { var targetOutput = output .replace(/[^a-zA-Z0-9 ]+/g, ' ') .replace(/\b\w/g, (char) => char.toUpperCase()) .replace(/\s+/g, ''); generateConnection(output); var notificationPullerIdVariable = await generatePullerNotification( targetPath, notificationConfig, profileTemplate, ); var notificationTaskIdVariable = await generateTaskNotification( targetPath, targetOutput, notificationConfig, ); var notificationTableIdVariable = await generateTableNotification( targetPath, targetOutput, notificationConfig, ); var taskIdVariable = notificationConfig.taskId; var taskObj = getTask(taskIdVariable); await generateDetailTransformations( targetPath, targetOutput, taskObj, notificationConfig, ); await generateErrorMessageTransformations( targetPath, targetOutput, taskObj, notificationConfig, profileTemplate, ); var notificationPusherIdVariable = await generatePusherNotification( targetPath, output, taskObj, notificationConfig, profileTemplate, ); // links task, puller, table and pusher if ( notificationPullerIdVariable && notificationTaskIdVariable && notificationTableIdVariable && notificationPusherIdVariable ) { const associations = getAssociation(); associations.objectAssociations[notificationTaskIdVariable] = { puller: { id: notificationPullerIdVariable, resultKey: 'items', }, table: { id: notificationTableIdVariable }, pusher: { id: notificationPusherIdVariable }, }; const associationFilePath = associations.__file_path; console.log(chalk.green(`Writing associations to ${associationFilePath}`)); delete associations.__file_path; fs.writeFileSync( associationFilePath, stringify(associations, { keepSourceTokens: true, lineWidth: 0 }), ); } } export async function generateNotificationsCommandHandler({ id, output, }: { id?: string; output: 'trello' | 'lark' | 'email'; }) { if (!output) { output = 'trello'; } const outputTemplate = getProjectOutput(); let notificationConfigs = getProjectNotificationConfigs(); var projectPath = getProjectPath(); var auditPath = path.join(projectPath, '__audit'); if (!fs.existsSync(auditPath)) { fs.mkdirSync(auditPath); } for (const notificationConfig of notificationConfigs) { if (id && notificationConfig.id !== id) { continue; } var taskName = notificationConfig.taskName; // convert taskName to slug (e.g: sales orders => sales-orders) var slugRelatedTaskName = taskName .replace(/[^a-zA-Z0-9 ]+/g, ' ') .replace(/\s+/g, '-') .toLowerCase(); var targetPath = path.join(auditPath, slugRelatedTaskName); if (!fs.existsSync(targetPath)) { fs.mkdirSync(targetPath); } await generateNotification( output, targetPath, notificationConfig, outputTemplate, ); } }