<commit_before>import cockpit from "cockpit";

/* SERVICE MANAGEMENT API
 *
 * The "service" module lets you monitor and manage a
 * system service on localhost in a simple way.
 *
 * It mainly exists because talking to the systemd D-Bus API is
 * not trivial enough to do it directly.
 *
 * - proxy = service.proxy(name)
 *
 * Create a proxy that represents the service named NAME.
 *
 * The proxy has properties and methods (described below) that
 * allow you to monitor the state of the service, and perform
 * simple actions on it.
 *
 * Initially, any of the properties can be "null" until their
 * actual values have been retrieved in the background.
 *
 * - proxy.addEventListener('changed', event => { ... })
 *
 * The 'changed' event is emitted whenever one of the properties
 * of the proxy changes.
 *
 * - proxy.exists
 *
 * A boolean that tells whether the service is known or not.  A
 * proxy with 'exists == false' will have 'state == undefined' and
 * 'enabled == undefined'.
 *
 * - proxy.state
 *
 * Either 'undefined' when the state can't be retrieved, or a
 * string that has one of the values "starting", "running",
 * "stopping", "stopped", or "failed".
 *
 * - proxy.enabled
 *
 * Either 'undefined' when the value can't be retrieved, or a
 * boolean that tells whether the service is started 'enabled'.
 * What it means exactly for a service to be enabled depends on
 * the service, but an enabled service is usually started on boot,
 * no matter whether other services need it or not.  A disabled
 * service is usually only started when it is needed by some other
 * service.
 *
 * - proxy.unit
 * - proxy.details
 *
 * The raw org.freedesktop.systemd1.Unit and type-specific D-Bus
 * interface proxies for the service.
 *
 * - proxy.service
 *
 * The deprecated name for proxy.details
 *
 * - promise = proxy.start()
 *
 * Start the service.  The return value is a standard jQuery
 * promise as returned from DBusClient.call.
 *
 * - promise =  proxy.restart()
 *
 * Restart the service.
 *
 * - promise = proxy.tryRestart()
 *
 * Try to restart the service if it's running or starting
 *
 * - promise = proxy.stop()
 *
 * Stop the service.
 *
 * - promise = proxy.enable()
 *
 * Enable the service.
 *
 * - promise = proxy.disable()
 *
 * Disable the service.
 */

let systemd_client;
let systemd_manager;

function wait_valid(proxy, callback) {
    proxy.wait(() => {
        if (proxy.valid)
            callback();
    });
}

function with_systemd_manager(done) {
    if (!systemd_manager) {
        // cached forever, only used for reading/watching; no superuser
        systemd_client = cockpit.dbus("org.freedesktop.systemd1");
        systemd_manager = systemd_client.proxy("org.freedesktop.systemd1.Manager",
                                               "/org/freedesktop/systemd1");
        wait_valid(systemd_manager, () => {
            systemd_manager.Subscribe()
                    .catch(error => {
                        if (error.name != "org.freedesktop.systemd1.AlreadySubscribed" &&
                        error.name != "org.freedesktop.DBus.Error.FileExists")
                            console.warn("Subscribing to systemd signals failed", error);
                    });
        });
    }
    wait_valid(systemd_manager, done);
}

export function proxy(name, kind) {
    const self = {
        exists: null,
        state: null,
        enabled: null,

        wait: wait,

        start: start,
        stop: stop,
        restart: restart,
        tryRestart: tryRestart,

        enable: enable,
        disable: disable
    };

    cockpit.event_target(self);

    let unit, details;
    const wait_callbacks = cockpit.defer();

    if (name.indexOf(".") == -1)
        name = name + ".service";
    if (kind === undefined)
        kind = "Service";

    function update_from_unit() {
        self.exists = (unit.LoadState != "not-found" || unit.ActiveState != "inactive");

        if (unit.ActiveState == "activating")
            self.state = "starting";
        else if (unit.ActiveState == "deactivating")
            self.state = "stopping";
        else if (unit.ActiveState == "active" || unit.ActiveState == "reloading")
            self.state = "running";
        else if (unit.ActiveState == "failed")
            self.state = "failed";
        else if (unit.ActiveState == "inactive" && self.exists)
            self.state = "stopped";
        else
            self.state = undefined;

        if (unit.UnitFileState == "enabled" || unit.UnitFileState == "linked")
            self.enabled = true;
        else if (unit.UnitFileState == "disabled" || unit.UnitFileState == "masked")
            self.enabled = false;
        else
            self.enabled = undefined;

        self.unit = unit;

        self.dispatchEvent("changed");
        wait_callbacks.resolve();
    }

    function update_from_details() {
        self.details = details;
        self.service = details;
        self.dispatchEvent("changed");
    }

    with_systemd_manager(function () {
        systemd_manager.LoadUnit(name)
                .then(path => {
                    unit = systemd_client.proxy('org.freedesktop.systemd1.Unit', path);
                    unit.addEventListener('changed', update_from_unit);
                    wait_valid(unit, update_from_unit);

                    details = systemd_client.proxy('org.freedesktop.systemd1.' + kind, path);
                    details.addEventListener('changed', update_from_details);
                    wait_valid(details, update_from_details);
                })
                .catch(() => {
                    self.exists = false;
                    self.dispatchEvent('changed');
                });
    });

    function refresh() {
        if (!unit || !details)
            return Promise.resolve();

        function refresh_interface(path, iface) {
            return systemd_client.call(path, "org.freedesktop.DBus.Properties", "GetAll", [iface])
                    .then(([result]) => {
                        const props = { };
                        for (const p in result)
                            props[p] = result[p].v;
                        systemd_client.notify({ [unit.path]: { [iface]: props } });
                    })
                    .catch(error => console.log(error));
        }

        return Promise.allSettled([
            refresh_interface(unit.path, "org.freedesktop.systemd1.Unit"),
            refresh_interface(details.path, "org.freedesktop.systemd1." + kind),
        ]);
    }

    function on_job_new_removed_refresh(event, number, path, unit_id, result) {
        if (unit_id == name)
            refresh();
    }

    /* HACK - https://bugs.freedesktop.org/show_bug.cgi?id=69575
     *
     * We need to explicitly get new property values when getting
     * a UnitNew signal since UnitNew doesn't carry them.
     * However, reacting to UnitNew with GetAll could lead to an
     * infinite loop since systemd emits a UnitNew in reaction to
     * GetAll for units that it doesn't want to keep loaded, such
     * as units without unit files.
     *
     * So we ignore UnitNew and instead assume that the unit state
     * only changes in interesting ways when there is a job for it
     * or when the daemon is reloaded (or when we get a property
     * change notification, of course).
     */

    // This is what we want to do:
    // systemd_manager.addEventListener("UnitNew", function (event, unit_id, path) {
    //     if (unit_id == name)
    //         refresh();
    // });

    // This is what we have to do:
    systemd_manager.addEventListener("Reloading", (event, reloading) => {
        if (!reloading)
            refresh();
    });

    systemd_manager.addEventListener("JobNew", on_job_new_removed_refresh);
    systemd_manager.addEventListener("JobRemoved", on_job_new_removed_refresh);

    function wait(callback) {
        wait_callbacks.promise.then(callback);
    }

    /* Actions
     *
     * We don't call methods on the persistent systemd_client, as that does not have superuser
     */

    function call_manager(dbus, method, args) {
        return dbus.call("/org/freedesktop/systemd1",
                         "org.freedesktop.systemd1.Manager",
                         method, args);
    }

    function call_manager_with_job(method, args) {
        return new Promise((resolve, reject) => {
            const dbus = cockpit.dbus("org.freedesktop.systemd1", { superuser: "try" });
            let pending_job_path;

            const subscription = dbus.subscribe(
                { interface: "org.freedesktop.systemd1.Manager", member: "JobRemoved" },
                (_path, _iface, _signal, [_number, path, _unit_id, result]) => {
                    if (path == pending_job_path) {
                        subscription.remove();
                        dbus.close();
                        refresh().then(() => {
                            if (result === "done")
                                resolve();
                            else
                                reject(new Error(`systemd job ${method} ${JSON.stringify(args)} failed with result ${result}`));
                        });
                    }
                });

            call_manager(dbus, method, args)
                    .then(([path]) => { pending_job_path = path })
                    .catch(() => {
                        dbus.close();
                        reject();
                    });
        });
    }

    function call_manager_with_reload(method, args) {
        const dbus = cockpit.dbus("org.freedesktop.systemd1", { superuser: "try" });
        return call_manager(dbus, method, args)
                .then(() => call_manager(dbus, "Reload", []))
                .then(refresh)
                .finally(dbus.close);
    }

    function start() {
        return call_manager_with_job("StartUnit", [name, "replace"]);
    }

    function stop() {
        return call_manager_with_job("StopUnit", [name, "replace"]);
    }

    function restart() {
        return call_manager_with_job("RestartUnit", [name, "replace"]);
    }

    function tryRestart() {
        return call_manager_with_job("TryRestartUnit", [name, "replace"]);
    }

    function enable() {
        return call_manager_with_reload("EnableUnitFiles", [[name], false, false]);
    }

    function disable() {
        return call_manager_with_reload("DisableUnitFiles", [[name], false]);
    }

    return self;
}
<commit_msg>lib: Add service.proxy.getRunJournal()<commit_after>import cockpit from "cockpit";

/* SERVICE MANAGEMENT API
 *
 * The "service" module lets you monitor and manage a
 * system service on localhost in a simple way.
 *
 * It mainly exists because talking to the systemd D-Bus API is
 * not trivial enough to do it directly.
 *
 * - proxy = service.proxy(name)
 *
 * Create a proxy that represents the service named NAME.
 *
 * The proxy has properties and methods (described below) that
 * allow you to monitor the state of the service, and perform
 * simple actions on it.
 *
 * Initially, any of the properties can be "null" until their
 * actual values have been retrieved in the background.
 *
 * - proxy.addEventListener('changed', event => { ... })
 *
 * The 'changed' event is emitted whenever one of the properties
 * of the proxy changes.
 *
 * - proxy.exists
 *
 * A boolean that tells whether the service is known or not.  A
 * proxy with 'exists == false' will have 'state == undefined' and
 * 'enabled == undefined'.
 *
 * - proxy.state
 *
 * Either 'undefined' when the state can't be retrieved, or a
 * string that has one of the values "starting", "running",
 * "stopping", "stopped", or "failed".
 *
 * - proxy.enabled
 *
 * Either 'undefined' when the value can't be retrieved, or a
 * boolean that tells whether the service is started 'enabled'.
 * What it means exactly for a service to be enabled depends on
 * the service, but an enabled service is usually started on boot,
 * no matter whether other services need it or not.  A disabled
 * service is usually only started when it is needed by some other
 * service.
 *
 * - proxy.unit
 * - proxy.details
 *
 * The raw org.freedesktop.systemd1.Unit and type-specific D-Bus
 * interface proxies for the service.
 *
 * - proxy.service
 *
 * The deprecated name for proxy.details
 *
 * - promise = proxy.start()
 *
 * Start the service.  The return value is a standard jQuery
 * promise as returned from DBusClient.call.
 *
 * - promise =  proxy.restart()
 *
 * Restart the service.
 *
 * - promise = proxy.tryRestart()
 *
 * Try to restart the service if it's running or starting
 *
 * - promise = proxy.stop()
 *
 * Stop the service.
 *
 * - promise = proxy.enable()
 *
 * Enable the service.
 *
 * - promise = proxy.disable()
 *
 * Disable the service.
 *
 * - journal = proxy.getRunJournal(options)
 *
 * Return the journal of the current (if running) or recent (if failed/stopped) service run,
 * similar to `systemctl status`. `options` is an optional array that gets appended to the `journalctl` call.
 */

