<commit_before>var slugify = require('slugify2'),
  async = require('async');

exports = module.exports = {
  'groupBlogs': function (kabam) {
    var GroupBlogsSchema = new kabam.mongoose.Schema({
      'groupId': kabam.mongoose.Schema.Types.ObjectId,
      'title': String,
      'description': String,
      'keywords': String,
      'contentPublic': String,
      'contentForMembers': String,
      'createdAt': {type: Date, default: new Date()}
    });
    GroupBlogsSchema.index({ groupId: 1, createdAt: 1});
    return kabam.mongoConnection.model('groupBlogs', GroupBlogsSchema);
  },
  'groupMessages': function (kabam) {
    var GroupMessagesSchema = new kabam.mongoose.Schema({
      'groupId': kabam.mongoose.Schema.Types.ObjectId,
      'userId': kabam.mongoose.Schema.Types.ObjectId,
      'message': String,
      'user': {
        'username': String,
        'firstName': String,
        'lastName': String,
        'gravatar': String
      },
      'createdAt': {type: Date, default: new Date()}
    });

    GroupMessagesSchema.index({ groupId: 1, createdAt: 1});
    return kabam.mongoConnection.model('groupMessages', GroupMessagesSchema);
  },
  'groups': function (kabam) {
    var GroupsSchema = new kabam.mongoose.Schema({
      'name': {
        type: String,
        trim: true,
        index: true
      },
      'uri': {
        type: String,
        trim: true,
        index: true
      },
      //0 - world, 1 - school, 2 - course/association, 3 - group
      'tier': {type: Number, min: 0, max: 3},

      'school_id': kabam.mongoose.Schema.Types.ObjectId,

      'course_id': kabam.mongoose.Schema.Types.ObjectId,

      'descriptionPublic': String,
      'descriptionForMembers': String,

      'members': [
        {
          '_id': kabam.mongoose.Schema.Types.ObjectId,
          'username': String,
          'gravatar': String,
          'firstName': String,
          'lastName': String,
          'role': String
        }
      ],

      'isOpenToAll': {type: Boolean, default: false},
      'isOpenToParent': {type: Boolean, default: false}
    });

    GroupsSchema.index({ name: 1, uri: 1, school_id: 1, course_id: 1 });

    GroupsSchema.methods.findRoleInThisGroup = function (user) {
      var role = 'visitor';
      this.members.map(function (member) {
        if (member.username == user.username) {
          console.log('role found');
          role = member.role;
        }
      });
      return role;
    };

    GroupsSchema.methods.checkRights = function (user, callback) {
      var thisGroup = this;
      if(!user){
        callback(null, 'visitor');//non authorized user is visitor
        return;
      }

      if(user.hasRole('helpdesk')){ //helpdesk is member of every group
        callback(null, 'member');
        return;
      }

      if(user.root || user.hasRole('hadmin')){
        callback(null,'admin'); //roots and hierarchy admins are admins of every group
      } else {
        async.parallel({
          'inSchool': function (cb) {
            if (this.school_id) {
              Groups.findOne({'_id': this.school_id}, function (err, schoolFound) {
                cb(err, schoolFound.findRoleInThisGroup(user));
              });
            } else {
              cb(null);
            }
          },
          'inCourse': function (cb) {
            if (this.course_id) {
              Groups.findOne({'_id': this.course_id}, function (err, courseFound) {
                cb(err, courseFound.findRoleInThisGroup(user));
              });
            } else {
              cb(null);
            }
          },
          'inThisGroup': function (cb) {
            cb(null, thisGroup.findRoleInThisGroup(user));
          }
        }, function(err,roles){
          //school
          if(thisGroup.tier === 1){
            callback(err, roles.inThisGroup);
            return;
          }

          //for course // association
          //if user is admin of school, he is admin of every course of school
          if(thisGroup.tier === 2){
            if(roles.inSchool === 'admin' || roles.inThisGroup === 'admin'){
              callback(err,'admin');
            } else {
              callback(err, roles.inThisGroup);
            }
            return;
          }

          //for students group
          //if user is admin of school/course, he is admin of every in course group
          if(thisGroup.tier === 3){
            if(roles.inSchool === 'admin' || roles.inCourse === 'admin' || roles.inThisGroup === 'admin'){
              callback(err,'admin');
            } else {
              callback(err, roles.inThisGroup);
            }
            return;
          }

          callback(err, 'visitor'); //something strange
        });
      }
    };

    GroupsSchema.methods.invite = function (user, role, callback) {
      var userIsNotMember = true;
      this.members.map(function(member){
        if(member.username === user.username){
          userIsNotMember = false
        }
      });
      if(userIsNotMember){
        this.members.push({
          '_id': user._id,
          'username': user.username,
          'gravatar': user.gravatar,
          'firstName': user.firstName,
          'lastName': user.lastName,
          'role': role
        });
        this.save(callback);
      } else {
        callback(new Error('User is already in this group!'));
      }
    };

    GroupsSchema.methods.inviteAdmin = function (usernameOrEmailOrUserObject, callback) {
      this.invite(usernameOrEmailOrUserObject, 'admin', callback);
    };

    GroupsSchema.methods.inviteMember = function (usernameOrEmailOrUserObject, callback) {
      this.invite(usernameOrEmailOrUserObject, 'member', callback);
    };

    GroupsSchema.methods.getParent = function(callback){
      switch(this.tier) {
        case 2:
          Groups.findOne({'_id':this.school_id, tier: 1},callback);
          break;
        case 3:
          Groups.findOne({'_id':this.course_id, tier: 2},callback);
          break;
        default:
          callback(null,null);
      }
    };

    GroupsSchema.methods.getChildren = function(callback){
      switch(this.tier) {
        case 1:
          Groups.find({'school_id':this._id, tier: 2},callback);
          break;
        case 2:
          Groups.find({'course_id':this._id, tier: 3},callback);
          break;
        default:
          callback(null,null);
      }
    };

    GroupsSchema.methods.sendMessage = function (usernameOrEmailOrUserObject, message, callback) {};

    //get blog entries suitable for this particular user
    GroupsSchema.methods.getBlog = function (user, callback) {};

    //export group as JSON object that can be viewed by this particular user
    GroupsSchema.methods.export = function(user, callback){
      var ret = {},
        thisGroup = this;

      async.parallel(
        {
          'name':function(cb){
            cb(null, thisGroup.name);
          },
          '_id':function(cb){
            cb(null, thisGroup._id);
          },
          'parent':function(cb){
            thisGroup.getParent(cb);
          },
          'uri':function(cb){
            cb(null, thisGroup.uri);
          },
          'blog':function(cb){
            cb(null,[]);
          },
          'members':function(cb){
            cb(null, thisGroup.members);
          },
          'children':function(cb){
            thisGroup.getChildren(cb);
          },
          'parentUri':function(cb){
            cb(null, thisGroup.parentUri);
          },
          'childrenUri':function(cb){
            cb(null, thisGroup.childrenUri);
          },
          'homepage':function(cb){
            thisGroup.checkRights(user, function(err, roles){
              switch(roles){
                case 'admin':
                  cb(err,{
                    'isAdmin':true,
                    'isMember':true,
                    'role':'administrator',
                    'descriptionPublic':thisGroup.descriptionPublic,
                    'descriptionForMembers':thisGroup.descriptionForMembers
                  });
                  break;
                case 'member':
                  cb(err,{
                    'isAdmin':false,
                    'isMember':true,
                    'role':'member',
                    'descriptionPublic':thisGroup.descriptionPublic,
                    'descriptionForMembers':thisGroup.descriptionForMembers
                  });
                  break;

                default: //visitor.. or something other
                  cb(err,{
                    'isAdmin':false,
                    'isMember':false,
                    'role':'visitor',
                    'descriptionPublic':thisGroup.descriptionPublic
                  });
              }
            });
          }
        },callback);
    };

    GroupsSchema.statics.findGroup = function (schoolUri, courseUri, groupUri, callback) {
      var groups = this;

      if (schoolUri && !courseUri && !groupUri && typeof callback === 'function') {
        //we try to find school by school uri
        groups.findOne({'uri': schoolUri, 'tier': 1}, function(err,schoolFound){
          if(err){
            callback(err);
          } else {
            if(schoolFound){
              schoolFound.parentUri = null;
              schoolFound.childrenUri = '/h/'+schoolFound.uri;
              callback(null,schoolFound);
            } else {
              callback(null,null);
            }
          }
        });
        return;
      }

      if (schoolUri &&  courseUri  && !groupUri && typeof callback === 'function') {
        async.waterfall(
          [
            function (cb) {
              groups.findOne({'uri': schoolUri, 'tier': 1}, function (err, schoolFound) {
                cb(err, schoolFound);
              });
            },
            function (school, cb) {
              if (school && school._id) {//school found
                groups.findOne({'uri': courseUri, 'tier': 2, 'school_id': school._id}, function (err, courseFound) {
                  cb(err, school, courseFound);
                });
              } else {
                cb(null, null, null)
              }

            },
            function (school, course, cb) {
              if (course) {
                course.school = school;
                course.parentUri = '/h/'+school.uri;
                course.childrenUri = '/h/'+school.uri+'/'+course.uri;
                cb(null, course);
              } else {
                cb(null, null);
              }
            }
          ], callback);
        return;
      }

      if (schoolUri &&  courseUri  && groupUri && typeof callback === 'function') {
        async.waterfall(
          [
            function (cb) {
              groups.findOne({'uri': schoolUri, 'tier': 1}, function (err, schoolFound) {
                cb(err, schoolFound);
              });
            },
            function (school, cb) {
              if (school && school._id) {//school found
                groups.findOne({'uri': courseUri, 'tier': 2, 'school_id': school._id}, function (err, courseFound) {
                  cb(err, school, courseFound);
                });
              } else {
                cb(null, null, null)
              }
            },
            function (school, course, cb) {
              if (school && course && school._id && course._id) {
                groups.findOne({'uri': groupUri, 'tier': 3, 'school_id': school._id, 'course_id': course._id}, function (err, groupFound) {
                  cb(err, school, course, groupFound);
                });
              } else {
                cb(null, null, null, null);
              }
            },
            function (school, course, group, cb) {
              if (group) {
                group.school = school;
                group.course = course;
                group.parentUri = '/h/'+school.uri+'/'+course.uri;
                group.childrenUri = null;
                cb(null, group);
              } else {
                cb(null, null);
              }
            }
          ], callback);
        return;
      }
    };

    var Groups = kabam.mongoConnection.model('groups', GroupsSchema);
    return Groups;
  }};
<commit_msg>[feature] - ban<commit_after>var slugify = require('slugify2'),
  async = require('async');

