<commit_before>import Ember from 'ember';
import DS from 'ember-data';
import { translationMacro as t } from "ember-i18n";
import SortableByPosition from 'ilios/mixins/sortable-by-position';

const { isEmpty, Component, computed, inject, RSVP, ObjectProxy } = Ember;
const { notEmpty, or, not } = computed;
const { service } = inject;
const { all, Promise } = RSVP;
const { PromiseArray } = DS;

export default Component.extend(SortableByPosition, {
  currentUser: service(),
  store: service(),
  i18n: service(),
  tagName: 'section',
  classNameBindings: [':detail-learningmaterials', 'displaySearchBox'],
  subject: null,
  isCourse: false,
  editable: true,
  isSorting: false,
  isSaving: false,
  isManaging: or('isManagingMaterial', 'isManagingMesh'),
  isManagingMaterial: notEmpty('managingMaterial'),
  isManagingMesh: notEmpty('meshMaterial'),
  managingMaterial: null,
  meshMaterial: null,
  isSession: not('isCourse'),
  newButtonTitle: t('general.add'),
  bufferMaterial: null,
  bufferTerms: [],
  totalMaterialsToSave: null,
  currentMaterialsSaved: null,

  isEditing: false,
  type: null,

  displaySearchBox: computed('isManaging', 'isEditing', 'isSorting', {
    get() {
      const isManaging = this.get('isManaging');
      const isEditing = this.get('isEditing');
      const editable = this.get('editable');
      const isSorting = this.get('isSorting');

      return (!isManaging && !isEditing && !isSorting && editable);
    }
  }).readOnly(),

  learningMaterialStatuses: computed(function(){
    var self = this;
    return PromiseArray.create({
      promise: self.get('store').findAll('learning-material-status')
    });
  }),
  learningMaterialUserRoles: computed(function(){
    var self = this;
    return PromiseArray.create({
      promise: self.get('store').findAll('learning-material-user-role')
    });
  }),
  proxyMaterials: computed('subject.learningMaterials.@each.{position}', function(){
    return new Promise(resolve => {
      let materialProxy = ObjectProxy.extend({
        sortTerms: ['name'],
        confirmRemoval: false,
        sortedDescriptors: computed.sort('content.meshDescriptors', 'sortTerms')
      });
      this.get('subject').get('learningMaterials').then(materials => {
        let sortedMaterials = materials.toArray().sort(this.get('positionSortingCallback'));
        resolve(sortedMaterials.map(material => {
          return materialProxy.create({
            content: material
          });
        }));
      });
    });
  }),

  parentMaterials: computed('subject.learningMaterials.[]', function(){
    let defer = RSVP.defer();
    this.get('subject.learningMaterials').then(subLms => {
      let promises = [];
      let learningMaterials = [];
      subLms.forEach(lm => {
        promises.pushObject(lm.get('learningMaterial').then(learningMaterial => {
          learningMaterials.pushObject(learningMaterial);
        }));
      });
      RSVP.all(promises).then(()=>{
        defer.resolve(learningMaterials);
      });
    });
    return PromiseArray.create({
      promise: defer.promise
    });
  }),

  hasMoreThanOneLearningMaterial: computed('subject.learningMaterials.[]', function() {
    return new Promise(resolve => {
      this.get('subject').get('learningMaterials').then(materials => {
        resolve(materials.length > 1);
      });
    });
  }),

  saveSomeMaterials(arr){
    let chunk = arr.splice(0, 5);
    return all(chunk.invoke('save')).then(() => {
      if (arr.length){
        this.set('currentMaterialsSaved', this.get('currentMaterialsSaved') + chunk.length);
        return this.saveSomeMaterials(arr);
      }
    });
  },

  actions: {
    manageMaterial(learningMaterial){
      var buffer = Ember.Object.create();
      buffer.set('publicNotes', learningMaterial.get('publicNotes'));
      buffer.set('required', learningMaterial.get('required'));
      buffer.set('notes', learningMaterial.get('notes'));
      learningMaterial.get('learningMaterial').then( parent => {
        parent.get('status').then(status => {
          buffer.set('status',status);
          this.set('bufferMaterial', buffer);
          this.set('managingMaterial', learningMaterial);
        });
      });
    },
    manageDescriptors(learningMaterial){
      learningMaterial.get('meshDescriptors').then(descriptors => {
        this.set('bufferTerms', descriptors.toArray());
        this.set('meshMaterial', learningMaterial);
      });
    },
    save(){
      if(this.get('isManagingMaterial')){
        let buffer = this.get('bufferMaterial');
        let learningMaterial = this.get('managingMaterial');
        let promises = [];
        learningMaterial.set('publicNotes', buffer.get('publicNotes'));
        learningMaterial.set('required', buffer.get('required'));
        learningMaterial.set('notes', buffer.get('notes'));
        promises.pushObject(learningMaterial.save());
        promises.pushObject(learningMaterial.get('learningMaterial').then( parent => {
          parent.set('status', buffer.get('status'));
          promises.pushObject(parent.save());
        }));

        Ember.RSVP.all(promises).then(()=> {
          this.set('bufferMaterial', null);
          this.set('managingMaterial', null);
        });
      }

      if(this.get('isManagingMesh')){
        let lm = this.get('meshMaterial');
        let terms = lm .get('meshDescriptors');
        let promises = [];

        let oldTerms = terms.filter(term => {
          return !this.get('bufferTerms').includes(term);
        });
        terms.clear();
        terms.addObjects(this.get('bufferTerms'));
        this.get('bufferTerms').forEach((term)=>{
          if(this.get('isCourse')){
            term.get('courseLearningMaterials').pushObject(lm);
          }
          if(this.get('isSession')){
            term.get('sessionLearningMaterials').pushObject(lm);
          }
        });
        oldTerms.forEach(term => {
          if(this.get('isCourse')){
            term.get('courseLearningMaterials').removeObject(lm);
          }
          if(this.get('isSession')){
            term.get('sessionLearningMaterials').removeObject(lm);
          }
        });
        promises.pushObject(lm.save());
        Ember.RSVP.all(promises).then(()=> {
          this.set('meshMaterial', null);
          this.set('bufferTerms', []);
        });
      }
    },

    cancel() {
      this.setProperties({ bufferMaterial: null, managingMaterial: null, bufferTerms: [], meshMaterial: null });
    },

    addNewLearningMaterial(type){
      this.setProperties({ type, isEditing: true });
    },

    saveNewLearningMaterial(lm) {
      return new Promise(resolve => {
        const store = this.get('store');
        const isCourse = this.get('isCourse');
        const subject = this.get('subject');
        lm.save().then((savedLm) => {
          subject.get('learningMaterials').then(learningMaterials => {
            let lmSubject;
            let position = 0;
            if (! isEmpty(learningMaterials)) {
              position = learningMaterials.toArray().sortBy('position').reverse()[0].get('position') + 1;
            }
            if (isCourse) {
              lmSubject = store.createRecord('course-learning-material', { course: subject, position });
            } else {
              lmSubject = store.createRecord('session-learning-material', { session: subject, position });
            }
            lmSubject.set('learningMaterial', savedLm);
            lmSubject.save().then(() => {
              this.set('isEditing', false);
              resolve();
            });
          });
        });
      });
    },

    saveSortOrder(learningMaterials){
      this.set('isSaving', true);
      for (let i = 0, n = learningMaterials.length; i < n; i++) {
        let lm = learningMaterials[i];
        lm.set('position', i + 1);
      }
      this.set('totalMaterialsToSave', learningMaterials.length);
      this.set('currentMaterialsSaved', 0);

      this.saveSomeMaterials(learningMaterials).then(() => {
        this.set('isSaving', false);
        this.set('isSorting', false);
      });
    },

    cancelSorting() {
      this.set('isSorting', false);
    },

    cancelNewLearningMaterial() {
      this.set('isEditing', false);
    },

    addTermToBuffer(term){
      this.get('bufferTerms').addObject(term);
    },
    removeTermFromBuffer(term){
      this.get('bufferTerms').removeObject(term);
    },
    changeStatus(newStatus){
      this.get('bufferMaterial').set('status', newStatus);
    },
    changeRequired(value){
      this.get('bufferMaterial').set('required', value);
    },
    changePublicNotes(value){
      this.get('bufferMaterial').set('publicNotes', value);
    },
    changeNotes(value){
      this.get('bufferMaterial').set('notes', value);
    },
    addLearningMaterial(parentLearningMaterial){
      return new Promise(resolve => {
        let newLearningMaterial;
        let lmCollectionType;
        let subject = this.get('subject');

        if(this.get('isCourse')){
          newLearningMaterial = this.get('store').createRecord('course-learning-material', {
            course: subject,
            learningMaterial: parentLearningMaterial,
            position: 0,
          });
          lmCollectionType = 'courseLearningMaterials';

        }
        if(this.get('isSession')){
          newLearningMaterial = this.get('store').createRecord('session-learning-material', {
            session: subject,
            learningMaterial: parentLearningMaterial,
            position: 0
          });
          lmCollectionType = 'sessionLearningMaterials';
        }
        subject.get('learningMaterials').then(learningMaterials => {
          let position = 0;
          if (learningMaterials.length > 1) {
            position = learningMaterials.toArray().sortBy('position').reverse()[0].get('position') + 1;
          }
          newLearningMaterial.set('position', position);
          newLearningMaterial.save().then(savedLearningMaterial => {
            parentLearningMaterial.get(lmCollectionType).then(children => {
              children.pushObject(savedLearningMaterial);
              resolve(savedLearningMaterial);
            });
          });
        });
      });
    },
    confirmRemoval(lmProxy){
      lmProxy.set('showRemoveConfirmation', true);
    },
    cancelRemove(lmProxy){
      lmProxy.set('showRemoveConfirmation', false);
    },
    remove(lmProxy){
      let subjectLearningMaterial = lmProxy.get('content');
      let lmCollectionType;
      if(this.get('isCourse')){
        lmCollectionType = 'courseLearningMaterials';
      }
      if(this.get('isSession')){
        lmCollectionType = 'sessionLearningMaterials';
      }
      this.get('subject').get('learningMaterials').then(lms => {
        subjectLearningMaterial.get('learningMaterial').then(parentLearningMaterial => {
          parentLearningMaterial.get(lmCollectionType).then(children => {
            children.removeObject(subjectLearningMaterial);
            lms.removeObject(subjectLearningMaterial);
            subjectLearningMaterial.deleteRecord();
            subjectLearningMaterial.save();
          });
        });
      });
    },
  }
});
<commit_msg>destructured Ember and RSVP.<commit_after>import Ember from 'ember';
import DS from 'ember-data';
import { translationMacro as t } from "ember-i18n";
import SortableByPosition from 'ilios/mixins/sortable-by-position';