let systemd_client;
let systemd_manager;

function wait_valid(proxy, callback) {
    proxy.wait(() => {
        if (proxy.valid)
            callback();
    });
}

function with_systemd_manager(done) {
    if (!systemd_manager) {
        // cached forever, only used for reading/watching; no superuser
        systemd_client = cockpit.dbus("org.freedesktop.systemd1");
        systemd_manager = systemd_client.proxy("org.freedesktop.systemd1.Manager",
                                               "/org/freedesktop/systemd1");
        wait_valid(systemd_manager, () => {
            systemd_manager.Subscribe()
                    .catch(error => {
                        if (error.name != "org.freedesktop.systemd1.AlreadySubscribed" &&
                        error.name != "org.freedesktop.DBus.Error.FileExists")
                            console.warn("Subscribing to systemd signals failed", error);
                    });
        });
    }
    wait_valid(systemd_manager, done);
}

export function proxy(name, kind) {
    const self = {
        exists: null,
        state: null,
        enabled: null,

        wait: wait,

        start: start,
        stop: stop,
        restart: restart,
        tryRestart: tryRestart,

        enable: enable,
        disable: disable,

        getRunJournal: getRunJournal,
    };

    cockpit.event_target(self);

    let unit, details;
    const wait_callbacks = cockpit.defer();

    if (name.indexOf(".") == -1)
        name = name + ".service";
    if (kind === undefined)
        kind = "Service";

    function update_from_unit() {
        self.exists = (unit.LoadState != "not-found" || unit.ActiveState != "inactive");

        if (unit.ActiveState == "activating")
            self.state = "starting";
        else if (unit.ActiveState == "deactivating")
            self.state = "stopping";
        else if (unit.ActiveState == "active" || unit.ActiveState == "reloading")
            self.state = "running";
        else if (unit.ActiveState == "failed")
            self.state = "failed";
        else if (unit.ActiveState == "inactive" && self.exists)
            self.state = "stopped";
        else
            self.state = undefined;

        if (unit.UnitFileState == "enabled" || unit.UnitFileState == "linked")
            self.enabled = true;
        else if (unit.UnitFileState == "disabled" || unit.UnitFileState == "masked")
            self.enabled = false;
        else
            self.enabled = undefined;

        self.unit = unit;

        self.dispatchEvent("changed");
        wait_callbacks.resolve();
    }

    function update_from_details() {
        self.details = details;
        self.service = details;
        self.dispatchEvent("changed");
    }

    with_systemd_manager(function () {
        systemd_manager.LoadUnit(name)
                .then(path => {
                    unit = systemd_client.proxy('org.freedesktop.systemd1.Unit', path);
                    unit.addEventListener('changed', update_from_unit);
                    wait_valid(unit, update_from_unit);

                    details = systemd_client.proxy('org.freedesktop.systemd1.' + kind, path);
                    details.addEventListener('changed', update_from_details);
                    wait_valid(details, update_from_details);
                })
                .catch(() => {
                    self.exists = false;
                    self.dispatchEvent('changed');
                });
    });

    function refresh() {
        if (!unit || !details)
            return Promise.resolve();

        function refresh_interface(path, iface) {
            return systemd_client.call(path, "org.freedesktop.DBus.Properties", "GetAll", [iface])
                    .then(([result]) => {
                        const props = { };
                        for (const p in result)
                            props[p] = result[p].v;
                        systemd_client.notify({ [unit.path]: { [iface]: props } });
                    })
                    .catch(error => console.log(error));
        }

        return Promise.allSettled([
            refresh_interface(unit.path, "org.freedesktop.systemd1.Unit"),
            refresh_interface(details.path, "org.freedesktop.systemd1." + kind),
        ]);
    }

    function on_job_new_removed_refresh(event, number, path, unit_id, result) {
        if (unit_id == name)
            refresh();
    }

    /* HACK - https://bugs.freedesktop.org/show_bug.cgi?id=69575
     *
     * We need to explicitly get new property values when getting
     * a UnitNew signal since UnitNew doesn't carry them.
     * However, reacting to UnitNew with GetAll could lead to an
     * infinite loop since systemd emits a UnitNew in reaction to
     * GetAll for units that it doesn't want to keep loaded, such
     * as units without unit files.
     *
     * So we ignore UnitNew and instead assume that the unit state
     * only changes in interesting ways when there is a job for it
     * or when the daemon is reloaded (or when we get a property
     * change notification, of course).
     */

    // This is what we want to do:
    // systemd_manager.addEventListener("UnitNew", function (event, unit_id, path) {
    //     if (unit_id == name)
    //         refresh();
    // });

    // This is what we have to do:
    systemd_manager.addEventListener("Reloading", (event, reloading) => {
        if (!reloading)
            refresh();
    });

    systemd_manager.addEventListener("JobNew", on_job_new_removed_refresh);
    systemd_manager.addEventListener("JobRemoved", on_job_new_removed_refresh);

    function wait(callback) {
        wait_callbacks.promise.then(callback);
    }

    /* Actions
     *
     * We don't call methods on the persistent systemd_client, as that does not have superuser
     */

    function call_manager(dbus, method, args) {
        return dbus.call("/org/freedesktop/systemd1",
                         "org.freedesktop.systemd1.Manager",
                         method, args);
    }

    function call_manager_with_job(method, args) {
        return new Promise((resolve, reject) => {
            const dbus = cockpit.dbus("org.freedesktop.systemd1", { superuser: "try" });
            let pending_job_path;

            const subscription = dbus.subscribe(
                { interface: "org.freedesktop.systemd1.Manager", member: "JobRemoved" },
                (_path, _iface, _signal, [_number, path, _unit_id, result]) => {
                    if (path == pending_job_path) {
                        subscription.remove();
                        dbus.close();
                        refresh().then(() => {
                            if (result === "done")
                                resolve();
                            else
                                reject(new Error(`systemd job ${method} ${JSON.stringify(args)} failed with result ${result}`));
                        });
                    }
                });

            call_manager(dbus, method, args)
                    .then(([path]) => { pending_job_path = path })
                    .catch(() => {
                        dbus.close();
                        reject();
                    });
        });
    }

    function call_manager_with_reload(method, args) {
        const dbus = cockpit.dbus("org.freedesktop.systemd1", { superuser: "try" });
        return call_manager(dbus, method, args)
                .then(() => call_manager(dbus, "Reload", []))
                .then(refresh)
                .finally(dbus.close);
    }

    function start() {
        return call_manager_with_job("StartUnit", [name, "replace"]);
    }

    function stop() {
        return call_manager_with_job("StopUnit", [name, "replace"]);
    }

    function restart() {
        return call_manager_with_job("RestartUnit", [name, "replace"]);
    }

    function tryRestart() {
        return call_manager_with_job("TryRestartUnit", [name, "replace"]);
    }

    function enable() {
        return call_manager_with_reload("EnableUnitFiles", [[name], false, false]);
    }

    function disable() {
        return call_manager_with_reload("DisableUnitFiles", [[name], false]);
    }

    function getRunJournal(options) {
        if (!details || !details.ExecMainStartTimestamp)
            return Promise.reject(new Error("getRunJournal(): unit is not known"));

        // collect the service journal since start time; property is μs, journal wants s
        const startTime = Math.floor(details.ExecMainStartTimestamp / 1000000);
        return cockpit.spawn(
            ["journalctl", "--unit", name, "--since=@" + startTime.toString()].concat(options || []),
            { superuser: "try", error: "message" });
    }

    return self;
}
<|endoftext|>


<commit_before>var express = require('express');
var cors = require('cors');
var bodyParser = require('body-parser');
var nano = require('nano')('http://localhost:5984');
var todo = nano.db.use('todo');

var app = express();

app.use(cors());
app.use(bodyParser.json());

app.get('/', function(req, res, next) {
  console.log('get triggered');
  var results = [];
  todo.view('todos', 'all_todos', function(err, body) {
    for (var row of body.rows) {
      results.push(row.value);
    }
    res.json(results);
  });
});

app.post('/', function(req, res, next) {
  console.log('post triggered');
  var task = {title: req.body.title, completed: false};
  todo.insert(task, function(err, body) {
    if (err) {
      console.log(err);
    }
    task._id = body.id;
    task._rev = body.rev;
    // assign a url based on id and commit back to database
    task.url = 'http://localhost:3000/' + body.id;
    todo.insert(task, function(err, body) {
      res.json(task);
    });
  });
});

app.delete('/', function(req, res, next) {
  console.log('delete triggered');
  todo.view('todos', 'all_todos', function(err, body) {
    if (body.rows.length !== 0) {
      body.rows.forEach(function(curr, index, array) {
        todo.destroy(curr.value._id, curr.value._rev, function(err, body) {
          if (index === array.length - 1) {
            res.json([]);
          }
        });
      });
    } else {
      res.json([]);
    }
  });
});

app.get('/:url', function(req, res, next) {
  todo.get(req.params.url, function(err, body) {
    console.log(body);
    res.json(body);
  });
});

app.listen(3000, function() {
  console.log('Cors enabled server listening on port 3000');
});
<commit_msg>a patch request will update the title (10/16).<commit_after>var express = require('express');
var cors = require('cors');
var bodyParser = require('body-parser');
var nano = require('nano')('http://localhost:5984');
var todo = nano.db.use('todo');

var app = express();

app.use(cors());
app.use(bodyParser.json());

app.get('/', function(req, res, next) {
  console.log('get triggered');
  var results = [];
  todo.view('todos', 'all_todos', function(err, body) {
    for (var row of body.rows) {
      results.push(row.value);
    }
    res.json(results);
  });
});

app.post('/', function(req, res, next) {
  console.log('post triggered');
  var task = {title: req.body.title, completed: false};
  todo.insert(task, function(err, body) {
    if (err) {
      console.log(err);
    }
    task._id = body.id;
    task._rev = body.rev;
    // assign a url based on id and commit back to database
    task.url = 'http://localhost:3000/' + body.id;
    todo.insert(task, function(err, body) {
      res.json(task);
    });
  });
});

