<commit_before>/*jslint forin:true sub:true anon:true, sloppy:true, stupid:true nomen:true, node:true continue:true*/

/*
 * Copyright (c) 2012, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

var fs = require('fs');
var colors = require("colors");
var UserAgent = require('useragent');
var FileUtil = require("../util/fileutil");
var path = require('path');
var XMLWriter = require('xml-writer');
var util = require('util');
var xw;
var log4js = require("log4js");
var existsSync = fs.existsSync || path.existsSync;

/**
 *
 * @param reportObj includes all information about testSessions, wsSessions and the test report itself
 * @constructor
 */
function ReportManager(reportObj) {
    this.passCount = 0;
    this.failCount = 0;
    this.skipCount = 0;
    this.timeCount = 0;
    this.totalCount = 0;
    this.totalTimeTaken = 0;
    this.logger = log4js.getLogger("ReportManager");

    if (reportObj) {
        this.timeReport = reportObj.timeReport;
    }

    xw = new XMLWriter();
    this.xw = xw;

    //checking if we need to do colorless reporting.
    if (global.color === false) {
        colors.mode = "none";
    } else {
        if (!process.stdout.isTTY) {
            colors.mode = "none";
        }
    }
    try {
        if (reportObj.arrTestSessions) {
            this.testSessions = reportObj.arrTestSessions;
        } else {
            this.testSessions = [];
        }
        if (reportObj.arrWDSessions) {
            this.wdSessions = reportObj.arrWDSessions;
        } else {
            this.wdSessions = [];
        }
        this.reportObj = reportObj;

    } catch (ex) {
        //console.log(ex);
    }

}

ReportManager.prototype.addProperty = function (name, val) {
    if (val) {
        val += '';  // convert to string, since XMLWriter is picky
        this.xw.startElement('property');
        this.xw.writeAttribute('name', name);
        this.xw.writeAttribute('value', val);
        this.xw.endElement();
    }
};

ReportManager.prototype.addTest = function (time, className, name, failureMessage) {
    this.xw.startElement('testcase');
    this.xw.writeAttribute('time', (time / 1000).toFixed(2));
    this.xw.writeAttribute('classname', className);
    this.xw.writeAttribute('name', name);

    if (failureMessage) {
        if (failureMessage === "skip") {
            this.xw.writeAttribute('executed', 'false');
        } else {
            this.xw.startElement('failure');
            this.xw.text(util.format(failureMessage));
            this.xw.endElement();
        }
    }

    this.xw.endElement();
};

ReportManager.prototype.parseYUIResults = function (testJson, dtName, self) {

    var l, m, errorRep,
        yuiUserAgent = testJson.ua,
        yuiSuiteName = testJson.name,
        browser = UserAgent.parse(yuiUserAgent).toAgent(),
        descTestName = dtName,
        currentUrl = testJson.currentUrl,
        yuiTestCaseName,
        timePerTest,
        className,
        testName;

    if (browser === "Other 0.0.0") {
        browser = "NodeJS";
    }

    for (l in testJson) {
        if (testJson[l]) {
            if (testJson[l].type === "testcase") {
                yuiTestCaseName = testJson[l].name;
                timePerTest = testJson[l].duration / testJson[l].total;
                self.passCount += testJson[l].passed;
                self.failCount += testJson[l].failed;
                self.skipCount += testJson[l].ignored;
                self.timeCount += testJson[l].duration;
                self.totalCount += testJson[l].total;

                for (m in testJson[l]) {
                    if (testJson[l][m].type === "test") {
                        className = browser + "." + descTestName;
                        testName = yuiSuiteName + "::" + yuiTestCaseName + "::" + testJson[l][m].name;
                        if (testJson[l][m].result === "fail") {
                            self.addTest(timePerTest, className, testName, testJson[l][m].message + "." + "\n" + " URL :" + currentUrl);
                        } else if (testJson[l][m].result === "pass") {
                            self.addTest(timePerTest, className, testName);
                        } else if (testJson[l][m].result === "ignore") {
                            self.addTest(timePerTest, className, testName, "skip");
                        }
                    }
                }
            }
        }
    }
};