const { isEmpty, Component, computed, inject, RSVP, ObjectProxy, Object } = Ember;
const { notEmpty, or, not } = computed;
const { service } = inject;
const { all, Promise } = RSVP;
const { PromiseArray } = DS;

export default Component.extend(SortableByPosition, {
  currentUser: service(),
  store: service(),
  i18n: service(),
  tagName: 'section',
  classNameBindings: [':detail-learningmaterials', 'displaySearchBox'],
  subject: null,
  isCourse: false,
  editable: true,
  isSorting: false,
  isSaving: false,
  isManaging: or('isManagingMaterial', 'isManagingMesh'),
  isManagingMaterial: notEmpty('managingMaterial'),
  isManagingMesh: notEmpty('meshMaterial'),
  managingMaterial: null,
  meshMaterial: null,
  isSession: not('isCourse'),
  newButtonTitle: t('general.add'),
  bufferMaterial: null,
  bufferTerms: [],
  totalMaterialsToSave: null,
  currentMaterialsSaved: null,

  isEditing: false,
  type: null,

  displaySearchBox: computed('isManaging', 'isEditing', 'isSorting', {
    get() {
      const isManaging = this.get('isManaging');
      const isEditing = this.get('isEditing');
      const editable = this.get('editable');
      const isSorting = this.get('isSorting');

      return (!isManaging && !isEditing && !isSorting && editable);
    }
  }).readOnly(),

  learningMaterialStatuses: computed(function(){
    var self = this;
    return PromiseArray.create({
      promise: self.get('store').findAll('learning-material-status')
    });
  }),
  learningMaterialUserRoles: computed(function(){
    var self = this;
    return PromiseArray.create({
      promise: self.get('store').findAll('learning-material-user-role')
    });
  }),
  proxyMaterials: computed('subject.learningMaterials.@each.{position}', function(){
    return new Promise(resolve => {
      let materialProxy = ObjectProxy.extend({
        sortTerms: ['name'],
        confirmRemoval: false,
        sortedDescriptors: computed.sort('content.meshDescriptors', 'sortTerms')
      });
      this.get('subject').get('learningMaterials').then(materials => {
        let sortedMaterials = materials.toArray().sort(this.get('positionSortingCallback'));
        resolve(sortedMaterials.map(material => {
          return materialProxy.create({
            content: material
          });
        }));
      });
    });
  }),

  parentMaterials: computed('subject.learningMaterials.[]', function(){
    let deferred = defer();
    this.get('subject.learningMaterials').then(subLms => {
      let promises = [];
      let learningMaterials = [];
      subLms.forEach(lm => {
        promises.pushObject(lm.get('learningMaterial').then(learningMaterial => {
          learningMaterials.pushObject(learningMaterial);
        }));
      });
      all(promises).then(()=>{
        deferred.resolve(learningMaterials);
      });
    });
    return PromiseArray.create({
      promise: deferred.promise
    });
  }),

  hasMoreThanOneLearningMaterial: computed('subject.learningMaterials.[]', function() {
    return new Promise(resolve => {
      this.get('subject').get('learningMaterials').then(materials => {
        resolve(materials.length > 1);
      });
    });
  }),

  saveSomeMaterials(arr){
    let chunk = arr.splice(0, 5);
    return all(chunk.invoke('save')).then(() => {
      if (arr.length){
        this.set('currentMaterialsSaved', this.get('currentMaterialsSaved') + chunk.length);
        return this.saveSomeMaterials(arr);
      }
    });
  },

  actions: {
    manageMaterial(learningMaterial){
      var buffer = Object.create();
      buffer.set('publicNotes', learningMaterial.get('publicNotes'));
      buffer.set('required', learningMaterial.get('required'));
      buffer.set('notes', learningMaterial.get('notes'));
      learningMaterial.get('learningMaterial').then( parent => {
        parent.get('status').then(status => {
          buffer.set('status',status);
          this.set('bufferMaterial', buffer);
          this.set('managingMaterial', learningMaterial);
        });
      });
    },
    manageDescriptors(learningMaterial){
      learningMaterial.get('meshDescriptors').then(descriptors => {
        this.set('bufferTerms', descriptors.toArray());
        this.set('meshMaterial', learningMaterial);
      });
    },
    save(){
      if(this.get('isManagingMaterial')){
        let buffer = this.get('bufferMaterial');
        let learningMaterial = this.get('managingMaterial');
        let promises = [];
        learningMaterial.set('publicNotes', buffer.get('publicNotes'));
        learningMaterial.set('required', buffer.get('required'));
        learningMaterial.set('notes', buffer.get('notes'));
        promises.pushObject(learningMaterial.save());
        promises.pushObject(learningMaterial.get('learningMaterial').then( parent => {
          parent.set('status', buffer.get('status'));
          promises.pushObject(parent.save());
        }));

        all(promises).then(()=> {
          this.set('bufferMaterial', null);
          this.set('managingMaterial', null);
        });
      }

      if(this.get('isManagingMesh')){
        let lm = this.get('meshMaterial');
        let terms = lm .get('meshDescriptors');
        let promises = [];

        let oldTerms = terms.filter(term => {
          return !this.get('bufferTerms').includes(term);
        });
        terms.clear();
        terms.addObjects(this.get('bufferTerms'));
        this.get('bufferTerms').forEach((term)=>{
          if(this.get('isCourse')){
            term.get('courseLearningMaterials').pushObject(lm);
          }
          if(this.get('isSession')){
            term.get('sessionLearningMaterials').pushObject(lm);
          }
        });
        oldTerms.forEach(term => {
          if(this.get('isCourse')){
            term.get('courseLearningMaterials').removeObject(lm);
          }
          if(this.get('isSession')){
            term.get('sessionLearningMaterials').removeObject(lm);
          }
        });
        promises.pushObject(lm.save());
        all(promises).then(()=> {
          this.set('meshMaterial', null);
          this.set('bufferTerms', []);
        });
      }
    },

    cancel() {
      this.setProperties({ bufferMaterial: null, managingMaterial: null, bufferTerms: [], meshMaterial: null });
    },

    addNewLearningMaterial(type){
      this.setProperties({ type, isEditing: true });
    },

    saveNewLearningMaterial(lm) {
      return new Promise(resolve => {
        const store = this.get('store');
        const isCourse = this.get('isCourse');
        const subject = this.get('subject');
        lm.save().then((savedLm) => {
          subject.get('learningMaterials').then(learningMaterials => {
            let lmSubject;
            let position = 0;
            if (! isEmpty(learningMaterials)) {
              position = learningMaterials.toArray().sortBy('position').reverse()[0].get('position') + 1;
            }
            if (isCourse) {
              lmSubject = store.createRecord('course-learning-material', { course: subject, position });
            } else {
              lmSubject = store.createRecord('session-learning-material', { session: subject, position });
            }
            lmSubject.set('learningMaterial', savedLm);
            lmSubject.save().then(() => {
              this.set('isEditing', false);
              resolve();
            });
          });
        });
      });
    },

    saveSortOrder(learningMaterials){
      this.set('isSaving', true);
      for (let i = 0, n = learningMaterials.length; i < n; i++) {
        let lm = learningMaterials[i];
        lm.set('position', i + 1);
      }
      this.set('totalMaterialsToSave', learningMaterials.length);
      this.set('currentMaterialsSaved', 0);

      this.saveSomeMaterials(learningMaterials).then(() => {
        this.set('isSaving', false);
        this.set('isSorting', false);
      });
    },

    cancelSorting() {
      this.set('isSorting', false);
    },

    cancelNewLearningMaterial() {
      this.set('isEditing', false);
    },

    addTermToBuffer(term){
      this.get('bufferTerms').addObject(term);
    },
    removeTermFromBuffer(term){
      this.get('bufferTerms').removeObject(term);
    },
    changeStatus(newStatus){
      this.get('bufferMaterial').set('status', newStatus);
    },
    changeRequired(value){
      this.get('bufferMaterial').set('required', value);
    },
    changePublicNotes(value){
      this.get('bufferMaterial').set('publicNotes', value);
    },
    changeNotes(value){
      this.get('bufferMaterial').set('notes', value);
    },
    addLearningMaterial(parentLearningMaterial){
      return new Promise(resolve => {
        let newLearningMaterial;
        let lmCollectionType;
        let subject = this.get('subject');

        if(this.get('isCourse')){
          newLearningMaterial = this.get('store').createRecord('course-learning-material', {
            course: subject,
            learningMaterial: parentLearningMaterial,
            position: 0,
          });
          lmCollectionType = 'courseLearningMaterials';

        }
        if(this.get('isSession')){
          newLearningMaterial = this.get('store').createRecord('session-learning-material', {
            session: subject,
            learningMaterial: parentLearningMaterial,
            position: 0
          });
          lmCollectionType = 'sessionLearningMaterials';
        }
        subject.get('learningMaterials').then(learningMaterials => {
          let position = 0;
          if (learningMaterials.length > 1) {
            position = learningMaterials.toArray().sortBy('position').reverse()[0].get('position') + 1;
          }
          newLearningMaterial.set('position', position);
          newLearningMaterial.save().then(savedLearningMaterial => {
            parentLearningMaterial.get(lmCollectionType).then(children => {
              children.pushObject(savedLearningMaterial);
              resolve(savedLearningMaterial);
            });
          });
        });
      });
    },
    confirmRemoval(lmProxy){
      lmProxy.set('showRemoveConfirmation', true);
    },
    cancelRemove(lmProxy){
      lmProxy.set('showRemoveConfirmation', false);
    },
    remove(lmProxy){
      let subjectLearningMaterial = lmProxy.get('content');
      let lmCollectionType;
      if(this.get('isCourse')){
        lmCollectionType = 'courseLearningMaterials';
      }
      if(this.get('isSession')){
        lmCollectionType = 'sessionLearningMaterials';
      }
      this.get('subject').get('learningMaterials').then(lms => {
        subjectLearningMaterial.get('learningMaterial').then(parentLearningMaterial => {
          parentLearningMaterial.get(lmCollectionType).then(children => {
            children.removeObject(subjectLearningMaterial);
            lms.removeObject(subjectLearningMaterial);
            subjectLearningMaterial.deleteRecord();
            subjectLearningMaterial.save();
          });
        });
      });
    },
  }
});
<|endoftext|>