app.delete('/', function(req, res, next) {
  console.log('delete triggered');
  todo.view('todos', 'all_todos', function(err, body) {
    if (body.rows.length !== 0) {
      body.rows.forEach(function(curr, index, array) {
        todo.destroy(curr.value._id, curr.value._rev, function(err, body) {
          if (index === array.length - 1) {
            res.json([]);
          }
        });
      });
    } else {
      res.json([]);
    }
  });
});

app.get('/:url', function(req, res, next) {
  todo.get(req.params.url, function(err, body) {
    console.log(body);
    res.json(body);
  });
});

app.patch('/:url', function(req, res, next) {
  todo.get(req.params.url, function(err, body) {
    if (req.body.title) {
      body.title  = req.body.title;
    }
    todo.insert(body, function(err, update) {
      res.json(body);
    });
  });
});

app.listen(3000, function() {
  console.log('Cors enabled server listening on port 3000');
});
<|endoftext|>


<commit_before>import forUsers from './Search/forUsers'
import forPosts from './Search/forPosts'
import { countTotal } from '../../lib/util/knex'
import addTermToQueryBuilder from './Search/addTermToQueryBuilder'
import { filterAndSortGroups } from './Search/util'
import { transform } from 'lodash'
import { flatten, flow, uniq, get } from 'lodash/fp'

module.exports = {
  forPosts,

  forUsers,

  forSkills: opts => Skill.search(opts),

  forGroups: function (opts) {
    return Group.query(qb => {
      if (opts.nearCoord) {
        qb.with('nearest_groups', bookshelf.knex.raw(`
        SELECT groups.id, ST_Distance(t.x, locations.center) AS nearest
         FROM (SELECT ST_GeographyFromText('SRID=4326;POINT(${opts.nearCoord.lng} ${opts.nearCoord.lat})')) AS t(x), groups
         INNER JOIN locations
         ON groups.location_id = locations.id
         WHERE ST_DWithin(t.x, locations.center, 10000000)`))
        qb.join('nearest_groups', 'groups.id', '=', 'nearest_groups.id')
      }

      if (opts.groupIds) {
        qb.whereIn('groups.id', opts.groupIds)
      }

      if (opts.autocomplete) {
        qb.whereRaw('groups.name ilike ?', opts.autocomplete + '%')
      }

      if (opts.slug) {
        qb.whereIn('groups.slug', opts.slug)
      }

      if (opts.visibility) {
        qb.whereIn('groups.visibility', opts.visibility)
      }

      if (opts.onlyMine) {
        const selectIdsForMember = Group.selectIdsForMember(opts.currentUserId)
        qb.whereIn('groups.id', selectIdsForMember)
      }

      if (opts.parentSlugs) {
        qb.join('group_relationships', 'groups.id', '=', 'group_relationships.child_group_id')
        qb.join('groups as parent_groups', 'parent_groups.id', '=', 'group_relationships.parent_group_id')
        qb.whereIn('parent_groups.slug', opts.parentSlugs)
      }

      filterAndSortGroups({
        search: opts.term,
        sortBy: opts.sort,
        boundingBox: opts.boundingBox
      }, qb)
      // this counts total rows matching the criteria, disregarding limit,
      // which is useful for pagination
      countTotal(qb, 'groups', opts.totalColumnName)
      qb.limit(opts.limit)
      qb.offset(opts.offset)
      if (!opts.nearCoord && !opts.sort === 'size') { // Because they are using CTEs and WITH statements, queries ordered by size or nearness don't like this group-by statement
        qb.groupBy('groups.id')
      }
    })
  },

  forTags: function (opts) {
    return Tag.query(q => {
      q.join('groups_tags', 'groups_tags.tag_id', '=', 'tags.id')
      q.join('groups', 'groups.id', '=', 'groups_tags.group_id')
      q.whereIn('groups.id', Group.selectIdsForMember(opts.userId))
      q.where('groups.active', true)

      if (opts.groupSlug) {
        q.where('groups.slug', '=', opts.groupSlug)
      }

      if (opts.name) {
        q.where('tags.name', opts.name)
      }

      if (opts.autocomplete) {
        q.whereRaw('tags.name ilike ?', opts.autocomplete + '%')
      }

      if (opts.isDefault) {
        q.where('groups_tags.is_default', true)
      }

      if (opts.visibility) {
        q.whereIn('groups_tags.visibility', opts.visibility)
      }

      if (opts.sort) {
        if (opts.sort === 'name') {
          q.orderByRaw('lower(tags.name) ASC')
        } else if (opts.sort === 'num_followers') {
          q.select(bookshelf.knex.raw('sum(groups_tags.num_followers) as num_followers'))
          q.orderBy('num_followers', 'desc')
        } else {
          q.orderBy(opts.sort, 'asc')
        }
      }

      countTotal(q, 'tags', opts.totalColumnName)

      q.groupBy('tags.id')
      q.limit(opts.limit)
    })
  },

  fullTextSearch: function (userId, args) {
    let items, total
    args.limit = args.first
    return fetchAllGroupIds(userId, args)
    .then(groupIds =>
      FullTextSearch.searchInGroups(groupIds, args))
      .then(items_ => {
        items = items_
        total = get('0.total', items)

        const ids = transform(items, (ids, item) => {
          const type = item.post_id ? 'posts'
            : item.comment_id ? 'comments' : 'people'

          if (!ids[type]) ids[type] = []
          const id = item.post_id || item.comment_id || item.user_id
          ids[type].push(id)
        }, {})

        return Promise.join(
          ids.posts && Post.where('id', 'in', ids.posts).fetchAll(),
          ids.comments && Comment.where('id', 'in', ids.comments).fetchAll(),
          ids.people && User.where('id', 'in', ids.people).fetchAll(),
          (posts, comments, people) =>
            items.map(presentResult(posts, comments, people))
        )
      })
      .then(models => ({models, total}))
  }
}

const fetchAllGroupIds = (userId, { groupIds }) => {
  if (groupIds) return Promise.resolve(groupIds)
  return Group.pluckIdsForMember(userId)
}

const obfuscate = text => Buffer.from(text).toString('hex')

const presentResult = (posts, comments, people) => item => {
  if (item.user_id) {
    return {
      id: obfuscate(`user_id-${item.user_id}`),
      content: people.find(p => p.id === item.user_id)
    }
  } else if (item.post_id) {
    return {
      id: obfuscate(`post_id-${item.post_id}`),
      content: posts.find(p => p.id === item.post_id)
    }
  } else if (item.comment_id) {
    return {
      id: obfuscate(`comment_id-${item.comment_id}`),
      content: comments.find(c => c.id === item.comment_id)
    }
  }
  return null
}
<commit_msg>Revert use of whereIn<commit_after>import forUsers from './Search/forUsers'
import forPosts from './Search/forPosts'
import { countTotal } from '../../lib/util/knex'
import addTermToQueryBuilder from './Search/addTermToQueryBuilder'
import { filterAndSortGroups } from './Search/util'
import { transform } from 'lodash'
import { flatten, flow, uniq, get } from 'lodash/fp'

module.exports = {
  forPosts,

  forUsers,

  forSkills: opts => Skill.search(opts),

  forGroups: function (opts) {
    return Group.query(qb => {
      if (opts.nearCoord) {
        qb.with('nearest_groups', bookshelf.knex.raw(`
        SELECT groups.id, ST_Distance(t.x, locations.center) AS nearest
         FROM (SELECT ST_GeographyFromText('SRID=4326;POINT(${opts.nearCoord.lng} ${opts.nearCoord.lat})')) AS t(x), groups
         INNER JOIN locations
         ON groups.location_id = locations.id
         WHERE ST_DWithin(t.x, locations.center, 10000000)`))
        qb.join('nearest_groups', 'groups.id', '=', 'nearest_groups.id')
      }

      if (opts.groupIds) {
        qb.whereIn('groups.id', opts.groupIds)
      }

      if (opts.autocomplete) {
        qb.whereRaw('groups.name ilike ?', opts.autocomplete + '%')
      }

      if (opts.slug) {
        qb.whereIn('groups.slug', opts.slug)
      }

      if (opts.visibility) {
        qb.where('groups.visibility', opts.visibility)
      }

      if (opts.onlyMine) {
        const selectIdsForMember = Group.selectIdsForMember(opts.currentUserId)
        qb.whereIn('groups.id', selectIdsForMember)
      }

      if (opts.parentSlugs) {
        qb.join('group_relationships', 'groups.id', '=', 'group_relationships.child_group_id')
        qb.join('groups as parent_groups', 'parent_groups.id', '=', 'group_relationships.parent_group_id')
        qb.whereIn('parent_groups.slug', opts.parentSlugs)
      }

      filterAndSortGroups({
        search: opts.term,
        sortBy: opts.sort,
        boundingBox: opts.boundingBox
      }, qb)
      // this counts total rows matching the criteria, disregarding limit,
      // which is useful for pagination
      countTotal(qb, 'groups', opts.totalColumnName)
      qb.limit(opts.limit)
      qb.offset(opts.offset)
      if (!opts.nearCoord && !opts.sort === 'size') { // Because they are using CTEs and WITH statements, queries ordered by size or nearness don't like this group-by statement
        qb.groupBy('groups.id')
      }
    })
  },

  forTags: function (opts) {
    return Tag.query(q => {
      q.join('groups_tags', 'groups_tags.tag_id', '=', 'tags.id')
      q.join('groups', 'groups.id', '=', 'groups_tags.group_id')
      q.whereIn('groups.id', Group.selectIdsForMember(opts.userId))
      q.where('groups.active', true)

      if (opts.groupSlug) {
        q.where('groups.slug', '=', opts.groupSlug)
      }

      if (opts.name) {
        q.where('tags.name', opts.name)
      }

      if (opts.autocomplete) {
        q.whereRaw('tags.name ilike ?', opts.autocomplete + '%')
      }

      if (opts.isDefault) {
        q.where('groups_tags.is_default', true)
      }

      if (opts.visibility) {
        q.whereIn('groups_tags.visibility', opts.visibility)
      }

      if (opts.sort) {
        if (opts.sort === 'name') {
          q.orderByRaw('lower(tags.name) ASC')
        } else if (opts.sort === 'num_followers') {
          q.select(bookshelf.knex.raw('sum(groups_tags.num_followers) as num_followers'))
          q.orderBy('num_followers', 'desc')
        } else {
          q.orderBy(opts.sort, 'asc')
        }
      }

      countTotal(q, 'tags', opts.totalColumnName)

      q.groupBy('tags.id')
      q.limit(opts.limit)
    })
  },

  fullTextSearch: function (userId, args) {
    let items, total
    args.limit = args.first
    return fetchAllGroupIds(userId, args)
    .then(groupIds =>
      FullTextSearch.searchInGroups(groupIds, args))
      .then(items_ => {
        items = items_
        total = get('0.total', items)

        const ids = transform(items, (ids, item) => {
          const type = item.post_id ? 'posts'
            : item.comment_id ? 'comments' : 'people'

          if (!ids[type]) ids[type] = []
          const id = item.post_id || item.comment_id || item.user_id
          ids[type].push(id)
        }, {})

        return Promise.join(
          ids.posts && Post.where('id', 'in', ids.posts).fetchAll(),
          ids.comments && Comment.where('id', 'in', ids.comments).fetchAll(),
          ids.people && User.where('id', 'in', ids.people).fetchAll(),
          (posts, comments, people) =>
            items.map(presentResult(posts, comments, people))
        )
      })
      .then(models => ({models, total}))
  }
}

