pcon/sfdc-eventMonitoring

View on GitHub
src/lib/utils.js

Summary

Maintainability
A
3 hrs
Test Coverage
var chalk = require('chalk');
var lo = require('lodash');
var jsonfile = require('jsonfile');
var moment = require('moment');
var Q = require('q');
var createCsvWriter = require('csv-writer').createObjectCsvWriter;
var createCsvStringifier = require('csv-writer').createObjectCsvStringifier;

const { table } = require('table');

var errorCodes = require('./errorCodes.js');
var sfdc = require('./sfdc.js');
var printer = require('./printer.js');

/**
 * Trims a provided Id down to 15 characters
 * @param {string} id The Id
 * @returns {string} The shortened Id
 */
var trimId = function (id) {
    if (id === undefined) {
        return;
    }

    return id.substring(0, 15);
};

/**
 * Escape a string with single quotes
 * @param {string} data The data to escape
 * @returns {string} The escaped string
 */
var escapeString = function (data) {
    return '\'' + data.replace(/'/, '\\\'') + '\'';
};

/**
 * Escape an array of strings
 * @param {string[]} data The data to escape
 * @return {string[]} An array of escaped strings
 */
var escapeArray = function (data) {
    var escaped = [];

    lo.forEach(data, function (d) {
        escaped.push(utils.escapeString(d));
    });

    return escaped;
};

/**
 * Maps the Id to the data
 * @param {array} sObjects The objects to map
 * @returns {object} A map of Id to object
 */
var idToObject = function (sObjects) {
    return lo.keyBy(sObjects, function (sObject) {
        return trimId(sObject.Id);
    });
};

/**
 * Runs and resolves a function with three parameters
 * @param {object} data The data
 * @param {string} key The key
 * @param {string} field The field
 * @param {function} func The function to run
 * @returns {Promise} A promise for the run
 */
var runFunc = function (data, key, field, func) {
    var deferred = Q.defer();

    deferred.resolve(func(data, key, field));

    return deferred.promise;
};

/**
 * Limits an array
 * @param {object[]} data The data to limit
 * @param {number} limit The limit
 * @returns {object[]} The limited data
 */
var limitArray = function (data, limit) {
    if (limit === undefined) {
        return data;
    }

    global.logger.debug('Limiting to ' + limit);
    return lo.slice(data, 0, limit);
};

/**
 * Limits a set of data without a promise
 * @param {object} data The data to limit.
 * @param {string} key The in the data to limit.
 * @param {number} limit The number of records to limit to
 * @returns {object} The limited data
 */
var limitNoPromise = function (data, key, limit) {
    lo.set(data, key, limitArray(lo.get(data, key), limit));
    return data;
};

/**
 * Limit a set of data based on a key and a limit
 * @param {object} data The data to limit.
 * @param {string} key The in the data to limit.
 * @param {number} limit The number of records to limit to
 * @returns {Promise} A promise with the limited data
 */
function genericLimit(data, key, limit) {
    return runFunc(data, key, limit, limitNoPromise);
}

/**
 * Limit a set of data based on a key using the global limit
 * @param {object} data The data to limit.
 * @param {string} key The in the data to limit.
 * @returns {Promise} A promise with the limited data
 */
var limitResults = function (data, key) {
    return genericLimit(data, key, global.config.limit);
};

/**
 * Limit a set of data based on a key using the global sublimit
 * @param {object} data The data to limit.
 * @param {string} key The in the data to limit.
 * @returns {Promise} A promise with the limited data
 */
var subLimitResults = function (data, key) {
    return genericLimit(data, key, global.config.sublimit);
};

/**
 * Gets the most recent files
 * @param {array} event_log_files All the log files to sort through
 * @returns {object} A map of event type to the most recent log files
 */
function getMostRecentFiles(event_log_files) {
    var most_recent_files = {};

    lo.forEach(event_log_files, function (event_log_file) {
        if (!lo.has(most_recent_files, event_log_file.EventType)) {
            lo.set(most_recent_files, event_log_file.EventType, event_log_file);
            return;
        }

        if (moment(lo.get(most_recent_files, event_log_file.EventType).LogDate).isBefore(event_log_file.LogDate)) {
            lo.set(most_recent_files, event_log_file.EventType, event_log_file);
        }
    });

    return most_recent_files;
}

/**
 * Make sure that we acutally have logs to download
 * @param {objects[]} event_log_files The files to download
 * @returns {undefined}
 */
var ensureLogFilesExist = function (event_log_files) {
    if (lo.isEmpty(event_log_files)) {
        global.logger.error('Unable to find log files');
        process.exit(errorCodes.NO_LOGFILES);
    }
};

/**
 * Gets the log files we should be fetching
 * @param {objects[]} event_log_files The event log files to download
 * @returns {object|object[]} The log files to download
 */
var getApplicableLogFiles = function (event_log_files) {
    if (global.config.latest) {
        return getMostRecentFiles(event_log_files);
    }

    return event_log_files;
};

