<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');

var CallHandler = require('../../../CallHandler');
var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index');
var dis = require('../../../dispatcher');

module.exports = React.createClass({
    displayName: 'MessageComposer',

    propTypes: {
        tabComplete: React.PropTypes.any,

        // a callback which is called when the height of the composer is
        // changed due to a change in content.
        onResize: React.PropTypes.func,

        // js-sdk Room object
        room: React.PropTypes.object.isRequired,

        // string representing the current voip call state
        callState: React.PropTypes.string,

        // callback when a file to upload is chosen
        uploadFile: React.PropTypes.func.isRequired,
    },

    onUploadClick: function(ev) {
        this.refs.uploadInput.click();
    },

    onUploadFileSelected: function(ev) {
        var files = ev.target.files;
        // MessageComposer shouldn't have to rely on its parent passing in a callback to upload a file
        if (files) {
            for(var i=0; i<files.length; i++) {
                this.props.uploadFile(files[i]);
            }
        }
        this.refs.uploadInput.value = null;
    },

    onHangupClick: function() {
        var call = CallHandler.getCallForRoom(this.props.room.roomId);
        //var call = CallHandler.getAnyActiveCall();
        if (!call) {
            return;
        }
        dis.dispatch({
            action: 'hangup',
            // hangup the call for this room, which may not be the room in props
            // (e.g. conferences which will hangup the 1:1 room instead)
            room_id: call.roomId
        });
    },

    onCallClick: function(ev) {
        dis.dispatch({
            action: 'place_call',
            type: ev.shiftKey ? "screensharing" : "video",
            room_id: this.props.room.roomId
        });
    },

    onVoiceCallClick: function(ev) {
        dis.dispatch({
            action: 'place_call',
            type: 'voice',
            room_id: this.props.room.roomId
        });
    },

    render: function() {
        var me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
        var uploadInputStyle = {display: 'none'};
        var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
        var TintableSvg = sdk.getComponent("elements.TintableSvg");
        var MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");

        var controls = [];

        controls.push(

                <MemberAvatar member={me} width={24} height={24} />

        );

        var callButton, videoCallButton, hangupButton;
        if (this.props.callState && this.props.callState !== 'ended') {
            hangupButton =

;
        }
        else {
            callButton =

                    <TintableSvg src="img/voice.svg" width="16" height="26"/>

            videoCallButton =

                    <TintableSvg src="img/call.svg" width="30" height="22"/>

        }

        var canSendMessages = this.props.room.currentState.maySendMessage(
            MatrixClientPeg.get().credentials.userId);

        if (canSendMessages) {
            // This also currently includes the call buttons. Really we should
            // check separately for whether we can call, but this is slightly
            // complex because of conference calls.
            var uploadButton = (

                    <TintableSvg src="img/upload.svg" width="19" height="24"/>
                    <input ref="uploadInput" type="file"
                        style={uploadInputStyle}
                        multiple
                        onChange={this.onUploadFileSelected} />

            );

            controls.push(
                <MessageComposerInput tabComplete={this.props.tabComplete}
                    onResize={this.props.onResize} room={this.props.room} />,
                uploadButton,
                hangupButton,
                callButton,
                videoCallButton
            );
        } else {
            controls.push(

                    You do not have permission to post to this room

            );
        }

        return (

                        {controls}

        );
    }
});

<commit_msg>Add confirmation dialog for file upload<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');

var CallHandler = require('../../../CallHandler');
var MatrixClientPeg = require('../../../MatrixClientPeg');
var Modal = require('../../../Modal');
var sdk = require('../../../index');
var dis = require('../../../dispatcher');

module.exports = React.createClass({
    displayName: 'MessageComposer',

    propTypes: {
        tabComplete: React.PropTypes.any,

        // a callback which is called when the height of the composer is
        // changed due to a change in content.
        onResize: React.PropTypes.func,

        // js-sdk Room object
        room: React.PropTypes.object.isRequired,

        // string representing the current voip call state
        callState: React.PropTypes.string,

        // callback when a file to upload is chosen
        uploadFile: React.PropTypes.func.isRequired,
    },

    onUploadClick: function(ev) {
        this.refs.uploadInput.click();
    },

    onUploadFileSelected: function(ev) {
        var files = ev.target.files;

        var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
        var TintableSvg = sdk.getComponent("elements.TintableSvg");

        var fileList = [];
        for(var i=0; i<files.length; i++) {
            fileList.push(

                <TintableSvg src="img/files.svg" width="16" height="16" /> {files[i].name}

);
        }

        Modal.createDialog(QuestionDialog, {
            title: "Upload Files",
            description: (

Are you sure you want upload the following files?

                        {fileList}

            ),
            onFinished: (shouldUpload) => {
                if(shouldUpload) {
                    // MessageComposer shouldn't have to rely on its parent passing in a callback to upload a file
                    if (files) {
                        for(var i=0; i<files.length; i++) {
                            this.props.uploadFile(files[i]);
                        }
                    }
                }

                this.refs.uploadInput.value = null;
            }
        });
    },

    onHangupClick: function() {
        var call = CallHandler.getCallForRoom(this.props.room.roomId);
        //var call = CallHandler.getAnyActiveCall();
        if (!call) {
            return;
        }
        dis.dispatch({
            action: 'hangup',
            // hangup the call for this room, which may not be the room in props
            // (e.g. conferences which will hangup the 1:1 room instead)
            room_id: call.roomId
        });
    },

    onCallClick: function(ev) {
        dis.dispatch({
            action: 'place_call',
            type: ev.shiftKey ? "screensharing" : "video",
            room_id: this.props.room.roomId
        });
    },

    onVoiceCallClick: function(ev) {
        dis.dispatch({
            action: 'place_call',
            type: 'voice',
            room_id: this.props.room.roomId
        });
    },

    render: function() {
        var me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
        var uploadInputStyle = {display: 'none'};
        var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
        var TintableSvg = sdk.getComponent("elements.TintableSvg");
        var MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");

        var controls = [];

        controls.push(

                <MemberAvatar member={me} width={24} height={24} />

        );

        var callButton, videoCallButton, hangupButton;
        if (this.props.callState && this.props.callState !== 'ended') {
            hangupButton =

;
        }
        else {
            callButton =

                    <TintableSvg src="img/voice.svg" width="16" height="26"/>

            videoCallButton =

                    <TintableSvg src="img/call.svg" width="30" height="22"/>

        }

        var canSendMessages = this.props.room.currentState.maySendMessage(
            MatrixClientPeg.get().credentials.userId);

        if (canSendMessages) {
            // This also currently includes the call buttons. Really we should
            // check separately for whether we can call, but this is slightly
            // complex because of conference calls.
            var uploadButton = (

                    <TintableSvg src="img/upload.svg" width="19" height="24"/>
                    <input ref="uploadInput" type="file"
                        style={uploadInputStyle}
                        multiple
                        onChange={this.onUploadFileSelected} />

            );

            controls.push(
                <MessageComposerInput tabComplete={this.props.tabComplete}
                    onResize={this.props.onResize} room={this.props.room} />,
                uploadButton,
                hangupButton,
                callButton,
                videoCallButton
            );
        } else {
            controls.push(

                    You do not have permission to post to this room

            );
        }

        return (

                        {controls}

        );
    }
});

<|endoftext|>


<commit_before>/* global Demo */

//------------------------------------------------------------------
//
// Defines a text object.  The spec is defined as:
//	{
//		text: '',
//		font: '',
//		fill: '',
//		position: { x: , y: }
//	}
//
//------------------------------------------------------------------
Demo.components.Text = function(spec) {
	'use strict';
	var that = {
		get text() { return spec.text; },
		set text(value) { spec.text = value; },
		get font() { return spec.font; },
		get fill() { return spec.fill; },
		get position() { return spec.position; },
	};

	return that;
};
<commit_msg>A little cleanup in the Input demo.<commit_after>//------------------------------------------------------------------
//
// Defines a text object.  The spec is defined as:
//    {
//        text: '',
//        font: '',
//        fill: '',
//        position: { x: , y: }
//    }
//
//------------------------------------------------------------------
Demo.components.Text = function(spec) {
    'use strict';
    var that = {
        get text() { return spec.text; },
        set text(value) { spec.text = value; },
        get font() { return spec.font; },
        get fill() { return spec.fill; },
        get position() { return spec.position; },
    };

    return that;
};
<|endoftext|>


<commit_before>var dbm = require('db-migrate');
var type = dbm.dataType;

var Q = require("q");

exports.up = function(db, callback) {

    var runSql = Q.nbind(db.runSql, db);

    Q()
    .then(function() {
        return runSql("ALTER TABLE userpasswords ADD COLUMN username VARCHAR(64)");
    })
    .then(function() {
        return runSql("UPDATE userPasswords UP " +
                      "JOIN USERS U ON U.id = UP.userId " +
                      "SET UP.username = u.friendlyName ");
    })
    .then(callback, callback);
};

exports.down = function(db, callback) {

    var runSql = Q.nbind(db.runSql, db);

    Q()
    .then(function() {
        return runSql("ALTER TABLE userpasswords DROP COLUMN username");
    })
    .then(callback, callback);
};<commit_msg>trying to track down failure...  this change shouldn't matter but its the only thing that is different<commit_after>var dbm = require('db-migrate');
var type = dbm.dataType;

var Q = require("q");

exports.up = function(db, callback) {

    var runSql = Q.nbind(db.runSql, db);

    Q()
    .then(function() {
        return runSql("ALTER TABLE userpasswords ADD COLUMN username VARCHAR(64)");
    })
    .then(function() {
        return runSql("UPDATE userPasswords UP " +
                      "JOIN USERS U ON U.id = UP.userId " +
                      "SET UP.username = u.friendlyName ");
    })
    .then(function() {
        callback();
    }, function(err) {
        callback(err);
    });
};

