<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});

XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
  "resource://gre/modules/NewTabUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
  "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Pocket",
  "chrome://pocket/content/Pocket.jsm");

const LINK_BLOCKED_EVENT = "newtab-linkBlocked";

/**
 * Observer - a wrapper around history/bookmark observers to add the QueryInterface.
 */
class Observer {
  constructor(dispatch, observerInterface) {
    this.dispatch = dispatch;
    this.QueryInterface = XPCOMUtils.generateQI([observerInterface, Ci.nsISupportsWeakReference]);
  }
}

/**
 * HistoryObserver - observes events from PlacesUtils.history
 */
class HistoryObserver extends Observer {
  constructor(dispatch) {
    super(dispatch, Ci.nsINavHistoryObserver);
  }

  /**
   * onDeleteURI - Called when an link is deleted from history.
   *
   * @param  {obj} uri        A URI object representing the link's url
   *         {str} uri.spec   The URI as a string
   */
  onDeleteURI(uri) {
    this.dispatch({
      type: at.PLACES_LINK_DELETED,
      data: {url: uri.spec}
    });
  }

  /**
   * onClearHistory - Called when the user clears their entire history.
   */
  onClearHistory() {
    this.dispatch({type: at.PLACES_HISTORY_CLEARED});
  }
}

/**
 * BookmarksObserver - observes events from PlacesUtils.bookmarks
 */
class BookmarksObserver extends Observer {
  constructor(dispatch) {
    super(dispatch, Ci.nsINavBookmarkObserver);
  }

  /**
   * onItemAdded - Called when a bookmark is added
   *
   * @param  {str} id
   * @param  {str} folderId
   * @param  {int} index
   * @param  {int} type       Indicates if the bookmark is an actual bookmark,
   *                          a folder, or a separator.
   * @param  {str} uri
   * @param  {str} title
   * @param  {int} dateAdded
   * @param  {str} guid      The unique id of the bookmark
   */
  async onItemAdded(...args) {
    const type = args[3];
    const guid = args[7];
    if (type !== PlacesUtils.bookmarks.TYPE_BOOKMARK) {
      return;
    }
    try {
      // bookmark: {bookmarkGuid, bookmarkTitle, lastModified, url}
      const bookmark = await NewTabUtils.activityStreamProvider.getBookmark(guid);
      this.dispatch({type: at.PLACES_BOOKMARK_ADDED, data: bookmark});
    } catch (e) {
      Cu.reportError(e);
    }
  }

  /**
   * onItemRemoved - Called when a bookmark is removed
   *
   * @param  {str} id
   * @param  {str} folderId
   * @param  {int} index
   * @param  {int} type       Indicates if the bookmark is an actual bookmark,
   *                          a folder, or a separator.
   * @param  {str} uri
   * @param  {str} guid      The unique id of the bookmark
   */
  onItemRemoved(id, folderId, index, type, uri, guid) {
    if (type === PlacesUtils.bookmarks.TYPE_BOOKMARK) {
      this.dispatch({
        type: at.PLACES_BOOKMARK_REMOVED,
        data: {url: uri.spec, bookmarkGuid: guid}
      });
    }
  }

  /**
   * onItemChanged - Called when a bookmark is modified
   *
   * @param  {str} id           description
   * @param  {str} property     The property that was modified (e.g. uri, title)
   * @param  {bool} isAnnotation
   * @param  {any} value
   * @param  {int} lastModified
   * @param  {int} type         Indicates if the bookmark is an actual bookmark,
   *                             a folder, or a separator.
   * @param  {int} parent
   * @param  {str} guid         The unique id of the bookmark
   */
  async onItemChanged(...args) {
    const property = args[1];
    const type = args[5];
    const guid = args[7];

    // Only process this event if it is a TYPE_BOOKMARK, and uri or title was the property changed.
    if (type !== PlacesUtils.bookmarks.TYPE_BOOKMARK || !["uri", "title"].includes(property)) {
      return;
    }
    try {
      // bookmark: {bookmarkGuid, bookmarkTitle, lastModified, url}
      const bookmark = await NewTabUtils.activityStreamProvider.getBookmark(guid);
      this.dispatch({type: at.PLACES_BOOKMARK_CHANGED, data: bookmark});
    } catch (e) {
      Cu.reportError(e);
    }
  }
}

class PlacesFeed {
  constructor() {
    this.historyObserver = new HistoryObserver(action => this.store.dispatch(ac.BroadcastToContent(action)));
    this.bookmarksObserver = new BookmarksObserver(action => this.store.dispatch(ac.BroadcastToContent(action)));
  }

  addObservers() {
    // NB: Directly get services without importing the *BIG* PlacesUtils module
    Cc["@mozilla.org/browser/nav-history-service;1"]
      .getService(Ci.nsINavHistoryService)
      .addObserver(this.historyObserver, true);
    Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
      .getService(Ci.nsINavBookmarksService)
      .addObserver(this.bookmarksObserver, true);

    Services.obs.addObserver(this, LINK_BLOCKED_EVENT);
  }

  removeObservers() {
    PlacesUtils.history.removeObserver(this.historyObserver);
    PlacesUtils.bookmarks.removeObserver(this.bookmarksObserver);
    Services.obs.removeObserver(this, LINK_BLOCKED_EVENT);
  }

  /**
   * observe - An observer for the LINK_BLOCKED_EVENT.
   *           Called when a link is blocked.
   *
   * @param  {null} subject
   * @param  {str} topic   The name of the event
   * @param  {str} value   The data associated with the event
   */
  observe(subject, topic, value) {
    if (topic === LINK_BLOCKED_EVENT) {
      this.store.dispatch(ac.BroadcastToContent({
        type: at.PLACES_LINK_BLOCKED,
        data: {url: value}
      }));
    }
  }

  openNewWindow(action, isPrivate = false) {
    const win = action._target.browser.ownerGlobal;
    const privateParam = {private: isPrivate};
    const params = (action.data.referrer) ?
      Object.assign(privateParam, {referrerURI: Services.io.newURI(action.data.referrer)}) : privateParam;
    win.openLinkIn(action.data.url, "window", params);
  }

  onAction(action) {
    switch (action.type) {
      case at.INIT:
        // Briefly avoid loading services for observing for better startup timing
        Services.tm.dispatchToMainThread(() => this.addObservers());
        break;
      case at.UNINIT:
        this.removeObservers();
        break;
      case at.BLOCK_URL:
        NewTabUtils.activityStreamLinks.blockURL({url: action.data});
        break;
      case at.BOOKMARK_URL:
        NewTabUtils.activityStreamLinks.addBookmark(action.data);
        break;
      case at.DELETE_BOOKMARK_BY_ID:
        NewTabUtils.activityStreamLinks.deleteBookmark(action.data);
        break;
      case at.DELETE_HISTORY_URL:
        NewTabUtils.activityStreamLinks.deleteHistoryEntry(action.data);
        break;
      case at.OPEN_NEW_WINDOW:
        this.openNewWindow(action);
        break;
      case at.OPEN_PRIVATE_WINDOW:
        this.openNewWindow(action, true);
        break;
      case at.SAVE_TO_POCKET:
        Pocket.savePage(action._target.browser, action.data.site.url, action.data.site.title);
        break;
      case at.OPEN_LINK: {
        const win = action._target.browser.ownerGlobal;
        const where = win.whereToOpenLink(action.data.event);
        if (action.data.referrer) {
          win.openLinkIn(action.data.url, where, {referrerURI: Services.io.newURI(action.data.referrer)});
        } else {
          win.openLinkIn(action.data.url, where);
        }
        break;
      }
    }
  }
}

this.PlacesFeed = PlacesFeed;

// Exported for testing only
PlacesFeed.HistoryObserver = HistoryObserver;
PlacesFeed.BookmarksObserver = BookmarksObserver;

this.EXPORTED_SYMBOLS = ["PlacesFeed"];
<commit_msg>fix(systemaddon): Always pass params to win.openLinkIn. Closes #3084. (#3085)<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});

XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
  "resource://gre/modules/NewTabUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
  "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Pocket",
  "chrome://pocket/content/Pocket.jsm");

const LINK_BLOCKED_EVENT = "newtab-linkBlocked";

/**
 * Observer - a wrapper around history/bookmark observers to add the QueryInterface.
 */
class Observer {
  constructor(dispatch, observerInterface) {
    this.dispatch = dispatch;
    this.QueryInterface = XPCOMUtils.generateQI([observerInterface, Ci.nsISupportsWeakReference]);
  }
}

/**
 * HistoryObserver - observes events from PlacesUtils.history
 */
class HistoryObserver extends Observer {
  constructor(dispatch) {
    super(dispatch, Ci.nsINavHistoryObserver);
  }

  /**
   * onDeleteURI - Called when an link is deleted from history.
   *
   * @param  {obj} uri        A URI object representing the link's url
   *         {str} uri.spec   The URI as a string
   */
  onDeleteURI(uri) {
    this.dispatch({
      type: at.PLACES_LINK_DELETED,
      data: {url: uri.spec}
    });
  }

  /**
   * onClearHistory - Called when the user clears their entire history.
   */
  onClearHistory() {
    this.dispatch({type: at.PLACES_HISTORY_CLEARED});
  }
}

/**
 * BookmarksObserver - observes events from PlacesUtils.bookmarks
 */
class BookmarksObserver extends Observer {
  constructor(dispatch) {
    super(dispatch, Ci.nsINavBookmarkObserver);
  }

  /**
   * onItemAdded - Called when a bookmark is added
   *
   * @param  {str} id
   * @param  {str} folderId
   * @param  {int} index
   * @param  {int} type       Indicates if the bookmark is an actual bookmark,
   *                          a folder, or a separator.
   * @param  {str} uri
   * @param  {str} title
   * @param  {int} dateAdded
   * @param  {str} guid      The unique id of the bookmark
   */
  async onItemAdded(...args) {
    const type = args[3];
    const guid = args[7];
    if (type !== PlacesUtils.bookmarks.TYPE_BOOKMARK) {
      return;
    }
    try {
      // bookmark: {bookmarkGuid, bookmarkTitle, lastModified, url}
      const bookmark = await NewTabUtils.activityStreamProvider.getBookmark(guid);
      this.dispatch({type: at.PLACES_BOOKMARK_ADDED, data: bookmark});
    } catch (e) {
      Cu.reportError(e);
    }
  }

  /**
   * onItemRemoved - Called when a bookmark is removed
   *
   * @param  {str} id
   * @param  {str} folderId
   * @param  {int} index
   * @param  {int} type       Indicates if the bookmark is an actual bookmark,
   *                          a folder, or a separator.
   * @param  {str} uri
   * @param  {str} guid      The unique id of the bookmark
   */
  onItemRemoved(id, folderId, index, type, uri, guid) {
    if (type === PlacesUtils.bookmarks.TYPE_BOOKMARK) {
      this.dispatch({
        type: at.PLACES_BOOKMARK_REMOVED,
        data: {url: uri.spec, bookmarkGuid: guid}
      });
    }
  }