/**
 * Concatenates all of the promise results together
 * @param {object[]} promise_results The results from the all settled promises
 * @param {object} deferred The Q defer
 * @returns {undefined}
 */
var concatenateResults = function (promise_results, deferred) {
    var errors = [];
    var results = [];

    lo.forEach(promise_results, function (result) {
        if (result.state === 'fulfilled') {
            results = lo.concat(results, result.value);
        } else {
            errors.push(result.reason);
        }
    });

    if (!lo.isEmpty(errors)) {
        deferred.reject(errors);
    } else {
        deferred.resolve(results);
    }
};

/**
 * Fetch and convert a list of log files
 * @param {array} event_log_files The event log files to download
 * @returns {Promise} A promise for the data from the event log files
 */
var fetchAndConvert = function (event_log_files) {
    var promises = [];
    var deferred = Q.defer();

    ensureLogFilesExist(event_log_files);
    var files = getApplicableLogFiles(event_log_files);

    lo.forEach(files, function (event_log_file) {
        promises.push(sfdc.fetchConvertFile(event_log_file));
    });

    Q.allSettled(promises)
        .then(function (promise_results) {
            concatenateResults(promise_results, deferred);
        });

    return deferred.promise;
};

/**
 * Method to do the filtering
 * @param {object} filters The filters to apply
 * @returns {function} The predicate function
 */
function filterPredicate(filters) {
    return function (data) {
        var matches = true;

        lo.forEach(filters, function (filter, key) {
            if (filter === undefined) {
                return;
            }

            if (lo.isArray(filter)) {
                if (!lo.includes(filter, lo.get(data, key))) {
                    matches = false;
                }
            } else if (filter !== lo.get(data, key)) {
                matches = false;
            }
        });

        return matches;
    };
}

/**
 * Filters data
 * @param {object} data The data to filter
 * @param {string} key The key of the data to filter
 * @param {object} filter The filter to apply
 * @returns {object} The filtered data
 */
var filterNoPromise = function (data, key, filter) {
    var filtered_results = lo.filter(lo.get(data, key), filterPredicate(filter));

    lo.set(data, key, filtered_results);

    return data;
};

/**
 * Filters data
 * @param {object} data The data to filter
 * @param {string} key The key of the data to filter
 * @param {object} filter The filter to apply
 * @returns {object} The filtered data
 */
var filterResults = function (data, key, filter) {
    var deferred = Q.defer();

    deferred.resolve(filterNoPromise(data, key, filter));

    return deferred.promise;
};

/**
 * Sorts an array
 * @param {object[]} data The data to sort
 * @param {string} sorter The sort string
 * @return {object[]} The sorted array
 */
var sortArray = function (data, sorter) {
    return lo.sortBy(data, lo.split(sorter, ',')).reverse();
};

/**
 * Sort a set of data based on a key and a sorter
 * @param {object} data The data to sort.
 * @param {string} key The key of the data to sort.
 * @param {string} sorter The string of fields to sort by.
 * @returns {object} The sorted data
 */
var sortNoPromise = function (data, key, sorter) {
    global.logger.debug('Sorting by ' + sorter);
    lo.set(data, key, sortArray(lo.get(data, key), sorter));

    if (global.config.asc) {
        global.logger.debug('Ascending');

        lo.set(data, key, lo.get(data, key).reverse());
    }

    return data;
};

/**
 * Sort a set of data based on a key and a sorter
 * @param {object} data The data to sort.
 * @param {string} key The key of the data to sort.
 * @param {string} sorter The string of fields to sort by.
 * @returns {Promise} A promise with the sorted data
 */
function genericSort(data, key, sorter) {
    return runFunc(data, key, sorter, sortNoPromise);
}

/**
 * Sort a set of data based on a key using the global sort
 * @param {object} data The data to sort.
 * @param {string} key The in the data to sort.
 * @returns {Promise} A promise with the sorted data
 */
var sortResults = function (data, key) {
    return genericSort(data, key, global.config.sort);
};

/**
 * Sort a set of data based on a key using the global subort
 * @param {object} data The data to sort.
 * @param {string} key The in the data to sort.
 * @returns {Promise} A promise with the sorted data
 */
var subSortResults = function (data, key) {
    return genericSort(data, key, global.config.subsort);
};

/**
 * Generates an array of table data
 * @param {array} rows The row data
 * @param {array} columns The columns (in order)
 * @param {object} output_info The column metadata
 * @return {array} The table data
 */
var generateTableData = function (rows, columns, output_info) {
    var drow = [];
    var data = [];

    lo.forEach(columns, function (column) {
        drow.push(chalk.bold(output_info[column].header));
    });

    data.push(drow);

    lo.forEach(rows, function (row) {
        drow = [];
        lo.forEach(columns, function (column) {
            try {
                drow.push(global.helper.formatter(column, row[column]));
            } catch (error) {
                drow.push(output_info[column].formatter(row[column]));
            }
        });
        data.push(drow);
    });

    return data;
};

/**
 * Prints the averages based on the format
 * @param {object} data The data
 * @returns {undefined}
 */