exports.down = function(db, callback) {

    var runSql = Q.nbind(db.runSql, db);

    Q()
    .then(function() {
        return runSql("ALTER TABLE userpasswords DROP COLUMN username");
    })
    .then(callback, callback);
};<|endoftext|>


<commit_before>import React, {Component} from 'react';

export default class Greetings2008 extends Component {
  render() {
    const src = require(`../../../static/Phillips Christmas Letter 2008.pdf`);
    return (

        <embed
          src={src}
          type="application/pdf"
          width={'100%'}
          height={1800}
        />

    );
  }
}
<commit_msg>fixed capitalization of filename<commit_after>import React, {Component} from 'react';

export default class Greetings2008 extends Component {
  render() {
    const src = require(`../../../static/Phillips Christmas letter 2008.pdf`);
    return (

        <embed
          src={src}
          type="application/pdf"
          width={'100%'}
          height={1800}
        />

    );
  }
}
<|endoftext|>


<commit_before>import { window, document } from 'ssr-window';
import $ from 'dom7';
import Template7 from 'template7';
import Utils from '../../utils/utils';
import vdom from './vdom';
import patch from './patch';

class Framework7Component {
  constructor(app, options, extendContext = {}, children = []) {
    const id = Utils.id();
    const self = Utils.merge(
      this,
      extendContext,
      {
        $,
        $$: $,
        $dom7: $,
        $app: app,
        $f7: app,
        $options: Utils.extend({ id }, options),
        $id: options.id || id,
        $children: children,
      }
    );
    const { $options } = self;

    // Root data and methods
    Object.defineProperty(self, '$root', {
      enumerable: true,
      configurable: true,
      get() {
        let root = Utils.merge({}, app.data, app.methods);
        if (window && window.Proxy) {
          root = new window.Proxy(root, {
            set(target, name, val) {
              app.data[name] = val;
            },
            deleteProperty(target, name) {
              delete app.data[name];
              delete app.methods[name];
            },
            has(target, name) {
              return (name in app.data || name in app.methods);
            },
          });
        }
        return root;
      },
      set() {},
    });

    // Bind hooks
    ['beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeDestroy', 'destroyed', 'updated']
      .forEach((cycleKey) => {
        if ($options[cycleKey]) $options[cycleKey] = $options[cycleKey].bind(self);
      });

    // Bind render
    if ($options.render) $options.render = $options.render.bind(self);

    // Bind methods
    if ($options.methods) {
      Object.keys($options.methods).forEach((methodName) => {
        self[methodName] = $options.methods[methodName].bind(self);
      });
    }

    // Bind Events
    if ($options.on) {
      Object.keys($options.on).forEach((eventName) => {
        $options.on[eventName] = $options.on[eventName].bind(self);
      });
    }
    if ($options.once) {
      Object.keys($options.once).forEach((eventName) => {
        $options.once[eventName] = $options.once[eventName].bind(self);
      });
    }

    // Bind Data
    if ($options.data) $options.data = $options.data.bind(self);

    return new Promise((resolve, reject) => {
      self.$hook('data', true)
        .then((data) => {
          if (data) Utils.extend(self, data);
          self.$hook('beforeCreate');
          let html = self.$render();

          if (self.$options.el) {
            html = html.trim();
            self.$vnode = vdom(html, self, true);
            if ($options.style) {
              self.$styleEl = document.createElement('style');
              self.$styleEl.innerHTML = $options.style;
              if ($options.styleScoped) {
                self.$vnode.data.attrs[`data-f7-${$options.id}`] = '';
              }
            }
            self.el = self.$options.el;
            patch(self.el, self.$vnode);
            self.el = self.$vnode.elm;
            self.$el = $(self.el);

            self.$attachEvents();
            self.el.f7Component = self;
            self.$hook('created');
            self.$mount();
            resolve(self);
            return;
          }
          // Make Dom
          if (html && typeof html === 'string') {
            html = html.trim();
            self.$vnode = vdom(html, self, true);
            if ($options.style && $options.styleScoped) {
              self.$vnode.data.attrs[`data-f7-${$options.id}`] = '';
            }
            self.el = document.createElement(self.$vnode.sel || 'div');
            patch(self.el, self.$vnode);
            self.$el = $(self.el);
          } else if (html) {
            self.el = html;
            self.$el = $(self.el);
            if ($options.style && $options.styleScoped) {
              self.el.setAttribute(`data-f7-${$options.id}`, '');
            }
          }
          if ($options.style) {
            self.$styleEl = document.createElement('style');
            self.$styleEl.innerHTML = $options.style;
          }

          self.$attachEvents();

          self.el.f7Component = self;

          self.$hook('created');
          resolve(self);
        })
        .catch((err) => {
          reject(err);
        });
    });
  }

  $attachEvents() {
    const self = this;
    const { $options, $el } = self;
    if ($options.on) {
      Object.keys($options.on).forEach((eventName) => {
        $el.on(Utils.eventNameToColonCase(eventName), $options.on[eventName]);
      });
    }
    if ($options.once) {
      Object.keys($options.once).forEach((eventName) => {
        $el.once(Utils.eventNameToColonCase(eventName), $options.once[eventName]);
      });
    }
  }

  $detachEvents() {
    const self = this;
    const { $options, $el } = self;
    if ($options.on) {
      Object.keys($options.on).forEach((eventName) => {
        $el.off(Utils.eventNameToColonCase(eventName), $options.on[eventName]);
      });
    }
    if ($options.once) {
      Object.keys($options.once).forEach((eventName) => {
        $el.off(Utils.eventNameToColonCase(eventName), $options.once[eventName]);
      });
    }
  }

  $render() {
    const self = this;
    const { $options } = self;
    let html = '';
    if ($options.render) {
      html = $options.render();
    } else if ($options.template) {
      if (typeof $options.template === 'string') {
        try {
          html = Template7.compile($options.template)(self);
        } catch (err) {
          throw err;
        }
      } else {
        // Supposed to be function
        html = $options.template(self);
      }
    }
    return html;
  }

  $update() {
    const self = this;
    let html = self.$render();

    // Make Dom
    if (html && typeof html === 'string') {
      html = html.trim();
      const newVNode = vdom(html, self, false);
      self.$vnode = patch(self.$vnode, newVNode);
    }
  }

  $setState(mergeState) {
    const self = this;
    Utils.merge(self, mergeState);
    self.$update();
  }

  $mount(mountMethod) {
    const self = this;
    self.$hook('beforeMount');
    if (self.$styleEl) $('head').append(self.$styleEl);
    if (mountMethod) mountMethod(self.el);
    self.$hook('mounted');
  }

  $destroy() {
    const self = this;
    self.$hook('beforeDestroy');

    if (self.$styleEl) $(self.$styleEl).remove();
    self.$detachEvents();
    self.$hook('destroyed');
    // Delete component instance
    if (self.el && self.el.f7Component) {
      self.el.f7Component = null;
      delete self.el.f7Component;
    }
    // Patch with empty node
    if (self.$vnode) {
      self.$vnode = patch(self.$vnode, { sel: self.$vnode.sel, data: {} });
    }
    Utils.deleteProps(self);
  }

  $hook(name, async) {
    const self = this;
    if (async) {
      return new Promise((resolve, reject) => {
        if (!self.$options[name]) {
          resolve();
          return;
        }
        const result = self.$options[name]();
        if (result instanceof Promise) {
          result
            .then(res => resolve(res))
            .catch(err => reject(err));
          return;
        }
        resolve(result);
      });
    }
    if (self.$options[name]) return self.$options[name]();
    return undefined;
  }
}

export default Framework7Component;
<commit_msg>v5: Core: Router component - async updates + new $tick method<commit_after>/* eslint no-underscore-dangle: "off" */
import { window, document } from 'ssr-window';
import $ from 'dom7';
import Template7 from 'template7';
import Utils from '../../utils/utils';
import vdom from './vdom';
import patch from './patch';