  /**
   * onItemChanged - Called when a bookmark is modified
   *
   * @param  {str} id           description
   * @param  {str} property     The property that was modified (e.g. uri, title)
   * @param  {bool} isAnnotation
   * @param  {any} value
   * @param  {int} lastModified
   * @param  {int} type         Indicates if the bookmark is an actual bookmark,
   *                             a folder, or a separator.
   * @param  {int} parent
   * @param  {str} guid         The unique id of the bookmark
   */
  async onItemChanged(...args) {
    const property = args[1];
    const type = args[5];
    const guid = args[7];

    // Only process this event if it is a TYPE_BOOKMARK, and uri or title was the property changed.
    if (type !== PlacesUtils.bookmarks.TYPE_BOOKMARK || !["uri", "title"].includes(property)) {
      return;
    }
    try {
      // bookmark: {bookmarkGuid, bookmarkTitle, lastModified, url}
      const bookmark = await NewTabUtils.activityStreamProvider.getBookmark(guid);
      this.dispatch({type: at.PLACES_BOOKMARK_CHANGED, data: bookmark});
    } catch (e) {
      Cu.reportError(e);
    }
  }
}

class PlacesFeed {
  constructor() {
    this.historyObserver = new HistoryObserver(action => this.store.dispatch(ac.BroadcastToContent(action)));
    this.bookmarksObserver = new BookmarksObserver(action => this.store.dispatch(ac.BroadcastToContent(action)));
  }

  addObservers() {
    // NB: Directly get services without importing the *BIG* PlacesUtils module
    Cc["@mozilla.org/browser/nav-history-service;1"]
      .getService(Ci.nsINavHistoryService)
      .addObserver(this.historyObserver, true);
    Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
      .getService(Ci.nsINavBookmarksService)
      .addObserver(this.bookmarksObserver, true);

    Services.obs.addObserver(this, LINK_BLOCKED_EVENT);
  }

  removeObservers() {
    PlacesUtils.history.removeObserver(this.historyObserver);
    PlacesUtils.bookmarks.removeObserver(this.bookmarksObserver);
    Services.obs.removeObserver(this, LINK_BLOCKED_EVENT);
  }

  /**
   * observe - An observer for the LINK_BLOCKED_EVENT.
   *           Called when a link is blocked.
   *
   * @param  {null} subject
   * @param  {str} topic   The name of the event
   * @param  {str} value   The data associated with the event
   */
  observe(subject, topic, value) {
    if (topic === LINK_BLOCKED_EVENT) {
      this.store.dispatch(ac.BroadcastToContent({
        type: at.PLACES_LINK_BLOCKED,
        data: {url: value}
      }));
    }
  }

  openNewWindow(action, isPrivate = false) {
    const win = action._target.browser.ownerGlobal;
    const privateParam = {private: isPrivate};
    const params = (action.data.referrer) ?
      Object.assign(privateParam, {referrerURI: Services.io.newURI(action.data.referrer)}) : privateParam;
    win.openLinkIn(action.data.url, "window", params);
  }

  onAction(action) {
    switch (action.type) {
      case at.INIT:
        // Briefly avoid loading services for observing for better startup timing
        Services.tm.dispatchToMainThread(() => this.addObservers());
        break;
      case at.UNINIT:
        this.removeObservers();
        break;
      case at.BLOCK_URL:
        NewTabUtils.activityStreamLinks.blockURL({url: action.data});
        break;
      case at.BOOKMARK_URL:
        NewTabUtils.activityStreamLinks.addBookmark(action.data);
        break;
      case at.DELETE_BOOKMARK_BY_ID:
        NewTabUtils.activityStreamLinks.deleteBookmark(action.data);
        break;
      case at.DELETE_HISTORY_URL:
        NewTabUtils.activityStreamLinks.deleteHistoryEntry(action.data);
        break;
      case at.OPEN_NEW_WINDOW:
        this.openNewWindow(action);
        break;
      case at.OPEN_PRIVATE_WINDOW:
        this.openNewWindow(action, true);
        break;
      case at.SAVE_TO_POCKET:
        Pocket.savePage(action._target.browser, action.data.site.url, action.data.site.title);
        break;
      case at.OPEN_LINK: {
        const win = action._target.browser.ownerGlobal;
        const where = win.whereToOpenLink(action.data.event);
        if (action.data.referrer) {
          win.openLinkIn(action.data.url, where, {referrerURI: Services.io.newURI(action.data.referrer)});
        } else {
          win.openLinkIn(action.data.url, where, {});
        }
        break;
      }
    }
  }
}

this.PlacesFeed = PlacesFeed;

// Exported for testing only
PlacesFeed.HistoryObserver = HistoryObserver;
PlacesFeed.BookmarksObserver = BookmarksObserver;

this.EXPORTED_SYMBOLS = ["PlacesFeed"];
<|endoftext|>


<commit_before>/**
 * Controller for the assignment overview.
 */
Ext.define('devilry_subjectadmin.controller.assignment.AssignmentController', {
    extend: 'Ext.app.Controller',

    mixins: {
        'loadAssignment': 'devilry_subjectadmin.utils.LoadAssignmentMixin',
        'setBreadcrumb': 'devilry_subjectadmin.utils.BasenodeBreadcrumbMixin',
        'onLoadFailure': 'devilry_subjectadmin.utils.DjangoRestframeworkLoadFailureMixin'
    },

    views: [
        'assignment.AssignmentOverview'
    ],

    requires: [
        'devilry_subjectadmin.utils.UrlLookup'
    ],

    models: [
        'Assignment'
    ],

    refs: [{
        ref: 'globalAlertmessagelist',
        selector: 'viewport floatingalertmessagelist#appAlertmessagelist'
    }, {
        ref: 'header',
        selector: 'assignmentoverview #header'
    }, {
        ref: 'assignmentOverview',
        selector: 'assignmentoverview'
    }, {
        ref: 'deleteButton',
        selector: 'assignmentoverview #deleteButton'
    }, {
        ref: 'renameButton',
        selector: 'assignmentoverview #renameButton'
    }, {
        ref: 'noGroupsMessage',
        selector: 'assignmentoverview #noGroupsMessage'
    }, {
        ref: 'adminsbox',
        selector: 'assignmentoverview adminsbox'
    }, {
        ref: 'linkList',
        selector: 'assignmentoverview #linkList'
    }, {
        ref: 'examinerRoleBox',
        selector: 'assignmentoverview #examinerRoleBox'
    }],

    init: function() {
        this.control({
            'viewport assignmentoverview': {
                render: this._onAssignmentViewRender
            },
            'viewport assignmentoverview #deleteButton': {
                click: this._onDelete
            },
            'viewport assignmentoverview #renameButton': {
                click: this._onRename
            }
        });
    },

    _onAssignmentViewRender: function() {
        this.assignment_id = this.getAssignmentOverview().assignment_id;
        this.loadAssignment(this.assignment_id);
    },

    _getPath: function() {
        return this.getPathFromBreadcrumb(this.assignmentRecord);
    },

    onLoadAssignmentSuccess: function(record) {
        this.assignmentRecord = record;
        this.application.setTitle(this._getPath());
        this.setBreadcrumb(this.assignmentRecord);
        this.getHeader().update({
            heading: record.get('long_name')
        });
        this._setDangerousActionsLabels();
        this.application.fireEvent('assignmentSuccessfullyLoaded', record);
        if(this.assignmentRecord.get('number_of_groups') === 0) {
            this._handleNoGroups();
        } else {
            this._updateLinkList(true);
            this._updateExaminerBox();
        }
        this.getAdminsbox().setBasenodeRecord(this.assignmentRecord);
    },
    onLoadAssignmentFailure: function(operation) {
        this.onLoadFailure(operation);
    },

    _updateLinkList: function(has_students) {
        this.getLinkList().update({
            managestudents_url: devilry_subjectadmin.utils.UrlLookup.manageStudents(this.assignment_id),
            managedeadlines_url: devilry_subjectadmin.utils.UrlLookup.bulkManageDeadlines(this.assignment_id),
            passedpreviousperiod_url: devilry_subjectadmin.utils.UrlLookup.passedPreviousPeriod(this.assignment_id),
            examinerstats_url: devilry_subjectadmin.utils.UrlLookup.assignmentExaminerStats(this.assignment_id),
            assignmentData: this.assignmentRecord.data,
            electronic: this.assignmentRecord.get('delivery_types') === 0,
            period_term: gettext('period'),
            has_students: has_students
        });
    },

    _updateExaminerBox: function() {
        this.getExaminerRoleBox().updateData({
            loading: false,
            is_published: this.assignmentRecord.get('is_published'),
            mygroupscount: this.assignmentRecord.get('number_of_groups_where_is_examiner'),
            totalgroups: this.assignmentRecord.get('number_of_groups'),
            examinerui_url: devilry_subjectadmin.utils.UrlLookup.examinerGroupsOverviewTodo(this.assignmentRecord.get('id'))
        });
    },

    _setDangerousActionsLabels: function() {
        var assignmentPath = this._getPath();

        if(this.assignmentRecord.get('can_delete')) {
            var deleteLabel = Ext.create('Ext.XTemplate', gettext('Delete {something}')).apply({
                something: assignmentPath
            });
            this.getDeleteButton().setTitleText(deleteLabel);
            this.getDeleteButton().show();
        }

        var renameLabel = Ext.create('Ext.XTemplate', gettext('Rename {something}')).apply({
            something: assignmentPath
        });
        this.getRenameButton().setTitleText(renameLabel);
    },

    _onRename: function() {
        Ext.create('devilry_subjectadmin.view.RenameBasenodeWindow', {
            basenodeRecord: this.assignmentRecord
        }).show();
    },
    _onDelete: function() {
        var short_description = this._getPath();
        devilry_subjectadmin.view.DeleteDjangoRestframeworkRecordDialog.showIfCanDelete({
            basenodeRecord: this.assignmentRecord,
            short_description: short_description,
            listeners: {
                scope: this,
                deleteSuccess: function() {
                    this.application.onAfterDelete(short_description);
                }
            }
        });
    },

    _handleNoGroups: function() {
        this.getNoGroupsMessage().show();
        this.getExaminerRoleBox().hide();
        this._updateLinkList(false);
    }
});
<commit_msg>devilry_subjectadmin...AssignmentController: Added _isElectronic() method.<commit_after>/**
 * Controller for the assignment overview.
 */
