Skip to content

How we implement groups functionality

Jamie Folsom edited this page Sep 17, 2013 · 22 revisions

Annotation Studio allows users to view both documents and annotations through membership in groups.

We implement the group feature in three elements:

  1. Annotator. We add several fields to the annotator data schema.
  2. Annotation Studio. User and Document objects can both be associated with any number of tags, each representing a group.
  3. MIT Annotation Data Store supports queries that limit results to annotations to matching values in their groups fields.

###Annotator

Here is our modified annotation data model. Note the two additions to the schema: groups and subgroups:

{
    "id": "xyz",
    "quote": "outward-bound",
    "text": "",
    "uri": "http://annotationstudio.mit.edu/documents/billy-budd",
    "user": "[email protected]",
    "username": "HyperStudio",
    "uuid": "1B9D142B",
    "subgroups": ["test"],
    "groups": ["21L.705", "test"],
    "permissions": {
        "delete": ["[email protected]"],
        "update": ["[email protected]"],
        "admin": ["[email protected]"],
        "read": []
    },
    "tags": [],
    "ranges": [{
        "start": "/div[1]/p[6]",
        "startOffset": 604,
        "end": "/div[1]/p[6]",
        "endOffset": 617,
    }],
    "updated": "2013-09-16T19:45:39.267Z",
    "created": "2013-09-16T19:45:13.012Z",
    "annotator_schema_version": "1.0"
}

Those group values are stored per-user, and are added automatically to all of each user's annotations.

###Annotation Studio

In Annotation Studio, we have a tags table

ans_dev=# select * from tags;
 id |            name
----+----------------------------
  1 | Partners
  2 | Hyperstudio
  3 | Instructors
  4 | Developers
  5 | Melville
  6 | Darwin

and a taggings mapping table

ans_dev=# select id, tag_id, taggable_id, taggable_type, context from taggings;
 id  | tag_id | taggable_id | taggable_type |   context
-----+--------+-------------+---------------+--------------
523 |     73 |         172 | User          | rep_subgroup
525 |     44 |          87 | Document      | rep_group
526 |     45 |          87 | Document      | rep_group
527 |     75 |          87 | Document      | rep_group
528 |     71 |         174 | User          | rep_group
529 |     75 |         174 | User          | rep_group
530 |     76 |         174 | User          | rep_subgroup
531 |     77 |         174 | User          | rep_subgroup
532 |     71 |         171 | User          | rep_group
534 |     76 |         175 | User          | rep_subgroup

This is the structure set up by the excellent acts_as_taggable gem, which is incorporated as part of our Repertoire Groups gem. Repertoire Groups also provides user roles (like administrator, editor, etc). Tags can be associated with User objects and with Document objects, and can in turn be subgrouped into contexts, in our case, rep_group, and rep_subgroup.

ans_dev=# select id, tag_id, taggable_id, taggable_type, context from taggings; id | tag_id | taggable_id | taggable_type | context -----+--------+-------------+---------------+-------------- 523 | 73 | 172 | User | rep_subgroup 525 | 44 | 87 | Document | rep_group 526 | 45 | 87 | Document | rep_group 527 | 75 | 87 | Document | rep_group 528 | 71 | 174 | User | rep_group 529 | 75 | 174 | User | rep_group 530 | 76 | 174 | User | rep_subgroup 531 | 77 | 174 | User | rep_subgroup 532 | 71 | 171 | User | rep_group 534 | 76 | 175 | User | rep_subgroup

###MIT Annotation Data Store

Finally, on the Data Store, which stores all annotations, in addition to storing annotations, we support queries against those groups and subgroups values.

app.get('/api/search', tokenOK, function (req, res) {
    var query;

    //...other query options...

    switch (req.query.mode) {
    case 'user':
       query.where('user').equals(req.query.user);
       break;
    case 'group':
       query.where('subgroups').in(req.query.subgroups);
       break;
    case 'class':
       query.where('groups').in(req.query.groups);
       break;
    case 'admin':
       break;
    }

    //...query execution...

}
Clone this wiki locally