<commit_before>// dependencies ------------------------------------------------------------

import aws     from '../libs/aws';
import scitran from '../libs/scitran';
import mongo         from '../libs/mongo';
import {ObjectID}    from 'mongodb';

let c = mongo.collections;

// handlers ----------------------------------------------------------------

/**
 * Jobs
 *
 * Handlers for job actions.
 */
let handlers = {

    /**
     * Create Job Definition
     */
    createJobDefinition(req, res, next) {
        let jobDef = req.body;

        aws.batch.sdk.registerJobDefinition(jobDef, (err, data) => {
            if (err) {
                return next(err);
            } else {
                res.send(data);
            }
        });
    },

    /**
    * Disable Job Definition
    */
    disableJobDefinition(req, res, next) {
        let jobArn = req.body.arn;

        aws.batch.sdk.deregisterJobDefinition({jobDefinition: jobArn}, (err, data) => {
            if (err) {
                return next(err);
            } else {
                res.send(data);
            }
        });
    },

    /**
     * Describe Job Definitions
     */
    describeJobDefinitions(req, res, next) {
        aws.batch.sdk.describeJobDefinitions({}, (err, data) => {
            if (err) {
                return next(err);
            } else {
                let definitions = {};
                for (let definition of data.jobDefinitions) {
                    if (!definitions.hasOwnProperty(definition.jobDefinitionName)) {
                        definitions[definition.jobDefinitionName] = {};
                    }
                    definitions[definition.jobDefinitionName][definition.revision] = definition;
                }

                res.send(definitions);
            }
        });
    },

    /**
     * Submit Job
     * Inserts a job document into mongo and starts snapshot upload
     * returns job to client
     */
    submitJob(req, res) {
        let job = req.body;

        const batchJobParams = {
            jobDefinition: job.jobDefinition,
            jobName:       job.jobName,
            parameters:    job.parameters
        };
        batchJobParams.jobQueue = 'bids-queue';

        job.uploadSnapshotComplete = !!job.uploadSnapshotComplete;
        job.analysis = {
            status: 'PENDING'
        };
        //making consistent with agave ??
        job.appLabel = job.jobName;
        job.appVersion = job.jobDefinition.match(/\d+$/)[0];

        c.crn.jobs.insertOne(job, (err, mongoJob) => {
            scitran.downloadSymlinkDataset(job.snapshotId, (err, hash) => {
                aws.s3.uploadSnapshot(hash, () => {
                    handlers.startBatchJob(batchJobParams, mongoJob.insertedId);
                });
            }, {snapshot: true});
            res.send({jobId: mongoJob.insertedId});
        });
    },

    /**
     * Start AWS Batch Job
     * starts an aws batch job
     * returns no return. Batch job start is happening after response has been send to client
     */
    startBatchJob(params, jobId) {
        aws.batch.sdk.submitJob(params, (err, data) => {
            //update mongo job with aws batch job id?
            c.crn.jobs.updateOne({_id: jobId}, {
                $set:{
                    // jobId: data.jobId,
                    analysis:{
                        analysisId: data.jobId,
                        status: 'PENDING', //setting status to pending as soon as job submissions is successful
                        attempts: 1
                    },
                    uploadSnapshotComplete: true
                }
            }, () => {
            //error handling???
            });
        });
    },

        /**
     * GET Job
     */
    getJob(req, res) {
        let jobId = req.params.jobId; //this is the mongo id for the job.

        c.crn.jobs.findOne({_id: ObjectID(jobId)}, {}, (err, job) => {
            let status = job.analysis.status;
            let analysisId = job.analysis.analysisId;
            // check if job is already known to be completed
            // there could be a scenario where we are polling before the AWS batch job has been setup. !analysisId check handles this.
            if ((status === 'SUCCEEDED' && job.results && job.results.length > 0) || status === 'FAILED' || !analysisId) {
                res.send(job);
            } else {
                let params = {
                    jobs: [analysisId]
                };
                aws.batch.sdk.describeJobs(params, (err, resp) => {
                    let analysis = resp.jobs[0];
                    // check status
                    if(analysis.status === 'SUCCEEDED' || analysis.status === 'FAILED'){
                        let params = {
                            Bucket: 'openneuro.outputs',
                            Prefix: '24fd3a7f24ce267eb488ec5afe5c98c1'
                        };
                        aws.s3.sdk.listObjectsV2(params, (err, data) => {
                            let results = [];
                            data.Contents.forEach((obj) => {
                                let result = {};
                                result.name = obj.Key;
                                result.path = params.Bucket + '/' + obj.Key;
                                results.push(result);
                            });
                            c.crn.jobs.updateOne({_id: ObjectID(jobId)}, {
                                $set:{
                                    'analysis.status': analysis.status,
                                    results: results
                                }
                            });
                        });
                    }
                    res.send({
                        analysis: analysis,
                        jobId: jobId,
                        datasetId: job.datasetId,
                        snapshotId: job.snapshotId
                    });

                    // notifications.jobComplete(job);

                    // if (resp.body.status === 'error' && resp.body.message.indexOf('No job found with job id') > -1) {
                    //     job.agave.status = 'FAILED';
                    //     c.crn.jobs.updateOne({jobId}, {$set: {agave: job.agave}}, {}, () => {
                    //         res.send({agave: resp.body.result, snapshotId: job.snapshotId});
                    //         notifications.jobComplete(job);
                    //     });
                    // } else if (resp.body && resp.body.result && (resp.body.result.status === 'FINISHED' || resp.body.result.status === 'FAILED')) {
                    //     job.agave = resp.body.result;
                    //     agave.getOutputs(jobId, (results, logs) => {
                    //         c.crn.jobs.updateOne({jobId}, {$set: {agave: resp.body.result, results, logs}}, {}, (err) => {
                    //             if (err) {res.send(err);}
                    //             else {res.send({agave: resp.body.result, results, logs, snapshotId: job.snapshotId});}
                    //             job.agave = resp.body.result;
                    //             job.results = results;
                    //             job.logs = logs;
                    //             if (status !== 'FINISHED') {notifications.jobComplete(job);}
                    //         });
                    //     });
                    // } else if (resp.body && resp.body.result && job.agave.status !== resp.body.result.status) {
                    //     job.agave = resp.body.result;
                    //     c.crn.jobs.updateOne({jobId}, {$set: {agave: resp.body.result}}, {}, (err) => {
                    //         if (err) {res.send(err);}
                    //         else {
                    //             res.send({
                    //                 agave:      resp.body.result,
                    //                 datasetId:  job.datasetId,
                    //                 snapshotId: job.snapshotId,
                    //                 jobId:      jobId
                    //             });
                    //         }
                    //     });
                    // } else {
                    //     res.send({
                    //         agave:      resp.body.result,
                    //         datasetId:  job.datasetId,
                    //         snapshotId: job.snapshotId,
                    //         jobId:      jobId
                    //     });
                    // }
                });
            }
        });
    }

};

export default handlers;
<commit_msg>Simplify batchJobParameters slightly.<commit_after>// dependencies ------------------------------------------------------------

import aws     from '../libs/aws';
import scitran from '../libs/scitran';
import mongo         from '../libs/mongo';
import {ObjectID}    from 'mongodb';

let c = mongo.collections;

// handlers ----------------------------------------------------------------

/**
 * Jobs
 *
 * Handlers for job actions.
 */