exports = module.exports = {
  'groupBlogs': function (kabam) {
    var GroupBlogsSchema = new kabam.mongoose.Schema({
      'groupId': kabam.mongoose.Schema.Types.ObjectId,
      'title': String,
      'description': String,
      'keywords': String,
      'contentPublic': String,
      'contentForMembers': String,
      'createdAt': {type: Date, default: new Date()}
    });
    GroupBlogsSchema.index({ groupId: 1, createdAt: 1});
    return kabam.mongoConnection.model('groupBlogs', GroupBlogsSchema);
  },
  'groupMessages': function (kabam) {
    var GroupMessagesSchema = new kabam.mongoose.Schema({
      'groupId': kabam.mongoose.Schema.Types.ObjectId,
      'userId': kabam.mongoose.Schema.Types.ObjectId,
      'message': String,
      'user': {
        'username': String,
        'firstName': String,
        'lastName': String,
        'gravatar': String
      },
      'createdAt': {type: Date, default: new Date()}
    });

    GroupMessagesSchema.index({ groupId: 1, createdAt: 1});
    return kabam.mongoConnection.model('groupMessages', GroupMessagesSchema);
  },
  'groups': function (kabam) {
    var GroupsSchema = new kabam.mongoose.Schema({
      'name': {
        type: String,
        trim: true,
        index: true
      },
      'uri': {
        type: String,
        trim: true,
        index: true
      },
      //0 - world, 1 - school, 2 - course/association, 3 - group
      'tier': {type: Number, min: 0, max: 3},

      'school_id': kabam.mongoose.Schema.Types.ObjectId,

      'course_id': kabam.mongoose.Schema.Types.ObjectId,

      'descriptionPublic': String,
      'descriptionForMembers': String,

      'members': [
        {
          '_id': kabam.mongoose.Schema.Types.ObjectId,
          'username': String,
          'gravatar': String,
          'firstName': String,
          'lastName': String,
          'role': String
        }
      ],

      'isOpenToAll': {type: Boolean, default: false},
      'isOpenToParent': {type: Boolean, default: false}
    });

    GroupsSchema.index({ name: 1, uri: 1, school_id: 1, course_id: 1 });

    GroupsSchema.methods.findRoleInThisGroup = function (user) {
      var role = 'visitor';
      this.members.map(function (member) {
        if (member.username == user.username) {
          console.log('role found');
          role = member.role;
        }
      });
      return role;
    };

    GroupsSchema.methods.checkRights = function (user, callback) {
      var thisGroup = this;
      if(!user){
        callback(null, 'visitor');//non authorized user is visitor
        return;
      }

      if(user.hasRole('helpdesk')){ //helpdesk is member of every group
        callback(null, 'member');
        return;
      }

      if(user.root || user.hasRole('hadmin')){
        callback(null,'admin'); //roots and hierarchy admins are admins of every group
      } else {
        async.parallel({
          'inSchool': function (cb) {
            if (this.school_id) {
              Groups.findOne({'_id': this.school_id}, function (err, schoolFound) {
                cb(err, schoolFound.findRoleInThisGroup(user));
              });
            } else {
              cb(null);
            }
          },
          'inCourse': function (cb) {
            if (this.course_id) {
              Groups.findOne({'_id': this.course_id}, function (err, courseFound) {
                cb(err, courseFound.findRoleInThisGroup(user));
              });
            } else {
              cb(null);
            }
          },
          'inThisGroup': function (cb) {
            cb(null, thisGroup.findRoleInThisGroup(user));
          }
        }, function(err,roles){
          //school
          if(thisGroup.tier === 1){
            callback(err, roles.inThisGroup);
            return;
          }

          //for course // association
          //if user is admin of school, he is admin of every course of school
          if(thisGroup.tier === 2){
            if(roles.inSchool === 'admin' || roles.inThisGroup === 'admin'){
              callback(err,'admin');
            } else {
              callback(err, roles.inThisGroup);
            }
            return;
          }

          //for students group
          //if user is admin of school/course, he is admin of every in course group
          if(thisGroup.tier === 3){
            if(roles.inSchool === 'admin' || roles.inCourse === 'admin' || roles.inThisGroup === 'admin'){
              callback(err,'admin');
            } else {
              callback(err, roles.inThisGroup);
            }
            return;
          }

          callback(err, 'visitor'); //something strange
        });
      }
    };

    GroupsSchema.methods.invite = function (user, role, callback) {
      var userIsNotMember = true;
      this.members.map(function(member){
        if(member.username === user.username){
          userIsNotMember = false
        }
      });
      if(userIsNotMember){
        this.members.push({
          '_id': user._id,
          'username': user.username,
          'gravatar': user.gravatar,
          'firstName': user.firstName,
          'lastName': user.lastName,
          'role': role
        });
        this.save(callback);
      } else {
        callback(new Error('User is already in this group!'));
      }
    };

    GroupsSchema.methods.ban = function(user,callback){
      for (var i=0; i<this.members.length; i++){
        if(this.members[i].username === user.username){
          this.members.splice(i,1);
          break;
        }
      }
      this.save(callback);
    };

    GroupsSchema.methods.inviteAdmin = function (usernameOrEmailOrUserObject, callback) {
      this.invite(usernameOrEmailOrUserObject, 'admin', callback);
    };

    GroupsSchema.methods.inviteMember = function (usernameOrEmailOrUserObject, callback) {
      this.invite(usernameOrEmailOrUserObject, 'member', callback);
    };

    GroupsSchema.methods.getParent = function(callback){
      switch(this.tier) {
        case 2:
          Groups.findOne({'_id':this.school_id, tier: 1},callback);
          break;
        case 3:
          Groups.findOne({'_id':this.course_id, tier: 2},callback);
          break;
        default:
          callback(null,null);
      }
    };

    GroupsSchema.methods.getChildren = function(callback){
      switch(this.tier) {
        case 1:
          Groups.find({'school_id':this._id, tier: 2},callback);
          break;
        case 2:
          Groups.find({'course_id':this._id, tier: 3},callback);
          break;
        default:
          callback(null,null);
      }
    };

    GroupsSchema.methods.sendMessage = function (usernameOrEmailOrUserObject, message, callback) {};

    //get blog entries suitable for this particular user
    GroupsSchema.methods.getBlog = function (user, callback) {};

    //export group as JSON object that can be viewed by this particular user
    GroupsSchema.methods.export = function(user, callback){
      var ret = {},
        thisGroup = this;

      async.parallel(
        {
          'name':function(cb){
            cb(null, thisGroup.name);
          },
          '_id':function(cb){
            cb(null, thisGroup._id);
          },
          'parent':function(cb){
            thisGroup.getParent(cb);
          },
          'uri':function(cb){
            cb(null, thisGroup.uri);
          },
          'blog':function(cb){
            cb(null,[]);
          },
          'members':function(cb){
            cb(null, thisGroup.members);
          },
          'children':function(cb){
            thisGroup.getChildren(cb);
          },
          'parentUri':function(cb){
            cb(null, thisGroup.parentUri);
          },
          'childrenUri':function(cb){
            cb(null, thisGroup.childrenUri);
          },
          'homepage':function(cb){
            thisGroup.checkRights(user, function(err, roles){
              switch(roles){
                case 'admin':
                  cb(err,{
                    'isAdmin':true,
                    'isMember':true,
                    'role':'administrator',
                    'descriptionPublic':thisGroup.descriptionPublic,
                    'descriptionForMembers':thisGroup.descriptionForMembers
                  });
                  break;
                case 'member':
                  cb(err,{
                    'isAdmin':false,
                    'isMember':true,
                    'role':'member',
                    'descriptionPublic':thisGroup.descriptionPublic,
                    'descriptionForMembers':thisGroup.descriptionForMembers
                  });
                  break;

                default: //visitor.. or something other
                  cb(err,{
                    'isAdmin':false,
                    'isMember':false,
                    'role':'visitor',
                    'descriptionPublic':thisGroup.descriptionPublic
                  });
              }
            });
          }
        },callback);
    };

    GroupsSchema.statics.findGroup = function (schoolUri, courseUri, groupUri, callback) {
      var groups = this;

      if (schoolUri && !courseUri && !groupUri && typeof callback === 'function') {
        //we try to find school by school uri
        groups.findOne({'uri': schoolUri, 'tier': 1}, function(err,schoolFound){
          if(err){
            callback(err);
          } else {
            if(schoolFound){
              schoolFound.parentUri = null;
              schoolFound.childrenUri = '/h/'+schoolFound.uri;
              callback(null,schoolFound);
            } else {
              callback(null,null);
            }
          }
        });
        return;
      }

      if (schoolUri &&  courseUri  && !groupUri && typeof callback === 'function') {
        async.waterfall(
          [
            function (cb) {
              groups.findOne({'uri': schoolUri, 'tier': 1}, function (err, schoolFound) {
                cb(err, schoolFound);
              });
            },
            function (school, cb) {
              if (school && school._id) {//school found
                groups.findOne({'uri': courseUri, 'tier': 2, 'school_id': school._id}, function (err, courseFound) {
                  cb(err, school, courseFound);
                });
              } else {
                cb(null, null, null)
              }

            },
            function (school, course, cb) {
              if (course) {
                course.school = school;
                course.parentUri = '/h/'+school.uri;
                course.childrenUri = '/h/'+school.uri+'/'+course.uri;
                cb(null, course);
              } else {
                cb(null, null);
              }
            }
          ], callback);
        return;
      }

      if (schoolUri &&  courseUri  && groupUri && typeof callback === 'function') {
        async.waterfall(
          [
            function (cb) {
              groups.findOne({'uri': schoolUri, 'tier': 1}, function (err, schoolFound) {
                cb(err, schoolFound);
              });
            },
            function (school, cb) {
              if (school && school._id) {//school found
                groups.findOne({'uri': courseUri, 'tier': 2, 'school_id': school._id}, function (err, courseFound) {
                  cb(err, school, courseFound);
                });
              } else {
                cb(null, null, null)
              }
            },
            function (school, course, cb) {
              if (school && course && school._id && course._id) {
                groups.findOne({'uri': groupUri, 'tier': 3, 'school_id': school._id, 'course_id': course._id}, function (err, groupFound) {
                  cb(err, school, course, groupFound);
                });
              } else {
                cb(null, null, null, null);
              }
            },
            function (school, course, group, cb) {
              if (group) {
                group.school = school;
                group.course = course;
                group.parentUri = '/h/'+school.uri+'/'+course.uri;
                group.childrenUri = null;
                cb(null, group);
              } else {
                cb(null, null);
              }
            }
          ], callback);
        return;
      }
    };

    var Groups = kabam.mongoConnection.model('groups', GroupsSchema);
    return Groups;
  }};
<|endoftext|>


<commit_before>var adminApp = angular
        .module('app', ['ng-admin'])
        .run(run);

//const sendash = 'https://sendash.com/';
const sendash = 'http://localhost:8191/sendash/api/admin/';