const fetchAllGroupIds = (userId, { groupIds }) => {
  if (groupIds) return Promise.resolve(groupIds)
  return Group.pluckIdsForMember(userId)
}

const obfuscate = text => Buffer.from(text).toString('hex')

const presentResult = (posts, comments, people) => item => {
  if (item.user_id) {
    return {
      id: obfuscate(`user_id-${item.user_id}`),
      content: people.find(p => p.id === item.user_id)
    }
  } else if (item.post_id) {
    return {
      id: obfuscate(`post_id-${item.post_id}`),
      content: posts.find(p => p.id === item.post_id)
    }
  } else if (item.comment_id) {
    return {
      id: obfuscate(`comment_id-${item.comment_id}`),
      content: comments.find(c => c.id === item.comment_id)
    }
  }
  return null
}
<|endoftext|>


<commit_before>// Dependencies:
//   miner/js/plot-util.js
//   d3.js

function mouseover(d) {
    var focus = d3.select("g.focus");
    // var text_anchor = mouseover.conf.xscale(d.x) > d3.mean(mouseover.conf.xscale.range()) ? "end" : "start";
    focus.attr("transform", "translate(" + mouseover.conf.xscale(d.x) + "," + mouseover.conf.yscale(d3.max([d.y0, d.y])) + ")");
    var tt = d.heading + ": " + (d.y).toFixed(1) + "%";
    var text = focus.select("text").text(tt).node();
    var SVGRect = text.getBBox();
    focus.select("rect").attr("x", SVGRect.x).attr("y", SVGRect.y).attr("width", SVGRect.width).attr("height", SVGRect.height);
    // console.log(d);
    // console.log(mouseover.conf.xscale(d.x));
    // console.log(d3.mean(mouseover.conf.xscale.range()));
    // console.log("translate(" + mouseover.conf.xscale(d.x) + "," + mouseover.conf.yscale(d.y0) + ")");
}
mouseover.conf = null;

function mouseout(d) {
  var focus = d3.select("g.focus");
  focus.select("text").text("");
  focus.select("rect").attr("width", 0).attr("height", 0);

  // FIXME: doesn't work
  // selector = ".row-"+d.row + ".col-"+d.col;
  // console.log('mouse out selector: ' + selector);
  // console.log(d);
  // d3.select().classed('hover', false);
}

function bar_plot(d3data, conf) {
    conf = normalize_conf(d3data, conf);
    // default_conf         = {"plot_container_id": "plot_container", "container_width": 960, "container_height": 500, "margin": {top: 30, right: 80, bottom: 30, left: 50}};

    // conf                   = typeof conf                   == "undefined" ? default_conf                                : conf;
    // conf.plot_container_id = typeof conf.plot_container_id == "undefined" ? default_conf.plot_container_id              : conf.plot_container_id;
    // conf.margin            = typeof conf.margin            == "undefined" ? default_conf.margin                         : conf.margin;
    // conf.container_width   = typeof conf.container_width   == "undefined" ? default_conf.container_width                : conf.container_width;
    // conf.container_height  = typeof conf.container_height  == "undefined" ? default_conf.container_height               : conf.container_height;
    // conf.width             = typeof conf.width             == "undefined" ? conf.container_width - conf.margin.left - conf.margin.right  : conf.width;
    // conf.height            = typeof conf.height            == "undefined" ? conf.container_height - conf.margin.top  - conf.margin.bottom : conf.height;

    // conf.xlabel  = typeof conf.xlabel == "undefined" ? d3data[0][0] : conf.xlabel;
    // xfield  = typeof d3data[0][0] == "string" ? d3data[0][0] : conf.xlabel;
    // conf.ylabel  = typeof conf.ylabel == "undefined" ? d3data[1][0] : conf.ylabel;
    // conf.ylabels = ((typeof conf.ylabel == "object") && (d3data.length == (1 + conf.ylabel.length))) ? conf.ylabel : d3data.slice(1).map(function(d) {return d[0];});
    // var conf.num_layers = conf.ylabels.length;

    console.log(conf.ylabels);
    console.log(conf.xfield);

    // TODO: standardize on the "series" data structure below which is also used in the line-plot
    var layers = split_d3_series(d3data);
    for (var i=0; i<layers.length; i++) {
        for (var j=0; j<layers[i].length; j++) {
            layers[i][j].series = layers[i];
            layers[i][j].layer  = layers[i];
            layers[i][j].col = i;
            layers[i][j].row = j;
            layers[i][j].heading = conf.ylabels[i];
        }
    }
    // console.log('layers (d3data as arrays of objects with x,y properties)');
    // console.log(layers);

    //d3data = arrays_as_objects(d3data);
    var num_stacks = d3data.length; // number of samples per layer

    conf.xscale = d3.scale.ordinal()
        .domain(d3data.map(function(d) { return d[conf.xfield]; }))
        .rangeRoundBands([0, conf.width], 0.1);
    conf.xlabel = "Horizontal Value (Time?)";
    conf.yscale = d3.scale.linear().range([conf.height, 0]);
    conf.ylabel = "Vertical Value";

    d3data.sort(function(a, b) { return a.x - b.x; });

    var color = d3.scale.linear()
        .domain([0, conf.num_layers - 1])
        .range(["#aad", "#556"]);

    // console.log('all_series');
    var all_series = ylabels.map(function(name) {
      var series = { name: name, values: null };
      series.values = d3data.map(function(d) {
            return { series: series, x: d[conf.xfield], y: +d[name] };
      }); // d3data.map(function(d) {
      return series;
    });
    // console.log(all_series);

    var ymin = d3.min(all_series, function(series) { return d3.min(series.values, function(d) { console.log(d); return d.y; }); });
    var ymax = d3.max(all_series, function(series) { return d3.max(series.values, function(d) { console.log(d); return d.y; }); });

    console.log([ymin, ymax]);

    conf.yGroupMax = ymax;
    conf.yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });

    // plot starts out stacked
    conf.yscale = d3.scale.linear()
        .domain([0, conf.yStackMax])
        .range([conf.height, 0]);

    // Dependencies:
    //   miner/js/plot-util.js
    //   d3.js

    function mouseover(d) {
        var focus = d3.select("g.focus");
        // var text_anchor = conf.xscale(d.x) > d3.mean(conf.xscale.range()) ? "end" : "start";
        focus.attr("transform", "translate(" + conf.xscale(d.x) + "," + conf.yscale(d3.max([d.y0, d.y])) + ")");
        var tt = d.heading + ": " + (d.y).toFixed(1) + "%";
        var text = focus.select("text").text(tt).node();
        var SVGRect = text.getBBox();
        focus.select("rect").attr("x", SVGRect.x).attr("y", SVGRect.y).attr("width", SVGRect.width).attr("height", SVGRect.height);
        console.log(d);
        console.log(conf.xscale(d.x));
        console.log(d3.mean(conf.xscale.range()));
        console.log("translate(" + conf.xscale(d.x) + "," + conf.yscale(d.y0) + ")");
    }

    function mouseout(d) {
      var focus = d3.select("g.focus");
      focus.select("text").text("");
      focus.select("rect").attr("width", 0).attr("height", 0);

      // FIXME: doesn't work
      // selector = ".row-"+d.row + ".col-"+d.col;
      // console.log('mouse out selector: ' + selector);
      // console.log(d);
      // d3.select().classed('hover', false);
    }

    var svg = create_svg_element(conf);

    // don't put a tick mark on bar-plots
    var xAxis = create_xaxis(conf).tickSize(0).tickPadding(6).orient("bottom");

    var yAxis = create_yaxis(conf).ticks(10, "%");

    // var yAxis = d3.svg.axis()
    //     .scale(conf.yscale)
    //     .orient("left");

    // // need something like this here to append the series data to each rect
    // var series = svg.selectAll(".series")
    //     .data(all_series)
    //   .enter().append("g")
    //     .attr("class", "series");

    var layer = svg.selectAll(".layer")
        .data(layers)
      .enter().append("g")
        .attr("class", "layer")
        .style("fill", function(d, i) { return color(i); });

      // chart.selectAll(".bar")
      //     .data(d3data)
      //   .enter().append("rect")
      //     .attr("class", "bar")
      //     .attr("x", function(d) { return x(d.name); })
      //     .attr("y", function(d) { return conf.yscale(d.value); })
      //     .attr("height", function(d) { return conf.height - y(d.value); })
      //     .attr("width", x.rangeBand());
    var rect_element;

    var rect = layer.selectAll("rect")
        .data(function(d) { console.log(d); return d; })
      .enter().append("rect")
        .attr("x", function(d) { return conf.xscale(d[conf.xfield]); })
        .attr("y", conf.height)
        .attr("width", conf.xscale.rangeBand())
        .attr("class", function(d) { return "bar row-" + d.row + " col-" + d.col; })
        .attr("height", 0)
        .on("mouseover", mouseover)
        .on("mouseout", mouseout);
    //    .on("click", mouseclick);

    rect.transition()
        .delay(function(d, i) { return i * 10; })
        .attr("y", function(d) { return conf.yscale(d.y0 + d.y); })
        .attr("height", function(d) { return conf.yscale(d.y0) - conf.yscale(d.y0 + d.y); });

    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + conf.height + ")")
        .call(xAxis);

    d3.selectAll("input").on("change", change);

    // focus must be the last SVG element so it will be on top
    var focus = svg.append("g").attr("class", "focus");
    var text = focus.append("text").attr("x", 0).attr("y", -12).attr("text-anchor", "start");
    focus = insert_text_background(focus);

    if (typeof conf.stacked == "undefined") {
        var timeout = setTimeout(function() {
          d3.select("input[value=\"grouped\"]").property("checked", true).each(change);
        }, 2000); }
    else {
        d3.select(conf.stacked == true ? "input[value=\"stacked\"]" : "input[value=\"grouped\"]").property("checked", true).each(change);
    }

    function change() {
      clearTimeout(timeout);
      if (this.value === "grouped") { transitionGrouped(); }
      else { transitionStacked(); }
    }

    function transitionGrouped() {
      conf.yscale.domain([0, conf.yGroupMax]);

      rect.transition()
          .duration(500)
          .delay(function(d, i) { return i * 10; })
          .attr("x", function(d, i, j) { return conf.xscale(d[conf.xfield]) + conf.xscale.rangeBand() / conf.num_layers * j; })
          .attr("width", conf.xscale.rangeBand() / conf.num_layers)
        .transition()
          .attr("y", function(d) { return conf.yscale(d.y); })
          .attr("height", function(d) { return conf.height - conf.yscale(d.y); });
      conf.stacked = false;
    }

    function transitionStacked() {
      conf.yscale.domain([0, conf.yStackMax]);

      rect.transition()
          .duration(500)
          .delay(function(d, i) { return i * 10; })
          .attr("y", function(d) { return conf.yscale(d.y0 + d.y); })
          .attr("height", function(d) { return conf.yscale(d.y0) - conf.yscale(d.y0 + d.y); })
        .transition()
          .attr("x", function(d) { return conf.xscale(d[xfield]); })
          .attr("width", conf.xscale.rangeBand());
      conf.stacked = true;
    }

} // function bar_plot(d3data)
<commit_msg>mouseover-bug merged<commit_after>// Dependencies:
//   miner/js/plot-util.js
//   d3.js