Ext.define('devilry_subjectadmin.controller.assignment.AssignmentController', {
    extend: 'Ext.app.Controller',

    mixins: {
        'loadAssignment': 'devilry_subjectadmin.utils.LoadAssignmentMixin',
        'setBreadcrumb': 'devilry_subjectadmin.utils.BasenodeBreadcrumbMixin',
        'onLoadFailure': 'devilry_subjectadmin.utils.DjangoRestframeworkLoadFailureMixin'
    },

    views: [
        'assignment.AssignmentOverview'
    ],

    requires: [
        'devilry_subjectadmin.utils.UrlLookup'
    ],

    models: [
        'Assignment'
    ],

    refs: [{
        ref: 'globalAlertmessagelist',
        selector: 'viewport floatingalertmessagelist#appAlertmessagelist'
    }, {
        ref: 'header',
        selector: 'assignmentoverview #header'
    }, {
        ref: 'assignmentOverview',
        selector: 'assignmentoverview'
    }, {
        ref: 'deleteButton',
        selector: 'assignmentoverview #deleteButton'
    }, {
        ref: 'renameButton',
        selector: 'assignmentoverview #renameButton'
    }, {
        ref: 'noGroupsMessage',
        selector: 'assignmentoverview #noGroupsMessage'
    }, {
        ref: 'adminsbox',
        selector: 'assignmentoverview adminsbox'
    }, {
        ref: 'linkList',
        selector: 'assignmentoverview #linkList'
    }, {
        ref: 'examinerRoleBox',
        selector: 'assignmentoverview #examinerRoleBox'
    }],

    init: function() {
        this.control({
            'viewport assignmentoverview': {
                render: this._onAssignmentViewRender
            },
            'viewport assignmentoverview #deleteButton': {
                click: this._onDelete
            },
            'viewport assignmentoverview #renameButton': {
                click: this._onRename
            }
        });
    },

    _onAssignmentViewRender: function() {
        this.assignment_id = this.getAssignmentOverview().assignment_id;
        this.loadAssignment(this.assignment_id);
    },

    _getPath: function() {
        return this.getPathFromBreadcrumb(this.assignmentRecord);
    },

    onLoadAssignmentSuccess: function(record) {
        this.assignmentRecord = record;
        this.application.setTitle(this._getPath());
        this.setBreadcrumb(this.assignmentRecord);
        this.getHeader().update({
            heading: record.get('long_name')
        });
        this._setDangerousActionsLabels();
        this.application.fireEvent('assignmentSuccessfullyLoaded', record);
        if(this.assignmentRecord.get('number_of_groups') === 0) {
            this._handleNoGroups();
        } else {
            this._updateLinkList(true);
            this._updateExaminerBox();
        }
        this.getAdminsbox().setBasenodeRecord(this.assignmentRecord);
    },
    onLoadAssignmentFailure: function(operation) {
        this.onLoadFailure(operation);
    },

    _isElectronic: function() {
        return this.assignmentRecord.get('delivery_types') === 0;
    },

    _updateLinkList: function(has_students) {
        this.getLinkList().update({
            managestudents_url: devilry_subjectadmin.utils.UrlLookup.manageStudents(this.assignment_id),
            managedeadlines_url: devilry_subjectadmin.utils.UrlLookup.bulkManageDeadlines(this.assignment_id),
            passedpreviousperiod_url: devilry_subjectadmin.utils.UrlLookup.passedPreviousPeriod(this.assignment_id),
            examinerstats_url: devilry_subjectadmin.utils.UrlLookup.assignmentExaminerStats(this.assignment_id),
            assignmentData: this.assignmentRecord.data,
            electronic: this._isElectronic(),
            period_term: gettext('period'),
            has_students: has_students
        });
    },

    _updateExaminerBox: function() {
        this.getExaminerRoleBox().updateData({
            loading: false,
            is_published: this.assignmentRecord.get('is_published'),
            mygroupscount: this.assignmentRecord.get('number_of_groups_where_is_examiner'),
            totalgroups: this.assignmentRecord.get('number_of_groups'),
            examinerui_url: devilry_subjectadmin.utils.UrlLookup.examinerGroupsOverviewTodo(this.assignmentRecord.get('id'))
        });
    },

    _setDangerousActionsLabels: function() {
        var assignmentPath = this._getPath();

        if(this.assignmentRecord.get('can_delete')) {
            var deleteLabel = Ext.create('Ext.XTemplate', gettext('Delete {something}')).apply({
                something: assignmentPath
            });
            this.getDeleteButton().setTitleText(deleteLabel);
            this.getDeleteButton().show();
        }

        var renameLabel = Ext.create('Ext.XTemplate', gettext('Rename {something}')).apply({
            something: assignmentPath
        });
        this.getRenameButton().setTitleText(renameLabel);
    },

    _onRename: function() {
        Ext.create('devilry_subjectadmin.view.RenameBasenodeWindow', {
            basenodeRecord: this.assignmentRecord
        }).show();
    },
    _onDelete: function() {
        var short_description = this._getPath();
        devilry_subjectadmin.view.DeleteDjangoRestframeworkRecordDialog.showIfCanDelete({
            basenodeRecord: this.assignmentRecord,
            short_description: short_description,
            listeners: {
                scope: this,
                deleteSuccess: function() {
                    this.application.onAfterDelete(short_description);
                }
            }
        });
    },

    _handleNoGroups: function() {
        this.getNoGroupsMessage().show();
        this.getExaminerRoleBox().hide();
        this._updateLinkList(false);
    }
});
<|endoftext|>


<commit_before>import $ from 'jquery';

import Evented from '@ember/object/evented';
import Service from '@ember/service';
import { getOwner } from '@ember/application';
import { assign } from '@ember/polyfills'
import { Promise, resolve, reject } from 'rsvp';
import { run } from '@ember/runloop';

export default Service.extend(Evented, {
  fbInitPromise: null,
  locale: null,
  refreshToken: true,

  FBInit(options = {}) {
    if (this.fbInitPromise) { return this.fbInitPromise; }

    const ENV = getOwner(this).resolveRegistration('config:environment');

    var initSettings = assign({}, ENV.FB || {}, options);

    // Detect language configuration and store it.
    const locale = initSettings.locale || 'en_US';
    this.locale = locale;

    if (ENV.FB && ENV.FB.skipInit) {
      this.fbInitPromise = resolve('skip init');
      return this.fbInitPromise;
    }

    var original = window.fbAsyncInit;
    if (!initSettings || !initSettings.appId || !initSettings.version) {
      return reject('No settings for init');
    }

    this.fbInitPromise = new Promise(function(resolve){
      window.fbAsyncInit = function() {
        window.FB.init(initSettings);
        run.next(null, resolve);
      };
      // URL for the SDK is built according to locale. Defaults to `en_US`.
      $.getScript(`https://connect.facebook.net/${locale}/sdk.js`, function() {
        // Do nothing here, wait for window.fbAsyncInit to be called.
      });
    }).then(function() {
      if (original) {
        window.fbAsyncInit = original;
        window.fbAsyncInit();
        window.fbAsyncInit.hasRun = true;
      }
    });

    return this.fbInitPromise;
  },

  setAccessToken(token) {
    this.accessToken = token;
    this.trigger('fb.setAccessToken', token);
    return token;
  },

  loginWith: function(token) {
    console.warn('DEPRECATED: please, use setAccessToken instead');
    this.setAccessToken(token);
  },

  _api(path) {
    var method = 'GET';
    var parameters = {};
    var arg;

    if (!path) { return reject('Please, provide a path for your request'); }

    switch (arguments.length) {
      case 2:
        arg = arguments[1];
        if (typeof arg === 'string') {
          method = arg;
        } else {
          parameters = arg;
        }
        break;
      case 3:
        method = arguments[1];
        parameters = arguments[2];
    }

    parameters = assign(parameters, {access_token: this.accessToken});

    return this.FBInit().then(function() {
      return new Promise(function(resolve, reject) {
        window.FB.api(path, method, parameters, function(response) {
          if (response.error) {
            run.next(null, reject, response.error);
            return;
          }

          run.next(null, resolve, response);
        });
      });
    });
  },

  api() {
    return this._api(...arguments).catch((error) => {
      if (this.refreshToken && error.code === 190) {
        console.debug('Trying to refresh Facebook session an re-do the Facebook API request');
        return this.getLoginStatus().then((response) => {
          if (response.status === 'connected') {
            this.setAccessToken(response.authResponse.accessToken);
            return this._api(...arguments);
          }
          return reject(response);
        });
      }
      return reject(error);
    });
  },

  ui: function(params) {
    return this.FBInit().then(function() {
      return new Promise(function(resolve, reject) {
        window.FB.ui(params, function(response) {
          if (response && !response.error_code) {
            run.next(null, resolve, response);
            return;
          }

          run.next(null, reject, response);
        });
      });
    });
  },

  // Facebook Login Methods

  getLoginStatus: function(forceRequest) {
    return this.FBInit().then(function() {
      return new Promise(function(resolve) {
        window.FB.getLoginStatus(function(response) {
          run.next(null, resolve, response);
        }, forceRequest);
      });
    });
  },

  login: function(scope, options={}) {

    var service = this;
    var params = {scope: scope, return_scopes: true};

    if (options) {
      params = assign({}, params, options);
    }

    return this.FBInit().then(function() {
      return new Promise(function(resolve, reject) {
        window.FB.login(function(response) {
          if (response.authResponse) {
            service.accessToken = response.authResponse.accessToken;
            run.next(null, resolve, response);
          } else {
            run.next(null, reject, response);
          }
        }, params);
      });
    });
  },

  logout: function() {
    return this.FBInit().then(function() {
      return new Promise(function(resolve) {
        window.FB.logout(function(response) {
          run.next(null, resolve, response);
        });
      });
    });
  },

  getAuthResponse: function() {
    return window.FB.getAuthResponse();
  },

  xfbml_parse: function() {
    return this.FBInit().then(function() {
      return new Promise(function(resolve) {
        return window.FB.XFBML.parse(undefined, function() {
          run.next(null, resolve, 'XFBML.parse');
        });
      });
    });
  }
});
<commit_msg>FIX: Token should be refreshed if error code is 2500<commit_after>import $ from 'jquery';

import Evented from '@ember/object/evented';
import Service from '@ember/service';
import { getOwner } from '@ember/application';
import { assign } from '@ember/polyfills'
import { Promise, resolve, reject } from 'rsvp';
import { run } from '@ember/runloop';

