-
Notifications
You must be signed in to change notification settings - Fork 30
How we implement groups functionality
Annotation Studio allows users to view both documents and annotations through membership in groups.
We implement the group feature in three elements:
- Annotator. We add several fields to the annotator data schema.
- Annotation Studio. User and Document objects can both be associated with any number of tags, each representing a group.
- 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, in addition to the users and documents tables, 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.
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':
// Admins can see everything, so don't limit
// results by group and subgroup values in query
break;
}
//...query execution...
}
This separation of concerns seems to be a good one -- Annotator is agnostic about the user and groups implementation in Annotation Studio, we simply pass group values into the annotation object before storing it, and we use those values in both the front end, and in the data store/API.
It would be easy in this architecture to implement other schemes, say with three kinds of groups, or with just one, or none.
That's what's suggested by the Annotator wiki, in fact, although we circumvented that approach with our own, since it was not clear to me how to make that one work.