let handlers = {

    /**
     * Create Job Definition
     */
    createJobDefinition(req, res, next) {
        let jobDef = req.body;

        aws.batch.sdk.registerJobDefinition(jobDef, (err, data) => {
            if (err) {
                return next(err);
            } else {
                res.send(data);
            }
        });
    },

    /**
    * Disable Job Definition
    */
    disableJobDefinition(req, res, next) {
        let jobArn = req.body.arn;

        aws.batch.sdk.deregisterJobDefinition({jobDefinition: jobArn}, (err, data) => {
            if (err) {
                return next(err);
            } else {
                res.send(data);
            }
        });
    },

    /**
     * Describe Job Definitions
     */
    describeJobDefinitions(req, res, next) {
        aws.batch.sdk.describeJobDefinitions({}, (err, data) => {
            if (err) {
                return next(err);
            } else {
                let definitions = {};
                for (let definition of data.jobDefinitions) {
                    if (!definitions.hasOwnProperty(definition.jobDefinitionName)) {
                        definitions[definition.jobDefinitionName] = {};
                    }
                    definitions[definition.jobDefinitionName][definition.revision] = definition;
                }

                res.send(definitions);
            }
        });
    },

    /**
     * Submit Job
     * Inserts a job document into mongo and starts snapshot upload
     * returns job to client
     */
    submitJob(req, res) {
        let job = req.body;

        const batchJobParams = {
            jobDefinition: job.jobDefinition,
            jobName:       job.jobName,
            jobQueue:      'bids-queue',
            parameters:    job.parameters
        };

        job.uploadSnapshotComplete = !!job.uploadSnapshotComplete;
        job.analysis = {
            status: 'PENDING'
        };
        //making consistent with agave ??
        job.appLabel = job.jobName;
        job.appVersion = job.jobDefinition.match(/\d+$/)[0];

        c.crn.jobs.insertOne(job, (err, mongoJob) => {
            scitran.downloadSymlinkDataset(job.snapshotId, (err, hash) => {
                aws.s3.uploadSnapshot(hash, () => {
                    handlers.startBatchJob(batchJobParams, mongoJob.insertedId);
                });
            }, {snapshot: true});
            res.send({jobId: mongoJob.insertedId});
        });
    },

    /**
     * Start AWS Batch Job
     * starts an aws batch job
     * returns no return. Batch job start is happening after response has been send to client
     */
    startBatchJob(params, jobId) {
        aws.batch.sdk.submitJob(params, (err, data) => {
            //update mongo job with aws batch job id?
            c.crn.jobs.updateOne({_id: jobId}, {
                $set:{
                    // jobId: data.jobId,
                    analysis:{
                        analysisId: data.jobId,
                        status: 'PENDING', //setting status to pending as soon as job submissions is successful
                        attempts: 1
                    },
                    uploadSnapshotComplete: true
                }
            }, () => {
            //error handling???
            });
        });
    },

        /**
     * GET Job
     */
    getJob(req, res) {
        let jobId = req.params.jobId; //this is the mongo id for the job.

        c.crn.jobs.findOne({_id: ObjectID(jobId)}, {}, (err, job) => {
            let status = job.analysis.status;
            let analysisId = job.analysis.analysisId;
            // check if job is already known to be completed
            // there could be a scenario where we are polling before the AWS batch job has been setup. !analysisId check handles this.
            if ((status === 'SUCCEEDED' && job.results && job.results.length > 0) || status === 'FAILED' || !analysisId) {
                res.send(job);
            } else {
                let params = {
                    jobs: [analysisId]
                };
                aws.batch.sdk.describeJobs(params, (err, resp) => {
                    let analysis = resp.jobs[0];
                    // check status
                    if(analysis.status === 'SUCCEEDED' || analysis.status === 'FAILED'){
                        let params = {
                            Bucket: 'openneuro.outputs',
                            Prefix: '24fd3a7f24ce267eb488ec5afe5c98c1'
                        };
                        aws.s3.sdk.listObjectsV2(params, (err, data) => {
                            let results = [];
                            data.Contents.forEach((obj) => {
                                let result = {};
                                result.name = obj.Key;
                                result.path = params.Bucket + '/' + obj.Key;
                                results.push(result);
                            });
                            c.crn.jobs.updateOne({_id: ObjectID(jobId)}, {
                                $set:{
                                    'analysis.status': analysis.status,
                                    results: results
                                }
                            });
                        });
                    }
                    res.send({
                        analysis: analysis,
                        jobId: jobId,
                        datasetId: job.datasetId,
                        snapshotId: job.snapshotId
                    });

                    // notifications.jobComplete(job);

                    // if (resp.body.status === 'error' && resp.body.message.indexOf('No job found with job id') > -1) {
                    //     job.agave.status = 'FAILED';
                    //     c.crn.jobs.updateOne({jobId}, {$set: {agave: job.agave}}, {}, () => {
                    //         res.send({agave: resp.body.result, snapshotId: job.snapshotId});
                    //         notifications.jobComplete(job);
                    //     });
                    // } else if (resp.body && resp.body.result && (resp.body.result.status === 'FINISHED' || resp.body.result.status === 'FAILED')) {
                    //     job.agave = resp.body.result;
                    //     agave.getOutputs(jobId, (results, logs) => {
                    //         c.crn.jobs.updateOne({jobId}, {$set: {agave: resp.body.result, results, logs}}, {}, (err) => {
                    //             if (err) {res.send(err);}
                    //             else {res.send({agave: resp.body.result, results, logs, snapshotId: job.snapshotId});}
                    //             job.agave = resp.body.result;
                    //             job.results = results;
                    //             job.logs = logs;
                    //             if (status !== 'FINISHED') {notifications.jobComplete(job);}
                    //         });
                    //     });
                    // } else if (resp.body && resp.body.result && job.agave.status !== resp.body.result.status) {
                    //     job.agave = resp.body.result;
                    //     c.crn.jobs.updateOne({jobId}, {$set: {agave: resp.body.result}}, {}, (err) => {
                    //         if (err) {res.send(err);}
                    //         else {
                    //             res.send({
                    //                 agave:      resp.body.result,
                    //                 datasetId:  job.datasetId,
                    //                 snapshotId: job.snapshotId,
                    //                 jobId:      jobId
                    //             });
                    //         }
                    //     });
                    // } else {
                    //     res.send({
                    //         agave:      resp.body.result,
                    //         datasetId:  job.datasetId,
                    //         snapshotId: job.snapshotId,
                    //         jobId:      jobId
                    //     });
                    // }
                });
            }
        });
    }

};

export default handlers;
<|endoftext|>


<commit_before>// dependencies ------------------------------------------------------------

import aws     from '../libs/aws';
import scitran from '../libs/scitran';
import crypto  from 'crypto';
import uuid    from 'uuid';
import mongo         from '../libs/mongo';
import {ObjectID}    from 'mongodb';

let c = mongo.collections;

// handlers ----------------------------------------------------------------

/**
 * Jobs
 *
 * Handlers for job actions.
 */