export default Service.extend(Evented, {
  fbInitPromise: null,
  locale: null,
  refreshToken: true,

  FBInit(options = {}) {
    if (this.fbInitPromise) { return this.fbInitPromise; }

    const ENV = getOwner(this).resolveRegistration('config:environment');

    var initSettings = assign({}, ENV.FB || {}, options);

    // Detect language configuration and store it.
    const locale = initSettings.locale || 'en_US';
    this.locale = locale;

    if (ENV.FB && ENV.FB.skipInit) {
      this.fbInitPromise = resolve('skip init');
      return this.fbInitPromise;
    }

    var original = window.fbAsyncInit;
    if (!initSettings || !initSettings.appId || !initSettings.version) {
      return reject('No settings for init');
    }

    this.fbInitPromise = new Promise(function(resolve){
      window.fbAsyncInit = function() {
        window.FB.init(initSettings);
        run.next(null, resolve);
      };
      // URL for the SDK is built according to locale. Defaults to `en_US`.
      $.getScript(`https://connect.facebook.net/${locale}/sdk.js`, function() {
        // Do nothing here, wait for window.fbAsyncInit to be called.
      });
    }).then(function() {
      if (original) {
        window.fbAsyncInit = original;
        window.fbAsyncInit();
        window.fbAsyncInit.hasRun = true;
      }
    });

    return this.fbInitPromise;
  },

  setAccessToken(token) {
    this.accessToken = token;
    this.trigger('fb.setAccessToken', token);
    return token;
  },

  loginWith: function(token) {
    console.warn('DEPRECATED: please, use setAccessToken instead');
    this.setAccessToken(token);
  },

  _api(path) {
    var method = 'GET';
    var parameters = {};
    var arg;

    if (!path) { return reject('Please, provide a path for your request'); }

    switch (arguments.length) {
      case 2:
        arg = arguments[1];
        if (typeof arg === 'string') {
          method = arg;
        } else {
          parameters = arg;
        }
        break;
      case 3:
        method = arguments[1];
        parameters = arguments[2];
    }

    parameters = assign(parameters, {access_token: this.accessToken});

    return this.FBInit().then(function() {
      return new Promise(function(resolve, reject) {
        window.FB.api(path, method, parameters, function(response) {
          if (response.error) {
            run.next(null, reject, response.error);
            return;
          }

          run.next(null, resolve, response);
        });
      });
    });
  },

  api() {
    return this._api(...arguments).catch((error) => {
      if (this.refreshToken && (error.code === 190 || error.code === 2500)) {
        console.debug('Trying to refresh Facebook session an re-do the Facebook API request');
        return this.getLoginStatus().then((response) => {
          if (response.status === 'connected') {
            this.setAccessToken(response.authResponse.accessToken);
            return this._api(...arguments);
          }
          return reject(response);
        });
      }
      return reject(error);
    });
  },

  ui: function(params) {
    return this.FBInit().then(function() {
      return new Promise(function(resolve, reject) {
        window.FB.ui(params, function(response) {
          if (response && !response.error_code) {
            run.next(null, resolve, response);
            return;
          }

          run.next(null, reject, response);
        });
      });
    });
  },

  // Facebook Login Methods

  getLoginStatus: function(forceRequest) {
    return this.FBInit().then(function() {
      return new Promise(function(resolve) {
        window.FB.getLoginStatus(function(response) {
          run.next(null, resolve, response);
        }, forceRequest);
      });
    });
  },

  login: function(scope, options={}) {

    var service = this;
    var params = {scope: scope, return_scopes: true};

    if (options) {
      params = assign({}, params, options);
    }

    return this.FBInit().then(function() {
      return new Promise(function(resolve, reject) {
        window.FB.login(function(response) {
          if (response.authResponse) {
            service.accessToken = response.authResponse.accessToken;
            run.next(null, resolve, response);
          } else {
            run.next(null, reject, response);
          }
        }, params);
      });
    });
  },

  logout: function() {
    return this.FBInit().then(function() {
      return new Promise(function(resolve) {
        window.FB.logout(function(response) {
          run.next(null, resolve, response);
        });
      });
    });
  },

  getAuthResponse: function() {
    return window.FB.getAuthResponse();
  },

  xfbml_parse: function() {
    return this.FBInit().then(function() {
      return new Promise(function(resolve) {
        return window.FB.XFBML.parse(undefined, function() {
          run.next(null, resolve, 'XFBML.parse');
        });
      });
    });
  }
});
<|endoftext|>