class Framework7Component {
  constructor(app, options, extendContext = {}, children = []) {
    const id = Utils.id();
    const self = Utils.merge(
      this,
      extendContext,
      {
        $,
        $$: $,
        $dom7: $,
        $app: app,
        $f7: app,
        $options: Utils.extend({ id }, options),
        $id: options.id || id,
        $children: children,
      }
    );
    const { $options } = self;

    // Root data and methods
    Object.defineProperty(self, '$root', {
      enumerable: true,
      configurable: true,
      get() {
        let root = Utils.merge({}, app.data, app.methods);
        if (window && window.Proxy) {
          root = new window.Proxy(root, {
            set(target, name, val) {
              app.data[name] = val;
            },
            deleteProperty(target, name) {
              delete app.data[name];
              delete app.methods[name];
            },
            has(target, name) {
              return (name in app.data || name in app.methods);
            },
          });
        }
        return root;
      },
      set() {},
    });

    // Bind hooks
    ['beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeDestroy', 'destroyed', 'updated']
      .forEach((cycleKey) => {
        if ($options[cycleKey]) $options[cycleKey] = $options[cycleKey].bind(self);
      });

    // Bind render
    if ($options.render) $options.render = $options.render.bind(self);

    // Bind methods
    if ($options.methods) {
      Object.keys($options.methods).forEach((methodName) => {
        self[methodName] = $options.methods[methodName].bind(self);
      });
    }

    // Bind Events
    if ($options.on) {
      Object.keys($options.on).forEach((eventName) => {
        $options.on[eventName] = $options.on[eventName].bind(self);
      });
    }
    if ($options.once) {
      Object.keys($options.once).forEach((eventName) => {
        $options.once[eventName] = $options.once[eventName].bind(self);
      });
    }

    // Bind Data
    if ($options.data) $options.data = $options.data.bind(self);

    return new Promise((resolve, reject) => {
      self.$hook('data', true)
        .then((data) => {
          if (data) Utils.extend(self, data);
          self.$hook('beforeCreate');
          let html = self.$render();

          if (self.$options.el) {
            html = html.trim();
            self.$vnode = vdom(html, self, true);
            if ($options.style) {
              self.$styleEl = document.createElement('style');
              self.$styleEl.innerHTML = $options.style;
              if ($options.styleScoped) {
                self.$vnode.data.attrs[`data-f7-${$options.id}`] = '';
              }
            }
            self.el = self.$options.el;
            patch(self.el, self.$vnode);
            self.el = self.$vnode.elm;
            self.$el = $(self.el);

            self.$attachEvents();
            self.el.f7Component = self;
            self.$hook('created');
            self.$mount();
            resolve(self);
            return;
          }
          // Make Dom
          if (html && typeof html === 'string') {
            html = html.trim();
            self.$vnode = vdom(html, self, true);
            if ($options.style && $options.styleScoped) {
              self.$vnode.data.attrs[`data-f7-${$options.id}`] = '';
            }
            self.el = document.createElement(self.$vnode.sel || 'div');
            patch(self.el, self.$vnode);
            self.$el = $(self.el);
          } else if (html) {
            self.el = html;
            self.$el = $(self.el);
            if ($options.style && $options.styleScoped) {
              self.el.setAttribute(`data-f7-${$options.id}`, '');
            }
          }
          if ($options.style) {
            self.$styleEl = document.createElement('style');
            self.$styleEl.innerHTML = $options.style;
          }

          self.$attachEvents();

          self.el.f7Component = self;

          self.$hook('created');
          resolve(self);
        })
        .catch((err) => {
          reject(err);
        });
    });
  }

  $attachEvents() {
    const self = this;
    const { $options, $el } = self;
    if ($options.on) {
      Object.keys($options.on).forEach((eventName) => {
        $el.on(Utils.eventNameToColonCase(eventName), $options.on[eventName]);
      });
    }
    if ($options.once) {
      Object.keys($options.once).forEach((eventName) => {
        $el.once(Utils.eventNameToColonCase(eventName), $options.once[eventName]);
      });
    }
  }

  $detachEvents() {
    const self = this;
    const { $options, $el } = self;
    if ($options.on) {
      Object.keys($options.on).forEach((eventName) => {
        $el.off(Utils.eventNameToColonCase(eventName), $options.on[eventName]);
      });
    }
    if ($options.once) {
      Object.keys($options.once).forEach((eventName) => {
        $el.off(Utils.eventNameToColonCase(eventName), $options.once[eventName]);
      });
    }
  }

  $render() {
    const self = this;
    const { $options } = self;
    let html = '';
    if ($options.render) {
      html = $options.render();
    } else if ($options.template) {
      if (typeof $options.template === 'string') {
        try {
          html = Template7.compile($options.template)(self);
        } catch (err) {
          throw err;
        }
      } else {
        // Supposed to be function
        html = $options.template(self);
      }
    }
    return html;
  }

  $tick(callback) {
    const self = this;
    window.requestAnimationFrame(() => {
      if (self.__updateIsPending) {
        window.requestAnimationFrame(() => {
          callback();
        });
      } else {
        callback();
      }
    });
  }

  $update() {
    const self = this;
    window.cancelAnimationFrame(self.__requestAnimationFrameId);
    delete self.__requestAnimationFrameId;
    self.__updateIsPending = true;
    self.__requestAnimationFrameId = window.requestAnimationFrame(() => {
      let html = self.$render();

      // Make Dom
      if (html && typeof html === 'string') {
        html = html.trim();
        const newVNode = vdom(html, self, false);
        self.$vnode = patch(self.$vnode, newVNode);
      }
      self.__updateIsPending = false;
      delete self.__updateIsPending;
    });
  }

  $setState(mergeState) {
    const self = this;
    Utils.merge(self, mergeState);
    self.$update();
  }

  $mount(mountMethod) {
    const self = this;
    self.$hook('beforeMount');
    if (self.$styleEl) $('head').append(self.$styleEl);
    if (mountMethod) mountMethod(self.el);
    self.$hook('mounted');
  }

  $destroy() {
    const self = this;
    self.$hook('beforeDestroy');

    if (self.$styleEl) $(self.$styleEl).remove();
    self.$detachEvents();
    self.$hook('destroyed');
    // Delete component instance
    if (self.el && self.el.f7Component) {
      self.el.f7Component = null;
      delete self.el.f7Component;
    }
    // Patch with empty node
    if (self.$vnode) {
      self.$vnode = patch(self.$vnode, { sel: self.$vnode.sel, data: {} });
    }
    // Clear update queue
    window.cancelAnimationFrame(self.__requestAnimationFrameId);

    // Delete all props
    Utils.deleteProps(self);
  }

  $hook(name, async) {
    const self = this;
    if (async) {
      return new Promise((resolve, reject) => {
        if (!self.$options[name]) {
          resolve();
          return;
        }
        const result = self.$options[name]();
        if (result instanceof Promise) {
          result
            .then(res => resolve(res))
            .catch(err => reject(err));
          return;
        }
        resolve(result);
      });
    }
    if (self.$options[name]) return self.$options[name]();
    return undefined;
  }
}

export default Framework7Component;
<|endoftext|>


<commit_before>define(
  'ephox.alloy.demo.DraggableDemo',

  [
    'ephox.alloy.api.Gui',
    'ephox.alloy.demo.HtmlDisplay',
    'ephox.sugar.api.Class',
    'ephox.sugar.api.Element',
    'ephox.sugar.api.Insert',
    'global!document'
  ],

  function (Gui, HtmlDisplay, Class, Element, Insert, document) {
    return function () {
      var gui = Gui.create();
      var body = Element.fromDom(document.body);
      Class.add(gui.element(), 'gui-root-demo-container');
      // Css.set(gui.element(), 'direction', 'rtl');

      Insert.append(body, gui.element());

      var button1 = HtmlDisplay.section(
        gui,
        'This button is a button that can be dragged',
        {
          uiType: 'button',
          dom: {
            tag: 'span',
            innerHtml: 'Drag me!'
          },
          dragging: {
            mode: 'mouse'
            // initial position?
          },
          unselecting: true
        }
      );

    };
  }
);<commit_msg>VAN-44-wheel-hackery-apis: specify event ordering for mousedown so draggable demo works<commit_after>define(
  'ephox.alloy.demo.DraggableDemo',

  [
    'ephox.alloy.api.Gui',
    'ephox.alloy.demo.HtmlDisplay',
    'ephox.sugar.api.Class',
    'ephox.sugar.api.Element',
    'ephox.sugar.api.Insert',
    'global!document'
  ],

  function (Gui, HtmlDisplay, Class, Element, Insert, document) {
    return function () {
      var gui = Gui.create();
      var body = Element.fromDom(document.body);
      Class.add(gui.element(), 'gui-root-demo-container');
      // Css.set(gui.element(), 'direction', 'rtl');

      Insert.append(body, gui.element());

      var button1 = HtmlDisplay.section(
        gui,
        'This button is a button that can be dragged',
        {
          uiType: 'button',
          dom: {
            tag: 'span',
            innerHtml: 'Drag me!'
          },
          dragging: {
            mode: 'mouse'
            // initial position?
          },
          eventOrder: {
            // Because this is a button, allow dragging. It will stop clicking.
            mousedown: [ 'dragging', 'alloy.base.behaviour' ]
          },
          unselecting: true
        }
      );

    };
  }
);<|endoftext|>


