<commit_before>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * 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.
 */

// Accumulator which formats objects in a column.  Used within GridView
var COL = {
  create: function() { return { __proto__: this, values: [] }; },
  put: function(v) { this.values.push(v); },
  toHTML: function() {
    var s = '';
    var vs = this.values;
    for ( var i = 0 ; i < vs.length ; i++ ) s += vs[i].toHTML ? vs[i].toHTML() : vs[i];
    return s;
  },
  initHTML: function() {
    for ( var i in this.values ) {
      var o = this.values[i];
      o.initHTML && o.initHTML();
    }
  },
  clone: function() { return this.create(); }
};

var DragAndDropGrid = FOAM({
  model_: 'Model',
  extendsModel: 'GridByExpr',

  properties: [
    {
      name: 'dao'
    }
  ],

  methods: {
    renderCell: function(x, y, value) {
      var cell = IssueDropCell.create({
        value: value,
        dao: this.dao,
        props: [this.xFunc, this.yFunc],
        values: [x, y]
      });
      this.children.push(cell);
      return cell.toHTML();
    },
    clone: function() {
      var clone = this.SUPER();
      clone.dao = this.dao;
      return clone;
    }
  }
});

var IssueDropCell = FOAM({
  model_: 'Model',

  extendsModel: 'AbstractView',

  properties: [
    {
      name: 'value'
    },
    {
      name: 'dao',
      hidden: true
    },
    {
      model_: 'ArrayProperty',
      name: 'props'
    },
    {
      model_: 'ArrayProperty',
      name: 'values'
    }
  ],

  methods: {
    toHTML: function() {
      this.on('dragenter', this.onDragEnter, this.getID());
      this.on('dragover', this.onDragEnter, this.getID());
      this.on('drop', this.onDrop, this.getID());
      return '' +
        (this.value ? (this.value.toHTML ? this.value.toHTML() : this.value) : '') + '	';
    },
    initHTML: function() {
      this.SUPER();
      this.value && this.value.initHTML && this.value.initHTML();
    },
    put: function(obj) {
      this.value.put(obj);
    }
  },

  listeners: [
    {
      name: 'onDragEnter',
      code: function(e) {
        for ( var i = 0; i < e.dataTransfer.types.length; i++ ) {
          if ( e.dataTransfer.types[i] === 'application/x-foam-id' ) {
            e.dataTransfer.dropEffect = "move";
            e.preventDefault();
            return;
          }
        }
      }
    },
    {
      name: 'onDrop',
      code: function(e) {
        var data = e.dataTransfer.getData('application/x-foam-id');
        if ( ! data ) return;

        e.preventDefault();

        var props = this.props;
        var values = this.values;
        var dao = this.dao;
        dao.find(data, {
          put: function(obj) {
            obj = obj.clone();
            for ( var i = 0; i < props.length; i++ ) {
              var p = props[i];
              var v = values[i];
              obj[p.name] = v;
            }
            dao.put(obj);
          }
        });
      }
    }
  ]
});

/*
 * An extension to COUNT() which turns count into a link which performs
 * a query for only the selected data when clicked.
 */
var ItemCount = Model.create({
  extendsModel: 'CountExpr',

  properties: [
    {
      name: 'browser'
    }
  ],

  methods: {
    put: function(obj) {
      if ( ! this.obj ) {
        this.obj = obj;
        this.eid = AbstractView.getPrototype().nextID();
      }
      this.SUPER(obj);
    },
    toHTML: function() {
      return '' + this.count + (this.count == 1 ? ' item' : ' items') + '';
    },
    initHTML: function() {
      var f = function() {
        var searchField = this.browser.searchField;
        var altView = this.browser.view;
        var col = altView.views[1].view().col.value.get();
        var row = altView.views[1].view().row.value.get();
        var q = AND(
          QueryParser.parseString(searchField.value.get()),
          EQ(col, col.f(this.obj)),
          EQ(row, row.f(this.obj))).partialEval();
        this.browser.location.mode = Location.MODE.fromMemento.call(this.browser, 'list');
        this.browser.location.q = q.toMQL();
      }.bind(this);
      $(this.eid).addEventListener('click', f, false);
    }
  }
});

// Formats Issue ID's as links to the main crbug.com site.
var IdFormatter = function(browser) {
  return {
    f: function(i) {
      var url = browser.url + '/issues/detail?id=' + i.id;
      return '' + i.id + ' ';
    }
  };
};

var priColorMap = {
  colorMap: {
    'Critical': 'hsl(  0, 100%, 70%)',
    'High':     'hsl(-340, 100%, 70%)',
    'Medium':   'hsl(-320, 100%, 70%)',
    'Low':      'hsl(-300, 100%, 70%)',
    '':         'lightgray'
  }
};

var QIssueTableView = FOAM({
  model_: 'Model',

  name: 'QIssueTableView',

  extendsModel: 'TableView2',

  properties: [
    { name: 'browser' }
  ],

  methods: {
    /*
    initHTML: function() {
      this.SUPER();

      this.selection.addListener(function(_,_,_,obj) {
        if ( obj.id && obj.id !== this.browser.previewID ) this.browser.preview(null);
      });
    },
    */
    toHTML: function() {
      return '

' + this.SUPER();
    }
  }
});

function createView(rowSelection, browser) {
  var location = browser.location;

  return AlternateView.create({
    dao: browser.filteredIssueDAO,
    headerView: browser.countField,
    views: [
      ViewChoice.create({
        label: 'List',
        view: function() {
          var tableView = QIssueTableView.create({
            model: QIssue,
            dao: browser.filteredIssueDAO,
            browser: browser,
            hardSelection: rowSelection,
            scrollEnabled: true,
            editColumnsEnabled: true
          });

          tableView.sortOrder$  = location.sort$;
          tableView.properties$ = location.colspec$;

          tableView.window = browser.X.window;  // TODO: fix
          return tableView;
        }
      }),
      ViewChoice.create({
        label: 'Grid',
        view: function() {
           var g = Model.create({
              name: 'QIssueGridView',
              extendsModel: 'GridView',
              properties: [
                {
                  name: 'dao',
                  postSet: function(old, dao) {
                     if ( this.listener ) {
                        old && old.unlisten(this.listener);
                        dao.listen(this.listener);
                     }
                     this.grid.dao = dao;
                     this.updateHTML();
                  },
                  // crbug limits grid view to 6000 rows, so do the same
                  getter: function() {
                    return ( this.acc.choice && this.acc.choice[1] === 'Tiles' && this.instance_.dao ) ?
                      this.instance_.dao.limit(2000) :
                      this.instance_.dao ;
                  }
                },
              ],
              methods: {
                 init: function(args) {
                    this.SUPER(args);
                    this.listener = {
                       put:    this.daoUpdate,
                       remove: this.daoUpdate
                    };
                 }
              },
              listeners: [
                 {
                    name: 'daoUpdate',
                    isMerged: 1000,
                    code: function() { this.updateHTML(); }
                 }
              ]}).create({
                model: QIssue,
                accChoices: [
                  [ MAP(QIssueTileView.create({browser: browser}), COL.create()), "Tiles" ],
                  [ MAP(IdFormatter(browser), COL.create()),                      "IDs" ],
                  [ ItemCount.create({browser: browser}),                         "Counts" ],
                  [ PIE(QIssue.STATUS),                                           "Pie(Status)"  ],
                  [ PIE(QIssue.PRIORITY, priColorMap),                            "Pie(Priority)" ]
                  //                 [ PIE(QIssue.STATE, {colorMap: {open:'red',closed:'green'}}), "PIE(State)" ]
                ],
              grid: /*GridByExpr*/DragAndDropGrid.create({})
           });

          g.row.value = location.y$;
          g.col.value = location.x$;

          // TODO: cleanup this block
          function setAcc() {
            var acc = g.accChoices[0];
            for ( var i = 1 ; i < g.accChoices.length ; i++ ) {
              if ( location.cells === g.accChoices[i][1].toLowerCase() ) acc = g.accChoices[i];
            }
            g.acc.choice = acc;
          }
          setAcc(location.cells);

          g.acc.data$.addListener(function(choice) { location.cells = g.acc.choice[1].toLowerCase(); });
          location.cells$.addListener(setAcc);

          return g;
        }
      })
    ]
  });
}
<commit_msg>QuickBug: Reconnected grid view row and col.<commit_after>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * 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.
 */

// Accumulator which formats objects in a column.  Used within GridView
var COL = {
  create: function() { return { __proto__: this, values: [] }; },
  put: function(v) { this.values.push(v); },
  toHTML: function() {
    var s = '';
    var vs = this.values;
    for ( var i = 0 ; i < vs.length ; i++ ) s += vs[i].toHTML ? vs[i].toHTML() : vs[i];
    return s;
  },
  initHTML: function() {
    for ( var i in this.values ) {
      var o = this.values[i];
      o.initHTML && o.initHTML();
    }
  },
  clone: function() { return this.create(); }
};