let handlers = {

    /**
     * Create Job Definition
     */
    createJobDefinition(req, res, next) {
        let jobDef = req.body;

        aws.batch.registerJobDefinition(jobDef, (err, data) => {
            if (err) {
                return next(err);
            } else {
                res.send(data);
            }
        });
    },

    /**
    * Disable Job Definition
    */
    disableJobDefinition(req, res, next) {
        let jobArn = req.body.arn;

        aws.batch.sdk.deregisterJobDefinition({jobDefinition: jobArn}, (err, data) => {
            if (err) {
                return next(err);
            } else {
                res.send(data);
            }
        });
    },

    /**
     * Describe Job Definitions
     */
    describeJobDefinitions(req, res, next) {
        aws.batch.sdk.describeJobDefinitions({}, (err, data) => {
            if (err) {
                return next(err);
            } else {
                let definitions = {};
                for (let definition of data.jobDefinitions) {
                    if (!definitions.hasOwnProperty(definition.jobDefinitionName)) {
                        definitions[definition.jobDefinitionName] = {};
                    }
                    definitions[definition.jobDefinitionName][definition.revision] = definition;
                }

                res.send(definitions);
            }
        });
    },

    /**
     * Submit Job
     * Inserts a job document into mongo and starts snapshot upload
     * returns job to client
     */
    submitJob(req, res, next) {
        let job = req.body;

        job.uploadSnapshotComplete = !!job.uploadSnapshotComplete;
        job.analysis = {
            analysisId: uuid.v4(),
            status: 'UPLOADING',
            created: new Date(),
            attempts: 0
        };
        //making consistent with agave ??
        job.appLabel = job.jobName;
        job.appVersion = job.jobDefinition.match(/\d+$/)[0];

        scitran.downloadSymlinkDataset(job.snapshotId, (err, hash) => {
            job.datasetHash = hash;
            job.parametersHash = crypto.createHash('md5').update(JSON.stringify(job.parameters)).digest('hex');

            // jobDefintion is the full ARN including version, region, etc
            c.crn.jobs.findOne({
                jobDefinition:  job.jobDefinition,
                datasetHash:    job.datasetHash,
                parametersHash: job.parametersHash,
                snapshotId:     job.snapshotId
            }, {}, (err, existingJob) => {
                if (err){return next(err);}
                if (existingJob) {
                    // allow retrying failed jobs
                    if (existingJob.analysis && existingJob.analysis.status === 'FAILED') {
                        handlers.retry({params: {jobId: existingJob.jobId}}, res, next);
                        return;
                    }
                    let error = new Error('A job with the same dataset and parameters has already been run.');
                    error.http_code = 409;
                    res.status(409).send({message: 'A job with the same dataset and parameters has already been run.'});
                    return;
                }

                c.crn.jobs.insertOne(job, (err, mongoJob) => {

                    // Finish the client request so S3 upload can happen async
                    res.send({jobId: mongoJob.insertedId});

                    // TODO - handle situation where upload to S3 fails
                    aws.s3.uploadSnapshot(hash, () => {
                        const batchJobParams = {
                            jobDefinition: job.jobDefinition,
                            jobName:       job.jobName,
                            jobQueue:      'bids-queue',
                            parameters:    job.parameters,
                            containerOverrides:{
                                environment: [{
                                    name: 'BIDS_SNAPSHOT_ID',
                                    value: hash
                                }, {
                                    name: 'BIDS_ANALYSIS_ID',
                                    value: job.analysis.analysisId
                                }]
                            }
                        };

                        aws.batch.startBatchJob(batchJobParams, mongoJob.insertedId);
                    });
                });
            });
        }, {snapshot: true});
    },

    /**
     * GET Job
     */
    getJob(req, res) {
        let jobId = req.params.jobId; //this is the mongo id for the job.

        c.crn.jobs.findOne({_id: ObjectID(jobId)}, {}, (err, job) => {
            if (!job) {
                res.status(404).send({message: 'Job not found.'});
                return;
            }
            let status = job.analysis.status;
            let analysisId = job.analysis.analysisId;
            let jobs = job.analysis.jobs;
            let createdDate = job.analysis.created;
            //let totalJobs = jobs.length; //total number of jobs in the analysis
            // check if job is already known to be completed
            // there could be a scenario where we are polling before the AWS batch job has been setup. !jobs check handles this.
            if ((status === 'SUCCEEDED' && job.results && job.results.length > 0) || status === 'FAILED' || !jobs) {
                res.send(job);
            } else {
                let params = {
                    jobs: jobs
                };
                aws.batch.sdk.describeJobs(params, (err, resp) => {
                    let analysis = {};
                    let statusArray = resp.jobs.map((job) => {
                        return job.status;
                    });
                    //if every status is either succeeded or failed, all jobs have completed.
                    let finished = statusArray.every((status) => {
                        return status === 'SUCCEEDED' || status === 'FAILED';
                    });

                    analysis.status = !finished ? 'RUNNING' : 'COMPLETING';
                    analysis.created = createdDate;
                    // check status
                    if(finished){
                        //Check if any jobs failed, if so analysis failed, else succeeded
                        let finalStatus = statusArray.some((status)=>{ return status === 'FAILED';}) ? 'FAILED' : 'SUCCEEDED';
                        let params = {
                            Bucket: 'openneuro.outputs',
                            Prefix: job.datasetHash + '/' + job.analysis.analysisId
                        };
                        aws.s3.sdk.listObjectsV2(params, (err, data) => {
                            let results = [];
                            data.Contents.forEach((obj) => {
                                let result = {};
                                result.name = obj.Key;
                                result.path = params.Bucket + '/' + obj.Key;
                                results.push(result);
                            });
                            c.crn.jobs.updateOne({_id: ObjectID(jobId)}, {
                                $set:{
                                    'analysis.status': finalStatus,
                                    results: results
                                }
                            });
                        });
                    }
                    res.send({
                        analysis: analysis,
                        jobId: analysisId,
                        datasetId: job.datasetId,
                        snapshotId: job.snapshotId
                    });

                    // notifications.jobComplete(job);

                    // if (resp.body.status === 'error' && resp.body.message.indexOf('No job found with job id') > -1) {
                    //     job.agave.status = 'FAILED';
                    //     c.crn.jobs.updateOne({jobId}, {$set: {agave: job.agave}}, {}, () => {
                    //         res.send({agave: resp.body.result, snapshotId: job.snapshotId});
                    //         notifications.jobComplete(job);
                    //     });
                    // } else if (resp.body && resp.body.result && (resp.body.result.status === 'FINISHED' || resp.body.result.status === 'FAILED')) {
                    //     job.agave = resp.body.result;
                    //     agave.getOutputs(jobId, (results, logs) => {
                    //         c.crn.jobs.updateOne({jobId}, {$set: {agave: resp.body.result, results, logs}}, {}, (err) => {
                    //             if (err) {res.send(err);}
                    //             else {res.send({agave: resp.body.result, results, logs, snapshotId: job.snapshotId});}
                    //             job.agave = resp.body.result;
                    //             job.results = results;
                    //             job.logs = logs;
                    //             if (status !== 'FINISHED') {notifications.jobComplete(job);}
                    //         });
                    //     });
                    // } else if (resp.body && resp.body.result && job.agave.status !== resp.body.result.status) {
                    //     job.agave = resp.body.result;
                    //     c.crn.jobs.updateOne({jobId}, {$set: {agave: resp.body.result}}, {}, (err) => {
                    //         if (err) {res.send(err);}
                    //         else {
                    //             res.send({
                    //                 agave:      resp.body.result,
                    //                 datasetId:  job.datasetId,
                    //                 snapshotId: job.snapshotId,
                    //                 jobId:      jobId
                    //             });
                    //         }
                    //     });
                    // } else {
                    //     res.send({
                    //         agave:      resp.body.result,
                    //         datasetId:  job.datasetId,
                    //         snapshotId: job.snapshotId,
                    //         jobId:      jobId
                    //     });
                    // }
                });
            }
        });
    },

    /**
     * Retry a job using existing parameters
     */
    retry (req, res, next) {
        // let jobId = req.params.jobId;
        // TODO - This is a stub for testing - need to resubmit using the same CRN job data but a new AWS Batch job
        let error = new Error('Retry is not yet supported.');
        error.http_code = 409;
        return next(error);
    }

};

export default handlers;
<commit_msg>update wording from COMPLETING to FINALIZING<commit_after>// dependencies ------------------------------------------------------------

import aws     from '../libs/aws';
import scitran from '../libs/scitran';
import crypto  from 'crypto';
import uuid    from 'uuid';
import mongo         from '../libs/mongo';
import {ObjectID}    from 'mongodb';

let c = mongo.collections;

// handlers ----------------------------------------------------------------

/**
 * Jobs
 *
 * Handlers for job actions.
 */