<commit_before>import * as _ from 'lodash';
import { ObjectId } from 'mongodb';

import { extractProjection, resolveProjection } from './core/projection';
import Tyr from './tyr';
import Path from './core/path';

export const metaRegex = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g;

export function setFalse(v) {
  return v !== undefined && !v;
}

export function escapeRegex(str) {
  return str.replace(metaRegex, '\\$&');
}

export function pathAdd(path, add) {
  return path ? path + '.' + add : add;
}

export function isArrow(fn) {
  return /^[a-zA-Z0-9,_\s]*=>/.test(fn.toString());
}

export function hasMongoUpdateOperator(update) {
  for (const key in update) {
    if (key.startsWith('$') && update.hasOwnProperty(key)) {
      return true;
    }
  }

  //return undefined;
}

export function extractUpdateFields(doc, opts) {
  const updateFields = {};

  const projection = extractProjection(opts);
  if (projection) {
    _.each(projection, (field, key) => {
      if (field) {
        updateFields[key] = 1;
      }
    });
  } else {
    _.each(doc, (field, key) => {
      if (key !== '_id') {
        updateFields[key] = 1;
      }
    });
  }

  return updateFields;
}

// Options parsing
// ===============

export function isOptions(opts) {
  // TODO:  this is a hack, need to figure out a better way (though most likely a non-issue in practice)
  return (
    opts &&
    (opts.asOf !== undefined ||
      opts.auth !== undefined ||
      opts.author !== undefined ||
      opts.comment !== undefined ||
      opts.fields !== undefined ||
      opts.historical !== undefined ||
      opts.limit !== undefined ||
      opts.multi !== undefined ||
      opts.parallel !== undefined ||
      opts.perm !== undefined ||
      opts.populate !== undefined ||
      opts.projection !== undefined ||
      opts.query !== undefined ||
      opts.skip !== undefined ||
      opts.timestamps !== undefined ||
      opts.tyranid !== undefined ||
      opts.upsert !== undefined ||
      opts.writeConcern !== undefined ||
      !_.keys(opts).length)
  );
}

export function processOptions(collection, opts) {
  if (opts) {
    const fields = extractProjection(opts);

    if (fields) {
      const f = resolveProjection(collection.def.projections, fields);

      if (f !== fields) {
        const newOpts = {
          ...opts,
          projection: f
        };
        delete newOpts.fields;
        return newOpts;
      }
    }

    return opts;
  } else {
    return {};
  }
}

export function extractOptions(collection, args) {
  if (args.length && isOptions(args[args.length - 1])) {
    return processOptions(collection, args.pop());
  } else {
    return {};
  }
}

/**
 * This functions tries to avoid creating a new option object unless it has to.
 */
export function combineOptions(...sources) {
  let first;
  let composite;
  for (const source of sources) {
    if (source) {
      if (composite) {
        _.assign(composite, source);
      } else if (first) {
        composite = {};
        _.assign(composite, first);
        _.assign(composite, source);
      } else {
        first = source;
      }
    }
  }
  return composite || first;
}

/**
 * Extracts the authorization out of a mongodb options-style object.
 */
export function extractAuthorization(opts) {
  if (!opts) {
    return undefined;
  }

  const auth = opts.auth;
  if (auth) {
    //delete opts.auth;
    return auth === true ? Tyr.local.user : auth;
  }

  // @deprecated
  const tyrOpts = opts.tyranid;
  if (tyrOpts) {
    delete opts.tyranid;
    if (tyrOpts.secure) {
      return tyrOpts.subject || tyrOpts.user || Tyr.local.user;
    }
  }

  //return undefined;
}

/**
 * TODO: parseSaveObj() creates a new object and updates it with values to ensure that excess
 *       stuff on the object does not get written to the database which is good.
 *
 *       However, this means that any computed values/timestamp updates made here do not get made
 *       to the calling object which is the object that is passed around in event handlers.
 *
 *       Maybe we should apply all computed values/timestamps to the original object first,
 *       and then create the new object from that and just copy in the values that are supposed
 *       to be there?
 */
export async function parseSaveObj(col, obj, opts) {
  const def = col.def;

  const timestamps = def.timestamps && (!opts || opts.timestamps !== false);

  if (timestamps) {
    const now = new Date();
    // Don't overwrite createdAt in case we are coming from an upsert update.
    obj.createdAt = obj.createdAt || now;
    obj.updatedAt = now;
  }

  const fields = await col.fieldsFor({ match: obj, static: true }),
    insertObj = new col();

  // this handles the case where they set "timestamps: true" but did not define the updatedAt/createdAt fields manually
  if (timestamps) {
    insertObj.createdAt = obj.createdAt;
    insertObj.updatedAt = obj.updatedAt;
  }

  _.each(col.denormal, function(field, name) {
    // we only need to copy it if it is a top-level level, nested values will be copied below
    if (name.indexOf('.') < 1) {
      name = Path.populateNameFor(name, true);
      insertObj[name] = obj[name];
    }
  });

  _.each(fields, function(field, name) {
    const fieldDef = field.def;

    if (fieldDef.db !== false && !(fieldDef.get || fieldDef.getServer)) {
      const v = obj[name];

      if (v === undefined) {
        const df = _.result(fieldDef, 'defaultValue');
        if (df !== undefined) {
          insertObj[name] = df;
        }
      } else {
        insertObj[name] = v;
      }
    }
  });

  const pkFieldName = def.primaryKey.field;
  if (insertObj[pkFieldName] === undefined) {
    const type = fields[pkFieldName].type;
    insertObj[pkFieldName] = type.generatePrimaryKeyVal();
  }

  if (def.primaryKey.defaultMatchIdOnInsert && insertObj._id === undefined) {
    insertObj._id = insertObj[def.primaryKey.field];
  }

  if (def.historical && obj._history) {
    insertObj._history = obj._history;
  }

  return insertObj;
}

export function parseProjection(col, obj) {
  const def = col.def,
    projection = Object.assign({}, obj);

  if (
    projection[def.primaryKey.field] === undefined &&
    // if an exclusion is present, don't add an inclusion
    _.find(projection, v => !v) === undefined
  ) {
    projection[def.primaryKey.field] = 1;
  }

  // TODO: consider default removing _id if not the primary

  return projection;
}

export function evaluateClient(client, key, doc, value, opts, proj) {
  if (_.isFunction(client)) {
    client = client.call(doc, value, opts, proj);

    // non-truthy from a function means no
    if (!client) {
      client = false;
    }
  }

  switch (client) {
    case false:
    case 0:
    case 'never':
      return false;

    case 'conditional':
      if (!proj || !proj[key]) {
        return false;
      }
    // fall through

    case true:
    case 1:
    case 'default':
    case undefined:
      return true;

    default:
      throw new Error(
        `Invalid client value: ${value}\n\nMust be one of 'never', 'conditional', 'default', true (or 1), false (or 0), or undefined`
      );
  }
}

export function toClient(col, doc, opts) {
  if (!doc) return doc;

  if (Array.isArray(doc)) return doc.map(doc2 => toClient(col, doc2, opts));

  if (doc.$access) doc.$redact();

  if (col) opts = processOptions(col, opts);
  const proj = extractProjection(opts);

  // fields is only for top-level objects, we do not want to recursively pass it down into embedded documents
  const dOpts = proj ? _.omit(opts, 'fields', 'projection', 'project') : opts;

  const obj = {};

  function projected(key) {
    if (!proj) return key !== '_history' && key !== '$options';

    const v = proj[key];
    return v === undefined ? key === '_id' : v;
  }

  const fields = col?.fields;
  for (const k in doc) {
    if (!doc.hasOwnProperty(k)) continue;

    let v = doc[k];
    let field;

    // note we are only looking at the top-level properties so call flattenProjection() before calling toClient()
    if (k.endsWith('$')) {
      if (!opts.populate?.[k.substring(0, k.length - 1)]) continue;
    } else {
      if (!projected(k)) continue;
    }

    if (fields && (field = fields[k])) {
      v = field.type.toClient(field, v, doc, opts, proj);

      if (v !== undefined) {
        obj[k] = v;
      }
    } else if (!v) {
      obj[k] = v;
    } else if (v.$toClient) {
      obj[k] = v.$toClient(dOpts);
    } else if (Array.isArray(v)) {
      // TODO:  we can figure out the type of k using metadata to make this work for the case when
      //        we have an array of pojos instead of instances
      obj[k] = toClient(null, v, dOpts);
    } else if (ObjectId.isValid(v.toString())) {
      obj[k] = v.toString();
    } else {
      // TODO:  right now we're sending down everything we don't have metadata for ...
      //        for example, populated values ... we probably need a more comprehensive solution here, not sure
      //        what it would be yet
      obj[k] = v;
    }
  }

  if (doc.$access) obj.$access = doc.$access;

  // send down computed fields ... maybe move everything into this so we only send down what we know about ... can also calculate populated names to send
  for (const name in fields) {
    if (!fields.hasOwnProperty(name)) continue;

    const field = fields[name];
    const fieldDef = field.def,
      getFn = fieldDef.getServer || fieldDef.get;
    if (!getFn) continue;

    if (
      !evaluateClient(
        fieldDef.client,
        name,
        doc,
        // we don't pass in the value in this case because in order to do that we'd need to calculate it which might be costly
        undefined,
        opts,
        proj
      ) ||
      !projected(name)
    ) {
      continue;
    }

    let value;
    if (name in doc) {
      // Property will have been set on CollectionInstances
      value = doc[name];
    } else if (_.isFunction(getFn)) {
      // Have to set manually for POJO
      value = getFn.call(doc);
    } else {
      throw new Error('Incorrect computed value definition: ' + name);
    }

    obj[name] = value;
  }

  const toClientFn = col && col.def && col.def.toClient;
  if (toClientFn) {
    toClientFn.call(obj, opts);
  }

  const postFn = opts.post;
  if (postFn) {
    postFn.call(obj, opts);
  }

  return obj;
}
<commit_msg>fix for toClient<commit_after>import * as _ from 'lodash';
import { ObjectId } from 'mongodb';