ReportManager.prototype.showReportOnConsole = function (result, verbose) {

    var lastSuite, suite, k, k1, test, msg, m, browser,
        log = console.log;

    log("");
    browser = result.ua;
    if (result.failed) {
        log("Failed ".red + result.name.blue + " on " + browser);
    } else {
        log("Passed ".green + result.name.blue + " on " + browser);
    }
    log((result.passed + " Passed").green + ", " + (result.failed + " Failed ").red + ", " + (result.ignored + " skipped ").grey);
    if (result.failed) {
        for (k in result) {
            suite = result[k];
            if ("object" === typeof suite) {

                if (suite.failed) {
                    for (k1 in suite) {
                        test = suite[k1];
                        if ("object" === typeof test) {
                            if ("fail" === test.result) {

                                if (!lastSuite || lastSuite !== suite.name) {
                                    log(" in " + suite.name.bold);
                                    lastSuite = suite.name;
                                }
                                msg = test.message.split('\n');
                                log(" " + test.name.bold.red + ":" + msg[0]);
                                for (m = 1; m < msg.length; m = m + 1) {
                                    log(" " + msg[m]);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    log("");
};

ReportManager.prototype.writeReports = function () {

    var i, j, k, strJunitReport, strReport, rep, results, testJson, browser,
        testName, testSuiteName, arrReport, xmlReportName, jsonReportName, errorRep,
        self = this,
        wdSessions = self.wdSessions,
        testSessions = self.testSessions,
        jsonReport = {},
        log = console.log,
        resultPresent = false,
        missingErrorCount = 0,
        xwRoot = new XMLWriter(),
        fileUtil = new FileUtil(),
        dirPath,
        relPath;
    function writeFile(fileName, data) {
        fs.writeFileSync(fileName, data, 'utf8');
        log(("Report Created: " + fileName).inverse.toString());
    }

    /**
     * log test results on console
     */
    function logTestResults(reportObj) {
        log();
        log(("Total Number of Executed Tests: " + reportObj.totalCount).blue.toString());
        log(("Total Number of Passed Tests: " + reportObj.passCount).green.toString());
        log(("Total Number of Failed Tests: " + reportObj.failCount).red.toString());
        log(("Total Number of Skipped Tests: " + reportObj.skipCount).cyan.toString());
        log();
        log(("Total Test Execution Time Inside Browser: " + (reportObj.timeCount / 1000).toFixed(2) + " seconds").green.bold.toString());
        log(("Total Test Execution Time:" + reportObj.totalTimeTaken + " seconds").green.bold.toString());
        log();
    }

    for (i = 0; i < testSessions.length; i = i + 1) {

        rep = testSessions[i].driver.getReports();

        if (rep.scenario) {
            arrReport = rep.scenario;
        } else {
            arrReport = rep.results;
        }
        resultPresent = false;
        for (j = 0; j < arrReport.length; j = j + 1) {
            if (rep.scenario) {
                results = arrReport[j].results;

                for (k = 0; k < results.length; k = k + 1) {

                    testJson = results[k];
                    if (testJson.type === "report") {
                        if (!(testJson.passed === 0 && testJson.failed === 0 && testJson.total === 0)) {
                            resultPresent = true;
                            this.parseYUIResults(testJson, testSessions[i].driver.testName, this);
                        }
                    }
                }
            } else {
                testJson = arrReport[j];
                if (testJson.type === "report") {
                    if (!(testJson.passed === 0 && testJson.failed === 0 && testJson.total === 0)) {
                        resultPresent = true;
                        this.parseYUIResults(testJson, testSessions[i].driver.testName, this);
                    }
                }
            }
        }
        if (!resultPresent) {
            missingErrorCount = missingErrorCount + 1;
            errorRep = arrReport[arrReport.length - 1];
            try {
                if (errorRep.results && errorRep.results.length > 0) {
                    this.addTest(0, "Error." + testSessions[i].driver.testName, "Error", errorRep.results[0].error);
                } else if (errorRep.error) {
                    this.addTest(0, "Error." + testSessions[i].driver.testName, "Error", errorRep.error);
                } else {
                    self.logger.debug("Test Report Error : UNKOWN");
                    this.addTest(0, "Error." + testSessions[i].driver.testName, "Error", "Arrow didn't receive result from the test !!!!");
                }
            } catch (e) {
                self.logger.error(e);
            }
        }
    }
    this.totalCount = this.totalCount + missingErrorCount;
    this.failCount = this.failCount + missingErrorCount;

    if (this.reportObj.descriptor) {

        dirPath = path.dirname(this.reportObj.descriptor);
        if (global.reportFolder) {

            // if condition to keep unit tests happy on node10
            if (global.workingDirectory ) {
                //Get relative path to the descriptor
                relPath = path.relative(global.workingDirectory, dirPath);
            } else {
                relPath = dirPath;
            }

            dirPath = path.resolve(global.reportFolder, 'arrow-report', relPath);
            // Create directory if it doesnt exist
            // This check helps when keepTestReport is set to true ( If its not set to true, arrow-report would have been cleaned up as part of setup anyways
            if (!existsSync(dirPath)) {
                fileUtil.createDirectory(dirPath);
            }

        }

        xmlReportName = path.resolve(dirPath, path.basename(this.reportObj.descriptor, '.json') + "-report.xml");
        jsonReportName = path.resolve(dirPath, path.basename(this.reportObj.descriptor, '.json') + "-report.json");

        if (this.failCount > 0) {
            global.failedDescriptors.push({"desc" : this.reportObj.descriptor, "failures" : this.failCount});
        }

        log(("Test Report Summary for the descriptor::" + this.reportObj.descriptor).bold.magenta.toString());

        logTestResults(this);

    } else {

        xmlReportName = "arrow-test-summary.xml";
        jsonReportName = "arrow-test-summary.json";

        if (this.reportObj.showConsolidatedReport) {

            log("\n*************************************************************************************");
            log("                            Consolidated Report                                          ");

            log("Test Report Summary".bold.magenta.toString());
            logTestResults(this);

            log("\n*************************************************************************************");

        }

        if (global.failedDescriptors.length > 0) {

            log("\n*************************************************************************************");
            log("                           List of failed descriptors                                  ");

            for (i = 0; i < global.failedDescriptors.length; i += 1) {
                log('\nFailed Descriptor Path : ' + global.failedDescriptors[i].desc);
                log('Total Number of Failed Tests : ' + global.failedDescriptors[i].failures);
                log("-----------------------------------------------------------------------");
            }
            log('\n');

        }

    }

    testSuiteName = "";
    if (this.reportObj.testSuiteName) {
        testSuiteName = this.reportObj.testSuiteName;
    } else {
        testSuiteName = "TestSuite";
    }
    this.errorCount = this.totalCount - this.passCount - this.failCount - this.skipCount;

    // Store report files in user specified location, if explicitly specified, otherwise store in arrow-target/arrow-report
    if (global.reportFolder) {

        this.xw.startElement("properties");
        this.addProperty("descriptor", this.reportObj.descriptor);
        this.addProperty("parallel", this.reportObj.parallel);
        this.addProperty("driver", this.reportObj.driver);
        this.addProperty("browser", this.reportObj.browser);
        this.addProperty("reuseSession", this.reportObj.reuseSession);
        this.addProperty("activeSeleniumSessionCount", wdSessions.length);
        this.addProperty("seleniumSessionIds", wdSessions.join());
        this.addProperty("group", this.reportObj.group);
        this.addProperty("testName", this.reportObj.testName);
        this.xw.endElement();
        //collecting all report
        strReport = xw.toString();

        //starting report root
        xwRoot.startDocument();
        xwRoot.startElement("testsuite");
        xwRoot.writeAttribute('failures', this.failCount.toString());
        xwRoot.writeAttribute('time', this.totalTimeTaken.toString());
        xwRoot.writeAttribute('errors', this.errorCount.toString());
        xwRoot.writeAttribute('skipped', this.skipCount.toString());
        xwRoot.writeAttribute('tests', this.totalCount.toString());
        xwRoot.writeAttribute('name', testSuiteName.toString());
        xwRoot.text("$$report$$");
        xwRoot.endElement();
        xwRoot.endDocument();
        strJunitReport = xwRoot.toString().replace("$$report$$", strReport);

        xmlReportName = path.resolve(global.reportFolder, 'arrow-report', xmlReportName);
        jsonReportName = path.resolve(global.reportFolder, 'arrow-report', jsonReportName);

        log('\n');
        writeFile(xmlReportName, strJunitReport);

        //writing json file
        for (i = 0; i < testSessions.length; i = i + 1) {
            jsonReport[testSessions[i].driver.testName] = JSON.parse(JSON.stringify(testSessions[i].driver.getReports()));
        }
        jsonReport.testSuiteName = testSuiteName;
        writeFile(jsonReportName, JSON.stringify(jsonReport));
        log('\n');

    }

};

ReportManager.prototype.writeTimeReport = function () {

    var timeReportJsonStr,
        descriptor,
        testSessionTimeArr,
        testName;

    timeReportJsonStr = '{ "descriptors" : [';

    for (descriptor in this.timeReport) {

        timeReportJsonStr += "{";
        timeReportJsonStr += ('"descriptor": ' + ' "' + descriptor + '"' + ' ,');
        timeReportJsonStr += ('"time": ' + ' "' + this.timeReport[descriptor].timeTaken + ' seconds' + '",');

        testSessionTimeArr = this.timeReport[descriptor].testSessionTimeArr;

        timeReportJsonStr += '"tests": [';

        for (testName in testSessionTimeArr) {
            timeReportJsonStr += '{';
            timeReportJsonStr += ('"Testname" :' + ' "' + testName + '"' + ',');
            timeReportJsonStr += ('"Time" :' + ' "' + testSessionTimeArr[testName].timeTaken + ' seconds' + '"');
            timeReportJsonStr += '} ,';
        }

        if (timeReportJsonStr.charAt(timeReportJsonStr.length - 1) === ",") {
            timeReportJsonStr = timeReportJsonStr.slice(0, -1);
        }

        timeReportJsonStr += " ] } ,";
    }

    if (timeReportJsonStr.charAt(timeReportJsonStr.length - 1) === ",") {
        timeReportJsonStr = timeReportJsonStr.slice(0, -1);
    }

    timeReportJsonStr += ('] ,"Total time":' + ' "' + this.totalTimeTaken + ' seconds' + '" ');
    timeReportJsonStr += ' }';

    fs.writeFileSync(path.resolve(global.reportFolder, 'arrow-report', 'timeReport.json'), timeReportJsonStr, 'utf8');

};

module.exports = ReportManager;
<commit_msg>Checking if errorRep exists<commit_after>/*jslint forin:true sub:true anon:true, sloppy:true, stupid:true nomen:true, node:true continue:true*/

/*
 * Copyright (c) 2012, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

var fs = require('fs');
var colors = require("colors");
var UserAgent = require('useragent');
var FileUtil = require("../util/fileutil");
var path = require('path');
var XMLWriter = require('xml-writer');
var util = require('util');
var xw;
var log4js = require("log4js");
var existsSync = fs.existsSync || path.existsSync;

/**
 *
 * @param reportObj includes all information about testSessions, wsSessions and the test report itself
 * @constructor
 */
function ReportManager(reportObj) {
    this.passCount = 0;
    this.failCount = 0;
    this.skipCount = 0;
    this.timeCount = 0;
    this.totalCount = 0;
    this.totalTimeTaken = 0;
    this.logger = log4js.getLogger("ReportManager");

    if (reportObj) {
        this.timeReport = reportObj.timeReport;
    }

    xw = new XMLWriter();
    this.xw = xw;

    //checking if we need to do colorless reporting.
    if (global.color === false) {
        colors.mode = "none";
    } else {
        if (!process.stdout.isTTY) {
            colors.mode = "none";
        }
    }
    try {
        if (reportObj.arrTestSessions) {
            this.testSessions = reportObj.arrTestSessions;
        } else {
            this.testSessions = [];
        }
        if (reportObj.arrWDSessions) {
            this.wdSessions = reportObj.arrWDSessions;
        } else {
            this.wdSessions = [];
        }
        this.reportObj = reportObj;

    } catch (ex) {
        //console.log(ex);
    }

}

ReportManager.prototype.addProperty = function (name, val) {
    if (val) {
        val += '';  // convert to string, since XMLWriter is picky
        this.xw.startElement('property');
        this.xw.writeAttribute('name', name);
        this.xw.writeAttribute('value', val);
        this.xw.endElement();
    }
};

ReportManager.prototype.addTest = function (time, className, name, failureMessage) {
    this.xw.startElement('testcase');
    this.xw.writeAttribute('time', (time / 1000).toFixed(2));
    this.xw.writeAttribute('classname', className);
    this.xw.writeAttribute('name', name);

    if (failureMessage) {
        if (failureMessage === "skip") {
            this.xw.writeAttribute('executed', 'false');
        } else {
            this.xw.startElement('failure');
            this.xw.text(util.format(failureMessage));
            this.xw.endElement();
        }
    }

    this.xw.endElement();
};

ReportManager.prototype.parseYUIResults = function (testJson, dtName, self) {

    var l, m, errorRep,
        yuiUserAgent = testJson.ua,
        yuiSuiteName = testJson.name,
        browser = UserAgent.parse(yuiUserAgent).toAgent(),
        descTestName = dtName,
        currentUrl = testJson.currentUrl,
        yuiTestCaseName,
        timePerTest,
        className,
        testName;

    if (browser === "Other 0.0.0") {
        browser = "NodeJS";
    }

    for (l in testJson) {
        if (testJson[l]) {
            if (testJson[l].type === "testcase") {
                yuiTestCaseName = testJson[l].name;
                timePerTest = testJson[l].duration / testJson[l].total;
                self.passCount += testJson[l].passed;
                self.failCount += testJson[l].failed;
                self.skipCount += testJson[l].ignored;
                self.timeCount += testJson[l].duration;
                self.totalCount += testJson[l].total;

                for (m in testJson[l]) {
                    if (testJson[l][m].type === "test") {
                        className = browser + "." + descTestName;
                        testName = yuiSuiteName + "::" + yuiTestCaseName + "::" + testJson[l][m].name;
                        if (testJson[l][m].result === "fail") {
                            self.addTest(timePerTest, className, testName, testJson[l][m].message + "." + "\n" + " URL :" + currentUrl);
                        } else if (testJson[l][m].result === "pass") {
                            self.addTest(timePerTest, className, testName);
                        } else if (testJson[l][m].result === "ignore") {
                            self.addTest(timePerTest, className, testName, "skip");
                        }
                    }
                }
            }
        }
    }
};

ReportManager.prototype.showReportOnConsole = function (result, verbose) {

    var lastSuite, suite, k, k1, test, msg, m, browser,
        log = console.log;

    log("");
    browser = result.ua;
    if (result.failed) {
        log("Failed ".red + result.name.blue + " on " + browser);
    } else {
        log("Passed ".green + result.name.blue + " on " + browser);
    }
    log((result.passed + " Passed").green + ", " + (result.failed + " Failed ").red + ", " + (result.ignored + " skipped ").grey);
    if (result.failed) {
        for (k in result) {
            suite = result[k];
            if ("object" === typeof suite) {

                if (suite.failed) {
                    for (k1 in suite) {
                        test = suite[k1];
                        if ("object" === typeof test) {
                            if ("fail" === test.result) {

                                if (!lastSuite || lastSuite !== suite.name) {
                                    log(" in " + suite.name.bold);
                                    lastSuite = suite.name;
                                }
                                msg = test.message.split('\n');
                                log(" " + test.name.bold.red + ":" + msg[0]);
                                for (m = 1; m < msg.length; m = m + 1) {
                                    log(" " + msg[m]);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    log("");
};

ReportManager.prototype.writeReports = function () {

    var i, j, k, strJunitReport, strReport, rep, results, testJson, browser,
        testName, testSuiteName, arrReport, xmlReportName, jsonReportName, errorRep,
        self = this,
        wdSessions = self.wdSessions,
        testSessions = self.testSessions,
        jsonReport = {},
        log = console.log,
        resultPresent = false,
        missingErrorCount = 0,
        xwRoot = new XMLWriter(),
        fileUtil = new FileUtil(),
        dirPath,
        relPath;
    function writeFile(fileName, data) {
        fs.writeFileSync(fileName, data, 'utf8');
        log(("Report Created: " + fileName).inverse.toString());
    }

    /**
     * log test results on console
     */
    function logTestResults(reportObj) {
        log();
        log(("Total Number of Executed Tests: " + reportObj.totalCount).blue.toString());
        log(("Total Number of Passed Tests: " + reportObj.passCount).green.toString());
        log(("Total Number of Failed Tests: " + reportObj.failCount).red.toString());
        log(("Total Number of Skipped Tests: " + reportObj.skipCount).cyan.toString());
        log();
        log(("Total Test Execution Time Inside Browser: " + (reportObj.timeCount / 1000).toFixed(2) + " seconds").green.bold.toString());
        log(("Total Test Execution Time:" + reportObj.totalTimeTaken + " seconds").green.bold.toString());
        log();
    }

    for (i = 0; i < testSessions.length; i = i + 1) {

        rep = testSessions[i].driver.getReports();

        if (rep.scenario) {
            arrReport = rep.scenario;
        } else {
            arrReport = rep.results;
        }
        resultPresent = false;
        for (j = 0; j < arrReport.length; j = j + 1) {
            if (rep.scenario) {
                results = arrReport[j].results;

                for (k = 0; k < results.length; k = k + 1) {

                    testJson = results[k];
                    if (testJson.type === "report") {
                        if (!(testJson.passed === 0 && testJson.failed === 0 && testJson.total === 0)) {
                            resultPresent = true;
                            this.parseYUIResults(testJson, testSessions[i].driver.testName, this);
                        }
                    }
                }
            } else {
                testJson = arrReport[j];
                if (testJson.type === "report") {
                    if (!(testJson.passed === 0 && testJson.failed === 0 && testJson.total === 0)) {
                        resultPresent = true;
                        this.parseYUIResults(testJson, testSessions[i].driver.testName, this);
                    }
                }
            }
        }
        if (!resultPresent) {
            missingErrorCount = missingErrorCount + 1;
            errorRep = arrReport[arrReport.length - 1];
            try {

                if (errorRep) {
                    if (errorRep.results && errorRep.results.length > 0) {
                        this.addTest(0, "Error." + testSessions[i].driver.testName, "Error", errorRep.results[0].error);
                    } else if (errorRep.error) {
                        this.addTest(0, "Error." + testSessions[i].driver.testName, "Error", errorRep.error);
                    } else {
                        self.logger.debug("Test Report Error : UNKOWN");
                        this.addTest(0, "Error." + testSessions[i].driver.testName, "Error", "Arrow didn't receive result from the test !!!!");
                    }
                } else {
                    self.logger.debug("Test failed without any error message");
                }
            } catch (e) {
                self.logger.debug(e);
            }
        }
    }
    this.totalCount = this.totalCount + missingErrorCount;
    this.failCount = this.failCount + missingErrorCount;

    if (this.reportObj.descriptor) {

        dirPath = path.dirname(this.reportObj.descriptor);
        if (global.reportFolder) {

            // if condition to keep unit tests happy on node10
            if (global.workingDirectory ) {
                //Get relative path to the descriptor
                relPath = path.relative(global.workingDirectory, dirPath);
            } else {
                relPath = dirPath;
            }

            dirPath = path.resolve(global.reportFolder, 'arrow-report', relPath);
            // Create directory if it doesnt exist
            // This check helps when keepTestReport is set to true ( If its not set to true, arrow-report would have been cleaned up as part of setup anyways
            if (!existsSync(dirPath)) {
                fileUtil.createDirectory(dirPath);
            }

        }

        xmlReportName = path.resolve(dirPath, path.basename(this.reportObj.descriptor, '.json') + "-report.xml");
        jsonReportName = path.resolve(dirPath, path.basename(this.reportObj.descriptor, '.json') + "-report.json");

        if (this.failCount > 0) {
            global.failedDescriptors.push({"desc" : this.reportObj.descriptor, "failures" : this.failCount});
        }

        log(("Test Report Summary for the descriptor::" + this.reportObj.descriptor).bold.magenta.toString());

        logTestResults(this);

    } else {

        xmlReportName = "arrow-test-summary.xml";
        jsonReportName = "arrow-test-summary.json";

        if (this.reportObj.showConsolidatedReport) {

            log("\n*************************************************************************************");
            log("                            Consolidated Report                                          ");

            log("Test Report Summary".bold.magenta.toString());
            logTestResults(this);

            log("\n*************************************************************************************");

        }

        if (global.failedDescriptors.length > 0) {

            log("\n*************************************************************************************");
            log("                           List of failed descriptors                                  ");

            for (i = 0; i < global.failedDescriptors.length; i += 1) {
                log('\nFailed Descriptor Path : ' + global.failedDescriptors[i].desc);
                log('Total Number of Failed Tests : ' + global.failedDescriptors[i].failures);
                log("-----------------------------------------------------------------------");
            }
            log('\n');

        }

    }

    testSuiteName = "";
    if (this.reportObj.testSuiteName) {
        testSuiteName = this.reportObj.testSuiteName;
    } else {
        testSuiteName = "TestSuite";
    }
    this.errorCount = this.totalCount - this.passCount - this.failCount - this.skipCount;

    // Store report files in user specified location, if explicitly specified, otherwise store in arrow-target/arrow-report
    if (global.reportFolder) {

        this.xw.startElement("properties");
        this.addProperty("descriptor", this.reportObj.descriptor);
        this.addProperty("parallel", this.reportObj.parallel);
        this.addProperty("driver", this.reportObj.driver);
        this.addProperty("browser", this.reportObj.browser);
        this.addProperty("reuseSession", this.reportObj.reuseSession);
        this.addProperty("activeSeleniumSessionCount", wdSessions.length);
        this.addProperty("seleniumSessionIds", wdSessions.join());
        this.addProperty("group", this.reportObj.group);
        this.addProperty("testName", this.reportObj.testName);
        this.xw.endElement();
        //collecting all report
        strReport = xw.toString();

        //starting report root
        xwRoot.startDocument();
        xwRoot.startElement("testsuite");
        xwRoot.writeAttribute('failures', this.failCount.toString());
        xwRoot.writeAttribute('time', this.totalTimeTaken.toString());
        xwRoot.writeAttribute('errors', this.errorCount.toString());
        xwRoot.writeAttribute('skipped', this.skipCount.toString());
        xwRoot.writeAttribute('tests', this.totalCount.toString());
        xwRoot.writeAttribute('name', testSuiteName.toString());
        xwRoot.text("$$report$$");
        xwRoot.endElement();
        xwRoot.endDocument();
        strJunitReport = xwRoot.toString().replace("$$report$$", strReport);

        xmlReportName = path.resolve(global.reportFolder, 'arrow-report', xmlReportName);
        jsonReportName = path.resolve(global.reportFolder, 'arrow-report', jsonReportName);

        log('\n');
        writeFile(xmlReportName, strJunitReport);

        //writing json file
        for (i = 0; i < testSessions.length; i = i + 1) {
            jsonReport[testSessions[i].driver.testName] = JSON.parse(JSON.stringify(testSessions[i].driver.getReports()));
        }
        jsonReport.testSuiteName = testSuiteName;
        writeFile(jsonReportName, JSON.stringify(jsonReport));
        log('\n');

    }

};

ReportManager.prototype.writeTimeReport = function () {

    var timeReportJsonStr,
        descriptor,
        testSessionTimeArr,
        testName;

    timeReportJsonStr = '{ "descriptors" : [';

    for (descriptor in this.timeReport) {

        timeReportJsonStr += "{";
        timeReportJsonStr += ('"descriptor": ' + ' "' + descriptor + '"' + ' ,');
        timeReportJsonStr += ('"time": ' + ' "' + this.timeReport[descriptor].timeTaken + ' seconds' + '",');

        testSessionTimeArr = this.timeReport[descriptor].testSessionTimeArr;

        timeReportJsonStr += '"tests": [';

        for (testName in testSessionTimeArr) {
            timeReportJsonStr += '{';
            timeReportJsonStr += ('"Testname" :' + ' "' + testName + '"' + ',');
            timeReportJsonStr += ('"Time" :' + ' "' + testSessionTimeArr[testName].timeTaken + ' seconds' + '"');
            timeReportJsonStr += '} ,';
        }

        if (timeReportJsonStr.charAt(timeReportJsonStr.length - 1) === ",") {
            timeReportJsonStr = timeReportJsonStr.slice(0, -1);
        }

        timeReportJsonStr += " ] } ,";
    }

    if (timeReportJsonStr.charAt(timeReportJsonStr.length - 1) === ",") {
        timeReportJsonStr = timeReportJsonStr.slice(0, -1);
    }

    timeReportJsonStr += ('] ,"Total time":' + ' "' + this.totalTimeTaken + ' seconds' + '" ');
    timeReportJsonStr += ' }';

    fs.writeFileSync(path.resolve(global.reportFolder, 'arrow-report', 'timeReport.json'), timeReportJsonStr, 'utf8');

};

module.exports = ReportManager;
<|endoftext|>


<commit_before>// Maybe look into a more legit solution later
// node-uuid doesn't work with old IE's
// Source: http://stackoverflow.com/a/8809472
module.exports = function generateUUID () {
  var d = new Date().getTime()
  var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    var r = (d + Math.random() * 16) % 16 | 0
    d = Math.floor(d / 16)
    return (c === 'x' ? r : (r & 0x7 | 0x8)).toString(16)
  })
  return uuid
}
<commit_msg>Update generateUUID to match latest version in StackOverflow<commit_after>// Maybe look into a more legit solution later
// node-uuid doesn't work with old IE's
// Source: http://stackoverflow.com/a/8809472
module.exports = function generateUUID () {
  var d = new Date().getTime()
  if (global.performance && typeof global.performance.now === 'function') {
    d += global.performance.now() // use high-precision timer if available
  }
  var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    var r = (d + Math.random() * 16) % 16 | 0
    d = Math.floor(d / 16)
    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
  })
  return uuid
}
<|endoftext|>


<commit_before>'use strict';

var Class = require( 'uberclass' )
  , path = require( 'path' )
  , packageJson = require( path.resolve( __dirname + '/../../' ) + '/package.json' )
  , fs = require( 'fs' )
  , async = require( 'async' )
  , debug = require( 'debug' )( 'moduleLoader' )
  , moduleClass;

var Module = module.exports = Class.extend(
{
    instance: null,

    getInstance: function() {
        if ( this.instance === null ) {
            this.instance = new this();
        }
        return this.instance;
    }
},
{
    modules: null,

    modulesLoaded: null,

    routesInitialized: null,

    init: function() {
        this.modules = [];
        this.modulesLoaded = false;
        this.routesInitialized = false;
    },

    moduleIsEnabled: function( moduleName ) {
        return packageJson.bundledDependencies.indexOf( moduleName ) !== -1;
    },

    loadModules: function() {
        if ( this.modulesLoaded === false ) {
            debug( 'Loading modules...' );

            // Load each of our modules
            packageJson.bundledDependencies.forEach( this.proxy( 'loadModule' ) );

            // Fire the modulesLoadedHook hook on each of our modules letting them know that all modules have finished loading
            this.modules.forEach( this.proxy( 'modulesLoadedHook' ) );

            // Let the module loader know we have already loaded our modules
            this.modulesLoaded = true;
        } else {
            debug( 'Warning: All modules have already been loaded.' );
        }
    },

    loadModule: function( moduleName ) {
        debug( [ 'Loading the', moduleName, 'module' ].join( ' ' ) );

        // Get a copy of the module class
        moduleClass = require( 'classes' ).ModuleClass;

        // Load (require) the module and add to our modules array
        this.modules.push( require( moduleName ) );
    },

    modulesLoadedHook: function( module ) {
        if ( module instanceof moduleClass && typeof module.modulesLoaded === 'function' ) {
            debug( [ 'modulesLoaded (hook) for module', module.name ].join( ' ' ) );
            module.modulesLoaded();
        }
    },

    initializeRoutes: function() {
        if ( this.routesInitialized === false ) {
            // Give the modules notice that we are about to add our routes to the app
            this.modules.forEach( this.proxy( 'preRouteHook' ) );

            // Initialize all the modules routes
            this.modules.forEach( this.proxy( 'initializeModuleRoutes' ) );

            // We only want to do this once
            this.routesInitialized = true;
        } else {
            debug( 'Warning: All modules routes have been initialized already.' );
        }
    },

    preRouteHook: function( module ) {
        if ( module instanceof moduleClass && typeof module.preRoute === 'function' ) {
            debug( [ 'preRoute (hook) for module', module.name ].join( ' ' ) );
            module.preRoute();
        }
    },

    initializeModuleRoutes: function( module ) {
        if ( module instanceof moduleClass ) {
            debug( [ 'Initializing the', module.name, 'modules routes.' ].join( ' ' ) );
            module.initRoutes();
        }
    }
});<commit_msg>fix(moduleLoader): Implemented shutdown hook so modules can close all connections and cleanup resources (so the app doesn't stay open)<commit_after>'use strict';

var Class = require( 'uberclass' )
  , path = require( 'path' )
  , packageJson = require( path.resolve( __dirname + '/../../' ) + '/package.json' )
  , fs = require( 'fs' )
  , async = require( 'async' )
  , debug = require( 'debug' )( 'moduleLoader' )
  , moduleClass;

var Module = module.exports = Class.extend(
{
    instance: null,

    getInstance: function() {
        if ( this.instance === null ) {
            this.instance = new this();
        }
        return this.instance;
    }
},
{
    modules: null,

    modulesLoaded: null,

    routesInitialized: null,

    shutdown: function() {
        this.modules.forEach( this.proxy( 'preShutdownHook' ) );
        debug( 'Shutdown complete, if your app hangs one of your modules has not closed all its connections/resources.' );
    },

    preShutdownHook: function( module ) {
        if ( module instanceof moduleClass && typeof module.preShutdown === 'function' ) {
            debug( [ 'preShutdown (hook) for module', module.name ].join( ' ' ) );
            module.preShutdown();
        }
    },

    init: function() {
        this.modules = [];
        this.modulesLoaded = false;
        this.routesInitialized = false;
    },

    moduleIsEnabled: function( moduleName ) {
        return packageJson.bundledDependencies.indexOf( moduleName ) !== -1;
    },

    loadModules: function() {
        if ( this.modulesLoaded === false ) {
            debug( 'Loading modules...' );

            // Load each of our modules
            packageJson.bundledDependencies.forEach( this.proxy( 'loadModule' ) );

            // Fire the modulesLoadedHook hook on each of our modules letting them know that all modules have finished loading
            this.modules.forEach( this.proxy( 'modulesLoadedHook' ) );

            // Let the module loader know we have already loaded our modules
            this.modulesLoaded = true;
        } else {
            debug( 'Warning: All modules have already been loaded.' );
        }
    },

    loadModule: function( moduleName ) {
        debug( [ 'Loading the', moduleName, 'module' ].join( ' ' ) );

        // Get a copy of the module class
        moduleClass = require( 'classes' ).ModuleClass;

        // Load (require) the module and add to our modules array
        this.modules.push( require( moduleName ) );
    },

    modulesLoadedHook: function( module ) {
        if ( module instanceof moduleClass && typeof module.modulesLoaded === 'function' ) {
            debug( [ 'modulesLoaded (hook) for module', module.name ].join( ' ' ) );
            module.modulesLoaded();
        }
    },

    initializeRoutes: function() {
        if ( this.routesInitialized === false ) {
            // Give the modules notice that we are about to add our routes to the app
            this.modules.forEach( this.proxy( 'preRouteHook' ) );

            // Initialize all the modules routes
            this.modules.forEach( this.proxy( 'initializeModuleRoutes' ) );

            // We only want to do this once
            this.routesInitialized = true;
        } else {
            debug( 'Warning: All modules routes have been initialized already.' );
        }
    },

    preRouteHook: function( module ) {
        if ( module instanceof moduleClass && typeof module.preRoute === 'function' ) {
            debug( [ 'preRoute (hook) for module', module.name ].join( ' ' ) );
            module.preRoute();
        }
    },

    initializeModuleRoutes: function( module ) {
        if ( module instanceof moduleClass ) {
            debug( [ 'Initializing the', module.name, 'modules routes.' ].join( ' ' ) );
            module.initRoutes();
        }
    }
});<|endoftext|>


<commit_before>'use strict';

/**
 * takes screenshot of the current viewport
 *
 * @param {String} filename  path of file to be saved
 */

var async = require('async'),
    gm = require('gm');

module.exports = function viewportScreenshot(fileName) {

    var ErrorHandler = this.instance.ErrorHandler;

    /*!
     * make sure that callback contains chainit callback
     */
    var callback = arguments[arguments.length - 1];

    /*!
     * parameter check
     */
    if (typeof fileName !== 'string') {
        return callback(new ErrorHandler.CommandError('number or type of arguments don\'t agree with saveScreenshot command'));
    }

    var self = this.instance,
        response = {
            execute: [],
            screenshot: []
        };

    var getScrollingPosition = function() {
        var position = [0, 0];
        if (typeof window.pageYOffset !== 'undefined') {
            position = [
                window.pageXOffset,
                window.pageYOffset
            ];
        } else if (typeof document.documentElement.scrollTop !== 'undefined' && document.documentElement.scrollTop > 0) {
            position = [
                document.documentElement.scrollLeft,
                document.documentElement.scrollTop
            ];
        } else if (typeof document.body.scrollTop !== 'undefined') {
            position = [
                document.body.scrollLeft,
                document.body.scrollTop
            ];
        }
        return position;
    };

    async.waterfall([

        /*!
         * get scroll position
         */
        function(cb) {
            self.execute(getScrollingPosition, null, cb);
        },

        /*!
         * get viewport width/height and total width/height
         */
        function(res, cb) {
            response.execute.push(res);

            self.execute(function() {
                return {
                    screenWidth: Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
                    screenHeight: Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
                };
            }, cb);
        },
        function(res, cb) {
            response.execute.push(res);
            self.screenshot(cb);
        },
        function(res, cb) {
            response.screenshot = res;

            gm(new Buffer(res.value, 'base64')).crop(
                // width
                response.execute[1].value.screenWidth,
                // height
                response.execute[1].value.screenHeight,
                // top
                response.execute[0].value[0],
                // left
                response.execute[0].value[1]
            ).write(fileName, cb);

        }
    ], function(err) {

        callback(err, null, response);

    });

};
<commit_msg>Promisify viewport screenshot<commit_after>'use strict';

/**
 * takes screenshot of the current viewport
 *
 * @param {String} filename  path of file to be saved
 */

var async = require('async'),
    gm = require('gm'),
    q = require('q');

module.exports = function viewportScreenshot(fileName) {

    var ErrorHandler = this.instance.ErrorHandler;
    var defer = q.defer();

    /*!
     * parameter check
     */
    if (typeof fileName !== 'string') {
        defer.reject(new ErrorHandler.CommandError('number or type of arguments don\'t agree with saveScreenshot command'));
        return defer.promise;
    }

    var self = this.instance,
        response = {
            execute: [],
            screenshot: []
        };

    var getScrollingPosition = function() {
        var position = [0, 0];
        if (typeof window.pageYOffset !== 'undefined') {
            position = [
                window.pageXOffset,
                window.pageYOffset
            ];
        } else if (typeof document.documentElement.scrollTop !== 'undefined' && document.documentElement.scrollTop > 0) {
            position = [
                document.documentElement.scrollLeft,
                document.documentElement.scrollTop
            ];
        } else if (typeof document.body.scrollTop !== 'undefined') {
            position = [
                document.body.scrollLeft,
                document.body.scrollTop
            ];
        }
        return position;
    };

    async.waterfall([

        /*!
         * get scroll position
         */
        function(cb) {
            self.execute(getScrollingPosition, null, cb);
        },

        /*!
         * get viewport width/height and total width/height
         */
        function(res, cb) {
            response.execute.push(res);

            self.execute(function() {
                return {
                    screenWidth: Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
                    screenHeight: Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
                };
            }, cb);
        },
        function(res, cb) {
            response.execute.push(res);
            self.screenshot(cb);
        },
        function(res, cb) {
            response.screenshot = res;

            gm(new Buffer(res.value, 'base64')).crop(
                // width
                response.execute[1].value.screenWidth,
                // height
                response.execute[1].value.screenHeight,
                // top
                response.execute[0].value[0],
                // left
                response.execute[0].value[1]
            ).write(fileName, cb);

        }
    ], function(err) {

        if (err) {
            return defer.reject(err);
        }

        defer.resolve(response);

    });

    return defer.promise;

};
<|endoftext|>


<commit_before>'use babel'

import { Emitter } from 'atom'
import { View } from 'atom-space-pen-views'
import { logo } from '../../assets/svgs'
import getKeybinding from '../utils/get-keybinding'

/* when this file is run the keybindings aren't ready yet
 that's why giving atom some time to prepare before we ask
 for the keybinding */
setTimeout(() => {
  let keybinding = getKeybinding('command-palette:toggle')
  descriptions.ALL_GOOD.description = descriptions.ALL_GOOD.description.replace('!!!!', keybinding)
}, 3000)

const descriptions = {
  NOT_INSTALLED: {
    title: 'kd is missing',
    description: 'Please open a terminal window and install it with the following commands and come back once done. (mac/linux only)'
  },
  NOT_LOGGEDIN: {
    title: 'Login/Register',
    description: 'You are not logged in, please log in with the following commands and come back.'
  },
  DAEMON_NOT_INSTALLED: {
    title: 'Daemon is missing',
    description: 'It seems like the kd binary is installed but its daemon is missing, please install it with the following command.'
  },
  DAEMON_STOPPED: {
    title: 'Daemon is not running',
    description: 'kd daemon is not running please run it with the following command:'
  },
  ALL_GOOD: {
    title: 'kd is up & running',
    description: `The daemon is running. You're logged in! <br><br>Now you can open the command palette(!!!!) and type 'kd:' to see the available kd commands`
  }
}

const success = {
  NOT_INSTALLED: 'kd is installed',
  NOT_LOGGEDIN: 'you are logged in',
  DAEMON_NOT_INSTALLED: 'kd daemon is installed',
  DAEMON_STOPPED: 'kd daemon is running'
}

const helpers = {
  NOT_INSTALLED: [
    'brew tap rjeczalik/kd',
    'brew install kd --devel',
    'sudo kd daemon install --team <yourteam> --baseurl https://koding.com'
  ],
  NOT_LOGGEDIN: [
    'kd auth login --team <yourteam>'
  ],
  DAEMON_NOT_INSTALLED: [
    'sudo kd daemon install --team <yourteam>'
  ],
  DAEMON_STOPPED: [
    'sudo kd daemon restart'
  ]
}

export default class KDHelpView extends View {

  static content (data = {}) {
    this.div(() => {
      if (!data.mini) {
        this.div({ class: 'kd-help-view--logo' }, () => { this.raw(logo) })
      }
      this.div({ outlet: 'content' }, () => {
        this.raw("
checking kd…
")
      })
    })
  }

  setData (data = {}) {
    this.__data = data
    return data
  }

  getData () {
    return this.__data
  }

  initialize (data) {
    let { prompt, succeeded } = data
    this.setData(data)
    this.emitter = new Emitter()
    if (prompt) {
      this.setContent(prompt)
    }
    if (!prompt && succeeded.length) {
      succeeded.forEach(step => {
        this.appendContent(step)
      })
    }
  }

  setContent (prompt) {
    this.content.html('')
    let commandsHTML = ''
    if (helpers[prompt]) {
      for (let command of Array.from(helpers[prompt])) {
        commandsHTML += `<code>${command}</code><br>`
      }
    }
    this.content.append(`<h2>${descriptions[prompt].title}</h2>`)
    this.content.append(`<p>${descriptions[prompt].description}</p>`)
    if (commandsHTML) {
      this.content.append(`<pre>${commandsHTML}</pre>`)
    }
  }

  appendContent (step) {
    if (success[step]) {
      this.content.append(`<div class='icon icon-check'>${success[step]}</div>`)
    }
  }

}
<commit_msg>added copy to clipboard support for command helpers<commit_after>'use babel'
/* globals atom */

import { Emitter } from 'atom'
import { View } from 'atom-space-pen-views'
import { logo } from '../../assets/svgs'
import getKeybinding from '../utils/get-keybinding'

/* when this file is run the keybindings aren't ready yet
 that's why giving atom some time to prepare before we ask
 for the keybinding */
setTimeout(() => {
  let keybinding = getKeybinding('command-palette:toggle')
  descriptions.ALL_GOOD.description = descriptions.ALL_GOOD.description.replace('!!!!', keybinding)
}, 3000)

const descriptions = {
  NOT_INSTALLED: {
    title: 'kd is missing',
    description: 'Please open a terminal window and install it with the following commands and come back once done. (mac/linux only)'
  },
  NOT_LOGGEDIN: {
    title: 'Login/Register',
    description: 'You are not logged in, please log in with the following commands and come back.'
  },
  DAEMON_NOT_INSTALLED: {
    title: 'Daemon is missing',
    description: 'It seems like the kd binary is installed but its daemon is missing, please install it with the following command.'
  },
  DAEMON_STOPPED: {
    title: 'Daemon is not running',
    description: 'kd daemon is not running please run it with the following command:'
  },
  ALL_GOOD: {
    title: 'kd is up & running',
    description: `The daemon is running. You're logged in! <br><br>Now you can open the command palette(!!!!) and type 'kd:' to see the available kd commands`
  }
}

const success = {
  NOT_INSTALLED: 'kd is installed',
  NOT_LOGGEDIN: 'you are logged in',
  DAEMON_NOT_INSTALLED: 'kd daemon is installed',
  DAEMON_STOPPED: 'kd daemon is running'
}

const helpers = {
  NOT_INSTALLED: [
    'brew tap rjeczalik/kd',
    'brew install kd --devel',
    'sudo kd daemon install --team <yourteam> --baseurl https://koding.com'
  ],
  NOT_LOGGEDIN: [
    'kd auth login --team <yourteam>'
  ],
  DAEMON_NOT_INSTALLED: [
    'sudo kd daemon install --team <yourteam>'
  ],
  DAEMON_STOPPED: [
    'sudo kd daemon restart'
  ]
}

export default class KDHelpView extends View {

  static content (data = {}) {
    this.div(() => {
      if (!data.mini) {
        this.div({ class: 'kd-help-view--logo' }, () => { this.raw(logo) })
      }
      this.div({ outlet: 'content', click: 'click' }, () => {
        this.raw("
checking kd…
")
      })
    })
  }

  setData (data = {}) {
    this.__data = data
    return data
  }

  getData () {
    return this.__data
  }

  initialize (data) {
    let { prompt, succeeded } = data
    this.setData(data)
    this.emitter = new Emitter()
    if (prompt) {
      this.setContent(prompt)
    }
    if (!prompt && succeeded.length) {
      succeeded.forEach(step => {
        this.appendContent(step)
      })
    }
  }

  setContent (prompt) {
    this.content.html('')
    let commandsHTML = ''
    if (helpers[prompt]) {
      for (let command of Array.from(helpers[prompt])) {
        commandsHTML += `<span class='icon icon-clippy clippy' data-command='${command}'></span><code class='clippy'>${command}</code><br>`
      }
    }
    this.content.append(`<h2>${descriptions[prompt].title}</h2>`)
    this.content.append(`<p>${descriptions[prompt].description}</p>`)
    if (commandsHTML) {
      this.content.append(`<pre>${commandsHTML}</pre>`)
    }
  }

  click (event) {
    if (event.target.classList.contains('clippy')) {
      return this.copyToClipboard(event)
    }
  }

  copyToClipboard (event) {
    let command = event.target.dataset.command || event.target.innerHTML
    atom.clipboard.write(command)
    atom.notifications.add('info', 'Command copied to the clipboard!', {
      icon: 'clippy',
      description: `\`${command}\``
    })
  }

  appendContent (step) {
    if (success[step]) {
      this.content.append(`<div class='icon icon-check'>${success[step]}</div>`)
    }
  }

}
<|endoftext|>


<commit_before>import path from 'path';
import React, {Fragment} from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';

import {EnableableOperationPropType} from '../prop-types';
import Tooltip from '../atom/tooltip';
import Commands, {Command} from '../atom/commands';
import AggregatedReviewsContainer from '../containers/aggregated-reviews-container';
import EmojiReactionsView from '../views/emoji-reactions-view';
import GithubDotcomMarkdown from './github-dotcom-markdown';
import ErrorView from './error-view';
import PatchPreviewView from './patch-preview-view';
import Timeago from './timeago';
import {checkoutStates} from '../controllers/pr-checkout-controller';
import RefHolder from '../models/ref-holder';

export default class ReviewsView extends React.Component {
  static propTypes = {
    // GraphQL results
    repository: PropTypes.shape({
      pullRequest: PropTypes.object.isRequired,
    }).isRequired,

    // Package models
    multiFilePatch: PropTypes.object.isRequired,
    contextLines: PropTypes.number.isRequired,

    // for the dotcom link in the empty state
    number: PropTypes.number.isRequired,
    repo: PropTypes.string.isRequired,
    owner: PropTypes.string.isRequired,
    workdir: PropTypes.string.isRequired,

    // Local model objects
    checkoutOp: EnableableOperationPropType.isRequired,

    // Atom environment
    workspace: PropTypes.object.isRequired,
    config: PropTypes.object.isRequired,
    commands: PropTypes.object.isRequired,
    tooltips: PropTypes.object.isRequired,

    // Action methods
    openFile: PropTypes.func.isRequired,
    openDiff: PropTypes.func.isRequired,
    moreContext: PropTypes.func.isRequired,
    lessContext: PropTypes.func.isRequired,
  }

  constructor(props) {
    super(props);

    this.rootHolder = new RefHolder();
  }

  render() {
    return (

        {this.renderCommands()}
        {this.renderHeader()}

          <AggregatedReviewsContainer pullRequest={this.props.repository.pullRequest}>
            {({errors, summaries, commentThreads, loading}) => {
              if (errors.length > 0) {
                return errors.map((error, i) => (
                  <ErrorView
                    key={`error-${i}`}
                    title="Pagination error"
                    descriptions={[error.stack || error.toString()]}
                  />
                ));
              }

              return (
                <Fragment>
                  {this.renderReviewSummaries(summaries)}
                  {this.renderReviewCommentThreads(commentThreads)}
                </Fragment>
              );
            }}
          </AggregatedReviewsContainer>

    );
  }

  renderCommands() {
    return (
      <Commands registry={this.props.commands} target={this.rootHolder}>
        <Command command="github:more-context" callback={this.props.moreContext} />
        <Command command="github:less-context" callback={this.props.lessContext} />
      </Commands>
    );
  }

  renderHeader() {
    return (

          Reviews for

            {this.props.owner}/{this.props.repo}#{this.props.number}

        <button className="github-Reviews-headerButton github-Reviews-clickable icon icon-repo-sync" />
        <button className="github-Reviews-headerButton btn btn-primary">
          Checkout
        </button>

    );
  }

  // todo: maybe extract this into its own component since it is used in 2 places.
  renderCheckoutButton() {
    const {checkoutOp} = this.props;
    let extraClass = null;
    let buttonText = 'Checkout';
    let buttonTitle = null;

    if (!checkoutOp.isEnabled()) {
      buttonTitle = checkoutOp.getMessage();
      const reason = checkoutOp.why();
      if (reason === checkoutStates.HIDDEN) {
        return null;
      }

      buttonText = reason.when({
        current: 'Checked out',
        default: 'Checkout',
      });

      extraClass = 'github-IssueishDetailView-checkoutButton--' + reason.when({
        disabled: 'disabled',
        busy: 'busy',
        current: 'current',
      });
    }

    const classNames = cx('btn', 'btn-primary', 'github-IssueishDetailView-checkoutButton', extraClass);
    return (
      <button
        className={classNames}
        disabled={!checkoutOp.isEnabled()}
        title={buttonTitle}
        onClick={() => checkoutOp.run()}>
        {buttonText}
      </button>
    );
  }

  renderEmptyState() {
    const {number, repo, owner} = this.props;
    // todo: make this open the review flow in Atom instead of dotcom
    const pullRequestURL = `https://www.github.com/${owner}/${repo}/pull/${number}/`;
    return (

          This pull request has no reviews

            Start a review

    );
  }

  renderReviewSummaries = reviewSummaries => {
    if (reviewSummaries.length === 0) {
      return this.renderEmptyState();
    }
    return (

          Summaries
          {/* this.renderCheckoutButton() */}

          {reviewSummaries.map(this.renderReviewSummary)}

    );
  }

  renderReviewSummary = review => {
    const reviewTypes = type => {
      return {
        APPROVED: {icon: 'icon-check', copy: 'approved these changes'},
        COMMENTED: {icon: 'icon-comment', copy: 'commented'},
        CHANGES_REQUESTED: {icon: 'icon-alert', copy: 'requested changes'},
      }[type] || {icon: '', copy: ''};
    };

    const {icon, copy} = reviewTypes(review.state);

    // filter non actionable empty summary comments from this view
    if (copy === 'commented' && review.body === '') {
      return null;
    }

    const reviewAuthor = review.author ? review.author.login : '';
    return (

          <span className={`github-ReviewSummary-icon icon ${icon}`} />

          <a className="github-ReviewSummary-username" href={`https://github.com/${reviewAuthor}`}>{reviewAuthor}
          {copy}
          <Timeago className="github-ReviewSummary-timeAgo" time={review.submittedAt} displayStyle="short" />

          <GithubDotcomMarkdown html={review.body} switchToIssueish={() => {}} />
          <EmojiReactionsView reactionGroups={review.reactionGroups} />

    );
  }

  renderReviewCommentThreads = commentThreads => {
    if (commentThreads.length === 0) {
      return null;
    }

    const resolvedThreads = commentThreads.filter(thread => thread.thread.isResolved).length;

    return (

          Comments

              Resolved
              {' '}{resolvedThreads}{' '}
              of
              {' '}{commentThreads.length}

            <progress className="github-Reviews-progessBar" value={resolvedThreads} max={commentThreads.length} />

          {commentThreads.map(this.renderReviewCommentThread)}

    );
  }

  renderReviewCommentThread = commentThread => {
    const {comments} = commentThread;
    const rootComment = comments[0];
    const {dir, base} = path.parse(rootComment.path);
    // TODO: fixme
    const lineNumber = rootComment.position;

    const refJumpToFileButton = new RefHolder();
    const jumpToFileDisabledLabel = 'Checkout this pull request to enable Jump To File.';

    return (
      <details className="github-Review" key={`review-comment-${rootComment.id}`}>

          {dir}
          /{base}
          {lineNumber}

          <Timeago className="github-Review-referenceTimeAgo" time={rootComment.createdAt} displayStyle="short" />

          <button className="github-Review-navButton icon icon-code"
            data-path={rootComment.path} data-line={lineNumber}
            onClick={this.openFile} disabled={this.props.checkoutOp.isEnabled()}
            ref={refJumpToFileButton.setter}>
            Jump To File
          </button>
          <button className="github-Review-navButton icon icon-diff"
            data-path={rootComment.path} data-line={lineNumber}
            onClick={this.openDiff}>
            Open Diff
          </button>
          {this.props.checkoutOp.isEnabled() &&
            <Tooltip
              manager={this.props.tooltips}
              target={refJumpToFileButton}
              title={jumpToFileDisabledLabel}
              showDelay={200}
            />
          }

        {rootComment.position !== null && (
          <PatchPreviewView
            multiFilePatch={this.props.multiFilePatch}
            fileName={rootComment.path}
            diffRow={rootComment.position}
            maxRowCount={this.props.contextLines}
            config={this.props.config}
          />
        )}

          {comments.map(this.renderComment)}

            <textarea className="github-Review-replyInput input-textarea native-key-bindings" placeholder="Reply..." />
            <button className="github-Review-replyButton btn" title="Add your comment">Comment</button>

          <button className="github-Review-resolveButton btn btn-primary icon icon-check" title="Mark as resolved">
            Mark as resolved
          </button>

    );
  }

  renderComment = comment => {
    return (

          <a className="github-Review-username" href={`https://github.com/${comment.author.login}`}>{comment.author.login}

            <Timeago displayStyle="long" time={comment.createdAt} />

          <GithubDotcomMarkdown html={comment.bodyHTML} switchToIssueish={() => {}} />
          <EmojiReactionsView reactionGroups={comment.reactionGroups} />

    );
  }

  openFile = evt => {
    if (!this.props.checkoutOp.isEnabled()) {
      const target = evt.currentTarget;
      this.props.openFile(target.dataset.path, target.dataset.line - 1);
    }
  }

  openDiff = evt => {
    const target = evt.currentTarget;
    this.props.openDiff(target.dataset.path, parseInt(target.dataset.line, 10));
  }
}
<commit_msg>actually wire up the checkout button<commit_after>import path from 'path';
import React, {Fragment} from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';

import {EnableableOperationPropType} from '../prop-types';
import Tooltip from '../atom/tooltip';
import Commands, {Command} from '../atom/commands';
import AggregatedReviewsContainer from '../containers/aggregated-reviews-container';
import EmojiReactionsView from '../views/emoji-reactions-view';
import GithubDotcomMarkdown from './github-dotcom-markdown';
import ErrorView from './error-view';
import PatchPreviewView from './patch-preview-view';
import Timeago from './timeago';
import {checkoutStates} from '../controllers/pr-checkout-controller';
import RefHolder from '../models/ref-holder';

export default class ReviewsView extends React.Component {
  static propTypes = {
    // GraphQL results
    repository: PropTypes.shape({
      pullRequest: PropTypes.object.isRequired,
    }).isRequired,

    // Package models
    multiFilePatch: PropTypes.object.isRequired,
    contextLines: PropTypes.number.isRequired,

    // for the dotcom link in the empty state
    number: PropTypes.number.isRequired,
    repo: PropTypes.string.isRequired,
    owner: PropTypes.string.isRequired,
    workdir: PropTypes.string.isRequired,

    // Local model objects
    checkoutOp: EnableableOperationPropType.isRequired,

    // Atom environment
    workspace: PropTypes.object.isRequired,
    config: PropTypes.object.isRequired,
    commands: PropTypes.object.isRequired,
    tooltips: PropTypes.object.isRequired,

    // Action methods
    openFile: PropTypes.func.isRequired,
    openDiff: PropTypes.func.isRequired,
    moreContext: PropTypes.func.isRequired,
    lessContext: PropTypes.func.isRequired,
  }

  constructor(props) {
    super(props);

    this.rootHolder = new RefHolder();
  }

  render() {
    return (

        {this.renderCommands()}
        {this.renderHeader()}

          <AggregatedReviewsContainer pullRequest={this.props.repository.pullRequest}>
            {({errors, summaries, commentThreads, loading}) => {
              if (errors.length > 0) {
                return errors.map((error, i) => (
                  <ErrorView
                    key={`error-${i}`}
                    title="Pagination error"
                    descriptions={[error.stack || error.toString()]}
                  />
                ));
              }

              return (
                <Fragment>
                  {this.renderReviewSummaries(summaries)}
                  {this.renderReviewCommentThreads(commentThreads)}
                </Fragment>
              );
            }}
          </AggregatedReviewsContainer>

    );
  }

  renderCommands() {
    return (
      <Commands registry={this.props.commands} target={this.rootHolder}>
        <Command command="github:more-context" callback={this.props.moreContext} />
        <Command command="github:less-context" callback={this.props.lessContext} />
      </Commands>
    );
  }

  renderHeader() {
    return (

          Reviews for

            {this.props.owner}/{this.props.repo}#{this.props.number}

        <button className="github-Reviews-headerButton github-Reviews-clickable icon icon-repo-sync" />
        {this.renderCheckoutButton()}

    );
  }

  // todo: maybe extract this into its own component since it is used in 2 places.
  renderCheckoutButton() {
    const {checkoutOp} = this.props;
    let extraClass = null;
    let buttonText = 'Checkout';
    let buttonTitle = null;

    if (!checkoutOp.isEnabled()) {
      buttonTitle = checkoutOp.getMessage();
      const reason = checkoutOp.why();
      if (reason === checkoutStates.HIDDEN) {
        return null;
      }

      buttonText = reason.when({
        current: 'Checked out',
        default: 'Checkout',
      });

      extraClass = 'github-Reviews-checkoutButton--' + reason.when({
        disabled: 'disabled',
        busy: 'busy',
        current: 'current',
      });
    }

    const classNames = cx('btn', 'btn-primary', 'github-Reviews-headerButton', extraClass);
    return (
      <button
        className={classNames}
        disabled={!checkoutOp.isEnabled()}
        title={buttonTitle}
        onClick={() => checkoutOp.run()}>
        {buttonText}
      </button>
    );
  }

  renderEmptyState() {
    const {number, repo, owner} = this.props;
    // todo: make this open the review flow in Atom instead of dotcom
    const pullRequestURL = `https://www.github.com/${owner}/${repo}/pull/${number}/`;
    return (

          This pull request has no reviews

            Start a review

    );
  }

  renderReviewSummaries = reviewSummaries => {
    if (reviewSummaries.length === 0) {
      return this.renderEmptyState();
    }
    return (

          Summaries

          {reviewSummaries.map(this.renderReviewSummary)}

    );
  }

  renderReviewSummary = review => {
    const reviewTypes = type => {
      return {
        APPROVED: {icon: 'icon-check', copy: 'approved these changes'},
        COMMENTED: {icon: 'icon-comment', copy: 'commented'},
        CHANGES_REQUESTED: {icon: 'icon-alert', copy: 'requested changes'},
      }[type] || {icon: '', copy: ''};
    };

    const {icon, copy} = reviewTypes(review.state);

    // filter non actionable empty summary comments from this view
    if (copy === 'commented' && review.body === '') {
      return null;
    }

    const reviewAuthor = review.author ? review.author.login : '';
    return (

          <span className={`github-ReviewSummary-icon icon ${icon}`} />

          <a className="github-ReviewSummary-username" href={`https://github.com/${reviewAuthor}`}>{reviewAuthor}
          {copy}
          <Timeago className="github-ReviewSummary-timeAgo" time={review.submittedAt} displayStyle="short" />

          <GithubDotcomMarkdown html={review.body} switchToIssueish={() => {}} />
          <EmojiReactionsView reactionGroups={review.reactionGroups} />

    );
  }

  renderReviewCommentThreads = commentThreads => {
    if (commentThreads.length === 0) {
      return null;
    }

    const resolvedThreads = commentThreads.filter(thread => thread.thread.isResolved).length;

    return (

          Comments

              Resolved
              {' '}{resolvedThreads}{' '}
              of
              {' '}{commentThreads.length}

            <progress className="github-Reviews-progessBar" value={resolvedThreads} max={commentThreads.length} />

          {commentThreads.map(this.renderReviewCommentThread)}

    );
  }

  renderReviewCommentThread = commentThread => {
    const {comments} = commentThread;
    const rootComment = comments[0];
    const {dir, base} = path.parse(rootComment.path);
    // TODO: fixme
    const lineNumber = rootComment.position;

    const refJumpToFileButton = new RefHolder();
    const jumpToFileDisabledLabel = 'Checkout this pull request to enable Jump To File.';

    return (
      <details className="github-Review" key={`review-comment-${rootComment.id}`}>

          {dir}
          /{base}
          {lineNumber}

          <Timeago className="github-Review-referenceTimeAgo" time={rootComment.createdAt} displayStyle="short" />

          <button className="github-Review-navButton icon icon-code"
            data-path={rootComment.path} data-line={lineNumber}
            onClick={this.openFile} disabled={this.props.checkoutOp.isEnabled()}
            ref={refJumpToFileButton.setter}>
            Jump To File
          </button>
          <button className="github-Review-navButton icon icon-diff"
            data-path={rootComment.path} data-line={lineNumber}
            onClick={this.openDiff}>
            Open Diff
          </button>
          {this.props.checkoutOp.isEnabled() &&
            <Tooltip
              manager={this.props.tooltips}
              target={refJumpToFileButton}
              title={jumpToFileDisabledLabel}
              showDelay={200}
            />
          }

        {rootComment.position !== null && (
          <PatchPreviewView
            multiFilePatch={this.props.multiFilePatch}
            fileName={rootComment.path}
            diffRow={rootComment.position}
            maxRowCount={this.props.contextLines}
            config={this.props.config}
          />
        )}

          {comments.map(this.renderComment)}

            <textarea className="github-Review-replyInput input-textarea native-key-bindings" placeholder="Reply..." />
            <button className="github-Review-replyButton btn" title="Add your comment">Comment</button>

          <button className="github-Review-resolveButton btn btn-primary icon icon-check" title="Mark as resolved">
            Mark as resolved
          </button>

    );
  }

  renderComment = comment => {
    return (

          <a className="github-Review-username" href={`https://github.com/${comment.author.login}`}>{comment.author.login}

            <Timeago displayStyle="long" time={comment.createdAt} />

          <GithubDotcomMarkdown html={comment.bodyHTML} switchToIssueish={() => {}} />
          <EmojiReactionsView reactionGroups={comment.reactionGroups} />

    );
  }

  openFile = evt => {
    if (!this.props.checkoutOp.isEnabled()) {
      const target = evt.currentTarget;
      this.props.openFile(target.dataset.path, target.dataset.line - 1);
    }
  }

  openDiff = evt => {
    const target = evt.currentTarget;
    this.props.openDiff(target.dataset.path, parseInt(target.dataset.line, 10));
  }
}
<|endoftext|>


<commit_before>/*
 * winston-papertrail.js:
 *
 *          Transport for logging to Papertrail Service
 *          www.papertrailapp.com
 *
 * (C) 2013 Ken Perkins
 * MIT LICENCE
 *
 */

var os = require('os'),
    net = require('net'),
    tls = require('tls'),
    syslogProducer = require('glossy').Produce,
    util = require('util'),
    winston = require('winston');

/**
 * Papertrail class
 *
 * @description constructor for the Papertrail transport
 *
 * @param {object}      options                 options for your papertrail transport
 *
 * @param {string}      options.host            host for papertrail endpoint
 *
 * @param {Number}      options.port            port for papertrail endpoint
 *
 * @param {Boolean}     [options.disableTls]    disable TLS connections, enabled by default
 *
 * @param {string}      [options.hostname]      name for the logging hostname in Papertrail
 *
 * @param {string}      [options.program]       name for the logging program
 *
 * @param {string}      [options.facility]      syslog facility for log messages
 *
 * @param {string}      [options.level]         log level for your transport (info)
 *
 * @param {string}      [options.levels]        custom mapping of log levels (npm levels to RFC5424 severities)
 *
 * @param {Function}    [options.logFormat]     function to format your log message before sending
 *
 * @param {Number}      [options.attemptsBeforeDecay]       how many reconnections should
 *                                                          be attempted before backing of (5)
 *
 * @param {Number}      [options.maximumAttempts]           maximum attempts before
 *                                                          disabling buffering (25)
 *
 * @param {Number}      [options.connectionDelay]           delay between
 *                                                          reconnection attempts in ms (1000)
 *
 * @param {Boolean}     [options.handleExceptions]          passed to base Transport (false)
 *
 * @param {Boolean}     [options.colorize]                  enable colors in Papertrail (false)
 *
 * @param {Number}      [options.maxDelayBetweenReconnection]   when backing off,
 *                                                              what's the max time between
 *                                                              reconnections (ms)
 *
 * @param {Boolean}     [options.inlineMeta]        inline multi-line messages (false)
 *
 * @type {Function}
 */
var Papertrail = exports.Papertrail = function (options) {

    var self = this;
    winston.Transport.call(self, options);

    self._KEEPALIVE_INTERVAL = 15 * 1000;

    options = options || {};

    self.name = 'Papertrail';
    self.level = options.level || 'info';

    // Default npm log levels
    self.levels = options.levels || {
        silly: 7,
        debug: 7,
        verbose: 7,
        info: 6,
        warn: 4,
        error: 3
    };

    // Papertrail Service Host
    self.host = options.host;

    // Papertrail Service Port
    self.port = options.port;

    // Disable TLS connections (enabled by default)
    self.disableTls = typeof options.disableTls === 'boolean' ? options.disableTls : false;

    // Hostname of the current app
    self.hostname = options.hostname || os.hostname();

    // Program is an affordance for Papertrail to name the source of log entries
    self.program = options.program || 'default';

    // Syslog facility to log messages as to Papertrail
    self.facility = options.facility || 'daemon';

    // Send ANSI color codes through to Papertrail
    self.colorize = options.colorize || false;

    // Format your log messages prior to delivery
    self.logFormat = options.logFormat || function (level, message) {
        return level + ' ' + message;
    };

    // Number of attempts before decaying reconnection
    self.attemptsBeforeDecay = options.attemptsBeforeDecay || 5;

    // Maximum number of reconnection attempts before disabling buffer
    self.maximumAttempts = options.maximumAttempts || 25;

    // Delay between normal attempts
    self.connectionDelay = options.connectionDelay || 1000;

    // Handle Exceptions
    self.handleExceptions = options.handleExceptions || false;

    // Maximum delay between attempts
    self.maxDelayBetweenReconnection =
        options.maxDelayBetweenReconnection || 60000;

    // Maximum buffer size (default: 1MB)
    self.maxBufferSize =
        options.maxBufferSize || 1 * 1024 * 1024;

    // Inline meta flag
    self.inlineMeta = options.inlineMeta || false;

    self.producer = new syslogProducer({ facility: self.facility });

    self.currentRetries = 0;
    self.totalRetries = 0;
    self.buffer = '';
    self.loggingEnabled = true;
    self._shutdown = false;

    // Error out if we don't have a host or port
    if (!self.host || !self.port) {
        throw new Error('Missing required parameters: host and port');
    }

    // Open the connection
    connectStream();

    function cleanup() {
        self._erroring = true;
        try {
            if (self.socket) {
                self.socket.destroy();

                //this throws some uncatchable thing, eww.
                //self.socket.end();
                self.socket = null;
            }
        }
        catch (e) { }

        try {
            if (self.stream) {
                //we're cleaning up, don't loop.
                self.stream.removeListener('end', connectStream);
                self.stream.removeListener('error', onErrored);

                self.stream.destroy();
                self.stream = null;
            }
        }
        catch (e) { }
        self._erroring = false;
    }

    function wireStreams() {
        self.stream.once('error', onErrored);

        // If we have the stream end, simply reconnect
        self.stream.once('end', connectStream);
    }

    // Opens a connection to Papertrail
    function connectStream() {
        // don't connect on either error or shutdown
        if (self._shutdown || self._erroring) {
            return;
        }

        cleanup();

        try {
            if (self.disableTls) {
                self.stream = net.createConnection(self.port, self.host, onConnected);
                self.stream.setKeepAlive(true, self._KEEPALIVE_INTERVAL);

                wireStreams();
            }
            else {
                var socket = net.createConnection(self.port, self.host, function () {
                    socket.setKeepAlive(true, self._KEEPALIVE_INTERVAL);

                    self.stream = tls.connect({
                        socket: socket,
                        rejectUnauthorized: false
                    }, onConnected);

                    wireStreams();
                });

                socket.on('error', onErrored);
                self.socket = socket;
            }
        }
        catch (e) {
            onErrored(e);
        }
    }
    self._reconnectStream = connectStream;

    function onErrored(err) {
        // make sure we prevent simultaneous attempts to connect and handle errors
        self._erroring = true;

        self.emit('error', err);

        // We may be disconnected from the papertrail endpoint for any number of reasons;
        // i.e. inactivity, network problems, etc, and we need to be resilient against this
        // that said, we back off reconnection attempts in case Papertrail is truly down
        setTimeout(function () {
            // Increment our retry counts
            self.currentRetries++;
            self.totalRetries++;

            // Decay the retry rate exponentially up to max between attempts
            if ((self.connectionDelay < self.maxDelayBetweenReconnection) &&
            (self.currentRetries >= self.attemptsBeforeDecay)) {
                self.connectionDelay = self.connectionDelay * 2;
                self.currentRetries = 0;
            }

            // Stop buffering messages after a fixed number of retries.
            // This is to keep the buffer from growing unbounded
            if (self.loggingEnabled &&
                (self.totalRetries >= (self.maximumAttempts))) {
                    self.loggingEnabled = false;
                    self.emit('error', new Error('Max entries eclipsed, disabling buffering'));
            }

            // continue
            self._erroring = false;
            connectStream();

        }, self.connectionDelay);
    }

    function onConnected() {
        // Reset our variables
        self.loggingEnabled = true;
        self.currentRetries = 0;
        self.totalRetries = 0;
        self.connectionDelay = options.connectionDelay || 1000;

        self.emit('connect', 'Connected to Papertrail at ' + self.host + ':' + self.port);

        // Did we get messages buffered
        if (self.buffer.length > 0) {
            self.stream.write(self.buffer);
            self.buffer = '';
        }
    }
};

//
//
// Inherit from `winston.Transport` so you can take advantage
// of the base functionality and `.handleExceptions()`.
//
util.inherits(Papertrail, winston.Transport);

//
// Define a getter so that `winston.transports.Papertrail`
// is available and thus backwards compatible.
//
winston.transports.Papertrail = Papertrail;

/**
 * Papertrail.log
 *
 * @description Core logging method exposed to Winston. Metadata is optional.
 *
 * @param {String}        level    Level at which to log the message.
 * @param {String}        msg        Message to log
 * @param {String|object|Function}        [meta]    Optional metadata to attach
 * @param {Function}    callback
 * @returns {*}
 */
Papertrail.prototype.log = function (level, msg, meta, callback) {

    var self = this;

    // make sure we handle when meta isn't provided
    if (typeof(meta) === 'function' && !callback) {
        callback = meta;
        meta = false;
    }

    if (meta && typeof meta === 'object' && (Object.keys(meta).length === 0)
        && (!util.isError(meta))) {
        meta = false;
    }

    // If the logging buffer is disabled, drop the message on the floor
    if (!this.loggingEnabled) {
        return callback(null, true);
    }

    var output = msg;

    // If we don't have a string for the message,
    // lets transform it before moving on
    if (typeof(output) !== 'string') {
        output = util.inspect(output, false, null, self.colorize);
    }

    if (meta) {
        if (typeof meta !== 'object') {
            output += ' ' + meta;
        }
        else if (meta) {
            if (this.inlineMeta) {
                output += ' ' + util.inspect(meta, false, null, self.colorize).replace(/[\n\t]\s*/gm, " ");
            }
            else {
                output += '\n' + util.inspect(meta, false, null, self.colorize);
            }
        }
    }

    this.sendMessage(this.hostname, this.program, level, output);

    callback(null, true);
};

/**
 * Papertrail.sendMessage
 *
 * @description sending the message to the stream, or buffering if not connected
 *
 * @param {String}    hostname    Hostname of the source application.
 * @param {String}    program     Name of the source application
 * @param {String}    level        Log level of the message
 * @param {String}    message        The message to deliver
 */
Papertrail.prototype.sendMessage = function (hostname, program, level, message) {

    var self = this,
        lines = [],
        msg = '',
        gap = '';

    // Only split if we actually have a message
    if (message) {
        lines = message.split('\n');
    }
    else {
        lines = [''];
    }

    // If the incoming message has multiple lines, break them and format each
    // line as it's own message
    for (var i = 0; i < lines.length; i++) {

        // don't send extra message if our message ends with a newline
        if ((lines[i].length === 0) && (i == lines.length - 1)) {
            break;
        }

        if (i == 1) {
            gap = '    ';
        }

        msg += self.producer.produce({
            severity: self.levels[level] ? self.levels[level] : level,
            host: hostname,
            appName: program,
            date: new Date(),
            message: self.logFormat(self.colorize ? winston.config.colorize(level) : level, gap + lines[i])
        }) + '\r\n';
    }

    if (this.stream && this.stream.writable) {
        this.stream.write(msg);
    }
    else if (this.loggingEnabled && this.buffer.length < this.maxBufferSize) {
        this.buffer += msg;
    }
};

/**
 * Papertrail.close
 *
 * @description closes the underlying TLS connection and disables automatic
 * reconnection, allowing the process to exit
 */
Papertrail.prototype.close = function() {
    var self = this;

    self._shutdown = true;

    if (self.stream) {
        self.stream.end();
    }
    // if there's no stream yet, that means we're still connecting
    // lets wire a connect handler, and then invoke close again
    else {
        self.on('connect', function() {
            self.close();
        });
    }
};
<commit_msg>Execute log callback once stream has been written<commit_after>/*
 * winston-papertrail.js:
 *
 *          Transport for logging to Papertrail Service
 *          www.papertrailapp.com
 *
 * (C) 2013 Ken Perkins
 * MIT LICENCE
 *
 */

var os = require('os'),
    net = require('net'),
    tls = require('tls'),
    syslogProducer = require('glossy').Produce,
    util = require('util'),
    winston = require('winston');

/**
 * Papertrail class
 *
 * @description constructor for the Papertrail transport
 *
 * @param {object}      options                 options for your papertrail transport
 *
 * @param {string}      options.host            host for papertrail endpoint
 *
 * @param {Number}      options.port            port for papertrail endpoint
 *
 * @param {Boolean}     [options.disableTls]    disable TLS connections, enabled by default
 *
 * @param {string}      [options.hostname]      name for the logging hostname in Papertrail
 *
 * @param {string}      [options.program]       name for the logging program
 *
 * @param {string}      [options.facility]      syslog facility for log messages
 *
 * @param {string}      [options.level]         log level for your transport (info)
 *
 * @param {string}      [options.levels]        custom mapping of log levels (npm levels to RFC5424 severities)
 *
 * @param {Function}    [options.logFormat]     function to format your log message before sending
 *
 * @param {Number}      [options.attemptsBeforeDecay]       how many reconnections should
 *                                                          be attempted before backing of (5)
 *
 * @param {Number}      [options.maximumAttempts]           maximum attempts before
 *                                                          disabling buffering (25)
 *
 * @param {Number}      [options.connectionDelay]           delay between
 *                                                          reconnection attempts in ms (1000)
 *
 * @param {Boolean}     [options.handleExceptions]          passed to base Transport (false)
 *
 * @param {Boolean}     [options.colorize]                  enable colors in Papertrail (false)
 *
 * @param {Number}      [options.maxDelayBetweenReconnection]   when backing off,
 *                                                              what's the max time between
 *                                                              reconnections (ms)
 *
 * @param {Boolean}     [options.inlineMeta]        inline multi-line messages (false)
 *
 * @type {Function}
 */
var Papertrail = exports.Papertrail = function (options) {

    var self = this;
    winston.Transport.call(self, options);

    self._KEEPALIVE_INTERVAL = 15 * 1000;

    options = options || {};

    self.name = 'Papertrail';
    self.level = options.level || 'info';

    // Default npm log levels
    self.levels = options.levels || {
        silly: 7,
        debug: 7,
        verbose: 7,
        info: 6,
        warn: 4,
        error: 3
    };

    // Papertrail Service Host
    self.host = options.host;

    // Papertrail Service Port
    self.port = options.port;

    // Disable TLS connections (enabled by default)
    self.disableTls = typeof options.disableTls === 'boolean' ? options.disableTls : false;

    // Hostname of the current app
    self.hostname = options.hostname || os.hostname();

    // Program is an affordance for Papertrail to name the source of log entries
    self.program = options.program || 'default';

    // Syslog facility to log messages as to Papertrail
    self.facility = options.facility || 'daemon';

    // Send ANSI color codes through to Papertrail
    self.colorize = options.colorize || false;

    // Format your log messages prior to delivery
    self.logFormat = options.logFormat || function (level, message) {
        return level + ' ' + message;
    };

    // Number of attempts before decaying reconnection
    self.attemptsBeforeDecay = options.attemptsBeforeDecay || 5;

    // Maximum number of reconnection attempts before disabling buffer
    self.maximumAttempts = options.maximumAttempts || 25;

    // Delay between normal attempts
    self.connectionDelay = options.connectionDelay || 1000;

    // Handle Exceptions
    self.handleExceptions = options.handleExceptions || false;

    // Maximum delay between attempts
    self.maxDelayBetweenReconnection =
        options.maxDelayBetweenReconnection || 60000;

    // Maximum buffer size (default: 1MB)
    self.maxBufferSize =
        options.maxBufferSize || 1 * 1024 * 1024;

    // Inline meta flag
    self.inlineMeta = options.inlineMeta || false;

    self.producer = new syslogProducer({ facility: self.facility });

    self.currentRetries = 0;
    self.totalRetries = 0;
    self.buffer = '';
    self.loggingEnabled = true;
    self._shutdown = false;

    // Error out if we don't have a host or port
    if (!self.host || !self.port) {
        throw new Error('Missing required parameters: host and port');
    }

    // Open the connection
    connectStream();

    function cleanup() {
        self._erroring = true;
        try {
            if (self.socket) {
                self.socket.destroy();

                //this throws some uncatchable thing, eww.
                //self.socket.end();
                self.socket = null;
            }
        }
        catch (e) { }

        try {
            if (self.stream) {
                //we're cleaning up, don't loop.
                self.stream.removeListener('end', connectStream);
                self.stream.removeListener('error', onErrored);

                self.stream.destroy();
                self.stream = null;
            }
        }
        catch (e) { }
        self._erroring = false;
    }

    function wireStreams() {
        self.stream.once('error', onErrored);

        // If we have the stream end, simply reconnect
        self.stream.once('end', connectStream);
    }

    // Opens a connection to Papertrail
    function connectStream() {
        // don't connect on either error or shutdown
        if (self._shutdown || self._erroring) {
            return;
        }

        cleanup();

        try {
            if (self.disableTls) {
                self.stream = net.createConnection(self.port, self.host, onConnected);
                self.stream.setKeepAlive(true, self._KEEPALIVE_INTERVAL);

                wireStreams();
            }
            else {
                var socket = net.createConnection(self.port, self.host, function () {
                    socket.setKeepAlive(true, self._KEEPALIVE_INTERVAL);

                    self.stream = tls.connect({
                        socket: socket,
                        rejectUnauthorized: false
                    }, onConnected);

                    wireStreams();
                });

                socket.on('error', onErrored);
                self.socket = socket;
            }
        }
        catch (e) {
            onErrored(e);
        }
    }
    self._reconnectStream = connectStream;

    function onErrored(err) {
        // make sure we prevent simultaneous attempts to connect and handle errors
        self._erroring = true;

        self.emit('error', err);

        // We may be disconnected from the papertrail endpoint for any number of reasons;
        // i.e. inactivity, network problems, etc, and we need to be resilient against this
        // that said, we back off reconnection attempts in case Papertrail is truly down
        setTimeout(function () {
            // Increment our retry counts
            self.currentRetries++;
            self.totalRetries++;

            // Decay the retry rate exponentially up to max between attempts
            if ((self.connectionDelay < self.maxDelayBetweenReconnection) &&
            (self.currentRetries >= self.attemptsBeforeDecay)) {
                self.connectionDelay = self.connectionDelay * 2;
                self.currentRetries = 0;
            }

            // Stop buffering messages after a fixed number of retries.
            // This is to keep the buffer from growing unbounded
            if (self.loggingEnabled &&
                (self.totalRetries >= (self.maximumAttempts))) {
                    self.loggingEnabled = false;
                    self.emit('error', new Error('Max entries eclipsed, disabling buffering'));
            }

            // continue
            self._erroring = false;
            connectStream();

        }, self.connectionDelay);
    }

    function onConnected() {
        // Reset our variables
        self.loggingEnabled = true;
        self.currentRetries = 0;
        self.totalRetries = 0;
        self.connectionDelay = options.connectionDelay || 1000;

        self.emit('connect', 'Connected to Papertrail at ' + self.host + ':' + self.port);

        // Did we get messages buffered
        if (self.buffer.length > 0) {
            self.stream.write(self.buffer);
            self.buffer = '';
        }
    }
};

//
//
// Inherit from `winston.Transport` so you can take advantage
// of the base functionality and `.handleExceptions()`.
//
util.inherits(Papertrail, winston.Transport);

//
// Define a getter so that `winston.transports.Papertrail`
// is available and thus backwards compatible.
//
winston.transports.Papertrail = Papertrail;

/**
 * Papertrail.log
 *
 * @description Core logging method exposed to Winston. Metadata is optional.
 *
 * @param {String}        level    Level at which to log the message.
 * @param {String}        msg        Message to log
 * @param {String|object|Function}        [meta]    Optional metadata to attach
 * @param {Function}    callback
 * @returns {*}
 */
Papertrail.prototype.log = function (level, msg, meta, callback) {

    var self = this;

    // make sure we handle when meta isn't provided
    if (typeof(meta) === 'function' && !callback) {
        callback = meta;
        meta = false;
    }

    if (meta && typeof meta === 'object' && (Object.keys(meta).length === 0)
        && (!util.isError(meta))) {
        meta = false;
    }

    // If the logging buffer is disabled, drop the message on the floor
    if (!this.loggingEnabled) {
        return callback(null, true);
    }

    var output = msg;

    // If we don't have a string for the message,
    // lets transform it before moving on
    if (typeof(output) !== 'string') {
        output = util.inspect(output, false, null, self.colorize);
    }

    if (meta) {
        if (typeof meta !== 'object') {
            output += ' ' + meta;
        }
        else if (meta) {
            if (this.inlineMeta) {
                output += ' ' + util.inspect(meta, false, null, self.colorize).replace(/[\n\t]\s*/gm, " ");
            }
            else {
                output += '\n' + util.inspect(meta, false, null, self.colorize);
            }
        }
    }

    this.sendMessage(this.hostname, this.program, level, output, callback);

};

/**
 * Papertrail.sendMessage
 *
 * @description sending the message to the stream, or buffering if not connected
 *
 * @param {String}    hostname    Hostname of the source application.
 * @param {String}    program     Name of the source application
 * @param {String}    level        Log level of the message
 * @param {String}    message        The message to deliver
 * @param {Function}  callback      callback to be executed when the log has been written to the stream
 */
Papertrail.prototype.sendMessage = function (hostname, program, level, message, callback) {

    var self = this,
        lines = [],
        msg = '',
        gap = '';

    // Only split if we actually have a message
    if (message) {
        lines = message.split('\n');
    }
    else {
        lines = [''];
    }

    // If the incoming message has multiple lines, break them and format each
    // line as it's own message
    for (var i = 0; i < lines.length; i++) {

        // don't send extra message if our message ends with a newline
        if ((lines[i].length === 0) && (i == lines.length - 1)) {
            break;
        }

        if (i == 1) {
            gap = '    ';
        }

        msg += self.producer.produce({
            severity: self.levels[level] ? self.levels[level] : level,
            host: hostname,
            appName: program,
            date: new Date(),
            message: self.logFormat(self.colorize ? winston.config.colorize(level) : level, gap + lines[i])
        }) + '\r\n';
    }

    if (this.stream && this.stream.writable) {
        this.stream.write(msg, callback);
    }
    else if (this.loggingEnabled && this.buffer.length < this.maxBufferSize) {
        this.buffer += msg;
    }
};

/**
 * Papertrail.close
 *
 * @description closes the underlying TLS connection and disables automatic
 * reconnection, allowing the process to exit
 */
Papertrail.prototype.close = function() {
    var self = this;

    self._shutdown = true;

    if (self.stream) {
        self.stream.end();
    }
    // if there's no stream yet, that means we're still connecting
    // lets wire a connect handler, and then invoke close again
    else {
        self.on('connect', function() {
            self.close();
        });
    }
};
<|endoftext|>