Permalink
Browse files

API endpoint for listing all split tests and getting split tests deta…

…il with historical data.

Split test API documentation moved from code to README.
  • Loading branch information...
assaf committed Apr 15, 2012
1 parent 5f7c534 commit b5677696efd7141483be8899966fe38dfa81eac0
View
@@ -163,58 +163,98 @@ DELETE /v1/activity/:id
```
-### Participate In Split Test
+## Web API for Split Tests
+
+### List Tests
+
+```
+PUT /v1/split
+```
+
+Returns a list of all active split test.
+
+Response JSON document includes a single property `tests` with an array of split
+tests, each an object with the following properties:
+* `id` - Test identifier
+* `title` - Human readable title
+* `created` - Timestamp when test was created
+
+### Get Test Results
+
+```
+PUT /v1/split/:test
+```
+
+Returns information about a split test.
+
+Response json document includes the following properties:
+* `id` - Test identifier
+* `title` - Human readable title
+* `created` - Timestamp when test was created
+* `alternatives` - Array of alternatives
+
+Each alternative includes the following properties:
+* `title` - Title of this alternative
+* `participants` - Number of participants in this test
+* `completed` - Number of participants that completed this test
+* `data` - Time series data
+
+Time series data is an array of entries, one for each hour, consisting of:
+* `time` - The time
+* `participants` - Number of participants in this test
+* `completed` - Number of participants that completed this test
+
+If the test does not exist, returns status code 404.
+
+### Add Participant
```
-PUT /v1/split/:test/:participant
+POST /v1/split/:test/:participant
```
-Request path specifies the test and participant identifiers.
+Send this request to indicate participant joined the test.
-The body is a JSON document (form parameters also supported) with the following
-properties:
+Request path specifies the test and participant identifier.
-* alternative - Alternative number (0 ... n)
-* outcome - A numeric value
+The request document must specify a single parameter (in any supported media
+type):
+* `alternative` - The alternative chosen for this participant, either 0 (A) or 1
+ (B)
-To indicate that participant is taking part in this split test, send a request
-with alternative number. You can send this request any number of times, only
-the first update is stored.
+Returns status code 200 and a JSON document with one property:
+* `alternative` - The alternative decided for this participant
-To indicate that participant converted, send a request with alternative number
-abd the outcome. You can send this request any number of times, only the first
-update is stored.
+If the test identifier or alternative are invalid, the request returns status
+code 400.
-If successful, this request returns status code 200 and a JSON document with the
-following properties:
+### Record Completion
-* participant - Participant identifeir
-* alternative - Alternative number
-* outcome - Recorded outcome
+```
+POST /v1/split/:test/:participant/completed
+```
-If participant was already added with a different alternative, the request
-returns status code 409 (Conflict) and the above JSON document.
+Send this request to indicate participant completed the test (converted).
-If the test identifier, alternative number or outcome are invalid, the request
-returns status code 400.
+Request path specifies the test and participant identifier.
+Returns status code 204 (No content).
-### Retrieve Participation In Split Test
+### Retrieve Participant
```
GET /v1/split/:test/:participant
```
-Request path specifies the test and participant identifiers.
+Returns information about participant.
-If successful, this request returns status code 200 and a JSON document with the
-following properties:
+Request path specifies the test and participant identifier.
-* participant - Participant identifeir
-* joined - When participant joined this test (RFC3339)
-* alternative - Alternative number
-* completed - When participant completed this test (RFC3339)
-* outcome - Recorded outcome
+Response JSON document includes the following properties:
+* `participant` - Identifier
+* `joined` - Timestamp when participant joined experiment
+* `alternative` - Alternative number
+* `completed` - Timestamp when participant completed experiment
+* `outcome` - Recorded outcome
If the participant never joined this split test, the request returns status code
404.
@@ -206,9 +206,9 @@ SplitTest =
id: ids[i]
title: test.title
created: Date.create(test.created)
- alternatives: [a,b].map(({ participants, completed })->
- participants: parseInt(participants)
- completed: parseInt(completed)
+ alternatives: [a,b].map(({ title, participants, completed }, i)->
+ participants: parseInt(participants) || 0
+ completed: parseInt(completed) || 0
title: title || "AB"[i]
)
)
@@ -227,72 +227,42 @@ SplitTest =
multi.hgetall "#{base_key}"
multi.hgetall "#{base_key}.0"
multi.hgetall "#{base_key}.1"
- multi.exec (error, [test, a, b])->
+ multi.hgetall "#{base_key}.participants"
+ multi.zrange "#{base_key}.joined", 0, -1, "withscores"
+ multi.hgetall "#{base_key}.converted.0"
+ multi.hgetall "#{base_key}.converted.1"
+ multi.exec (error, [test, a, b, participants, joined, converted_a, converted_b])->
+ converted = [converted_a, converted_b]
return callback(error) if error
- if test?.title
- callback null,
- id: test_id
- title: test.title
- created: Date.create(test.created)
- alternatives: [a,b].map(({ participants, completed, title }, i)->
- participants: parseInt(participants)
- completed: parseInt(completed)
- title: title || "AB"[i]
- )
- else
+ unless test?.title # no such test
callback(null)
+ return
-
- # Loads test data and passes callback array with one element for each
- # alternative, containing:
- # title - Alternative title
- # weight - Designated weight
- # data - Data points
- #
- # Each data point has the properties:
- # time - Time stamp (in hour increments) RFC3999
- # participants - How many participants joined during that hour
- # completed - How many of these participants completed the test
- data: (test_id, callback)->
- base_key = SplitTest.baseKey(test_id)
- Async.waterfall [
- (done)->
- # First we need to determine which participant is assigned what
- # alternative. This gives us a map from participant ID to alternative
- # number.
- redis.hgetall "#{base_key}.participants", done
-
- , (participants, doneJoined)->
- # Next we need to determine how many participants joined each
- # alternative in any given hour.
- hourly = [{}, {}]
- redis.zrange "#{base_key}.joined", 0, -1, "withscores", (error, joined)->
- return doneJoined(error) if error
- for [id, time] in joined.inGroupsOf(2)
- time -= time % 3600000 # round down to nearest hour
- set = hourly[participants[id]]
- hour = set[time] ||= { time: Date.create(time).toISOString() }
- hour.participants = (hour.participants || 0) + 1
- doneJoined(null, hourly)
-
- , (hourly, doneConverted)->
- # Load everything we know about conversion for each given time slot, and
- # update the data record.
- Async.map [0, 1], (alternative, doneEach)->
- set = hourly[alternative]
- redis.hgetall "#{base_key}.converted.#{alternative}", (error, converted)->
- return doneEach(error) if error
- for _, entry of set
- entry.converted = parseInt(converted[entry.time]) || 0
- doneEach(null, set)
- , doneConverted
-
- , (hourly, done)->
- # Now let's turn each hourly map into a sorted array.
- sorted = (Object.values(set).sort("time") for set in hourly)
- done(null, sorted)
-
- ], callback
+ # Next we need to determine how many participants joined each
+ # alternative in any given hour.
+ hourly = [{}, {}]
+ for [id, time] in joined.inGroupsOf(2)
+ time -= time % 3600000 # round down to nearest hour
+ set = hourly[participants[id]]
+ hour = set[time] ||= { time: Date.create(time).toISOString() }
+ hour.participants = (hour.participants || 0) + 1
+
+ # And from that we can determine how many converted in each hour.
+ [0, 1].each (alternative)->
+ set = hourly[alternative]
+ for _, entry of set
+ entry.converted = parseInt(converted[alternative][entry.time]) || 0
+
+ callback null,
+ id: test_id
+ title: test.title
+ created: Date.create(test.created)
+ alternatives: [a,b].map(({ participants, completed, title }, alternative)->
+ participants: parseInt(participants) || 0
+ completed: parseInt(completed) || 0
+ title: title || "AB"[alternative]
+ data: Object.values(hourly[alternative]).sort("time")
+ )
# -- Utility --
@@ -4,12 +4,15 @@ server = require("../config/server")
SplitTest = require("../models/split_test")
+# Returns a list of all active split test.
+server.get "/v1/split", (req, res, next)->
+ SplitTest.list (error, tests)->
+ if tests
+ res.send tests: tests
+ else
+ next(error)
+
# Returns information about a split test.
-#
-# Response JSON document includes the following properties:
-# id - Test identifier
-# title - Human readable title
-# created - Timestamp when test was created
server.get "/v1/split/:test", (req, res, next)->
SplitTest.load req.params.test, (error, test)->
if test
@@ -21,15 +24,7 @@ server.get "/v1/split/:test", (req, res, next)->
else
next(error)
-
# Send this request to indicate participant joined the test.
-#
-# The request must specify a single parameter (in any supported media type):
-# alternative - The alternative chosen for this participant, either
-# 0 (A) or 1 (B)
-#
-# Returns status code 200 and a JSON document with one property:
-# alternative - The alternative decided for this participant
server.post "/v1/split/:test/:participant", (req, res, next)->
alternative = parseInt(req.body.alternative, 10)
try
@@ -41,10 +36,7 @@ server.post "/v1/split/:test/:participant", (req, res, next)->
catch error
res.send error.message, 400
-
# Send this request to indicate participant completed the test.
-#
-# Returns status code 204 (No content).
server.post "/v1/split/:test/:participant/completed", (req, res, next)->
try
SplitTest.completed req.params.test, req.params.participant, (error)->
@@ -55,39 +47,11 @@ server.post "/v1/split/:test/:participant/completed", (req, res, next)->
catch error
res.send error.message, 404
-
-# Returns the raw data part of this split test.
-#
-# Response JSON document is an array with one element for each alternative.
-# Each element is itself an array with the properties:
-# time - Date/time at 1 hour resoultion (RFC3339)
-# participants - Number of participants joined during that hour
-# completed - How many of these participants completed the test
-server.get "/v1/split/:test/data", (req, res, next)->
- SplitTest.data req.params.test, (error, data)->
- if data
- res.send data
- else
- next(error)
-
-
# Returns information about participant.
-#
-# Path includes test and participant identifier.
-#
-# Response JSON document includes the following properties:
-# participant - Identifier
-# joined - Timestamp when participant joined experiment
-# alternative - Alternative number
-# completed - Timestamp when participant completed experiment
-# outcome - Recorded outcome
server.get "/v1/split/:test/:participant", (req, res, next)->
- try
- SplitTest.getParticipant req.params.test, req.params.participant, (error, result)->
- if result
- res.send result
- else
- next(error)
- catch error
- res.send error.message, 404
+ SplitTest.getParticipant req.params.test, req.params.participant, (error, result)->
+ if result
+ res.send result
+ else
+ next(error)
Oops, something went wrong.

0 comments on commit b567769

Please sign in to comment.