import { extractProjection, resolveProjection } from './core/projection';
import Tyr from './tyr';
import Path from './core/path';

export const metaRegex = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g;

export function setFalse(v) {
  return v !== undefined && !v;
}

export function escapeRegex(str) {
  return str.replace(metaRegex, '\\$&');
}

export function pathAdd(path, add) {
  return path ? path + '.' + add : add;
}

export function isArrow(fn) {
  return /^[a-zA-Z0-9,_\s]*=>/.test(fn.toString());
}

export function hasMongoUpdateOperator(update) {
  for (const key in update) {
    if (key.startsWith('$') && update.hasOwnProperty(key)) {
      return true;
    }
  }

  //return undefined;
}

export function extractUpdateFields(doc, opts) {
  const updateFields = {};

  const projection = extractProjection(opts);
  if (projection) {
    _.each(projection, (field, key) => {
      if (field) {
        updateFields[key] = 1;
      }
    });
  } else {
    _.each(doc, (field, key) => {
      if (key !== '_id') {
        updateFields[key] = 1;
      }
    });
  }

  return updateFields;
}

// Options parsing
// ===============

export function isOptions(opts) {
  // TODO:  this is a hack, need to figure out a better way (though most likely a non-issue in practice)
  return (
    opts &&
    (opts.asOf !== undefined ||
      opts.auth !== undefined ||
      opts.author !== undefined ||
      opts.comment !== undefined ||
      opts.fields !== undefined ||
      opts.historical !== undefined ||
      opts.limit !== undefined ||
      opts.multi !== undefined ||
      opts.parallel !== undefined ||
      opts.perm !== undefined ||
      opts.populate !== undefined ||
      opts.projection !== undefined ||
      opts.query !== undefined ||
      opts.skip !== undefined ||
      opts.timestamps !== undefined ||
      opts.tyranid !== undefined ||
      opts.upsert !== undefined ||
      opts.writeConcern !== undefined ||
      !_.keys(opts).length)
  );
}

export function processOptions(collection, opts) {
  if (opts) {
    const fields = extractProjection(opts);

    if (fields) {
      const f = resolveProjection(collection.def.projections, fields);

      if (f !== fields) {
        const newOpts = {
          ...opts,
          projection: f
        };
        delete newOpts.fields;
        return newOpts;
      }
    }

    return opts;
  } else {
    return {};
  }
}

export function extractOptions(collection, args) {
  if (args.length && isOptions(args[args.length - 1])) {
    return processOptions(collection, args.pop());
  } else {
    return {};
  }
}

/**
 * This functions tries to avoid creating a new option object unless it has to.
 */
export function combineOptions(...sources) {
  let first;
  let composite;
  for (const source of sources) {
    if (source) {
      if (composite) {
        _.assign(composite, source);
      } else if (first) {
        composite = {};
        _.assign(composite, first);
        _.assign(composite, source);
      } else {
        first = source;
      }
    }
  }
  return composite || first;
}

/**
 * Extracts the authorization out of a mongodb options-style object.
 */
export function extractAuthorization(opts) {
  if (!opts) {
    return undefined;
  }

  const auth = opts.auth;
  if (auth) {
    //delete opts.auth;
    return auth === true ? Tyr.local.user : auth;
  }

  // @deprecated
  const tyrOpts = opts.tyranid;
  if (tyrOpts) {
    delete opts.tyranid;
    if (tyrOpts.secure) {
      return tyrOpts.subject || tyrOpts.user || Tyr.local.user;
    }
  }

  //return undefined;
}

/**
 * TODO: parseSaveObj() creates a new object and updates it with values to ensure that excess
 *       stuff on the object does not get written to the database which is good.
 *
 *       However, this means that any computed values/timestamp updates made here do not get made
 *       to the calling object which is the object that is passed around in event handlers.
 *
 *       Maybe we should apply all computed values/timestamps to the original object first,
 *       and then create the new object from that and just copy in the values that are supposed
 *       to be there?
 */
export async function parseSaveObj(col, obj, opts) {
  const def = col.def;

  const timestamps = def.timestamps && (!opts || opts.timestamps !== false);

  if (timestamps) {
    const now = new Date();
    // Don't overwrite createdAt in case we are coming from an upsert update.
    obj.createdAt = obj.createdAt || now;
    obj.updatedAt = now;
  }

  const fields = await col.fieldsFor({ match: obj, static: true }),
    insertObj = new col();

  // this handles the case where they set "timestamps: true" but did not define the updatedAt/createdAt fields manually
  if (timestamps) {
    insertObj.createdAt = obj.createdAt;
    insertObj.updatedAt = obj.updatedAt;
  }

  _.each(col.denormal, function(field, name) {
    // we only need to copy it if it is a top-level level, nested values will be copied below
    if (name.indexOf('.') < 1) {
      name = Path.populateNameFor(name, true);
      insertObj[name] = obj[name];
    }
  });

  _.each(fields, function(field, name) {
    const fieldDef = field.def;

    if (fieldDef.db !== false && !(fieldDef.get || fieldDef.getServer)) {
      const v = obj[name];

      if (v === undefined) {
        const df = _.result(fieldDef, 'defaultValue');
        if (df !== undefined) {
          insertObj[name] = df;
        }
      } else {
        insertObj[name] = v;
      }
    }
  });

  const pkFieldName = def.primaryKey.field;
  if (insertObj[pkFieldName] === undefined) {
    const type = fields[pkFieldName].type;
    insertObj[pkFieldName] = type.generatePrimaryKeyVal();
  }

  if (def.primaryKey.defaultMatchIdOnInsert && insertObj._id === undefined) {
    insertObj._id = insertObj[def.primaryKey.field];
  }

  if (def.historical && obj._history) {
    insertObj._history = obj._history;
  }

  return insertObj;
}

export function parseProjection(col, obj) {
  const def = col.def,
    projection = Object.assign({}, obj);

  if (
    projection[def.primaryKey.field] === undefined &&
    // if an exclusion is present, don't add an inclusion
    _.find(projection, v => !v) === undefined
  ) {
    projection[def.primaryKey.field] = 1;
  }

  // TODO: consider default removing _id if not the primary

  return projection;
}

export function evaluateClient(client, key, doc, value, opts, proj) {
  if (_.isFunction(client)) {
    client = client.call(doc, value, opts, proj);

    // non-truthy from a function means no
    if (!client) {
      client = false;
    }
  }

  switch (client) {
    case false:
    case 0:
    case 'never':
      return false;

    case 'conditional':
      if (!proj || !proj[key]) {
        return false;
      }
    // fall through

    case true:
    case 1:
    case 'default':
    case undefined:
      return true;

    default:
      throw new Error(
        `Invalid client value: ${value}\n\nMust be one of 'never', 'conditional', 'default', true (or 1), false (or 0), or undefined`
      );
  }
}

export function toClient(col, doc, opts) {
  if (!doc) return doc;

  if (Array.isArray(doc)) return doc.map(doc2 => toClient(col, doc2, opts));

  if (doc.$access) doc.$redact();

  if (col) opts = processOptions(col, opts);
  const proj = extractProjection(opts);

  // fields is only for top-level objects, we do not want to recursively pass it down into embedded documents
  const dOpts = proj ? _.omit(opts, 'fields', 'projection', 'project') : opts;

  const obj = {},
    { populate } = opts;

  // note we are only looking at the top-level properties so call flattenProjection() before calling toClient()
  function projected(key) {
    if (key.endsWith('$')) {
      if (!populate) return false;
      const baseKey = key.substring(0, key.length - 1);
      return populate[baseKey] || populate.includes?.(baseKey);
    } else {
      if (!proj) return key !== '_history' && key !== '$options';

      const v = proj[key];
      return v === undefined ? key === '_id' : v;
    }
  }

  const fields = col?.fields;
  for (const k in doc) {
    if (!doc.hasOwnProperty(k)) continue;

    let v = doc[k];
    let field;

    if (!projected(k)) continue;

    if (fields && (field = fields[k])) {
      v = field.type.toClient(field, v, doc, opts, proj);

      if (v !== undefined) {
        obj[k] = v;
      }
    } else if (!v) {
      obj[k] = v;
    } else if (v.$toClient) {
      obj[k] = v.$toClient(dOpts);
    } else if (Array.isArray(v)) {
      // TODO:  we can figure out the type of k using metadata to make this work for the case when
      //        we have an array of pojos instead of instances
      obj[k] = toClient(null, v, dOpts);
    } else if (ObjectId.isValid(v.toString())) {
      obj[k] = v.toString();
    } else {
      // TODO:  right now we're sending down everything we don't have metadata for ...
      //        for example, populated values ... we probably need a more comprehensive solution here, not sure
      //        what it would be yet
      obj[k] = v;
    }
  }

  if (doc.$access) obj.$access = doc.$access;

  // send down computed fields ... maybe move everything into this so we only send down what we know about ... can also calculate populated names to send
  for (const name in fields) {
    if (!fields.hasOwnProperty(name)) continue;

    const field = fields[name];
    const fieldDef = field.def,
      getFn = fieldDef.getServer || fieldDef.get;
    if (!getFn) continue;

    if (
      !evaluateClient(
        fieldDef.client,
        name,
        doc,
        // we don't pass in the value in this case because in order to do that we'd need to calculate it which might be costly
        undefined,
        opts,
        proj
      ) ||
      !projected(name)
    ) {
      continue;
    }

    let value;
    if (name in doc) {
      // Property will have been set on CollectionInstances
      value = doc[name];
    } else if (_.isFunction(getFn)) {
      // Have to set manually for POJO
      value = getFn.call(doc);
    } else {
      throw new Error('Incorrect computed value definition: ' + name);
    }

    obj[name] = value;
  }

  const toClientFn = col && col.def && col.def.toClient;
  if (toClientFn) {
    toClientFn.call(obj, opts);
  }

  const postFn = opts.post;
  if (postFn) {
    postFn.call(obj, opts);
  }

  return obj;
}
<|endoftext|>