var printJSON = function (data) {
    global.printer.print(JSON.stringify(data));
};

/**
 * Prints the averages based on the format
 * @param {object} data The data
 * @param {array} columns The columns
 * @param {object} output_info The column metadata
 * @returns {Promise} A promise for when the data has been printed
 */
var printFormattedData = function (data, columns, output_info) {
    var deferred = Q.defer();

    if (global.config.format === 'json') {
        printJSON(data);
    } else if (global.config.format === 'table') {
        if (lo.isEmpty(data)) {
            global.printer.print('No data to display');
        } else {
            global.printer.print(table(generateTableData(data, columns, output_info)));
        }
    }

    deferred.resolve();

    return deferred.promise;
};

/**
 * Write JSON data to a file
 * @param {object} data The data
 * @param {string} filename The file name
 * @returns {Promise} A promise for when the file was written
 */
var writeJSONtoFile = function (data, filename) {
    var deferred = Q.defer();

    jsonfile.writeFile(filename, data, function (error) {
        if (error) {
            deferred.reject(error);
        } else {
            deferred.resolve();
        }
    });

    return deferred.promise;
};

/**
 * Generates the CSV header
 * @param {object} data The data
 * @return {object[]} The header information
 */
function generateCSVheader(data) {
    var results = [];

    lo.forEach(lo.keys(lo.head(data)), function (field_name) {
        results.push({
            id: field_name,
            title: field_name
        });
    });

    return results;
}

/**
 * Write CSV data to a file
 * @param {object} data The data
 * @param {string} filename The file name
 * @returns {Promise} A promise for when the file was written
 */
var writeCSVtoFile = function (data, filename) {
    var deferred = Q.defer();
    var csvWriter = createCsvWriter({
        path: filename,
        header: generateCSVheader(data)
    });

    csvWriter.writeRecords(data)
        .then(function () {
            deferred.resolve();
        }).catch(function (error) {
            deferred.reject(error);
        });

    return deferred.promise;
};

/**
 * Output the logs to the console
 * @param {array} data The data to output
 * @returns {Promise} A promise for when the data has been outputted
 */
var outputCSVToConsole = function (data) {
    var deferred = Q.defer();
    var csvStringifier = createCsvStringifier({ header: generateCSVheader(data) });

    printer.print(lo.trim(csvStringifier.getHeaderString()));
    printer.print(csvStringifier.stringifyRecords(data));
    deferred.resolve();

    return deferred.promise;
};

/**
 * Splits data to a map by a field
 * @param {object} data The data
 * @param {string} field_name The field name
 * @returns {object} A map of field_name to an array of data
 */
var splitByField = function (data, field_name) {
    var field_value;
    var results = {};

    lo.forEach(data, function (row) {
        field_value = lo.get(row, field_name);

        if (field_value === undefined) {
            return;
        }

        if (!lo.has(results, field_value)) {
            lo.set(results, field_value, []);
        }

        results[field_value].push(row);
    });

    return results;
};

/**
 * Output the logs to the console
 * @param {array} data The data to output
 * @returns {Promise} A promise for when the data has been outputted
 */
var outputJSONToConsole = function (data) {
    var deferred = Q.defer();

    printJSON(data);
    deferred.resolve();

    return deferred.promise;
};

/**
 * Trims down the user ids to 15 characters if needed
 * @returns {undefined}
 */
var updateUserIdCriteria = function () {
    if (global.config.userid === undefined) {
        return;
    }

    if (lo.isArray(global.config.userid)) {
        global.config.userid = lo.map(global.config.userid, trimId);
    } else {
        global.config.userid = trimId(global.config.userid);
    }
};

/**
 * Converts to a timestamp
 * @param {string} datetime The date time
 * @return {number} The timestamp
 */
var toTimestamp = function (datetime) {
    return moment.utc(datetime).valueOf();
};

var utils = {
    concatenateResults: concatenateResults,
    ensureLogFilesExist: ensureLogFilesExist,
    escapeString: escapeString,
    escapeArray: escapeArray,
    fetchAndConvert: fetchAndConvert,
    filterNoPromise: filterNoPromise,
    filterResults: filterResults,
    generateTableData: generateTableData,
    getApplicableLogFiles: getApplicableLogFiles,
    idToObject: idToObject,
    limitArray: limitArray,
    limitNoPromise: limitNoPromise,
    limitResults: limitResults,
    outputCSVToConsole: outputCSVToConsole,
    outputJSONToConsole: outputJSONToConsole,
    printJSON: printJSON,
    printFormattedData: printFormattedData,
    runFunc: runFunc,
    sortArray: sortArray,
    sortNoPromise: sortNoPromise,
    sortResults: sortResults,
    splitByField: splitByField,
    subLimitResults: subLimitResults,
    subSortResults: subSortResults,
    toTimestamp: toTimestamp,
    trimId: trimId,
    updateUserIdCriteria: updateUserIdCriteria,
    writeCSVtoFile: writeCSVtoFile,
    writeJSONtoFile: writeJSONtoFile
};

module.exports = utils;