let handlers = {

    /**
     * Create Job Definition
     */
    createJobDefinition(req, res, next) {
        let jobDef = req.body;

        aws.batch.registerJobDefinition(jobDef, (err, data) => {
            if (err) {
                return next(err);
            } else {
                res.send(data);
            }
        });
    },

    /**
    * Disable Job Definition
    */
    disableJobDefinition(req, res, next) {
        let jobArn = req.body.arn;

        aws.batch.sdk.deregisterJobDefinition({jobDefinition: jobArn}, (err, data) => {
            if (err) {
                return next(err);
            } else {
                res.send(data);
            }
        });
    },

    /**
     * Describe Job Definitions
     */
    describeJobDefinitions(req, res, next) {
        aws.batch.sdk.describeJobDefinitions({}, (err, data) => {
            if (err) {
                return next(err);
            } else {
                let definitions = {};
                for (let definition of data.jobDefinitions) {
                    if (!definitions.hasOwnProperty(definition.jobDefinitionName)) {
                        definitions[definition.jobDefinitionName] = {};
                    }
                    definitions[definition.jobDefinitionName][definition.revision] = definition;
                }

                res.send(definitions);
            }
        });
    },

    /**
     * Submit Job
     * Inserts a job document into mongo and starts snapshot upload
     * returns job to client
     */
    submitJob(req, res, next) {
        let job = req.body;

        job.uploadSnapshotComplete = !!job.uploadSnapshotComplete;
        job.analysis = {
            analysisId: uuid.v4(),
            status: 'UPLOADING',
            created: new Date(),
            attempts: 0
        };
        //making consistent with agave ??
        job.appLabel = job.jobName;
        job.appVersion = job.jobDefinition.match(/\d+$/)[0];

        scitran.downloadSymlinkDataset(job.snapshotId, (err, hash) => {
            job.datasetHash = hash;
            job.parametersHash = crypto.createHash('md5').update(JSON.stringify(job.parameters)).digest('hex');

            // jobDefintion is the full ARN including version, region, etc
            c.crn.jobs.findOne({
                jobDefinition:  job.jobDefinition,
                datasetHash:    job.datasetHash,
                parametersHash: job.parametersHash,
                snapshotId:     job.snapshotId
            }, {}, (err, existingJob) => {
                if (err){return next(err);}
                if (existingJob) {
                    // allow retrying failed jobs
                    if (existingJob.analysis && existingJob.analysis.status === 'FAILED') {
                        handlers.retry({params: {jobId: existingJob.jobId}}, res, next);
                        return;
                    }
                    let error = new Error('A job with the same dataset and parameters has already been run.');
                    error.http_code = 409;
                    res.status(409).send({message: 'A job with the same dataset and parameters has already been run.'});
                    return;
                }

                c.crn.jobs.insertOne(job, (err, mongoJob) => {

                    // Finish the client request so S3 upload can happen async
                    res.send({jobId: mongoJob.insertedId});

                    // TODO - handle situation where upload to S3 fails
                    aws.s3.uploadSnapshot(hash, () => {
                        const batchJobParams = {
                            jobDefinition: job.jobDefinition,
                            jobName:       job.jobName,
                            jobQueue:      'bids-queue',
                            parameters:    job.parameters,
                            containerOverrides:{
                                environment: [{
                                    name: 'BIDS_SNAPSHOT_ID',
                                    value: hash
                                }, {
                                    name: 'BIDS_ANALYSIS_ID',
                                    value: job.analysis.analysisId
                                }]
                            }
                        };

                        aws.batch.startBatchJob(batchJobParams, mongoJob.insertedId);
                    });
                });
            });
        }, {snapshot: true});
    },

    /**
     * GET Job
     */
    getJob(req, res) {
        let jobId = req.params.jobId; //this is the mongo id for the job.

        c.crn.jobs.findOne({_id: ObjectID(jobId)}, {}, (err, job) => {
            if (!job) {
                res.status(404).send({message: 'Job not found.'});
                return;
            }
            let status = job.analysis.status;
            let analysisId = job.analysis.analysisId;
            let jobs = job.analysis.jobs;
            let createdDate = job.analysis.created;
            //let totalJobs = jobs.length; //total number of jobs in the analysis
            // check if job is already known to be completed
            // there could be a scenario where we are polling before the AWS batch job has been setup. !jobs check handles this.
            if ((status === 'SUCCEEDED' && job.results && job.results.length > 0) || status === 'FAILED' || !jobs) {
                res.send(job);
            } else {
                let params = {
                    jobs: jobs
                };
                aws.batch.sdk.describeJobs(params, (err, resp) => {
                    let analysis = {};
                    let statusArray = resp.jobs.map((job) => {
                        return job.status;
                    });
                    //if every status is either succeeded or failed, all jobs have completed.
                    let finished = statusArray.every((status) => {
                        return status === 'SUCCEEDED' || status === 'FAILED';
                    });

                    analysis.status = !finished ? 'RUNNING' : 'FINALIZING';
                    analysis.created = createdDate;
                    // check status
                    if(finished){
                        //Check if any jobs failed, if so analysis failed, else succeeded
                        let finalStatus = statusArray.some((status)=>{ return status === 'FAILED';}) ? 'FAILED' : 'SUCCEEDED';
                        let params = {
                            Bucket: 'openneuro.outputs',
                            Prefix: job.datasetHash + '/' + job.analysis.analysisId
                        };
                        aws.s3.sdk.listObjectsV2(params, (err, data) => {
                            let results = [];
                            data.Contents.forEach((obj) => {
                                let result = {};
                                result.name = obj.Key;
                                result.path = params.Bucket + '/' + obj.Key;
                                results.push(result);
                            });
                            c.crn.jobs.updateOne({_id: ObjectID(jobId)}, {
                                $set:{
                                    'analysis.status': finalStatus,
                                    results: results
                                }
                            });
                        });
                    }
                    res.send({
                        analysis: analysis,
                        jobId: analysisId,
                        datasetId: job.datasetId,
                        snapshotId: job.snapshotId
                    });

                    // notifications.jobComplete(job);

                    // if (resp.body.status === 'error' && resp.body.message.indexOf('No job found with job id') > -1) {
                    //     job.agave.status = 'FAILED';
                    //     c.crn.jobs.updateOne({jobId}, {$set: {agave: job.agave}}, {}, () => {
                    //         res.send({agave: resp.body.result, snapshotId: job.snapshotId});
                    //         notifications.jobComplete(job);
                    //     });
                    // } else if (resp.body && resp.body.result && (resp.body.result.status === 'FINISHED' || resp.body.result.status === 'FAILED')) {
                    //     job.agave = resp.body.result;
                    //     agave.getOutputs(jobId, (results, logs) => {
                    //         c.crn.jobs.updateOne({jobId}, {$set: {agave: resp.body.result, results, logs}}, {}, (err) => {
                    //             if (err) {res.send(err);}
                    //             else {res.send({agave: resp.body.result, results, logs, snapshotId: job.snapshotId});}
                    //             job.agave = resp.body.result;
                    //             job.results = results;
                    //             job.logs = logs;
                    //             if (status !== 'FINISHED') {notifications.jobComplete(job);}
                    //         });
                    //     });
                    // } else if (resp.body && resp.body.result && job.agave.status !== resp.body.result.status) {
                    //     job.agave = resp.body.result;
                    //     c.crn.jobs.updateOne({jobId}, {$set: {agave: resp.body.result}}, {}, (err) => {
                    //         if (err) {res.send(err);}
                    //         else {
                    //             res.send({
                    //                 agave:      resp.body.result,
                    //                 datasetId:  job.datasetId,
                    //                 snapshotId: job.snapshotId,
                    //                 jobId:      jobId
                    //             });
                    //         }
                    //     });
                    // } else {
                    //     res.send({
                    //         agave:      resp.body.result,
                    //         datasetId:  job.datasetId,
                    //         snapshotId: job.snapshotId,
                    //         jobId:      jobId
                    //     });
                    // }
                });
            }
        });
    },

    /**
     * Retry a job using existing parameters
     */
    retry (req, res, next) {
        // let jobId = req.params.jobId;
        // TODO - This is a stub for testing - need to resubmit using the same CRN job data but a new AWS Batch job
        let error = new Error('Retry is not yet supported.');
        error.http_code = 409;
        return next(error);
    }

};

export default handlers;
<|endoftext|>


<commit_before>const fs = require("fs");
const path = require("path");
const defer = require("promise-defer");
const moment = require("moment-timezone");
let settings = require("../settings.js");
let bot = require("../bot.js");
let minimumPermissions = settings.secrets.minimumPermissions;

function getSettings() {
  return require("../settings.js");
}

function getLogChannel() {
  return bot.client.channels.cache.get(getSettings().secrets.log_discord_channel);
}

function formatLogMessage(message) {
  return `[${new Date().toUTCString()}] ${message}`;
}

function log(...logItems) {
  const logMessage = logItems.join(" ");
  const logString = formatLogMessage(logMessage);
  const tripleGrave = "```";
  const logChannel = getLogChannel();
  if (logChannel) {
    logChannel.send(tripleGrave + logString + tripleGrave);
    if (logString.includes("Bot is logged in.") || logString.includes("error running main message handler")) {
      logChannel.send("<@" + settings.secrets.super_admin + ">");
    }
  } else {
    console.log("no log channel found");
  }
  console.log(logString);
}

function logError() {
  log("[ERROR]", Array.from(arguments).slice(1).join(" "));
}

function readFile(path) {
  try {
    return JSON.parse(fs.readFileSync(path, "utf8"));
  } catch (err) {
    log("error reading file " + err);
    // return valid JSON to trigger update
    return {};
  }
}

function readFileSettingsDefault(filePath, defaultValue) {
  try {
    const fileData = fs.readFileSync(filePath, "utf8");
    return JSON.parse(fileData);
  } catch (err) {
    if (err.code !== "ENOENT") {
      throw err;
    }

    fs.writeFileSync(filePath, defaultValue, {
      encoding: "utf8",
      flag: "wx"
    });
    return JSON.parse(defaultValue);
  }
}

function fullname(user) {
  return `${user.username}#${user.discriminator}`;
}