function mouseover(d) {
    var focus = d3.select("g.focus");
    translate_value = "translate(" + mouseover.conf.xscale(d.x);
    // var text_anchor = mouseover.conf.xscale(d.x) > d3.mean(mouseover.conf.xscale.range()) ? "end" : "start";
    if (mouseover.conf.stacked) {
      translate_value = translate_value + "," + mouseover.conf.yscale(d3.max([d.y0, d.y])) + ")";
      }
    else {
      translate_value = translate_value + "," + mouseover.conf.yscale(d.y) + ")";
      }
    focus.attr("transform", translate_value);
    console.log('translate = ' + translate_value + '   stacked = ' + mouseover.conf.stacked);
    console.log(mouseover.conf.yscale);
    var tt = d.heading + ": " + (d.y).toFixed(1) + "%";
    var text = focus.select("text").text(tt).node();
    var SVGRect = text.getBBox();
    focus.select("rect").attr("x", SVGRect.x).attr("y", SVGRect.y).attr("width", SVGRect.width).attr("height", SVGRect.height);
    console.log(d);
    console.log(mouseover.conf.yscale(d.y));
    console.log(d3.mean(mouseover.conf.yscale.range()));
    console.log(d3.mean(mouseover.conf.yscale.domain()));
}
mouseover.conf = null;

function mouseout(d) {
  var focus = d3.select("g.focus");
  focus.select("text").text("");
  focus.select("rect").attr("width", 0).attr("height", 0);

  // FIXME: doesn't work
  // selector = ".row-"+d.row + ".col-"+d.col;
  // console.log('mouse out selector: ' + selector);
  // console.log(d);
  // d3.select().classed('hover', false);
}

function bar_plot(d3data, conf) {
    conf = normalize_conf(d3data, conf);

    // TODO: standardize on the "series" data structure below which is also used in the line-plot
    var layers = split_d3_series(d3data);
    for (var i=0; i<layers.length; i++) {
        for (var j=0; j<layers[i].length; j++) {
            layers[i][j].series = layers[i];
            layers[i][j].layer  = layers[i];
            layers[i][j].col = i;
            layers[i][j].row = j;
            layers[i][j].heading = conf.ylabels[i];
        }
    }
    console.log('layers (d3data as arrays of objects with x,y properties)');
    console.log(layers);

    //d3data = arrays_as_objects(d3data);
    var num_stacks = d3data.length; // number of samples per layer

    conf.xscale = d3.scale.ordinal()
        .domain(d3data.map(function(d) { return d[conf.xfield]; }))
        .rangeRoundBands([0, conf.width], 0.1);
    conf.xlabel = "Horizontal Value (Time?)";
    conf.yscale = d3.scale.linear().range([conf.height, 0]);
    conf.ylabel = "Vertical Value";

    d3data.sort(function(a, b) { return a.x - b.x; });

    var color = d3.scale.linear()
        .domain([0, conf.num_layers - 1])
        .range(["#aad", "#556"]);

    console.log('all_series');
    var all_series = ylabels.map(function(name) {
      var series = { name: name, values: null };
      series.values = d3data.map(function(d) {
            return { series: series, x: d[conf.xfield], y: +d[name] };
      }); // d3data.map(function(d) {
      return series;
    });
    console.log(all_series);

    var ymin = d3.min(all_series, function(series) { return d3.min(series.values, function(d) { console.log(d); return d.y; }); });
    var ymax = d3.max(all_series, function(series) { return d3.max(series.values, function(d) { console.log(d); return d.y; }); });

    console.log([ymin, ymax]);

    conf.yGroupMax = ymax;
    conf.yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });

    // plot starts out stacked
    conf.yscale = d3.scale.linear()
        .domain([0, conf.yStackMax])
        .range([conf.height, 0]);

    // Dependencies:
    //   miner/js/plot-util.js
    //   d3.js

    function mouseover(d) {
        var focus = d3.select("g.focus");
        // var text_anchor = conf.xscale(d.x) > d3.mean(conf.xscale.range()) ? "end" : "start";
        focus.attr("transform", "translate(" + conf.xscale(d.x) + "," + conf.yscale(d3.max([d.y0, d.y])) + ")");
        var tt = d.heading + ": " + (d.y).toFixed(1) + "%";
        var text = focus.select("text").text(tt).node();
        var SVGRect = text.getBBox();
        focus.select("rect").attr("x", SVGRect.x).attr("y", SVGRect.y).attr("width", SVGRect.width).attr("height", SVGRect.height);
        console.log(d);
        console.log(conf.xscale(d.x));
        console.log(d3.mean(conf.xscale.range()));
        console.log("translate(" + conf.xscale(d.x) + "," + conf.yscale(d.y0) + ")");
    }

    function mouseout(d) {
      var focus = d3.select("g.focus");
      focus.select("text").text("");
      focus.select("rect").attr("width", 0).attr("height", 0);

      // FIXME: doesn't work
      // selector = ".row-"+d.row + ".col-"+d.col;
      // console.log('mouse out selector: ' + selector);
      // console.log(d);
      // d3.select().classed('hover', false);
    }

    var svg = create_svg_element(conf);

    // don't put a tick mark on bar-plots
    var xAxis = create_xaxis(conf).tickSize(0).tickPadding(6).orient("bottom");

    var yAxis = create_yaxis(conf).ticks(10, "%");

    // var yAxis = d3.svg.axis()
    //     .scale(conf.yscale)
    //     .orient("left");

    // // need something like this here to append the series data to each rect
    // var series = svg.selectAll(".series")
    //     .data(all_series)
    //   .enter().append("g")
    //     .attr("class", "series");

    var layer = svg.selectAll(".layer")
        .data(layers)
      .enter().append("g")
        .attr("class", "layer")
        .style("fill", function(d, i) { return color(i); });

      // chart.selectAll(".bar")
      //     .data(d3data)
      //   .enter().append("rect")
      //     .attr("class", "bar")
      //     .attr("x", function(d) { return x(d.name); })
      //     .attr("y", function(d) { return conf.yscale(d.value); })
      //     .attr("height", function(d) { return conf.height - y(d.value); })
      //     .attr("width", x.rangeBand());
    var rect_element;

    var rect = layer.selectAll("rect")
        .data(function(d) { console.log(d); return d; })
      .enter().append("rect")
        .attr("x", function(d) { return conf.xscale(d[conf.xfield]); })
        .attr("y", conf.height)
        .attr("width", conf.xscale.rangeBand())
        .attr("class", function(d) { return "bar row-" + d.row + " col-" + d.col; })
        .attr("height", 0)
        .on("mouseover", mouseover)
        .on("mouseout", mouseout);
    //    .on("click", mouseclick);

    rect.transition()
        .delay(function(d, i) { return i * 10; })
        .attr("y", function(d) { return conf.yscale(d.y0 + d.y); })
        .attr("height", function(d) { return conf.yscale(d.y0) - conf.yscale(d.y0 + d.y); });

    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + conf.height + ")")
        .call(xAxis);

    d3.selectAll("input").on("change", change);

    // focus must be the last SVG element so it will be on top
    var focus = svg.append("g").attr("class", "focus");
    var text = focus.append("text").attr("x", 0).attr("y", -12).attr("text-anchor", "start");
    focus = insert_text_background(focus);

    if (typeof conf.stacked == "undefined") {
        var timeout = setTimeout(function() {
          d3.select("input[value=\"grouped\"]").property("checked", true).each(change);
        }, 2000); }
    else {
        d3.select(conf.stacked == true ? "input[value=\"stacked\"]" : "input[value=\"grouped\"]").property("checked", true).each(change);
    }

    function change() {
      clearTimeout(timeout);
      if (this.value === "grouped") { transitionGrouped(); }
      else { transitionStacked(); }
    }

    function transitionGrouped() {
      conf.yscale.domain([0, conf.yGroupMax]);

      rect.transition()
          .duration(500)
          .delay(function(d, i) { return i * 10; })
          .attr("x", function(d, i, j) { return conf.xscale(d[conf.xfield]) + conf.xscale.rangeBand() / conf.num_layers * j; })
          .attr("width", conf.xscale.rangeBand() / conf.num_layers)
        .transition()
          .attr("y", function(d) { return conf.yscale(d.y); })
          .attr("height", function(d) { return conf.height - conf.yscale(d.y); });
      conf.stacked = false;
      mouseover.conf = conf;
      mouseout.conf = conf;
    }

    function transitionStacked() {
      conf.yscale.domain([0, conf.yStackMax]);

      rect.transition()
          .duration(500)
          .delay(function(d, i) { return i * 10; })
          .attr("y", function(d) { return conf.yscale(d.y0 + d.y); })
          .attr("height", function(d) { return conf.yscale(d.y0) - conf.yscale(d.y0 + d.y); })
        .transition()
          .attr("x", function(d) { return conf.xscale(d[xfield]); })
          .attr("width", conf.xscale.rangeBand());
      conf.stacked = true;
      mouseover.conf = conf;
      mouseout.conf = conf;
    }

} // function bar_plot(d3data)
<|endoftext|>


<commit_before>import _ from 'lodash'

