diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 00000000..8d4b125d --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,27 @@ + +'use strict'; +var loadProperties = require('./grunt/loadProperties.js'); + +var tasksToLoad = [ 'dataPacksJob' ]; + +module.exports = function(grunt) { + grunt.log.oklns("Vlocity Build Tools"); + + var properties = loadProperties(grunt); + + // Load grunt tasks automatically + require('load-grunt-tasks')(grunt, {pattern: ['grunt-*', '!grunt-template-jasmine-istanbul']}); + + grunt.initConfig({ + properties: properties + }); + + tasksToLoad.forEach(function(taskName) { + var config = require('./grunt/' + taskName + 'Task.js')(grunt); + if (config) { + grunt.config.merge(config); + } + }); + + grunt.registerTask("build-js", [ ]); +}; diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..25608156 --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +The MIT License (MIT) +Copyright (c) 2016 Vlocity Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..6abb0376 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +To begin, fill in the information in the build.properties file, or create your own property file. + +sf.username: Salesforce Username +sf.password: Salesforce Password +vlocity.namespace: The namespace of the Vlocity Package. vlocity_ins, vlocity_cmt, or vlocity_ps +vlocity.dataPackJob: The name of the Job being run. + +To run a DataPack Job through Ant use the following: +"ant packDeploy" - Deploy the files specified in the Job +or +"ant packExport" - Export from the org to the local file system + +Also supports "-propertyfile filename.properties" + +To run a DataPack job through Grunt use the following: +grunt -job JOB_FILE_NAME ACTION_NAME + +The supported ACTION_NAMEs are as follow: +packDeploy: Deploy all contents of folder in expansionPath +packImport: Import contents of file in buildFile +packExport: Export from all queries and manifest +packBuildFile: Build the buildFile from the expansionPath data +packExpandFile: Create the contents of folders in expansionPath from the buildFile + +The files defining Jobs can be found in the dataPacksJobs folder. + +For a complete job definition see dataPacksJobs/ReadMe-Example.yaml which includes detailed information on each property. + + + + diff --git a/apex/ActivateTemplates.cls b/apex/ActivateTemplates.cls new file mode 100644 index 00000000..2de782ed --- /dev/null +++ b/apex/ActivateTemplates.cls @@ -0,0 +1,3 @@ +//include BaseUtilities.cls; + +setTemplatesActiveStatus(true); \ No newline at end of file diff --git a/apex/BaseUtilities.cls b/apex/BaseUtilities.cls new file mode 100644 index 00000000..0d6a4de3 --- /dev/null +++ b/apex/BaseUtilities.cls @@ -0,0 +1,12 @@ + +void setTemplatesActiveStatus(Boolean status) +{ + List layouts = [SELECT Id, Name, Active__c FROM vlocity_namespace__VlocityUITemplate__c]; + + for (vlocity_namespace__VlocityUITemplate__c layout : layouts) + { + layout.Active__c = status; + } + + update layouts; +} \ No newline at end of file diff --git a/apex/DeactivateTemplates.cls b/apex/DeactivateTemplates.cls new file mode 100644 index 00000000..bc3256c9 --- /dev/null +++ b/apex/DeactivateTemplates.cls @@ -0,0 +1,3 @@ +//include BaseUtilities.cls; + +setTemplatesActiveStatus(false); \ No newline at end of file diff --git a/bin/run-grunt b/bin/run-grunt new file mode 100755 index 00000000..09af8c56 --- /dev/null +++ b/bin/run-grunt @@ -0,0 +1,15 @@ +# /bin/sh + +echo $@ + +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm + +command -v nvm >/dev/null 2>&1 || { echo >&2 "\nIMPORTANT!!!!\n\nI require NVM to ensure we use the right version of Node.\n\nPlease download the latest NVM from https://github.com/creationix/nvm#installation\n\n\n"; exit 1; } + +nvm install +nvm use + +npm install +./node_modules/.bin/grunt build-js $* +echo "Grunt completed..." \ No newline at end of file diff --git a/build.properties b/build.properties new file mode 100644 index 00000000..181b9ea0 --- /dev/null +++ b/build.properties @@ -0,0 +1,11 @@ +# Salesforce Username and Password + Security Token +sf.username = +sf.password = +# Your Package's Namespace vlocity_ins, vlocity_cmt, vlocity_ps +vlocity.namespace = +# The Name of the yml manifest file in ./dataPackJobs +vlocity.dataPackJob = + +# Please uncomment both if using a Sandbox +#sf.serverurl = https://test.salesforce.com +#sf.loginUrl = https://test.salesforce.com \ No newline at end of file diff --git a/build.xml b/build.xml new file mode 100644 index 00000000..96194494 --- /dev/null +++ b/build.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + No propertyfile selected. Please create a "build.properties" file following the template provided in template.build.properties. Or specify a -propertyfile. + + + + + + + + Developer Org - ${sf.username} + + + + Description - ${sf.description} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ------------------------------------------- + ------------------------------------------- + Running - @{packCommand} - @{job} + + + + + + + + Finished + + + + + diff --git a/dataPacksJobs/QueryDefinitions.yaml b/dataPacksJobs/QueryDefinitions.yaml new file mode 100644 index 00000000..e6699240 --- /dev/null +++ b/dataPacksJobs/QueryDefinitions.yaml @@ -0,0 +1,15 @@ + +# Shared Query Definitions + +ActiveOmniScripts: + query: Select Id from %vlocity_namespace%__OmniScript__c where %vlocity_namespace%__IsActive__c = true + VlocityDataPackType: OmniScript +ActiveCards: + query: Select Id from %vlocity_namespace%__VlocityUILayout__c where %vlocity_namespace%__Active__c = true + VlocityDataPackType: VlocityUILayout +ActiveTemplates: + query: Select Id from %vlocity_namespace%__VlocityUITemplate__c where %vlocity_namespace%__Active__c = true + VlocityDataPackType: VlocityUITemplate + + + diff --git a/dataPacksJobs/ReadMe-Example.yaml b/dataPacksJobs/ReadMe-Example.yaml new file mode 100644 index 00000000..eaa611fb --- /dev/null +++ b/dataPacksJobs/ReadMe-Example.yaml @@ -0,0 +1,34 @@ + +# The Job defines all the different data for each supported task +# the supported tasks are: +# packDeploy: Deploy all contents of folder in expansionPath +# packImport: Import contents of file in buildFile +# packExport: Export from all queries and manifest and expand at expansionPath +# packBuildFile: Build the buildFile from the expansionPath data +# packExpandFile: Create the contents of folders in expansionPath from the buildFile + +projectPath: vlocity.rm.src # Primary project path allows for merging in Property File properties or can be a regular value +expansionPath: datapack-expanded/CampaignBaseTemplates # The Specific Path after the projectPath to insert these Queried Files +buildFile: staticresources/CampaignBaseTemplates.resource # File to create +compileOnBuild: true # Compiled files will not be generated as part of this Export +# You can also run Anonymous Apex before and After a Job by job type. +# Available types are Import, Export, Deploy BuildFile, ExpandFile +# Apex files live in vloc_release/apex or in your projectPath +# You can include multiple Apex files with "//include FileName.cls;" in you .cls file +preJobApex: + Export: DeactivateTemplates.cls +postJobApex: + Export: ActivateTemplates.cls +# Data for Job can be Specified through either queries OR manfifest not both +queries: # Array of Queries to get Data for Job by DataPackType + - VlocityDataPackType: VlocityUITemplate # The VlocityDataPackType the Query is finding Id's for + query: Select Id from %vlocity_namespace%__VlocityUITemplate__c where Name LIKE 'campaign%' # SOQL Query use %vlocity_namespace%__ +manifestOnly: false # If true, an Export job will only save items specifically listed in the manifest +manifest: # Object defining Type and Export Data - Can be String or Object + VlocityCard: # VlocityDataPackType + - Campaign-Story # Name + OmniScript: # VlocityDataPackType + - Type: Insurance # This object would export the currently Active OmniScript + SubType: Billing # for this Type/SubType/Language + Language: English +delete: true # Delete the VlocityDataPack__c file on finish diff --git a/grunt/dataPacksJobTask.js b/grunt/dataPacksJobTask.js new file mode 100644 index 00000000..db324589 --- /dev/null +++ b/grunt/dataPacksJobTask.js @@ -0,0 +1,152 @@ +var request = require('request'); +var yaml = require('js-yaml'); +var fs = require('fs-extra'); + +var node_vlocity = require('../node_vlocity/vlocity.js'); + +var notify = require('../node_modules/grunt-notify/lib/notify-lib'); + +module.exports = function (grunt) { + + var dataPacksJobFolder = './dataPacksJobs'; + + function dataPacksJob(action, jobName, callback, skipUpload) { + + var properties = grunt.config.get('properties'); + + var vlocity = new node_vlocity({ + username: properties['sf.username'], + password: properties['sf.password'], + vlocityNamespace: properties['vlocity.namespace'], + verbose: grunt.option('verbose'), + loginUrl: properties['sf.loginUrl'] + }); + + grunt.log.writeln('DataPacks Job Started - ' + jobName + ' Org: ' + properties['sf.username']); + + var dataPacksJobsData = {}; + + fs.readdirSync(dataPacksJobFolder).filter(function(file) { + + console.log(file); + + try { + var fileName = file.substr(0,file.indexOf('.')); + dataPacksJobsData[fileName] = yaml.safeLoad(fs.readFileSync(dataPacksJobFolder + '/' + file, 'utf8')); + } catch (e) { + //console.log(e); + } + }); + + if (dataPacksJobsData[jobName]) { + if (dataPacksJobsData[jobName].projectPath && properties[dataPacksJobsData[jobName].projectPath]) { + dataPacksJobsData[jobName].projectPath = properties[dataPacksJobsData[jobName].projectPath]; + } + + if (dataPacksJobsData[jobName].projectPath) { + dataPacksJobsData[jobName].projectPath = grunt.template.process(dataPacksJobsData[jobName].projectPath, {data: properties}); + } + + if (grunt.option('query') && grunt.option('type')) { + dataPacksJobsData[jobName].queries = [{ + query: grunt.option('query'), + VlocityDataPackType: grunt.option('type') + }]; + } + + vlocity.datapacksjob.runJob(dataPacksJobsData, jobName, action, + function(result) { + grunt.log.ok('DataPacks Job Success - ' + action + ' - ' + jobName); + + notify({ + title: 'DataPacks', + message: 'Success - ' + jobName + }); + callback(); + }, + function(result) { + grunt.log.error('DataPacks Job Failed - ' + action + ' - ' + jobName + ' - ' + result.errorMessage); + + notify({ + title: 'DataPacks', + message: 'Failed - ' + jobName + }); + callback(result); + }, skipUpload); + } else { + grunt.log.error('DataPacks Job Not Found - ' + jobName); + callback(jobName); + } + } + + grunt.registerTask('packDeploy', 'Run a DataPacks Job', function() { + + var done = this.async(); + + dataPacksJob('Deploy', grunt.option('job'), function() { + done(); + }); + }); + + grunt.registerTask('packExport', 'Run a DataPacks Job', function() { + + var done = this.async(); + + dataPacksJob('Export', grunt.option('job'), function() { + done(); + }); + }); + + grunt.registerTask('packBuildFile', 'Run a DataPacks Job', function() { + + var done = this.async(); + + dataPacksJob('BuildFile', grunt.option('job'), function() { + done(); + }); + }); + + grunt.registerTask('packAllBuildFiles', 'Run a DataPacks Job', function() { + + var done = this.async(); + + var dataPacksJobsData = {}; + + fs.readdirSync(dataPacksJobFolder).filter(function(file) { + try { + var fileName = file.substr(0,file.indexOf('.')); + if (!/ReadMe-Example/.test(fileName)) { + dataPacksJobsData[fileName] = yaml.safeLoad(fs.readFileSync(dataPacksJobFolder + '/' + file, 'utf8')); + } + } catch (e) { + //console.log(e); + } + }); + + var countDown = Object.keys(dataPacksJobsData).length; + + Object.keys(dataPacksJobsData).forEach(function(job, index) { + grunt.log.ok('Kicking off ' + index + ' job'); + dataPacksJob('BuildFile', job, function() { + countDown--; + if (countDown === 0) { + grunt.log.ok('All files built successfully'); + done(); + return; + } + grunt.log.ok(countDown + ' packs left to build'); + }, true); + }) + }); + + grunt.registerTask('packExpandFile', 'Run a DataPacks Job', function() { + + var done = this.async(); + + dataPacksJob('ExpandFile', grunt.option('job'), function() { + done(); + }); + }); + + return dataPacksJob; +}; \ No newline at end of file diff --git a/grunt/loadProperties.js b/grunt/loadProperties.js new file mode 100644 index 00000000..1bb0fd26 --- /dev/null +++ b/grunt/loadProperties.js @@ -0,0 +1,58 @@ +'use strict'; +var path = require('path'); +var fs = require('fs'); +var _ = require('lodash'); +var properties = require ('properties'); + + +module.exports = function(grunt) { + var basedir = process.env.PWD; + + function loadProperties(file, returnNullOnFail) { + try { + if (fs.statSync(file)) { + return properties.parse(grunt.file.read(file)); + } + } catch (e) { + grunt.log.errorlns('Could not load properties file from ' + file); + grunt.verbose.errorlns(e); + if (returnNullOnFail) { + return null; + } + } + return {}; + } + + function loadEnvProperties() { + return _.defaults({}, process.env); + } + + function loadBuildProperties() { + if (grunt.option('propertyfile')) { + grunt.verbose.writelns('Loading custom property file from: ' + grunt.option('propertyfile')); + return loadProperties(grunt.option('propertyfile'), true); + } else if (fs.statSync(path.join(basedir, 'build.properties'))) { + return loadProperties(path.join(basedir, 'build.properties'), true); + } else { + return {}; + } + } + + var buildProperties = {}; + if (grunt.option('simple')) { + buildProperties['grunt.vloc.simpleMode'] = !!grunt.option('simple'); + } + _.defaults(buildProperties, loadEnvProperties()); + + if (!buildProperties['sf.username']) { + var props = loadBuildProperties(); + if (!props) { + grunt.log.errorlns('No propertyfile selected. Please create a \"build.properties\" file following the template provided in template.build.properties. Or specify a -propertyfile.'); + return; + } else { + _.defaults(buildProperties, props); + } + } + + return buildProperties; +}; \ No newline at end of file diff --git a/lib/ant-contrib-1.0b3.jar b/lib/ant-contrib-1.0b3.jar new file mode 100644 index 00000000..06253766 Binary files /dev/null and b/lib/ant-contrib-1.0b3.jar differ diff --git a/node_vlocity/datapacks.js b/node_vlocity/datapacks.js new file mode 100644 index 00000000..96048376 --- /dev/null +++ b/node_vlocity/datapacks.js @@ -0,0 +1,234 @@ +var jsforce = require('jsforce'); + +var DATA_PACKS_REST_RESOURCE = "/v1/VlocityDataPacks/"; + +var DataPacks = module.exports = function(vlocity) { + this.vlocity = vlocity || {}; + this.dataPacksEndpoint = '/' + this.vlocity.namespace + DATA_PACKS_REST_RESOURCE; +}; + +DataPacks.prototype.getAllDataPacks = function(callback) { + var self = this; + + self.vlocity.checkLogin(function(){ + self.vlocity.jsForceConnection.apex.get(self.dataPacksEndpoint, function(err, res) { + if (err) { throw err; } + callback(JSON.parse(res)); + }); + }); +} + +DataPacks.prototype.getDataPackData = function(dataPackId, callback) { + var self = this; + + self.vlocity.checkLogin(function(){ + self.vlocity.jsForceConnection.apex.get(self.dataPacksEndpoint+dataPackId, function(err, res) { + if (err) { throw err; } + + var dataPackData = JSON.parse(res); + + if (dataPackData.isChunked) { + + var keysToRetrieveInitial = []; + + // Get all the keys to retrieve as chunks + dataPackData.dataPacks.forEach(function(dataPack) { + keysToRetrieveInitial.push(dataPack.VlocityDataPackKey); + }); + + // Empty out the dataPacks Array + dataPackData.dataPacks = []; + + var dataPackDataInfoInitial = { + dataPackId: dataPackId, + allRetrievedData: dataPackData, + keysToRetrieve: keysToRetrieveInitial, + retrievedKeys: [] + }; + + self.getDataPackDataChunked(dataPackDataInfoInitial, null, callback); + } else { + callback(dataPackData); + } + }); + }); +} + +DataPacks.prototype.getDataPackDataChunked = function(dataPackDataInfo, chunkResult, callback) { + var self = this; + + self.vlocity.checkLogin(function() { + if (chunkResult != null) { + if (chunkResult.dataPacks) { + chunkResult.dataPacks.forEach(function(dataPack) { + dataPackDataInfo.allRetrievedData.dataPacks.push(dataPack); + dataPackDataInfo.retrievedKeys.push(dataPack.VlocityDataPackKey); + }); + } + } + + var keysRemaining = []; + + dataPackDataInfo.keysToRetrieve.forEach(function(key) { + if (dataPackDataInfo.retrievedKeys.indexOf(key) === -1 && keysRemaining.length < 100) { + keysRemaining.push(key); + } + }); + + // Finished getting chunks + if (keysRemaining.length == 0) { + callback(dataPackDataInfo.allRetrievedData); + } else { + + // Need to double encode the commas + var chunkedGetURL = self.dataPacksEndpoint+dataPackDataInfo.dataPackId + '?chunks=' + keysRemaining.join("%252C"); + + self.vlocity.jsForceConnection.apex.get(chunkedGetURL, function(err, res) { + if (err) { throw err; } + + self.getDataPackDataChunked(dataPackDataInfo, JSON.parse(res), callback); + }); + } + }); +} + +DataPacks.prototype.getErrorsFromDataPack = function(dataPackData, callback) { + var self = this; + var errors = []; + + if (dataPackData.errors) { + + var mapOfDataPacks = {}; + + dataPackData.dataPacks.forEach(function(dataPack) { + mapOfDataPacks[dataPack.VlocityDataPackKey] = dataPack; + }); + + Object.keys(dataPackData.errors).forEach(function(dataPackKey) { + + var errorMessage = dataPackData.errors[dataPackKey][0].VlocityDataPackType + ' --- ' + dataPackData.errors[dataPackKey][0].VlocityDataPackName + ' --- ' +dataPackData.errors[dataPackKey][0].VlocityDataPackMessage; + + if (mapOfDataPacks[dataPackKey].VlocityDataPackAllRelationships != null + && Object.keys(mapOfDataPacks[dataPackKey].VlocityDataPackAllRelationships).length > 0) + { + var listOfRels = ''; + + Object.keys(mapOfDataPacks[dataPackKey].VlocityDataPackAllRelationships).forEach(function(parentKey) { + + if (listOfRels != '') { + listOfRels += ' | '; + } + + listOfRels += mapOfDataPacks[parentKey].VlocityDataPackName; + }); + + errorMessage += ' --- Referenced by: ' + listOfRels; + } + + errors.push(errorMessage); + }); + } + + callback(errors); +} + + +DataPacks.prototype.getErrors = function(dataPackId, callback) { + var self = this; + + self.vlocity.checkLogin(function(){ + self.vlocity.jsForceConnection.apex.get(self.dataPacksEndpoint+dataPackId, function(err, res) { + if (err) { throw err; } + + var dataPackData = JSON.parse(res); + self.getErrorsFromDataPack(dataPackData, callback); + }); + }); +} + +DataPacks.prototype.runDataPackProcess = function(dataPackData, options, onSuccess, onError) { + var self = this; + + if (options && dataPackData && dataPackData.processData) { + + Object.keys(options).forEach(function(optionKey){ + dataPackData.processData[optionKey] = options[optionKey]; + }); + } + + self.vlocity.checkLogin(function(){ + self.vlocity.jsForceConnection.apex.post(self.dataPacksEndpoint, dataPackData, function(err, result) { + if (typeof result == "string") { + result = JSON.parse(result); + } + + console.log('Result', result); + + if (err) { + if (onError) onError(err); + else throw err; + } else if (/(Ready|InProgress)/.test(result.Status)) { + dataPackData.processData = result; + + if (result.Total && result.Finished) { + console.log(result.VlocityDataPackProcess + ' Status - Finished: ' + result.Finished + ' Total: ' + result.Total); + } + + setTimeout(function() { self.runDataPackProcess(dataPackData, options, onSuccess, onError); }, result.Async ? 3000 : 1); + } else if (/(Complete|Deleted)/.test(result.Status)) { + if (onSuccess) onSuccess(result); + else console.log(result); + } else if (/Error/.test(result.Status)) { + if (onError) onError(result); + else console.log(result); + } + }); + }); +} + +DataPacks.prototype.export = function(dataPackType, exportData, options, onSuccess, onError) { + var dataPackData = { + processType: 'Export', + processData: { + VlocityDataPackType: dataPackType, + VlocityDataPackData: exportData + } + }; + + console.log('dataPackData', dataPackData); + + this.runDataPackProcess(dataPackData, options, onSuccess, onError); +} + +DataPacks.prototype.import = function(dataJson, options, onSuccess, onError) { + var dataPackData = { + processType: 'Import', + processData: { 'VlocityDataPackData': dataJson } + }; + + this.runDataPackProcess(dataPackData, options, onSuccess, onError); +} + +DataPacks.prototype.activate = function(dataPackId, dataPackKeysToActivate, options, onSuccess, onError) { + var dataPackData = { + processType: 'Activate', + processData: { + 'VlocityDataPackId': dataPackId, + 'VlocityDataPackKeysToActivate': dataPackKeysToActivate + } + }; + + this.runDataPackProcess(dataPackData, options, onSuccess, onError); +} + +DataPacks.prototype.delete = function(dataPackId, options, onSuccess, onError) { + var dataPackData = { + processType: 'Delete', + processData: { + 'VlocityDataPackId': dataPackId + } + }; + + this.runDataPackProcess(dataPackData, options, onSuccess, onError); +} + diff --git a/node_vlocity/datapacksbuilder.js b/node_vlocity/datapacksbuilder.js new file mode 100644 index 00000000..1f324d06 --- /dev/null +++ b/node_vlocity/datapacksbuilder.js @@ -0,0 +1,311 @@ +var request = require('request'); +var yaml = require('js-yaml'); +var fs = require('fs-extra'); +var sass = require('node-sass'); +var stringify = require('json-stable-stringify'); + +var UTF8_EXTENSIONS = [ "css", "json", "yaml", "scss", "html", "js"]; + +var DataPacksBuilder = module.exports = function(vlocity) { + this.vlocity = vlocity || {}; + + this.dataPacksExpandedDefinition = JSON.parse(fs.readFileSync('./node_vlocity/datapacksexpanddefinition.json', 'utf8')); + this.defaultDataPack = JSON.parse(fs.readFileSync('./node_vlocity/defaultdatapack.json', 'utf8')); + this.currentStatus; + this.currentImportData = {}; +}; + +DataPacksBuilder.prototype.buildImport = function(importPath, manifest, jobInfo) { + var self = this; + + var dataPackImport = JSON.parse(fs.readFileSync('./node_vlocity/defaultdatapack.json', 'utf8')); + + var MAX_IMPORT_SIZE = 400000; + + if (jobInfo.expansionPath) { + importPath += '/' + jobInfo.expansionPath; + } + + self.compileOnBuild = jobInfo.compileOnBuild; + + if (!self.currentStatus) { + self.initializeImportStatus(importPath, manifest); + } + + var nextImport; + + do { + nextImport = self.getNextImport(importPath, Object.keys(self.currentStatus)); + + if (nextImport) { + dataPackImport.dataPacks.push(nextImport); + } + + } while (nextImport && (jobInfo.singleFile || stringify(dataPackImport).length < MAX_IMPORT_SIZE)) + + return dataPackImport.dataPacks.length > 0 ? dataPackImport : null; +}; + + +DataPacksBuilder.prototype.loadFilesAtPath = function(srcpath) { + var self = this; + + self.vlocity.datapacksutils.getFiles(srcpath).forEach(function(filename){ + + var encoding = 'base64'; + + var extension = filename.substr(filename.lastIndexOf('.')+1); + + if (UTF8_EXTENSIONS.indexOf(extension) > -1) { + encoding = 'utf8'; + } + + if (!self.allFileDataMap) { + self.allFileDataMap = {}; + } + + self.allFileDataMap[srcpath + '/' + filename] = fs.readFileSync(srcpath + '/' + filename, encoding); + }); +}; + +DataPacksBuilder.prototype.initializeImportStatus = function(importPath, manifest) { + var self = this; + var matchingFunction = function(dataPackType, dataPackName) { + if (Array.isArray(manifest[dataPackType])) { + return !!manifest[dataPackType].find(function(entry) { + return (entry == dataPackName); + }); + } + var matchingString = manifest[dataPackType]; + if (typeof matchingString != "string") { + matchingString = stringify(matchingString); + } + return (matchingString.indexOf(dataPackName) != -1); + } + + self.currentStatus = {}; + + if (manifest) { + self.pendingFromManifest = JSON.parse(stringify(manifest)); + } else { + self.pendingFromManifest = {}; + } + + var importPaths = self.vlocity.datapacksutils.getDirectories(importPath); + if (self.vlocity.verbose) { + console.log('\x1b[31m', 'Found import paths >>' ,'\x1b[0m', importPaths); + } + importPaths.forEach(function(dataPackType) { + + var dataPackTypeDir = importPath + '/' + dataPackType; + + var allDataPacksOfType = self.vlocity.datapacksutils.getDirectories(dataPackTypeDir); + + if (self.vlocity.verbose) { + console.log('\x1b[31m', 'Found datapacks >>' ,'\x1b[0m', allDataPacksOfType); + } + + if (allDataPacksOfType) { + allDataPacksOfType.forEach(function(dataPackName) { + + var metadataFilename = dataPackTypeDir + '/' + dataPackName + '/' + dataPackName + '_DataPack.json'; + + var dataPackKey = dataPackType + '/' + dataPackName; + + try { + if ((!manifest || (manifest[dataPackType] && matchingFunction(dataPackType, dataPackName)))) { + + if (self.vlocity.datapacksutils.fileExists(metadataFilename)) { + self.currentStatus[dataPackKey] = 'Ready'; + self.loadFilesAtPath(dataPackTypeDir + '/' + dataPackName); + if (Array.isArray(self.pendingFromManifest[dataPackType])) { + var beforeCount = self.pendingFromManifest[dataPackType].length; + self.pendingFromManifest[dataPackType] = self.pendingFromManifest[dataPackType].filter(function(value) { + return value !== dataPackName; + }); + } else { + if (self.pendingFromManifest[dataPackType]) { + self.pendingFromManifest[dataPackType].replace(dataPackName, ''); + } + } + } else { + console.error('\x1b[31m', 'Missing metadata file for _DataPack.json >> ', '\x1b[0m', dataPackKey); + } + } + } catch (e) { + console.error('\x1b[31m', 'Error whilst processing >>' ,'\x1b[0m', dataPackKey, e); + } + }); + } + }); + var hasMissingEntries = false; + Object.keys(self.pendingFromManifest).forEach(function(key) { + if (self.pendingFromManifest[key].length > 0) { + hasMissingEntries = true; + } + }); + if (hasMissingEntries) { + console.error("Unmatched but required files:\n" + stringify(self.pendingFromManifest, null, 2)); + } +}; + +DataPacksBuilder.prototype.getNextImport = function(importPath, dataPackKeys, singleFile) { + var self = this; + + var nextImport; + + dataPackKeys.forEach(function(dataPackKey) { + + if (!nextImport) { + + if (self.currentStatus[dataPackKey] == 'Ready') { + try { + var typeIndex = dataPackKey.indexOf('/'); + var dataPackType = dataPackKey.substr(0, typeIndex); + var dataNameIndex = dataPackKey.lastIndexOf('/')+1; + var dataPackName = dataPackKey.substr(dataNameIndex); + var fullPathToFiles = importPath + '/' + dataPackKey; + var parentData = self.allFileDataMap[ fullPathToFiles + '/' + dataPackName + '_ParentKeys.json']; + var allRelatedKeys = null; + + if (self.allFileDataMap[ fullPathToFiles + '/' + dataPackName + '_AllRelationshipKeys.json']) + { + allRelatedKeys = JSON.parse(self.allFileDataMap[ fullPathToFiles + '/' + dataPackName + '_AllRelationshipKeys.json']); + } + + var needsParents = false; + + if (!singleFile && parentData) { + parentData = JSON.parse(parentData); + + parentData.forEach(function(parentKey) { + if (self.currentStatus[parentKey] == 'Ready') { + needsParents = true; + } + }); + + if (needsParents) { + return; + } + } + + nextImport = { + VlocityDataPackKey: dataPackKey, + VlocityDataPackType: dataPackType, + VlocityDataPackParents: parentData, + VlocityDataPackStatus: 'Success', + VlocityDataPackIsIncluded: true, + VlocityDataPackAllRelationships: allRelatedKeys, + VlocityDataPackName: dataPackName, + VlocityDataPackData: { + VlocityDataPackKey: dataPackKey, + VlocityDataPackType: dataPackType, + VlocityDataPackIsIncluded: true + } + } + + var sobjectDataField = self.dataPacksExpandedDefinition[dataPackType].Data; + + // Always an Array in Actualy Data Model + nextImport.VlocityDataPackData[sobjectDataField] = self.buildFromFiles(JSON.parse(self.allFileDataMap[fullPathToFiles + '/' + dataPackName + '_DataPack.json']), fullPathToFiles, dataPackType, sobjectDataField); + + self.currentStatus[dataPackKey] = 'Added'; + } catch (e) { + console.log('\x1b[31m', 'Error Formatting Deploy >>' ,'\x1b[0m', dataPackKey); + throw e; + } + + } + } + }); + + return nextImport; +}; + +DataPacksBuilder.prototype.buildFromFiles = function(dataPackDataArray, fullPathToFiles, dataPackType, currentDataField) { + var self = this; + + // The SObjectData in the DataPack is always stored in arrays + if (!Array.isArray(dataPackDataArray)){ + dataPackDataArray = [ dataPackDataArray ]; + } + + var dataPackDef = self.dataPacksExpandedDefinition[dataPackType]; + + if (dataPackDef[currentDataField]) { + var dataFieldDef = dataPackDef[currentDataField]; + + dataPackDataArray.forEach(function(dataPackData) { + + if (dataPackData.VlocityDataPackType) { + dataPackData.VlocityDataPackIsIncluded = true; + } + + Object.keys(dataPackData).forEach(function(field) { + if (dataFieldDef[field]) { + + var fileNames = dataPackData[field]; + + var fileType = self.dataPacksExpandedDefinition[dataPackType][currentDataField][field]; + + if (fileType == 'object' && Array.isArray(fileNames)) { + + var allDataPackFileData = []; + + fileNames.forEach(function(fileInArray) { + var fileInArray = fullPathToFiles + "/" + fileInArray; + + if (self.allFileDataMap[fileInArray]) { + allDataPackFileData = allDataPackFileData.concat(self.buildFromFiles(JSON.parse(self.allFileDataMap[fileInArray]), fullPathToFiles, dataPackType, field)); + } else { + console.log('\x1b[31m', 'File Does Not Exist >>' ,'\x1b[0m', fileInArray); + } + }); + + dataPackData[field] = allDataPackFileData; + } else { + + var filename = fullPathToFiles + "/" + dataPackData[field]; + + if (self.allFileDataMap[filename]) { + + if (fileType == 'list' || fileType == 'object') { + dataPackData[field] = self.buildFromFiles(JSON.parse(self.allFileDataMap[filename]), fullPathToFiles, dataPackType, field); + } else { + if (self.compileOnBuild && self.dataPacksExpandedDefinition[dataPackType][currentDataField][field].CompiledField) { + if (self.dataPacksExpandedDefinition[dataPackType][currentDataField][field].FileType == 'scss') { + + var includePathsForSass = []; + + self.vlocity.datapacksutils.getDirectories(fullPathToFiles + "/..").forEach(function(dir) { + includePathsForSass.push(fullPathToFiles + "/../" + dir + "/"); + }); + + var sassResult = sass.renderSync({ + data: self.allFileDataMap[filename], + includePaths: includePathsForSass + }); + + dataPackData[self.dataPacksExpandedDefinition[dataPackType][currentDataField][field].CompiledField] = sassResult.css.toString(); + dataPackData[field] = self.allFileDataMap[filename]; + } + } else if (!self.compileOnBuild || !self.dataPacksExpandedDefinition[dataPackType][currentDataField].CompiledFields || + self.dataPacksExpandedDefinition[dataPackType][currentDataField].CompiledFields.indexOf(field) == -1) { + dataPackData[field] = self.allFileDataMap[filename]; + } + } + } + } + } + }); + + if (dataFieldDef.JsonFields) { + dataFieldDef.JsonFields.forEach(function(jsonField) { + dataPackData[jsonField] = stringify(dataPackData[jsonField]); + }); + } + }); + } + + return dataPackDataArray; +}; diff --git a/node_vlocity/datapacksexpand.js b/node_vlocity/datapacksexpand.js new file mode 100644 index 00000000..19ad6dff --- /dev/null +++ b/node_vlocity/datapacksexpand.js @@ -0,0 +1,440 @@ +var request = require("request"); +var yaml = require("js-yaml"); +var fs = require("fs-extra"); +var path = require("path"); +var stringify = require('json-stable-stringify'); +var unidecode = require('unidecode'); + +var DataPacksExpand = module.exports = function(vlocity) { + var self = this; + self.vlocity = vlocity || {}; + self.utils = self.vlocity.datapacksutils; + + self.vlocityKeysToNewNamesMap = {}; + self.vlocityRecordSourceKeyMap = {}; +}; + +DataPacksExpand.prototype.generateFolderPath = function(dataPackType, parentName) { + var self = this; + //Replace spaces with dash (-) to have a valid file name for cards + var validParentName = parentName.replace(/\s+/g, "-"); + return self.targetPath + "/" + dataPackType + "/" + validParentName + "/"; +}; + +//Generate the full file path +DataPacksExpand.prototype.generateFilepath = function(dataPackType, parentName, filename, extension) { + var self = this; + //Replace spaces with dash (-) to have a valid file name for cards + var validFileName = filename.replace(/\s+/g, "-"); + return self.generateFolderPath(dataPackType, parentName) + validFileName + "." + extension; +}; + +DataPacksExpand.prototype.getNameWithFields = function(nameFields, dataPackData) { + var self = this; + var filename = ""; + + nameFields.forEach(function(key) { + + if (filename != "") { + filename += "_"; + } + + // If key references a field adds that otherwise is literal string + if (dataPackData[key]) { + filename += unidecode(dataPackData[key].replace(/\//g, "-")); + } else { + filename += key; + } + }); + + return filename; +}; + +DataPacksExpand.prototype.getDataPackName = function(dataPackType, sObjectType, dataPackData) { + var self = this; + return self.getNameWithFields(self.utils.getFileName(dataPackType, sObjectType), dataPackData); +}; + +DataPacksExpand.prototype.expandDatapackElement = function(datapackElement) { + var self = this; + + if (self.utils.isValidType(datapackElement.VlocityDataPackType)) { + var dataField = self.utils.getDataField(datapackElement); + var dataPackData = datapackElement[dataField]; + + if (dataPackData) { + self.processDataPackData(datapackElement.VlocityDataPackType, null, null, dataPackData[0]); + } + } +}; + +DataPacksExpand.prototype.processList = function(dataPackType, parentName, filename, listData) { + var self = this; + + if (listData.length > 0) { + + var sObjectType = listData[0].VlocityRecordSObjectType; + + listData.forEach(function(dataPack) { + self.processObjectEntry(dataPackType, dataPack); + }); + + var sortFields = self.utils.getSortFields(dataPackType, sObjectType); + var fileType = self.utils.getFileType(dataPackType, sObjectType); + + listData.sort(function(a, b) { + return self.listSortBy(a, b, sortFields, 0); + }); + + var dataPackName = self.getDataPackName(dataPackType, sObjectType, listData[0]); + var packName; + + if (!parentName) { + parentName = dataPackName; + } + + if (filename) { + packName = filename + "_" + dataPackName; + } else { + packName = dataPackName; + } + + return self.writeFile(dataPackType, parentName, packName, fileType, listData); + } +}; + +DataPacksExpand.prototype.listSortBy = function(obj1, obj2, fieldsArray, fieldsArrayIndex) { + var self = this; + if (stringify(obj1[fieldsArray[fieldsArrayIndex]]) < stringify(obj2[fieldsArray[fieldsArrayIndex]])) { + return -1; + } + + if (stringify(obj1[fieldsArray[fieldsArrayIndex]]) > stringify(obj2[fieldsArray[fieldsArrayIndex]])) { + return 1; + } + + if (fieldsArrayIndex == fieldsArray.length-1) { + return 0; + } + + return this.listSortBy(obj1, obj2, fieldsArray, fieldsArrayIndex+1); +}; + +DataPacksExpand.prototype.processObjectEntry = function(dataPackType, dataPackData) +{ + var self = this; + var sObjectType = dataPackData.VlocityRecordSObjectType; + + var defaultFilterFields = self.utils.getFilterFields(); + + defaultFilterFields.forEach(function(field) { + delete dataPackData[field]; + }); + + var filterFields = self.utils.getFilterFields(dataPackType, sObjectType); + + if (filterFields) { + filterFields.forEach(function(field) { + delete dataPackData[field]; + }); + } + + var jsonFields = self.utils.getJsonFields(dataPackType, sObjectType); + + if (jsonFields) { + jsonFields.forEach(function(field) { + if (typeof dataPackData[field] === "string") { + + try { + dataPackData[field] = JSON.parse(dataPackData[field]); + } catch (e) { + console.log(field, e); + } + } + }); + } +}; + +DataPacksExpand.prototype.preprocessDataPack = function(currentData, dataPackKey) { + + var self = this; + + if (currentData) { + + if (Array.isArray(currentData)) { + currentData.forEach(function(childData) { + self.preprocessDataPack(childData, dataPackKey); + }); + + } else { + + if (currentData.VlocityRecordSObjectType) { + + // Must already be found + if (currentData.VlocityMatchingRecordSourceKey) { + currentData.VlocityMatchingRecordSourceKey = this.vlocityRecordSourceKeyMap[currentData.VlocityMatchingRecordSourceKey]; + } else { + + var keyFields = self.utils.getSourceKeyDefinitionFields(currentData.VlocityRecordSObjectType); + + var newSourceKey = dataPackKey + "/" + currentData.VlocityRecordSObjectType; + + keyFields.forEach(function(keyField) { + newSourceKey += "/" + currentData[keyField]; + }); + + self.vlocityRecordSourceKeyMap[currentData.VlocityRecordSourceKey] = newSourceKey; + + if (currentData.Id) { + this.vlocityRecordSourceKeyMap[currentData.Id] = newSourceKey; + } + + if (currentData.VlocityRecordSourceKey) { + currentData.VlocityRecordSourceKey = newSourceKey; + } + + if (currentData.VlocityLookupRecordSourceKey) { + currentData.VlocityLookupRecordSourceKey = newSourceKey; + } + } + } + + if (currentData.VlocityDataPackData) { + + var dataPackType = currentData.VlocityDataPackType; + + if (self.utils.isValidType(dataPackType)) { + var dataField = self.utils.getDataField(currentData); + var dataPackDataChild = currentData.VlocityDataPackData[dataField]; + + if (dataPackDataChild) { + + // Top level is always an array with 1 element + dataPackDataChild = dataPackDataChild[0]; + + var parentName = this.getDataPackName(dataPackType, dataPackDataChild.VlocityRecordSObjectType, dataPackDataChild); + + this.vlocityKeysToNewNamesMap[currentData.VlocityDataPackKey] = dataPackType + "/" + parentName; + } + + dataPackKey = dataPackType + "/" + parentName; + } + } + + Object.keys(currentData).forEach(function(sobjectField) { + if (typeof currentData[sobjectField] === "object") { + self.preprocessDataPack(currentData[sobjectField], dataPackKey); + } else if (self.vlocityRecordSourceKeyMap[currentData[sobjectField]]) { + // This attempts to replace any Id with a SourceKey + currentData[sobjectField] = self.vlocityRecordSourceKeyMap[currentData[sobjectField]]; + } + }); + } + } +}; + +DataPacksExpand.prototype.processDataPack = function(dataPackData, options) { + + var self = this; + if (dataPackData.VlocityDataPackData) { + + var dataPackType = dataPackData.VlocityDataPackType; + + if ((!options.manifestOnly || self.utils.isInManifest(dataPackData.VlocityDataPackData, options.manifest)) && self.utils.isValidType(dataPackType)) { + + var dataField = self.utils.getDataField(dataPackData); + + var dataPackDataChild = dataPackData.VlocityDataPackData[dataField]; + + if (dataPackDataChild) { + + // Top level is always an array with 1 element + dataPackDataChild = dataPackDataChild[0]; + + var parentName = this.getDataPackName(dataPackType, dataPackDataChild.VlocityRecordSObjectType, dataPackDataChild); + + fs.emptyDirSync(this.generateFolderPath(dataPackType, parentName)); + + if (dataPackData.VlocityDataPackParents && dataPackData.VlocityDataPackParents.length > 0) { + var sanitizedParentKeys = []; + + dataPackData.VlocityDataPackParents.forEach(function(parentKey) { + if (self.vlocityKeysToNewNamesMap[parentKey]) { + sanitizedParentKeys.push(self.vlocityKeysToNewNamesMap[parentKey]); + } + }); + + if (sanitizedParentKeys.length > 0) { + self.writeFile(dataPackType, parentName, parentName + "_ParentKeys","json", sanitizedParentKeys); + } + } + + if (dataPackData.VlocityDataPackAllRelationships) { + var sanitizedRels = {}; + + Object.keys(dataPackData.VlocityDataPackAllRelationships).forEach(function(relKey) { + if (self.vlocityKeysToNewNamesMap[relKey]) { + sanitizedRels[self.vlocityKeysToNewNamesMap[relKey]] = dataPackData.VlocityDataPackAllRelationships[relKey]; + } + }); + + if (Object.keys(sanitizedRels).length > 0) { + self.writeFile(dataPackType, parentName, parentName + "_AllRelationshipKeys", "json", sanitizedRels); + } + } + + self.processDataPackData(dataPackType, parentName, null, dataPackDataChild); + } + } + } +} + + +DataPacksExpand.prototype.processDataPackData = function(dataPackType, parentName, filename, dataPackData) { + var self = this; + + if (dataPackData) { + + if (dataPackData.VlocityRecordSObjectType) { + + var sObjectType = dataPackData.VlocityRecordSObjectType; + + var currentObjectName = this.getDataPackName(dataPackType, sObjectType, dataPackData); + + var packName; + var nameExtension = ''; + var fileType; + + if (filename) { + packName = filename + "_" + currentObjectName; + fileType = self.utils.getFileType(dataPackType, sObjectType); + } else { + packName = currentObjectName; + nameExtension = "_DataPack"; + fileType = "json"; + } + + var dataPackMetadata = {}; + + this.processObjectEntry(dataPackType, dataPackData); + + Object.keys(dataPackData).forEach(function(sobjectField) { + + if (self.utils.isValidSObject(dataPackType, sObjectType)) { + var expansionType = self.utils.getExpandedDefinition(dataPackType, sObjectType, sobjectField); + + if (expansionType) { + + var extension = expansionType; + var filenameKeys; + + if (expansionType.FileType) { + filenameKeys = expansionType.FileName; + extension = expansionType.FileType; + expansionType = expansionType.FileType; + } + + var expansionData = dataPackData[sobjectField]; + if (expansionData) { + if (expansionType == "list") { + dataPackMetadata[sobjectField] = self.processList(dataPackType, parentName, packName, expansionData); + } else if (expansionType == "object") { + var listExpansion = []; + + expansionData.forEach(function(childInList) { + listExpansion.push(self.processDataPackData(dataPackType, parentName, packName, childInList)); + }); + + if (expansionData.length == 1) { + dataPackMetadata[sobjectField] = listExpansion[0]; + } else if (expansionData.length > 1) { + dataPackMetadata[sobjectField] = listExpansion; + } + } else { + // Skip compiled fields + if (self.compileOnBuild && self.utils.isCompiledField(dataPackType, sObjectType, sobjectField)) { + return; + } + + var encoding; + + var dataFileName = packName; + + if (filenameKeys) { + dataFileName += "_" + self.getNameWithFields(filenameKeys, dataPackData); + } + + if (expansionType == "base64") { + encoding = "base64"; + + if (dataPackData[extension]) { + extension = dataPackData[extension]; + } + } + + dataPackMetadata[sobjectField] = self.writeFile(dataPackType, parentName, dataFileName, extension, expansionData, encoding); + } + } + } else { + dataPackMetadata[sobjectField] = dataPackData[sobjectField]; + } + } + }); + + return this.writeFile(dataPackType, parentName, packName + nameExtension, fileType, dataPackMetadata); + } + } +}; + +DataPacksExpand.prototype.writeFile = function(dataPackType, parentName, filename, fileType, fileData, encoding) { + var self = this; + if (fileType == "json") { + if (typeof fileData === "object") { + fileData = stringify(fileData, { space: 4 }); + } else { + try { + fileData = stringify(JSON.parse(fileData), { space: 4 }); + } catch (e) { + console.log("Error: " + filename + "." + fileType, e); + } + } + } + + // File Path should have "Project Name" + var fullFilePath = this.generateFilepath(dataPackType, parentName, filename, fileType); + + if (!encoding) { + encoding = 'utf8'; + } + + fs.outputFileSync(fullFilePath, fileData, { "encoding": encoding }); + console.log(fullFilePath + " file created"); + + return filename.replace(/\s+/g, "-") + "." + fileType; +}; + +DataPacksExpand.prototype.expandFile = function(targetPath, expandFile, options) { + var self = this; + + try { + self.expand(targetPath, JSON.parse(fs.readFileSync(expandFile, 'utf8')), options); + } catch (e) { + console.log("Invalid DataPackFile " + expandFile + ' ' + e.message); + } +}; + +DataPacksExpand.prototype.expand = function(targetPath, dataPackData, options) { + var self = this; + self.compileOnBuild = options.compileOnBuild; + self.targetPath = targetPath; + if (dataPackData.dataPacks) { + + dataPackData.dataPacks.forEach(function(dataPack) { + self.preprocessDataPack(dataPack); + }); + + dataPackData.dataPacks.forEach(function(dataPack) { + self.processDataPack(dataPack, options); + }); + } +}; diff --git a/node_vlocity/datapacksexpanddefinition.json b/node_vlocity/datapacksexpanddefinition.json new file mode 100644 index 00000000..95c4d40f --- /dev/null +++ b/node_vlocity/datapacksexpanddefinition.json @@ -0,0 +1,269 @@ +{ + "SObject": { + + }, + "AttributeCategory": { + "%vlocity_namespace%__AttributeCategory__c": { + "%vlocity_namespace%__Attribute__c": "object" + }, + "%vlocity_namespace%__Attribute__c": { + "FileName": [ + "Attribute", + "Name" + ], + "FileType": "json" + }, + "Data": "%vlocity_namespace%__AttributeCategory__c" + }, + "CalculationMatrix": { + "%vlocity_namespace%__CalculationMatrixRow__c": { + "FileName": [ + "Rows" + ], + "FileType": "json", + "JsonFields": [ + "%vlocity_namespace%__InputData__c", + "%vlocity_namespace%__OutputData__c" + ], + "SortFields": [ + "%vlocity_namespace%__InputData__c" + ] + }, + "%vlocity_namespace%__CalculationMatrixVersion__c": { + "FileName": [ + "Version" + ], + "FileType": "json", + "%vlocity_namespace%__CalculationMatrixRow__c": "list" + }, + "%vlocity_namespace%__CalculationMatrix__c": { + "%vlocity_namespace%__CalculationMatrixVersion__c": "object" + }, + "Data": "%vlocity_namespace%__CalculationMatrix__c" + }, + "CalculationProcedure": { + "%vlocity_namespace%__CalculationProcedureStep__c": { + "FileName": [ + "Steps" + ], + "FileType": "json" + }, + "%vlocity_namespace%__CalculationProcedureVersion__c": { + "FileName": [ + "Version" + ], + "FileType": "json", + "%vlocity_namespace%__CalculationProcedureStep__c": "list" + }, + "%vlocity_namespace%__CalculationProcedure__c": { + "%vlocity_namespace%__CalculationProcedureVersion__c": "object" + }, + "Data": "%vlocity_namespace%__CalculationProcedure__c" + }, + "DataRaptor": { + "%vlocity_namespace%__DRBundle__c": { + "%vlocity_namespace%__DRMapItem__c": "list", + "%vlocity_namespace%__InputJson__c": { + "FileName": [ + "InputJson" + ], + "FileType": "json" + }, + "%vlocity_namespace%__TargetOutJson__c": { + "FileName": [ + "TargetOutJson" + ], + "FileType": "json" + } + }, + "%vlocity_namespace%__DRMapItem__c": { + "FileName": [ + "Mappings" + ], + "FileType": "json", + "SortFields": [ + "%vlocity_namespace%__DomainObjectCreationOrder__c", + "%vlocity_namespace%__InterfaceObjectLookupOrder__c" + ] + }, + "Data": "%vlocity_namespace%__DRBundle__c" + }, + "DefaultValues": { + "FileName": [ + "Name" + ], + "FilterFields": [ + "Id", + "VlocityDataPackIsIncluded", + "LastViewedDate", + "LastReferencedDate" + ], + "SortFields": [ + "Name" + ], + "SourceKeyFields": [ + "Name" + ] + }, + "Document": { + "Data": "Document", + "Document": { + "Body": { + "FileExt": "Type", + "FileType": "base64" + } + } + }, + "OmniScript": { + "%vlocity_namespace%__Element__c": { + "FileName": [ + "Elements" + ], + "FileType": "json", + "JsonFields": [ + "%vlocity_namespace%__PropertySet__c" + ], + "SortFields": [ + "%vlocity_namespace%__Level__c", + "%vlocity_namespace%__Order__c" + ] + }, + + "%vlocity_namespace%__OmniScript__c": { + "%vlocity_namespace%__CustomJavaScript__c": { + "FileName": [ + "JavaScript" + ], + "FileType": "js" + }, + "%vlocity_namespace%__Element__c": "list", + "%vlocity_namespace%__OmniScriptDefinition__c": "object", + "%vlocity_namespace%__PropertySet__c": { + "FileName": [ + "PropertySet" + ], + "FileType": "json" + }, + "%vlocity_namespace%__TestHTMLTemplates__c": { + "FileName": [ + "HTMLTemplates" + ], + "FileType": "html" + }, + "FileName": [ + "%vlocity_namespace%__Type__c", + "%vlocity_namespace%__SubType__c", + "%vlocity_namespace%__Language__c" + ] + }, + "%vlocity_namespace%__OmniScriptDefinition__c": { + "FileName": [ + "Definition" + ], + "FileType": "json", + "%vlocity_namespace%__Content__c": { + "FileName": [ + "Compiled" + ], + "FileType": "json" + } + }, + "Data": "%vlocity_namespace%__OmniScript__c" + }, + "Product2": { + "%vlocity_namespace%__AttributeAssignment__c": { + "FileName": [ + "AttributeAssignments" + ], + "FileType": "json", + "FilterFields": [ + "%vlocity_namespace%__ObjectLink__c" + ], + "JsonFields": [ + "%vlocity_namespace%__ValidValuesData__c" + ] + }, + "Data": "Product2", + "PricebookEntry": { + "FileName": [ + "PricebookEntry" + ], + "FileType": "json" + }, + "Product2": { + "%vlocity_namespace%__AttributeAssignment__c": "list", + "%vlocity_namespace%__JSONAttribute__c": { + "FileName": [ + "Attributes" + ], + "FileType": "json" + }, + "PricebookEntry": "object" + } + }, + "ProductChildItem": { + "%vlocity_namespace%__ProductChildItem__c": {}, + "Data": "%vlocity_namespace%__ProductChildItem__c" + }, + "SourceKeyDefinitions": { + "%vlocity_namespace%__DRMapItem__c": [ + "%vlocity_namespace%__MapId__c" + ], + "%vlocity_namespace%__OmniScript__c": [ + "%vlocity_namespace%__Type__c", + "%vlocity_namespace%__SubType__c", + "%vlocity_namespace%__Language__c" + ] + }, + "VlocityAction": { + "%vlocity_namespace%__VlocityAction__c": {}, + "Data": "%vlocity_namespace%__VlocityAction__c" + }, + "VlocityCard": { + "%vlocity_namespace%__VlocityCard__c": { + "%vlocity_namespace%__Definition__c": "json" + }, + "Data": "%vlocity_namespace%__VlocityCard__c" + }, + "VlocityStateModel": { + "%vlocity_namespace%__VlocityStateModel__c": { + "FileName": [ + "v", + "testdr13perf__VersionNumber__c" + ], + "FileType": "json", + "JsonFields": [ + "%vlocity_namespace%__VlocityStateTransition__c" + ] + }, + "%vlocity_namespace%__VlocityState__c": {}, + "Data": "%vlocity_namespace%__VlocityStateModel__c" + }, + "VlocityUILayout": { + "%vlocity_namespace%__VlocityUILayout__c": { + "%vlocity_namespace%__Definition__c": "json" + }, + "Data": "%vlocity_namespace%__VlocityUILayout__c" + }, + "VlocityUITemplate": { + "%vlocity_namespace%__VlocityUITemplate__c": { + "%vlocity_namespace%__CSS__c": "css", + "%vlocity_namespace%__CustomJavascript__c": "js", + "%vlocity_namespace%__HTML__c": "html", + "%vlocity_namespace%__SampleJson__c": { + "FileName": [ + "Sample" + ], + "FileType": "json" + }, + "%vlocity_namespace%__Sass__c": { + "CompiledField": "%vlocity_namespace%__CSS__c", + "FileType": "scss" + }, + "CompiledFields": [ + "%vlocity_namespace%__CSS__c" + ] + }, + "Data": "%vlocity_namespace%__VlocityUITemplate__c" + } +} \ No newline at end of file diff --git a/node_vlocity/datapacksjob.js b/node_vlocity/datapacksjob.js new file mode 100644 index 00000000..0eda82ba --- /dev/null +++ b/node_vlocity/datapacksjob.js @@ -0,0 +1,472 @@ +var fs = require('fs-extra'); +var async = require('async'); +var stringify = require('json-stable-stringify'); + +var DataPacksJob = module.exports = function(vlocity) { + this.vlocity = vlocity || {}; +}; + +var SUPPORTED_JOB_KEY_TO_OPTION_MAP = { + ignoreAllErrors: 'ignoreAllErrors', + maxDepth: 'maxDepth', + processMultiple: 'processMultiple', + dataPackName: 'name', + description: 'description', + version: 'version', + source: 'source' +}; + +DataPacksJob.prototype.getOptionsFromJobInfo = function(jobInfo) { + var options = {}; + + Object.keys(SUPPORTED_JOB_KEY_TO_OPTION_MAP).forEach(function(jobKey) { + + if (jobInfo[jobKey] != null) { + options[SUPPORTED_JOB_KEY_TO_OPTION_MAP[jobKey]] = jobInfo[jobKey]; + } + }); + + return options; +} + +DataPacksJob.prototype.runJob = function(jobData, jobName, action, onSuccess, onError, skipUpload) { + var self = this; + + var jobInfo = jobData[jobName]; + + jobInfo.jobName = jobName; + + if (!jobInfo.projectPath) { + jobInfo.projectPath = './'; + } + + if (jobInfo.queries) { + for (var i = 0; i < jobInfo.queries.length; i++) { + if (typeof jobInfo.queries[i] === 'string') { + jobInfo.queries[i] = jobData.QueryDefinitions[jobInfo.queries[i]]; + } + } + } + + var toolingApi = self.vlocity.jsForceConnection.tooling; + + function executeJob() { + var prePromise; + + if (jobInfo.preJobApex && jobInfo.preJobApex[action]) { + prePromise = self.vlocity.datapacksutils.runApex(jobInfo.projectPath, jobInfo.preJobApex[action]); + } else { + prePromise = Promise.resolve(true); + } + + prePromise.then(function() { + return new Promise(function(resolve, reject) { + try { + self.doRunJob(jobInfo, action, resolve); + } catch (e) { + jobInfo.hasError = true; + jobInfo.errorMessage = e.message; + reject(jobInfo); + } + }); + }) + .then(function(jobStatus) { + + if (!jobStatus.hasError && jobInfo.postJobApex && jobInfo.postJobApex[action]) { + return self.vlocity.datapacksutils.runApex(jobInfo.projectPath, jobInfo.postJobApex[action]); + } else { + return Promise.resolve(jobStatus); + } + }) + .then(function(jobStatus) { + + if (self.vlocity.verbose) { + console.log('\x1b[36m', '>>' ,'\x1b[0m', jobStatus); + } + + if (!jobStatus.hasError) { + if (onSuccess) { + onSuccess(jobStatus); + } else { + console.log('jobStatus', stringify(jobStatus, { space: 2 })); + } + } else { + if (onError) { + onError(jobStatus); + } else { + console.log('jobStatus', jobStatus.errorMessage); + } + } + }, onError); + } + + if (skipUpload) { + executeJob(); + } else { + self.vlocity.checkLogin(executeJob); + } +}; + +DataPacksJob.prototype.doRunJob = function(jobInfo, action, onComplete) { + var self = this; + + if (action == 'Export') { + self.exportJob(jobInfo, onComplete); + } else if (action == 'Import') { + self.importJob(jobInfo, onComplete); + } else if (action == 'Deploy') { + self.deployJob(jobInfo, onComplete); + } else if (action == 'BuildFile') { + self.buildFile(jobInfo, onComplete); + } else if (action == 'ExpandFile') { + self.expandFile(jobInfo, onComplete); + } else { + console.log('Bad Job Info', jobInfo); + } +} + +DataPacksJob.prototype.buildManifestFromQueries = function(jobInfo, onComplete) { + var self = this; + + if (jobInfo.queries) { + + if (!jobInfo.manifest) { + jobInfo.manifest = {}; + } + + async.eachSeries(jobInfo.queries, function(queryData, callback) { + + if (!jobInfo.manifest[queryData.VlocityDataPackType]) { + jobInfo.manifest[queryData.VlocityDataPackType] = []; + } + + var query = queryData.query.replace(/%vlocity_namespace%/g, self.vlocity.namespace); + + self.vlocity.jsForceConnection.query(query, function(err, res) { + + if (self.vlocity.verbose) { + if (err) { + console.log('\x1b[31m', 'Query result had error >>' ,'\x1b[0m', err); + } + if (res) { + console.log('\x1b[36m', 'Query result >>' ,'\x1b[0m', res); + } + } + + res.records.forEach(function(record) { + jobInfo.manifest[queryData.VlocityDataPackType].push(record.Id); + }); + + callback(); + }); + + }, function(err, result) { + onComplete(jobInfo); + }); + } else { + onComplete(jobInfo); + } +} + +DataPacksJob.prototype.exportJob = function(jobInfo, onComplete) { + var self = this; + + self.vlocity.checkLogin(function(){ + self.buildManifestFromQueries(jobInfo, function(jobStatus) { + self.exportFromManifest(jobStatus, onComplete); + }); + }); +} + +DataPacksJob.prototype.exportFromManifest = function(jobInfo, onComplete) { + var self = this; + + async.eachSeries(Object.keys(jobInfo.manifest), function(dataPackType, callback) { + var dataList = jobInfo.manifest[dataPackType]; + var exportData = []; + + dataList.forEach(function(exData) { + if (typeof exData === 'object') { + exportData.push(exData); + } else { + exportData.push({ Id: exData }); + } + }); + + self.vlocity.datapacks.export(dataPackType, exportData, self.getOptionsFromJobInfo(jobInfo), + function(result) { + if (self.vlocity.verbose) { + console.log('\x1b[36m', 'datapacks.export >>' ,'\x1b[0m', result); + } + if (!jobInfo.VlocityDataPackIds) { + jobInfo.VlocityDataPackIds = []; + } + + jobInfo.VlocityDataPackIds.push(result.VlocityDataPackId); + + self.vlocity.datapacks.getDataPackData(result.VlocityDataPackId, function(dataPackData) { + if (self.vlocity.verbose) { + console.log('\x1b[36m', 'datapacks.getDataPackData >>' ,'\x1b[0m', dataPackData); + } + + if (jobInfo.expansionPath) { + self.vlocity.datapacksexpand.expand(jobInfo.projectPath + '/' + jobInfo.expansionPath, dataPackData, jobInfo); + } + + var afterDelete = function() { + callback(); + } + + self.vlocity.datapacks.delete(result.VlocityDataPackId, self.getOptionsFromJobInfo(jobInfo), afterDelete, afterDelete); + }); + }, + function(err) { + if (self.vlocity.verbose) { + console.error('\x1b[31m', 'datapacks.export >>' ,'\x1b[0m', err); + } + + if (jobInfo.expansionPath) { + self.vlocity.datapacks.getDataPackData(err.VlocityDataPackId, function(dataPackData) { + + self.vlocity.datapacksexpand.expand(jobInfo.projectPath + '/' + jobInfo.expansionPath, dataPackData, jobInfo); + + if (self.vlocity.verbose) { + console.error('\x1b[31m', 'writing error file to >>' ,'\x1b[0m', 'vlocity-deploy-temp/deployError.json'); + fs.outputFileSync('vlocity-deploy-temp/deployError.json', stringify(dataPackData, { space: 4 }), 'utf8'); + } + + self.getJobErrors(dataPackData, jobInfo, onComplete); + }); + } + else { + self.getJobErrors(err, jobInfo, onComplete); + } + }); + }, function(err, result) { + onComplete(jobInfo); + }); +}; + +DataPacksJob.prototype.importJob = function(jobInfo, onComplete) { + var self = this; + + var dataJson = fs.readFileSync(jobInfo.projectPath + '/' + jobInfo.buildFile, 'utf8'); + + self.vlocity.datapacks.import(JSON.parse(dataJson), self.getOptionsFromJobInfo(jobInfo), + function(result) { + jobInfo.VlocityDataPackId = result.VlocityDataPackId; + + if (jobInfo.activate) { + self.vlocity.datapacks.activate(jobInfo.VlocityDataPackId, ['ALL'], self.getOptionsFromJobInfo(jobInfo), + function(activateResult){ + if (onComplete) { + onComplete(jobInfo); + } + }, + onComplete); + } else if (onComplete) { + onComplete(jobInfo); + } + }, + function(err) { + self.getJobErrors(err, jobInfo, onComplete); + }); + +}; + +DataPacksJob.prototype.buildFile = function(jobInfo, onComplete) { + var self = this; + + var fullDataPath = jobInfo.projectPath; + + jobInfo.singleFile = true; + + if (self.vlocity.verbose) { + console.log('\x1b[31m', 'buildImport >>' ,'\x1b[0m', fullDataPath, jobInfo.manifest, jobInfo); + } + var dataJson = self.vlocity.datapacksbuilder.buildImport(fullDataPath, jobInfo.manifest, jobInfo); + + if (dataJson && jobInfo.dataPackName) { + dataJson.name = jobInfo.dataPackName; + } + + if (dataJson) { + fs.outputFileSync(jobInfo.projectPath + '/' + jobInfo.buildFile, stringify(dataJson, { space: 4 }), 'utf8'); + // also create .resource-meta.xml + fs.outputFileSync(jobInfo.projectPath + '/' + jobInfo.buildFile + '-meta.xml', 'Publictext/json', + 'utf8'); + console.log('\x1b[31m', 'Creating File >>' ,'\x1b[0m', jobInfo.projectPath + '/' + jobInfo.buildFile); + + } else { + jobInfo.hasError = true; + jobInfo.errorMessage = 'Project Path Empty: ' + fullDataPath; + } + + onComplete(jobInfo); +} + +DataPacksJob.prototype.expandFile = function(jobInfo, onComplete) { + var self = this; + + self.vlocity.datapacksexpand.expandFile(jobInfo.projectPath + '/' + jobInfo.expansionPath, jobInfo.projectPath + '/' + jobInfo.buildFile, jobInfo); + + if (onComplete) { + onComplete(jobInfo); + } +} + +DataPacksJob.prototype.deployJob = function(jobInfo, onComplete) { + var self = this; + + if (!jobInfo.VlocityDataPackIds) { + jobInfo.VlocityDataPackIds = []; + } + + // If there are both queries and manifest then assume the user wants to deploy all + // Otherwise only deploy the manifest + var deployManifest = jobInfo.manifest; + if (jobInfo.queries) { + deployManifest = null; + } + + var dataJson = self.vlocity.datapacksbuilder.buildImport(jobInfo.projectPath, deployManifest, jobInfo); + + if (dataJson) { + fs.outputFileSync('./vlocity-deploy-temp/deploy.json', stringify(dataJson, { space: 4 }), 'utf8'); + } + + if (dataJson) { + this.vlocity.datapacks.import(dataJson, self.getOptionsFromJobInfo(jobInfo), + function(result) { + jobInfo.VlocityDataPackIds.push(result.VlocityDataPackId); + self.deployJob(jobInfo, onComplete); + }, + function(err) { + self.getJobErrors(err, jobInfo, onComplete); + }); + } else { + if ((jobInfo.activate || jobInfo.delete) && jobInfo.VlocityDataPackIds) { + async.eachSeries(jobInfo.VlocityDataPackIds, function(dataPackId, callback) { + if (jobInfo.activate) { + if (!jobInfo.activationStatus) { + jobInfo.activationStatus = {}; + } + + self.vlocity.datapacks.activate(dataPackId, ['ALL'], self.getOptionsFromJobInfo(jobInfo), + function(activateResult) { + jobInfo.activationStatus[dataPackId] = 'Success'; + + if (jobInfo.delete) { + self.vlocity.datapacks.delete(dataPackId, self.getOptionsFromJobInfo(jobInfo), + function(res) { + callback(); + }, + function(res) { + callback(); + }); + } else { + callback(); + } + }, + function(error) { + jobInfo.activationStatus[dataPackId] = 'Error'; + + if (jobInfo.delete) { + self.vlocity.datapacks.delete(dataPackId, self.getOptionsFromJobInfo(jobInfo), + function(res) { + callback(); + }, + function(res) { + callback(); + }); + } else { + callback(); + } + }); + + } else if (jobInfo.delete) { + self.vlocity.datapacks.delete(dataPackId, self.getOptionsFromJobInfo(jobInfo), + function(res) { + callback(); + }, + function(res) { + callback(); + }); + } + }, function(err, result) { + if (onComplete) { + onComplete(jobInfo); + } + }); + } else if (onComplete) { + onComplete(jobInfo); + } + } +}; + +DataPacksJob.prototype.getJobErrors = function(err, jobInfo, onComplete) { + var self = this; + + var processErrors = function(errors) { + if (!jobInfo.errors) { + jobInfo.errors = []; + } + + if (self.vlocity.verbose) { + console.error('\x1b[31m', 'datapacks.getDataPackData.errors >>' ,'\x1b[0m', errors); + } + + jobInfo.hasError = true; + jobInfo.errors = jobInfo.errors.concat(errors); + jobInfo.errorMessage = "Export Failed:\n" + jobInfo.errors.join("\n"); + + var afterDelete = function() { + onComplete(jobInfo); + } + + if (jobInfo.delete) { + self.vlocity.datapacks.delete(err.VlocityDataPackId ? err.VlocityDataPackId : err.dataPackId, self.getOptionsFromJobInfo(jobInfo), afterDelete, afterDelete); + } else { + onComplete(jobInfo); + } + } + + if (err.VlocityDataPackId) { + self.vlocity.datapacks.getErrors(err.VlocityDataPackId, processErrors ); + } else if (err.dataPackId) { + self.vlocity.datapacks.getErrorsFromDataPack(err, processErrors); + } else { + onComplete(jobInfo); + } +}; + +DataPacksJob.prototype.getPublishedDataPacks = function(jobInfo, onComplete) { + var self = this; + + this.vlocity.datapacks.getAllDataPacks(function(allDataPacks) { + + async.eachSeries(allDataPacks, function(dataSummaryData, callback) { + + self.vlocity.datapacks.getDataPackData(dataSummaryData.dataPackId, function(dataPackData) { + + var filename = jobInfo.projectPath + '/' + dataPath + '/' + dataPackData.name + '.json'; + + fs.outputFileSync(filename, stringify(dataPackData, { space: 4 })); + + if (jobInfo.expansionPath) { + self.vlocity.datapacksexpand.expand(jobInfo.projectPath + '/' + jobInfo.expansionPath, dataPackData, jobInfo); + } + + jobInfo.allStatus[dataSummaryData.dataPackId] = 'Success'; + + callback(); + }); + }, function(err, result) { + if (onComplete) { + onComplete(jobInfo); + } + }); + }); + +} + diff --git a/node_vlocity/datapacksutils.js b/node_vlocity/datapacksutils.js new file mode 100644 index 00000000..f1fce5b2 --- /dev/null +++ b/node_vlocity/datapacksutils.js @@ -0,0 +1,230 @@ +var fs = require("fs-extra"); +var path = require('path'); + +var DataPacksUtils = module.exports = function(vlocity) { + this.vlocity = vlocity || {}; + + this.dataPacksExpandedDefinition = JSON.parse(fs.readFileSync("./node_vlocity/datapacksexpanddefinition.json", 'utf8')); +}; + +DataPacksUtils.prototype.getSourceKeyDefinitionFields = function(SObjectType) { + + if (this.dataPacksExpandedDefinition.SourceKeyDefinitions[SObjectType]) { + return this.dataPacksExpandedDefinition.SourceKeyDefinitions[SObjectType]; + } + + return this.dataPacksExpandedDefinition.DefaultValues.SourceKeyFields; +} + +DataPacksUtils.prototype.isValidType = function(dataPackType) { + return this.dataPacksExpandedDefinition.hasOwnProperty(dataPackType); +} + +DataPacksUtils.prototype.isValidSObject = function(dataPackType, SObjectType) { + return dataPackType == 'SObject' || (this.isValidType(dataPackType) && this.dataPacksExpandedDefinition[dataPackType].hasOwnProperty(SObjectType)); +} + +DataPacksUtils.prototype.getDataField = function(dataPackData) { + var dataKey; + + Object.keys(dataPackData.VlocityDataPackData).forEach(function(key) { + + if (Array.isArray(dataPackData.VlocityDataPackData[key]) && dataPackData.VlocityDataPackData[key].length > 0 && dataPackData.VlocityDataPackData[key][0].VlocityRecordSObjectType) { + dataKey = dataPackData.VlocityDataPackData[key][0].VlocityRecordSObjectType; + } + }); + + return dataKey; +} + +DataPacksUtils.prototype.getSortFields = function(dataPackType, SObjectType) { + return this.getExpandedDefinition(dataPackType, SObjectType, "SortFields"); +} + +DataPacksUtils.prototype.getFilterFields = function(dataPackType, SObjectType) { + return this.getExpandedDefinition(dataPackType, SObjectType, "FilterFields"); +} + +DataPacksUtils.prototype.getFileName = function(dataPackType, SObjectType) { + return this.getExpandedDefinition(dataPackType, SObjectType, "FileName"); +} + +DataPacksUtils.prototype.getFileType = function(dataPackType, SObjectType) { + return this.getExpandedDefinition(dataPackType, SObjectType, "FileType"); +} + +DataPacksUtils.prototype.getJsonFields = function(dataPackType, SObjectType) { + return this.getExpandedDefinition(dataPackType, SObjectType, "JsonFields"); +} + +DataPacksUtils.prototype.isCompiledField = function(dataPackType, SObjectType, field) { + var compiledFields = this.getExpandedDefinition(dataPackType, SObjectType, "CompiledFields"); + return compiledFields && compiledFields.indexOf(field) != -1; +} + +DataPacksUtils.prototype.getExpandedDefinition = function(dataPackType, SObjectType, dataKey) { + var definitionValue; + + if (this.isValidType(dataPackType)) { + + if (SObjectType) { + if (this.dataPacksExpandedDefinition[dataPackType][SObjectType]) { + definitionValue = this.dataPacksExpandedDefinition[dataPackType][SObjectType][dataKey]; + } + } else { + definitionValue = this.dataPacksExpandedDefinition[dataPackType][dataKey]; + } + } + if (!definitionValue) { + definitionValue = this.dataPacksExpandedDefinition.DefaultValues[dataKey]; + } + + return definitionValue; +} + +// Traverse JSON and get all Ids +DataPacksUtils.prototype.getAllSObjectIds = function(currentData, currentIdsOnly, typePlusId) { + var self = this; + + if (currentData) { + + if (Array.isArray(currentData)) { + currentData.forEach(function(childData) { + self.getAllSObjectIds(childData, currentIdsOnly, typePlusId); + }); + + } else { + + if (currentData.VlocityDataPackType == "SObject") { + if (currentData.Id) { + currentIdsOnly.push(currentData.Id); + typePlusId.push({ SObjectType: currentData.VlocityRecordSObjectType, Id: currentData.Id }); + } + } + + Object.keys(currentData).forEach(function(sobjectField) { + if (typeof currentData[sobjectField] === "object") { + self.getAllSObjectIds(currentData[sobjectField], currentIdsOnly, typePlusId); + } + }); + } + } +}; + +DataPacksUtils.prototype.getDirectories = function(srcpath) { + return fs.readdirSync(srcpath).filter(function(file) { + return fs.statSync(path.join(srcpath, file)).isDirectory(); + }); +}; + +DataPacksUtils.prototype.getFiles = function(srcpath) { + return fs.readdirSync(srcpath).filter(function(file) { + return fs.statSync(path.join(srcpath, file)).isFile(); + }); +}; + +DataPacksUtils.prototype.fileExists = function(srcpath) { + try { + fs.statSync(srcpath); + } catch (e) { + return false; + } + + return true; +} + +DataPacksUtils.prototype.isInManifest = function(dataPackData, manifest) { + + if (manifest[dataPackData.VlocityDataPackType]) { + for (var i = 0; i < manifest[dataPackData.VlocityDataPackType].length; i++) + { + var man = manifest[dataPackData.VlocityDataPackType][i]; + + if (typeof(man) == 'object') { + var isMatching = true; + + Object.keys(man).forEach(function(key) { + if (man[key] != dataPackData[key]) { + isMatching = false; + } + }); + + if (isMatching) { + return isMatching; + } + } else if (man == dataPackData.Id) { + return true; + } + } + } + + return false; +} + + +DataPacksUtils.prototype.runApex = function(projectPath, filePath) { + var self = this; + + console.log('Executing runApex: ' + projectPath + ' -- ' + filePath); + if (projectPath && filePath) { + var apexFileName; + + if (self.vlocity.datapacksutils.fileExists(projectPath + '/' + filePath)) { + apexFileName = projectPath + '/' + jobInfo.preJobApex[action]; + } else if (self.vlocity.datapacksutils.fileExists('apex/' + filePath)) { + apexFileName = 'apex/' + filePath; + } + + if (apexFileName) { + var apexFileData = fs.readFileSync(apexFileName, 'utf8'); + + var includedClasses = apexFileData.match(/\/\/include(.*?);/g); + + if (includedClasses) { + var srcdir = path.dirname(apexFileName); + + for (var i = 0; i < includedClasses.length; i++) { + + var className = includedClasses[i].replace("//include ", "").replace(";", ""); + + var includedFileData = fs.readFileSync(srcdir+'/'+className, 'utf8'); + + apexFileData = apexFileData.replace(includedClasses[i], includedFileData); + } + } + + apexFileData = apexFileData.replace(/vlocity_namespace/g, this.vlocity.namespace); + + return self.vlocity.jsForceConnection.tooling.executeAnonymous(apexFileData, function(err, res) { + + if (res.success === false) { + if (res.compileProblem) { + console.log('\x1b[36m', '>>' ,'\x1b[0m Compilation Error', res.compileProblem); + } + if (res.exceptionMessage) { + console.log('\x1b[36m', '>>' ,'\x1b[0m Exception Message', res.exceptionMessage); + } + + if (res.exceptionStackTrace) { + console.log('\x1b[36m', '>>' ,'\x1b[0m Exception StackTrace', res.exceptionStackTrace); + } + + return Promise.resolve(true); + } else { + return Promise.resolve(true); + } + }); + } else { + return Promise.resolve(true); + } + } else { + return Promise.resolve(true); + } +} + + + + + + + diff --git a/node_vlocity/defaultdatapack.json b/node_vlocity/defaultdatapack.json new file mode 100644 index 00000000..204a29f4 --- /dev/null +++ b/node_vlocity/defaultdatapack.json @@ -0,0 +1,10 @@ +{ + "status": "Complete", + "processMultiple": true, + "primaryDataPackKey": "VlocityDeploy", + "dataPacks": [ + ], + "dataPackId": "", + "name": "VlocityDeploy", + "version": 1 +} \ No newline at end of file diff --git a/node_vlocity/vlocity.js b/node_vlocity/vlocity.js new file mode 100644 index 00000000..a4ef0b9c --- /dev/null +++ b/node_vlocity/vlocity.js @@ -0,0 +1,49 @@ +var jsforce = require('jsforce'); + +var datapacks = require('./datapacks'); +var datapacksjob = require('./datapacksjob'); +var datapacksexpand = require('./datapacksexpand'); +var datapacksbuilder = require('./datapacksbuilder'); +var datapacksutils = require('./datapacksutils'); + +var Vlocity = module.exports = function(options) { + options = options || {}; + + this.username = options.username; + this.password = options.password; + + this.namespace = options.vlocityNamespace; + this.verbose = !!options.verbose; + if (this.verbose) { + console.log('Verbose mode enabled'); + } + this.jsForceConnection = new jsforce.Connection({ + loginUrl: options.loginUrl ? options.loginUrl : 'https://login.salesforce.com' + }); + this.isLoggedIn = false; + + this.datapacksutils = new datapacksutils(this); + this.datapacks = new datapacks(this); + this.datapacksjob = new datapacksjob(this); + this.datapacksexpand = new datapacksexpand(this); + this.datapacksbuilder = new datapacksbuilder(this); + +}; + +Vlocity.prototype.checkLogin = function(thenRun) { + var self = this; + + if (!self.isLoggedIn) { + self.jsForceConnection.login(self.username, self.password, function(err, res) { + if (err) { + console.error(err); + return false; + } + + self.isLoggedIn = true; + thenRun(); + }); + } else { + thenRun(); + } +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..491b329a --- /dev/null +++ b/package.json @@ -0,0 +1,69 @@ +{ + "name": "vloc_release", + "version": "1.0.0", + "description": "", + "main": "Gruntfile.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "build@vlocity.com", + "license": "MIT", + "devDependencies": { + "angular-mocks": "1.4.8", + "async": "2.0.1", + "autoprefixer-core": "5.2.1", + "blanket": "1.2.1", + "bower": "1.7.1", + "compass-mixins": "0.12.10", + "datauri": "0.7.1", + "fs": "0.0.2", + "fs-extra": "0.30.0", + "glob": "7.0.0", + "grunt": "0.4.5", + "grunt-banner": "0.4.0", + "grunt-browserify": "3.8.0", + "grunt-cli": "0.1.13", + "grunt-code-coverage-enforcer": "git://github.com/mattgoldspink/grunt-code-coverage-enforcer", + "grunt-contrib-clean": "0.6.0", + "grunt-contrib-connect": "0.9.0", + "grunt-contrib-jasmine": "0.9.2", + "grunt-contrib-jshint": "0.11.3", + "grunt-contrib-uglify": "1.0.1", + "grunt-contrib-watch": "0.6.1", + "grunt-html2js": "0.3.5", + "grunt-istanbul": "0.4.2", + "grunt-jscs": "2.5.0", + "grunt-newer": "1.1.1", + "grunt-notify": "0.4.5", + "grunt-open": "0.2.3", + "grunt-postcss": "0.5.5", + "grunt-prompt": "1.3.3", + "grunt-sass": "1.1.0", + "grunt-shell": "1.1.2", + "grunt-template-jasmine-istanbul": "git://github.com/mattgoldspink/grunt-template-jasmine-istanbul", + "grunt-xmlstoke": "0.7.1", + "grunt-zip": "0.16.2", + "js-yaml": "3.6.1", + "jsforce": "1.7.0", + "load-grunt-tasks": "3.4.0", + "lodash": "3.10.1", + "node-sass": "3.8.0", + "open": "0.0.5", + "properties": "1.2.1", + "request": "2.70.0", + "rimraf": "2.5.2", + "svgify": "git://github.com/mattgoldspink/svgify", + "node-buffers": "https://github.com/substack/node-buffers.git", + "validator": "5.2.0", + "xml2json": "0.7.1", + "xmldom": "0.1.22", + "xpath": "0.0.21" + }, + "engine": "node == 4.3.0", + "dependencies": { + "grunt-contrib-copy": "^1.0.0", + "json-stable-stringify": "1.0.1", + "livereload": "0.5.0", + "unidecode": "0.1.8" + } +}