var DragAndDropGrid = FOAM({
  model_: 'Model',
  extendsModel: 'GridByExpr',

  properties: [
    {
      name: 'dao'
    }
  ],

  methods: {
    renderCell: function(x, y, value) {
      var cell = IssueDropCell.create({
        value: value,
        dao: this.dao,
        props: [this.xFunc, this.yFunc],
        values: [x, y]
      });
      this.children.push(cell);
      return cell.toHTML();
    },
    clone: function() {
      var clone = this.SUPER();
      clone.dao = this.dao;
      return clone;
    }
  }
});

var IssueDropCell = FOAM({
  model_: 'Model',

  extendsModel: 'AbstractView',

  properties: [
    {
      name: 'value'
    },
    {
      name: 'dao',
      hidden: true
    },
    {
      model_: 'ArrayProperty',
      name: 'props'
    },
    {
      model_: 'ArrayProperty',
      name: 'values'
    }
  ],

  methods: {
    toHTML: function() {
      this.on('dragenter', this.onDragEnter, this.getID());
      this.on('dragover', this.onDragEnter, this.getID());
      this.on('drop', this.onDrop, this.getID());
      return '' +
        (this.value ? (this.value.toHTML ? this.value.toHTML() : this.value) : '') + '	';
    },
    initHTML: function() {
      this.SUPER();
      this.value && this.value.initHTML && this.value.initHTML();
    },
    put: function(obj) {
      this.value.put(obj);
    }
  },

  listeners: [
    {
      name: 'onDragEnter',
      code: function(e) {
        for ( var i = 0; i < e.dataTransfer.types.length; i++ ) {
          if ( e.dataTransfer.types[i] === 'application/x-foam-id' ) {
            e.dataTransfer.dropEffect = "move";
            e.preventDefault();
            return;
          }
        }
      }
    },
    {
      name: 'onDrop',
      code: function(e) {
        var data = e.dataTransfer.getData('application/x-foam-id');
        if ( ! data ) return;

        e.preventDefault();

        var props = this.props;
        var values = this.values;
        var dao = this.dao;
        dao.find(data, {
          put: function(obj) {
            obj = obj.clone();
            for ( var i = 0; i < props.length; i++ ) {
              var p = props[i];
              var v = values[i];
              obj[p.name] = v;
            }
            dao.put(obj);
          }
        });
      }
    }
  ]
});

/*
 * An extension to COUNT() which turns count into a link which performs
 * a query for only the selected data when clicked.
 */
var ItemCount = Model.create({
  extendsModel: 'CountExpr',

  properties: [
    {
      name: 'browser'
    }
  ],

  methods: {
    put: function(obj) {
      if ( ! this.obj ) {
        this.obj = obj;
        this.eid = AbstractView.getPrototype().nextID();
      }
      this.SUPER(obj);
    },
    toHTML: function() {
      return '' + this.count + (this.count == 1 ? ' item' : ' items') + '';
    },
    initHTML: function() {
      var f = function() {
        var searchField = this.browser.searchField;
        var altView = this.browser.view;
        var col = altView.views[1].view().col.value.get();
        var row = altView.views[1].view().row.value.get();
        var q = AND(
          QueryParser.parseString(searchField.value.get()),
          EQ(col, col.f(this.obj)),
          EQ(row, row.f(this.obj))).partialEval();
        this.browser.location.mode = Location.MODE.fromMemento.call(this.browser, 'list');
        this.browser.location.q = q.toMQL();
      }.bind(this);
      $(this.eid).addEventListener('click', f, false);
    }
  }
});

// Formats Issue ID's as links to the main crbug.com site.
var IdFormatter = function(browser) {
  return {
    f: function(i) {
      var url = browser.url + '/issues/detail?id=' + i.id;
      return '' + i.id + ' ';
    }
  };
};

var priColorMap = {
  colorMap: {
    'Critical': 'hsl(  0, 100%, 70%)',
    'High':     'hsl(-340, 100%, 70%)',
    'Medium':   'hsl(-320, 100%, 70%)',
    'Low':      'hsl(-300, 100%, 70%)',
    '':         'lightgray'
  }
};

var QIssueTableView = FOAM({
  model_: 'Model',

  name: 'QIssueTableView',

  extendsModel: 'TableView2',

  properties: [
    { name: 'browser' }
  ],

  methods: {
    /*
    initHTML: function() {
      this.SUPER();

      this.selection.addListener(function(_,_,_,obj) {
        if ( obj.id && obj.id !== this.browser.previewID ) this.browser.preview(null);
      });
    },
    */
    toHTML: function() {
      return '

' + this.SUPER();
    }
  }
});