export default {
  charts: [
    {name: 'LineChart', groupBy: true, chooseAxis: false, timeRadios: ['all', '1year', '3month']},
    {name: 'PieChart', groupBy: false, chooseAxis: false, timeRadios :['1month']},
    {name: 'ChoroplethMap' ,groupBy:false, chooseAxis:false, timeRadios: ['1month']},
    {name: 'ColumnChart', groupBy: true, chooseAxis: false, timeRadios: ['1year', '3month', '1month']},
    {name: 'ScatterChart', groupBy: false, chooseAxis: true, timeRadios: ['1month']},
    {name: 'BarChart', groupBy: true, chooseAxis: false, timeRadios: ['1month']}
  ],
  groups: [
    {value: 'indicator', title: 'Indicators'},
    {value: 'location', title: 'Locations'}
  ],
  times: [
    {
      value: 'all',
      title:'All Time',
      getLower: start => { return null },
      json: null
    },
    {
      value: '1year',
      title:'Past Year',
      getLower: start => { return start.clone().startOf('month').subtract(1,'year') },
      json: {years: 1}
    },
    {
      value: '3month',
      title: 'Past 3 Months',
      getLower: start => { return start.clone().startOf('month').subtract(3,'month') },
      json: {months: 2}
    },
    {
      value: '1month',
      title: 'Current Campaign',
      getLower: start => { return start.clone().startOf('month') },
      json: {months: 0}
    }
  ],
  formats: [
    {
      value: ',.0f',
      title: 'Integer'
    },
    {
      value: ',.4f',
      title: 'Real Number'
    },
    {
      value: '%',
      title: 'Percentage'
    }
  ],
  locationLevels: [
    {
      value: "selected",
      title: "Selected location only",
      getAggregated: (locationSelected, locationIndex) => { return [locationSelected] }
    },
    {
      value: "type",
      title: "Locations with the same level",
      getAggregated: (locationSelected, locationIndex) => { return [locationSelected] }
    },
    {
      value: "sublocations",
      title: "Sublocations 1 level below selected",
      getAggregated: (locationSelected, locationIndex) => {
        return _.filter(locationIndex, {parent_location_id: locationSelected.id})
      }
    }
  ]
}
<commit_msg>implement same-type location level<commit_after>import _ from 'lodash'

export default {
  charts: [
    {name: 'LineChart', groupBy: true, chooseAxis: false, timeRadios: ['all', '1year', '3month']},
    {name: 'PieChart', groupBy: false, chooseAxis: false, timeRadios :['1month']},
    {name: 'ChoroplethMap' ,groupBy:false, chooseAxis:false, timeRadios: ['1month']},
    {name: 'ColumnChart', groupBy: true, chooseAxis: false, timeRadios: ['1year', '3month', '1month']},
    {name: 'ScatterChart', groupBy: false, chooseAxis: true, timeRadios: ['1month']},
    {name: 'BarChart', groupBy: true, chooseAxis: false, timeRadios: ['1month']}
  ],
  groups: [
    {value: 'indicator', title: 'Indicators'},
    {value: 'location', title: 'Locations'}
  ],
  times: [
    {
      value: 'all',
      title:'All Time',
      getLower: start => { return null },
      json: null
    },
    {
      value: '1year',
      title:'Past Year',
      getLower: start => { return start.clone().startOf('month').subtract(1,'year') },
      json: {years: 1}
    },
    {
      value: '3month',
      title: 'Past 3 Months',
      getLower: start => { return start.clone().startOf('month').subtract(3,'month') },
      json: {months: 2}
    },
    {
      value: '1month',
      title: 'Current Campaign',
      getLower: start => { return start.clone().startOf('month') },
      json: {months: 0}
    }
  ],
  formats: [
    {
      value: ',.0f',
      title: 'Integer'
    },
    {
      value: ',.4f',
      title: 'Real Number'
    },
    {
      value: '%',
      title: 'Percentage'
    }
  ],
  locationLevels: [
    {
      value: "selected",
      title: "Selected location only",
      getAggregated: (locationSelected, locationIndex) => { return [locationSelected] }
    },
    {
      value: "type",
      title: "Locations with the same level",
      getAggregated: (locationSelected, locationIndex) => {
        return _.filter(locationIndex,
          (locationSelected.parent_location_id && locationSelected.parent_location_id != "None") ?
            {location_type_id: locationSelected.location_type_id, office_id: locationSelected.office_id} :
            {location_type_id: locationSelected.location_type_id}
        )
      }
    },
    {
      value: "sublocations",
      title: "Sublocations 1 level below selected",
      getAggregated: (locationSelected, locationIndex) => {
        return _.filter(locationIndex, {parent_location_id: locationSelected.id})
      }
    }
  ]
}
<|endoftext|>