adminApp.config(['NgAdminConfigurationProvider', 'RestangularProvider', function (nga, RestangularProvider) {
    // create an admin application
    var admin = nga.application('Sendash Admin')
        .baseApiUrl(sendash);

    var role = nga.entity('role');
    role.readOnly();
    admin.addEntity(role);

    var client = nga.entity('client');
    client.listView().fields([
        nga.field('id'),
        nga.field('name')
    ]);
    client.listView().listActions(['edit', 'delete']);

    client.creationView().fields([
        nga.field('name')

    ]);
    // use the same fields for the editionView as for the creationView
    client.editionView().fields(client.creationView().fields());
    admin.addEntity(client);

    var user = nga.entity('user');
    // set the fields of the user entity list view
    user.listView().fields([
        nga.field('email'),
        nga.field('firstName')
            .label('First Name'),
        nga.field('lastName')
            .label('Last Name'),
        nga.field('roles', 'template')
            .label('Roles')
            .template('
{{n.name}}
'),
        nga.field('clients', 'embedded_list')
            .label('Clients')
            .template('
{{n.name}}
')
    ]);
    user.listView().listActions(['edit', 'delete']);

    user.creationView().fields([
        nga.field('email', 'email'),
        nga.field('firstName')
            .label('First Name'),
        nga.field('lastName')
            .label('Last Name'),
        nga.field('roles', 'choice')
            .choices([{label: "ROLE_USER", value: "1"}, {label: "ROLE_ADMIN", value: "2"}]),
        nga.field('clients')
    ]);
    // use the same fields for the editionView as for the creationView
    user.editionView().fields(user.creationView().fields());

    admin.addEntity(user);

    var endpoint = nga.entity('endpoint');
    // set the fields of the user entity list view
    endpoint.listView().fields([
        nga.field('id'),
        nga.field('client.name')
            .label('Client Name'),
        nga.field('hostName')
            .label('Host Name'),
        nga.field('apiKey')
            .label('API Key'),
        nga.field('updateScriptRequest', 'datetime')
            .label('Last Request')
    ]);
    endpoint.listView()
        .listActions(['edit', 'delete']);

    endpoint.creationView().fields([
        nga.field('client.id', 'reference')
            .label('Client Name')
            .targetEntity(client)
            .targetField(nga.field('name'))
            .attributes({ placeholder: 'Select one' }),
        nga.field('hostName')
            .label('Host Name'),
        nga.field('apiKey')
            .label('API Key')
    ]);
    // use the same fields for the editionView as for the creationView
    endpoint.editionView().fields(endpoint.creationView().fields());
    admin.addEntity(endpoint);

    var pendingEndpoint = nga.entity('pending-endpoint');
    // set the fields of the user entity list view
    pendingEndpoint.listView().fields([
        nga.field('client.clientName')
            .label('Client Name'),
        nga.field('hostName')
            .label('Host Name'),
        nga.field('apiKey')
            .label('API Key')
    ]);
    pendingEndpoint.listView()
        .listActions(['edit', 'delete'])
        .batchActions(['<batch-approve type="accept" selection="selection"></batch-approve>',
            '<batch-approve type="reject" selection="selection"></batch-approve>',
            'delete']);

    pendingEndpoint.creationView().fields([
        nga.field('client.id', 'reference')
            .label('Client Name')
            .targetEntity(client)
            .targetField(nga.field('name'))
            .attributes({ placeholder: 'Select one' }),
        nga.field('hostName')
            .label('Host Name'),
        nga.field('apiKey')
            .label('API Key')
    ]);
    // use the same fields for the editionView as for the creationView
    pendingEndpoint.editionView().fields(pendingEndpoint.creationView().fields());
    admin.addEntity(pendingEndpoint);

    var githubPayload = nga.entity('github').label('Github Payload');
    githubPayload.listView().fields([
        nga.field('receivedTimestamp', 'datetime')
            .label('Received'),
        nga.field('commits')
    ]);
    githubPayload.listView().title('Github Payloads List');
    githubPayload.readOnly();
    admin.addEntity(githubPayload);

    admin.header(require('./header.html'));
    admin.menu(nga.menu()
            .addChild(nga.menu(user).icon(''))
            .addChild(nga.menu(client).icon(''))
            .addChild(nga.menu(endpoint).icon(''))
            .addChild(nga.menu(pendingEndpoint).icon(''))
            .addChild(nga.menu(githubPayload).icon(''))
    );

    nga.configure(admin);
}]);

adminApp.config(['$stateProvider', function($stateProvider) {
    $stateProvider.state('login', {
        parent: 'main',
        url: '/login',
        controller: 'LoginController',
        controllerAs: 'vm',
        templateUrl: 'login/login.view.html'
    });
}]);

adminApp.config(['RestangularProvider', function (RestangularProvider) {
    RestangularProvider.addFullRequestInterceptor(function(element, operation, what, url, headers, params) {
        if (operation == "getList") {
            // custom pagination params
            if (params._page) {
                params._page = (params._page - 1);
            }
        }
        return { params: params };
    });

}]);

adminApp.controller('username', ['$scope', '$window', '$location', function($scope, $window, $location) { // used in header.html
    if($window.sessionStorage.getItem('userInfo')) {
        $scope.username = JSON.parse($window.sessionStorage.getItem('userInfo')).username;
    }
    else {
        $location.path("/login");
    }
}]);

adminApp.controller('userRoleCtl', ['$scope', 'UserRoleService', function($scope, UserRoleService) {
    $scope.userRoles = UserRoleService.UserRoles();
}]);

run.$inject = ['$rootScope', '$location',  '$http', 'AuthenticationService', 'Restangular'];
function run($rootScope, $location,  $http, AuthenticationService, Restangular) {

    $rootScope.$on('$locationChangeStart', function (event, next, current) {
        // redirect to login page if not logged in and trying to access a restricted page
        var restrictedPage = $.inArray($location.path(), ['/login', '/register']) === -1;
        var loggedIn = AuthenticationService.IsAuthenticated();
        if (restrictedPage && !loggedIn) {
            $location.path('/login');
            Restangular.setDefaultHeaders({});
        }
        else {
            Restangular.setDefaultHeaders({'Authorization': 'Bearer ' + AuthenticationService.GetToken()});
        }
    });
}
<commit_msg>if I'm already hard coding, then go ahead and make the roles look nicer than what's stored in the db.<commit_after>var adminApp = angular
        .module('app', ['ng-admin'])
        .run(run);

//const sendash = 'https://sendash.com/';
const sendash = 'http://localhost:8191/sendash/api/admin/';

adminApp.config(['NgAdminConfigurationProvider', 'RestangularProvider', function (nga, RestangularProvider) {
    // create an admin application
    var admin = nga.application('Sendash Admin')
        .baseApiUrl(sendash);

    var role = nga.entity('role');
    role.readOnly();
    admin.addEntity(role);

    var client = nga.entity('client');
    client.listView().fields([
        nga.field('id'),
        nga.field('name')
    ]);
    client.listView().listActions(['edit', 'delete']);

    client.creationView().fields([
        nga.field('name')

    ]);
    // use the same fields for the editionView as for the creationView
    client.editionView().fields(client.creationView().fields());
    admin.addEntity(client);

    var user = nga.entity('user');
    // set the fields of the user entity list view
    user.listView().fields([
        nga.field('email'),
        nga.field('firstName')
            .label('First Name'),
        nga.field('lastName')
            .label('Last Name'),
        nga.field('roles', 'template')
            .label('Roles')
            .template('
{{n.name}}
'),
        nga.field('clients', 'embedded_list')
            .label('Clients')
            .template('
{{n.name}}
')
    ]);
    user.listView().listActions(['edit', 'delete']);

    user.creationView().fields([
        nga.field('email', 'email'),
        nga.field('firstName')
            .label('First Name'),
        nga.field('lastName')
            .label('Last Name'),
        nga.field('roles', 'choice')
            .choices([{label: "User", value: "1"}, {label: "Admin", value: "2"}]),
        nga.field('clients')
    ]);
    // use the same fields for the editionView as for the creationView
    user.editionView().fields(user.creationView().fields());

    admin.addEntity(user);

    var endpoint = nga.entity('endpoint');
    // set the fields of the user entity list view
    endpoint.listView().fields([
        nga.field('id'),
        nga.field('client.name')
            .label('Client Name'),
        nga.field('hostName')
            .label('Host Name'),
        nga.field('apiKey')
            .label('API Key'),
        nga.field('updateScriptRequest', 'datetime')
            .label('Last Request')
    ]);
    endpoint.listView()
        .listActions(['edit', 'delete']);

    endpoint.creationView().fields([
        nga.field('client.id', 'reference')
            .label('Client Name')
            .targetEntity(client)
            .targetField(nga.field('name'))
            .attributes({ placeholder: 'Select one' }),
        nga.field('hostName')
            .label('Host Name'),
        nga.field('apiKey')
            .label('API Key')
    ]);
    // use the same fields for the editionView as for the creationView
    endpoint.editionView().fields(endpoint.creationView().fields());
    admin.addEntity(endpoint);

    var pendingEndpoint = nga.entity('pending-endpoint');
    // set the fields of the user entity list view
    pendingEndpoint.listView().fields([
        nga.field('client.clientName')
            .label('Client Name'),
        nga.field('hostName')
            .label('Host Name'),
        nga.field('apiKey')
            .label('API Key')
    ]);
    pendingEndpoint.listView()
        .listActions(['edit', 'delete'])
        .batchActions(['<batch-approve type="accept" selection="selection"></batch-approve>',
            '<batch-approve type="reject" selection="selection"></batch-approve>',
            'delete']);

    pendingEndpoint.creationView().fields([
        nga.field('client.id', 'reference')
            .label('Client Name')
            .targetEntity(client)
            .targetField(nga.field('name'))
            .attributes({ placeholder: 'Select one' }),
        nga.field('hostName')
            .label('Host Name'),
        nga.field('apiKey')
            .label('API Key')
    ]);
    // use the same fields for the editionView as for the creationView
    pendingEndpoint.editionView().fields(pendingEndpoint.creationView().fields());
    admin.addEntity(pendingEndpoint);

    var githubPayload = nga.entity('github').label('Github Payload');
    githubPayload.listView().fields([
        nga.field('receivedTimestamp', 'datetime')
            .label('Received'),
        nga.field('commits')
    ]);
    githubPayload.listView().title('Github Payloads List');
    githubPayload.readOnly();
    admin.addEntity(githubPayload);

    admin.header(require('./header.html'));
    admin.menu(nga.menu()
            .addChild(nga.menu(user).icon(''))
            .addChild(nga.menu(client).icon(''))
            .addChild(nga.menu(endpoint).icon(''))
            .addChild(nga.menu(pendingEndpoint).icon(''))
            .addChild(nga.menu(githubPayload).icon(''))
    );

    nga.configure(admin);
}]);

adminApp.config(['$stateProvider', function($stateProvider) {
    $stateProvider.state('login', {
        parent: 'main',
        url: '/login',
        controller: 'LoginController',
        controllerAs: 'vm',
        templateUrl: 'login/login.view.html'
    });
}]);

adminApp.config(['RestangularProvider', function (RestangularProvider) {
    RestangularProvider.addFullRequestInterceptor(function(element, operation, what, url, headers, params) {
        if (operation == "getList") {
            // custom pagination params
            if (params._page) {
                params._page = (params._page - 1);
            }
        }
        return { params: params };
    });

}]);

adminApp.controller('username', ['$scope', '$window', '$location', function($scope, $window, $location) { // used in header.html
    if($window.sessionStorage.getItem('userInfo')) {
        $scope.username = JSON.parse($window.sessionStorage.getItem('userInfo')).username;
    }
    else {
        $location.path("/login");
    }
}]);

adminApp.controller('userRoleCtl', ['$scope', 'UserRoleService', function($scope, UserRoleService) {
    $scope.userRoles = UserRoleService.UserRoles();
}]);

run.$inject = ['$rootScope', '$location',  '$http', 'AuthenticationService', 'Restangular'];
function run($rootScope, $location,  $http, AuthenticationService, Restangular) {

    $rootScope.$on('$locationChangeStart', function (event, next, current) {
        // redirect to login page if not logged in and trying to access a restricted page
        var restrictedPage = $.inArray($location.path(), ['/login', '/register']) === -1;
        var loggedIn = AuthenticationService.IsAuthenticated();
        if (restrictedPage && !loggedIn) {
            $location.path('/login');
            Restangular.setDefaultHeaders({});
        }
        else {
            Restangular.setDefaultHeaders({'Authorization': 'Bearer ' + AuthenticationService.GetToken()});
        }
    });
}
<|endoftext|>


<commit_before>/**
 * Created by Vignesh Jagadeesh on 18/5/16.
 *
 *
 */

'use strict';

const client = require('mongodb').MongoClient,
  Promise = require('bluebird'),
  Redis = require('ioredis'),
  logger = require('winston'),
  EventEmitter = require('events').EventEmitter;

const Schema = require('./schema'),
  Model = require('./model');

function Movent() {
  this.models = {};
  this.modelSchemas = {};
  this.db = null;
  this.redis = null;
  this.notify = null;
}

require('util').inherits(Movent,EventEmitter);

Movent.prototype.setNotifier = function(amqpPublisher){
  this.notify = amqpPublisher;
};

Movent.prototype.connectToMongo = function (mongoUrl,options) {
  const _this = this;

  if(options.replset) options.replSet = {rs_name:options.replset};

  console.log('--------------- MONGO DEBUG --------');
  console.log('MONGO URL ' , mongoUrl);
  console.log('OPTIONS',options);

  return new Promise((resolve, reject)=> {
    client.connect(mongoUrl,options,function(err,db){
      if(err) {
        logger.error('✗ MongoDB Connection Error. Please make sure MongoDB is running: ', err);
        return reject(err);
      }
      logger.info('Connected to mongodb',{url:mongoUrl});
      _this.db = db;
      return resolve();
    });
  });
};

Movent.prototype.connectToRedis = function(redisPort,redisUrl){
  const _this = this;
  return new Promise((resolve,reject)=>{
    _this.redis = new Redis(redisPort, redisUrl);

    //Test if redis is connected;
    _this.redis.set('test','0').then(()=>{
      logger.info(`Connected to redis server on ${redisUrl}:${redisPort}`);
      resolve();
    }).catch((err)=>{
      logger.error('✗ Redis Connection Error. Please make sure Redis Server is running: ', err);
    });
  });
};

Movent.prototype.Schema = Schema;

Movent.prototype.model = function(name,schema){
  const _this = this;

  //Return the model if no schema is given.
  if(!schema) {

    if(!_this.models[name]){
      return  new Error('Schema not initialized for model ' + name);
    }
    return this.models[name];
  }

  this.models[name] = new Model(name,schema,_this);

  return this.models[name];
};

Movent.prototype.getModelNames = function(){
  return Object.keys(this.models);
};

module.exports = new Movent();<commit_msg>Mongo connection with options<commit_after>/**
 * Created by Vignesh Jagadeesh on 18/5/16.
 *
 *
 */

'use strict';

const client = require('mongodb').MongoClient,
  Promise = require('bluebird'),
  Redis = require('ioredis'),
  logger = require('winston'),
  EventEmitter = require('events').EventEmitter;

const Schema = require('./schema'),
  Model = require('./model');

function Movent() {
  this.models = {};
  this.modelSchemas = {};
  this.db = null;
  this.redis = null;
  this.notify = null;
}

require('util').inherits(Movent,EventEmitter);

Movent.prototype.setNotifier = function(amqpPublisher){
  this.notify = amqpPublisher;
};

Movent.prototype.connectToMongo = function (mongoUrl,options) {
  const _this = this;

  if(options.replset && typeof options.replset === 'string') options.replSet = {rs_name:options.replset};

  console.log('--------------- MONGO DEBUG --------');
  console.log('MONGO URL ' , mongoUrl);
  console.log('OPTIONS',options);

  return new Promise((resolve, reject)=> {
    client.connect(mongoUrl,options,function(err,db){
      if(err) {
        logger.error('✗ MongoDB Connection Error. Please make sure MongoDB is running: ', err);
        return reject(err);
      }
      logger.info('Connected to mongodb',{url:mongoUrl});
      _this.db = db;
      return resolve();
    });
  });
};

Movent.prototype.connectToRedis = function(redisPort,redisUrl){
  const _this = this;
  return new Promise((resolve,reject)=>{
    _this.redis = new Redis(redisPort, redisUrl);

    //Test if redis is connected;
    _this.redis.set('test','0').then(()=>{
      logger.info(`Connected to redis server on ${redisUrl}:${redisPort}`);
      resolve();
    }).catch((err)=>{
      logger.error('✗ Redis Connection Error. Please make sure Redis Server is running: ', err);
    });
  });
};

Movent.prototype.Schema = Schema;

Movent.prototype.model = function(name,schema){
  const _this = this;

  //Return the model if no schema is given.
  if(!schema) {

    if(!_this.models[name]){
      return  new Error('Schema not initialized for model ' + name);
    }
    return this.models[name];
  }

  this.models[name] = new Model(name,schema,_this);

  return this.models[name];
};

Movent.prototype.getModelNames = function(){
  return Object.keys(this.models);
};

module.exports = new Movent();<|endoftext|>


<commit_before>;((exports) => {
    "use strict";

    // https://mozilla.design/firefox/color/
    const FIREFOX_YELLOW_6 = "#FFA537";
    const FIREFOX_BLUE_4 = "#00B3F5";

    const MS_PER_DAY = 24 * 60 * 60 * 1000;
    const MS_PER_WEEK = 7 * MS_PER_DAY;
    const MS_PER_MONTH = 4 * MS_PER_WEEK;

    function getForecastedNightlyVersion(forecastDate) {
        const NIGHTLY_FREEZE_DATES = [
            makeFreezeDate(90, "2021-05-27"),
            makeFreezeDate(91, "2021-07-08"),
            makeFreezeDate(92, "2021-08-05"),
            makeFreezeDate(93, "2021-09-02"),
            makeFreezeDate(94, "2021-09-30"),
            makeFreezeDate(95, "2021-10-28"),
            makeFreezeDate(96, "2021-12-02"),
            makeFreezeDate(97, "2021-01-06"),
            makeFreezeDate(98, "2022-02-03"),
            makeFreezeDate(99, "2022-03-03"),
        ];

        let forecastTime = forecastDate.getTime();
        for (const nightly of NIGHTLY_FREEZE_DATES) {
            if (forecastTime <= nightly.freezeTime) {
                return nightly.version;
            }
        }

        return "2022? 😵";

      function makeFreezeDate(nightlyVersion, freezeYMD) {
          return {version: nightlyVersion, freezeTime: new Date(freezeYMD).getTime()};
      }
    }

    const DEBUG = true;
    function debug(...args) { DEBUG && console.debug(...args); }

    function days(d) { return d * MS_PER_DAY; }
    function weeks(w) { return days(7 * w); }
    function months(m) { return weeks(4 * m); }

    const queryString = getQueryString();
    const searchParams = parseQueryString(queryString);
    const chartStartDate = getChartStartDate();

    function getQueryString() {
        // e.g. "?foo=bar&baz=qux&/"
        let qs = window.location.search;
        if (qs.length <= 1) {
            return "";
        }
        const slash = (qs[qs.length - 1] === '/') ? -1 : undefined;
        return qs.slice(1, slash);
    }

    function parseQueryString(qs) {
        // e.g. "foo=bar&baz=qux&"
        const kvs = {};
        const params = qs.split("&");
        for (let kv of params) {
            kv = kv.split("=", 2);
            const key = kv[0].toLowerCase();
            if (key.length === 0) {
                continue; // "&&"
            }
            const value = (kv.length > 1) ? decodeURIComponent(kv[1]) : "";
            kvs[key] = value;
        }
        return kvs;
    }

    function getChartStartDate() {
      const CHART_START_PERIOD = months(3);
      return (searchParams.burnup_since || searchParams.since) ||
             yyyy_mm_dd(new Date(Date.now() - CHART_START_PERIOD));
    }

    function getElementValue(id) {
        return document.getElementById(id).value;
    }

    function yyyy_mm_dd(date) {
        return date.toISOString().slice(0,10);
    }

    function drawChart(bugDates, openBugCounts, closedBugCounts) {
        bb.generate({
            data: {
                xs: {
                    "openBugCounts": "bugDates",
                    "closedBugCounts": "bugDates",
                },
                columns: [
                    ["bugDates", ...bugDates],
                    ["closedBugCounts", ...closedBugCounts],
                    ["openBugCounts", ...openBugCounts],
                ],
                names: {
                    "openBugCounts": "Open Bugs",
                    "closedBugCounts": "Closed Bugs",
                },
                types: {
                    "openBugCounts": "area",
                    "closedBugCounts": "area",
                },
                colors: {
                    "openBugCounts": FIREFOX_YELLOW_6,
                    "closedBugCounts": FIREFOX_BLUE_4,
                },
                groups: [["openBugCounts", "closedBugCounts"]],
                order: null,
            },
            axis: {
                x: {
                    type: "timeseries",
                    tick: {format: "%Y-%m-%d"},
                }
            },
        });
    }

    function createElement(tag, child) {
        const element = document.createElement(tag);
        if (typeof child !== "undefined") {
            if (typeof child !== "object") {
                child = document.createTextNode(child.toString());
            }
            element.appendChild(child);
        }
        return element;
    }

    function createLink(text, url) {
        const element = createElement("a", text);
        element.setAttribute("href", url);
        return element;
    }

    function setErrorText(msg) {
        let chart = document.getElementById("chart");
        chart.innerText = msg;
    }

    function searchAndPlotBugs(bugzillaQuery) {
        const t0 = Date.now();
        debug(`searchAndPlotBugs: ${bugzillaQuery}`);
        if (!bugzillaQuery) {
            setErrorText("🙈 Zarro boogs found");
            return;
        }

        $bugzilla.searchBugs(bugzillaQuery, (error, bugs) => {
            const t1 = Date.now();
            debug(`searchAndPlotBugs: ${t1 - t0} ms`);

            if (error) {
                setErrorText(`🤮 ${error.type}`);
                return;
            }

            if (bugs.length === 0) {
                setErrorText("🙈 Zarro boogs found");
                return;
            }

            let bugActivity = {};

            function openedBugOn(date) {
                let bugDate = bugActivity[date];
                if (bugDate) {
                    bugDate.opened++;
                } else { // is undefined
                    bugActivity[date] = {date: date, opened: 1, closed: 0};
                }
            }

            function closedBugOn(date) {
                let bugDate = bugActivity[date];
                if (bugDate) {
                    bugDate.closed++;
                } else { // is undefined
                    bugActivity[date] = {date: date, opened: 0, closed: 1};
                }
            }

            const bugList = document.getElementById("bugs");
            let bugListURL = `https://bugzilla.mozilla.org/buglist.cgi?bug_id=`;

            for (let bug of bugs) {
                let openDate = yyyy_mm_dd(bug.creationTime);
                if (openDate < chartStartDate) {
                    openDate = chartStartDate;
                }
                openedBugOn(openDate);

                if (bug.open) {
                    const bugURL = $bugzilla.makeURL(bug.id);
                    const bugRow = createElement("div");
                    bugRow.appendChild(createLink(`bug ${bug.id} - ${bug.summary}`, bugURL));
                    bugList.appendChild(bugRow);
                    bugListURL += `${bug.id},`;
                } else {
                    let closedDate = yyyy_mm_dd(bug.resolutionTime);
                    if (closedDate < chartStartDate) {
                        closedDate = chartStartDate;
                    }
                    closedBugOn(closedDate);
                }
            }

            const openLink = createLink("Open bug list in Bugzilla", bugListURL);
            openLink.classList.add('open-bugzilla');
            bugList.appendChild(openLink);

            let bugDates = [];
            let openBugCounts = [];
            let closedBugCounts = [];

            let openBugCount = 0;
            let closedBugCount = 0;

            bugActivity = _.sortBy(bugActivity, "date");
            for (let {date, opened, closed} of bugActivity) {
                bugDates.push(date);

                openBugCount += opened - closed;
                openBugCounts.push(openBugCount);

                // Don't display bugs closed before the start date.
                if (closedBugCounts.length == 0 &&
                    searchParams.burnup_ignore_old_closed_bugs !== undefined) {
                  closedBugCounts.push(closedBugCount);
                } else {
                  closedBugCount += closed;
                  closedBugCounts.push(closedBugCount);
                }
            }

            // Extend last bug count to today, so burndown ends on today.
            const today = yyyy_mm_dd(new Date());
            if (bugDates.length > 0 && _.last(bugDates) < today) {
                bugDates.push(today);
                openBugCounts.push(openBugCount);
                closedBugCounts.push(closedBugCount);
            }

            drawChart(bugDates, openBugCounts, closedBugCounts);

            let chartPeriodInMs = Date.parse(_.last(bugDates)) - Date.parse(_.first(bugDates));
            let chartPeriodInDays = Math.ceil(chartPeriodInMs / MS_PER_DAY);

            let initialClosedBugCount = _.first(closedBugCounts);
            let currentClosedBugCount = _.last(closedBugCounts);
            let bugsClosed = currentClosedBugCount - initialClosedBugCount;
            let bugsClosedPerDay = (chartPeriodInDays > 0) ? (bugsClosed / chartPeriodInDays) : 0;

            let initialOpenBugCount = _.first(openBugCounts);
            let currentOpenBugCount = _.last(openBugCounts);
            let bugsOpened = currentOpenBugCount - initialOpenBugCount + bugsClosed;
            let bugsOpenedPerDay = (chartPeriodInDays > 0) ? (bugsOpened / chartPeriodInDays) : 0;
            let bugsOpenedAndClosedPerDay = bugsClosedPerDay - bugsOpenedPerDay;

            console.log(`Progress: ${currentClosedBugCount} of ${currentOpenBugCount + currentClosedBugCount} bugs closed = ${formatPercentage(currentClosedBugCount / (currentOpenBugCount + currentClosedBugCount))}`);
            console.log(`Velocity: ${bugsClosed} bugs closed (${initialClosedBugCount} -> ${currentClosedBugCount}) in ${chartPeriodInDays} days = ${roundToTwoDecimals(bugsClosedPerDay)} bugs closed per day`);
            console.log(`Velocity: ${bugsOpened} bugs opened (${initialOpenBugCount} -> ${currentOpenBugCount + bugsClosed}) / ${chartPeriodInDays} days = ${roundToTwoDecimals(bugsOpenedPerDay)} bugs opened per day`);

            let newBugCount = currentOpenBugCount + currentClosedBugCount - initialOpenBugCount;
            console.log(`Scope creep: ${initialOpenBugCount} open bugs -> ${currentOpenBugCount + bugsClosed} open + closed bugs = ${formatPercentage((currentOpenBugCount + bugsClosed) / initialOpenBugCount)}`);

            //logForecast("Old best case forecast", bugsClosedPerDay);
            //logForecast("Old worst case forecast", bugsOpenedAndClosedPerDay);

            logForecast("FISSION FORECAST: Optimistic", 2.5);
            logForecast("FISSION FORECAST: Pessimistic", 1.5);

            function formatPercentage(percentage) {
              return `${Math.round(percentage * 100)}%`;
            }

            function roundToTwoDecimals(f) {
              return Math.floor(f * 100) / 100;
            }

            function logForecast(desc, bugsClosedPerDay) {
              if (bugsClosedPerDay > 0) {
                let daysToZeroOpenBugs = Math.ceil(currentOpenBugCount / bugsClosedPerDay);
                let msToZeroOpenBugs = daysToZeroOpenBugs * MS_PER_DAY;
                let dateOfZeroOpenBugs = new Date(Date.now() + msToZeroOpenBugs);
                let ymdOfZeroOpenBugs = yyyy_mm_dd(dateOfZeroOpenBugs);
                console.log(`${desc}: ${currentOpenBugCount} open bugs / ${roundToTwoDecimals(bugsClosedPerDay)} bugs closed per day = ${daysToZeroOpenBugs} days -> ${ymdOfZeroOpenBugs} (Nightly ${getForecastedNightlyVersion(dateOfZeroOpenBugs)})`);
              } else {
                console.log(`${desc}: ${currentOpenBugCount} open bugs / ${roundToTwoDecimals(bugsClosedPerDay)} bugs closed per day -> Infinity`);
              }
            }
        });
    }

    const PRODUCT_FILTER = "&classification=Client%20Software&classification=Components&product=DevTools&product=Firefox&product=Core&product=Testing&product=Toolkit&product=WebExtensions";
    let bugzillaQuery = queryString + PRODUCT_FILTER;
    searchAndPlotBugs(bugzillaQuery);

    const title = queryString.split("&").join(", ");
    document.title = `Burning up: ${title}`;
})(this);
<commit_msg>Remove Fission log messages<commit_after>;((exports) => {
    "use strict";

    // https://mozilla.design/firefox/color/
    const FIREFOX_YELLOW_6 = "#FFA537";
    const FIREFOX_BLUE_4 = "#00B3F5";

    const MS_PER_DAY = 24 * 60 * 60 * 1000;
    const MS_PER_WEEK = 7 * MS_PER_DAY;
    const MS_PER_MONTH = 4 * MS_PER_WEEK;

    function getForecastedNightlyVersion(forecastDate) {
        const NIGHTLY_FREEZE_DATES = [
            makeFreezeDate(90, "2021-05-27"),
            makeFreezeDate(91, "2021-07-08"),
            makeFreezeDate(92, "2021-08-05"),
            makeFreezeDate(93, "2021-09-02"),
            makeFreezeDate(94, "2021-09-30"),
            makeFreezeDate(95, "2021-10-28"),
            makeFreezeDate(96, "2021-12-02"),
            makeFreezeDate(97, "2021-01-06"),
            makeFreezeDate(98, "2022-02-03"),
            makeFreezeDate(99, "2022-03-03"),
        ];

        let forecastTime = forecastDate.getTime();
        for (const nightly of NIGHTLY_FREEZE_DATES) {
            if (forecastTime <= nightly.freezeTime) {
                return nightly.version;
            }
        }

        return "2022? 😵";

      function makeFreezeDate(nightlyVersion, freezeYMD) {
          return {version: nightlyVersion, freezeTime: new Date(freezeYMD).getTime()};
      }
    }

    const DEBUG = true;
    function debug(...args) { DEBUG && console.debug(...args); }

    function days(d) { return d * MS_PER_DAY; }
    function weeks(w) { return days(7 * w); }
    function months(m) { return weeks(4 * m); }

    const queryString = getQueryString();
    const searchParams = parseQueryString(queryString);
    const chartStartDate = getChartStartDate();

    function getQueryString() {
        // e.g. "?foo=bar&baz=qux&/"
        let qs = window.location.search;
        if (qs.length <= 1) {
            return "";
        }
        const slash = (qs[qs.length - 1] === '/') ? -1 : undefined;
        return qs.slice(1, slash);
    }

    function parseQueryString(qs) {
        // e.g. "foo=bar&baz=qux&"
        const kvs = {};
        const params = qs.split("&");
        for (let kv of params) {
            kv = kv.split("=", 2);
            const key = kv[0].toLowerCase();
            if (key.length === 0) {
                continue; // "&&"
            }
            const value = (kv.length > 1) ? decodeURIComponent(kv[1]) : "";
            kvs[key] = value;
        }
        return kvs;
    }

    function getChartStartDate() {
      const CHART_START_PERIOD = months(3);
      return (searchParams.burnup_since || searchParams.since) ||
             yyyy_mm_dd(new Date(Date.now() - CHART_START_PERIOD));
    }

    function getElementValue(id) {
        return document.getElementById(id).value;
    }

    function yyyy_mm_dd(date) {
        return date.toISOString().slice(0,10);
    }

    function drawChart(bugDates, openBugCounts, closedBugCounts) {
        bb.generate({
            data: {
                xs: {
                    "openBugCounts": "bugDates",
                    "closedBugCounts": "bugDates",
                },
                columns: [
                    ["bugDates", ...bugDates],
                    ["closedBugCounts", ...closedBugCounts],
                    ["openBugCounts", ...openBugCounts],
                ],
                names: {
                    "openBugCounts": "Open Bugs",
                    "closedBugCounts": "Closed Bugs",
                },
                types: {
                    "openBugCounts": "area",
                    "closedBugCounts": "area",
                },
                colors: {
                    "openBugCounts": FIREFOX_YELLOW_6,
                    "closedBugCounts": FIREFOX_BLUE_4,
                },
                groups: [["openBugCounts", "closedBugCounts"]],
                order: null,
            },
            axis: {
                x: {
                    type: "timeseries",
                    tick: {format: "%Y-%m-%d"},
                }
            },
        });
    }

    function createElement(tag, child) {
        const element = document.createElement(tag);
        if (typeof child !== "undefined") {
            if (typeof child !== "object") {
                child = document.createTextNode(child.toString());
            }
            element.appendChild(child);
        }
        return element;
    }

    function createLink(text, url) {
        const element = createElement("a", text);
        element.setAttribute("href", url);
        return element;
    }

    function setErrorText(msg) {
        let chart = document.getElementById("chart");
        chart.innerText = msg;
    }

    function searchAndPlotBugs(bugzillaQuery) {
        const t0 = Date.now();
        debug(`searchAndPlotBugs: ${bugzillaQuery}`);
        if (!bugzillaQuery) {
            setErrorText("🙈 Zarro boogs found");
            return;
        }

        $bugzilla.searchBugs(bugzillaQuery, (error, bugs) => {
            const t1 = Date.now();
            debug(`searchAndPlotBugs: ${t1 - t0} ms`);

            if (error) {
                setErrorText(`🤮 ${error.type}`);
                return;
            }

            if (bugs.length === 0) {
                setErrorText("🙈 Zarro boogs found");
                return;
            }

            let bugActivity = {};

            function openedBugOn(date) {
                let bugDate = bugActivity[date];
                if (bugDate) {
                    bugDate.opened++;
                } else { // is undefined
                    bugActivity[date] = {date: date, opened: 1, closed: 0};
                }
            }

            function closedBugOn(date) {
                let bugDate = bugActivity[date];
                if (bugDate) {
                    bugDate.closed++;
                } else { // is undefined
                    bugActivity[date] = {date: date, opened: 0, closed: 1};
                }
            }

            const bugList = document.getElementById("bugs");
            let bugListURL = `https://bugzilla.mozilla.org/buglist.cgi?bug_id=`;

            for (let bug of bugs) {
                let openDate = yyyy_mm_dd(bug.creationTime);
                if (openDate < chartStartDate) {
                    openDate = chartStartDate;
                }
                openedBugOn(openDate);

                if (bug.open) {
                    const bugURL = $bugzilla.makeURL(bug.id);
                    const bugRow = createElement("div");
                    bugRow.appendChild(createLink(`bug ${bug.id} - ${bug.summary}`, bugURL));
                    bugList.appendChild(bugRow);
                    bugListURL += `${bug.id},`;
                } else {
                    let closedDate = yyyy_mm_dd(bug.resolutionTime);
                    if (closedDate < chartStartDate) {
                        closedDate = chartStartDate;
                    }
                    closedBugOn(closedDate);
                }
            }

            const openLink = createLink("Open bug list in Bugzilla", bugListURL);
            openLink.classList.add('open-bugzilla');
            bugList.appendChild(openLink);

            let bugDates = [];
            let openBugCounts = [];
            let closedBugCounts = [];

            let openBugCount = 0;
            let closedBugCount = 0;

            bugActivity = _.sortBy(bugActivity, "date");
            for (let {date, opened, closed} of bugActivity) {
                bugDates.push(date);

                openBugCount += opened - closed;
                openBugCounts.push(openBugCount);

                // Don't display bugs closed before the start date.
                if (closedBugCounts.length == 0 &&
                    searchParams.burnup_ignore_old_closed_bugs !== undefined) {
                  closedBugCounts.push(closedBugCount);
                } else {
                  closedBugCount += closed;
                  closedBugCounts.push(closedBugCount);
                }
            }

            // Extend last bug count to today, so burndown ends on today.
            const today = yyyy_mm_dd(new Date());
            if (bugDates.length > 0 && _.last(bugDates) < today) {
                bugDates.push(today);
                openBugCounts.push(openBugCount);
                closedBugCounts.push(closedBugCount);
            }

            drawChart(bugDates, openBugCounts, closedBugCounts);

            let chartPeriodInMs = Date.parse(_.last(bugDates)) - Date.parse(_.first(bugDates));
            let chartPeriodInDays = Math.ceil(chartPeriodInMs / MS_PER_DAY);

            let initialClosedBugCount = _.first(closedBugCounts);
            let currentClosedBugCount = _.last(closedBugCounts);
            let bugsClosed = currentClosedBugCount - initialClosedBugCount;
            let bugsClosedPerDay = (chartPeriodInDays > 0) ? (bugsClosed / chartPeriodInDays) : 0;

            let initialOpenBugCount = _.first(openBugCounts);
            let currentOpenBugCount = _.last(openBugCounts);
            let bugsOpened = currentOpenBugCount - initialOpenBugCount + bugsClosed;
            let bugsOpenedPerDay = (chartPeriodInDays > 0) ? (bugsOpened / chartPeriodInDays) : 0;
            let bugsOpenedAndClosedPerDay = bugsClosedPerDay - bugsOpenedPerDay;

            console.log(`Progress: ${currentClosedBugCount} of ${currentOpenBugCount + currentClosedBugCount} bugs closed = ${formatPercentage(currentClosedBugCount / (currentOpenBugCount + currentClosedBugCount))}`);
            console.log(`Velocity: ${bugsClosed} bugs closed (${initialClosedBugCount} -> ${currentClosedBugCount}) in ${chartPeriodInDays} days = ${roundToTwoDecimals(bugsClosedPerDay)} bugs closed per day`);
            console.log(`Velocity: ${bugsOpened} bugs opened (${initialOpenBugCount} -> ${currentOpenBugCount + bugsClosed}) / ${chartPeriodInDays} days = ${roundToTwoDecimals(bugsOpenedPerDay)} bugs opened per day`);

            let newBugCount = currentOpenBugCount + currentClosedBugCount - initialOpenBugCount;
            console.log(`Scope creep: ${initialOpenBugCount} open bugs -> ${currentOpenBugCount + bugsClosed} open + closed bugs = ${formatPercentage((currentOpenBugCount + bugsClosed) / initialOpenBugCount)}`);

            logForecast("Optimistic forecast", bugsClosedPerDay);
            logForecast("Pessimistic forecast", bugsOpenedAndClosedPerDay);

            function formatPercentage(percentage) {
              return `${Math.round(percentage * 100)}%`;
            }

            function roundToTwoDecimals(f) {
              return Math.floor(f * 100) / 100;
            }

            function logForecast(desc, bugsClosedPerDay) {
              if (bugsClosedPerDay > 0) {
                let daysToZeroOpenBugs = Math.ceil(currentOpenBugCount / bugsClosedPerDay);
                let msToZeroOpenBugs = daysToZeroOpenBugs * MS_PER_DAY;
                let dateOfZeroOpenBugs = new Date(Date.now() + msToZeroOpenBugs);
                let ymdOfZeroOpenBugs = yyyy_mm_dd(dateOfZeroOpenBugs);
                console.log(`${desc}: ${currentOpenBugCount} open bugs / ${roundToTwoDecimals(bugsClosedPerDay)} bugs closed per day = ${daysToZeroOpenBugs} days -> ${ymdOfZeroOpenBugs} (Nightly ${getForecastedNightlyVersion(dateOfZeroOpenBugs)})`);
              } else {
                console.log(`${desc}: ${currentOpenBugCount} open bugs / ${roundToTwoDecimals(bugsClosedPerDay)} bugs closed per day -> Infinity`);
              }
            }
        });
    }

    const PRODUCT_FILTER = "&classification=Client%20Software&classification=Components&product=DevTools&product=Firefox&product=Core&product=Testing&product=Toolkit&product=WebExtensions";
    let bugzillaQuery = queryString + PRODUCT_FILTER;
    searchAndPlotBugs(bugzillaQuery);

    const title = queryString.split("&").join(", ");
    document.title = `Burning up: ${title}`;
})(this);
<|endoftext|>


<commit_before>/**
 * @license
 * Latitude/longitude spherical geodesy formulae taken from
 * http://www.movable-type.co.uk/scripts/latlong.html
 * Licensed under CC-BY-3.0.
 */

goog.provide('ol.Sphere');

goog.require('ol.math');

/**
 * @classdesc
 * Class to create objects that can be used with {@link
 * ol.geom.Polygon.circular}.
 *
 * For example to create a sphere whose radius is equal to the semi-major
 * axis of the WGS84 ellipsoid:
 *
 * ```js
 * var wgs84Sphere= new ol.Sphere(6378137);
 * ```
 *
 * @constructor
 * @param {number} radius Radius.
 * @api
 */
ol.Sphere = function(radius) {

  /**
   * @type {number}
   */
  this.radius = radius;

};

/**
 * Returns the geodesic area for a list of coordinates.
 *
 * [Reference](http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409)
 * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
 * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
 * Laboratory, Pasadena, CA, June 2007
 *
 * @param {Array.<ol.Coordinate>} coordinates List of coordinates of a linear
 * ring. If the ring is oriented clockwise, the area will be positive,
 * otherwise it will be negative.
 * @return {number} Area.
 * @api
 */
ol.Sphere.prototype.geodesicArea = function(coordinates) {
  var area = 0, len = coordinates.length;
  var x1 = coordinates[len - 1][0];
  var y1 = coordinates[len - 1][1];
  for (var i = 0; i < len; i++) {
    var x2 = coordinates[i][0], y2 = coordinates[i][1];
    area += ol.math.toRadians(x2 - x1) *
        (2 + Math.sin(ol.math.toRadians(y1)) +
        Math.sin(ol.math.toRadians(y2)));
    x1 = x2;
    y1 = y2;
  }
  return area * this.radius * this.radius / 2.0;
};

/**
 * Returns the distance from c1 to c2 using the haversine formula.
 *
 * @param {ol.Coordinate} c1 Coordinate 1.
 * @param {ol.Coordinate} c2 Coordinate 2.
 * @return {number} Haversine distance.
 * @api
 */
ol.Sphere.prototype.haversineDistance = function(c1, c2) {
  var lat1 = ol.math.toRadians(c1[1]);
  var lat2 = ol.math.toRadians(c2[1]);
  var deltaLatBy2 = (lat2 - lat1) / 2;
  var deltaLonBy2 = ol.math.toRadians(c2[0] - c1[0]) / 2;
  var a = Math.sin(deltaLatBy2) * Math.sin(deltaLatBy2) +
      Math.sin(deltaLonBy2) * Math.sin(deltaLonBy2) *
      Math.cos(lat1) * Math.cos(lat2);
  return 2 * this.radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
};

/**
 * Returns the coordinate at the given distance and bearing from `c1`.
 *
 * @param {ol.Coordinate} c1 The origin point (`[lon, lat]` in degrees).
 * @param {number} distance The great-circle distance between the origin
 *     point and the target point.
 * @param {number} bearing The bearing (in radians).
 * @return {ol.Coordinate} The target point.
 */
ol.Sphere.prototype.offset = function(c1, distance, bearing) {
  var lat1 = ol.math.toRadians(c1[1]);
  var lon1 = ol.math.toRadians(c1[0]);
  var dByR = distance / this.radius;
  var lat = Math.asin(
      Math.sin(lat1) * Math.cos(dByR) +
      Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing));
  var lon = lon1 + Math.atan2(
      Math.sin(bearing) * Math.sin(dByR) * Math.cos(lat1),
      Math.cos(dByR) - Math.sin(lat1) * Math.sin(lat));
  return [ol.math.toDegrees(lon), ol.math.toDegrees(lat)];
};
<commit_msg>Update sphere.js<commit_after>/**
 * @license
 * Latitude/longitude spherical geodesy formulae taken from
 * http://www.movable-type.co.uk/scripts/latlong.html
 * Licensed under CC-BY-3.0.
 */

goog.provide('ol.Sphere');

goog.require('ol.math');

/**
 * @classdesc
 * Class to create objects that can be used with {@link
 * ol.geom.Polygon.circular}.
 *
 * For example to create a sphere whose radius is equal to the semi-major
 * axis of the WGS84 ellipsoid:
 *
 * ```js
 * var wgs84Sphere= new ol.Sphere(6378137);
 * ```
 *
 * @constructor
 * @param {number} radius Radius.
 * @api
 */
ol.Sphere = function(radius) {

  /**
   * @type {number}
   */
  this.radius = radius;

};

/**
 * Returns the geodesic area for a list of coordinates.
 *
 * [Reference](https://trs-new.jpl.nasa.gov/handle/2014/40409)
 * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
 * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
 * Laboratory, Pasadena, CA, June 2007
 *
 * @param {Array.<ol.Coordinate>} coordinates List of coordinates of a linear
 * ring. If the ring is oriented clockwise, the area will be positive,
 * otherwise it will be negative.
 * @return {number} Area.
 * @api
 */
ol.Sphere.prototype.geodesicArea = function(coordinates) {
  var area = 0, len = coordinates.length;
  var x1 = coordinates[len - 1][0];
  var y1 = coordinates[len - 1][1];
  for (var i = 0; i < len; i++) {
    var x2 = coordinates[i][0], y2 = coordinates[i][1];
    area += ol.math.toRadians(x2 - x1) *
        (2 + Math.sin(ol.math.toRadians(y1)) +
        Math.sin(ol.math.toRadians(y2)));
    x1 = x2;
    y1 = y2;
  }
  return area * this.radius * this.radius / 2.0;
};

/**
 * Returns the distance from c1 to c2 using the haversine formula.
 *
 * @param {ol.Coordinate} c1 Coordinate 1.
 * @param {ol.Coordinate} c2 Coordinate 2.
 * @return {number} Haversine distance.
 * @api
 */
ol.Sphere.prototype.haversineDistance = function(c1, c2) {
  var lat1 = ol.math.toRadians(c1[1]);
  var lat2 = ol.math.toRadians(c2[1]);
  var deltaLatBy2 = (lat2 - lat1) / 2;
  var deltaLonBy2 = ol.math.toRadians(c2[0] - c1[0]) / 2;
  var a = Math.sin(deltaLatBy2) * Math.sin(deltaLatBy2) +
      Math.sin(deltaLonBy2) * Math.sin(deltaLonBy2) *
      Math.cos(lat1) * Math.cos(lat2);
  return 2 * this.radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
};

/**
 * Returns the coordinate at the given distance and bearing from `c1`.
 *
 * @param {ol.Coordinate} c1 The origin point (`[lon, lat]` in degrees).
 * @param {number} distance The great-circle distance between the origin
 *     point and the target point.
 * @param {number} bearing The bearing (in radians).
 * @return {ol.Coordinate} The target point.
 */
ol.Sphere.prototype.offset = function(c1, distance, bearing) {
  var lat1 = ol.math.toRadians(c1[1]);
  var lon1 = ol.math.toRadians(c1[0]);
  var dByR = distance / this.radius;
  var lat = Math.asin(
      Math.sin(lat1) * Math.cos(dByR) +
      Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing));
  var lon = lon1 + Math.atan2(
      Math.sin(bearing) * Math.sin(dByR) * Math.cos(lat1),
      Math.cos(dByR) - Math.sin(lat1) * Math.sin(lat));
  return [ol.math.toDegrees(lon), ol.math.toDegrees(lat)];
};
<|endoftext|>


<commit_before>/**
 * Operative
 * ---
 * Operative is a small JS utility for seamlessly creating Web Worker scripts.
 * ---
 * @author James Padolsey http://james.padolsey.com
 * @repo http://github.com/padolsey/operative
 * @license MIT
 */

(function (root, factory) {
	if (typeof window == 'undefined' && self.importScripts) {
		// Exit if operative.js is being loaded as worker (no blob support flow);
		return;
	}

	if (typeof define === 'function' && define.amd) {
		define([], factory);
	} else if (typeof exports === 'object') {
		module.exports = factory();
	} else {
		root.operative = factory();
	}
}(this, function () {

	var hasOwn = {}.hasOwnProperty;

	// Note: This will work only in the built dist:
	// (Otherwise you must explicitly set selfURL to BrowserWorker.js)
	var scripts = document.getElementsByTagName('script');
	var opScript = scripts[scripts.length - 1];
	var opScriptURL = /operative/.test(opScript.src) && opScript.src;

	operative.pool = function(size, module, dependencies) {
		size = 0 | Math.abs(size) || 1;
		var operatives = [];
		var current = 0;

		for (var i = 0; i < size; ++i) {
			operatives.push(operative(module, dependencies));
		}

		return {
			terminate: function() {
				for (var i = 0; i < size; ++i) {
					operatives[i].destroy();
				}
			},
			next: function() {
				current = current + 1 === size ? 0 : current + 1;
				return operatives[current];
			}
		};
	};

	/**
	 * Exposed operative factory
	 */
	function operative(module, dependencies) {

		var getBase = operative.getBaseURL.bind(this);
		var getSelf = operative.getSelfURL.bind(this);

		var OperativeContext = operative.hasWorkerSupport ? operative.Operative.BrowserWorker : operative.Operative.Iframe;

		if (typeof module == 'function') {
			// Allow a single function to be passed.
			var o = new OperativeContext({ main: module }, dependencies, getBase, getSelf);
			var singularOperative = function() {
				return o.api.main.apply(o, arguments);
			};
			singularOperative.transfer = function() {
				return o.api.main.transfer.apply(o, arguments);
			};
			// Copy across exposable API to the returned function:
			for (var i in o.api) {
				if (hasOwn.call(o.api, i)) {
					singularOperative[i] = o.api[i];
				}
			}
			return singularOperative;
		}

		return new OperativeContext(module, dependencies, getBase, getSelf).api;

	}

	// Indicates whether operatives will run within workers:
	operative.hasWorkerSupport = !!window.Worker;
	operative.hasWorkerViaBlobSupport = false;
	operative.hasTransferSupport = false;

	// Default base URL (to be prepended to relative dependency URLs)
	// is current page's parent dir:
	var baseURL = (
		location.protocol + '//' +
		location.hostname +
		(location.port?':'+location.port:'') +
		location.pathname
	).replace(/[^\/]+$/, '');

	/**
	 * Provide Object.create shim
	 */
	operative.objCreate = Object.create || function(o) {
		function F() {}
		F.prototype = o;
		return new F();
	};

	/**
	 * Set and get Self URL, i.e. the url of the
	 * operative script itself.
	 */

	operative.setSelfURL = function(url) {
		opScriptURL = url;
	};

	operative.getSelfURL = function(url) {
		return opScriptURL;
	};

	/**
	 * Set and get Base URL, i.e. the path used
	 * as a base for getting dependencies
	 */

	operative.setBaseURL = function(base) {
		baseURL = base;
	};

	operative.getBaseURL = function() {
		return baseURL;
	};

	return operative;
}));
<commit_msg>Remove pointless bind<commit_after>/**
 * Operative
 * ---
 * Operative is a small JS utility for seamlessly creating Web Worker scripts.
 * ---
 * @author James Padolsey http://james.padolsey.com
 * @repo http://github.com/padolsey/operative
 * @license MIT
 */

(function (root, factory) {
	if (typeof window == 'undefined' && self.importScripts) {
		// Exit if operative.js is being loaded as worker (no blob support flow);
		return;
	}

	if (typeof define === 'function' && define.amd) {
		define([], factory);
	} else if (typeof exports === 'object') {
		module.exports = factory();
	} else {
		root.operative = factory();
	}
}(this, function () {

	var hasOwn = {}.hasOwnProperty;

	// Note: This will work only in the built dist:
	// (Otherwise you must explicitly set selfURL to BrowserWorker.js)
	var scripts = document.getElementsByTagName('script');
	var opScript = scripts[scripts.length - 1];
	var opScriptURL = /operative/.test(opScript.src) && opScript.src;

	operative.pool = function(size, module, dependencies) {
		size = 0 | Math.abs(size) || 1;
		var operatives = [];
		var current = 0;

		for (var i = 0; i < size; ++i) {
			operatives.push(operative(module, dependencies));
		}

		return {
			terminate: function() {
				for (var i = 0; i < size; ++i) {
					operatives[i].destroy();
				}
			},
			next: function() {
				current = current + 1 === size ? 0 : current + 1;
				return operatives[current];
			}
		};
	};

	/**
	 * Exposed operative factory
	 */
	function operative(module, dependencies) {

		var getBase = operative.getBaseURL;
		var getSelf = operative.getSelfURL;

		var OperativeContext = operative.hasWorkerSupport ? operative.Operative.BrowserWorker : operative.Operative.Iframe;

		if (typeof module == 'function') {
			// Allow a single function to be passed.
			var o = new OperativeContext({ main: module }, dependencies, getBase, getSelf);
			var singularOperative = function() {
				return o.api.main.apply(o, arguments);
			};
			singularOperative.transfer = function() {
				return o.api.main.transfer.apply(o, arguments);
			};
			// Copy across exposable API to the returned function:
			for (var i in o.api) {
				if (hasOwn.call(o.api, i)) {
					singularOperative[i] = o.api[i];
				}
			}
			return singularOperative;
		}

		return new OperativeContext(module, dependencies, getBase, getSelf).api;

	}

	// Indicates whether operatives will run within workers:
	operative.hasWorkerSupport = !!window.Worker;
	operative.hasWorkerViaBlobSupport = false;
	operative.hasTransferSupport = false;

	// Default base URL (to be prepended to relative dependency URLs)
	// is current page's parent dir:
	var baseURL = (
		location.protocol + '//' +
		location.hostname +
		(location.port?':'+location.port:'') +
		location.pathname
	).replace(/[^\/]+$/, '');

	/**
	 * Provide Object.create shim
	 */
	operative.objCreate = Object.create || function(o) {
		function F() {}
		F.prototype = o;
		return new F();
	};

	/**
	 * Set and get Self URL, i.e. the url of the
	 * operative script itself.
	 */

	operative.setSelfURL = function(url) {
		opScriptURL = url;
	};

	operative.getSelfURL = function(url) {
		return opScriptURL;
	};

	/**
	 * Set and get Base URL, i.e. the path used
	 * as a base for getting dependencies
	 */

	operative.setBaseURL = function(base) {
		baseURL = base;
	};

	operative.getBaseURL = function() {
		return baseURL;
	};

	return operative;
}));
<|endoftext|>


<commit_before>/*
	Accepts entity changes, the cache and options, returns a stream of notify objects.
	{ user, resource, censored_change, noteType, score }

	Used by:
	- store, to get userIds whose status is lower than "reading".
	- socket, to get current server resourceIDs with status "online" or higher.
	- push, to get users with push ids whose status is lower than "online"
	- email, to get

	Options include filtering by gateway (the prefix of the identity) and minimum score.
*/
var pg = require("./pg.js");

function getNotifyStream(itemId) {
	var query;
	return pg.readStream(connStr, query);
}
module.exports = function notify (changes, cache, options) {
	for (let itemId in changes.entities) {

	}

	return/* Stream object */;
};

/*
	What all triggers notify.
	update/create of entities and relations.
	so we will have to figure out which are the ones to send notification?
	will this result in spliting setstate into multiple setstate? mostly it will be one.
*/<commit_msg>Adding the basic notify function. should generate queries though.<commit_after>/*
	Accepts entity changes, the cache and options, returns a stream of notify objects.
	{ user, resource, censored_change, noteType, score }

	Used by:
	- store, to get userIds whose status is lower than "reading".
	- socket, to get current server resourceIDs with status "online" or higher.
	- push, to get users with push ids whose status is lower than "online"
	- email, to get

	Options include filtering by gateway (the prefix of the identity) and minimum score.
*/
var pg = require("./pg.js");

function getNotifyStream(item) {
	var outStream = new EventEmitter(), pgStream;
	var query = {}, parts = [];
	/*
		construct the query.
	*/

	parts.push({
		$: "select * from relations where "
	});

	pgStream = pg.readStream(connStr, query);

	pgStream.on("end", function (result) {
		outStream.emit("row", row, result);
	});

	pgStream.on("end", function (result) {
		outStream.emit("end", result);
	});
	return outStream;
}
module.exports = function notify (changes, cache, options) {
	var outStreams = [];
	for (let itemId in changes.entities) outStreams.push(getNotifyStream(changes.entities[itemId]));
	return outStreams;
};

/*
	What all triggers notify.
	update/create of entities and relations.
	so we will have to figure out which are the ones to send notification?
	will this result in spliting setstate into multiple setstate? mostly it will be one.
*/<|endoftext|>


<commit_before>const ERR = require('async-stacktrace');
const request = require('request');
const config = require('./config');

module.exports = {};

module.exports.canSendMessages = () => {
    return !!config.secretSlackOpsBotEndpoint;
};

module.exports.sendMessage = (msg, callback) => {
    // No-op if there's no url specified
    if (!module.exports.canSendMessages()) {
        callback(null);
    }

    const options = {
        uri: config.secretSlackOpsBotEndpoint,
        method: 'POST',
        json: {
            text: msg,
        },
    };
    request(options, (err, res, body) => {
        if (ERR(err, callback)) return;
        callback(null, res, body);
    });
};

module.exports.sendProctorMessage = (msg, callback) => {
    // No-op if there's no token specified
    if (!config.secretSlackProctorToken) {
        callback(null);
    }

    const options = {
        uri: 'https://slack.com/api/chat.postMessage',
        method: 'POST',
        json: {
            text: msg,
            token: config.secretSlackProctorToken,
            channel: config.secretSlackProctorChannel,
            as_user: true,
        },
    };
    request(options, (err, res, body) => {
        if (ERR(err, callback)) return;
        callback(null, res, body);
    });
};
<commit_msg>detect errors from slack posting<commit_after>const ERR = require('async-stacktrace');
const request = require('request');

const error = require('@prairielearn/prairielib/error');
const config = require('./config');

module.exports = {};

module.exports.canSendMessages = () => {
    return !!config.secretSlackOpsBotEndpoint;
};

module.exports.sendMessage = (msg, callback) => {
    // No-op if there's no url specified
    if (!module.exports.canSendMessages()) {
        callback(null);
    }

    const options = {
        uri: config.secretSlackOpsBotEndpoint,
        method: 'POST',
        json: {
            text: msg,
        },
    };
    request(options, (err, res, body) => {
        if (ERR(err, callback)) return;
        callback(null, res, body);
    });
};

module.exports.sendProctorMessage = (msg, callback) => {
    // No-op if there's no token specified
    if (!config.secretSlackProctorToken) {
        callback(null);
    }

    const options = {
        uri: 'https://slack.com/api/chat.postMessage',
        method: 'POST',
        json: {
            text: msg,
            token: config.secretSlackProctorToken,
            channel: config.secretSlackProctorChannel,
            as_user: true,
        },
    };
    request(options, (err, res, body) => {
        if (ERR(err, callback)) return;
        if (!body.ok) {
            callback(error.makeWithData('Error sending message to proctors', {body}));
            return;
        }
        callback(null, res, body);
    });
};
<|endoftext|>


<commit_before>/*
 * Optics / option.js
 * copyright (c) 2016 Susisu
 */

/**
 * @module option
 */

"use strict";

function endModule() {
    module.exports = Object.freeze({
        OptionArgument,
        OptionalOptionArgument,
        Option
    });
}

/**
 * An instance of `OptionArgument` represents an argument of option.
 * @static
 */
class OptionArgument {
    /**
     * Creates a new `OptionArgument` instance.
     * @param {string} name A string used in placeholder.
     * @param {Function} reader A function which reads value given as argument.
     * @throws {TypeError} `name` is not a string.
     * @throws {TypeError} `reader` is not a function.
     */
    constructor(name, reader) {
        if (typeof name !== "string") {
            throw new TypeError("name must be a string");
        }
        if (typeof reader !== "function") {
            throw new TypeError("reader must be a function");
        }
        this.name   = name;
        this.reader = reader;
    }

    /**
     * Returns whether the argument is required.
     * This method always returns `true`.
     * @returns {boolean} `true`.
     */
    isRequired() {
        return true;
    }

    /**
     * Returns a string used as a placeholder of the argument of a short-named option.
     * @returns {string} A string used as a placeholder.
     */
    toShortPlaceholder() {
        return this.name;
    }

    /**
     * Returns a string used as a placeholder of the argument of a long-named option.
     * @returns {string} A string used as a placeholder.
     */
    toLongPlaceholder() {
        return `=${this.name}`;
    }

    /**
     * Returns the default value, which is used when the argument is not specified.
     * @throws {Error} No default value for a requried argument.
     */
    getDefaultValue() {
        throw new Error("no default value");
    }
}

/**
 * An instance of `OptionalOptionArgument` represents an optional argument of option.
 * @static
 * @extends {module:option.OptionArgument}
 */
class OptionalOptionArgument extends OptionArgument {
    /**
     * Creates a new `OptionalOptionArgument` instance.
     * @param {string} name A string used in placeholder.
     * @param defaultVal The default value, which is used when the argument is not specified.
     * @param {Function} reader A function which reads value given as argument.
     */
    constructor(name, defaultVal, reader) {
        super(name, reader);
        this.defaultVal = defaultVal;
    }

    /**
     * Returns whether the argument is required.
     * This method always returns `false`.
     * @returns {boolean} `false`.
     */
    isRequired() {
        return false;
    }

    /**
     * Returns a string used as a placeholder of the argument of a short-named option.
     * @returns {string} A string used as a placeholder.
     */
    toShortPlaceholder() {
        return `[${this.name}]`;
    }

    /**
     * Returns a string used as a placeholder of the argument of a long-named option.
     * @returns {string} A string used as a placeholder.
     */
    toLongPlaceholder() {
        return `[=${this.name}]`;
    }

    /**
     * Returns the default value, which is used when the argument is not specified.
     * @returns The default value specified at the constructor.
     */
    getDefaultValue() {
        return this.defaultVal;
    }
}

/**
 * An instance of `Option` represents a command-line option of command.
 * @static
 */
class Option {
    /**
     * Creates an new instance of `Option`
     * @param {(string | undefined)} shortName Short name of the option.
     * @param {(string | undefined)} longName Long name of the option.
     * @param {(module:option.OptionArgument | null)} arg Argument of the option.
     * If `null` is specified, the option has no argument.
     * @param {string} desc Description of the option.
     * @throws {TypeError} `shortName` is not a string nor undefined.
     * @throws {Error} The length of `shortName` is not 1.
     * @throws {TypeError} `longName` is not a string nor undefined.
     * @throws {Error} The length of `longName` is 0.
     * @throws {Error} Both `shortName` and `longName` are undefined.
     * @throws {TypeError} `arg` is not an instance of OptionArgument nor null.
     * @throws {TypeError} `desc` is not a string.
     */
    constructor(shortName, longName, arg, desc) {
        if (typeof shortName !== "string" && typeof shortName !== "undefined") {
            throw new TypeError("shortName must be a string or undefined");
        }
        if (typeof shortName === "string" && shortName.length !== 1) {
            throw new Error("the length of shortName must be 1");
        }
        if (typeof longName !== "string" && typeof longName !== "undefined") {
            throw new TypeError("longName must be a string or undefined")
        }
        if (typeof longName === "string" && longName.length === 0) {
            throw new Error("the length of longName must be greater than 0");
        }
        if (typeof shortName === "undefined" && typeof longName === "undefined") {
            throw new Error("one of shortName or longName must be specified");
        }
        if (!(arg instanceof OptionArgument) && arg !== null) {
            throw new TypeError("arg must be an instance of OptionArgument or null");
        }
        if (typeof desc !== "string") {
            throw new TypeError("desc must be a string");
        }
        this.shortName = shortName;
        this.longName  = longName;
        this.arg       = arg;
        this.desc      = desc;
    }
}

endModule();
<commit_msg>Add SpecialOption class<commit_after>/*
 * Optics / option.js
 * copyright (c) 2016 Susisu
 */

/**
 * @module option
 */

"use strict";

function endModule() {
    module.exports = Object.freeze({
        OptionArgument,
        OptionalOptionArgument,
        Option,
        SpecialOption
    });
}

/**
 * An instance of `OptionArgument` represents an argument of option.
 * @static
 */
class OptionArgument {
    /**
     * Creates a new `OptionArgument` instance.
     * @param {string} name A string used in placeholder.
     * @param {Function} reader A function which reads value given as argument.
     * @throws {TypeError} `name` is not a string.
     * @throws {TypeError} `reader` is not a function.
     */
    constructor(name, reader) {
        if (typeof name !== "string") {
            throw new TypeError("name must be a string");
        }
        if (typeof reader !== "function") {
            throw new TypeError("reader must be a function");
        }
        this.name   = name;
        this.reader = reader;
    }

    /**
     * Returns whether the argument is required.
     * This method always returns `true`.
     * @returns {boolean} `true`.
     */
    isRequired() {
        return true;
    }

    /**
     * Returns a string used as a placeholder of the argument of a short-named option.
     * @returns {string} A string used as a placeholder.
     */
    toShortPlaceholder() {
        return this.name;
    }

    /**
     * Returns a string used as a placeholder of the argument of a long-named option.
     * @returns {string} A string used as a placeholder.
     */
    toLongPlaceholder() {
        return `=${this.name}`;
    }

    /**
     * Returns the default value, which is used when the argument is not specified.
     * @throws {Error} No default value for a requried argument.
     */
    getDefaultValue() {
        throw new Error("no default value");
    }
}

/**
 * An instance of `OptionalOptionArgument` represents an optional argument of option.
 * @static
 * @extends {module:option.OptionArgument}
 */
class OptionalOptionArgument extends OptionArgument {
    /**
     * Creates a new `OptionalOptionArgument` instance.
     * @param {string} name A string used in placeholder.
     * @param defaultVal The default value, which is used when the argument is not specified.
     * @param {Function} reader A function which reads value given as argument.
     */
    constructor(name, defaultVal, reader) {
        super(name, reader);
        this.defaultVal = defaultVal;
    }

    /**
     * Returns whether the argument is required.
     * This method always returns `false`.
     * @returns {boolean} `false`.
     */
    isRequired() {
        return false;
    }

    /**
     * Returns a string used as a placeholder of the argument of a short-named option.
     * @returns {string} A string used as a placeholder.
     */
    toShortPlaceholder() {
        return `[${this.name}]`;
    }

    /**
     * Returns a string used as a placeholder of the argument of a long-named option.
     * @returns {string} A string used as a placeholder.
     */
    toLongPlaceholder() {
        return `[=${this.name}]`;
    }

    /**
     * Returns the default value, which is used when the argument is not specified.
     * @returns The default value specified at the constructor.
     */
    getDefaultValue() {
        return this.defaultVal;
    }
}

/**
 * An instance of `Option` represents a command-line option of command.
 * @static
 */
class Option {
    /**
     * Creates an new instance of `Option`
     * @param {(string | undefined)} shortName Short name of the option.
     * @param {(string | undefined)} longName Long name of the option.
     * @param {(module:option.OptionArgument | null)} arg Argument of the option.
     * If `null` is specified, the option has no argument.
     * @param {string} desc Description of the option.
     * @throws {TypeError} `shortName` is not a string nor undefined.
     * @throws {Error} The length of `shortName` is not 1.
     * @throws {TypeError} `longName` is not a string nor undefined.
     * @throws {Error} The length of `longName` is 0.
     * @throws {Error} Both `shortName` and `longName` are undefined.
     * @throws {TypeError} `arg` is not an instance of OptionArgument nor null.
     * @throws {TypeError} `desc` is not a string.
     */
    constructor(shortName, longName, arg, desc) {
        if (typeof shortName !== "string" && typeof shortName !== "undefined") {
            throw new TypeError("shortName must be a string or undefined");
        }
        if (typeof shortName === "string" && shortName.length !== 1) {
            throw new Error("the length of shortName must be 1");
        }
        if (typeof longName !== "string" && typeof longName !== "undefined") {
            throw new TypeError("longName must be a string or undefined")
        }
        if (typeof longName === "string" && longName.length === 0) {
            throw new Error("the length of longName must be greater than 0");
        }
        if (typeof shortName === "undefined" && typeof longName === "undefined") {
            throw new Error("one of shortName or longName must be specified");
        }
        if (!(arg instanceof OptionArgument) && arg !== null) {
            throw new TypeError("arg must be an instance of OptionArgument or null");
        }
        if (typeof desc !== "string") {
            throw new TypeError("desc must be a string");
        }
        this.shortName = shortName;
        this.longName  = longName;
        this.arg       = arg;
        this.desc      = desc;
    }
}

/**
 * An instance of the `SpecialOption` class represents a special option.
 * The action associated to a special option is invoked on parsing command-line arguments,
 * and can interrupt its process.
 * @static
 */
class SpecialOption extends Option {
    /**
     * Creates a new instance of `SpecialOption`.
     * @param {(string | undefined)} shortName Short name of the option.
     * @param {(string | undefined)} longName Long name of the option.
     * @param {(module:option.OptionArgument | null)} arg Argument of the option.
     * If `null` is specified, the option has no argument.
     * @param {string} desc Description of the option.
     * @param {Function} action A function associated to the option.
     */
    constructor(shortName, longName, arg, desc, action) {
        super(shortName, longName, arg, desc);
        this.action = action;
    }

    /**
     * Returns if the option is a special option.
     * This method always returns `true`.
     * @returns {boolean} `true`.
     */
    isSpecial() {
        return true;
    }

    /**
     * Invokes the action associated to the option.
     * @param {module:command.CommandBase} cmd The command by which the option is invoked.
     * @param {module:output.Output} out Output target.
     * @param arg Argument given to the option.
     */
    invoke(cmd, out, arg) {
        return this.action.call(this, cmd, out, arg);
    }
}

endModule();
<|endoftext|>


<commit_before>import store from "./store";

store.defineMapper('_initdb', {collection: '_initdb'});
store.findAll('_initdb').then(console.log);

export default store;<commit_msg>Redefine mapper to initdb.<commit_after>import store from "./store";

store.defineMapper('initdb', {collection: 'initdb'});
store.findAll('initdb').then(console.log);

export default store;<|endoftext|>