const guildDatabasePath = path.join(__dirname, "..", "stores", "guilddatabase.json");
let guildDatabase;

function getGuildDatabase() {
  guildDatabase = guildDatabase || readFile(guildDatabasePath);
  return guildDatabase;
}

function writeGuildDatabase() {
  const formattedJson = JSON.stringify(guildDatabase, "", "\t");
  fs.writeFile(guildDatabasePath, formattedJson, (err) => {
    if (!err) {
      return;
    }
    return logError("writing the guild database", err);
  });
}

function amendGuildDatabase(partialGuildDb) {
  Object.assign(guildDatabase, partialGuildDb);
  writeGuildDatabase();
}

function removeGuildFromDatabase(guildId) {
  delete guildDatabase[guildId];
  writeGuildDatabase();
}

function deleteFolderRecursive(path) {
  if (fs.existsSync(path)) {
    fs.readdirSync(path).forEach(function(file, index) {
      var curPath = path + "/" + file;
      if (fs.lstatSync(curPath).isDirectory()) {
        deleteFolderRecursive(curPath);
      } else {
        fs.unlinkSync(curPath);
      }
    });
    fs.rmdirSync(path);
  }
}

function writeGuildSpecific(guildid, json, file) {
  let fullPath = path.join(__dirname, "..", "stores", guildid, file + ".json");
  fs.writeFile(fullPath, JSON.stringify(json, "", "\t"), (err) => {
    if (err) {
      return log("error writing guild specific database: " + err);
    }
  });
}

const userStorePath = path.join(__dirname, "..", "stores", "users.json");
const users = readFileSettingsDefault(userStorePath, "{}");

const userDefaults = {};

//uses cached version of user database
function amendUserSettings(userId, partialSettings) {
  users[userId] = Object.assign({}, users[userId], partialSettings);

  const formattedJson = JSON.stringify(users, "", "\t");
  fs.writeFile(userStorePath, formattedJson, (err) => {
    if (!err) {
      return;
    }
    return logError("writing the users database", err);
  });
}

function getUserSetting(userId, settingName) {
  const apparentSettings = Object.assing({}, userDefaults, users[userId]);
  return apparentSettings[settingName];
}

function mentioned(msg, x) {
  if (!Array.isArray(x)) {
    x = [x];
  }
  return msg.mentions.has(bot.client.user.id) && x.some((c) => msg.content.toLowerCase().includes(c));
}

function firstUpper(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
}

// timezone validation
const validateTz = (timezone) => { return moment.tz.zone(timezone); };

// parse timezone and adjust time
function addTz(time, timezone) {
  if (validateTz(timezone)) { // passes moment timezone test
    return moment(time).tz(timezone);
  } else { // does not pass moment timezone test (old timezone)
    return moment(time).utcOffset(timezone);
  }
}

// returns date object adjusted for tz
function convertDate(dateToConvert, guildid) {
  let guildSettingsPath = path.join(__dirname, "..", "stores", guildid, "settings.json");
  let guildSettings = readFile(guildSettingsPath);
  return addTz(dateToConvert, guildSettings.timezone).toDate();
}

function stringDate(date, guildid, hour) {
  let guildSettingsPath = path.join(__dirname, "..", "stores", guildid, "settings.json");
  let guildSettings = readFile(guildSettingsPath);
  return addTz(date, guildSettings.timezone).toISOString(true);
}

function getStringTime(date, format) {
  // m.format(hA:mm) - 9:05AM
  // m.format(HH:mm) - 09:05
  const m = moment(date);
  if (m.minutes() === 0) { // if on the hour
    return ((format === 24) ? m.format("HH") : m.format("hA"));
  } else { // if not on the hour
    return ((format === 24) ? m.format("HH:mm") : m.format("h:mmA"));
  }
}

function sendMessageHandler(message, err) {
  if (err.message === "Missing Permissions") {
    return message.author.send("Oh no! I don't have the right permissions in the channel you're trying to use me in! Toggle on all of the 'text permissions' for the **Niles** role");
  } else {
    return log(err);
  }
}

function checkRole(message) {
  let guildSettingsPath = path.join(__dirname, "..", "stores", message.guild.id, "settings.json");
  let guildSettings = readFile(guildSettingsPath);
  let userRoles = message.member.roles.cache.map((role) => role.name);
  if (guildSettings.allowedRoles.length === 0) {
    return true;
  }
  if (guildSettings.allowedRoles.length > 0) {
    if (userRoles.includes(guildSettings.allowedRoles[0])) {
      return true;
    } else {
      return false;
    }
  }
}

function checkPermissions(message) {
  let botPermissions = message.channel.permissionsFor(bot.client.user).serialize(true);
  let missingPermissions = "";
  minimumPermissions.forEach(function(permission) {
    if (!botPermissions[permission]) {
      missingPermissions += "\n" + String(permission);
    }
  });
  if (missingPermissions !== "") {
    return false;
  }
  return true;
}

function checkPermissionsManual(message, cmd) {
  let botPermissions = message.channel.permissionsFor(bot.client.user).serialize(true);
  let missingPermissions = "";
  minimumPermissions.forEach(function(permission) {
    if (!botPermissions[permission]) {
      missingPermissions += "\n" + String(permission);
    }
  });
  if (missingPermissions !== "") {
    return message.author.send(`Hey I noticed you tried to use the command \`\`${cmd}\`\`. I am missing the following permissions in channel **${message.channel.name}**: \`\`\`` + missingPermissions + "```" + "\nIf you want to stop getting these DMs type `!permissions 0` in this DM chat.");
  }
  return message.author.send(`I have all the permissions I need in channel **${message.channel.name}**`);
}

function yesThenCollector(message) {
  let p = defer();
  const collector = message.channel.createMessageCollector((m) => message.author.id === m.author.id, {
    time: 30000
  });
  collector.on("collect", (m) => {
    if (["y", "yes"].includes(m.content.toLowerCase())) {
      p.resolve();
    } else {
      message.channel.send("Okay, I won't do that");
      p.reject();
    }
    collector.stop();
  });
  collector.on("end", (collected, reason) => {
    if (reason === "time") {
      return message.channel.send("Command response timeout");
    }
  });
  return p.promise;
}

module.exports = {
  fullname,
  deleteFolderRecursive,
  getGuildDatabase,
  removeGuildFromDatabase,
  writeGuildDatabase,
  amendGuildDatabase,
  writeGuildSpecific,
  amendUserSettings,
  getUserSetting,
  mentioned,
  firstUpper,
  validateTz,
  log,
  logError,
  readFile,
  getStringTime,
  stringDate,
  convertDate,
  sendMessageHandler,
  checkPermissions,
  checkPermissionsManual,
  checkRole,
  yesThenCollector
};
<commit_msg>use parseZone instead of parsing locally<commit_after>const fs = require("fs");
const path = require("path");
const defer = require("promise-defer");
const moment = require("moment-timezone");
let settings = require("../settings.js");
let bot = require("../bot.js");
let minimumPermissions = settings.secrets.minimumPermissions;

function getSettings() {
  return require("../settings.js");
}

function getLogChannel() {
  return bot.client.channels.cache.get(getSettings().secrets.log_discord_channel);
}

function formatLogMessage(message) {
  return `[${new Date().toUTCString()}] ${message}`;
}

function log(...logItems) {
  const logMessage = logItems.join(" ");
  const logString = formatLogMessage(logMessage);
  const tripleGrave = "```";
  const logChannel = getLogChannel();
  if (logChannel) {
    logChannel.send(tripleGrave + logString + tripleGrave);
    if (logString.includes("Bot is logged in.") || logString.includes("error running main message handler")) {
      logChannel.send("<@" + settings.secrets.super_admin + ">");
    }
  } else {
    console.log("no log channel found");
  }
  console.log(logString);
}

function logError() {
  log("[ERROR]", Array.from(arguments).slice(1).join(" "));
}

function readFile(path) {
  try {
    return JSON.parse(fs.readFileSync(path, "utf8"));
  } catch (err) {
    log("error reading file " + err);
    // return valid JSON to trigger update
    return {};
  }
}

function readFileSettingsDefault(filePath, defaultValue) {
  try {
    const fileData = fs.readFileSync(filePath, "utf8");
    return JSON.parse(fileData);
  } catch (err) {
    if (err.code !== "ENOENT") {
      throw err;
    }

    fs.writeFileSync(filePath, defaultValue, {
      encoding: "utf8",
      flag: "wx"
    });
    return JSON.parse(defaultValue);
  }
}