<commit_before>define([
    "backbone",
    "audio_video/AudioVideo",
    "comment/Comments",
    "datum/DatumField",
    "datum/DatumFields",
    "datum/DatumState",
    "datum/DatumStates",
    "datum/DatumTag",
    "datum/DatumTags",
    "datum/Session",
    "libs/Utils"
], function(
    Backbone,
    AudioVideo,
    Comments,
    DatumField,
    DatumFields,
    DatumState,
    DatumStates,
    DatumTag,
    DatumTags,
    Session
) {
  var Datum = Backbone.Model.extend(
  /** @lends Datum.prototype */
  {
    /**
     * @class The Datum widget is the place where all linguistic data is
     *        entered; one at a time.
     *
     * @property {DatumField} utterance The utterance field generally
     *           corresponds to the first line in linguistic examples that can
     *           either be written in the language's orthography or a
     *           romanization of the language. An additional field can be added
     *           if the language has a non-roman script.
     * @property {DatumField} gloss The gloss field corresponds to the gloss
     *           line in linguistic examples where the morphological details of
     *           the words are displayed.
     * @property {DatumField} translation The translation field corresponds to
     *           the third line in linguistic examples where in general an
     *           English translation. An additional field can be added if
     *           translations into other languages is needed.
     * @property {DatumField} judgement The judgement is the grammaticality
     *           judgement associated with the datum, so grammatical,
     *           ungrammatical, felicitous, unfelicitous etc.
     * @property {AudioVisual} audioVisual Datums can be associated with an audio or video
     *           file.
     * @property {Session} session The session provides details about the set of
     *           data elicited. The session will contain details such as date,
     *           language, consultant etc.
     * @property {Comments} comments The comments is a collection of comments
     *           associated with the datum, this is meant for comments like on a
     *           blog, not necessarily notes, which can be encoded in a
     *           field.(Use Case: team discussing a particular datum)
     * @property {DatumTags} datumtags The datum tags are a collection of tags
     *           associated with the datum. These are made completely by the
     *           user.They are like blog tags, a way for the user to make
     *           categories without make a hierarchical structure, and make
     *           datum easier for search.
     * @property {Date} dateEntered The date the Datum was first saved.
     *
     * @description The initialize function brings up the datum widget in small
     *              view with one set of datum fields. However, the datum widget
     *              can contain more than datum field set and can also be viewed
     *              in full screen mode.
     *
     * @extends Backbone.Model
     * @constructs
     */
    initialize : function() {
      // Initially, the first datumState is selected
      if (this.get("datumStates") && (this.get("datumStates").models.length > 0)) {
        this.get("datumStates").models[0].set("selected", "selected");
      }

      // If there's no audioVideo, give it a new one.
      if (!this.get("audioVideo")) {
        this.set("audioVideo", new AudioVideo());
      }

      // If there are no comments, give it a new one
      if (!this.get("comments")) {
        this.set("comments", new Comments());
      }

      // If there are no datumTags, give it a new one
      if (!this.get("datumTags")) {
        this.set("datumTags", new DatumTags());
      }
    },

    // Internal models: used by the parse function
    model : {
      datumFields : DatumFields,
      audioVideo : AudioVideo,
      session : Session,
      comments : Comments,
      datumStates : DatumStates,
      datumTags : DatumTags
    },

    changeCorpus : function(corpusname, callback) {
      if(this.pouch == undefined){
        this.pouch = Backbone.sync.pouch(Utils.androidApp() ? Utils.touchUrl + corpusname : Utils.pouchUrl + corpusname);
      }
      if(typeof callback == "function"){
        callback();
      }
    },

    /**
     * Gets all the DatumIds in the current Corpus sorted by their date.
     *
     * @param {Function} callback A function that expects a single parameter. That
     * parameter is the result of calling "get_datum_ids/by_date". So it is an array
     * of objects. Each object has a 'key' and a 'value' attribute. The 'key'
     * attribute contains the Datum's dateEntered and the 'value' attribute contains
     * the Datum's ID.
     */
    getAllDatumIdsByDate : function(callback) {
      var self = this;
      this.changeCorpus(this.get("corpusname"),function(){
        self.pouch(function(err, db) {
          db.query("get_datum_ids/by_date", {reduce: false}, function(err, response) {
            if ((!err) && (typeof callback == "function"))  {
              console.log("Callback with: ", response.rows);
              callback(response.rows);
            }
          });
        });
      });
    },

    searchByQueryString : function(queryString, callback) {
      var self = this;
      this.changeCorpus(this.get("corpusname"), function() {
        self.pouch(function(err, db) {
          db.query("get_datum_field/get_datum_fields", {reduce: false}, function(err, response) {
            var matchIds = [];

            if (!err) {
              // Process the given query string into tokens
              var queryTokens = self.processQueryString(queryString);

              // Go through all the rows of results
              for (i in response.rows) {
                // Determine if this datum matches the first search criteria
                var thisDatumIsIn = self.matchesSingleCriteria(response.rows[i].key, queryTokens[0]);

                // Progressively determine whether the datum still matches based on
                // subsequent search criteria
                for (j = 1; j < queryTokens.length; j += 2) {
                  if (queryTokens[j] == "AND") {
                    // Short circuit: if it's already false then it continues to be false
                    if (!thisDatumIsIn) {
                      break;
                    }

                    // Do an intersection
                    thisDatumIsIn = thisDatumIsIn && self.matchesSingleCriteria(response.rows[i].key, queryTokens[j+1]);
                  } else {
                    // Do a union
                    thisDatumIsIn = thisDatumIsIn || self.matchesSingleCriteria(response.rows[i].key, queryTokens[j+1]);
                  }
                }

                // If the row's datum matches the given query string
                if (thisDatumIsIn) {
                  // Keep its datum's ID, which is the value
                  matchIds.push(response.rows[i].value);
                }
              }
            }

            callback(matchIds);
          });
        });
      });
    },

    /**
     * Determines whether the given object to search through matches the given
     * search criteria.
     *
     * @param {Object} objectToSearchThrough An object representing a datum that
     * contains (key, value) pairs where the key is the datum field label and the
     * value is the datum field value of that attribute.
     * @param {String} criteria The single search criteria in the form of a string
     * made up of a label followed by a colon followed by the value that we wish
     * to match.
     *
     * @return {Boolean} True if the given object matches the given criteria.
     * False otherwise.
     */
    matchesSingleCriteria : function(objectToSearchThrough, criteria) {
      var delimiterIndex = criteria.indexOf(":");
      var label = criteria.substring(0, delimiterIndex);
      var value = criteria.substring(delimiterIndex + 1);

      return objectToSearchThrough[label] && (objectToSearchThrough[label].toLowerCase().indexOf(value) >= 0);
    },

    /**
     * Process the given string into an array of tokens where each token is
     * either a search criteria or an operator (AND or OR). Also makes each
     * search criteria token lowercase, so that searches will be case-
     * insensitive.
     *
     * @param {String} queryString The string to tokenize.
     *
     * @return {String} The tokenized string
     */
    processQueryString : function(queryString) {
      // Split on spaces
      var queryArray = queryString.split(" ");

      // Create an array of tokens out of the query string where each token is
      // either a search criteria or an operator (AND or OR).
      var queryTokens = [];
      var currentString = "";
      for (i in queryArray) {
        var currentItem = queryArray[i].trim();
        if (currentItem.length <= 0) {
          break;
        } else if ((currentItem == "AND") || (currentItem == "OR")) {
          queryTokens.push(currentString);
          queryTokens.push(currentItem);
          currentString = "";
        } else if (currentString) {
          currentString = currentString + " " + currentItem.toLowerCase();
        } else {
          currentString = currentItem.toLowerCase();
        }
      }
      queryTokens.push(currentString);

      return queryTokens;
    },

    /**
     * Clone the current Datum and return the clone. The clone is put in the current
     * Session, regardless of the origin Datum's Session.
     *
     * @return The clone of the current Datum.
     */
    clone : function() {
      // Create a new Datum based on the current Datum
      var datum = new Datum({
        audioVideo : new AudioVideo(this.get("audioVideo").toJSON(), {parse: true}),
        comments : new Comments(this.get("comments").toJSON(), {parse: true}),
        dateEntered : this.get("dateEntered"),
        datumFields : new DatumFields(this.get("datumFields").toJSON(), {parse: true}),
        datumStates : new DatumStates(this.get("datumStates").toJSON(), {parse: true}),
        datumTags : new DatumTags(this.get("datumTags").toJSON(), {parse: true}),
        corpusname : this.get("corpusname")
        // Don't need to do Session here since it will be overwritten in DatumContainerEditView.prependDatum()
      });

      return datum;
    },

    /**
     * The LaTeXiT function automatically mark-ups an example in LaTeX code
     * (\exg. \"a) and then copies it on the expor modal so that when the user
     * switches over to their LaTeX file they only need to paste it in.
     *
     * We did a poll on Facebook among EGGers, and other linguists we know and
     * found that Linguex was very popular, and GB4E, so we did the export in
     * GB4E.
     */
    laTeXiT : function(showInExportModal) {
      utterance= this.get("datumFields").where({label: "utterance"})[0].get("value");
      gloss = this.get("datumFields").where({label: "gloss"})[0].get("value");
      translation= this.get("datumFields").where({label: "translation"})[0].get("value");
      var result = "\n \\begin{exe} "
            + "\n \\ex [*] \\gll " + utterance + " \\\\"
            + "\n\t" + gloss + " \\\\"
            + "\n\t\\glt `" + translation + "'"
            + "\n\\end{exe}\n\n";
      if (showInExportModal != null) {
        $("#export-type-description").html(" as LaTeX (GB4E)");
        $("#export-text-area").val($("#export-text-area").val() + result);
        $("#export-modal").modal("show");
      }

      return result;
    },

    /**
     * This function simply takes the utterance gloss and translation and puts
     * them out as plain text so the user can do as they wish.
     */
    exportAsPlainText : function(showInExportModal) {
      utterance= this.get("datumFields").where({label: "utterance"})[0].get("value");
      gloss = this.get("datumFields").where({label: "gloss"})[0].get("value");
      translation= this.get("datumFields").where({label: "translation"})[0].get("value");
      var result =  utterance+"\n"
            +gloss+"\n"
            +translation
            +"\n\n";
      if(showInExportModal != null){
        $("#export-type-description").html(" as text (Word)");
        $("#export-text-area").val( $("#export-text-area").val()+
           result
                 );
        $("#export-modal").modal("show");
      }
      return result;
    },

    /**
     * This takes as an argument the order of fields and then creates a row of csv.
     */
    exportAsCSV : function(showInExportModal, orderedFields, printheader) {
      if (orderedFields == null) {
        orderedFields = ["judgement","utterance","morphemes","gloss","translation"];
      }
      judgement = this.get("datumFields").where({label: "judgement"})[0].get("value");
      morphemes = this.get("datumFields").where({label: "morphemes"})[0].get("value");
      utterance= this.get("datumFields").where({label: "utterance"})[0].get("value");
      gloss = this.get("datumFields").where({label: "gloss"})[0].get("value");
      translation= this.get("datumFields").where({label: "translation"})[0].get("value");
      var resultarray =  [judgement,utterance,morphemes,gloss,translation];
      var result = '"' + resultarray.join('","') + '"';
      if (printheader != null) {
        var header = '"' + orderedFields.join('","') + '"';
        result = header + "\n" + result;
      }
      if (showInExportModal != null) {
        $("#export-type-description").html(" as CSV (Excel, Filemaker Pro)");
        $("#export-text-area").val($("#export-text-area").val() + result);
        $("#export-modal").modal("show");
      }
      return result;
    },

    /**
     * This function takes in a boolean whether the data should be appended in the import, the fields the data is in, and the header from the data which corresponds to datum fields.
     *
     * @param showInImportModal
     * @param orderedFields the values which correspond to datumfields
     * @param header the header fields which correspond to datumfields
     */
    importCSV : function(orderedFields, header) {
      for (f in header) {
        if (this.get("datumFields").where({label: header[f]})[0] != undefined) {
          this.get("datumFields").where({label: header[f]})[0].set("value", orderedFields[f]);
        } else {
          var n = new DatumField();
          n.label = header[f];
          n.value = orderedFields[f];
          this.get("datumFields").add(n);
        }
      }
      var csvDebugResult = this.exportAsCSV(null, null, true);

      return result;
    }
  });

  return Datum;
});
<commit_msg>Reformatting. No code changes.<commit_after>define([
    "backbone",
    "audio_video/AudioVideo",
    "comment/Comments",
    "datum/DatumField",
    "datum/DatumFields",
    "datum/DatumState",
    "datum/DatumStates",
    "datum/DatumTag",
    "datum/DatumTags",
    "datum/Session",
    "libs/Utils"
], function(
    Backbone,
    AudioVideo,
    Comments,
    DatumField,
    DatumFields,
    DatumState,
    DatumStates,
    DatumTag,
    DatumTags,
    Session
) {
  var Datum = Backbone.Model.extend(
  /** @lends Datum.prototype */
  {
    /**
     * @class The Datum widget is the place where all linguistic data is
     *        entered; one at a time.
     *
     * @property {DatumField} utterance The utterance field generally
     *           corresponds to the first line in linguistic examples that can
     *           either be written in the language's orthography or a
     *           romanization of the language. An additional field can be added
     *           if the language has a non-roman script.
     * @property {DatumField} gloss The gloss field corresponds to the gloss
     *           line in linguistic examples where the morphological details of
     *           the words are displayed.
     * @property {DatumField} translation The translation field corresponds to
     *           the third line in linguistic examples where in general an
     *           English translation. An additional field can be added if
     *           translations into other languages is needed.
     * @property {DatumField} judgement The judgement is the grammaticality
     *           judgement associated with the datum, so grammatical,
     *           ungrammatical, felicitous, unfelicitous etc.
     * @property {AudioVisual} audioVisual Datums can be associated with an audio or video
     *           file.
     * @property {Session} session The session provides details about the set of
     *           data elicited. The session will contain details such as date,
     *           language, consultant etc.
     * @property {Comments} comments The comments is a collection of comments
     *           associated with the datum, this is meant for comments like on a
     *           blog, not necessarily notes, which can be encoded in a
     *           field.(Use Case: team discussing a particular datum)
     * @property {DatumTags} datumtags The datum tags are a collection of tags
     *           associated with the datum. These are made completely by the
     *           user.They are like blog tags, a way for the user to make
     *           categories without make a hierarchical structure, and make
     *           datum easier for search.
     * @property {Date} dateEntered The date the Datum was first saved.
     *
     * @description The initialize function brings up the datum widget in small
     *              view with one set of datum fields. However, the datum widget
     *              can contain more than datum field set and can also be viewed
     *              in full screen mode.
     *
     * @extends Backbone.Model
     * @constructs
     */
    initialize : function() {
      // Initially, the first datumState is selected
      if (this.get("datumStates") && (this.get("datumStates").models.length > 0)) {
        this.get("datumStates").models[0].set("selected", "selected");
      }

      // If there's no audioVideo, give it a new one.
      if (!this.get("audioVideo")) {
        this.set("audioVideo", new AudioVideo());
      }

      // If there are no comments, give it a new one
      if (!this.get("comments")) {
        this.set("comments", new Comments());
      }

      // If there are no datumTags, give it a new one
      if (!this.get("datumTags")) {
        this.set("datumTags", new DatumTags());
      }
    },

    // Internal models: used by the parse function
    model : {
      datumFields : DatumFields,
      audioVideo : AudioVideo,
      session : Session,
      comments : Comments,
      datumStates : DatumStates,
      datumTags : DatumTags
    },

    changeCorpus : function(corpusname, callback) {
      if (this.pouch == undefined) {
        this.pouch = Backbone.sync.pouch(Utils.androidApp() ? Utils.touchUrl + corpusname : Utils.pouchUrl + corpusname);
      }
      if (typeof callback == "function") {
        callback();
      }
    },

    /**
     * Gets all the DatumIds in the current Corpus sorted by their date.
     *
     * @param {Function} callback A function that expects a single parameter. That
     * parameter is the result of calling "get_datum_ids/by_date". So it is an array
     * of objects. Each object has a 'key' and a 'value' attribute. The 'key'
     * attribute contains the Datum's dateEntered and the 'value' attribute contains
     * the Datum's ID.
     */
    getAllDatumIdsByDate : function(callback) {
      var self = this;
      this.changeCorpus(this.get("corpusname"),function(){
        self.pouch(function(err, db) {
          db.query("get_datum_ids/by_date", {reduce: false}, function(err, response) {
            if ((!err) && (typeof callback == "function"))  {
              console.log("Callback with: ", response.rows);
              callback(response.rows);
            }
          });
        });
      });
    },

    searchByQueryString : function(queryString, callback) {
      var self = this;
      this.changeCorpus(this.get("corpusname"), function() {
        self.pouch(function(err, db) {
          db.query("get_datum_field/get_datum_fields", {reduce: false}, function(err, response) {
            var matchIds = [];

            if (!err) {
              // Process the given query string into tokens
              var queryTokens = self.processQueryString(queryString);

              // Go through all the rows of results
              for (i in response.rows) {
                // Determine if this datum matches the first search criteria
                var thisDatumIsIn = self.matchesSingleCriteria(response.rows[i].key, queryTokens[0]);

                // Progressively determine whether the datum still matches based on
                // subsequent search criteria
                for (j = 1; j < queryTokens.length; j += 2) {
                  if (queryTokens[j] == "AND") {
                    // Short circuit: if it's already false then it continues to be false
                    if (!thisDatumIsIn) {
                      break;
                    }

                    // Do an intersection
                    thisDatumIsIn = thisDatumIsIn && self.matchesSingleCriteria(response.rows[i].key, queryTokens[j+1]);
                  } else {
                    // Do a union
                    thisDatumIsIn = thisDatumIsIn || self.matchesSingleCriteria(response.rows[i].key, queryTokens[j+1]);
                  }
                }

                // If the row's datum matches the given query string
                if (thisDatumIsIn) {
                  // Keep its datum's ID, which is the value
                  matchIds.push(response.rows[i].value);
                }
              }
            }

            callback(matchIds);
          });
        });
      });
    },

    /**
     * Determines whether the given object to search through matches the given
     * search criteria.
     *
     * @param {Object} objectToSearchThrough An object representing a datum that
     * contains (key, value) pairs where the key is the datum field label and the
     * value is the datum field value of that attribute.
     * @param {String} criteria The single search criteria in the form of a string
     * made up of a label followed by a colon followed by the value that we wish
     * to match.
     *
     * @return {Boolean} True if the given object matches the given criteria.
     * False otherwise.
     */
    matchesSingleCriteria : function(objectToSearchThrough, criteria) {
      var delimiterIndex = criteria.indexOf(":");
      var label = criteria.substring(0, delimiterIndex);
      var value = criteria.substring(delimiterIndex + 1);

      return objectToSearchThrough[label] && (objectToSearchThrough[label].toLowerCase().indexOf(value) >= 0);
    },

    /**
     * Process the given string into an array of tokens where each token is
     * either a search criteria or an operator (AND or OR). Also makes each
     * search criteria token lowercase, so that searches will be case-
     * insensitive.
     *
     * @param {String} queryString The string to tokenize.
     *
     * @return {String} The tokenized string
     */
    processQueryString : function(queryString) {
      // Split on spaces
      var queryArray = queryString.split(" ");

      // Create an array of tokens out of the query string where each token is
      // either a search criteria or an operator (AND or OR).
      var queryTokens = [];
      var currentString = "";
      for (i in queryArray) {
        var currentItem = queryArray[i].trim();
        if (currentItem.length <= 0) {
          break;
        } else if ((currentItem == "AND") || (currentItem == "OR")) {
          queryTokens.push(currentString);
          queryTokens.push(currentItem);
          currentString = "";
        } else if (currentString) {
          currentString = currentString + " " + currentItem.toLowerCase();
        } else {
          currentString = currentItem.toLowerCase();
        }
      }
      queryTokens.push(currentString);

      return queryTokens;
    },

    /**
     * Clone the current Datum and return the clone. The clone is put in the current
     * Session, regardless of the origin Datum's Session.
     *
     * @return The clone of the current Datum.
     */
    clone : function() {
      // Create a new Datum based on the current Datum
      var datum = new Datum({
        audioVideo : new AudioVideo(this.get("audioVideo").toJSON(), {parse: true}),
        comments : new Comments(this.get("comments").toJSON(), {parse: true}),
        dateEntered : this.get("dateEntered"),
        datumFields : new DatumFields(this.get("datumFields").toJSON(), {parse: true}),
        datumStates : new DatumStates(this.get("datumStates").toJSON(), {parse: true}),
        datumTags : new DatumTags(this.get("datumTags").toJSON(), {parse: true}),
        corpusname : this.get("corpusname")
        // Don't need to do Session here since it will be overwritten in DatumContainerEditView.prependDatum()
      });

      return datum;
    },

    /**
     * The LaTeXiT function automatically mark-ups an example in LaTeX code
     * (\exg. \"a) and then copies it on the expor modal so that when the user
     * switches over to their LaTeX file they only need to paste it in.
     *
     * We did a poll on Facebook among EGGers, and other linguists we know and
     * found that Linguex was very popular, and GB4E, so we did the export in
     * GB4E.
     */
    laTeXiT : function(showInExportModal) {
      utterance= this.get("datumFields").where({label: "utterance"})[0].get("value");
      gloss = this.get("datumFields").where({label: "gloss"})[0].get("value");
      translation= this.get("datumFields").where({label: "translation"})[0].get("value");
      var result = "\n \\begin{exe} "
            + "\n \\ex [*] \\gll " + utterance + " \\\\"
            + "\n\t" + gloss + " \\\\"
            + "\n\t\\glt `" + translation + "'"
            + "\n\\end{exe}\n\n";
      if (showInExportModal != null) {
        $("#export-type-description").html(" as LaTeX (GB4E)");
        $("#export-text-area").val($("#export-text-area").val() + result);
        $("#export-modal").modal("show");
      }

      return result;
    },

    /**
     * This function simply takes the utterance gloss and translation and puts
     * them out as plain text so the user can do as they wish.
     */
    exportAsPlainText : function(showInExportModal) {
      utterance= this.get("datumFields").where({label: "utterance"})[0].get("value");
      gloss = this.get("datumFields").where({label: "gloss"})[0].get("value");
      translation= this.get("datumFields").where({label: "translation"})[0].get("value");
      var result =  utterance+"\n"
            +gloss+"\n"
            +translation
            +"\n\n";
      if(showInExportModal != null){
        $("#export-type-description").html(" as text (Word)");
        $("#export-text-area").val( $("#export-text-area").val()+
           result
                 );
        $("#export-modal").modal("show");
      }
      return result;
    },

    /**
     * This takes as an argument the order of fields and then creates a row of csv.
     */
    exportAsCSV : function(showInExportModal, orderedFields, printheader) {
      if (orderedFields == null) {
        orderedFields = ["judgement","utterance","morphemes","gloss","translation"];
      }
      judgement = this.get("datumFields").where({label: "judgement"})[0].get("value");
      morphemes = this.get("datumFields").where({label: "morphemes"})[0].get("value");
      utterance= this.get("datumFields").where({label: "utterance"})[0].get("value");
      gloss = this.get("datumFields").where({label: "gloss"})[0].get("value");
      translation= this.get("datumFields").where({label: "translation"})[0].get("value");
      var resultarray =  [judgement,utterance,morphemes,gloss,translation];
      var result = '"' + resultarray.join('","') + '"';
      if (printheader != null) {
        var header = '"' + orderedFields.join('","') + '"';
        result = header + "\n" + result;
      }
      if (showInExportModal != null) {
        $("#export-type-description").html(" as CSV (Excel, Filemaker Pro)");
        $("#export-text-area").val($("#export-text-area").val() + result);
        $("#export-modal").modal("show");
      }
      return result;
    },

    /**
     * This function takes in a boolean whether the data should be appended in the import, the fields the data is in, and the header from the data which corresponds to datum fields.
     *
     * @param showInImportModal
     * @param orderedFields the values which correspond to datumfields
     * @param header the header fields which correspond to datumfields
     */
    importCSV : function(orderedFields, header) {
      for (f in header) {
        if (this.get("datumFields").where({label: header[f]})[0] != undefined) {
          this.get("datumFields").where({label: header[f]})[0].set("value", orderedFields[f]);
        } else {
          var n = new DatumField();
          n.label = header[f];
          n.value = orderedFields[f];
          this.get("datumFields").add(n);
        }
      }
      var csvDebugResult = this.exportAsCSV(null, null, true);

      return result;
    }
  });

  return Datum;
});
<|endoftext|>