<commit_before>define(
  'ephox.alloy.demo.DraggableDemo',

  [
    'ephox.alloy.api.Gui',
    'ephox.alloy.api.behaviour.Dragging',
    'ephox.alloy.demo.HtmlDisplay',
    'ephox.alloy.dragging.DragCoord',
    'ephox.peanut.Fun',
    'ephox.perhaps.Option',
    'ephox.sugar.alien.Position',
    'ephox.sugar.api.Class',
    'ephox.sugar.api.Css',
    'ephox.sugar.api.Element',
    'ephox.sugar.api.Insert',
    'global!document'
  ],

  function (Gui, Dragging, HtmlDisplay, DragCoord, Fun, Option, Position, Class, Css, Element, Insert, document) {
    return function () {
      var gui = Gui.create();
      var body = Element.fromDom(document.body);
      Class.add(gui.element(), 'gui-root-demo-container');
      // Css.set(gui.element(), 'direction', 'rtl');

      Insert.append(body, gui.element());
      Css.set(body, 'margin-bottom', '2000px');

      var button1 = HtmlDisplay.section(
        gui,
        'This button is a button that can be dragged',
        {
          uiType: 'container',
          dom: {
            tag: 'div'
          },
          components: [
            {
              uiType: 'container',
              dom: {
                styles: {
                  position: 'fixed',
                  width: '100px',
                  height: '20px',
                  left: '300px',
                  top: '10px',
                  background: 'blue'
                },
                innerHtml: 'A fixed dock'
              }
            },
            {
              uiType: 'container',
              dom: {
                styles: {
                  position: 'absolute',
                  width: '10px',
                  height: '10px',
                  left: '300px',
                  top: '500px',
                  background: 'red'
                }
              }
            },
            {
              uiType: 'button',
              dom: {
                tag: 'span',
                innerHtml: 'Drag me!',
                styles: {
                  padding: '10px',
                  display: 'inline-block',
                  background: '#333',
                  color: '#fff'
                }
              },
              dragging: {
                mode: 'mouse',
                snaps: {
                  getSnapPoints: function () {
                    return [
                      Dragging.snap({
                        sensor: DragCoord.fixed(300, 10),
                        range: Position(1000, 30),
                        output: DragCoord.fixed(Option.none(), Option.some(10))
                      }),

                      Dragging.snap({
                        sensor: DragCoord.offset(300, 500),
                        range: Position(40, 40),
                        output: DragCoord.absolute(Option.some(300), Option.some(500))
                      })
                    ];
                  },
                  leftAttr: 'data-drag-left',
                  topAttr: 'data-drag-top'
                }
                // initial position?
              },
              eventOrder: {
                // Because this is a button, allow dragging. It will stop clicking.
                mousedown: [ 'dragging', 'alloy.base.behaviour' ]
              },
              unselecting: true
            }
          ]
        }
      );

    };
  }
);<commit_msg>VAN-44-wheel-hackery-apis-2: draggable demo<commit_after>define(
  'ephox.alloy.demo.DraggableDemo',

  [
    'ephox.alloy.api.Gui',
    'ephox.alloy.api.behaviour.Dragging',
    'ephox.alloy.api.ui.Button',
    'ephox.alloy.demo.HtmlDisplay',
    'ephox.alloy.dragging.DragCoord',
    'ephox.boulder.api.Objects',
    'ephox.peanut.Fun',
    'ephox.perhaps.Option',
    'ephox.sugar.alien.Position',
    'ephox.sugar.api.Class',
    'ephox.sugar.api.Css',
    'ephox.sugar.api.Element',
    'ephox.sugar.api.Insert',
    'global!document'
  ],

  function (Gui, Dragging, Button, HtmlDisplay, DragCoord, Objects, Fun, Option, Position, Class, Css, Element, Insert, document) {
    return function () {
      var gui = Gui.create();
      var body = Element.fromDom(document.body);
      Class.add(gui.element(), 'gui-root-demo-container');
      // Css.set(gui.element(), 'direction', 'rtl');

      Insert.append(body, gui.element());
      Css.set(body, 'margin-bottom', '2000px');

      var button1 = HtmlDisplay.section(
        gui,
        'This button is a button that can be dragged',
        {
          uiType: 'container',
          dom: {
            tag: 'div'
          },
          components: [
            {
              uiType: 'container',
              dom: {
                styles: {
                  position: 'fixed',
                  width: '100px',
                  height: '20px',
                  left: '300px',
                  top: '10px',
                  background: 'blue'
                },
                innerHtml: 'A fixed dock'
              }
            },
            {
              uiType: 'container',
              dom: {
                styles: {
                  position: 'absolute',
                  width: '10px',
                  height: '10px',
                  left: '300px',
                  top: '500px',
                  background: 'red'
                }
              }
            },
            Button.build({
              dom: {
                tag: 'span',
                innerHtml: 'Drag me!',
                styles: {
                  padding: '10px',
                  display: 'inline-block',
                  background: '#333',
                  color: '#fff'
                }
              },

              behaviours: Objects.wrapAll([
                Dragging.config({
                  mode: 'mouse',
                  blockerClass: [ 'blocker' ],
                  snaps: {
                    getSnapPoints: function () {
                      return [
                        Dragging.snap({
                          sensor: DragCoord.fixed(300, 10),
                          range: Position(1000, 30),
                          output: DragCoord.fixed(Option.none(), Option.some(10))
                        }),

                        Dragging.snap({
                          sensor: DragCoord.offset(300, 500),
                          range: Position(40, 40),
                          output: DragCoord.absolute(Option.some(300), Option.some(500))
                        })
                      ];
                    },
                    leftAttr: 'data-drag-left',
                    topAttr: 'data-drag-top'
                  }
                })
              ]),
              eventOrder: {
                // Because this is a button, allow dragging. It will stop clicking.
                mousedown: [ 'dragging', 'alloy.base.behaviour' ]
              },
              unselecting: true
            })
          ]
        }
      );

    };
  }
);<|endoftext|>


<commit_before>'use strict';

const Promise = require('promise');
const uuid = require('uuid/v4');
const cassandraConnector = require('../../clients/cassandra/CassandraConnector');
const withRunTime = require('../shared').withRunTime;

const STREAM_PIPELINE_TWITTER = 'twitter';
const STREAM_CONNECTOR_TWITTER = 'twitter';

const TRUSTED_SOURCES_CONNECTOR_TWITTER = 'Twitter';
const TRUSTED_SOURCES_RANK_DEFAULT = 10;

/**
 * @param {{input: {targetBbox: number[], defaultZoomLevel: number, logo: string, title: string, name: string, defaultLocation: number[], storageConnectionString: string, featuresConnectionString: string, mapzenApiKey: string, fbToken: string, supportedLanguages: string[]}}} args
 * @returns {Promise.<{name: string, properties: {targetBBox: number[], defaultZoomLevel: number, logo: string, title: string, defaultLocation: number[], storageConnectionString: string, featuresConnectionString: string, mapzenApiKey: string, fbToken: string, supportedLanguages: string[]}}>}
 */
function createOrReplaceSite(args, res) { // eslint-disable-line no-unused-vars
}

/**
 * @param {{input: {site: string, pages: Array<{pageUrl: string, RowKey: string}>}}} args
 * @returns {Promise.<{runTime: string, pages: Array<{pageUrl: string, RowKey: string}>}>}
 */
function modifyFacebookPages(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    reject('Modification not yet supported.');
  });
}

/**
 * @param {{input: {site: string, pages: Array<{pageUrl: string, RowKey: string}>}}} args
 * @returns {Promise.<{runTime: string, pages: Array<{pageUrl: string, RowKey: string}>}>}
 */
function removeFacebookPages(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    reject('Removal not yet supported.');
  });
}

function trustedTwitterAccountRowKeyToPrimaryKey(account) {
  const params = account.RowKey.split(',');
  if (params.length != 3) {
    throw('Expecting three element comma-delimited RowKey representing (connector, sourceid, sourcetype).');
  }
  return trustedTwitterAccountPrimaryKeyValuesToRowKey(params);
}

function trustedTwitterAccountPrimaryKeyValuesToRowKey(values) {
  return [ TRUSTED_SOURCES_CONNECTOR_TWITTER, values[1], values[2] ];
}

function normalizedTrustedTwitterAccount(account) {
  const keyValues = trustedTwitterAccountRowKeyToPrimaryKey(account);
  return {
    RowKey: trustedTwitterAccountPrimaryKeyValuesToRowKey(keyValues),
    acctUrl: keyValues[1]
  };
}

/**
 * @param {{input: {site: string, accounts: Array<{acctUrl: string, RowKey: string}>}}} args
 * @returns {Promise.<{runTime: string, accounts: Array<{pageUrl: string, RowKey: string}>}>}
 */
function modifyTrustedTwitterAccounts(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    const accounts = args && args.input && args.input.accounts;
    if (!accounts || !accounts.length) return reject('No accounts specified');

    const statement = 'INSERT INTO fortis.trustedsources (connector, sourceid, sourcetype, insertion_time, rank) VALUES (?, ?, ?, dateof(now()), ?)';
    const queries = accounts.map(account => {
      const params = trustedTwitterAccountRowKeyToPrimaryKey(account);
      params.push(TRUSTED_SOURCES_RANK_DEFAULT);

      return {query: statement, params: params};
    });

    cassandraConnector.executeBatchMutations(queries)
    .then(() => { resolve({ accounts: accounts.map(normalizedTrustedTwitterAccount) }); })
    .catch(reject)
    ;
  });
}

/**
 * @param {{input: {site: string, accounts: Array<{acctUrl: string, RowKey: string}>}}} args
 * @returns {Promise.<{runTime: string, accounts: Array<{pageUrl: string, RowKey: string}>}>}
 */
function removeTrustedTwitterAccounts(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    const accounts = args && args.input && args.input.accounts;
    if (!accounts || !accounts.length) return reject('No accounts specified');

    const deleteByPrimaryKey = 'DELETE FROM fortis.trustedsources WHERE connector = ? AND sourceid = ? AND sourcetype = ?';
    const queries = accounts.map(account => {
      const params = trustedTwitterAccountRowKeyToPrimaryKey(account);
      return {query: deleteByPrimaryKey, params: params};
    });

    cassandraConnector.executeBatchMutations(queries)
    .then(() => { resolve({ accounts: accounts.map(normalizedTrustedTwitterAccount) }); })
    .catch(reject)
    ;
  });
}

/**
 * @param {{input: {site: string, accounts: Array<{accountName: string, consumerKey: string, consumerSecret: string, token: string, tokenSecret: string}>}}} args
 * @returns {Promise.<{runTime: string, accounts: Array<{accountName: string, consumerKey: string, consumerSecret: string, token: string, tokenSecret: string}>}>}
 */