function fullname(user) {
  return `${user.username}#${user.discriminator}`;
}

const guildDatabasePath = path.join(__dirname, "..", "stores", "guilddatabase.json");
let guildDatabase;

function getGuildDatabase() {
  guildDatabase = guildDatabase || readFile(guildDatabasePath);
  return guildDatabase;
}

function writeGuildDatabase() {
  const formattedJson = JSON.stringify(guildDatabase, "", "\t");
  fs.writeFile(guildDatabasePath, formattedJson, (err) => {
    if (!err) {
      return;
    }
    return logError("writing the guild database", err);
  });
}

function amendGuildDatabase(partialGuildDb) {
  Object.assign(guildDatabase, partialGuildDb);
  writeGuildDatabase();
}

function removeGuildFromDatabase(guildId) {
  delete guildDatabase[guildId];
  writeGuildDatabase();
}

function deleteFolderRecursive(path) {
  if (fs.existsSync(path)) {
    fs.readdirSync(path).forEach(function(file, index) {
      var curPath = path + "/" + file;
      if (fs.lstatSync(curPath).isDirectory()) {
        deleteFolderRecursive(curPath);
      } else {
        fs.unlinkSync(curPath);
      }
    });
    fs.rmdirSync(path);
  }
}

function writeGuildSpecific(guildid, json, file) {
  let fullPath = path.join(__dirname, "..", "stores", guildid, file + ".json");
  fs.writeFile(fullPath, JSON.stringify(json, "", "\t"), (err) => {
    if (err) {
      return log("error writing guild specific database: " + err);
    }
  });
}

const userStorePath = path.join(__dirname, "..", "stores", "users.json");
const users = readFileSettingsDefault(userStorePath, "{}");

const userDefaults = {};

//uses cached version of user database
function amendUserSettings(userId, partialSettings) {
  users[userId] = Object.assign({}, users[userId], partialSettings);

  const formattedJson = JSON.stringify(users, "", "\t");
  fs.writeFile(userStorePath, formattedJson, (err) => {
    if (!err) {
      return;
    }
    return logError("writing the users database", err);
  });
}

function getUserSetting(userId, settingName) {
  const apparentSettings = Object.assing({}, userDefaults, users[userId]);
  return apparentSettings[settingName];
}

function mentioned(msg, x) {
  if (!Array.isArray(x)) {
    x = [x];
  }
  return msg.mentions.has(bot.client.user.id) && x.some((c) => msg.content.toLowerCase().includes(c));
}

function firstUpper(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
}

// timezone validation
const validateTz = (timezone) => { return moment.tz.zone(timezone); };

// parse timezone and adjust time
function addTz(time, timezone) {
  if (validateTz(timezone)) { // passes moment timezone test
    return moment.parseZone(time).tz(timezone);
  } else { // does not pass moment timezone test (old timezone)
    return moment.parseZone(time).utcOffset(timezone);
  }
}

// returns date object adjusted for tz
function convertDate(dateToConvert, guildid) {
  let guildSettingsPath = path.join(__dirname, "..", "stores", guildid, "settings.json");
  let guildSettings = readFile(guildSettingsPath);
  return addTz(dateToConvert, guildSettings.timezone).toDate();
}

function stringDate(date, guildid, hour) {
  let guildSettingsPath = path.join(__dirname, "..", "stores", guildid, "settings.json");
  let guildSettings = readFile(guildSettingsPath);
  return addTz(date, guildSettings.timezone).toISOString(true);
}

function getStringTime(date, format) {
  // m.format(hA:mm) - 9:05AM
  // m.format(HH:mm) - 09:05
  const m = moment.parseZone(date);
  if (m.minutes() === 0) { // if on the hour
    return ((format === 24) ? m.format("HH") : m.format("hA"));
  } else { // if not on the hour
    return ((format === 24) ? m.format("HH:mm") : m.format("h:mmA"));
  }
}

function sendMessageHandler(message, err) {
  if (err.message === "Missing Permissions") {
    return message.author.send("Oh no! I don't have the right permissions in the channel you're trying to use me in! Toggle on all of the 'text permissions' for the **Niles** role");
  } else {
    return log(err);
  }
}

function checkRole(message) {
  let guildSettingsPath = path.join(__dirname, "..", "stores", message.guild.id, "settings.json");
  let guildSettings = readFile(guildSettingsPath);
  let userRoles = message.member.roles.cache.map((role) => role.name);
  if (guildSettings.allowedRoles.length === 0) {
    return true;
  }
  if (guildSettings.allowedRoles.length > 0) {
    if (userRoles.includes(guildSettings.allowedRoles[0])) {
      return true;
    } else {
      return false;
    }
  }
}

function checkPermissions(message) {
  let botPermissions = message.channel.permissionsFor(bot.client.user).serialize(true);
  let missingPermissions = "";
  minimumPermissions.forEach(function(permission) {
    if (!botPermissions[permission]) {
      missingPermissions += "\n" + String(permission);
    }
  });
  if (missingPermissions !== "") {
    return false;
  }
  return true;
}

function checkPermissionsManual(message, cmd) {
  let botPermissions = message.channel.permissionsFor(bot.client.user).serialize(true);
  let missingPermissions = "";
  minimumPermissions.forEach(function(permission) {
    if (!botPermissions[permission]) {
      missingPermissions += "\n" + String(permission);
    }
  });
  if (missingPermissions !== "") {
    return message.author.send(`Hey I noticed you tried to use the command \`\`${cmd}\`\`. I am missing the following permissions in channel **${message.channel.name}**: \`\`\`` + missingPermissions + "```" + "\nIf you want to stop getting these DMs type `!permissions 0` in this DM chat.");
  }
  return message.author.send(`I have all the permissions I need in channel **${message.channel.name}**`);
}

function yesThenCollector(message) {
  let p = defer();
  const collector = message.channel.createMessageCollector((m) => message.author.id === m.author.id, {
    time: 30000
  });
  collector.on("collect", (m) => {
    if (["y", "yes"].includes(m.content.toLowerCase())) {
      p.resolve();
    } else {
      message.channel.send("Okay, I won't do that");
      p.reject();
    }
    collector.stop();
  });
  collector.on("end", (collected, reason) => {
    if (reason === "time") {
      return message.channel.send("Command response timeout");
    }
  });
  return p.promise;
}

module.exports = {
  fullname,
  deleteFolderRecursive,
  getGuildDatabase,
  removeGuildFromDatabase,
  writeGuildDatabase,
  amendGuildDatabase,
  writeGuildSpecific,
  amendUserSettings,
  getUserSetting,
  mentioned,
  firstUpper,
  validateTz,
  log,
  logError,
  readFile,
  getStringTime,
  stringDate,
  convertDate,
  sendMessageHandler,
  checkPermissions,
  checkPermissionsManual,
  checkRole,
  yesThenCollector
};
<|endoftext|>