<commit_before>/*global describe, it, browser, beforeEach, expect, require */

describe('datepicker', function () {
    'use strict';

    var options = {};

    beforeEach(function (done) {
        require('../common').initWebdriverCss(browser, options, done);
    });

    it('should take screenshot of closed datepickers', function (done) {
        var screenshotName = 'datepicker_closed',
            pageName = options.prefix + screenshotName + '_full';
        browser
            .url('/datepicker/fixtures/test.full.html')
            .webdrivercss(pageName, [
                {
                    name: screenshotName,
                    elem: '#screenshot-datepickers'
                }
            ], function (err, res) {
                expect(err).toBe(undefined);
                expect(res[screenshotName][0].isWithinMisMatchTolerance).toBe(true);
            }).call(done);
    });

    it('should take a screenshot of the open datepicker', function (done) {
        var screenshotName = 'datepicker_open',
            pageName = options.prefix + screenshotName + '_full';
        browser
            .url('/datepicker/fixtures/test.full.html')
            .click('#screenshot-datepicker .bb-date-field-calendar-button')
            .waitForVisible('ul[datepicker-popup-wrap]')
            .webdrivercss(pageName, [
                {
                    name: screenshotName,
                    elem: '#screenshot-datepicker'
                }
            ], function (err, res) {
                expect(err).toBe(undefined);
                expect(res[screenshotName][0].isWithinMisMatchTolerance).toBe(true);
            }).call(done);
    });

    it('should take a screenshot of the open datepicker append', function (done) {
        var screenshotName = 'datepicker_open_append',
            pageName = options.prefix + screenshotName + '_full';
        browser
            .url('/datepicker/fixtures/test.full.html')
            .click('#screenshot-datepicker-append-to-body .bb-date-field-calendar-button')
            .waitForVisible('ul[datepicker-popup-wrap]')
            .webdrivercss(pageName, [
                {
                    name: screenshotName,
                    elem: '#screenshot-datepicker-append-to-body'
                }
            ], function (err, res) {
                expect(err).toBe(undefined);
                expect(res[screenshotName][0].isWithinMisMatchTolerance).toBe(true);
            }).call(done);
    });

});
<commit_msg>update datepicker for new prefix<commit_after>/*global describe, it, browser, beforeEach, expect, require */

describe('datepicker', function () {
    'use strict';

    var options = {};

    beforeEach(function (done) {
        require('../common').initWebdriverCss(browser, options, done);
    });

    it('should take screenshot of closed datepickers', function (done) {
        var screenshotName = 'datepicker_closed',
            pageName = options.prefix + screenshotName + '_full';
        browser
            .url('/datepicker/fixtures/test.full.html')
            .webdrivercss(pageName, [
                {
                    name: screenshotName,
                    elem: '#screenshot-datepickers'
                }
            ], function (err, res) {
                expect(err).toBe(undefined);
                expect(res[screenshotName][0].isWithinMisMatchTolerance).toBe(true);
            }).call(done);
    });

    it('should take a screenshot of the open datepicker', function (done) {
        var screenshotName = 'datepicker_open',
            pageName = options.prefix + screenshotName + '_full';
        browser
            .url('/datepicker/fixtures/test.full.html')
            .click('#screenshot-datepicker .bb-date-field-calendar-button')
            .waitForVisible('ul[uib-datepicker-popup-wrap]')
            .webdrivercss(pageName, [
                {
                    name: screenshotName,
                    elem: '#screenshot-datepicker'
                }
            ], function (err, res) {
                expect(err).toBe(undefined);
                expect(res[screenshotName][0].isWithinMisMatchTolerance).toBe(true);
            }).call(done);
    });

    it('should take a screenshot of the open datepicker append', function (done) {
        var screenshotName = 'datepicker_open_append',
            pageName = options.prefix + screenshotName + '_full';
        browser
            .url('/datepicker/fixtures/test.full.html')
            .click('#screenshot-datepicker-append-to-body .bb-date-field-calendar-button')
            .waitForVisible('ul[uib-datepicker-popup-wrap]')
            .webdrivercss(pageName, [
                {
                    name: screenshotName,
                    elem: '#screenshot-datepicker-append-to-body'
                }
            ], function (err, res) {
                expect(err).toBe(undefined);
                expect(res[screenshotName][0].isWithinMisMatchTolerance).toBe(true);
            }).call(done);
    });

});
<|endoftext|>