<commit_before>var React = require('react');

var PasswordColumn = React.createClass({
	render: function() {
		var value = this.props.data.fields[this.props.col.path];
		return (

{value ? '********' : ''}

		);
	}
});

module.exports = PasswordColumn;
<commit_msg>cleanup password column<commit_after>import React from 'react';
import ItemsTableCell from '../../../admin/src/components/ItemsTableCell';
import ItemsTableValue from '../../../admin/src/components/ItemsTableValue';

var PasswordColumn = React.createClass({
	displayName: 'PasswordColumn',
	propTypes: {
		col: React.PropTypes.object,
		data: React.PropTypes.object,
	},
	renderValue: function() {
		let value = this.props.data.fields[this.props.col.path];
		return value ? '********' : '';
	},
	render: function() {
		return (
			<ItemsTableCell>
				<ItemsTableValue field={this.props.col.path}>
					{this.renderValue()}
				</ItemsTableValue>
			</ItemsTableCell>
		);
	}
});

module.exports = PasswordColumn;
<|endoftext|>


<commit_before>define(['text!./analyzer.html', 'app', 'lodash', 'require', 'jquery', './analyzer-section', 'filters/lstring', 'filters/cases'],
        function(templateHtml, app, _, require, $) { 'use strict';

    var baseUrl = require.toUrl('');

    //====================================
    //
    //
    //====================================
    function mapReduce(key) {

        return function(map, v) {

            if(key) map[v[key]] = v;
            else    map[v]      = v;

            return  map;

        };
    }

    //==============================================
    //
    //
    //==============================================
    app.directive('nationalReportAnalyzer', ['$http', '$q', 'locale', '$filter', function($http, $q, locale, $filter) {
        return {
            restrict : 'E',
            replace : true,
            template : templateHtml,
            require : 'nationalReportAnalyzer',
            scope :  {
                selectedRegions: '=regions',
                selectedQuestions: '=questions',
                selectedReportType: '=reportType'
            },
            link: function ($scope, $element, attr, nrAnalyzer) {

                $element.find(".filter.panel").affix({ offset: { top : 350 } });

                $scope.allRegionsMap = {};
                $scope.filters = [];
                $scope.filtersCountriesMap = {};
                $scope.sumType = 'sum';
                $scope.sumTypes = ['sum', 'percentGlobal', 'percentRow', 'percentColumn' ];

                var loaded = false;

                //====================================
                //
                //
                //====================================
                $scope.$watch('selectedRegions',    function() { if(!loaded) load(); });
                $scope.$watch('selectedQuestions',  function() { if(!loaded) load(); });
                $scope.$watch('selectedReportType', function() { if(!loaded) load(); });

                //====================================
                //
                //
                //====================================
                function load() {

                    if(!$scope.selectedRegions)    return;
                    if(!$scope.selectedQuestions)  return;
                    if(!$scope.selectedReportType) return;

                    loaded = true;
                    $scope.filter = undefined;

                    $q.all([loadRegions(), loadSections(), nrAnalyzer.loadReports(), loadPreviousReportQuestionsMapping()]).then(function(results) {

                        var regions  = results[0];
                        var sections = results[1];
                        var reports  = results[2];
                        var prevMapping = results[3];

                        var reportsCountriesMap = _(reports).pluck('government').sortBy().map(function(id){
                            return $scope.allRegionsMap[id];
                        }).reduce(mapReduce('identifier'), {});

                        regions = _.map(regions, function(region){

                            region.htmlTitle    = htmlTitle(region.shortTitle, region.title);
                            region.countriesMap = _.pick(reportsCountriesMap, [region.identifier].concat(region.expandedNarrowerTerms||region.narrowerTerms||[]));
                            region.countries    = _.keys(region.countriesMap);

                            return region;
                        });

                        $scope.regions = regions;
                        $scope.sections = sections;
                        $scope.previousMapping = prevMapping;

                    }).then(function(){

                        if($scope.sections && $scope.sections.length) {
                            nrAnalyzer.toggleSection($scope.sections[0].key, true);
                        }
                    });
                }

                //====================================
                //
                //
                //====================================
                function loadRegions() {

                    var lstring = $filter('lstring');

                    var countries = $http.get('/api/v2013/thesaurus/domains/countries/terms', { cache : true }).then(function(res) {
                        return res.data;
                    });

                    var regions = $http.get('/api/v2013/thesaurus/domains/regions/terms?relations', { cache : true }).then(function(res) {
                        return res.data;
                    });

                    return $q.all([countries, regions]).then(function(results){

                        var allRegionsMap = _(results).flatten().reduce(mapReduce('identifier'), {});

                        $scope.allRegionsMap = allRegionsMap;

                        return _($scope.selectedRegions).map(function(id) {
                            return allRegionsMap[id];
                        }).sortBy(function(term) {
                            return lstring(term.shortTitle) || lstring(term.title);
                        }).value();
                    });
                }

                //====================================
                //
                //
                //====================================
                function loadPreviousReportQuestionsMapping() {

                    var reportType = $scope.selectedReportType;

                    return $http.get(baseUrl+'resources/national-reports/mapping/'+reportType+'.json', { cache : true }).then(function(res) {

                        return res.data;

                    }).catch(function() {

                        return undefined;

                    });
                }

                //====================================
                //
                //
                //====================================
                function loadSections() {

                    var reportType = $scope.selectedReportType;

                    return $http.get(baseUrl+'resources/national-reports/'+locale+'/'+reportType+'.json', { cache : true }).then(function(res) {

                        var selection = _($scope.selectedQuestions).reduce(mapReduce(), {});

                        return _.filter(res.data, function(section) {

                            section.questions = _.filter(section.questions, function(q) {
                                return selection[q.key];
                            });

                            return section.questions.length;
                        });
                    });
                }

                //====================================
                //
                //
                //====================================
                function htmlTitle(shortTitle, title) {

                    var lstring = $filter('lstring');

                    shortTitle = lstring(shortTitle);

                    if(shortTitle)
                        return htmlEncode(shortTitle).split(/[\-–]/).join('<br>');

                    return htmlEncode(lstring(title));
                }

                //====================================
                //
                //
                //====================================
                function htmlEncode(value){
                  return $('<div/>').text(value).html();
                }

                //==============================================
                //
                //
                //==============================================
                $scope.showSumTypeSelector = function() {
                    nrAnalyzer.showSumTypeSelector(true);
                };

                //====================================
                //
                //
                //====================================
                $scope.setSumType = function(type) {
                    $scope.sumType = type;
                    nrAnalyzer.showSumTypeSelector(false);
                };

                //====================================
                //
                //
                //====================================
                $scope.clearFilter = function() {
                    nrAnalyzer.filter(undefined);
                };

                //====================================
                //
                //
                //====================================
                $scope.showSettings = function() {
                    $scope.$emit('nr.analyzer.settings');
                };

                //====================================
                //
                //
                //====================================
                var sumTypeDialog = $element.find("#sumTypeDialog");

                sumTypeDialog.on("shown.bs.modal",    function() { $scope.$apply(function() { $scope.sumTypeDialogVisible = true;  }); });
                sumTypeDialog.on("hidden.bs.modal",   function() { $scope.$apply(function() { $scope.sumTypeDialogVisible = false; }); });
                $scope.$watch('sumTypeDialogVisible', function(visible) {

                    if(sumTypeDialog.is(":visible") != (visible || false)) {

                        if(visible) sumTypeDialog.modal('show');
                        else        sumTypeDialog.modal('hide');
                    }
                });

                //====================================
                //
                //
                //====================================
                var textsDialog = $element.find("#textsDialog");

                textsDialog.on("shown.bs.modal",  function() { textsDialog.find(".modal-body").scrollTop(0); });
                textsDialog.on("shown.bs.modal",  function() { $scope.$apply(function() { $scope.textsDialogVisible = true;  }); });
                textsDialog.on("hidden.bs.modal", function() { $scope.$apply(function() { $scope.textsDialogVisible = false; }); });
                $scope.$watch('textsDialogVisible', function(visible) {

                    if(sumTypeDialog.is(":visible") != (visible || false)) {

                        if(visible) textsDialog.modal('show');
                        else        textsDialog.modal('hide');
                    }
                });
            },

            controller : ['$scope', function($scope){

                var _self = this;

                //====================================
                //
                //
                //====================================
                this.showSumTypeSelector = function(visible) {

                    if(visible===undefined)
                        visible = true;

                    $scope.sumTypeDialogVisible = visible;
                };

                //====================================
                //
                //
                //====================================
                this.showTexts = function(countriesTexts, question) {

                    var lstring = $filter('lstring');

                    countriesTexts = _.sortBy(countriesTexts||[], function(item){
                        return lstring($scope.allRegionsMap[item.government].title);
                    });

                    $scope.currentQuestion = question;
                    $scope.countriesTexts  = countriesTexts;
                    $scope.textsDialogVisible = !_.isEmpty(countriesTexts);
                };

                //====================================
                //
                //
                //====================================
                this.toggleSection = function(sectionName, expanded) {

                    var section = _.findWhere($scope.sections||[], { key : sectionName });

                    if(!section)
                        return;

                    if(expanded === undefined)
                        expanded = !section.expanded;

                    expanded = !!expanded; // !! force boolean

                    if(section.reports) {

                        section.expanded = expanded;

                        return $q.resolve(expanded);

                    }

                    return _self.loadReports({

                        questions : _.pluck(section.questions, 'key').concat(['documentId'])

                    }).then(function(reports){

                        section.reports  = reports;
                        section.expanded = expanded;

                        return expanded;

                    });
                };

                //====================================
                //
                //
                //====================================
                this.filter = function(filter) {

                    if(!arguments.length) {
                        return $scope.filter;
                    }

                    $scope.filter = filter;
                    $scope.$broadcast('nr.analyzer.filter', filter);
                };

                //====================================
                //
                //
                //====================================
                this.loadReports = function(options) {

                    options = _.defaults(options||{}, {
                        reportType : $scope.selectedReportType,
                        regions : $scope.selectedRegions,
                        questions : []
                    });

                    var query  = { 'government_REL' : { $in: options.regions } };
                    var fields = _(options.questions).union(['government']).reduce(function(ret, key) {
                        ret[key] = 1;
                        return ret;
                    }, {});

                    var collectionUrls = {
                        cpbNationalReport2 : "/api/v2015/national-reports-cpb-2",
                        cpbNationalReport3 : "/api/v2015/national-reports-cpb-3"
                    };

                    return $http.get(collectionUrls[options.reportType], {  params: { q : query, f : fields }, cache : true }).then(function(res) {

                        return _.map(res.data, function(report) {
                            return _.mapValues(report, toRawValue);
                        });
                    });

                    function toRawValue(v) {

                        if(_.isArray(v))
                            return _.map(v, toRawValue);

                        v = v.value || v.identifier || v;

                        if(typeof(v)=='boolean')
                            v = v.toString();

                        return v;
                    }
                };
            }]
        };
    }]);
});
<commit_msg>Set default sumType to to  percentGlobal and order<commit_after>define(['text!./analyzer.html', 'app', 'lodash', 'require', 'jquery', './analyzer-section', 'filters/lstring', 'filters/cases'],
        function(templateHtml, app, _, require, $) { 'use strict';

    var baseUrl = require.toUrl('');

    //====================================
    //
    //
    //====================================
    function mapReduce(key) {

        return function(map, v) {

            if(key) map[v[key]] = v;
            else    map[v]      = v;

            return  map;

        };
    }

    //==============================================
    //
    //
    //==============================================
    app.directive('nationalReportAnalyzer', ['$http', '$q', 'locale', '$filter', function($http, $q, locale, $filter) {
        return {
            restrict : 'E',
            replace : true,
            template : templateHtml,
            require : 'nationalReportAnalyzer',
            scope :  {
                selectedRegions: '=regions',
                selectedQuestions: '=questions',
                selectedReportType: '=reportType'
            },
            link: function ($scope, $element, attr, nrAnalyzer) {

                $element.find(".filter.panel").affix({ offset: { top : 350 } });

                $scope.allRegionsMap = {};
                $scope.filters = [];
                $scope.filtersCountriesMap = {};
                $scope.sumType = 'percentGlobal';
                $scope.sumTypes = ['percentGlobal', 'percentColumn', 'percentRow', 'sum' ];

                var loaded = false;

                //====================================
                //
                //
                //====================================
                $scope.$watch('selectedRegions',    function() { if(!loaded) load(); });
                $scope.$watch('selectedQuestions',  function() { if(!loaded) load(); });
                $scope.$watch('selectedReportType', function() { if(!loaded) load(); });

                //====================================
                //
                //
                //====================================
                function load() {

                    if(!$scope.selectedRegions)    return;
                    if(!$scope.selectedQuestions)  return;
                    if(!$scope.selectedReportType) return;

                    loaded = true;
                    $scope.filter = undefined;

                    $q.all([loadRegions(), loadSections(), nrAnalyzer.loadReports(), loadPreviousReportQuestionsMapping()]).then(function(results) {

                        var regions  = results[0];
                        var sections = results[1];
                        var reports  = results[2];
                        var prevMapping = results[3];

                        var reportsCountriesMap = _(reports).pluck('government').sortBy().map(function(id){
                            return $scope.allRegionsMap[id];
                        }).reduce(mapReduce('identifier'), {});

                        regions = _.map(regions, function(region){

                            region.htmlTitle    = htmlTitle(region.shortTitle, region.title);
                            region.countriesMap = _.pick(reportsCountriesMap, [region.identifier].concat(region.expandedNarrowerTerms||region.narrowerTerms||[]));
                            region.countries    = _.keys(region.countriesMap);

                            return region;
                        });

                        $scope.regions = regions;
                        $scope.sections = sections;
                        $scope.previousMapping = prevMapping;

                    }).then(function(){

                        if($scope.sections && $scope.sections.length) {
                            nrAnalyzer.toggleSection($scope.sections[0].key, true);
                        }
                    });
                }

                //====================================
                //
                //
                //====================================
                function loadRegions() {

                    var lstring = $filter('lstring');

                    var countries = $http.get('/api/v2013/thesaurus/domains/countries/terms', { cache : true }).then(function(res) {
                        return res.data;
                    });

                    var regions = $http.get('/api/v2013/thesaurus/domains/regions/terms?relations', { cache : true }).then(function(res) {
                        return res.data;
                    });

                    return $q.all([countries, regions]).then(function(results){

                        var allRegionsMap = _(results).flatten().reduce(mapReduce('identifier'), {});

                        $scope.allRegionsMap = allRegionsMap;

                        return _($scope.selectedRegions).map(function(id) {
                            return allRegionsMap[id];
                        }).sortBy(function(term) {
                            return lstring(term.shortTitle) || lstring(term.title);
                        }).value();
                    });
                }

                //====================================
                //
                //
                //====================================
                function loadPreviousReportQuestionsMapping() {

                    var reportType = $scope.selectedReportType;

                    return $http.get(baseUrl+'resources/national-reports/mapping/'+reportType+'.json', { cache : true }).then(function(res) {

                        return res.data;

                    }).catch(function() {

                        return undefined;

                    });
                }

                //====================================
                //
                //
                //====================================
                function loadSections() {

                    var reportType = $scope.selectedReportType;

                    return $http.get(baseUrl+'resources/national-reports/'+locale+'/'+reportType+'.json', { cache : true }).then(function(res) {

                        var selection = _($scope.selectedQuestions).reduce(mapReduce(), {});

                        return _.filter(res.data, function(section) {

                            section.questions = _.filter(section.questions, function(q) {
                                return selection[q.key];
                            });

                            return section.questions.length;
                        });
                    });
                }

                //====================================
                //
                //
                //====================================
                function htmlTitle(shortTitle, title) {

                    var lstring = $filter('lstring');

                    shortTitle = lstring(shortTitle);

                    if(shortTitle)
                        return htmlEncode(shortTitle).split(/[\-–]/).join('<br>');

                    return htmlEncode(lstring(title));
                }

                //====================================
                //
                //
                //====================================
                function htmlEncode(value){
                  return $('<div/>').text(value).html();
                }

                //==============================================
                //
                //
                //==============================================
                $scope.showSumTypeSelector = function() {
                    nrAnalyzer.showSumTypeSelector(true);
                };

                //====================================
                //
                //
                //====================================
                $scope.setSumType = function(type) {
                    $scope.sumType = type;
                    nrAnalyzer.showSumTypeSelector(false);
                };

                //====================================
                //
                //
                //====================================
                $scope.clearFilter = function() {
                    nrAnalyzer.filter(undefined);
                };

                //====================================
                //
                //
                //====================================
                $scope.showSettings = function() {
                    $scope.$emit('nr.analyzer.settings');
                };

                //====================================
                //
                //
                //====================================
                var sumTypeDialog = $element.find("#sumTypeDialog");

                sumTypeDialog.on("shown.bs.modal",    function() { $scope.$apply(function() { $scope.sumTypeDialogVisible = true;  }); });
                sumTypeDialog.on("hidden.bs.modal",   function() { $scope.$apply(function() { $scope.sumTypeDialogVisible = false; }); });
                $scope.$watch('sumTypeDialogVisible', function(visible) {

                    if(sumTypeDialog.is(":visible") != (visible || false)) {

                        if(visible) sumTypeDialog.modal('show');
                        else        sumTypeDialog.modal('hide');
                    }
                });

                //====================================
                //
                //
                //====================================
                var textsDialog = $element.find("#textsDialog");

                textsDialog.on("shown.bs.modal",  function() { textsDialog.find(".modal-body").scrollTop(0); });
                textsDialog.on("shown.bs.modal",  function() { $scope.$apply(function() { $scope.textsDialogVisible = true;  }); });
                textsDialog.on("hidden.bs.modal", function() { $scope.$apply(function() { $scope.textsDialogVisible = false; }); });
                $scope.$watch('textsDialogVisible', function(visible) {

                    if(sumTypeDialog.is(":visible") != (visible || false)) {

                        if(visible) textsDialog.modal('show');
                        else        textsDialog.modal('hide');
                    }
                });
            },

            controller : ['$scope', function($scope){

                var _self = this;

                //====================================
                //
                //
                //====================================
                this.showSumTypeSelector = function(visible) {

                    if(visible===undefined)
                        visible = true;

                    $scope.sumTypeDialogVisible = visible;
                };

                //====================================
                //
                //
                //====================================
                this.showTexts = function(countriesTexts, question) {

                    var lstring = $filter('lstring');

                    countriesTexts = _.sortBy(countriesTexts||[], function(item){
                        return lstring($scope.allRegionsMap[item.government].title);
                    });

                    $scope.currentQuestion = question;
                    $scope.countriesTexts  = countriesTexts;
                    $scope.textsDialogVisible = !_.isEmpty(countriesTexts);
                };

                //====================================
                //
                //
                //====================================
                this.toggleSection = function(sectionName, expanded) {

                    var section = _.findWhere($scope.sections||[], { key : sectionName });

                    if(!section)
                        return;

                    if(expanded === undefined)
                        expanded = !section.expanded;

                    expanded = !!expanded; // !! force boolean

                    if(section.reports) {

                        section.expanded = expanded;

                        return $q.resolve(expanded);

                    }

                    return _self.loadReports({

                        questions : _.pluck(section.questions, 'key').concat(['documentId'])

                    }).then(function(reports){

                        section.reports  = reports;
                        section.expanded = expanded;

                        return expanded;

                    });
                };

                //====================================
                //
                //
                //====================================
                this.filter = function(filter) {

                    if(!arguments.length) {
                        return $scope.filter;
                    }

                    $scope.filter = filter;
                    $scope.$broadcast('nr.analyzer.filter', filter);
                };

                //====================================
                //
                //
                //====================================
                this.loadReports = function(options) {

                    options = _.defaults(options||{}, {
                        reportType : $scope.selectedReportType,
                        regions : $scope.selectedRegions,
                        questions : []
                    });

                    var query  = { 'government_REL' : { $in: options.regions } };
                    var fields = _(options.questions).union(['government']).reduce(function(ret, key) {
                        ret[key] = 1;
                        return ret;
                    }, {});

                    var collectionUrls = {
                        cpbNationalReport2 : "/api/v2015/national-reports-cpb-2",
                        cpbNationalReport3 : "/api/v2015/national-reports-cpb-3"
                    };

                    return $http.get(collectionUrls[options.reportType], {  params: { q : query, f : fields }, cache : true }).then(function(res) {

                        return _.map(res.data, function(report) {
                            return _.mapValues(report, toRawValue);
                        });
                    });

                    function toRawValue(v) {

                        if(_.isArray(v))
                            return _.map(v, toRawValue);

                        v = v.value || v.identifier || v;

                        if(typeof(v)=='boolean')
                            v = v.toString();

                        return v;
                    }
                };
            }]
        };
    }]);
});
<|endoftext|>