function modifyTwitterAccounts(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    const accounts = args && args.input && args.input.accounts;
    if (!accounts || !accounts.length) return reject('No accounts specified');

    const updateStatement = 'UPDATE fortis.streams set connector = ?, params = ? WHERE pipeline= ? AND streamid = ?';
    const insertStatement = 'INSERT INTO fortis.streams (pipeline, streamid, connector, params) VALUES (?, ?, ?, ?)';
    const queries = [];
    const expectedRecords = [];
    accounts.forEach( account => {
      // TODO: Arrive at a consensus as to what a canonical account should be in order to create a proper copy of the incoming record.
      const updatedAccount = account;
      if (account.RowKey) {
        queries.push({ query: updateStatement, params: [STREAM_CONNECTOR_TWITTER, account, STREAM_PIPELINE_TWITTER, account.RowKey] });
      }
      else {
        updatedAccount.RowKey = uuid();
        queries.push({ query: insertStatement, params: [STREAM_PIPELINE_TWITTER, updatedAccount.RowKey, STREAM_CONNECTOR_TWITTER, account] });
      }
      expectedRecords.push(updatedAccount);
    });

    cassandraConnector.executeBatchMutations(queries)
    .then(() => { resolve({ accounts: expectedRecords }); })
    .catch(reject)
    ;

  });
}

/**
 * @param {{input: {site: string, accounts: Array<{accountName: string, consumerKey: string, consumerSecret: string, token: string, tokenSecret: string}>}}} args
 * @returns {Promise.<{runTime: string, accounts: Array<{accountName: string, consumerKey: string, consumerSecret: string, token: string, tokenSecret: string}>}>}
 */
function removeTwitterAccounts(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    const accounts = args && args.input && args.input.accounts;
    if (!accounts || !accounts.length) return reject('No accounts specified');

    const invalidAccounts = accounts.filter(account=>!account.RowKey);
    if (invalidAccounts.length > 0) {
      reject(`RowKey required for ${JSON.stringify(invalidAccounts)}`);
      return;
    }

    const statement = 'DELETE FROM fortis.streams WHERE streamid = ?';
    const queries = accounts.map( account => { return { query: statement, params: [ account.RowKey ] }; } );

    cassandraConnector.executeBatchMutations(queries)
    .then(() => { resolve({ accounts: accounts }); })
    .catch(reject)
    ;

  });
}

/**
 * @param {{input: {site: string, terms: Array<{RowKey: string, lang: string, filteredTerms: string[]}>}}} args
 * @returns {Promise.<{runTime: string, filters: Array<{filteredTerms: string[], lang: string, RowKey: string}>}>}
 */
function modifyBlacklist(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    reject('Modification not yet supported.');
  });
}

/**
 * @param {{input: {site: string, terms: Array<{RowKey: string, lang: string, filteredTerms: string[]}>}}} args
 * @returns {Promise.<{runTime: string, filters: Array<{filteredTerms: string[], lang: string, RowKey: string}>}>}
 */
function removeBlacklist(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    const terms = args && args.input && args.input.terms;
    if (!terms || !terms.length) return reject('No terms specified');

    const params = [terms.map( t => { return t.RowKey; } )];
    const update = 'DELETE FROM fortis.blacklist WHERE id IN ?';
    cassandraConnector.executeQuery(update, params)
    .then(() => { resolve({ filters: terms }); })
    .catch(reject)
    ;
  });
}

module.exports = {
  createOrReplaceSite: createOrReplaceSite,
  modifyFacebookPages: withRunTime(modifyFacebookPages),
  removeFacebookPages: withRunTime(removeFacebookPages),
  modifyTrustedTwitterAccounts: withRunTime(modifyTrustedTwitterAccounts),
  removeTrustedTwitterAccounts: withRunTime(removeTrustedTwitterAccounts),
  modifyTwitterAccounts: withRunTime(modifyTwitterAccounts),
  removeTwitterAccounts: withRunTime(removeTwitterAccounts),
  modifyBlacklist: withRunTime(modifyBlacklist),
  removeBlacklist: withRunTime(removeBlacklist)
};
<commit_msg>Updating the stream connector to match value in project-fortis-spark.<commit_after>'use strict';

const Promise = require('promise');
const uuid = require('uuid/v4');
const cassandraConnector = require('../../clients/cassandra/CassandraConnector');
const withRunTime = require('../shared').withRunTime;

const STREAM_PIPELINE_TWITTER = 'twitter';
const STREAM_CONNECTOR_TWITTER = 'Twitter';

const TRUSTED_SOURCES_CONNECTOR_TWITTER = 'Twitter';
const TRUSTED_SOURCES_RANK_DEFAULT = 10;

/**
 * @param {{input: {targetBbox: number[], defaultZoomLevel: number, logo: string, title: string, name: string, defaultLocation: number[], storageConnectionString: string, featuresConnectionString: string, mapzenApiKey: string, fbToken: string, supportedLanguages: string[]}}} args
 * @returns {Promise.<{name: string, properties: {targetBBox: number[], defaultZoomLevel: number, logo: string, title: string, defaultLocation: number[], storageConnectionString: string, featuresConnectionString: string, mapzenApiKey: string, fbToken: string, supportedLanguages: string[]}}>}
 */
function createOrReplaceSite(args, res) { // eslint-disable-line no-unused-vars
}

/**
 * @param {{input: {site: string, pages: Array<{pageUrl: string, RowKey: string}>}}} args
 * @returns {Promise.<{runTime: string, pages: Array<{pageUrl: string, RowKey: string}>}>}
 */
function modifyFacebookPages(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    reject('Modification not yet supported.');
  });
}

/**
 * @param {{input: {site: string, pages: Array<{pageUrl: string, RowKey: string}>}}} args
 * @returns {Promise.<{runTime: string, pages: Array<{pageUrl: string, RowKey: string}>}>}
 */
function removeFacebookPages(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    reject('Removal not yet supported.');
  });
}

function trustedTwitterAccountRowKeyToPrimaryKey(account) {
  const params = account.RowKey.split(',');
  if (params.length != 3) {
    throw('Expecting three element comma-delimited RowKey representing (connector, sourceid, sourcetype).');
  }
  return trustedTwitterAccountPrimaryKeyValuesToRowKey(params);
}

function trustedTwitterAccountPrimaryKeyValuesToRowKey(values) {
  return [ TRUSTED_SOURCES_CONNECTOR_TWITTER, values[1], values[2] ];
}

function normalizedTrustedTwitterAccount(account) {
  const keyValues = trustedTwitterAccountRowKeyToPrimaryKey(account);
  return {
    RowKey: trustedTwitterAccountPrimaryKeyValuesToRowKey(keyValues),
    acctUrl: keyValues[1]
  };
}

/**
 * @param {{input: {site: string, accounts: Array<{acctUrl: string, RowKey: string}>}}} args
 * @returns {Promise.<{runTime: string, accounts: Array<{pageUrl: string, RowKey: string}>}>}
 */
function modifyTrustedTwitterAccounts(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    const accounts = args && args.input && args.input.accounts;
    if (!accounts || !accounts.length) return reject('No accounts specified');

    const statement = 'INSERT INTO fortis.trustedsources (connector, sourceid, sourcetype, insertion_time, rank) VALUES (?, ?, ?, dateof(now()), ?)';
    const queries = accounts.map(account => {
      const params = trustedTwitterAccountRowKeyToPrimaryKey(account);
      params.push(TRUSTED_SOURCES_RANK_DEFAULT);

      return {query: statement, params: params};
    });

    cassandraConnector.executeBatchMutations(queries)
    .then(() => { resolve({ accounts: accounts.map(normalizedTrustedTwitterAccount) }); })
    .catch(reject)
    ;
  });
}

/**
 * @param {{input: {site: string, accounts: Array<{acctUrl: string, RowKey: string}>}}} args
 * @returns {Promise.<{runTime: string, accounts: Array<{pageUrl: string, RowKey: string}>}>}
 */
function removeTrustedTwitterAccounts(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    const accounts = args && args.input && args.input.accounts;
    if (!accounts || !accounts.length) return reject('No accounts specified');

    const deleteByPrimaryKey = 'DELETE FROM fortis.trustedsources WHERE connector = ? AND sourceid = ? AND sourcetype = ?';
    const queries = accounts.map(account => {
      const params = trustedTwitterAccountRowKeyToPrimaryKey(account);
      return {query: deleteByPrimaryKey, params: params};
    });

    cassandraConnector.executeBatchMutations(queries)
    .then(() => { resolve({ accounts: accounts.map(normalizedTrustedTwitterAccount) }); })
    .catch(reject)
    ;
  });
}

/**
 * @param {{input: {site: string, accounts: Array<{accountName: string, consumerKey: string, consumerSecret: string, token: string, tokenSecret: string}>}}} args
 * @returns {Promise.<{runTime: string, accounts: Array<{accountName: string, consumerKey: string, consumerSecret: string, token: string, tokenSecret: string}>}>}
 */
function modifyTwitterAccounts(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    const accounts = args && args.input && args.input.accounts;
    if (!accounts || !accounts.length) return reject('No accounts specified');

    const updateStatement = 'UPDATE fortis.streams set connector = ?, params = ? WHERE pipeline= ? AND streamid = ?';
    const insertStatement = 'INSERT INTO fortis.streams (pipeline, streamid, connector, params) VALUES (?, ?, ?, ?)';
    const queries = [];
    const expectedRecords = [];
    accounts.forEach( account => {
      // TODO: Arrive at a consensus as to what a canonical account should be in order to create a proper copy of the incoming record.
      const updatedAccount = account;
      if (account.RowKey) {
        queries.push({ query: updateStatement, params: [STREAM_CONNECTOR_TWITTER, account, STREAM_PIPELINE_TWITTER, account.RowKey] });
      }
      else {
        updatedAccount.RowKey = uuid();
        queries.push({ query: insertStatement, params: [STREAM_PIPELINE_TWITTER, updatedAccount.RowKey, STREAM_CONNECTOR_TWITTER, account] });
      }
      expectedRecords.push(updatedAccount);
    });

    cassandraConnector.executeBatchMutations(queries)
    .then(() => { resolve({ accounts: expectedRecords }); })
    .catch(reject)
    ;

  });
}