<commit_before>(function () {
    var utils = {
        getIcon: function(question) {
            if (question.tag === 'upload') {
                return ' ';
            }
            return '';
        },
        getDisplay: function (question, MAXLEN) {
            return utils.getIcon(question) + utils.getLabel(question, MAXLEN)
                    + " (" + (question.hashtagValue || question.value) + ")";
        },
        getTruncatedDisplay: function (question, MAXLEN) {
            return utils.getIcon(question) + utils.getLabel(question, MAXLEN)
                    + " (" + utils.truncateValue(question.hashtagValue || question.value, MAXLEN) + ")";
        },
        getLabel: function (question, MAXLEN) {
            return utils.truncateLabel((question.repeat ? '- ' : '')
                    + question.label, question.tag === 'hidden' ? ' (Hidden)' : '', MAXLEN);
        },
        truncateLabel: function (label, suffix, MAXLEN) {
            suffix = suffix || "";
            var MAXLEN = MAXLEN || 40,
                maxlen = MAXLEN - suffix.length;
            return ((label.length <= maxlen) ? (label) : (label.slice(0, maxlen) + "...")) + suffix;
        },
        truncateValue: function (value, MAXLEN) {
            MAXLEN = MAXLEN || 40;
            return (value.length <= MAXLEN) ? (value) : (value.slice(0, MAXLEN/2) + "..." + value.slice(value.length - MAXLEN/2, value.length - 1));
        },
    };

    ko.bindingHandlers.questionsSelect = {
        /*
            The value used with this binding should be in the form:
            [
                {value: "someValue", label: "someLabel"},
                ...
            ]
         */
        init: function (element, valueAccessor) {
            $(element).after('

');
        },
        update: function (element, valueAccessor, allBindingsAccessor) {
            var optionObjects = ko.utils.unwrapObservable(valueAccessor());
            var allBindings = ko.utils.unwrapObservable(allBindingsAccessor());
            var value = ko.utils.unwrapObservable(allBindings.value);
            var $warning = $(element).next();
            if (value && !_.some(optionObjects, function (option) {
                return option.value === value;
            })) {
                var option = {
                    label: 'Unidentified Question (' + value + ')',
                    value: value
                };
                optionObjects = [option].concat(optionObjects);
                $warning.show().text('We cannot find this question in the allowed questions for this field. ' +
                    'It is likely that you deleted or renamed the question. ' +
                    'Please choose a valid question from the dropdown.');
            } else {
                $warning.hide();
            }
            _.delay(function () {
                $(element).select2({
                    placeholder: 'Select a Question',
                    data: {
                        results: _(optionObjects).map(function (o) {
                            return {id: o.value, text: utils.getDisplay(o), question: o};
                        })
                    },
                    formatSelection: function (o) {
                        return utils.getTruncatedDisplay(o.question);
                    },
                    formatResult: function (o) {
                        return utils.getTruncatedDisplay(o.question, 90);
                    },
                    dropdownCssClass: 'bigdrop'
                });
            });
            allBindings.optstrText = utils.getLabel;
        }
    };
}());

ko.bindingHandlers.casePropertyAutocomplete = {
    /*
     * Strip "attachment:" prefix and show icon for attachment properties.
     * Replace any spaces in free text with underscores.
     */
    init: function (element, valueAccessor) {
        $(element).on('textchange', function() {
            var $el = $(this);
            if ($el.val().match(/\s/)) {
                var pos = $el.caret('pos');
                $el.val($el.val().replace(/\s/g, '_'));
                $el.caret('pos', pos);
            }
        });
        ko.bindingHandlers.autocompleteAtwho.init(element, valueAccessor);
    },
    update: function (element, valueAccessor, allBindingsAccessor) {
        function wrappedValueAccessor() {
            return _.map(ko.unwrap(valueAccessor()), function(value) {
                if (value.indexOf("attachment:") === 0) {
                    var text = value.substring(11),
                        html = ' ' + text;
                    return {name: text, content: html};
                }
                return {name: value, content: value};
            })
        }
        ko.bindingHandlers.autocompleteAtwho.update(element, wrappedValueAccessor, allBindingsAccessor);
    },
};

// Originally from http://stackoverflow.com/a/17998880
ko.extenders.withPrevious = function (target) {
    // Define new properties for previous value and whether it's changed
    target.previous = ko.observable();

    // Subscribe to observable to update previous, before change.
    target.subscribe(function (v) {
        target.previous(v);
    }, null, 'beforeChange');

    // Return modified observable
    return target;
};

ko.bindingHandlers.numericValue = {
    init : function(element, valueAccessor, allBindingsAccessor) {
        var underlyingObservable = valueAccessor();
        var interceptor = ko.dependentObservable({
            read: underlyingObservable,
            write: function(value) {
                if ($.isNumeric(value)) {
                    underlyingObservable(parseFloat(value));
                } else if (value === '') {
                    underlyingObservable(null);
                }
            }
        });
        ko.bindingHandlers.value.init(element, function() { return interceptor; }, allBindingsAccessor);
    },
    update : ko.bindingHandlers.value.update
};
<commit_msg>Fix last letter not displaying in case selected case property<commit_after>(function () {
    var utils = {
        getIcon: function(question) {
            if (question.tag === 'upload') {
                return ' ';
            }
            return '';
        },
        getDisplay: function (question, MAXLEN) {
            return utils.getIcon(question) + utils.getLabel(question, MAXLEN)
                    + " (" + (question.hashtagValue || question.value) + ")";
        },
        getTruncatedDisplay: function (question, MAXLEN) {
            return utils.getIcon(question) + utils.getLabel(question, MAXLEN)
                    + " (" + utils.truncateValue(question.hashtagValue || question.value, MAXLEN) + ")";
        },
        getLabel: function (question, MAXLEN) {
            return utils.truncateLabel((question.repeat ? '- ' : '')
                    + question.label, question.tag === 'hidden' ? ' (Hidden)' : '', MAXLEN);
        },
        truncateLabel: function (label, suffix, MAXLEN) {
            suffix = suffix || "";
            var MAXLEN = MAXLEN || 40,
                maxlen = MAXLEN - suffix.length;
            return ((label.length <= maxlen) ? (label) : (label.slice(0, maxlen) + "...")) + suffix;
        },
        truncateValue: function (value, MAXLEN) {
            MAXLEN = MAXLEN || 40;
            return (value.length <= MAXLEN) ? (value) : (value.slice(0, MAXLEN/2) + "..." + value.slice(value.length - MAXLEN/2));
        },
    };

    ko.bindingHandlers.questionsSelect = {
        /*
            The value used with this binding should be in the form:
            [
                {value: "someValue", label: "someLabel"},
                ...
            ]
         */
        init: function (element, valueAccessor) {
            $(element).after('

');
        },
        update: function (element, valueAccessor, allBindingsAccessor) {
            var optionObjects = ko.utils.unwrapObservable(valueAccessor());
            var allBindings = ko.utils.unwrapObservable(allBindingsAccessor());
            var value = ko.utils.unwrapObservable(allBindings.value);
            var $warning = $(element).next();
            if (value && !_.some(optionObjects, function (option) {
                return option.value === value;
            })) {
                var option = {
                    label: 'Unidentified Question (' + value + ')',
                    value: value
                };
                optionObjects = [option].concat(optionObjects);
                $warning.show().text('We cannot find this question in the allowed questions for this field. ' +
                    'It is likely that you deleted or renamed the question. ' +
                    'Please choose a valid question from the dropdown.');
            } else {
                $warning.hide();
            }
            _.delay(function () {
                $(element).select2({
                    placeholder: 'Select a Question',
                    data: {
                        results: _(optionObjects).map(function (o) {
                            return {id: o.value, text: utils.getDisplay(o), question: o};
                        })
                    },
                    formatSelection: function (o) {
                        return utils.getTruncatedDisplay(o.question);
                    },
                    formatResult: function (o) {
                        return utils.getTruncatedDisplay(o.question, 90);
                    },
                    dropdownCssClass: 'bigdrop'
                });
            });
            allBindings.optstrText = utils.getLabel;
        }
    };
}());

ko.bindingHandlers.casePropertyAutocomplete = {
    /*
     * Strip "attachment:" prefix and show icon for attachment properties.
     * Replace any spaces in free text with underscores.
     */
    init: function (element, valueAccessor) {
        $(element).on('textchange', function() {
            var $el = $(this);
            if ($el.val().match(/\s/)) {
                var pos = $el.caret('pos');
                $el.val($el.val().replace(/\s/g, '_'));
                $el.caret('pos', pos);
            }
        });
        ko.bindingHandlers.autocompleteAtwho.init(element, valueAccessor);
    },
    update: function (element, valueAccessor, allBindingsAccessor) {
        function wrappedValueAccessor() {
            return _.map(ko.unwrap(valueAccessor()), function(value) {
                if (value.indexOf("attachment:") === 0) {
                    var text = value.substring(11),
                        html = ' ' + text;
                    return {name: text, content: html};
                }
                return {name: value, content: value};
            })
        }
        ko.bindingHandlers.autocompleteAtwho.update(element, wrappedValueAccessor, allBindingsAccessor);
    },
};

// Originally from http://stackoverflow.com/a/17998880
ko.extenders.withPrevious = function (target) {
    // Define new properties for previous value and whether it's changed
    target.previous = ko.observable();

    // Subscribe to observable to update previous, before change.
    target.subscribe(function (v) {
        target.previous(v);
    }, null, 'beforeChange');

    // Return modified observable
    return target;
};

ko.bindingHandlers.numericValue = {
    init : function(element, valueAccessor, allBindingsAccessor) {
        var underlyingObservable = valueAccessor();
        var interceptor = ko.dependentObservable({
            read: underlyingObservable,
            write: function(value) {
                if ($.isNumeric(value)) {
                    underlyingObservable(parseFloat(value));
                } else if (value === '') {
                    underlyingObservable(null);
                }
            }
        });
        ko.bindingHandlers.value.init(element, function() { return interceptor; }, allBindingsAccessor);
    },
    update : ko.bindingHandlers.value.update
};
<|endoftext|>


<commit_before>var db = require('./database');

// Collections
var Games = db.collection('games');
var AchievementsIndex = db.collection('achievements_index');
var CategoriesIndex = db.collection('categories_index');
var DevelopersIndex = db.collection('developers_index');
var GenresIndex = db.collection('genres_index');
var DemosIndex = db.collection('demos_index');
var LanguagesIndex = db.collection('languages_index');
var MetacriticIndex = db.collection('metacritic_index');
var PlatformsIndex = db.collection('platforms_index');
var PublishersIndex = db.collection('publishers_index');
var RecommendationsIndex = db.collection('recommendations_index');

/**
 * Parses information from a game to build the search indexes
 * @param id number
 * @param game {{}}
 * @param [_id] Mongo ID if updating
 */
exports.parseGame = function(id,game,_id){
    var i;
    var len;

    // Save the game, update if it already exists
    if(_id){
        game._id = _id;
    }
    Games.save(game);

    // Save the total achievement count for the game
    if(typeof game.achievements === 'object'){
        AchievementsIndex.update(
            {id: id, games: [id]},
            {$set: {
                total: game.achievements.total
            }},
            {upsert: true}
        );
    }

    // Save if it has a demo
    // TODO: Remove if no demo
    if(typeof game.demos === 'object'){
        DemosIndex.save({hasDemo: true, games: [id], id: id, demos: game.demos});
    }

    // Save the game categories
    if(typeof game.categories === 'object'){
        i = 0;
        len = game.categories.length;

        while(i < len){
            var category = game.categories[i];
            category.id = parseInt(category.id,10);
            updateCategoryType(category,id);
            i++;
        }
    }

    // Save the game developers
    if(typeof game.developers === 'object'){
        i = 0;
        len = game.developers.length;

        while(i < len){
            updateDevelopers(game.developers[i],id);
            i++;
        }
    }

    // Save the genres
    if(typeof game.genres === 'object'){
        i = 0;
        len = game.genres.length;

        while(i < len){
            var genre = game.genres[i];
            genre.id = parseInt(genre.id,10);
            updateGenreType(genre,id);
            i++;
        }
    }

    // Save the metacritic score
    if(typeof game.metacritic === 'object'){
        MetacriticIndex.update(
            {id: id, games: [id]},
            {$set: {
                score: game.metacritic.score
            }},
            {upsert: true}
        );
    }

    // Save the platforms
    if(typeof game.platforms === 'object'){
        updatePlatforms(game.platforms,id);
    }

    // Save the publishers
    if(typeof game.publishers === 'object'){
        i = 0;
        len = game.publishers.length;

        while(i < len){
            updatePublishers(game.publishers[i],id);
            i++;
        }
    }

    // Save the recommendations
    if(typeof game.recommendations === 'object'){
        RecommendationsIndex.update(
            {id: id, games: [id]},
            {$set : {
                total: game.recommendations.total
            }},
            {upsert: true}
        );
    }

    // Save the languages
    if(typeof game.supported_languages === 'string'){
        game.supported_languages.split(',').forEach(function(lang){
            updateLanguages(lang.split('<')[0].trim(),id);
        });
    }
};

/**
 * If category not found, creates category and category index, otherwise adds the game to that category's list array
 * @param category
 * @param id
 */
var updateCategoryType = function(category,id){
    CategoriesIndex.findOne({id:category.id},function(err,res){
        if(err || res == null){
            // Create the category type
            category.name = category.description;
            delete category.description;
            category.games = [id];
            category.total = 1;
            CategoriesIndex.save(category);
        } else {
            // Append new game to the category index
            CategoriesIndex.findAndModify({
                query: {id: category.id},
                update: {
                    $addToSet: {games: id},
                    $inc: {total: 1}
                }
            });
        }
    });
};

/**
 * Creates/updates the developers with a game id array
 * @param developer
 * @param id
 */
var updateDevelopers = function(developer,id){
    DevelopersIndex.findAndModify({
        query: {name: developer},
        update: {$addToSet: {games: id}},
        upsert: true
    });
};

/**
 * If genre not found, creates genre and genre index, otherwise adds the game to that genre's list array
 * @param genre
 * @param id
 */
var updateGenreType = function(genre,id){
    GenresIndex.findOne({id:genre.id},function(err,res){
        if(err || res == null){
            // Create the genre type
            genre.name = genre.description;
            delete genre.description;
            genre.games = [id];
            genre.total = 1;
            GenresIndex.save(genre);
        } else {
            // Append new game to the genre index
            GenresIndex.findAndModify({
                query: {id: genre.id},
                update: {
                    $addToSet: {games: id},
                    $inc: {total: 1}
                }
            });
        }
    });
};

/**
 * Creates/updates the platforms with a game id array, or removes the game from a platform
 * @param platforms
 * @param id
 */
var updatePlatforms = function(platforms,id){
    for(var platform in platforms){
        var available = platforms[platform];
        var action = available ? {$addToSet: {games: id}} : {$pull: {games: id}};

        PlatformsIndex.findAndModify({
            query: {name: platform},
            update: action,
            upsert: true
        });
    }
};

/**
 * Creates/updates the publishers with a game id array
 * @param publisher
 * @param id
 */
var updatePublishers = function(publisher,id){
    PublishersIndex.findAndModify({
        query: {name: publisher},
        update: {$addToSet: {games: id}},
        upsert: true
    });
};

/**
 * If language not found, creates language and language index, otherwise adds the game to that language's list array
 * @param language
 * @param id
 */
var updateLanguages = function(language,id){
    LanguagesIndex.findAndModify({
        query: {name: language},
        update: {$addToSet: {games: id}},
        upsert: true
    });
};<commit_msg>Allow updating the game object<commit_after>var db = require('./database');

// Collections
var Games = db.collection('games');
var AchievementsIndex = db.collection('achievements_index');
var CategoriesIndex = db.collection('categories_index');
var DevelopersIndex = db.collection('developers_index');
var GenresIndex = db.collection('genres_index');
var DemosIndex = db.collection('demos_index');
var LanguagesIndex = db.collection('languages_index');
var MetacriticIndex = db.collection('metacritic_index');
var PlatformsIndex = db.collection('platforms_index');
var PublishersIndex = db.collection('publishers_index');
var RecommendationsIndex = db.collection('recommendations_index');

/**
 * Parses information from a game to build the search indexes
 * @param id number
 * @param game {{}}
 * @param [_id] Mongo ID if updating
 */
exports.parseGame = function(id,game,_id){
    var i;
    var len;

    // Save the game, update if it already exists
    if(_id){
        game._id = _id;
    }
    Games.update(
        {steam_appid: id},
        {$set: game},
        {upsert: true}
    );

    // Save the total achievement count for the game
    if(typeof game.achievements === 'object'){
        AchievementsIndex.update(
            {id: id, games: [id]},
            {$set: {
                total: game.achievements.total
            }},
            {upsert: true}
        );
    }

    // Save if it has a demo
    // TODO: Remove if no demo
    if(typeof game.demos === 'object'){
        DemosIndex.save({hasDemo: true, games: [id], id: id, demos: game.demos});
    }

    // Save the game categories
    if(typeof game.categories === 'object'){
        i = 0;
        len = game.categories.length;

        while(i < len){
            var category = game.categories[i];
            category.id = parseInt(category.id,10);
            updateCategoryType(category,id);
            i++;
        }
    }

    // Save the game developers
    if(typeof game.developers === 'object'){
        i = 0;
        len = game.developers.length;

        while(i < len){
            updateDevelopers(game.developers[i],id);
            i++;
        }
    }

    // Save the genres
    if(typeof game.genres === 'object'){
        i = 0;
        len = game.genres.length;

        while(i < len){
            var genre = game.genres[i];
            genre.id = parseInt(genre.id,10);
            updateGenreType(genre,id);
            i++;
        }
    }

    // Save the metacritic score
    if(typeof game.metacritic === 'object'){
        MetacriticIndex.update(
            {id: id, games: [id]},
            {$set: {
                score: game.metacritic.score
            }},
            {upsert: true}
        );
    }

    // Save the platforms
    if(typeof game.platforms === 'object'){
        updatePlatforms(game.platforms,id);
    }

    // Save the publishers
    if(typeof game.publishers === 'object'){
        i = 0;
        len = game.publishers.length;

        while(i < len){
            updatePublishers(game.publishers[i],id);
            i++;
        }
    }

    // Save the recommendations
    if(typeof game.recommendations === 'object'){
        RecommendationsIndex.update(
            {id: id, games: [id]},
            {$set : {
                total: game.recommendations.total
            }},
            {upsert: true}
        );
    }

    // Save the languages
    if(typeof game.supported_languages === 'string'){
        game.supported_languages.split(',').forEach(function(lang){
            updateLanguages(lang.split('<')[0].trim(),id);
        });
    }
};

/**
 * If category not found, creates category and category index, otherwise adds the game to that category's list array
 * @param category
 * @param id
 */
var updateCategoryType = function(category,id){
    CategoriesIndex.findOne({id:category.id},function(err,res){
        if(err || res == null){
            // Create the category type
            category.name = category.description;
            delete category.description;
            category.games = [id];
            category.total = 1;
            CategoriesIndex.save(category);
        } else {
            // Append new game to the category index
            CategoriesIndex.findAndModify({
                query: {id: category.id},
                update: {
                    $addToSet: {games: id},
                    $inc: {total: 1}
                }
            });
        }
    });
};

/**
 * Creates/updates the developers with a game id array
 * @param developer
 * @param id
 */
var updateDevelopers = function(developer,id){
    DevelopersIndex.findAndModify({
        query: {name: developer},
        update: {$addToSet: {games: id}},
        upsert: true
    });
};

/**
 * If genre not found, creates genre and genre index, otherwise adds the game to that genre's list array
 * @param genre
 * @param id
 */
var updateGenreType = function(genre,id){
    GenresIndex.findOne({id:genre.id},function(err,res){
        if(err || res == null){
            // Create the genre type
            genre.name = genre.description;
            delete genre.description;
            genre.games = [id];
            genre.total = 1;
            GenresIndex.save(genre);
        } else {
            // Append new game to the genre index
            GenresIndex.findAndModify({
                query: {id: genre.id},
                update: {
                    $addToSet: {games: id},
                    $inc: {total: 1}
                }
            });
        }
    });
};

/**
 * Creates/updates the platforms with a game id array, or removes the game from a platform
 * @param platforms
 * @param id
 */
var updatePlatforms = function(platforms,id){
    for(var platform in platforms){
        var available = platforms[platform];
        var action = available ? {$addToSet: {games: id}} : {$pull: {games: id}};

        PlatformsIndex.findAndModify({
            query: {name: platform},
            update: action,
            upsert: true
        });
    }
};

/**
 * Creates/updates the publishers with a game id array
 * @param publisher
 * @param id
 */
var updatePublishers = function(publisher,id){
    PublishersIndex.findAndModify({
        query: {name: publisher},
        update: {$addToSet: {games: id}},
        upsert: true
    });
};

/**
 * If language not found, creates language and language index, otherwise adds the game to that language's list array
 * @param language
 * @param id
 */
var updateLanguages = function(language,id){
    LanguagesIndex.findAndModify({
        query: {name: language},
        update: {$addToSet: {games: id}},
        upsert: true
    });
};<|endoftext|>


<commit_before>import React from 'react';
import { map } from 'lodash';
import './signup-card.scss';
import PostTile from '../PostTile';
import { RestApiClient } from '@dosomething/gateway';
import { extractPostsFromSignups } from '../../helpers';

class SignupCard extends React.Component {
  render() {
    const signup = this.props.signup;
    const campaign = this.props.campaign;
    const gallerySize = 4;

    const extraPostCount = signup.posts.data.length - gallerySize;

    const posts = signup.posts.data.slice(0, gallerySize).map((post, index) => {
      return <PostTile key={index} details={post} />;
    });

    const signupUrl = `/signups/${signup.signup_id}`;

    return (

{campaign ? campaign.title : signup.campaign_id}

Why Statement

{signup.why_participated}

              { signup.quantity ?

{signup.quantity}

{campaign ? `${campaign.reportback_info.noun} ${campaign.reportback_info.verb}` : '' }

              : null }
              {posts.length ?

Items

                    {posts}
                    {extraPostCount > 0 ?

+{extraPostCount}

                    : null}

              : null }

    )
  }
}

export default SignupCard;
<commit_msg>exposes campaign run id<commit_after>import React from 'react';
import { map } from 'lodash';
import './signup-card.scss';
import PostTile from '../PostTile';
import { RestApiClient } from '@dosomething/gateway';
import { extractPostsFromSignups } from '../../helpers';

class SignupCard extends React.Component {
  render() {
    const signup = this.props.signup;
    const campaign = this.props.campaign;
    const gallerySize = 4;

    const extraPostCount = signup.posts.data.length - gallerySize;

    const posts = signup.posts.data.slice(0, gallerySize).map((post, index) => {
      return <PostTile key={index} details={post} />;
    });
    const signupUrl = `/signups/${signup.signup_id}`;

    return (

{campaign ? campaign.title : signup.campaign_id}

Campaign Run ID: {signup.campaign_run_id}

Why Statement

{signup.why_participated}

              { signup.quantity ?

{signup.quantity}

{campaign ? `${campaign.reportback_info.noun} ${campaign.reportback_info.verb}` : '' }

              : null }
              {posts.length ?

Items

                    {posts}
                    {extraPostCount > 0 ?

+{extraPostCount}

                    : null}

              : null }

    )
  }
}

export default SignupCard;
<|endoftext|>


<commit_before>;(function(exports) {
  var Isla;
  if(typeof module !== 'undefined' && module.exports) { // node
    Isla = require('../node_modules/isla/src/isla.js').Isla;
  } else { // browser
    Isla = window.Isla;
  }

  var demo, terminal, textGrabber;
  var DemoRunner = function(Demo) {
    $(document).ready(function() {
      demo = new Demo($('#canvas')[0].getContext('2d'));
      var envStore = new EnvStore();
      envStore.write({ event:"temp", env:Isla.Library.getInitialEnv() });
      envStore.write({ event:"commit" });

      terminal = new Terminal();
      terminal.events.bind(this, 'keypress', function(line) {
        runDemoCode(line, envStore);
      });

      terminal.events.bind(this, 'history', function(line) {
        runDemoCode(line, envStore);
      });

      terminal.events.bind(this, 'submit', function(line) {
        var result;
        try {
          var env = Isla.Interpreter.interpret(line, envStore.latest());
          envStore.write({ event:"commit" });
          result = { msg: env.ret || "", error: false };
        } catch(e) {
          result = { msg: e.message, error: true };
        }

        terminal.setPromptText({
          html: getHighlightedSyntax(line),
          cursor: false
        });

        terminal.setResult(result);
      });

      var helper = new Helper(terminal, envStore);
    });
  };

  // Two code run scenarios:
  // - As you type (fn below). Fails silently. Updates demo w new env.
  // - On submit.  Prints any errors.  Doesn't update demo (don't want new
  //   random vals when code re-run).
  var runDemoCode = function(line, envStore) {
    try {
      var env = Isla.Interpreter.interpret(line, envStore.latest());
      env.ctx = demo.write(env.ctx);
      envStore.write({ event:"temp", env:env });
    } catch(e) {
      // fail silently
    }

    terminal.setPromptText({ html: getHighlightedSyntax(line), cursor: true });
  };

  var getHighlightedSyntax = function(code) {
    var markup = Highlighter.highlight(code);
    if (markup === undefined) {
      markup = code;
    }

    return markup;
  };

  exports.DemoRunner = DemoRunner;
})(typeof exports === 'undefined' ? this : exports)
<commit_msg>Restart from last committed env on every keystroke.<commit_after>;(function(exports) {
  var Isla;
  if(typeof module !== 'undefined' && module.exports) { // node
    Isla = require('../node_modules/isla/src/isla.js').Isla;
  } else { // browser
    Isla = window.Isla;
  }

  var demo, terminal, textGrabber;
  var DemoRunner = function(Demo) {
    $(document).ready(function() {
      demo = new Demo($('#canvas')[0].getContext('2d'));
      var envStore = new EnvStore();
      envStore.write({ event:"temp", env:Isla.Library.getInitialEnv() });
      envStore.write({ event:"commit" });

      terminal = new Terminal();
      terminal.events.bind(this, 'keypress', function(line) {
        runDemoCode(line, envStore);
      });

      terminal.events.bind(this, 'history', function(line) {
        runDemoCode(line, envStore);
      });

      terminal.events.bind(this, 'submit', function(line) {
        var result;
        try {
          var env = Isla.Interpreter.interpret(line, envStore.latest());
          envStore.write({ event:"commit" });
          result = { msg: env.ret || "", error: false };
        } catch(e) {
          result = { msg: e.message, error: true };
        }

        terminal.setPromptText({
          html: getHighlightedSyntax(line),
          cursor: false
        });

        terminal.setResult(result);
      });

      var helper = new Helper(terminal, envStore);
    });
  };

  // Two code run scenarios:
  // - As you type (fn below). Fails silently. Updates demo w new env.
  // - On submit.  Prints any errors.  Doesn't update demo (don't want new
  //   random vals when code re-run).
  var runDemoCode = function(line, envStore) {
    try {
      var env = Isla.Interpreter.interpret(line, envStore.latestCommitted());
      env.ctx = demo.write(env.ctx);
      envStore.write({ event:"temp", env:env });
    } catch(e) {
      // fail silently
    }

    terminal.setPromptText({ html: getHighlightedSyntax(line), cursor: true });
  };

  var getHighlightedSyntax = function(code) {
    var markup = Highlighter.highlight(code);
    if (markup === undefined) {
      markup = code;
    }

    return markup;
  };

  exports.DemoRunner = DemoRunner;
})(typeof exports === 'undefined' ? this : exports)
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import Select from 'react-select';
import 'react-select/dist/react-select.css';

import DashboardHelpers from '../DashboardHelpers';
import StudentsTable from '../StudentsTable';
import DashboardBarChart from '../DashboardBarChart';
import DashRangeButtons from '../DashRangeButtons';
import {latestNoteDateText} from '../../../helpers/latestNoteDateText';
import {sortByGrade} from '../../../helpers/SortHelpers';
import ExperimentalBanner from '../../../components/ExperimentalBanner';

class SchoolDisciplineDashboard extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      startDate: DashboardHelpers.schoolYearStart(),
      selectedChart: 'location',
      selectedCategory: null
    };
    this.setDate = this.setDate.bind(this);
    this.setStudentList = this.setStudentList.bind(this);
    this.resetStudentList = this.resetStudentList.bind(this);
    this.selectChart = this.selectChart.bind(this);
  }

  setDate(range) {
    this.setState({
      startDate: moment.unix(range[0]).format("YYYY-MM-DD")
    });
  }
  setStudentList(highchartsEvent) {
    this.setState({selectedCategory: highchartsEvent.point.category});
  }
  resetStudentList() {
    this.setState({selectedCategory: null});
  }
  selectChart(selection) {
    this.setState({selectedChart: selection.value, selectedCategory: null});
  }

  filterIncidentDates(incidents) {
    return incidents.filter((incident) => {
      return moment.utc(incident.occurred_at).isSameOrAfter(moment.utc(this.state.startDate));
    });
  }

  studentDisciplineIncidentCounts(incidentCategory) {
    let studentDisciplineIncidentCounts = {};
    const selectedChartData = this.getChartData(this.state.selectedChart);
    const incidents = incidentCategory ? selectedChartData.disciplineIncidents[incidentCategory] : this.props.schoolDisciplineEvents;
    this.filterIncidentDates(incidents).forEach((incident) => {
      studentDisciplineIncidentCounts[incident.student_id] = studentDisciplineIncidentCounts[incident.student_id] || 0;
      studentDisciplineIncidentCounts[incident.student_id]++;
    });
    return studentDisciplineIncidentCounts;
  }

  getChartData(selectedChart) {
    return {
      type: selectedChart,
      disciplineIncidents: _.groupBy(this.props.schoolDisciplineEvents, selectedChart),
      title: "Incidents by " + selectedChart};
  }

  sortChartKeys(chartKeys) {
    switch(this.state.selectedChart) {
    case 'time': return this.sortedTimes(chartKeys);
    case 'day': return this.sortedDays(chartKeys);
    case 'grade': return this.sortedGrades(chartKeys);
    case 'classroom': return this.sortedClassrooms(chartKeys);
    default: return chartKeys;
    }
  }

  sortedDays(chartKeys) {
    return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
  }

  sortedTimes(chartKeys) {
    //chartKeys will either contain a time like "4:00 pm", "10:00 am", or "Not Logged"
    return chartKeys.sort((a, b) => {
      if (a == "Not Logged") return -1;
      if (b == "Not Logged") return 1;
      return new Date('1970/01/01 ' + a) - new Date('1970/01/01 ' + b);
    });
  }

  sortedGrades(chartKeys) {
    return chartKeys.sort((a,b) => sortByGrade(a,b));

  }

  sortedClassrooms(chartKeys) {
    const chart = this.getChartData(this.state.selectedChart);
    return chartKeys.sort((a,b) => {
      return chart.disciplineIncidents[b].length - chart.disciplineIncidents[a].length;
    });
  }

  render() {
    const selectedChart = this.getChartData(this.state.selectedChart);
    const chartOptions = [
      {value: 'location', label: 'Location'},
      {value: 'time', label: 'Time'},
      {value: 'classroom', label: 'Classroom'},
      {value: 'grade', label: 'Grade'},
      {value: 'day', label: 'Day'},
      {value: 'offense', label: 'Offense'},
    ];

    return(

      <ExperimentalBanner />

            {this.renderRangeSelector()}
            {this.renderStudentDisciplineTable()}

                Incidents by:

              <Select
                value={this.state.selectedChart}
                onChange={this.selectChart}
                options={chartOptions}
                style={styles.dropdown}
              />

           {this.renderDisciplineChart(selectedChart)}

    );
  }

  renderDisciplineChart(selectedChart) {
    const categories = this.sortChartKeys(Object.keys(selectedChart.disciplineIncidents));
    const seriesData = categories.map((type) => {
      if (!selectedChart.disciplineIncidents[type]) return [];
      const incidents = this.filterIncidentDates(selectedChart.disciplineIncidents[type]);
      return [type, incidents.length];
    });

    return (
        <DashboardBarChart
          id = "Discipline"
          categories = {{categories: categories}}
          seriesData = {seriesData}
          titleText = {null}
          measureText = {'Number of Incidents'}
          tooltip = {{
            pointFormat: 'Total incidents: {point.y}'}}
          onColumnClick = {this.setStudentList}
          onBackgroundClick = {this.resetStudentList}/>
    );
  }

  renderStudentDisciplineTable() {
    const students = this.props.dashboardStudents;
    const studentDisciplineIncidentCounts = this.studentDisciplineIncidentCounts(this.state.selectedCategory);
    let rows =[];
    students.forEach((student) => {
      rows.push({
        id: student.id,
        first_name: student.first_name,
        last_name: student.last_name,
        last_sst_date_text: latestNoteDateText(300, student.sst_notes),
        events: studentDisciplineIncidentCounts[student.id] || 0,
        grade: student.grade
      });
    });

    return (
      <StudentsTable
        rows = {rows}
        selectedCategory = {this.state.selectedCategory}
        incidentType={"Incidents"}
        resetFn={this.resetStudentList}/>
    );
  }

  renderRangeSelector() {
    const ninetyDaysAgo = moment.utc().subtract(90, 'days').format("YYYY-MM-DD");
    const fortyFiveDaysAgo = moment.utc().subtract(45, 'days').format("YYYY-MM-DD");
    const schoolYearStart = DashboardHelpers.schoolYearStart();
    return (

        <DashRangeButtons
          schoolYearFilter={() => this.setState({startDate: schoolYearStart, selectedRange: 'School Year'})}
          ninetyDayFilter={() => this.setState({startDate: ninetyDaysAgo, selectedRange: '90 Days'})}
          fortyFiveDayFilter={() => this.setState({startDate: fortyFiveDaysAgo, selectedRange: '45 Days'})}/>

    );
  }
}