<commit_before>Taskar.Sections.Ordering = function(element){
  new Taskar.Dnd.Sortable(element, {
    item:       '.section',
    handle:     '.drag',
    moveX:      false,
    moveY:      true
  });
};<commit_msg>fixed width/height after the drag&drop of the sections<commit_after>Taskar.Sections.Ordering = function(element){
  new Taskar.Dnd.Sortable(element, {
    item:       '.section',
    handle:     '.drag',
    moveX:      false,
    moveY:      true
  });

  element.observe('drag:finish', function(e){
    e.memo.element.setStyle({
      width:  null,
      height: null
    });
  });
};<|endoftext|>


<commit_before>/**
 * Copyright 2015 Ian Davies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
define([], function() {

    var augmentEvent = function(e, api) {
        e.expired = Date.now() > e.date;

        e.teacher = e.tags.indexOf("teacher") > -1;
        e.student = e.tags.indexOf("student") > -1;
        e.virtual =  e.tags.indexOf("virtual") > -1;

        e.field = e.tags.indexOf("physics") > -1 ? "physics" : (e.tags.indexOf("maths") > -1 ? "maths" : undefined);

        // we have to fix the event image url.
        if(e.eventThumbnail) {
            e.eventThumbnail.src = api.getImageUrl(e.eventThumbnail.src);
        }
    }

    var ListController = ['$scope', 'api', '$timeout', '$stateParams', function($scope, api, $timeout, $stateParams) {

        $timeout(function() {
            // Call this asynchronously, so that loading icon doesn't get immediately clobbered by $stateChangeSuccess.
            $scope.loadMore();
        });

        var startIndex = 0;
        var eventsPerPage = 6;
        var showActiveOnly = $stateParams.show_active_only ? $stateParams.show_active_only : false;
        var showInactiveOnly = $stateParams.show_inactive_only ? $stateParams.show_inactive_only : false;
        var filterEventsByType = null;

        var showByTag = null;

        $scope.filterEventsByStatus = "UPCOMING";
        $scope.filterEventsByType = "all";

        $scope.$watch('filterEventsByStatus + filterEventsByType', function(newValue, oldValue){
            if ($scope.filterEventsByStatus == "UPCOMING") {
                showActiveOnly = true;
                showInactiveOnly = false;
            } else {
                showActiveOnly = false;
                showInactiveOnly = false;
            }

            if ($scope.filterEventsByType == "all") {
                filterEventsByType = null;
            } else {
                filterEventsByType = $scope.filterEventsByType
            }

            startIndex = 0;
            $scope.events = [];
            $scope.loadMore()
        });

        $scope.events = [];
        $scope.loadMore = function() {
            $scope.globalFlags.isLoading = true;
            api.getEventsList(startIndex, eventsPerPage, showActiveOnly, showInactiveOnly, filterEventsByType).$promise.then(function(result) {
                $scope.globalFlags.isLoading = false;

                for(var i in result.results) {
                    var e = result.results[i];
                    augmentEvent(e, api);
                    $scope.events.push(e);
                }

                startIndex += result.results.length;

                if (startIndex >= result.totalResults) {
                    $scope.noMoreResults = true;
                } else {
                    $scope.noMoreResults = false;
                }
            });
        }
    }];

    var DetailController = ['$scope', 'api', '$timeout', '$stateParams', '$state', function($scope, api, $timeout, $stateParams, $state) {
        var loaded = false;
        $timeout(function() {
            // Call this asynchronously, so that loading icon doesn't get immediately clobbered by $stateChangeSuccess.
            $scope.globalFlags.isLoading = !loaded;
        });

        $scope.event = api.events.get({id: $stateParams.id});

        $scope.event.$promise.then(function(e) {
            loaded = true;
            $scope.globalFlags.isLoading = false;

            augmentEvent(e, api);
        }).catch(function() {
            $scope.globalFlags.isLoading = false;
            $state.go('404', {target: $state.href("event", $stateParams)});
        });
    }];

    return {
        ListController: ListController,
        DetailController: DetailController,
    };
})<commit_msg>It seemed that load more was being called multiple times and causing duplicate events to render.<commit_after>/**
 * Copyright 2015 Ian Davies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
define([], function() {

    var augmentEvent = function(e, api) {
        e.expired = Date.now() > e.date;

        e.teacher = e.tags.indexOf("teacher") > -1;
        e.student = e.tags.indexOf("student") > -1;
        e.virtual =  e.tags.indexOf("virtual") > -1;

        e.field = e.tags.indexOf("physics") > -1 ? "physics" : (e.tags.indexOf("maths") > -1 ? "maths" : undefined);

        // we have to fix the event image url.
        if(e.eventThumbnail) {
            e.eventThumbnail.src = api.getImageUrl(e.eventThumbnail.src);
        }
    }

    var ListController = ['$scope', 'api', '$timeout', '$stateParams', function($scope, api, $timeout, $stateParams) {
        var startIndex = 0;
        var eventsPerPage = 6;
        var showActiveOnly = $stateParams.show_active_only ? $stateParams.show_active_only : false;
        var showInactiveOnly = $stateParams.show_inactive_only ? $stateParams.show_inactive_only : false;
        var filterEventsByType = null;

        var showByTag = null;

        $scope.filterEventsByStatus = "UPCOMING";
        $scope.filterEventsByType = "all";

        $scope.$watch('filterEventsByStatus + filterEventsByType', function(newValue, oldValue){
            if ($scope.filterEventsByStatus == "UPCOMING") {
                showActiveOnly = true;
                showInactiveOnly = false;
            } else {
                showActiveOnly = false;
                showInactiveOnly = false;
            }

            if ($scope.filterEventsByType == "all") {
                filterEventsByType = null;
            } else {
                filterEventsByType = $scope.filterEventsByType
            }

            startIndex = 0;
            $scope.events = [];
            $scope.loadMore();
        });

        $scope.events = [];
        $scope.loadMore = function() {
            $scope.globalFlags.isLoading = true;
            api.getEventsList(startIndex, eventsPerPage, showActiveOnly, showInactiveOnly, filterEventsByType).$promise.then(function(result) {
                $scope.globalFlags.isLoading = false;

                for(var i in result.results) {
                    var e = result.results[i];
                    augmentEvent(e, api);
                    $scope.events.push(e);
                }

                startIndex += result.results.length;

                if (startIndex >= result.totalResults) {
                    $scope.noMoreResults = true;
                } else {
                    $scope.noMoreResults = false;
                }
            });
        }
    }];

    var DetailController = ['$scope', 'api', '$timeout', '$stateParams', '$state', function($scope, api, $timeout, $stateParams, $state) {
        var loaded = false;
        $timeout(function() {
            // Call this asynchronously, so that loading icon doesn't get immediately clobbered by $stateChangeSuccess.
            $scope.globalFlags.isLoading = !loaded;
        });

        $scope.event = api.events.get({id: $stateParams.id});

        $scope.event.$promise.then(function(e) {
            loaded = true;
            $scope.globalFlags.isLoading = false;

            augmentEvent(e, api);
        }).catch(function() {
            $scope.globalFlags.isLoading = false;
            $state.go('404', {target: $state.href("event", $stateParams)});
        });
    }];

    return {
        ListController: ListController,
        DetailController: DetailController,
    };
})<|endoftext|>


<commit_before>/*
 * Copyright (C) 2018 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import Big from 'big.js'
import round from 'compiled/util/round'
import {gradeToScore, scoreToGrade} from '../../gradebook/GradingSchemeHelper'
import numberHelper from '../../shared/helpers/numberHelper'

const MAX_PRECISION = 15 // the maximum precision of a score persisted to the database

function toNumber(bigValue) {
  return Number.parseFloat(bigValue.round(MAX_PRECISION).toString(), 10)
}

function pointsFromPercentage(percentage, pointsPossible) {
  return toNumber(new Big(percentage).div(100).times(pointsPossible))
}

function percentageFromPoints(points, pointsPossible) {
  return toNumber(new Big(points).div(pointsPossible).times(100))
}

function invalid(value) {
  return {
    enteredAs: null,
    excused: false,
    grade: value,
    score: null,
    valid: false
  }
}

function parseAsGradingScheme(value, options) {
  if (!options.gradingScheme) {
    return null
  }

  const percentage = gradeToScore(value, options.gradingScheme)
  if (percentage == null) {
    return null
  }

  return {
    enteredAs: 'gradingScheme',
    percent: options.pointsPossible ? percentage : 0,
    points: options.pointsPossible ? pointsFromPercentage(percentage, options.pointsPossible) : 0,
    schemeKey: scoreToGrade(percentage, options.gradingScheme)
  }
}

function parseAsPercent(value, options) {
  const percentage = numberHelper.parse(value.replace(/[%％﹪٪]/, ''))
  if (isNaN(percentage)) {
    return null
  }

  let percent = percentage
  let points = pointsFromPercentage(percentage, options.pointsPossible)

  if (!options.pointsPossible) {
    points = numberHelper.parse(value)
    if (isNaN(points)) {
      percent = 0
      points = 0
    }
  }

  return {
    enteredAs: 'percent',
    percent,
    points,
    schemeKey: scoreToGrade(percent, options.gradingScheme)
  }
}

function parseAsPoints(value, options) {
  const points = numberHelper.parse(value)
  if (isNaN(points)) {
    return null
  }

  const percent = options.pointsPossible ? percentageFromPoints(points, options.pointsPossible) : 0

  return {
    enteredAs: 'points',
    percent: null,
    points,
    schemeKey: scoreToGrade(percent, options.gradingScheme)
  }
}

function parseForGradingScheme(value, options) {
  const result =
    parseAsGradingScheme(value, options) ||
    parseAsPoints(value, options) ||
    parseAsPercent(value, options)

  if (result) {
    return {
      enteredAs: result.enteredAs,
      excused: false,
      grade: result.schemeKey,
      score: result.points,
      valid: true
    }
  }

  return invalid(value)
}

function parseForPercent(value, options) {
  const result = parseAsPercent(value, options) || parseAsGradingScheme(value, options)

  if (result) {
    return {
      enteredAs: result.enteredAs,
      excused: false,
      grade: `${result.percent}%`,
      score: result.points,
      valid: true
    }
  }

  return invalid(value)
}

function parseForPoints(value, options) {
  const result =
    parseAsPoints(value, options) ||
    parseAsGradingScheme(value, options) ||
    parseAsPercent(value, options)

  if (result) {
    return {
      enteredAs: result.enteredAs,
      excused: false,
      grade: `${result.points}`,
      score: result.points,
      valid: true
    }
  }

  return invalid(value)
}

function parseForPassFail(value, options) {
  const cleanValue = value.toLowerCase()
  const result = {enteredAs: 'passFail', excused: false, grade: cleanValue, valid: true}

  if (cleanValue === 'complete') {
    result.score = options.pointsPossible || 0
  } else if (cleanValue === 'incomplete') {
    result.score = 0
  } else {
    return invalid(value)
  }

  return result
}

export function isExcused(grade) {
  return `${grade}`.trim().toLowerCase() === 'ex'
}

export function parseTextValue(value, options) {
  const trimmedValue = value != null ? `${value}`.trim() : ''

  if (trimmedValue === '') {
    return {enteredAs: null, excused: false, grade: null, score: null, valid: true}
  }

  if (isExcused(trimmedValue)) {
    return {enteredAs: 'excused', excused: true, grade: null, score: null, valid: true}
  }

  switch (options.enterGradesAs) {
    case 'gradingScheme': {
      return parseForGradingScheme(trimmedValue, options)
    }
    case 'percent': {
      return parseForPercent(trimmedValue, options)
    }
    case 'passFail': {
      return parseForPassFail(trimmedValue, options)
    }
    default: {
      return parseForPoints(trimmedValue, options)
    }
  }
}

export function hasGradeChanged(submission, gradeInfo, options) {
  if (!gradeInfo.valid) {
    // the given submission is always assumed to be valid
    return true
  }

  if (gradeInfo.excused !== submission.excused) {
    return true
  }

  if (gradeInfo.enteredAs === 'gradingScheme') {
    /*
     * When the value given is a grading scheme key, it must be compared to
     * the grade on the submission instead of the score. This avoids updating
     * the grade when the stored score and interpreted score differ and the
     * input value was not changed.
     *
     * To avoid updating the grade in cases where the stored grade is of a
     * different type but otherwise equivalent, get the grading data for the
     * stored grade and compare it to the grading data from the input.
     */
    const submissionGradeInfo = parseTextValue(submission.enteredGrade, options)
    return submissionGradeInfo.grade !== gradeInfo.grade
  }

  return submission.enteredScore !== gradeInfo.score
}
<commit_msg>replace use of Number.parseFloat<commit_after>/*
 * Copyright (C) 2018 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import Big from 'big.js'
import round from 'compiled/util/round'
import {gradeToScore, scoreToGrade} from '../../gradebook/GradingSchemeHelper'
import numberHelper from '../../shared/helpers/numberHelper'

const MAX_PRECISION = 15 // the maximum precision of a score persisted to the database

function toNumber(bigValue) {
  return parseFloat(bigValue.round(MAX_PRECISION).toString(), 10)
}

function pointsFromPercentage(percentage, pointsPossible) {
  return toNumber(new Big(percentage).div(100).times(pointsPossible))
}

function percentageFromPoints(points, pointsPossible) {
  return toNumber(new Big(points).div(pointsPossible).times(100))
}

function invalid(value) {
  return {
    enteredAs: null,
    excused: false,
    grade: value,
    score: null,
    valid: false
  }
}

function parseAsGradingScheme(value, options) {
  if (!options.gradingScheme) {
    return null
  }

  const percentage = gradeToScore(value, options.gradingScheme)
  if (percentage == null) {
    return null
  }

  return {
    enteredAs: 'gradingScheme',
    percent: options.pointsPossible ? percentage : 0,
    points: options.pointsPossible ? pointsFromPercentage(percentage, options.pointsPossible) : 0,
    schemeKey: scoreToGrade(percentage, options.gradingScheme)
  }
}

function parseAsPercent(value, options) {
  const percentage = numberHelper.parse(value.replace(/[%％﹪٪]/, ''))
  if (isNaN(percentage)) {
    return null
  }

  let percent = percentage
  let points = pointsFromPercentage(percentage, options.pointsPossible)

  if (!options.pointsPossible) {
    points = numberHelper.parse(value)
    if (isNaN(points)) {
      percent = 0
      points = 0
    }
  }

  return {
    enteredAs: 'percent',
    percent,
    points,
    schemeKey: scoreToGrade(percent, options.gradingScheme)
  }
}

function parseAsPoints(value, options) {
  const points = numberHelper.parse(value)
  if (isNaN(points)) {
    return null
  }

  const percent = options.pointsPossible ? percentageFromPoints(points, options.pointsPossible) : 0

  return {
    enteredAs: 'points',
    percent: null,
    points,
    schemeKey: scoreToGrade(percent, options.gradingScheme)
  }
}

function parseForGradingScheme(value, options) {
  const result =
    parseAsGradingScheme(value, options) ||
    parseAsPoints(value, options) ||
    parseAsPercent(value, options)

  if (result) {
    return {
      enteredAs: result.enteredAs,
      excused: false,
      grade: result.schemeKey,
      score: result.points,
      valid: true
    }
  }

  return invalid(value)
}

function parseForPercent(value, options) {
  const result = parseAsPercent(value, options) || parseAsGradingScheme(value, options)

  if (result) {
    return {
      enteredAs: result.enteredAs,
      excused: false,
      grade: `${result.percent}%`,
      score: result.points,
      valid: true
    }
  }

  return invalid(value)
}

function parseForPoints(value, options) {
  const result =
    parseAsPoints(value, options) ||
    parseAsGradingScheme(value, options) ||
    parseAsPercent(value, options)

  if (result) {
    return {
      enteredAs: result.enteredAs,
      excused: false,
      grade: `${result.points}`,
      score: result.points,
      valid: true
    }
  }

  return invalid(value)
}

function parseForPassFail(value, options) {
  const cleanValue = value.toLowerCase()
  const result = {enteredAs: 'passFail', excused: false, grade: cleanValue, valid: true}

  if (cleanValue === 'complete') {
    result.score = options.pointsPossible || 0
  } else if (cleanValue === 'incomplete') {
    result.score = 0
  } else {
    return invalid(value)
  }

  return result
}

export function isExcused(grade) {
  return `${grade}`.trim().toLowerCase() === 'ex'
}

export function parseTextValue(value, options) {
  const trimmedValue = value != null ? `${value}`.trim() : ''

  if (trimmedValue === '') {
    return {enteredAs: null, excused: false, grade: null, score: null, valid: true}
  }

  if (isExcused(trimmedValue)) {
    return {enteredAs: 'excused', excused: true, grade: null, score: null, valid: true}
  }

  switch (options.enterGradesAs) {
    case 'gradingScheme': {
      return parseForGradingScheme(trimmedValue, options)
    }
    case 'percent': {
      return parseForPercent(trimmedValue, options)
    }
    case 'passFail': {
      return parseForPassFail(trimmedValue, options)
    }
    default: {
      return parseForPoints(trimmedValue, options)
    }
  }
}

export function hasGradeChanged(submission, gradeInfo, options) {
  if (!gradeInfo.valid) {
    // the given submission is always assumed to be valid
    return true
  }

  if (gradeInfo.excused !== submission.excused) {
    return true
  }

  if (gradeInfo.enteredAs === 'gradingScheme') {
    /*
     * When the value given is a grading scheme key, it must be compared to
     * the grade on the submission instead of the score. This avoids updating
     * the grade when the stored score and interpreted score differ and the
     * input value was not changed.
     *
     * To avoid updating the grade in cases where the stored grade is of a
     * different type but otherwise equivalent, get the grading data for the
     * stored grade and compare it to the grading data from the input.
     */
    const submissionGradeInfo = parseTextValue(submission.enteredGrade, options)
    return submissionGradeInfo.grade !== gradeInfo.grade
  }

  return submission.enteredScore !== gradeInfo.score
}
<|endoftext|>


<commit_before>export default Ember.HTMLBars.makeBoundHelper(function (params) {
  var paramNames = params.slice(1).map(function(val, idx) { return "p" + idx; });
  var func = Function.apply(this, paramNames.concat("return " + params[0] + ";"))
  return func.apply(params[1] || this);
});
<commit_msg>Supply missing arguments to js expression, 'this' should still refer to first argument if it's value is false<commit_after>export default Ember.HTMLBars.makeBoundHelper(function (params) {
  var paramNames = params.slice(1).map(function(val, idx) { return "p" + idx; });
  var func = Function.apply(this, paramNames.concat("return " + params[0] + ";"))
  return func.apply(params[1] === undefined ? this : params[1], params.slice(1));
});
<|endoftext|>