/**
 * @param {{input: {site: string, accounts: Array<{accountName: string, consumerKey: string, consumerSecret: string, token: string, tokenSecret: string}>}}} args
 * @returns {Promise.<{runTime: string, accounts: Array<{accountName: string, consumerKey: string, consumerSecret: string, token: string, tokenSecret: string}>}>}
 */
function removeTwitterAccounts(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    const accounts = args && args.input && args.input.accounts;
    if (!accounts || !accounts.length) return reject('No accounts specified');

    const invalidAccounts = accounts.filter(account=>!account.RowKey);
    if (invalidAccounts.length > 0) {
      reject(`RowKey required for ${JSON.stringify(invalidAccounts)}`);
      return;
    }

    const statement = 'DELETE FROM fortis.streams WHERE streamid = ?';
    const queries = accounts.map( account => { return { query: statement, params: [ account.RowKey ] }; } );

    cassandraConnector.executeBatchMutations(queries)
    .then(() => { resolve({ accounts: accounts }); })
    .catch(reject)
    ;

  });
}

/**
 * @param {{input: {site: string, terms: Array<{RowKey: string, lang: string, filteredTerms: string[]}>}}} args
 * @returns {Promise.<{runTime: string, filters: Array<{filteredTerms: string[], lang: string, RowKey: string}>}>}
 */
function modifyBlacklist(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    reject('Modification not yet supported.');
  });
}

/**
 * @param {{input: {site: string, terms: Array<{RowKey: string, lang: string, filteredTerms: string[]}>}}} args
 * @returns {Promise.<{runTime: string, filters: Array<{filteredTerms: string[], lang: string, RowKey: string}>}>}
 */
function removeBlacklist(args, res) { // eslint-disable-line no-unused-vars
  return new Promise((resolve, reject) => {
    const terms = args && args.input && args.input.terms;
    if (!terms || !terms.length) return reject('No terms specified');

    const params = [terms.map( t => { return t.RowKey; } )];
    const update = 'DELETE FROM fortis.blacklist WHERE id IN ?';
    cassandraConnector.executeQuery(update, params)
    .then(() => { resolve({ filters: terms }); })
    .catch(reject)
    ;
  });
}

module.exports = {
  createOrReplaceSite: createOrReplaceSite,
  modifyFacebookPages: withRunTime(modifyFacebookPages),
  removeFacebookPages: withRunTime(removeFacebookPages),
  modifyTrustedTwitterAccounts: withRunTime(modifyTrustedTwitterAccounts),
  removeTrustedTwitterAccounts: withRunTime(removeTrustedTwitterAccounts),
  modifyTwitterAccounts: withRunTime(modifyTwitterAccounts),
  removeTwitterAccounts: withRunTime(removeTwitterAccounts),
  modifyBlacklist: withRunTime(modifyBlacklist),
  removeBlacklist: withRunTime(removeBlacklist)
};
<|endoftext|>


<commit_before>import assert from 'assert';

import {
  generateImageList,
  SPECIFY_IMAGE_PROPERTY,
  ADD_IMAGE,
  ADD_NEW_IMAGE,
} from '../generateImageList';

describe('generateImageList', () => {
  function createImageData(width, height, data) {
    return {
      width,
      height,
      data: Uint8ClampedArray.from(data),
    };
  }
  let originalImageList;
  beforeEach(() => {
    originalImageList = [
      {
        id: 0,
        isShow: true,
        imageData: createImageData(100, 100, [1, 2, 3]),
        obj: {a: 5},
      },
      {
        id: 1,
        isShow: true,
        imageData: createImageData(100, 100, [1, 2, 3]),
        obj: {a: 5},
      },
    ];
  });

  describe('SPECIFY_IMAGE_PROPERTY', () => {
    it('指定したIDで変更対象のオブジェクトを指定できる', () => {
      const imageData = 'newValue';
      let newList = generateImageList({
        type: SPECIFY_IMAGE_PROPERTY,
        data: {imageData},
        currentState: originalImageList,
        target: 0,
      });
      assert(newList[0].imageData === imageData);
      assert(newList[1].imageData !== imageData);
      newList = generateImageList({
        type: SPECIFY_IMAGE_PROPERTY,
        data: {imageData},
        currentState: originalImageList,
        target: 1,
      });
      assert(newList[1].imageData === imageData);
    });
    it('リテラルである要素を副作用なく変更できる', () => {
      const newList = generateImageList({
        type: SPECIFY_IMAGE_PROPERTY,
        data: {isShow: false},
        currentState: originalImageList,
        target: 0,
      });
      assert(newList[0].isShow === false);
      assert(originalImageList[0].isShow === true);
    });
    it('オブジェクトである要素を副作用なく変更できる', () => {
      const DATA = [7, 8, 9];
      const newList = generateImageList({
        type: SPECIFY_IMAGE_PROPERTY,
        data: {imageData: createImageData(40, 40, DATA)},
        currentState: originalImageList,
        target: 0,
      });
      assert(newList[0].imageData.data[0] === DATA[0]);
      assert(originalImageList[0].imageData.data[0] === 1);
      assert(originalImageList[0].imageData !== newList[0].imageData);
      // 変更する要素以外は参照渡しになるので注意
      assert(originalImageList[0].obj === newList[0].obj);
    });
    it('複数の要素を副作用なく変更できる', () => {
      const DATA = [7, 8, 9];
      const newList = generateImageList({
        type: SPECIFY_IMAGE_PROPERTY,
        data: {isShow: false, imageData: createImageData(40, 40, DATA)},
        currentState: originalImageList,
        target: 0,
      });

      assert(newList[0].imageData.data[2] === DATA[2]);
      assert(newList[1].imageData.data[2] === 3);
      assert(originalImageList[0].imageData.data[2] === 3);

      assert(newList[0].isShow === false);
      assert(newList[1].isShow === true);
      assert(originalImageList[0].isShow === true);
    });
  });

  describe('ADD_IMAGE', () => {
    it('指定したimageが先頭に追加され、副作用がなくcurrentStateに影響を与えない', () => {
      const ID = 9;
      const image = {id: ID};
      const newList = generateImageList({
        type: ADD_IMAGE,
        image,
        currentState: originalImageList,
      });
      assert(newList.length === 3);
      assert(newList[0].id === ID);
      assert(originalImageList.length === 2);
      assert(originalImageList[0].id === 0);
    });
  });

  describe('ADD_NEW_IMAGE', () => {
    it('指定したサイズのimageが先頭に追加され、副作用がなくcurrentStateに影響を与えない', () => {
      const WIDTH = 185;
      const HEIGHT = 97;
      const newList = generateImageList({
        type: ADD_NEW_IMAGE,
        data: {width: WIDTH, height: HEIGHT},
        currentState: originalImageList,
      });
      assert(newList[0].imageData.width === WIDTH);
      assert(newList[0].imageData.height === HEIGHT);
      assert(originalImageList.length === 2);
    });
  });
});
<commit_msg>マジックナンバーを削除。<commit_after>import assert from 'assert';

import {
  generateImageList,
  SPECIFY_IMAGE_PROPERTY,
  ADD_IMAGE,
  ADD_NEW_IMAGE,
} from '../generateImageList';

describe('generateImageList', () => {
  function createImageData(width, height, data) {
    return {
      width,
      height,
      data: Uint8ClampedArray.from(data),
    };
  }
  let originalImageList;
  beforeEach(() => {
    originalImageList = [
      {
        id: 0,
        isShow: true,
        imageData: createImageData(100, 100, [1, 2, 3]),
        obj: {a: 5},
      },
      {
        id: 1,
        isShow: true,
        imageData: createImageData(100, 100, [1, 2, 3]),
        obj: {a: 5},
      },
    ];
  });

  describe('SPECIFY_IMAGE_PROPERTY', () => {
    it('指定したIDで変更対象のオブジェクトを指定できる', () => {
      const imageData = 'newValue';
      let newList = generateImageList({
        type: SPECIFY_IMAGE_PROPERTY,
        data: {imageData},
        currentState: originalImageList,
        target: 0,
      });
      assert(newList[0].imageData === imageData);
      assert(newList[1].imageData !== imageData);
      newList = generateImageList({
        type: SPECIFY_IMAGE_PROPERTY,
        data: {imageData},
        currentState: originalImageList,
        target: 1,
      });
      assert(newList[1].imageData === imageData);
    });
    it('リテラルである要素を副作用なく変更できる', () => {
      const newList = generateImageList({
        type: SPECIFY_IMAGE_PROPERTY,
        data: {isShow: false},
        currentState: originalImageList,
        target: 0,
      });
      assert(newList[0].isShow === false);
      assert(originalImageList[0].isShow === true);
    });
    it('オブジェクトである要素を副作用なく変更できる', () => {
      const DATA = [7, 8, 9];
      const newList = generateImageList({
        type: SPECIFY_IMAGE_PROPERTY,
        data: {imageData: createImageData(40, 40, DATA)},
        currentState: originalImageList,
        target: 0,
      });
      assert(newList[0].imageData.data[0] === DATA[0]);
      assert(originalImageList[0].imageData.data[0] === 1);
      assert(originalImageList[0].imageData !== newList[0].imageData);
      // 変更する要素以外は参照渡しになるので注意
      assert(originalImageList[0].obj === newList[0].obj);
    });
    it('複数の要素を副作用なく変更できる', () => {
      const DATA = [7, 8, 9];
      const newList = generateImageList({
        type: SPECIFY_IMAGE_PROPERTY,
        data: {isShow: false, imageData: createImageData(40, 40, DATA)},
        currentState: originalImageList,
        target: 0,
      });

      assert(newList[0].imageData.data[2] === DATA[2]);
      assert(newList[1].imageData.data[2] === 3);
      assert(originalImageList[0].imageData.data[2] === 3);

      assert(newList[0].isShow === false);
      assert(newList[1].isShow === true);
      assert(originalImageList[0].isShow === true);
    });
  });

  describe('ADD_IMAGE', () => {
    it('指定したimageが先頭に追加され、副作用がなくcurrentStateに影響を与えない', () => {
      const originalLength = originalImageList.length;
      const ID = 9;
      const image = {id: ID};
      const newList = generateImageList({
        type: ADD_IMAGE,
        image,
        currentState: originalImageList,
      });
      assert(newList.length === originalLength + 1);
      assert(newList[0].id === ID);
      assert(originalImageList.length === originalLength);
      assert(originalImageList[0].id === 0);
    });
  });

  describe('ADD_NEW_IMAGE', () => {
    it('指定したサイズのimageが先頭に追加され、副作用がなくcurrentStateに影響を与えない', () => {
      const originalLength = originalImageList.length;
      const WIDTH = 185;
      const HEIGHT = 97;
      const newList = generateImageList({
        type: ADD_NEW_IMAGE,
        data: {width: WIDTH, height: HEIGHT},
        currentState: originalImageList,
      });
      assert(newList[0].imageData.width === WIDTH);
      assert(newList[0].imageData.height === HEIGHT);
      assert(originalImageList.length === originalLength);
    });
  });
});
<|endoftext|>