<commit_before>var scroll = (function(){
    var v = [],
        w = "wrapper",s = "scrollBar",S = "scrollBarContainer",a = "",m = "",
        // properties
        oT = "offsetTop",pE = "parentElement",pes= "previousElementSibling",
        iH = "innerHTML",cT = "currentTarget",sK = "scroll-k",U = "%",d = ".",
        // IE8 properties
        // (Dev note: remove below variables from all over the code to exclude IE8 compatibility)
        pN = "parentNode",pS = "previousSibling",sE = "srcElement",
        // Initial function
        useSlimScroll = function(C, p){
            if(C.offsetHeight < C.scrollHeight){
                var h = C[iH],k = v.length, i = v[k] = {}, q = i.E = {};
                // setting user defined classes
                p = p || {};
                q.w = p.wrapperClass || "";
                q.s = p.scrollBarClass || "";
                q.S = p.scrollBarContainerClass || "";
                q.a = p.scrollBarContainerSpecialClass ? " " + p.scrollBarContainerSpecialClass : "";
                q.mH = p.scrollBarMinHeight || 25;
                q.sH = p.scrollBarFixedHeight;  // could be undefined

                C[iH] = "";
                i[w] = cE(q.w, h, C);
                i[S] = cE(q.S + q.a, "", C);
                i[s] = cE(q.s, "", i[S]);
                setValues(k);
                //store the key 'k' in the container
                i[w].setAttribute(sK, k);

                // Attaching mouse events
                addEvent('mousedown', i[s], beginScroll);
                addEvent('click', i[S], setScroll);

                // For scroll
                addEvent('scroll', i[w], doScroll);
            }
        },
        setValues = function(k){
            if(typeof k === "number"){
                assignValues(k);
            }
            else{
                for(var j=0;j<v.length;j++){
                    assignValues(j);
                }
            }
        },
        assignValues = function(k){
            var i = v[k], q = i.E;
            i.wH = (i[w][pE] || i[w][pN]).offsetHeight;
            i.sH = i[w].scrollHeight;
            i.sP = (i.wH/i.sH) * 100;
            // i.sbh is scroll bar height in pixels without pixel unit.
            i.sbh = i.sP * i.wH/100;
            // Manually set the height of the scrollbar (in percentage)
            // if user hasn't provided the fixed scroll height value
            if(!q.sH) i.sP1 = i.sbh < q.mH? (q.mH/i.wH * 100): i.sP;
            else i.sP1 = q.sH/i.wH * 100;

            i.rP1 = 100 - i.sP1;
            i.x = (i.sH - i.wH) * ((i.sP1 - i.sP)/(100 - i.sP));
            i.sH1 = Math.abs((i.x / (i.rP1)) + (i.sH/100));
            i[s].style.height = i.sP1 + U;

            i.reposition = i[s][oT]
        },
        // Start of private functions
        getAttr = function(e, p){
            if(!e) return;
            return e.getAttribute(p);
        },
        addClass = function(e, c){
            if(c.length) e.className = c;
        },
        cE = function(c, h, p){
            var d = document.createElement('div');
            addClass(d, c);
            d[iH] = h;
            p.appendChild(d);
            return d;
        },
        setScroll = function(e){
            var e = e || event,el = e.target || event[sE],
                p = el[pE] || el[pN],
                k = getAttr(el[pS] || el[pes], sK);

            if(!k) return;
            var i = v[k],q = i.E;

            if(!i || p === i[S]) return;
            var eY = e.pageY || event.clientY,
                top = ((eY - (i[w][pE] || i[w][pN])[oT])/i.wH * 100) - i.sP1/2;
            if(top > i.rP1) top = i.rP1;
            else if(top < 0) top = 0;
            i[s].style.top = top + U;
            i[w].scrollTop = top * i.sH1;
            addClass(i[S], q.S + q.a);
        },
        beginScroll = function(e){
            // removing selected text
            // Link: http://stackoverflow.com/a/3171348
            var sel = window.getSelection ? window.getSelection() : document.selection;
            if (sel) {
                if (sel.removeAllRanges) sel.removeAllRanges();
                else if (sel.empty) sel.empty();
            }
            var e = e || event,
                el = e[cT] || e[sE],
                k = getAttr((el[pE] || el[pN])[pS], sK),i = v[k];

            addEvent('mousemove', document, moveScroll);
            addEvent('mouseup', document, endScroll);

            i[oT] = i[w][oT];
            i.firstY = e.pageY || event.clientY;
            if(!i.reposition) i.reposition = i[s][oT];
            currentkey = k;
        },
        moveScroll = function(e){
            var e = e || event,
                k = currentkey,i = v[k],q = i.E,
                eY = e.pageY || e.clientY,
                top = (i.reposition + eY - i.firstY)/i.wH * 100;

            if(i.rP1 < top) top = i.rP1;
            if(!i.previousTop) i.previousTop = top + 1;
            var blnThreshold = top >= 0 && i.firstY > i.offsetTop;
            if((i.previousTop > top && blnThreshold) || (blnThreshold && (i[w].scrollTop + i.wH !== i.sH))){
                i[s].style.top = top + U;
                i.previousTop = top;
                i[w].scrollTop = top * i.sH1;
            }
            addClass(i[S], q.S);
        },
        endScroll = function(e){
            var e = e || event,k = currentkey,i = v[k], q = i.E;

            removeEvent('mousemove', document);
            removeEvent('mouseup', document);

            i.reposition = 0;
            addClass(i[S], q.S + q.a);
        },
        doScroll = function(e){
            var e = e || event,
                k = getAttr((e[cT] || e[sE]), sK),i = v[k];
            if(!i) return;
            var q = i.E;
            addClass(i[S], q.S);
            i[s].style.top = i[w].scrollTop/i.sH1 + U;
            addClass(i[S], q.S + q.a);
        },
        addEvent = function(e, el, func){
            el['on' + e] = func;
            // el.addEventListener(e, func, false);
        },
        removeEvent = function(e, el){
            el['on' + e] = null;
            // el.removeEventListener(e, func, false);
        },
        addCSSRule = function(S, s, r, i) {
            if(S.insertRule) S.insertRule(s + "{" + r + "}", i);
            else if(S.addRule) S.addRule(s, r, i);
        },
        insertCss = function(){
            // Inserting css rules
            // Link: http://davidwalsh.name/add-rules-stylesheets
            var slim = ".slimScroll",
                imp = " !important",
                pA = "position:absolute"+imp,
                // classes
                w = pA+";overflow:auto"+imp+";left:0px;top:0px"+imp+";right:-18px;bottom:0px"+imp+";padding-right:8px"+imp+";",
                S = pA+";top:0px"+imp+";bottom:0px"+imp+";right:0px;left:auto;width:5px;cursor:pointer"+imp+";padding-right:0px"+imp+";",
                s = pA+";background-color:#999;top:0px;left:0px;right:0px;",
                //creating a sheet
                style = document.createElement('style');
            try{
                // WebKit hack :(
                style.appendChild(document.createTextNode(""));
                document.head.appendChild(style);
                var sheet = style.sheet;
                // adding above css to the sheet
                addCSSRule(sheet, slim + ">div", w, 0);
                addCSSRule(sheet, slim + ">div+div", S, 0);
                addCSSRule(sheet, slim + ">div+div>div", s, 0);
            }
            catch(ex){
                var head = document.getElementsByTagName('head')[0];
                head.appendChild(style);
                style.styleSheet.cssText = slim + ">div{"+w+"}"+slim+">div+div{"+S+"}"+slim+">div+div>div{"+s+"}";
            }
        }();
    return {
        useSlimScroll : useSlimScroll,
        setValues : setValues
    }
})();
<commit_msg>small change<commit_after>var scroll = (function(){
    var v = [],
        w = "wrapper",s = "scrollBar",S = "scrollBarContainer",a = "",m = "",
        // properties
        oT = "offsetTop",pE = "parentElement",pes= "previousElementSibling",
        iH = "innerHTML",cT = "currentTarget",sK = "scroll-k",U = "%",d = ".",
        // IE8 properties
        // (Dev note: remove below variables from all over the code to exclude IE8 compatibility)
        pN = "parentNode",pS = "previousSibling",sE = "srcElement",
        // Initial function
        useSlimScroll = function(C, p){
            if(C.offsetHeight < C.scrollHeight){
                var h = C[iH],k = v.length, i = v[k] = {}, q = i.E = {};
                // setting user defined classes
                p = p || {};
                q.w = p.wrapperClass || "";
                q.s = p.scrollBarClass || "";
                q.S = p.scrollBarContainerClass || "";
                q.a = p.scrollBarContainerSpecialClass ? " " + p.scrollBarContainerSpecialClass : "";
                q.mH = p.scrollBarMinHeight || 25;
                q.sH = p.scrollBarFixedHeight;  // could be undefined

                C[iH] = "";
                i[w] = cE(q.w, h, C);
                i[S] = cE(q.S + q.a, "", C);
                i[s] = cE(q.s, "", i[S]);
                setValues(k);
                //store the key 'k' in the container
                i[w].setAttribute(sK, k);

                // Attaching mouse events
                addEvent('mousedown', i[s], beginScroll);
                addEvent('click', i[S], setScroll);

                // For scroll
                addEvent('scroll', i[w], doScroll);
            }
        },
        setValues = function(k){
            if(typeof k === "number"){
                assignValues(k);
            }
            else{
                for(var j=0;j<v.length;j++){
                    assignValues(j);
                }
            }
        },
        assignValues = function(k){
            var i = v[k], q = i.E;
            i.h = i[S].offsetHeight;
            i.sH = i[w].scrollHeight;
            i.sP = (i.h/i.sH) * 100;
            // i.sbh is scroll bar height in pixels without pixel unit.
            i.sbh = i.sP * i.h/100;
            // Manually set the height of the scrollbar (in percentage)
            // if user hasn't provided the fixed scroll height value
            if(!q.sH) i.sP1 = i.sbh < q.mH? (q.mH/i.h * 100): i.sP;
            else i.sP1 = q.sH/i.h * 100;

            i.rP1 = 100 - i.sP1;
            i.x = (i.sH - i.h) * ((i.sP1 - i.sP)/(100 - i.sP));
            i.sH1 = Math.abs((i.x / (i.rP1)) + (i.sH/100));
            i[s].style.height = i.sP1 + U;

            i.reposition = i[s][oT]
        },
        // Start of private functions
        getAttr = function(e, p){
            if(!e) return;
            return e.getAttribute(p);
        },
        addClass = function(e, c){
            if(c.length) e.className = c;
        },
        cE = function(c, h, p){
            var d = document.createElement('div');
            addClass(d, c);
            d[iH] = h;
            p.appendChild(d);
            return d;
        },
        setScroll = function(e){
            var e = e || event,el = e.target || event[sE],
                p = el[pE] || el[pN],
                k = getAttr(el[pS] || el[pes], sK);

            if(!k) return;
            var i = v[k],q = i.E;

            if(!i || p === i[S]) return;
            var eY = e.pageY || event.clientY,
                top = ((eY - (i[w][pE] || i[w][pN])[oT])/i.h * 100) - i.sP1/2;
            if(top > i.rP1) top = i.rP1;
            else if(top < 0) top = 0;
            i[s].style.top = top + U;
            i[w].scrollTop = top * i.sH1;
            addClass(i[S], q.S + q.a);
        },
        beginScroll = function(e){
            // removing selected text
            // Link: http://stackoverflow.com/a/3171348
            var sel = window.getSelection ? window.getSelection() : document.selection;
            if (sel) {
                if (sel.removeAllRanges) sel.removeAllRanges();
                else if (sel.empty) sel.empty();
            }
            var e = e || event,
                el = e[cT] || e[sE],
                k = getAttr((el[pE] || el[pN])[pS], sK),i = v[k];

            addEvent('mousemove', document, moveScroll);
            addEvent('mouseup', document, endScroll);

            i[oT] = i[w][oT];
            i.firstY = e.pageY || event.clientY;
            if(!i.reposition) i.reposition = i[s][oT];
            currentkey = k;
        },
        moveScroll = function(e){
            var e = e || event,
                k = currentkey,i = v[k],q = i.E,
                eY = e.pageY || e.clientY,
                top = (i.reposition + eY - i.firstY)/i.h * 100;

            if(i.rP1 < top) top = i.rP1;
            if(!i.previousTop) i.previousTop = top + 1;
            var blnThreshold = top >= 0 && i.firstY > i.offsetTop;
            if((i.previousTop > top && blnThreshold) || (blnThreshold && (i[w].scrollTop + i.h !== i.sH))){
                i[s].style.top = top + U;
                i.previousTop = top;
                i[w].scrollTop = top * i.sH1;
            }
            addClass(i[S], q.S);
        },
        endScroll = function(e){
            var e = e || event,k = currentkey,i = v[k], q = i.E;

            removeEvent('mousemove', document);
            removeEvent('mouseup', document);

            i.reposition = 0;
            addClass(i[S], q.S + q.a);
        },
        doScroll = function(e){
            var e = e || event,
                k = getAttr((e[cT] || e[sE]), sK),i = v[k];
            if(!i) return;
            var q = i.E;
            addClass(i[S], q.S);
            i[s].style.top = i[w].scrollTop/i.sH1 + U;
            addClass(i[S], q.S + q.a);
        },
        addEvent = function(e, el, func){
            el['on' + e] = func;
            // el.addEventListener(e, func, false);
        },
        removeEvent = function(e, el){
            el['on' + e] = null;
            // el.removeEventListener(e, func, false);
        },
        addCSSRule = function(S, s, r, i) {
            if(S.insertRule) S.insertRule(s + "{" + r + "}", i);
            else if(S.addRule) S.addRule(s, r, i);
        },
        insertCss = function(){
            // Inserting css rules
            // Link: http://davidwalsh.name/add-rules-stylesheets
            var slim = ".slimScroll",
                imp = " !important",
                pA = "position:absolute"+imp,
                // classes
                w = pA+";overflow:auto"+imp+";left:0px;top:0px"+imp+";right:-18px;bottom:0px"+imp+";padding-right:8px"+imp+";",
                S = pA+";top:0px"+imp+";bottom:0px"+imp+";right:0px;left:auto;width:5px;cursor:pointer"+imp+";padding-right:0px"+imp+";",
                s = pA+";background-color:#999;top:0px;left:0px;right:0px;",
                //creating a sheet
                style = document.createElement('style');
            try{
                // WebKit hack :(
                style.appendChild(document.createTextNode(""));
                document.head.appendChild(style);
                var sheet = style.sheet;
                // adding above css to the sheet
                addCSSRule(sheet, slim + ">div", w, 0);
                addCSSRule(sheet, slim + ">div+div", S, 0);
                addCSSRule(sheet, slim + ">div+div>div", s, 0);
            }
            catch(ex){
                var head = document.getElementsByTagName('head')[0];
                head.appendChild(style);
                style.styleSheet.cssText = slim + ">div{"+w+"}"+slim+">div+div{"+S+"}"+slim+">div+div>div{"+s+"}";
            }
        }();
    return {
        useSlimScroll : useSlimScroll,
        setValues : setValues
    }
})();
<|endoftext|>