SchoolDisciplineDashboard.propTypes = {
  dashboardStudents: PropTypes.array.isRequired,
  schoolDisciplineEvents: PropTypes.arrayOf(PropTypes.shape({
    student_id: PropTypes.number.isRequired, //ID of student involved in incident
    location: PropTypes.string, //Place where incident occurred
    time: PropTypes.string, //Time of day for incident - NULL if no specific time recorded
    classroom: PropTypes.string, //Name of student's homeroom teacher
    student_grade: PropTypes.string, //Grade of student
    day: PropTypes.string, //Day of week on which incident occurred
    offense: PropTypes.string, //Specific type of incident
    student_race: PropTypes.string, //Race of student
    occurred_at: PropTypes.string, //Date for incident, used in filtering specific date ranges
    last_sst_date_text: PropTypes.string //Date of last SST meeting
  })).isRequired
};

export default SchoolDisciplineDashboard;

const styles = {
  graphTitle: {
    display: 'flex',
    justifyContent: 'center',
    marginTop: '20px'
  },
  titleText: {
    fontSize: '18px',
    marginRight: '10px',
    alignSelf: 'center'
  },
  dropdown: {
    width: '200px'
  }
};
<commit_msg>restrict student table to selected category<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import Select from 'react-select';
import 'react-select/dist/react-select.css';