<commit_before>import {AppError} from '@grid/core/infrastructure';
import {isArray} from '@grid/core/utility';

export default class TemplateLink {
	constructor($compile, $templateCache) {
		this.$compile = $compile;
		this.$templateCache = $templateCache;
	}

	link(templateUrl, resource, keys = ['content', '$default']) {
		const resourceData = resource.data;
		const resourceScope = resource.scope;
		const resourceKey = this.findResourceKey(resourceData, keys);
		const template = resourceKey !== null
			? resourceData[resourceKey]
			: isArray(templateUrl)
				? templateUrl
					.map(url => this.$templateCache.get(url))
					.filter(tpl => !!tpl)[0]
				: this.$templateCache.get(templateUrl);

		return (element, scope, container = element) => {
			if (resourceScope.hasOwnProperty(resourceKey)) {
				const env = resourceScope[resourceKey];
				for (let name of Object.keys(env)) {
					if (scope.hasOwnProperty(name)) {
						throw new AppError(
							'template.core',
							`"${name}" is reserved, use another name`
						);
					}

					scope[name] = env[name];
				}
			}

			container.html('<!--qgrid: template-->' + template);
			const linkTo = this.$compile(element.contents());
			linkTo(scope);
		};
	}

	findResourceKey(resourceData, keys) {
		for (let key of keys) {
			if (resourceData.hasOwnProperty(key)) {
				return key;
			}
		}

		return null;
	}
}<commit_msg>Correct check on not defined tpl<commit_after>import {AppError} from '@grid/core/infrastructure';
import {isArray, isUndefined} from '@grid/core/utility';

export default class TemplateLink {
	constructor($compile, $templateCache) {
		this.$compile = $compile;
		this.$templateCache = $templateCache;
	}

	link(templateUrl, resource, keys = ['content', '$default']) {
		const resourceData = resource.data;
		const resourceScope = resource.scope;
		const resourceKey = this.findResourceKey(resourceData, keys);
		const template = resourceKey !== null
			? resourceData[resourceKey]
			: isArray(templateUrl)
				? templateUrl
					.map(url => this.$templateCache.get(url))
					.filter(tpl => !isUndefined(tpl))[0]
				: this.$templateCache.get(templateUrl);

		return (element, scope, container = element) => {
			if (resourceScope.hasOwnProperty(resourceKey)) {
				const env = resourceScope[resourceKey];
				for (let name of Object.keys(env)) {
					if (scope.hasOwnProperty(name)) {
						throw new AppError(
							'template.core',
							`"${name}" is reserved, use another name`
						);
					}

					scope[name] = env[name];
				}
			}

			container.html('<!--qgrid: template-->' + template);
			const linkTo = this.$compile(element.contents());
			linkTo(scope);
		};
	}

	findResourceKey(resourceData, keys) {
		for (let key of keys) {
			if (resourceData.hasOwnProperty(key)) {
				return key;
			}
		}

		return null;
	}
}<|endoftext|>


<commit_before>/**
 * Copyright (c) 2016, Foothill-De Anza Community College District
 * 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 the copyright holder 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.
 */

'use strict';
let BannerOperations = require('../../BannerOperations.js');
let CollegeManager = require('../../CollegeManager.js');
let Common = require('../../Common.js');
let CourseNameHelper = require('../../CourseNameHelper');
let Logger = require('fhda-pubsub-logging')('ws-action-create-course');
let ProgressMonitor = require('../../ProgressMonitor.js');
let Random = require('random-gen');

/**
 * Handle a websocket request to create a new Canvas course/site classroom.
 * @param  {Object} data Event data payload
 * @param  {Function} respond Callback function to send a response back to the client
 * @return {Promise} Resolved when the operation is complete
 */
module.exports = async function (data, respond) {
    // Capture audit record
    await BannerOperations.recordWebAudit(
        this.decoded_token.aud,
        'canvas:createCourse',
        data,
        this.conn.remoteAddress);

    // Lookup college configuration
    let college = CollegeManager[data.college];

    // Create an execution context
    let context = Object.assign(data, {
        canvasApi: college.canvasApi,
        canvasSections: {},
        college: college,
        enrollment: {},
        instructors: [],
        migrateFrom: data.migrateFrom,
        progress: new ProgressMonitor(),
        ws: this
    }, data);

    // Attach an event handler to reporting progress changed back to the UI
    context.progress.on('progressUpdated', function(data) {
        context.ws.emit('ui:progress:setPercent', data);
    });

    try {
        // Execute a pipline of coordinating course creation tasks
        // (each function is passed the context for the operation where data and
        // objects are shared across the request)
        await getEnrollmentTerm(context);
        await validateSectionsDoNotExist(context);
        await getBannerCourse(context);
        await getBannerSections(context);
        await createCourseSite(context);
        await createSections(context);
        await enrollInstructors(context);
        await migrateCourseContent(context);

        // Tell websocket caller we are done
        respond({status: 'done'});

        // Return context for additional integration testing
        return context;
    }
    catch(error) {
        // Log the error
        Logger.error('A serious error occurred while attempting to create a new Canvas course', {
            error: error.message,
            parentTerm: context.parentTerm,
            parentCrn: context.parentCrn,
            sections: context.sections
        });

        // Tell websocket about the error
        respond({status: 'error', message: error.message});

        // Rollback the course
        await context.canvasApi.deleteCourse(context.canvasCourse.id);
        Logger.warn(`Rolled back course that failed its creation phase (id = ${context.canvasCourse.id})`);
        for(let section of context.sections) {
            await BannerOperations.untrackCourseSectionByTermCrn(section.term, section.crn);
            Logger.warn(`Rolled back course section that failed its creation phase (term = ${section.term}, crn = ${section.crn})`);
        }
    }
    finally {
        // Notify the UI that the creation is complete
        context.ws.emit('ui:progress:hide');
    }

};

async function createCourseSite(context) {
    context.ws.emit('ui:progress:setText', {text: 'Creating course'});

    // Generate course name and code
    let courseName = await CourseNameHelper.generateCourseName(
        context.parentTerm,
        context.parentCrn,
        context.sections,
        context.college.config.courseNameTemplate);
    Logger.info(`Generated course name ${courseName}`);

    let courseCode = await CourseNameHelper.generateCourseCode(
        context.parentTerm,
        context.parentCrn,
        context.sections);
    Logger.info(`Generated course code ${courseCode}`);

    // Create course in Canvas
    context.canvasCourse = await context.canvasApi.createCourse({
        'course[name]': courseName,
        'course[course_code]': courseCode,
        'course[term_id]': context.enrollmentTerm.id,
        'course[sis_course_id]': `${context.parentTerm}:${context.sanitizedSubject}${context.sanitizedCourseNumber}:${Random.number(4)}`
    });
}

async function createSections(context) {
    context.ws.emit('ui:progress:setText', {text: 'Creating sections, and enrolling students'});

    // Create each section and enroll students
    await Promise.map(context.sections, async section => {
        let enrollments = await context.college.createSection(section.term, section.crn, context.canvasCourse.id, context.progress);

        // Add enrollments to context
        context.enrollment[section.crn] = enrollments;
    }, Common.concurrency.MULTI);
}

async function enrollInstructors(context) {
    // Get instructors for parent course
    let instructors = await BannerOperations.getInstructors(context.parentTerm, context.parentCrn);

    // Iterate each instructor to sync account and enroll in course
    for(let instructor of instructors) {
        // Add instructor to context
        context.instructors.push(instructor);
        Logger.info(`Preparing to enroll instructors`, instructors);
        Logger.info(`Enrollment contexts`, context.enrollment);

        // Sync Canvas account
        let profile = await context.canvasApi.syncUser(instructor);

        // Enroll in course
        let enrollment = await context.canvasApi.enrollInstructor(
            context.canvasCourse.id,
            profile.id);

        // Enroll instructor in each section
        for(let section of Object.values(context.enrollment)) {
            await context.canvasApi.enrollInstructorSection(
                section.id,
                profile.id);
        }

        // Add tracked enrollment to Banner
        await BannerOperations.trackEnrollment(
            context.college,
            context.parentTerm,
            context.parentCrn,
            instructor.pidm,
            profile.id,
            'TeacherEnrollment',
            enrollment.id,
            context.canvasCourse.id);

        Logger.info(`Successfully enrolled instructor for course section ${context.parentTerm}:${context.parentCrn}`, instructor);
    }
}