<commit_before>// This is an indepedently executed script that parses triggers
// from a functions package directory.
'use strict';

var extractTriggers = require('./extractTriggers');
var EXIT = function() { process.exit(0); };

(function() { // wrap in function to allow return without exiting process
  var packageDir = process.argv[2];
  if (!packageDir) {
    process.send({error: 'Must supply package directory for functions trigger parsing.'}, EXIT);
    return;
  }

  var mod;
  var triggers = [];
  try {
    mod = require(packageDir);
  } catch (e) {
    if (e.code === 'MODULE_NOT_FOUND') {
      process.send({error: 'Error parsing triggers: ' + e.message + '\n\nTry running "npm install" in your functions directory before deploying.'}, EXIT);
      return;
    } else if (/Firebase config variables are not available/.test(e.message)) {
      process.send({error: 'Error occurred while parsing your function triggers. Please ensure you have the latest firebase-functions SDK by running "npm i --save firebase-functions@latest" inside your functions folder.\n\n' + e.stack}, EXIT);
    }

    process.send({error: 'Error occurred while parsing your function triggers.\n\n' + e.stack}, EXIT);
    return;
  }

  extractTriggers(mod, triggers);

  process.send({triggers: triggers}, EXIT);
})();
<commit_msg>propagating dash in function error to console display (#603)<commit_after>// This is an indepedently executed script that parses triggers
// from a functions package directory.
'use strict';

var extractTriggers = require('./extractTriggers');
var EXIT = function() { process.exit(0); };

(function() { // wrap in function to allow return without exiting process
  var packageDir = process.argv[2];
  if (!packageDir) {
    process.send({error: 'Must supply package directory for functions trigger parsing.'}, EXIT);
    return;
  }

  var mod;
  var triggers = [];
  try {
    mod = require(packageDir);
  } catch (e) {
    if (e.code === 'MODULE_NOT_FOUND') {
      process.send({error: 'Error parsing triggers: ' + e.message + '\n\nTry running "npm install" in your functions directory before deploying.'}, EXIT);
      return;
    } else if (/Firebase config variables are not available/.test(e.message)) {
      process.send({error: 'Error occurred while parsing your function triggers. Please ensure you have the latest firebase-functions SDK by running "npm i --save firebase-functions@latest" inside your functions folder.\n\n' + e.stack}, EXIT);
    }

    process.send({error: 'Error occurred while parsing your function triggers.\n\n' + e.stack}, EXIT);
    return;
  }

  try {
    extractTriggers(mod, triggers);
  } catch (err) {
    process.send({error: err.message}, EXIT);
  }

  process.send({triggers: triggers}, EXIT);
})();
<|endoftext|>