import DashboardHelpers from '../DashboardHelpers';
import StudentsTable from '../StudentsTable';
import DashboardBarChart from '../DashboardBarChart';
import DashRangeButtons from '../DashRangeButtons';
import {latestNoteDateText} from '../../../helpers/latestNoteDateText';
import {sortByGrade} from '../../../helpers/SortHelpers';
import ExperimentalBanner from '../../../components/ExperimentalBanner';

class SchoolDisciplineDashboard extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      startDate: DashboardHelpers.schoolYearStart(),
      selectedChart: 'location',
      selectedCategory: null
    };
    this.setDate = this.setDate.bind(this);
    this.setStudentList = this.setStudentList.bind(this);
    this.resetStudentList = this.resetStudentList.bind(this);
    this.selectChart = this.selectChart.bind(this);
  }

  setDate(range) {
    this.setState({
      startDate: moment.unix(range[0]).format("YYYY-MM-DD")
    });
  }
  setStudentList(highchartsEvent) {
    this.setState({selectedCategory: highchartsEvent.point.category});
  }
  resetStudentList() {
    this.setState({selectedCategory: null});
  }
  selectChart(selection) {
    this.setState({selectedChart: selection.value, selectedCategory: null});
  }

  filterIncidentDates(incidents) {
    return incidents.filter((incident) => {
      return moment.utc(incident.occurred_at).isSameOrAfter(moment.utc(this.state.startDate));
    });
  }

  studentDisciplineIncidentCounts(incidentCategory) {
    let studentDisciplineIncidentCounts = {};
    const selectedChartData = this.getChartData(this.state.selectedChart);
    const incidents = incidentCategory ? selectedChartData.disciplineIncidents[incidentCategory] : this.props.schoolDisciplineEvents;
    this.filterIncidentDates(incidents).forEach((incident) => {
      studentDisciplineIncidentCounts[incident.student_id] = studentDisciplineIncidentCounts[incident.student_id] || 0;
      studentDisciplineIncidentCounts[incident.student_id]++;
    });
    return studentDisciplineIncidentCounts;
  }

  getChartData(selectedChart) {
    return {
      type: selectedChart,
      disciplineIncidents: _.groupBy(this.props.schoolDisciplineEvents, selectedChart),
      title: "Incidents by " + selectedChart};
  }

  sortChartKeys(chartKeys) {
    switch(this.state.selectedChart) {
    case 'time': return this.sortedTimes(chartKeys);
    case 'day': return this.sortedDays(chartKeys);
    case 'grade': return this.sortedGrades(chartKeys);
    case 'classroom': return this.sortedClassrooms(chartKeys);
    default: return chartKeys;
    }
  }

  sortedDays(chartKeys) {
    return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
  }

  sortedTimes(chartKeys) {
    //chartKeys will either contain a time like "4:00 pm", "10:00 am", or "Not Logged"
    return chartKeys.sort((a, b) => {
      if (a == "Not Logged") return -1;
      if (b == "Not Logged") return 1;
      return new Date('1970/01/01 ' + a) - new Date('1970/01/01 ' + b);
    });
  }

  sortedGrades(chartKeys) {
    return chartKeys.sort((a,b) => sortByGrade(a,b));

  }

  sortedClassrooms(chartKeys) {
    const chart = this.getChartData(this.state.selectedChart);
    return chartKeys.sort((a,b) => {
      return chart.disciplineIncidents[b].length - chart.disciplineIncidents[a].length;
    });
  }

  //For grades and classrooms, the students table should only show the relevant students
  groupStudents() {
    if (this.state.selectedChart === 'grade' && this.state.selectedCategory) {
      return this.props.dashboardStudents.filter(student => student.grade === this.state.selectedCategory);
    } else if (this.state.selectedChart === 'classroom' && this.state.selectedCategory) {
      return this.props.dashboardStudents.filter(student => student.homeroom_label === this.state.selectedCategory);
    } else return this.props.dashboardStudents;
  }

  render() {
    const selectedChart = this.getChartData(this.state.selectedChart);
    const chartOptions = [
      {value: 'location', label: 'Location'},
      {value: 'time', label: 'Time'},
      {value: 'classroom', label: 'Classroom'},
      {value: 'grade', label: 'Grade'},
      {value: 'day', label: 'Day'},
      {value: 'offense', label: 'Offense'},
    ];

    return(

      <ExperimentalBanner />

            {this.renderRangeSelector()}
            {this.renderStudentDisciplineTable()}

                Incidents by:

              <Select
                value={this.state.selectedChart}
                onChange={this.selectChart}
                options={chartOptions}
                style={styles.dropdown}
              />

           {this.renderDisciplineChart(selectedChart)}

    );
  }

  renderDisciplineChart(selectedChart) {
    const categories = this.sortChartKeys(Object.keys(selectedChart.disciplineIncidents));
    const seriesData = categories.map((type) => {
      if (!selectedChart.disciplineIncidents[type]) return [];
      const incidents = this.filterIncidentDates(selectedChart.disciplineIncidents[type]);
      return [type, incidents.length];
    });

    return (
        <DashboardBarChart
          id = "Discipline"
          categories = {{categories: categories}}
          seriesData = {seriesData}
          titleText = {null}
          measureText = {'Number of Incidents'}
          tooltip = {{
            pointFormat: 'Total incidents: {point.y}'}}
          onColumnClick = {this.setStudentList}
          onBackgroundClick = {this.resetStudentList}/>
    );
  }

  renderStudentDisciplineTable() {
    const students = this.groupStudents();
    const studentDisciplineIncidentCounts = this.studentDisciplineIncidentCounts(this.state.selectedCategory);
    let rows =[];
    students.forEach((student) => {
      rows.push({
        id: student.id,
        first_name: student.first_name,
        last_name: student.last_name,
        last_sst_date_text: latestNoteDateText(300, student.sst_notes),
        events: studentDisciplineIncidentCounts[student.id] || 0,
        grade: student.grade
      });
    });

    return (
      <StudentsTable
        rows = {rows}
        selectedCategory = {this.state.selectedCategory}
        incidentType={"Incidents"}
        resetFn={this.resetStudentList}/>
    );
  }

  renderRangeSelector() {
    const ninetyDaysAgo = moment.utc().subtract(90, 'days').format("YYYY-MM-DD");
    const fortyFiveDaysAgo = moment.utc().subtract(45, 'days').format("YYYY-MM-DD");
    const schoolYearStart = DashboardHelpers.schoolYearStart();
    return (

        <DashRangeButtons
          schoolYearFilter={() => this.setState({startDate: schoolYearStart, selectedRange: 'School Year'})}
          ninetyDayFilter={() => this.setState({startDate: ninetyDaysAgo, selectedRange: '90 Days'})}
          fortyFiveDayFilter={() => this.setState({startDate: fortyFiveDaysAgo, selectedRange: '45 Days'})}/>

    );
  }
}