async function getBannerCourse(context) {
    // Lookup parent course in Banner
    context.parentCourse = await BannerOperations.getCourse(context.parentTerm, context.parentCrn);

    // Add data transformations to context
    context.sanitizedSubject = Common.sanitizeSubjectCode(context.parentCourse.subjectCode);
    context.sanitizedCourseNumber = Common.sanitizeCourseNumber(context.parentCourse.courseNumber);
}

async function getBannerSections(context) {
    // Map sections to Banner section records
    context.sections = await Promise.map(
        context.sections,
        section => BannerOperations.getCourseSection(section.term, section.crn));
}

async function getEnrollmentTerm(context) {
    context.ws.emit('ui:progress:show', {text: 'Getting data from Banner'});

    // Add enrollment term object to context from Canvas filtered by SIS ID
    context.enrollmentTerm = await context.canvasApi.getEnrollmentTermBySisId(context.parentTerm);
    Logger.debug(`Looked up Canvas enrollment term by SIS ID ${context.parentTerm}`, context.enrollmentTerm);
}

async function migrateCourseContent(context) {
    // Is a course content migration requested?
    if(context.migrateFrom) {
        Logger.info(`Course creation task includes a content migration request from course ${context.migrateFrom}`);

        // Create migration request
        await context.canvasApi.createMigration(context.migrateFrom, context.canvasCourse.id);
    }
}

async function validateSectionsDoNotExist(context) {
    // Check each section in the request
    for(let section of context.sections) {
        Logger.info(`Verifiying that course section ${section.term}:${section.crn} is not already provisioned in Canvas`);

        // Run database query to check section status
        let sectioninBanner = (await BannerOperations.isSectionTracked(section.term, section.crn, { rejectOnUntracked: false })) !== null;
        Logger.info(`Validation of ${section.term}:${section.crn} completed`);

        // Check result
        if(sectioninBanner) {
            throw new Error(`Cannot create a Canvas course because section ${section.term}:${section.crn} is already provisioned`);
        }
    }
}
<commit_msg>Cleaned up log statements<commit_after>/**
 * Copyright (c) 2016, Foothill-De Anza Community College District
 * 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 the copyright holder 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.
 */

'use strict';
let BannerOperations = require('../../BannerOperations.js');
let CollegeManager = require('../../CollegeManager.js');
let Common = require('../../Common.js');
let CourseNameHelper = require('../../CourseNameHelper');
let Logger = require('fhda-pubsub-logging')('ws-action-create-course');
let ProgressMonitor = require('../../ProgressMonitor.js');
let Random = require('random-gen');

/**
 * Handle a websocket request to create a new Canvas course/site classroom.
 * @param  {Object} data Event data payload
 * @param  {Function} respond Callback function to send a response back to the client
 * @return {Promise} Resolved when the operation is complete
 */
module.exports = async function (data, respond) {
    // Capture audit record
    await BannerOperations.recordWebAudit(
        this.decoded_token.aud,
        'canvas:createCourse',
        data,
        this.conn.remoteAddress);

    // Lookup college configuration
    let college = CollegeManager[data.college];

    // Create an execution context
    let context = Object.assign(data, {
        canvasApi: college.canvasApi,
        canvasSections: {},
        college: college,
        enrollment: {},
        instructors: [],
        migrateFrom: data.migrateFrom,
        progress: new ProgressMonitor(),
        ws: this
    }, data);

    // Attach an event handler to reporting progress changed back to the UI
    context.progress.on('progressUpdated', function(data) {
        context.ws.emit('ui:progress:setPercent', data);
    });

    try {
        // Execute a pipline of coordinating course creation tasks
        // (each function is passed the context for the operation where data and
        // objects are shared across the request)
        await getEnrollmentTerm(context);
        await validateSectionsDoNotExist(context);
        await getBannerCourse(context);
        await getBannerSections(context);
        await createCourseSite(context);
        await createSections(context);
        await enrollInstructors(context);
        await migrateCourseContent(context);

        // Tell websocket caller we are done
        respond({status: 'done'});

        // Return context for additional integration testing
        return context;
    }
    catch(error) {
        // Log the error
        Logger.error('A serious error occurred while attempting to create a new Canvas course', {
            error: error.message,
            parentTerm: context.parentTerm,
            parentCrn: context.parentCrn,
            sections: context.sections
        });

        // Tell websocket about the error
        respond({status: 'error', message: error.message});

        // Rollback the course
        await context.canvasApi.deleteCourse(context.canvasCourse.id);
        Logger.warn(`Rolled back course that failed its creation phase (id = ${context.canvasCourse.id})`);
        for(let section of context.sections) {
            await BannerOperations.untrackCourseSectionByTermCrn(section.term, section.crn);
            Logger.warn(`Rolled back course section that failed its creation phase (term = ${section.term}, crn = ${section.crn})`);
        }
    }
    finally {
        // Notify the UI that the creation is complete
        context.ws.emit('ui:progress:hide');
    }

};

async function createCourseSite(context) {
    context.ws.emit('ui:progress:setText', {text: 'Creating course'});

    // Generate course name and code
    let courseName = await CourseNameHelper.generateCourseName(
        context.parentTerm,
        context.parentCrn,
        context.sections,
        context.college.config.courseNameTemplate);
    Logger.info(`Generated course name ${courseName}`);

    let courseCode = await CourseNameHelper.generateCourseCode(
        context.parentTerm,
        context.parentCrn,
        context.sections);
    Logger.info(`Generated course code ${courseCode}`);

    // Create course in Canvas
    context.canvasCourse = await context.canvasApi.createCourse({
        'course[name]': courseName,
        'course[course_code]': courseCode,
        'course[term_id]': context.enrollmentTerm.id,
        'course[sis_course_id]': `${context.parentTerm}:${context.sanitizedSubject}${context.sanitizedCourseNumber}:${Random.number(4)}`
    });
}

async function createSections(context) {
    context.ws.emit('ui:progress:setText', {text: 'Creating sections, and enrolling students'});

    // Create each section and enroll students
    await Promise.map(context.sections, async section => {
        let enrollments = await context.college.createSection(section.term, section.crn, context.canvasCourse.id, context.progress);

        // Add enrollments to context
        context.enrollment[section.crn] = enrollments;
    }, Common.concurrency.MULTI);
}

async function enrollInstructors(context) {
    // Get instructors for parent course
    let instructors = await BannerOperations.getInstructors(context.parentTerm, context.parentCrn);

    // Iterate each instructor to sync account and enroll in course
    for(let instructor of instructors) {
        // Add instructor to context
        context.instructors.push(instructor);
        Logger.info(`Preparing to enroll instructor`, instructor);

        // Sync Canvas account
        let profile = await context.canvasApi.syncUser(instructor);

        // Enroll in course
        let enrollment = await context.canvasApi.enrollInstructor(
            context.canvasCourse.id,
            profile.id);

        // Enroll instructor in each section
        for(let section of Object.values(context.enrollment)) {
            await context.canvasApi.enrollInstructorSection(
                section.id,
                profile.id);
        }

        // Add tracked enrollment to Banner
        await BannerOperations.trackEnrollment(
            context.college,
            context.parentTerm,
            context.parentCrn,
            instructor.pidm,
            profile.id,
            'TeacherEnrollment',
            enrollment.id,
            context.canvasCourse.id);

        Logger.info(`Successfully enrolled instructor for course section ${context.parentTerm}:${context.parentCrn}`, instructor);
    }
}

async function getBannerCourse(context) {
    // Lookup parent course in Banner
    context.parentCourse = await BannerOperations.getCourse(context.parentTerm, context.parentCrn);

    // Add data transformations to context
    context.sanitizedSubject = Common.sanitizeSubjectCode(context.parentCourse.subjectCode);
    context.sanitizedCourseNumber = Common.sanitizeCourseNumber(context.parentCourse.courseNumber);
}

async function getBannerSections(context) {
    // Map sections to Banner section records
    context.sections = await Promise.map(
        context.sections,
        section => BannerOperations.getCourseSection(section.term, section.crn));
}

async function getEnrollmentTerm(context) {
    context.ws.emit('ui:progress:show', {text: 'Getting data from Banner'});

    // Add enrollment term object to context from Canvas filtered by SIS ID
    context.enrollmentTerm = await context.canvasApi.getEnrollmentTermBySisId(context.parentTerm);
    Logger.debug(`Looked up Canvas enrollment term by SIS ID ${context.parentTerm}`, context.enrollmentTerm);
}

async function migrateCourseContent(context) {
    // Is a course content migration requested?
    if(context.migrateFrom) {
        Logger.info(`Course creation task includes a content migration request from course ${context.migrateFrom}`);

        // Create migration request
        await context.canvasApi.createMigration(context.migrateFrom, context.canvasCourse.id);
    }
}

async function validateSectionsDoNotExist(context) {
    // Check each section in the request
    for(let section of context.sections) {
        Logger.info(`Verifiying that course section ${section.term}:${section.crn} is not already provisioned in Canvas`);

        // Run database query to check section status
        let sectioninBanner = (await BannerOperations.isSectionTracked(section.term, section.crn, { rejectOnUntracked: false })) !== null;
        Logger.info(`Validation of ${section.term}:${section.crn} completed`);

        // Check result
        if(sectioninBanner) {
            throw new Error(`Cannot create a Canvas course because section ${section.term}:${section.crn} is already provisioned`);
        }
    }
}
<|endoftext|>