function createView(rowSelection, browser) {
  var location = browser.location;

  return AlternateView.create({
    dao: browser.filteredIssueDAO,
    headerView: browser.countField,
    views: [
      ViewChoice.create({
        label: 'List',
        view: function() {
          var tableView = QIssueTableView.create({
            model:              QIssue,
            dao:                browser.filteredIssueDAO,
            browser:            browser,
            hardSelection:      rowSelection,
            scrollEnabled:      true,
            editColumnsEnabled: true
          });

          tableView.sortOrder$  = location.sort$;
          tableView.properties$ = location.colspec$;

          tableView.window = browser.X.window;  // TODO: fix
          return tableView;
        }
      }),
      ViewChoice.create({
        label: 'Grid',
        view: function() {
           var g = Model.create({
              name: 'QIssueGridView',
              extendsModel: 'GridView',
              properties: [
                {
                  name: 'dao',
                  postSet: function(old, dao) {
                     if ( this.listener ) {
                        old && old.unlisten(this.listener);
                        dao.listen(this.listener);
                     }
                     this.grid.dao = dao;
                     this.updateHTML();
                  },
                  // crbug limits grid view to 6000 rows, so do the same
                  getter: function() {
                    return ( this.acc.choice && this.acc.choice[1] === 'Tiles' && this.instance_.dao ) ?
                      this.instance_.dao.limit(2000) :
                      this.instance_.dao ;
                  }
                },
              ],
              methods: {
                 init: function(args) {
                    this.SUPER(args);
                    this.listener = {
                       put:    this.daoUpdate,
                       remove: this.daoUpdate
                    };
                 }
              },
              listeners: [
                 {
                    name: 'daoUpdate',
                    isMerged: 1000,
                    code: function() { this.updateHTML(); }
                 }
              ]}).create({
                model: QIssue,
                accChoices: [
                  [ MAP(QIssueTileView.create({browser: browser}), COL.create()), "Tiles" ],
                  [ MAP(IdFormatter(browser), COL.create()),                      "IDs" ],
                  [ ItemCount.create({browser: browser}),                         "Counts" ],
                  [ PIE(QIssue.STATUS),                                           "Pie(Status)"  ],
                  [ PIE(QIssue.PRIORITY, priColorMap),                            "Pie(Priority)" ]
                  //                 [ PIE(QIssue.STATE, {colorMap: {open:'red',closed:'green'}}), "PIE(State)" ]
                ],
              grid: /*GridByExpr*/DragAndDropGrid.create({})
           });

          g.row.data$ = location.y$;
          g.col.data$ = location.x$;

          // TODO: cleanup this block
          function setAcc() {
            var acc = g.accChoices[0];
            for ( var i = 1 ; i < g.accChoices.length ; i++ ) {
              if ( location.cells === g.accChoices[i][1].toLowerCase() ) acc = g.accChoices[i];
            }
            g.acc.choice = acc;
          }
          setAcc(location.cells);

          g.acc.data$.addListener(function(choice) { location.cells = g.acc.choice[1].toLowerCase(); });
          location.cells$.addListener(setAcc);

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


<commit_before>import AWS from 'aws-sdk';
import Datastore from 'nedb-promise';

import config from './config';

export const usingAWS = !!config.db;

const databases = {
    nedb: !usingAWS && {
        messages: new Datastore({
            filename: `./db/messages.db`,
            timestampData: true,
            autoload: true
        }),
        users: new Datastore({
            filename: `./db/users.db`,
            timestampData: true,
            autoload: true
        })
    },
    dynamodb: usingAWS && {
        messages: new AWS.DynamoDB.DocumentClient({
            ...config.db.messages,
            logger: console
        }),
        users: new AWS.DynamoDB.DocumentClient({
            ...config.db.users,
            logger: console
        })
    }
};

export async function findUserByID(id) {
    if (usingAWS) {
        const params = {
            Key: {
                id: parseInt(id, 10)
            }
        };

        return await new Promise((resolve, reject) => {
            databases.dynamodb.users.get(params, function (err, data) {
                if (err) {
                    return reject(err);
                }

                // DynamoDB returns an empty object if not found, so resolve that as a null
                if (Object.keys(data).length === 0 && data.constructor === Object) {
                    return resolve(null);
                }

                resolve(data.Item);
            });
        });
    } else {
        return await databases.nedb.users.findOne({id});
    }
}

/**
 * This will update a user in the database by a given ID, upserting it if it doesn't exist.
 *
 * @param {number} id
 * @param {object} data
 */
export function updateUserByID(id, data) {
    if (usingAWS) {
        const params = {
            Key: {
                id: parseInt(id, 10)
            },
            ExpressionAttributeValues: {
                ":1": data.warnings,
                ":2": data.username,
                ":3": data.discriminator
            },
            ExpressionAttributeNames: {
                "#1": "warnings",
                "#2": "username",
                "#3": "discriminator"
            },
            UpdateExpression: "SET #1=:1, #2=:2, #3=:3",
            ReturnValues: "ALL_NEW"
        };

        return new Promise((resolve, reject) => {
            databases.dynamodb.users.update(params, function (err, data) {
                if (err) {
                    return reject(err);
                }

                resolve(data);
            });
        });
    } else {
        return databases.nedb.users.update({id}, data, {upsert: true});
    }
}

/**
 * This will update a message in the database by a given ID, upserting it if it doesn't exist.
 *
 * @param {number} id
 * @param {object} data
 */
export function updateMessageByID(id, data) {
    if (usingAWS) {
        const params = {
            Key: {
                id: parseInt(id, 10)
            },
            ExpressionAttributeValues: {
                ":1": data.isSystemMessage,
                ":2": data.isBotMessage,
                ":3": data.content,
                ":4": {
                    id: data.channel.id,
                    name: data.channel.name
                },
                ":5": {
                    id: data.user.id,
                    username: data.user.username,
                    discriminator: data.user.discriminator
                },
                ":6": data.timestamp,
                ":7": data.deletedAt
            },
            ExpressionAttributeNames: {
                "#1": "isSystemMessage",
                "#2": "isBotMessage",
                "#3": "content",
                "#4": "channel",
                "#5": "user",
                "#6": "timestamp",
                "#7": "deletedAt",
            },
            UpdateExpression: "SET #1=:1, #2=:2, #3=:3, #4=:4, #5=:5, #6=:6, #7=:7",
            ReturnValues: "ALL_NEW"
        };

        return new Promise((resolve, reject) => {
            databases.dynamodb.messages.update(params, function (err, data) {
                if (err) {
                    return reject(err);
                }

                resolve(data);
            });
        });
    } else {
        return databases.nedb.messages.update({id}, data, {upsert: true});
    }
}

/**
 * This will count the number of messages matching provided message in the last x seconds.
 *
 * @param {string} message
 * @param {number} [seconds=30]
 */
export async function countMessagesInLast(message, seconds = 30) {
    let timeAgo = new Date();
    timeAgo.setSeconds(timeAgo.getSeconds() - seconds);

    if (usingAWS) {
        const params = {
            Select: 'COUNT',
            FilterExpression: '#1 > :1',
            ExpressionAttributeValues: {
                ':1': timeAgo.toISOString(),
            },
            ExpressionAttributeNames: {
                '#1': 'timestamp'
            },
            ReturnValues: 'ALL_NEW'
        };

        return new Promise((resolve, reject) => {
            databases.dynamodb.messages.scan(params, function (err, data) {
                if (err) {
                    return reject(err);
                }

                resolve(data.Count || 0);
            });
        });
    } else {
        return await databases.nedb.messages.count({
            content: message,
            createdAt: {$gt: timeAgo}
        });
    }
}<commit_msg>Fix broken count<commit_after>import AWS from 'aws-sdk';
import Datastore from 'nedb-promise';

import config from './config';

export const usingAWS = !!config.db;

const databases = {
    nedb: !usingAWS && {
        messages: new Datastore({
            filename: `./db/messages.db`,
            timestampData: true,
            autoload: true
        }),
        users: new Datastore({
            filename: `./db/users.db`,
            timestampData: true,
            autoload: true
        })
    },
    dynamodb: usingAWS && {
        messages: new AWS.DynamoDB.DocumentClient({
            ...config.db.messages,
            logger: console
        }),
        users: new AWS.DynamoDB.DocumentClient({
            ...config.db.users,
            logger: console
        })
    }
};

export async function findUserByID(id) {
    if (usingAWS) {
        const params = {
            Key: {
                id: parseInt(id, 10)
            }
        };

        return await new Promise((resolve, reject) => {
            databases.dynamodb.users.get(params, function (err, data) {
                if (err) {
                    return reject(err);
                }

                // DynamoDB returns an empty object if not found, so resolve that as a null
                if (Object.keys(data).length === 0 && data.constructor === Object) {
                    return resolve(null);
                }

                resolve(data.Item);
            });
        });
    } else {
        return await databases.nedb.users.findOne({id});
    }
}

/**
 * This will update a user in the database by a given ID, upserting it if it doesn't exist.
 *
 * @param {number} id
 * @param {object} data
 */
export function updateUserByID(id, data) {
    if (usingAWS) {
        const params = {
            Key: {
                id: parseInt(id, 10)
            },
            ExpressionAttributeValues: {
                ":1": data.warnings,
                ":2": data.username,
                ":3": data.discriminator
            },
            ExpressionAttributeNames: {
                "#1": "warnings",
                "#2": "username",
                "#3": "discriminator"
            },
            UpdateExpression: "SET #1=:1, #2=:2, #3=:3",
            ReturnValues: "ALL_NEW"
        };

        return new Promise((resolve, reject) => {
            databases.dynamodb.users.update(params, function (err, data) {
                if (err) {
                    return reject(err);
                }

                resolve(data);
            });
        });
    } else {
        return databases.nedb.users.update({id}, data, {upsert: true});
    }
}

/**
 * This will update a message in the database by a given ID, upserting it if it doesn't exist.
 *
 * @param {number} id
 * @param {object} data
 */
export function updateMessageByID(id, data) {
    if (usingAWS) {
        const params = {
            Key: {
                id: parseInt(id, 10)
            },
            ExpressionAttributeValues: {
                ":1": data.isSystemMessage,
                ":2": data.isBotMessage,
                ":3": data.content,
                ":4": {
                    id: data.channel.id,
                    name: data.channel.name
                },
                ":5": {
                    id: data.user.id,
                    username: data.user.username,
                    discriminator: data.user.discriminator
                },
                ":6": data.timestamp,
                ":7": data.deletedAt
            },
            ExpressionAttributeNames: {
                "#1": "isSystemMessage",
                "#2": "isBotMessage",
                "#3": "content",
                "#4": "channel",
                "#5": "user",
                "#6": "timestamp",
                "#7": "deletedAt",
            },
            UpdateExpression: "SET #1=:1, #2=:2, #3=:3, #4=:4, #5=:5, #6=:6, #7=:7",
            ReturnValues: "ALL_NEW"
        };

        return new Promise((resolve, reject) => {
            databases.dynamodb.messages.update(params, function (err, data) {
                if (err) {
                    return reject(err);
                }

                resolve(data);
            });
        });
    } else {
        return databases.nedb.messages.update({id}, data, {upsert: true});
    }
}

/**
 * This will count the number of messages matching provided message in the last x seconds.
 *
 * @param {string} message
 * @param {number} [seconds=30]
 */
export async function countMessagesInLast(message, seconds = 30) {
    let timeAgo = new Date();
    timeAgo.setSeconds(timeAgo.getSeconds() - seconds);

    if (usingAWS) {
        const params = {
            Select: 'COUNT',
            FilterExpression: '#1 == :1 AND #2  > :2',
            ExpressionAttributeValues: {
                ':1': message,
                ':2': timeAgo.toISOString()
            },
            ExpressionAttributeNames: {
                '#1': 'content',
                '#2': 'timestamp'
            },
            ReturnValues: 'ALL_NEW'
        };

        return new Promise((resolve, reject) => {
            databases.dynamodb.messages.scan(params, function (err, data) {
                if (err) {
                    return reject(err);
                }

                resolve(data.Count || 0);
            });
        });
    } else {
        return await databases.nedb.messages.count({
            content: message,
            createdAt: {$gt: timeAgo}
        });
    }
}<|endoftext|>


<commit_before>'use strict';

angular.module('civis.youpower.cooperatives', ['highcharts-ng'])

.controller('CooperativeCtrl', function($scope,$timeout,$state,$q,Cooperatives) {

  $scope.comparisons = [
    {name: ""},
    {name: "similar cooperatives (average)"},
    {name: "previous year"},
    {name: "previous year (normalized)"}
  ]

  var currentDate = new Date();
  currentDate.setDate(1);

  $scope.settings = {
    granularity: "monthly",
    compareTo: "",
    type: "electricity",
    endDate: currentDate
  }

  $scope.changeComparison = function(){
    updateEnergyData();
  };

  $scope.changeType = function(type){
    $scope.settings.type = type;
    updateEnergyData();
  }

  $scope.changeGranularity = function(granularity){
    $scope.settings.granularity = granularity;
    updateEnergyData();
  }

  $scope.$on("$ionicView.enter",function(){
    // Get the cooperative, currently hardcoded
    Cooperatives.get({id:'55f14ce337d4bef728a861ab'},function(data){
      $scope.cooperative = data;
      initChart();
    });
  })

  // Sets the initial chart configuration
  var initChart = function(data) {
    if($scope.chartConfig) {
      return;
    }

    $scope.chartConfig = {

        // Standard configuration option
        options: {
            //This is the Main Highcharts chart config. Any Highchart options are valid here.
            //will be overriden by values specified below.
            chart: {
              animation: true,
              type: 'column'
            },
            legend: {
              enabled: false
            },
            yAxis: [{
              gridLineWidth: 0,
              title: null,
              labels: {
                enabled: false
              }
            },{
              gridLineWidth: 0,
              title: null,
              labels: {
                enabled: false
              }
            }],
        },

        //The below properties are watched separately for changes.
        series: [{
           data: data,
           // pointPadding: 0.001,
           groupPadding: 0.01
        },{
          type: 'spline'
        }],

        title: {
           text: null
        },

        xAxis: {
          // categories: $scope.categories,
          type: 'datetime',
          // tickWidth: 0,
          // tickLength: 20,
          // labels: {
          //   y: -10,
          //   useHTML: true,
          //   formatter: function(){
          //     if($scope.cooperative) {
          //       var currentPeriod = this.value;
          //       var actionsInPeriod = _.reduce($scope.cooperative.actions,function(result, action, index){
          //         var date = new Date(action.date);
          //         if(currentPeriod.value == date.getMonth()){
          //           result = result + (result.length ? ", " : "") + (index + 1);
          //         }
          //         return result;
          //       },"");
          //       return this.value.label + "
" + actionsInPeriod + "";
          //     } else return this.value.label;
          //   }
          // },
        },
        func: function(chart) {
          $timeout(function(){
            chart.series[1].setVisible(false);
            updateEnergyData();

            // View functions that only make sense when data is loaded
            $scope.getMainMax = function(){
              // var chart = $scope.chartConfig.getHighcharts();
              return _.max(chart.series[0].data,function(value){return value.y});
            }

            $scope.getMainMin = function(){
              // var chart = $scope.chartConfig.getHighcharts();
              return _.min(chart.series[0].data,function(value){return value.y});
            }

            $scope.getMainAvg = function(){
              // var chart = $scope.chartConfig.getHighcharts();
              return _.reduce(chart.series[0].data,function(memo, value){return memo + (value ? value.y : 0)},0)/chart.series[0].data.length;
            }

            $scope.getComparedMax = function(){
              // var chart = $scope.chartConfig.getHighcharts();
              return _.max(chart.series[1].data,function(value){return value.y});
            }

            $scope.getComparedMin = function(){
              // var chart = $scope.chartConfig.getHighcharts();
              return _.min(chart.series[1].data,function(value){return value.y});
            }

            $scope.getComparedAvg = function(){
              // var chart = $scope.chartConfig.getHighcharts();
              return _.reduce(chart.series[1].data,function(memo, value){return memo + (value ? value.y : 0)},0)/chart.series[1].data.length;
            }
          });
        }
      }
  }

  // Helper function should be moved somewhere more general
  var padMonth = function(value) {
    return value < 10 ? '0' + value : '' + value;
  }

  // Updates the chart depending on the granularity, period, type of energy and comparison
  var updateEnergyData = function() {
    $scope.chartConfig.loading = true;
    var period;
    var chart = $scope.chartConfig.getHighcharts();
    var startDate = new Date($scope.settings.endDate);
    var toYear = startDate.getFullYear();
    if($scope.settings.granularity == 'yearly'){
      var fromYear = 2010;
      period = fromYear + '-' + toYear;
    } else {
      var toMonth = padMonth(startDate.getMonth())
      startDate.setFullYear(startDate.getFullYear()-1);
      var fromYear = startDate.getFullYear();
      var fromMonth = padMonth(startDate.getMonth() + 1);
      period = fromYear + fromMonth + '-' + toYear + toMonth;
    }
    var results = [];
    results.push($scope.cooperative.getEnergyData($scope.settings.type,'month',period));
    if($scope.settings.compareTo ==  "previous year" && $scope.settings.granularity == 'monthly'){
      period = (fromYear -1) + fromMonth + '-' + (toYear-1) + toMonth;
      results.push($scope.cooperative.getEnergyData($scope.settings.type,'month',period));
    }
    $q.all(results).then(function(response){
      _.each(response,function(data,i){
        if($scope.settings.granularity == 'yearly'){
          chart.series[i].setData(_.reduce(data.data.data[0].periods[0].energy,function(memo,value,index){
            if(index % 12 == 0){
              var date = new Date(2010 + Math.floor(index/12),1,1);
              memo.push({x:date, y:value});
            } else {
              memo[memo.length - 1].y += value;
            }
            return memo;
          },[]));
        } else {
          chart.series[i].setData(_.map(data.data.data[0].periods[0].energy,function(value, index){
            var date = new Date(startDate);
            date.setDate(1);
            date.setMonth(date.getMonth()+index)
            return { x: date, y:value/1000}
          }));
        }
        chart.series[i].setVisible(true);
      });
      chart.redraw();
      $scope.chartConfig.loading = false;
    });
  }

  // Moves the chart one month forward/back
  $scope.move = function(direction) {
    $scope.chartConfig.loading = true;
    var fn = direction > 0 ? _.last : _.first;
    var chart = $scope.chartConfig.getHighcharts();
    var date = new Date(fn(chart.series[0].data).x)
    date.setMonth(date.getMonth() + direction);
    $scope.settings.endDate.setMonth($scope.settings.endDate.getMonth() + direction);
    var period = date.getFullYear() + '' + padMonth(date.getMonth()+1);
    var results = []
    results.push($scope.cooperative.getEnergyData($scope.settings.type,'month',period));
    if($scope.settings.compareTo ==  "previous year"){
      period = (date.getFullYear() - 1) + '' + padMonth(date.getMonth()+1);
      results.push($scope.cooperative.getEnergyData($scope.settings.type,'month',period));
    }
    $q.all(results).then(function(data){
      var value1 = data[0].data.data[0].periods[0].energy[0];
      var value2 = data.length == 2 ? data[1].data.data[0].periods[0].energy[0] : null;
      if (value1 != null) {
        if(direction > 0) {
          chart.series[0].removePoint(0,false);
          chart.series[0].addPoint({x:date,y:value1/1000},false,false);
          chart.series[1].removePoint(0,false);
          if (value2 != null) {
            chart.series[1].addPoint({x:date,y:value2/1000},false,false);
          }
        } else {
          chart.series[0].removePoint(11,false);
          chart.series[0].addPoint({x:date,y:value1/1000},false,false);
          chart.series[1].removePoint(11,false);
          if (value2 != null) {
            chart.series[1].addPoint({x:date,y:value2/1000},false,false);
          }
        }
        chart.redraw();
        $scope.chartConfig.loading = false;
      }
    });
  }

})

.controller('CooperativeEditCtrl', function($scope,$state,Cooperatives){
  $scope.$on("$ionicView.enter",function(){
    // Get the cooperative, currently hardcoded
    Cooperatives.get({id:'55f14ce337d4bef728a861ab'},function(data){
      $scope.cooperative = data;
    });
  })

  $scope.deleteAction = function(action){
    Cooperatives.deleteAction({id:'55f14ce337d4bef728a861ab',actionId:action._id},function(){
      $scope.cooperative.actions.splice($scope.cooperative.actions.indexOf(action),1);
    });
  }

  $scope.save = function(){
    Cooperatives.update({id:'55f14ce337d4bef728a861ab'},$scope.cooperative,function(){
      $state.go("^");
    })
  }

})

.controller('CooperativeActionAddCtrl', function($scope,$state,Cooperatives){
  $scope.action = {};

  $scope.addAction = function(){
    Cooperatives.addAction({id:'55f14ce337d4bef728a861ab'},$scope.action,function(){
      $state.go("^");
    })
  };
})

.controller('CooperativeActionUpdateCtrl', function($scope,$state,$stateParams,Cooperatives){
  Cooperatives.get({id:'55f14ce337d4bef728a861ab'},function(data){
    $scope.action = _.findWhere(data.actions,{_id:$stateParams.id});
    $scope.action.date = new Date($scope.action.date);
  });

  $scope.updateAction = function(){
    Cooperatives.updateAction({id:'55f14ce337d4bef728a861ab',actionId:$scope.action._id},$scope.action,function(){
      $state.go("^");
    })
  };
})
<commit_msg>frontend: Making cooperative controllers dynamic<commit_after>'use strict';

angular.module('civis.youpower.cooperatives', ['highcharts-ng'])

.controller('CooperativeCtrl', function($scope,$timeout,$state,$q,Cooperatives,currentUser) {

  $scope.comparisons = [
    {name: ""},
    {name: "similar cooperatives (average)"},
    {name: "previous year"},
    {name: "previous year (normalized)"}
  ]

  var currentDate = new Date();
  currentDate.setDate(1);

  $scope.settings = {
    granularity: "monthly",
    compareTo: "",
    type: "electricity",
    endDate: currentDate
  }

  $scope.changeComparison = function(){
    updateEnergyData();
  };

  $scope.changeType = function(type){
    $scope.settings.type = type;
    updateEnergyData();
  }

  $scope.changeGranularity = function(granularity){
    $scope.settings.granularity = granularity;
    updateEnergyData();
  }

  $scope.$on("$ionicView.enter",function(){
    // Get the cooperative, currently hardcoded
    Cooperatives.get({id:currentUser.cooperativeId},function(data){
      $scope.cooperative = data;
      initChart();
    });
  })

  // Sets the initial chart configuration
  var initChart = function(data) {
    if($scope.chartConfig) {
      return;
    }

    $scope.chartConfig = {

        // Standard configuration option
        options: {
            //This is the Main Highcharts chart config. Any Highchart options are valid here.
            //will be overriden by values specified below.
            chart: {
              animation: true,
              type: 'column'
            },
            legend: {
              enabled: false
            },
            yAxis: [{
              gridLineWidth: 0,
              title: null,
              labels: {
                enabled: false
              }
            },{
              gridLineWidth: 0,
              title: null,
              labels: {
                enabled: false
              }
            }],
        },

        //The below properties are watched separately for changes.
        series: [{
           data: data,
           // pointPadding: 0.001,
           groupPadding: 0.01
        },{
          type: 'spline'
        }],

        title: {
           text: null
        },

        xAxis: {
          // categories: $scope.categories,
          type: 'datetime',
          // tickWidth: 0,
          // tickLength: 20,
          // labels: {
          //   y: -10,
          //   useHTML: true,
          //   formatter: function(){
          //     if($scope.cooperative) {
          //       var currentPeriod = this.value;
          //       var actionsInPeriod = _.reduce($scope.cooperative.actions,function(result, action, index){
          //         var date = new Date(action.date);
          //         if(currentPeriod.value == date.getMonth()){
          //           result = result + (result.length ? ", " : "") + (index + 1);
          //         }
          //         return result;
          //       },"");
          //       return this.value.label + "
" + actionsInPeriod + "";
          //     } else return this.value.label;
          //   }
          // },
        },
        func: function(chart) {
          $timeout(function(){
            chart.series[1].setVisible(false);
            updateEnergyData();

            // View functions that only make sense when data is loaded
            $scope.getMainMax = function(){
              // var chart = $scope.chartConfig.getHighcharts();
              return _.max(chart.series[0].data,function(value){return value.y});
            }

            $scope.getMainMin = function(){
              // var chart = $scope.chartConfig.getHighcharts();
              return _.min(chart.series[0].data,function(value){return value.y});
            }

            $scope.getMainAvg = function(){
              // var chart = $scope.chartConfig.getHighcharts();
              return _.reduce(chart.series[0].data,function(memo, value){return memo + (value ? value.y : 0)},0)/chart.series[0].data.length;
            }

            $scope.getComparedMax = function(){
              // var chart = $scope.chartConfig.getHighcharts();
              return _.max(chart.series[1].data,function(value){return value.y});
            }

            $scope.getComparedMin = function(){
              // var chart = $scope.chartConfig.getHighcharts();
              return _.min(chart.series[1].data,function(value){return value.y});
            }

            $scope.getComparedAvg = function(){
              // var chart = $scope.chartConfig.getHighcharts();
              return _.reduce(chart.series[1].data,function(memo, value){return memo + (value ? value.y : 0)},0)/chart.series[1].data.length;
            }
          });
        }
      }
  }

  // Helper function should be moved somewhere more general
  var padMonth = function(value) {
    return value < 10 ? '0' + value : '' + value;
  }

  // Updates the chart depending on the granularity, period, type of energy and comparison
  var updateEnergyData = function() {
    $scope.chartConfig.loading = true;
    var period;
    var chart = $scope.chartConfig.getHighcharts();
    var startDate = new Date($scope.settings.endDate);
    var toYear = startDate.getFullYear();
    if($scope.settings.granularity == 'yearly'){
      var fromYear = 2010;
      period = fromYear + '-' + toYear;
    } else {
      var toMonth = padMonth(startDate.getMonth())
      startDate.setFullYear(startDate.getFullYear()-1);
      var fromYear = startDate.getFullYear();
      var fromMonth = padMonth(startDate.getMonth() + 1);
      period = fromYear + fromMonth + '-' + toYear + toMonth;
    }
    var results = [];
    results.push($scope.cooperative.getEnergyData($scope.settings.type,'month',period));
    if($scope.settings.compareTo ==  "previous year" && $scope.settings.granularity == 'monthly'){
      period = (fromYear -1) + fromMonth + '-' + (toYear-1) + toMonth;
      results.push($scope.cooperative.getEnergyData($scope.settings.type,'month',period));
    }
    $q.all(results).then(function(response){
      _.each(response,function(data,i){
        if($scope.settings.granularity == 'yearly'){
          chart.series[i].setData(_.reduce(data.data.data[0].periods[0].energy,function(memo,value,index){
            if(index % 12 == 0){
              var date = new Date(2010 + Math.floor(index/12),1,1);
              memo.push({x:date, y:value});
            } else {
              memo[memo.length - 1].y += value;
            }
            return memo;
          },[]));
        } else {
          chart.series[i].setData(_.map(data.data.data[0].periods[0].energy,function(value, index){
            var date = new Date(startDate);
            date.setDate(1);
            date.setMonth(date.getMonth()+index)
            return { x: date, y:value/1000}
          }));
        }
        chart.series[i].setVisible(true);
      });
      chart.redraw();
      $scope.chartConfig.loading = false;
    });
  }

  // Moves the chart one month forward/back
  $scope.move = function(direction) {
    $scope.chartConfig.loading = true;
    var fn = direction > 0 ? _.last : _.first;
    var chart = $scope.chartConfig.getHighcharts();
    var date = new Date(fn(chart.series[0].data).x)
    date.setMonth(date.getMonth() + direction);
    $scope.settings.endDate.setMonth($scope.settings.endDate.getMonth() + direction);
    var period = date.getFullYear() + '' + padMonth(date.getMonth()+1);
    var results = []
    results.push($scope.cooperative.getEnergyData($scope.settings.type,'month',period));
    if($scope.settings.compareTo ==  "previous year"){
      period = (date.getFullYear() - 1) + '' + padMonth(date.getMonth()+1);
      results.push($scope.cooperative.getEnergyData($scope.settings.type,'month',period));
    }
    $q.all(results).then(function(data){
      var value1 = data[0].data.data[0].periods[0].energy[0];
      var value2 = data.length == 2 ? data[1].data.data[0].periods[0].energy[0] : null;
      if (value1 != null) {
        if(direction > 0) {
          chart.series[0].removePoint(0,false);
          chart.series[0].addPoint({x:date,y:value1/1000},false,false);
          chart.series[1].removePoint(0,false);
          if (value2 != null) {
            chart.series[1].addPoint({x:date,y:value2/1000},false,false);
          }
        } else {
          chart.series[0].removePoint(11,false);
          chart.series[0].addPoint({x:date,y:value1/1000},false,false);
          chart.series[1].removePoint(11,false);
          if (value2 != null) {
            chart.series[1].addPoint({x:date,y:value2/1000},false,false);
          }
        }
        chart.redraw();
        $scope.chartConfig.loading = false;
      }
    });
  }

})

.controller('CooperativeEditCtrl', function($scope,$state,Cooperatives,currentUser){
  $scope.$on("$ionicView.enter",function(){
    // Get the cooperative, currently hardcoded
    Cooperatives.get({id:currentUser.cooperativeId},function(data){
      $scope.cooperative = data;
    });
  })

  $scope.deleteAction = function(action){
    Cooperatives.deleteAction({id:$scope.cooperative._id,actionId:action._id},function(){
      $scope.cooperative.actions.splice($scope.cooperative.actions.indexOf(action),1);
    });
  }

  $scope.save = function(){
    Cooperatives.update({id:$scope.cooperative._id},$scope.cooperative,function(){
      $state.go("^");
    })
  }

})

.controller('CooperativeActionAddCtrl', function($scope,$state,Cooperatives,currentUser){
  $scope.action = {};

  $scope.addAction = function(){
    Cooperatives.addAction({id:currentUser.cooperativeId},$scope.action,function(){
      $state.go("^");
    })
  };
})

.controller('CooperativeActionUpdateCtrl', function($scope,$state,$stateParams,Cooperatives){
  Cooperatives.get({id:currentUser.cooperativeId},function(data){
    $scope.action = _.findWhere(data.actions,{_id:$stateParams.id});
    $scope.action.date = new Date($scope.action.date);
  });

  $scope.updateAction = function(){
    Cooperatives.updateAction({id:currentUser.cooperativeId,actionId:$scope.action._id},$scope.action,function(){
      $state.go("^");
    })
  };
})
<|endoftext|>


<commit_before>set_global('$', global.make_zjquery());
set_global('i18n', global.stub_i18n);

set_global('templates', {});
set_global('alert_words', {
    words: ['foo', 'bar'],
});
set_global('channel', {});

zrequire('alert_words_ui');

run_test('render_alert_words_ui', () => {
    var word_list = $('#alert_words_list');
    var appended = [];
    word_list.append = (rendered) => {
        appended.push(rendered);
    };

    var alert_word_items = $.create('alert_word_items');
    word_list.set_find_results('.alert-word-item', alert_word_items);

    templates.render =  (name, args) => {
        assert.equal(name, 'alert_word_settings_item');
        return 'stub-' + args.word;
    };

    var new_alert_word = $('#create_alert_word_name');
    assert(!new_alert_word.is_focused());

    alert_words_ui.render_alert_words_ui();

    assert.deepEqual(appended, [
        'stub-foo',
        'stub-bar',
        'stub-',
    ]);
    assert(new_alert_word.is_focused());
});

run_test('add_alert_word', () => {
    alert_words_ui.render_alert_words_ui = () => {}; // we've already tested this above

    alert_words_ui.set_up_alert_words();

    var word_list = $('#alert_words_list');
    var add_func = word_list.get_on_handler('click', '#create_alert_word_button');

    var new_alert_word = $('#create_alert_word_name');
    var alert_word_status = $('#alert_word_status');
    var alert_word_status_text = $('.alert_word_status_text');
    alert_word_status.set_find_results('.alert_word_status_text', alert_word_status_text);

    // add '' as alert word
    add_func();
    assert.equal(new_alert_word.val(), '');
    assert(alert_word_status.hasClass('alert-danger'));
    assert.equal(alert_word_status_text.text(), "translated: Alert word can't be empty!");
    assert(alert_word_status.visible());

    // add 'foo' as alert word (existing word)
    new_alert_word.val('foo');

    add_func();
    assert(alert_word_status.hasClass('alert-danger'));
    assert.equal(alert_word_status_text.text(), "translated: Alert word already exists!");
    assert(alert_word_status.visible());

    // add 'zot' as alert word (new word)
    new_alert_word.val('zot');

    var success_func;
    var fail_func;
    channel.post = (opts) => {
        assert.equal(opts.url, '/json/users/me/alert_words');
        assert.deepEqual(opts.data, {alert_words: '["zot"]'});
        success_func = opts.success;
        fail_func = opts.error;
    };

    add_func();

    // test failure
    fail_func();
    assert(alert_word_status.hasClass('alert-danger'));
    assert.equal(alert_word_status_text.text(), "translated: Error adding alert word!");
    assert(alert_word_status.visible());

    // test success
    success_func();
    assert(alert_word_status.hasClass('alert-success'));
    assert.equal(alert_word_status_text.text(), "translated: Alert word added successfully!");
    assert(alert_word_status.visible());
});

run_test('add_alert_word_keypress', () => {
    var word_list = $('#alert_words_list');
    var keypress_func = word_list.get_on_handler('keypress', '#create_alert_word_name');

    var new_alert_word = $('#create_alert_word_name');
    new_alert_word.val('zot');

    var event = {
        preventDefault: () => {},
        which: 13,
        target: '#create_alert_word_name',
    };

    var called = false;
    channel.post = (opts) => {
        assert.deepEqual(opts.data, {alert_words: '["zot"]'});
        called = true;
    };

    keypress_func(event);
    assert(called);
});

<commit_msg>tests: Test removing alert words.<commit_after>set_global('$', global.make_zjquery());
set_global('i18n', global.stub_i18n);

set_global('templates', {});
set_global('alert_words', {
    words: ['foo', 'bar'],
});
set_global('channel', {});

zrequire('alert_words_ui');

run_test('render_alert_words_ui', () => {
    var word_list = $('#alert_words_list');
    var appended = [];
    word_list.append = (rendered) => {
        appended.push(rendered);
    };

    var alert_word_items = $.create('alert_word_items');
    word_list.set_find_results('.alert-word-item', alert_word_items);

    templates.render =  (name, args) => {
        assert.equal(name, 'alert_word_settings_item');
        return 'stub-' + args.word;
    };

    var new_alert_word = $('#create_alert_word_name');
    assert(!new_alert_word.is_focused());

    alert_words_ui.render_alert_words_ui();

    assert.deepEqual(appended, [
        'stub-foo',
        'stub-bar',
        'stub-',
    ]);
    assert(new_alert_word.is_focused());
});

run_test('add_alert_word', () => {
    alert_words_ui.render_alert_words_ui = () => {}; // we've already tested this above

    alert_words_ui.set_up_alert_words();

    var word_list = $('#alert_words_list');
    var add_func = word_list.get_on_handler('click', '#create_alert_word_button');

    var new_alert_word = $('#create_alert_word_name');
    var alert_word_status = $('#alert_word_status');
    var alert_word_status_text = $('.alert_word_status_text');
    alert_word_status.set_find_results('.alert_word_status_text', alert_word_status_text);

    // add '' as alert word
    add_func();
    assert.equal(new_alert_word.val(), '');
    assert(alert_word_status.hasClass('alert-danger'));
    assert.equal(alert_word_status_text.text(), "translated: Alert word can't be empty!");
    assert(alert_word_status.visible());

    // add 'foo' as alert word (existing word)
    new_alert_word.val('foo');

    add_func();
    assert(alert_word_status.hasClass('alert-danger'));
    assert.equal(alert_word_status_text.text(), "translated: Alert word already exists!");
    assert(alert_word_status.visible());

    // add 'zot' as alert word (new word)
    new_alert_word.val('zot');

    var success_func;
    var fail_func;
    channel.post = (opts) => {
        assert.equal(opts.url, '/json/users/me/alert_words');
        assert.deepEqual(opts.data, {alert_words: '["zot"]'});
        success_func = opts.success;
        fail_func = opts.error;
    };

    add_func();

    // test failure
    fail_func();
    assert(alert_word_status.hasClass('alert-danger'));
    assert.equal(alert_word_status_text.text(), "translated: Error adding alert word!");
    assert(alert_word_status.visible());

    // test success
    success_func();
    assert(alert_word_status.hasClass('alert-success'));
    assert.equal(alert_word_status_text.text(), "translated: Alert word added successfully!");
    assert(alert_word_status.visible());
});

run_test('add_alert_word_keypress', () => {
    var word_list = $('#alert_words_list');
    var keypress_func = word_list.get_on_handler('keypress', '#create_alert_word_name');

    var new_alert_word = $('#create_alert_word_name');
    new_alert_word.val('zot');

    var event = {
        preventDefault: () => {},
        which: 13,
        target: '#create_alert_word_name',
    };

    var called = false;
    channel.post = (opts) => {
        assert.deepEqual(opts.data, {alert_words: '["zot"]'});
        called = true;
    };

    keypress_func(event);
    assert(called);
});

run_test('remove_alert_word', () => {
    var word_list = $('#alert_words_list');
    var remove_func = word_list.get_on_handler('click', '.remove-alert-word');

    var remove_alert_word = $('.remove-alert-word');
    var list_item = $('li.alert-word-item');
    var val_item = $('span.value');
    val_item.text(i18n.t('zot'));

    remove_alert_word.set_parents_result('li', list_item);
    list_item.set_find_results('.value', val_item);

    var event = {
        currentTarget: '.remove-alert-word',
    };

    var success_func;
    var fail_func;
    channel.del = (opts) => {
        assert.equal(opts.url, '/json/users/me/alert_words');
        assert.deepEqual(opts.data, {alert_words: '["translated: zot"]'});
        success_func = opts.success;
        fail_func = opts.error;
    };

    remove_func(event);

    var alert_word_status = $('#alert_word_status');
    var alert_word_status_text = $('.alert_word_status_text');
    alert_word_status.set_find_results('.alert_word_status_text', alert_word_status_text);

    // test failure
    fail_func();
    assert(alert_word_status.hasClass('alert-danger'));
    assert.equal(alert_word_status_text.text(), "translated: Error removing alert word!");
    assert(alert_word_status.visible());

    // test success
    success_func();
    assert(alert_word_status.hasClass('alert-success'));
    assert.equal(alert_word_status_text.text(), "translated: Alert word removed successfully!");
    assert(alert_word_status.visible());
});

<|endoftext|>


<commit_before>"use strict";

const {strict: assert} = require("assert");

const {zrequire, mock_esm} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const blueslip = require("../zjsunit/zblueslip");
const $ = require("../zjsunit/zjquery");

const input_pill = zrequire("input_pill");
const pill_typeahead = zrequire("pill_typeahead");
const noop = function () {};

const peer_data = zrequire("peer_data");
const people = zrequire("people");
const stream_data = zrequire("stream_data");
const user_groups = zrequire("user_groups");

// set global test variables.
let sort_recipients_called = false;
let sort_streams_called = false;
const fake_rendered_person = $.create("fake-rendered-person");
const fake_rendered_stream = $.create("fake-rendered-stream");
const fake_rendered_group = $.create("fake-redered-group");

mock_esm("../../static/js/typeahead_helper", {
    render_person() {
        return fake_rendered_person;
    },
    render_user_group() {
        return fake_rendered_group;
    },
    render_stream() {
        return fake_rendered_stream;
    },
    sort_streams() {
        sort_streams_called = true;
    },
    sort_recipients() {
        sort_recipients_called = true;
    },
});

const jill = {
    email: "jill@zulip.com",
    user_id: 10,
    full_name: "Jill Hill",
};
const mark = {
    email: "mark@zulip.com",
    user_id: 20,
    full_name: "Marky Mark",
};
const fred = {
    email: "fred@zulip.com",
    user_id: 30,
    full_name: "Fred Flintstone",
};
const me = {
    email: "me@example.com",
    user_id: 40,
    full_name: "me",
};

const persons = [jill, mark, fred, me];
for (const person of persons) {
    people.add_active_user(person);
}

const admins = {
    name: "Admins",
    description: "foo",
    id: 1,
    members: [jill.user_id, mark.user_id],
};
const testers = {
    name: "Testers",
    description: "bar",
    id: 2,
    members: [mark.user_id, fred.user_id, me.user_id],
};

const groups = [admins, testers];
for (const group of groups) {
    user_groups.add(group);
}

const denmark = {
    stream_id: 1,
    name: "Denmark",
    subscribed: true,
    render_subscribers: true,
};
peer_data.set_subscribers(denmark.stream_id, [me.user_id, mark.user_id]);

const sweden = {
    stream_id: 2,
    name: "Sweden",
    subscribed: false,
};
peer_data.set_subscribers(sweden.stream_id, [mark.user_id, jill.user_id]);

const subs = [denmark, sweden];
for (const sub of subs) {
    stream_data.add_sub(sub);
}

run_test("set_up", () => {
    let input_pill_typeahead_called = false;
    const fake_input = $.create(".input");
    fake_input.before = noop;

    const container = $.create(".pill-container");
    container.find = () => fake_input;

    const pill_widget = input_pill.create({
        container,
        create_item_from_text: noop,
        get_text_from_item: noop,
    });

    let opts = {};
    fake_input.typeahead = (config) => {
        assert.equal(config.items, 5);
        assert.ok(config.fixed);
        assert.ok(config.dropup);
        assert.ok(config.stopAdvance);

        assert.equal(typeof config.source, "function");
        assert.equal(typeof config.highlighter, "function");
        assert.equal(typeof config.matcher, "function");
        assert.equal(typeof config.sorter, "function");
        assert.equal(typeof config.updater, "function");

        // test queries
        const fake_stream_this = {
            query: "#Denmark",
        };
        const fake_person_this = {
            query: "me",
        };
        const fake_group_this = {
            query: "test",
        };

        (function test_highlighter() {
            if (opts.stream) {
                // Test stream highlighter for widgets that allow stream pills.
                assert.equal(
                    config.highlighter.call(fake_stream_this, denmark),
                    fake_rendered_stream,
                );
            }
            if (opts.user_group && opts.user) {
                // If user is also allowed along with user_group
                // then we should check that each of them rendered correctly.
                assert.equal(
                    config.highlighter.call(fake_group_this, testers),
                    fake_rendered_group,
                );
                assert.equal(config.highlighter.call(fake_person_this, me), fake_rendered_person);
            }
            if (opts.user && !opts.user_group) {
                assert.equal(config.highlighter.call(fake_person_this, me), fake_rendered_person);
            }
            if (!opts.user && opts.user_group) {
                assert.equal(
                    config.highlighter.call(fake_group_this, testers),
                    fake_rendered_group,
                );
            }
        })();

        (function test_matcher() {
            let result;
            if (opts.stream) {
                result = config.matcher.call(fake_stream_this, denmark);
                assert.ok(result);
                result = config.matcher.call(fake_stream_this, sweden);
                assert.ok(!result);
            }
            if (opts.user_group && opts.user) {
                /* If user pills are also allowed along with user groups.
                We should check that queries matching either a person
                or group is returned. */

                // group query, with correct item.
                result = config.matcher.call(fake_group_this, testers);
                assert.ok(result);
                // group query, with wrong item.
                result = config.matcher.call(fake_group_this, admins);
                assert.ok(!result);
                // person query with correct item.
                result = config.matcher.call(fake_person_this, me);
                assert.ok(result);
                // person query with wrong item.
                result = config.matcher.call(fake_person_this, jill);
                assert.ok(!result);
            }
            if (opts.user_group && !opts.user) {
                result = config.matcher.call(fake_group_this, testers);
                assert.ok(result);
                result = config.matcher.call(fake_group_this, admins);
                assert.ok(!result);
            }
            if (opts.user && !opts.user_group) {
                result = config.matcher.call(fake_person_this, me);
                assert.ok(result);
                result = config.matcher.call(fake_person_this, jill);
                assert.ok(!result);
            }
        })();

        (function test_sorter() {
            if (opts.stream) {
                sort_streams_called = false;
                config.sorter.call(fake_stream_this);
                assert.ok(sort_streams_called);
            }
            if (opts.user_group) {
                sort_recipients_called = false;
                config.sorter.call(fake_group_this, [testers]);
                assert.ok(sort_recipients_called);
            }
            if (opts.user) {
                sort_recipients_called = false;
                config.sorter.call(fake_person_this, [me]);
                assert.ok(sort_recipients_called);
            }
        })();

        (function test_source() {
            let result;
            if (opts.stream) {
                result = config.source.call(fake_stream_this);
                const stream_ids = result.map((stream) => stream.stream_id);
                const expected_stream_ids = [denmark.stream_id, sweden.stream_id];
                assert.deepEqual(stream_ids, expected_stream_ids);
            }

            let expected_result = [];
            let actual_result = [];
            function is_group(item) {
                return item.members;
            }
            result = config.source.call(fake_person_this);
            actual_result = result
                .map((item) => {
                    if (is_group(item)) {
                        return item.id;
                    }
                    return item.user_id;
                })
                .filter(Boolean);
            if (opts.user_group) {
                expected_result = expected_result.concat(groups);
            }
            if (opts.user) {
                if (opts.user_source) {
                    expected_result = expected_result.concat(opts.user_source());
                } else {
                    expected_result = expected_result.concat(persons);
                }
            }
            expected_result = expected_result
                .map((item) => {
                    if (is_group(item)) {
                        return item.id;
                    }
                    return item.user_id;
                })
                .filter(Boolean);
            assert.deepEqual(actual_result, expected_result);
        })();

        (function test_updater() {
            if (opts.user && opts.user_group && opts.stream) {
                // Test it only for the case when all types of pills
                // are allowed, as it would be difficult to keep track
                // of number of items as we call with different types multiple
                // times in this test. So this case checks all possible cases handled by
                // updater in pill_typeahead.

                function number_of_pills() {
                    const pills = pill_widget.items();
                    return pills.length;
                }
                assert.equal(number_of_pills(), 0);
                config.updater.call(fake_stream_this, denmark);
                assert.equal(number_of_pills(), 1);
                config.updater.call(fake_person_this, me);
                assert.equal(number_of_pills(), 2);
                config.updater.call(fake_group_this, testers);
                assert.equal(number_of_pills(), 3);
            }
        })();

        // input_pill_typeahead_called is set true if
        // no exception occurs in pill_typeahead.set_up.
        input_pill_typeahead_called = true;
    };

    function test_pill_typeahead(opts) {
        pill_typeahead.set_up(fake_input, pill_widget, opts);
        assert.ok(input_pill_typeahead_called);
    }

    const all_possible_opts = [
        // These are various possible cases of opts that
        // currently occur in web-app codebase. This list
        // can be extended if some other configuration
        // is added later and its logic is to be tested.

        {user: true},
        // user and custom user source.
        {user: true, user_source: () => [fred, mark]},
        {stream: true},
        {user_group: true},
        {user_group: true, stream: true},
        {user_group: true, user: true},
        {user: true, stream: true},
        {user_group: true, stream: true, user: true},
    ];

    for (const config of all_possible_opts) {
        opts = config;
        test_pill_typeahead(config);
    }

    // Special case to test coverage and to test
    // that we enforce type is always specified
    // by caller.
    opts = {};
    input_pill_typeahead_called = false;
    blueslip.expect("error", "Unspecified possible item types");
    pill_typeahead.set_up(fake_input, pill_widget, {});
    assert.ok(!input_pill_typeahead_called);
});
<commit_msg>node tests: Use mock_template for pill_typeahead.<commit_after>"use strict";

const {strict: assert} = require("assert");

const {zrequire, mock_esm, mock_template} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const blueslip = require("../zjsunit/zblueslip");
const $ = require("../zjsunit/zjquery");

const render_input_pill = mock_template("input_pill.hbs", true);

const input_pill = zrequire("input_pill");
const pill_typeahead = zrequire("pill_typeahead");
const noop = function () {};

const peer_data = zrequire("peer_data");
const people = zrequire("people");
const stream_data = zrequire("stream_data");
const user_groups = zrequire("user_groups");

// set global test variables.
let sort_recipients_called = false;
let sort_streams_called = false;
const fake_rendered_person = $.create("fake-rendered-person");
const fake_rendered_stream = $.create("fake-rendered-stream");
const fake_rendered_group = $.create("fake-redered-group");

mock_esm("../../static/js/typeahead_helper", {
    render_person() {
        return fake_rendered_person;
    },
    render_user_group() {
        return fake_rendered_group;
    },
    render_stream() {
        return fake_rendered_stream;
    },
    sort_streams() {
        sort_streams_called = true;
    },
    sort_recipients() {
        sort_recipients_called = true;
    },
});

const jill = {
    email: "jill@zulip.com",
    user_id: 10,
    full_name: "Jill Hill",
};
const mark = {
    email: "mark@zulip.com",
    user_id: 20,
    full_name: "Marky Mark",
};
const fred = {
    email: "fred@zulip.com",
    user_id: 30,
    full_name: "Fred Flintstone",
};
const me = {
    email: "me@example.com",
    user_id: 40,
    full_name: "me",
};

const persons = [jill, mark, fred, me];
for (const person of persons) {
    people.add_active_user(person);
}

const admins = {
    name: "Admins",
    description: "foo",
    id: 1,
    members: [jill.user_id, mark.user_id],
};
const testers = {
    name: "Testers",
    description: "bar",
    id: 2,
    members: [mark.user_id, fred.user_id, me.user_id],
};

const groups = [admins, testers];
for (const group of groups) {
    user_groups.add(group);
}

const denmark = {
    stream_id: 1,
    name: "Denmark",
    subscribed: true,
    render_subscribers: true,
};
peer_data.set_subscribers(denmark.stream_id, [me.user_id, mark.user_id]);

const sweden = {
    stream_id: 2,
    name: "Sweden",
    subscribed: false,
};
peer_data.set_subscribers(sweden.stream_id, [mark.user_id, jill.user_id]);

const subs = [denmark, sweden];
for (const sub of subs) {
    stream_data.add_sub(sub);
}

run_test("set_up", ({override}) => {
    override(render_input_pill, "f", (data, html) => {
        assert.equal(typeof data.id, "string");
        assert.equal(typeof data.display_value, "string");
        assert.equal(typeof data.has_image, "boolean");
        return html;
    });
    let input_pill_typeahead_called = false;
    const fake_input = $.create(".input");
    fake_input.before = noop;

    const container = $.create(".pill-container");
    container.find = () => fake_input;

    const pill_widget = input_pill.create({
        container,
        create_item_from_text: noop,
        get_text_from_item: noop,
    });

    let opts = {};
    fake_input.typeahead = (config) => {
        assert.equal(config.items, 5);
        assert.ok(config.fixed);
        assert.ok(config.dropup);
        assert.ok(config.stopAdvance);

        assert.equal(typeof config.source, "function");
        assert.equal(typeof config.highlighter, "function");
        assert.equal(typeof config.matcher, "function");
        assert.equal(typeof config.sorter, "function");
        assert.equal(typeof config.updater, "function");

        // test queries
        const fake_stream_this = {
            query: "#Denmark",
        };
        const fake_person_this = {
            query: "me",
        };
        const fake_group_this = {
            query: "test",
        };

        (function test_highlighter() {
            if (opts.stream) {
                // Test stream highlighter for widgets that allow stream pills.
                assert.equal(
                    config.highlighter.call(fake_stream_this, denmark),
                    fake_rendered_stream,
                );
            }
            if (opts.user_group && opts.user) {
                // If user is also allowed along with user_group
                // then we should check that each of them rendered correctly.
                assert.equal(
                    config.highlighter.call(fake_group_this, testers),
                    fake_rendered_group,
                );
                assert.equal(config.highlighter.call(fake_person_this, me), fake_rendered_person);
            }
            if (opts.user && !opts.user_group) {
                assert.equal(config.highlighter.call(fake_person_this, me), fake_rendered_person);
            }
            if (!opts.user && opts.user_group) {
                assert.equal(
                    config.highlighter.call(fake_group_this, testers),
                    fake_rendered_group,
                );
            }
        })();

        (function test_matcher() {
            let result;
            if (opts.stream) {
                result = config.matcher.call(fake_stream_this, denmark);
                assert.ok(result);
                result = config.matcher.call(fake_stream_this, sweden);
                assert.ok(!result);
            }
            if (opts.user_group && opts.user) {
                /* If user pills are also allowed along with user groups.
                We should check that queries matching either a person
                or group is returned. */

                // group query, with correct item.
                result = config.matcher.call(fake_group_this, testers);
                assert.ok(result);
                // group query, with wrong item.
                result = config.matcher.call(fake_group_this, admins);
                assert.ok(!result);
                // person query with correct item.
                result = config.matcher.call(fake_person_this, me);
                assert.ok(result);
                // person query with wrong item.
                result = config.matcher.call(fake_person_this, jill);
                assert.ok(!result);
            }
            if (opts.user_group && !opts.user) {
                result = config.matcher.call(fake_group_this, testers);
                assert.ok(result);
                result = config.matcher.call(fake_group_this, admins);
                assert.ok(!result);
            }
            if (opts.user && !opts.user_group) {
                result = config.matcher.call(fake_person_this, me);
                assert.ok(result);
                result = config.matcher.call(fake_person_this, jill);
                assert.ok(!result);
            }
        })();

        (function test_sorter() {
            if (opts.stream) {
                sort_streams_called = false;
                config.sorter.call(fake_stream_this);
                assert.ok(sort_streams_called);
            }
            if (opts.user_group) {
                sort_recipients_called = false;
                config.sorter.call(fake_group_this, [testers]);
                assert.ok(sort_recipients_called);
            }
            if (opts.user) {
                sort_recipients_called = false;
                config.sorter.call(fake_person_this, [me]);
                assert.ok(sort_recipients_called);
            }
        })();

        (function test_source() {
            let result;
            if (opts.stream) {
                result = config.source.call(fake_stream_this);
                const stream_ids = result.map((stream) => stream.stream_id);
                const expected_stream_ids = [denmark.stream_id, sweden.stream_id];
                assert.deepEqual(stream_ids, expected_stream_ids);
            }

            let expected_result = [];
            let actual_result = [];
            function is_group(item) {
                return item.members;
            }
            result = config.source.call(fake_person_this);
            actual_result = result
                .map((item) => {
                    if (is_group(item)) {
                        return item.id;
                    }
                    return item.user_id;
                })
                .filter(Boolean);
            if (opts.user_group) {
                expected_result = expected_result.concat(groups);
            }
            if (opts.user) {
                if (opts.user_source) {
                    expected_result = expected_result.concat(opts.user_source());
                } else {
                    expected_result = expected_result.concat(persons);
                }
            }
            expected_result = expected_result
                .map((item) => {
                    if (is_group(item)) {
                        return item.id;
                    }
                    return item.user_id;
                })
                .filter(Boolean);
            assert.deepEqual(actual_result, expected_result);
        })();

        (function test_updater() {
            if (opts.user && opts.user_group && opts.stream) {
                // Test it only for the case when all types of pills
                // are allowed, as it would be difficult to keep track
                // of number of items as we call with different types multiple
                // times in this test. So this case checks all possible cases handled by
                // updater in pill_typeahead.

                function number_of_pills() {
                    const pills = pill_widget.items();
                    return pills.length;
                }
                assert.equal(number_of_pills(), 0);
                config.updater.call(fake_stream_this, denmark);
                assert.equal(number_of_pills(), 1);
                config.updater.call(fake_person_this, me);
                assert.equal(number_of_pills(), 2);
                config.updater.call(fake_group_this, testers);
                assert.equal(number_of_pills(), 3);
            }
        })();

        // input_pill_typeahead_called is set true if
        // no exception occurs in pill_typeahead.set_up.
        input_pill_typeahead_called = true;
    };

    function test_pill_typeahead(opts) {
        pill_typeahead.set_up(fake_input, pill_widget, opts);
        assert.ok(input_pill_typeahead_called);
    }

    const all_possible_opts = [
        // These are various possible cases of opts that
        // currently occur in web-app codebase. This list
        // can be extended if some other configuration
        // is added later and its logic is to be tested.

        {user: true},
        // user and custom user source.
        {user: true, user_source: () => [fred, mark]},
        {stream: true},
        {user_group: true},
        {user_group: true, stream: true},
        {user_group: true, user: true},
        {user: true, stream: true},
        {user_group: true, stream: true, user: true},
    ];

    for (const config of all_possible_opts) {
        opts = config;
        test_pill_typeahead(config);
    }

    // Special case to test coverage and to test
    // that we enforce type is always specified
    // by caller.
    opts = {};
    input_pill_typeahead_called = false;
    blueslip.expect("error", "Unspecified possible item types");
    pill_typeahead.set_up(fake_input, pill_widget, {});
    assert.ok(!input_pill_typeahead_called);
});
<|endoftext|>