SchoolDisciplineDashboard.propTypes = {
  dashboardStudents: PropTypes.array.isRequired,
  schoolDisciplineEvents: PropTypes.arrayOf(PropTypes.shape({
    student_id: PropTypes.number.isRequired, //ID of student involved in incident
    location: PropTypes.string, //Place where incident occurred
    time: PropTypes.string, //Time of day for incident - NULL if no specific time recorded
    classroom: PropTypes.string, //Name of student's homeroom teacher
    student_grade: PropTypes.string, //Grade of student
    day: PropTypes.string, //Day of week on which incident occurred
    offense: PropTypes.string, //Specific type of incident
    student_race: PropTypes.string, //Race of student
    occurred_at: PropTypes.string, //Date for incident, used in filtering specific date ranges
    last_sst_date_text: PropTypes.string //Date of last SST meeting
  })).isRequired
};

export default SchoolDisciplineDashboard;

const styles = {
  graphTitle: {
    display: 'flex',
    justifyContent: 'center',
    marginTop: '20px'
  },
  titleText: {
    fontSize: '18px',
    marginRight: '10px',
    alignSelf: 'center'
  },
  dropdown: {
    width: '200px'
  }
};
<|endoftext|>


<commit_before>// *************************************************************************
// Copyright (c) 2014-2017, SUSE LLC
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of SUSE LLC nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// *************************************************************************
//
// app/int-lib.js
//
"use strict";

define ([
    'jquery',
    'ajax',
    'app/act-lib',
    'current-user',
    'datetime',
    'lib',
    'stack',
], function (
    $,
    ajax,
    actLib,
    currentUser,
    datetime,
    coreLib,
    stack,
) {

    var emptyObj = {
            "iNdate": "",
            "iNtimerange": "",
            "iNact": "",
            "iNdesc": ""
        },
        genIntvl = function (date, timerange) {
            var ctr = datetime.canonicalizeTimeRange(timerange);
            if (ctr === null) {
                coreLib.displayError('Time range ->' + timerange + '<- is invalid');
                stack.restart();
            } else {
                return '[ "' +
                       date +
                       ' ' +
                       ctr[0] +
                       '", "' +
                       date +
                       ' ' +
                       ctr[1] +
                       '" )';
            }
        },
        rest = {
            "method": 'POST',
            "path": 'interval/new'
        },
        sc = function (st) {
            console.log("AJAX: " + rest["method"] + " " + rest["path"] + " returned", st);
            coreLib.displayResult("Interval " + st.payload.iid + " created");
            stack.restart(emptyObj);
        },
        fc = function (st) {
            console.log("AJAX: " + rest["method"] + " " + rest["path"] + " failed", st);
            stack.restart(undefined, { "resultLine": st.payload.message });
        };

    // here is where we define methods implementing the various
    // interval-related actions (see daction-start.js)
    return {

        createSingleIntSave: function (obj) {
            // obj will look like this:
            // {
            //     iNdate: "foo bar in a box",
            //     iNtimerange: "25:00-27:00",
            //     iNact: "LOITERING",
            //     iNdesc: "none",
            //     mm: true
            // }
            // any of the above properties may be present or missing
            // also, there may or may not be an acTaid property with the AID of
            // the chosen activity
            console.log("createSingleIntSave called with obj", obj);
            var cu = currentUser('obj');

            // check that all mandatory properties are present
            if (! obj.hasOwnProperty('iNdate')) {
                stack.restart(undefined, { "resultLine": "Interval date missing" });
            }
            if (obj.iNdate.length === 0) {
                stack.restart(undefined, { "resultLine": "Interval date missing" });
            }

            if (! obj.hasOwnProperty('iNtimerange')) {
                stack.restart(undefined, { "resultLine": "Interval time range missing" });
            }
            if (obj.iNtimerange.length === 0) {
                stack.restart(undefined, { "resultLine": "Interval time range missing" });
            }

            if (! obj.hasOwnProperty('iNact')) {
                stack.restart(undefined, { "resultLine": "Interval activity code missing" });
            }
            if (obj.iNact.length === 0) {
                stack.restart(undefined, { "resultLine": "Interval activity code missing" });
            }
            if (! obj.hasOwnProperty('acTaid')) {
                console.log("Looking up activity " + obj.iNact + " in cache");
                obj.acTaid = actLib.getActByCode(obj.iNact).aid;
                if (! obj.acTaid) {
                    stack.restart(undefined, { "resultLine": 'Activity ' + obj.iNact + ' not found' });
                }
            }

            rest.body = {
                "eid": cu.eid,
                "aid": obj.acTaid,
                "intvl": genIntvl(obj.iNdate, obj.iNtimerange),
                "long_desc": obj.iNdesc,
                "remark": null,
            }
            ajax(rest, sc, fc);
        },

    };

});
<commit_msg>js: int-lib: cursor placement after successful interval creation<commit_after>// *************************************************************************
// Copyright (c) 2014-2017, SUSE LLC
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of SUSE LLC nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// *************************************************************************
//
// app/int-lib.js
//
"use strict";

define ([
    'jquery',
    'ajax',
    'app/act-lib',
    'current-user',
    'datetime',
    'lib',
    'stack',
], function (
    $,
    ajax,
    actLib,
    currentUser,
    datetime,
    coreLib,
    stack,
) {

    var emptyObj = {
            "iNdate": "",
            "iNtimerange": "",
            "iNact": "",
            "iNdesc": ""
        },
        genIntvl = function (date, timerange) {
            var ctr = datetime.canonicalizeTimeRange(timerange);
            if (ctr === null) {
                coreLib.displayError('Time range ->' + timerange + '<- is invalid');
                stack.restart();
            } else {
                return '[ "' +
                       date +
                       ' ' +
                       ctr[0] +
                       '", "' +
                       date +
                       ' ' +
                       ctr[1] +
                       '" )';
            }
        },
        rest = {
            "method": 'POST',
            "path": 'interval/new'
        },
        sc = function (st) {
            console.log("AJAX: " + rest["method"] + " " + rest["path"] + " returned", st);
            stack.restart(
                emptyObj,
                {
                    "resultLine": "Interval " + st.payload.iid + " created",
                    "inputId": "iNdate",
                }
            );
        },
        fc = function (st) {
            console.log("AJAX: " + rest["method"] + " " + rest["path"] + " failed", st);
            stack.restart(undefined, { "resultLine": st.payload.message });
        };

    // here is where we define methods implementing the various
    // interval-related actions (see daction-start.js)
    return {

        createSingleIntSave: function (obj) {
            // obj will look like this:
            // {
            //     iNdate: "foo bar in a box",
            //     iNtimerange: "25:00-27:00",
            //     iNact: "LOITERING",
            //     iNdesc: "none",
            //     mm: true
            // }
            // any of the above properties may be present or missing
            // also, there may or may not be an acTaid property with the AID of
            // the chosen activity
            console.log("createSingleIntSave called with obj", obj);
            var cu = currentUser('obj');

            // check that all mandatory properties are present
            if (! obj.hasOwnProperty('iNdate')) {
                stack.restart(undefined, { "resultLine": "Interval date missing" });
            }
            if (obj.iNdate.length === 0) {
                stack.restart(undefined, { "resultLine": "Interval date missing" });
            }

            if (! obj.hasOwnProperty('iNtimerange')) {
                stack.restart(undefined, { "resultLine": "Interval time range missing" });
            }
            if (obj.iNtimerange.length === 0) {
                stack.restart(undefined, { "resultLine": "Interval time range missing" });
            }

            if (! obj.hasOwnProperty('iNact')) {
                stack.restart(undefined, { "resultLine": "Interval activity code missing" });
            }
            if (obj.iNact.length === 0) {
                stack.restart(undefined, { "resultLine": "Interval activity code missing" });
            }
            if (! obj.hasOwnProperty('acTaid')) {
                console.log("Looking up activity " + obj.iNact + " in cache");
                obj.acTaid = actLib.getActByCode(obj.iNact).aid;
                if (! obj.acTaid) {
                    stack.restart(undefined, { "resultLine": 'Activity ' + obj.iNact + ' not found' });
                }
            }

            rest.body = {
                "eid": cu.eid,
                "aid": obj.acTaid,
                "intvl": genIntvl(obj.iNdate, obj.iNtimerange),
                "long_desc": obj.iNdesc,
                "remark": null,
            }
            ajax(rest, sc, fc);
        },

    };

});
<|endoftext|>