ucberkeley/moocchat

View on GitHub
README.rdoc

Summary

Maintainability
Test Coverage
{<img src="https://codeclimate.com/github/ucberkeley/moocchat/badges/gpa.svg" />}[https://codeclimate.com/github/ucberkeley/moocchat]

{<img src="https://codeclimate.com/github/ucberkeley/moocchat/badges/coverage.svg" />}[https://codeclimate.com/github/ucberkeley/moocchat]

MOOCchat is a SaaS app for integrating peer learning/peer discussion
into MOOCs and similar settings.

This {short screencast}[https://www.youtube.com/watch?v=FIc36pfeyqA]
shows the previous version of this software in action.  In this new 
version, the flow and appearance are basically the same, but the code
has been completely rewritten: a new back end (Rails instead of Node) 
and new front end (jQuery and Websockets).

This document gives an overview of how to set up experiments/activities
with MOOCchat and an overview of the structure of the backend server.  
Partial URLs are given for all paths, so that if the
app is hosted on <tt>http://moocchat.herokuapp.com</tt>, the path
<tt>/tasks/welcome</tt> refers to <tt>http://moocchat.herokuapp.com/tasks/welcome</tt>.

= Data Model and Terminology

== User Roles

Single-table inheritance is used to distinguish three types
of Users:

* A Learner is a person in a course who participates in peer instruction
  activities.  
* An Instructor is an instructional staff member or researcher who sets
  up peer instruction activities and can also do anything a Learner can do
  (for example, test-drive an activity).
* An Administrator administers the use of the system and can also do
  everything that Learners and Instructors can do.

In addition, a Cohort is a group of learners, e.g., the enrollees in a
course.  A learner can belong to multiple cohorts.

== Describing an Experiment

As {this video}[https://www.youtube.com/watch?v=FIc36pfeyqA] shows, a
typical "experiment" involves a cohort of students who as a group go
through an activity in which they answer a question individually,
discuss the answer in small groups using text chat, and then possibly
get to answer again.

An *experimental condition* is a collection of parameters defining such
an activity, as follows:

A Question consists of some question text accompanied by a set of
multiple-choice answers and an indicator of which answer is correct.

An ActivitySchema is a list of Questions that may feature some peer
instruction, for example, a quiz review session.  Besides the list of
questions, the ActivitySchema has a name, a start and end time, and a
"starts every" interval -- how often
the activity starts.  For example, if "starts every" is 10 minutes,
learners can only do the activity between the 
start and end times, and when they arrive, they must wait until the next
10-minute boundary before they begin.

A Template is an HTML page processed through Erb that can contain
elements such as questions, radiobuttons for learners to select an
answer, text fields for learners to provide textual explanations,
a chat box, and so on.  The next section describes how instructors can
create templates.

A Condition describes how a learner or group of learners interacts
with an Activity Schema (set of questions).  The Condition consists of a
set of page views based on Templates.  Specifically, a condition
specifies:

* a sequence of zero or more prologue pages (e.g., instructions to the
learner) 
* a sequence of one or more body pages, which can contain questions,
chat boxes, and so on
* a repeat count for the number of times the body sequence is to be repeated
* a sequence of zero or more epilogue pages, to be shown after the last
repeat of the body sequence.

The rationale is that the same ActivitySchema, that is, the same list of
questions to be used during the same review session, can be instantiated
under different experimental conditions.  For example, one group of
learners assigned to Condition A would be given a followup ("transfer")
question  after the initial question, while those in Condition B would
not; or the learners in conditions A and B would be allowed differing
amounts of time to discuss their answers.


== The Task model

All of the above data structures (Condition, ActivitySchema, Learner,
Question, Template) are created to support the run of an experiment.

A Task is a runtime-only data structure that captures a single learner's
session interacting with an experiment.  Specifically, a Task associates
the triplet (Learner, Condition, ActivitySchema), and is created only
when such a triplet is supplied.  The Task#static view presents a form
that can be submitted to create a task, or an HTTP POST to
Task#create_from_params can do it (for example, by embedding a form in
the online course).

At Task creation time, each learner is assigned to a chat group with
other learners; the preferred and minimum allowed sizes of a chat group
are determined by the associated Condition.

Thereafter, the Task object is responsible for the "session
state" that tracks each learner through the activity:

* The Task captures the Learner answers and explanations, and makes them
available to the Templates shown to other learners in the same chat group.

* Each template can specify whether to advance the question
counter or not, to consume successive questions from the ActivitySchema.
For example, suppose the body contains two pages, 
each page consumes a question, and the body is to be repeated five
times.  Then the Activity Schema should contain at least ten questions
to avoid repetition.  (The question counter will wrap around to the
beginning of the question list if it runs out of questions.)

* The Task is responsible for receiving all interaction from a Learner
(choosing an answer, voting to continue, etc) and logging an appropriate
event; see Logging below.

= Creating Templates

Templates should be complete legal HTML 5 pages.
At the moment, we store the raw HTML of the pages verbatim, so you can
author your templates in whatever environment you want and then
cut-and-paste the HTML.
You can incorporate inline CSS styles in the template, or use a <tt><link></tt>
element to point to an external stylesheet.

There are two rules you **must** follow when creating a template:

0.  Inside the <tt>head</tt> element, you **must** include the following
line, to ensure the page loads the JavaScript functions that allow the
timer and other interactions to work:

    <%= javascript_include_tag "application" %>

0. The +body+ element **must not** have the CSS class "admin".  The
presence of this class indicates that the rendered page is **not** a
template that's part of a flow, and therefore inhibits some JavaScript
behaviors from loading (so they won't affect the administrative
interface). 

Templates are processed through <tt>erb</tt>, which allows the values of
Ruby variables and expressions to be interpolated into the HTML.  The
notation <tt><%= expr %></tt> evaluates the Ruby 
expression <tt>expr</tt> and inserts the result into the HTML template.

Non-Rubyists may find it helpful to know that Ruby instance variable
names begin with <tt>@</tt>.

== Templates and Timers

Because the activities require each chat group to move through the
activity more or less together, most pages have a "Continue" button, but
that button just registers (via AJAX) the learner's intent to continue;
it does not actually do the form-submit that causes the next-page action
to happen.

Instead, most templates will have a timer element that automatically
advances to the next page when the timer runs out.  The timer value is
determined by the Condition.

When a template is first displayed to the learner, the
HTML5 element with id <tt>interstitial</tt> (if one exists)
is immediately hidden; if the learner tries to advance to the next page
before the timer runs out, this element is revealed, so it should
display a message to the effect that "You'll proceed to the next step as
soon as all learners in your group are ready."  You can use CSS to float
the element in a lightbox, overlay it on top of the main HTML, or
whatever.

*Exception:* if *ALL* learners vote to continue before the timer runs
out, they should be allowed to do so.

== Displaying a question and answer choices


Below is a line-numbered example of the <tt><body></tt> of a template keyed to
the following explanation.  Line numbers are in parentheses.

(1) <tt>@start_form_tag</tt>:: 
  A complete <form> tag to start the form
  whose submission will cause the learner to move on to the next part of
  the task.  See the next subsection on Learner Navigation.  Do not try to
  construct the <form> tag manually.  The macro gives this form an +id+
  of +_main+, which various JavaScript helpers rely on.

(3) <tt>@question.text</tt>:: 
  The text of the current question.  In practice you'd probably put this
  in a properly-styled +div+.

(6) <tt>@question.answers</tt>:: 
  An array of strings representing the possible
  answers; you can iterate over it with <tt>each</tt> or <tt>each_with_index</tt>.
  <tt>@question.correct_answer_index</tt> 
  is the zero-based index of which answer is the correct one.

(7, 11)  <tt>@u</tt>:: 
  A hash
  that stores any user-defined state information associated with *this
  learner*; you can name elements of this hash in your forms to cause data
  to be collected or displayed.  *Only* form fields named <tt>u[...]</tt> will be
  persisted and logged.  

(23) <tt>timer</tt>:: 
    A macro that creates a JavaScript countdown
    timer.  By default it will count down from <tt>@timer</tt> seconds,
    which is set from the experimental condition, and on reaching zero, it will cause the
    form-submit button on the page's single form to be "clicked".  Both of
    these default behaviors can be overridden; see the TemplateHelper#timer documentation.

  1 <%= @start_form_tag %>
  2   <p>Here is a question:</p>
  3       <%= @question.text %>
  4   <p>Select the best answer:</p>
  5     <!-- capture learner's answer as choice number, 0..n-1  -->
  6     <% @question.answers.each_with_index do |answer, index| %>
  7       <input type="radio" value="<%= index %>" name="u[response]"> <%= answer %>
  8     <% end %>
  9   <!-- optional: collect free-text explanation from learner -->
 10   <p>Please explain your answer (will be seen by instructor)</p>
 11   <input type="textarea" name="u[explanation]"> <br>
 12   <!-- timer - by default, countdown specified by Condition -->
 13   <%= timer %>
 14   <!-- submit button - automatically "clicked" if timer expires first -->
 15   <input type="submit" value="Ready to Continue">
 16 </form>


== Showing a chat room and/or showing other learners' responses

(3) <tt>@data</tt>::  
  An _array of hashes_, where each element is a hash of
  one learner's user data (collected by form fields named
  <tt>u[...]</tt>).  Here it's used to display what answers and explanations the
  other learners gave; its keys are the same as those you specified as
  in lines 7 and 11 in the previous example.

(4) <tt>@me</tt>:: 
  The index of the array corresponding to *this* learner.
  <tt>@u['foo']</tt> is therefore a synonym for <tt>@data[@me]['foo']</tt>.  *BUT
  (important)* to *persist* collected data, you must use form fields named
  <tt>u[...]</tt>.

(10) <tt>chat</tt>:: 
   A macro that shows a chat window that is "listening"
   to the chat channel <tt>@chat_group</tt>.
  
If learners all click Request to End Chat, this overrides the
timer-based form submission and allows everyone to proceed.

  1 <%= @start_form_tag %>
  2   <p>Here are the answers your peers gave:</p>
  3   <% @data.each_with_index do |other, index| %>
  4     <% next if index == @me %>
  5     <p>Student <%= index %> selected choice <%= other['response'] %> because:</p>
  6     <p> <%= other['explanation'] %>
  7   <% end %>
  8   <!-- chat room -->
  9   <p>Discuss with others until time runs out or you're ready to go on :</p>
 10   <%= chat %> 
 11   <%= timer %>  
 12   <input type="submit" value="Request to end chat">  
 13 </form>

== Revealing the correct answer and going on to next question

(3) <tt>@question.correct_answer_index</tt>::
  Numerical index into the array of answers.    

(5)  <tt>next_question</tt>:: 
    If this hidden field is present and has any nonblank
    value, the question counter will be advanced to cause the next
    question to be consumed.  You would use this if a single pass
    through the activity involves answering more than one question, for
    example, a basic question followed by a transfer question.

  1 <%= @start_form_tag %>
  2   <p>The correct answer was:</p>
  3   <%= @question.answers[@question.correct_answer_index] %>
  4   <!-- make next page in flow advance to the next question -->
  5   <input type="hidden" name="next_question" value="true">
  6 </form>

= Server's view of task flow

The relevant controller actions in <tt>TasksController</tt> are:

+static+ (GET)::
  Serve a static page that lets learner choose activity and condition
  from menus.  Used primarily for testing.

+create+ (POST)::
  Create a new +Task+ from a learner name, +Condition+
  (experimental setup specifying page flow, group sizes, time spent on
  each page, and so on) and
  +ActivitySchema+ (list of questions to be used in the task).  Also
  adds the task to a +WaitingRoom+ with other learners assigned to the
  same condition and activity.  The +ActivitySchema+ specifies how often the
  waiting room is "emptied". 

+welcome+ (GET):: 
  A successful +POST+ to +create+ results in a redirect to the
  welcome action, which shows a timer that counts down to when this 
  waiting room is to be emptied.  On expiration, a POST to
  +first_page+ occurs.

+join_group+ (POST)::
  processes the waiting room, so that every +Task+ in the
  waiting room gets assigned to a +chat_group+. 
  If the value of +chat_group+ is the same as the value of the constant
  <tt>WaitingRoom::CHAT_GROUP_NONE</tt>, then there weren't enough
  waiting learners to fill out the minimum group size specified by the
  +Condition+, and this learner is redirected to +sorry+ and asked to
  retry the activity later.  Otherwise, the learner is redirected to
  +page+, a generic endpoint that will be called to render the next
  template in the flow.

+page+:: 
  sets up all the variables described under Templates, above, and
  renders the page.  Most templates will have a timer.  Advancing past
  the current page, whether manually by clicking the Submit button or
  automatically on timer expiration, results in a POST to +next_page+, 
  which advances the page counter and then redirects back to +page+.
  As described above, the presence of the <tt>next_question</tt> hidden form
  element determines whether the next usage of <tt>@question</tt> (and its
  corresponding <tt>@answers</tt>) will use the same question or a new one.  The
  body portion of the condition will be repeated while questions
  remain.  This action is idempotent: reloading a page while in this
  state has no effect.

+sorry+::
  A dead-end page that asks the learner to come back later, since they
  couldn't be placed into a chat group. 

== Additional template elements

These will be less frequently used but are listed here for completeness.

<tt>@task_id</tt>::
  an identifier for a data structure that associates a
  specific Learner, Condition, and Activity Schema, and stores all state
  related to that learner.  Useful to display for debugging purposes.

<tt>@counter</tt>::
  an integer tracking the page number in the overall
  activity, starting from 1 for the first prologue page.  For example, a
  task with 1 prologue page, 2 epilogue pages, and a 4-page sequence for
  each of three questions has 1<tt>2</tt>(3*4)=15 total pages, so counter values
  will range from 1 to 15.

<tt>@subcounter</tt>:: 
  a _zero-based_ counter tracking the page number within
  the current 
  sequence (prologue, body, epilogue).  E.g., for the
  preceding example, its successive values would be 0, 0,1,2,3, 0,1,2,3,
  0,1,2,3, 0,1.

<tt>@where</tt>:: 
  one of the three strings <tt>prologue</tt>,
  <tt>body</tt>, or  <tt>epilogue</tt>,
  indicating where we are in the current condition.  Can be used, e.g., in
  conjunction with <tt>@subcounter</tt> and/or <tt>@task_id</tt> to
  style page elements 
  accordingly by giving them CSS classes based on these variables' values,
  as in <tt><body class=<%= @where </tt> "-" @subcounter %>>', giving class names
  such as <tt>prologue-0</tt>, <tt>body-2</tt>, etc.

<tt>@chat_group</tt>:: 
  a string that identifies the chat group this
  learner is in; think of it as a channel.  After the Welcome page of
  the task, this is guaranteed to be nonblank (the learner could be in a
  chat group of size 1, but she will definitely be in some group).

= Logging

Every interesting "event" is logged in a table from which log
information can be easily extracted as JSON or CSV.  Some events are
logged at the server side; for events detected at the client side, 
<tt>TasksController#log_event</tt> can receive a POST (from whose URL
the ID of the task can be determined) and will look for
fields +name+ (required) and +value+ (optional since not all events use
it) to create and log the event, and return a 200 OK if all goes well.

Each row of the table has the following attributes:

<tt>created_at</tt>::
  When this event was logged, to the nearest second in UTC.

<tt>task_id, learner_id, activity_schema_id, condition_id</tt>::
  Foreign keys to the corresponding entities associated with the event.

<tt>counter, subcounter, question_counter</tt>::
  Current values of the counter (overall page in the task, 0-based), subcounter
  (subpage within prologue/body/epilogue), question counter (how many
  questions have been consumed so far)

<tt>question_id</tt>::
  The ID of the current question pointed to by the question counter
  (whether or not the question is displayed on the current page)

<tt>chat_group</tt>::
  For all events where the learner is already associated with a chat
  group (typically, any except +start+ and +reject+), the chat group
  channel name.

<tt>name, value</tt>::
  +name+ is a lower_snake_case string indicating what event
  happened.  A few events also have a string +value+, as indicated below; 
  for events with no value, the +value+ column is blank:

0. +start+ start new activity
0. +reject+ learner must quit since not enough other learners to chat),
0. +abandon+ voluntarily abandonment of task (closed browser, etc.) - assuming we can reliably detect
0. +broken_pipe+ involuntary abandonment of task, eg network failure - assuming we can reliably detect
0. +form_group+  get placed in a chat group
0. +continue+ learner clicks "Continue"; value of +counter+ field is which page in task (starting from 0 for first prologue page) she was continuing **from**
0. +view_page+ after clicking Continue and waiting for peers, learner is served the next page.  Typically a +continue+ event with +counter+ value n would be followed shortly after by a +proceed+ event with counter value n+1.
0. +finish+  complete activity by exiting from its final page
0. +user_state+ Sets +value+ to "key=new_data": learner modifies user data in any way, even while still on page.  Since user data is a set of key-value pairs per user, if the learner modifies multiple data items, an event will be logged for **each** modified item.  The key is guaranteed not to contain an equal sign, so splitting this string on the first '=' will always reconstruct the original +key+ and +new_data+.
0. +chat+: sets +value+ to what this user just typed in chat box
0. +quit_chat+ vote to stop chatting
0. +rejoin_chat+ undo (cancel) a previous vote to stop chatting