diff --git a/README.md b/README.md index ad5286488..ca5f54931 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ For releases and release notes, see the [Releases](https://github.com/yetanalyti - [Developer Documentation](doc/dev.md) - [Example AWS Deployment](doc/aws.md) - [Reactions](doc/reactions.md) + - [JSON Spec](doc/reactions/spec.md) - [Sending xAPI statement(s) with Postman](doc/postman.md) ### Demos diff --git a/doc/dev.md b/doc/dev.md index 2facd7676..9ec931773 100644 --- a/doc/dev.md +++ b/doc/dev.md @@ -118,3 +118,4 @@ java -cp bench.jar lrsql.bench [arguments] Sample insert and query inputs can be found in the distribution at `bench/` [<- Back to Index](index.md) + diff --git a/doc/images/reactions/edit_condition_alpha.png b/doc/images/reactions/edit_condition_alpha.png new file mode 100644 index 000000000..2c3eb154d Binary files /dev/null and b/doc/images/reactions/edit_condition_alpha.png differ diff --git a/doc/images/reactions/edit_condition_beta.png b/doc/images/reactions/edit_condition_beta.png new file mode 100644 index 000000000..4c4406643 Binary files /dev/null and b/doc/images/reactions/edit_condition_beta.png differ diff --git a/doc/images/reactions/edit_condition_beta_ref.png b/doc/images/reactions/edit_condition_beta_ref.png new file mode 100644 index 000000000..fd0c1c333 Binary files /dev/null and b/doc/images/reactions/edit_condition_beta_ref.png differ diff --git a/doc/images/reactions/edit_dynamic_vars.png b/doc/images/reactions/edit_dynamic_vars.png new file mode 100644 index 000000000..b213aa02e Binary files /dev/null and b/doc/images/reactions/edit_dynamic_vars.png differ diff --git a/doc/images/reactions/edit_identity_path.png b/doc/images/reactions/edit_identity_path.png new file mode 100644 index 000000000..9135fdef6 Binary files /dev/null and b/doc/images/reactions/edit_identity_path.png differ diff --git a/doc/images/reactions/edit_intro.png b/doc/images/reactions/edit_intro.png new file mode 100644 index 000000000..d502d5cd3 Binary files /dev/null and b/doc/images/reactions/edit_intro.png differ diff --git a/doc/images/reactions/edit_template.png b/doc/images/reactions/edit_template.png new file mode 100644 index 000000000..d5da228fb Binary files /dev/null and b/doc/images/reactions/edit_template.png differ diff --git a/doc/images/reactions/edit_title.png b/doc/images/reactions/edit_title.png new file mode 100644 index 000000000..35cabb030 Binary files /dev/null and b/doc/images/reactions/edit_title.png differ diff --git a/doc/images/reactions/table.png b/doc/images/reactions/table.png new file mode 100644 index 000000000..8ceb51a9b Binary files /dev/null and b/doc/images/reactions/table.png differ diff --git a/doc/images/reactions/view_1.png b/doc/images/reactions/view_1.png new file mode 100644 index 000000000..2544480c4 Binary files /dev/null and b/doc/images/reactions/view_1.png differ diff --git a/doc/images/reactions/view_2.png b/doc/images/reactions/view_2.png new file mode 100644 index 000000000..8ed07e5c6 Binary files /dev/null and b/doc/images/reactions/view_2.png differ diff --git a/doc/images/reactions/view_3.png b/doc/images/reactions/view_3.png new file mode 100644 index 000000000..1ef0c8637 Binary files /dev/null and b/doc/images/reactions/view_3.png differ diff --git a/doc/reactions.md b/doc/reactions.md index 2e0da0f22..d968deac3 100644 --- a/doc/reactions.md +++ b/doc/reactions.md @@ -2,158 +2,55 @@ # Reactions -Reactions allow SQL LRS to watch for patterns in submitted xAPI data and dynamically generate new statements in response. +Reactions is a new feature that allows SQL LRS to watch for patterns in submitted xAPI data and dynamically generate new statements in response. ## Usage -To use Reactions the `LRSQL_ENABLE_REACTIONS` environment variable or the `enableReactions` LRS configuration property must be set to `true`. Reactions are disabled by default. +To use Reactions the `LRSQL_ENABLE_REACTIONS` environment variable or the `enableReactions` LRS configuration property must be set to `true`. Reactions is disabled by default. -Reaction "rulesets" are defined in JSON: +To view a reaction, click on the reaction in the table above. This will display a reaction view page: -``` json +![reactions table](images/reactions/table.png) -{ - "identityPaths": [ - [ - "actor", - "mbox" - ], - [ - "actor", - "mbox_sha1sum" - ], - [ - "actor", - "openid" - ], - [ - "actor", - "account", - "homePage" - ], - [ - "actor", - "account", - "name" - ] - ], - "conditions": { - "a": { - "and": [ - { - "path": [ - "object", - "id" - ], - "op": "eq", - "val": "https://example.com/activities/a" - }, - { - "path": [ - "verb", - "id" - ], - "op": "eq", - "val": "https://example.com/verbs/completed" - }, - { - "path": [ - "result", - "success" - ], - "op": "eq", - "val": true - } - ] - }, - "b": { - "and": [ - { - "path": [ - "object", - "id" - ], - "op": "eq", - "val": "https://example.com/activities/b" - }, - { - "path": [ - "verb", - "id" - ], - "op": "eq", - "val": "https://example.com/verbs/completed" - }, - { - "path": [ - "result", - "success" - ], - "op": "eq", - "val": true - }, - { - "path": [ - "timestamp" - ], - "op": "gt", - "ref": { - "condition": "a", - "path": [ - "timestamp" - ] - } - } - ] - } - }, - "template": { - "actor": { - "mbox": { - "$templatePath": [ - "a", - "actor", - "mbox" - ] - } - }, - "verb": { - "id": "https://example.com/verbs/completed" - }, - "object": { - "id": "https://example.com/activities/a-and-b", - "objectType": "Activity" - } - } -} +Each reaction has a title, ID, and created/modified timestamps. It also has an active vs. inactive status; a reaction can either be manually toggled as inactive, or it may be automatically set as inactive if an error is encountered when a reaction is triggered. +![reaction view 1](images/reactions/view_1.png) +![reaction view 2](images/reactions/view_2.png) +![reaction view 3](images/reactions/view_3.png) -``` +The most important part of a reaction is its ruleset. Each ruleset in turn contains the following properties: conditions, template, and identity paths. Each ruleset component is explained in the sections below. -### Identity Paths +To create a new reaction, click on "Add New Reactions" on the main Reactions page. Likewise, to edit a current reaction, click on "Edit". -Identity Paths (`identityPaths` in the ruleset JSON) are a method of grouping statements for which you are attempting to match conditions. Typically, Reactions may revolve around actor Inverse Functional Identifiers (IFIs), e.g. `["actor", "mbox"]` or `["actor", "account", "name"]` which is equivalent to saying "For a given Actor, look for statements that share IFI values". +Creating a new reaction will display an edit page, where the user can modify the reaction title, active status, and ruleset: -Alternative approaches to Identity Path may be used by modifying `identityPaths`, for instance `["context", "registration"]` to group statements by learning session. +![reaction edit](images/reactions/edit_intro.png) ### Conditions -`conditions` is a mapping of names to rules for finding significant statements. Rules can be composed with boolean logic. +Each condition is a set of rules for finding relevant statements. Each condition has a unique name followed by its rules, which can be composed with boolean logic. + +In the example given, statement "alpha" must have `object.id` equal to `https://example.com/alpha` AND `verb.id` equal to `https://example.com/completed` AND `result.success` property equal to `true`: + +![reaction edit alpha](images/reactions/edit_condition_alpha.png) -In the example given above statement `a` must have an object id equal to `https://example.com/activities/a`, a verb id equal to `https://example.com/verbs/completed`, and a result success equal to `true`. Statement `b` must have the same verb and result success but an object id equal to `https://example.com/activities/b` and a timestamp greater than that of `a`. +Statement "beta" must have `verb.id` equal to `https://example.com/completed` AND `result.success` equal to `true` (just like statement "alpha") AND `object.id` equal to `https://example.com/beta` AND `timestamp` greater than that of statement "alpha": + +![reaction edit beta](images/reactions/edit_condition_beta.png) +![reaction edit beta ref](images/reactions/edit_condition_beta_ref.png) #### Rules -All rules have a `path` array that indicates a path in an xAPI statement and an `op` that is one of the following operators: +All rules have a path array that indicates a path in an xAPI statement and an operator that is one of the following: -* `gt` - Greater than -* `lt` - Less than -* `gte` - Greater than or equal -* `lte` - Less than or equal -* `eq` - Equal -* `noteq` - Not equal -* `like` - Fuzzy match using SQL `%` syntax. For example, `bo%` matches `bob` and `boz`. -* `contains` - Array contains +* Greater than +* Less than +* Greater than or equal +* Less than or equal +* Equal +* Not equal +* Like (fuzzy match using SQL `%` syntax; for example, `bo%` matches `bob` and `boz`.) +* Array contains Rules either have a `val` literal value or a `ref` which is a path into a statement found for another condition. @@ -161,13 +58,33 @@ Rules either have a `val` literal value or a `ref` which is a path into a statem Booleans compose multiple rules together. Booleans are objects with a single key: -* `and` - Array of rules which must all be true -* `or` - Array of rules of which one must be true -* `not` - Rule to nullify +* AND: Array of rules which must all be true +* OR: Array of rules of which one must be true +* NOT: Rule that must _not_ be true + +Rule types (either "Statement Criteria" or a boolean) can be selected using the topmost select input, or via the "Add sub-clause" button. Booleans can be nested arbitrarily, e.g. an AND clause can have multiple OR subclauses, which can each have a NOT clause, and so on and so forth. ### Template -`template` describes the xAPI statement the reaction will produce. It is identical to an xAPI statement, except that object properties may be substituted with `$templatePath`. This is a path that points to a value in a statement matched by `conditions`, using the same syntax as an `identityPaths` path. In the above example, the `$templatePath` points to the actor `mbox` for the actor matched by condition `a`. +The template describes the xAPI statement the reaction will produce. It is identical to an xAPI statement, except that object properties may be substituted with `$templatePath`. This is a path that points to a value in a statement matched by `conditions`, using a JSON array of xAPI statement properties. + +To help with creating template paths, the above panel can be opened, which guides the user on how to create paths. In this example, we create a template path to the actor `mbox` for the actor matched by `condition_alpha`: + +![edit dynamic vars](images/reactions/edit_dynamic_vars.png) + +We can then copy-paste the template path into our statement template: + +![edit template](images/reactions/edit_template.png) + +### Identity Paths + +Identity Paths are a method of grouping statements for which you are attempting to match conditions on. Typically, reactions may revolve around actor Inverse Functional Identifiers (IFIs), e.g. actor `mbox` or account `name` strings. Any statements with the same IFI properties will be considered a "group" to match conditions on. This is equivalent to saying "For a given Actor, look for statements that share IFI values." + +Alternative approaches to Identity Path may be used by modifying `identityPaths`, for instance using the `registration` context property to group statements by learning session. + +In this example (the default set of identity paths), say we have Statements A and B that share an IFI and match `condition_alpha` and `condition_beta`, respectively. This will trigger the reaction. Conversely, if Statements A and B have _different_ IFIs, then the reaction _will not trigger_: + +![edit identity paths](images/reactions/edit_identity_path.png) ## Example @@ -180,10 +97,10 @@ Given the reaction specified above, if the following statements are posted to th "mbox": "mailto:bob@example.com" }, "verb": { - "id": "https://example.com/verbs/completed" + "id": "https://example.com/completed" }, "object": { - "id": "https://example.com/activities/a", + "id": "https://example.com/alpha", "objectType": "Activity" }, "result": { @@ -196,10 +113,10 @@ Given the reaction specified above, if the following statements are posted to th "mbox": "mailto:bob@example.com" }, "verb": { - "id": "https://example.com/verbs/completed" + "id": "https://example.com/completed" }, "object": { - "id": "https://example.com/activities/b", + "id": "https://example.com/beta", "objectType": "Activity" }, "result": { @@ -208,7 +125,6 @@ Given the reaction specified above, if the following statements are posted to th "timestamp": "2024-01-23T02:00:00.000Z" } ] - ``` Then the following statement will be added subsequently (note that some unrelated fields are removed for clarity): @@ -219,14 +135,17 @@ Then the following statement will be added subsequently (note that some unrelate "mbox": "mailto:bob@example.com" }, "verb": { - "id": "https://example.com/verbs/completed" + "id": "https://example.com/completed" }, "object": { - "id": "https://example.com/activities/a-and-b", + "id": "https://example.com/alpha-and-beta", "objectType": "Activity" } } - ``` +### JSON Format + +Reaction data is stored internally in JSON format. The Reactions spec, including a JSON example, can be found [here](reactions/spec.md). + [<- Back to Index](index.md) diff --git a/doc/reactions/spec.md b/doc/reactions/spec.md new file mode 100644 index 000000000..585d8e759 --- /dev/null +++ b/doc/reactions/spec.md @@ -0,0 +1,125 @@ +[<- Back to Index](index.md) + +# Reactions JSON Spec + +Reaction data is internally stored in SQL LRS as JSON objects. The top-level spec for Reactions is as follows, with some properties being autogenerated when the reaction is stored or updated in SQL LRS: +| Property | Type | Autogenerated? +| --- | --- | --- +| `id` | UUID | True +| `created` | Timestamp | True +| `modified` | Timestamp | True +| `title` | String | False +| `active` | Boolean | False +| `ruleset` | Object | False + +The following are properties of `ruleset` objects: +| Property | Type | Description +| --- | --- | --- +| `conditions` | Object | Map from condition name strings to condition objects. +| `template` | Object | xAPI Statement object that allows additional `$templatePath` properties. +| `identityPaths` | Array | Array of path arrays (see below). + +A path array is an array of xAPI statement properties pointing to a particular location in the statement object, e.g. `["actor", "mbox"]`. A `$templatePath` array is similar, except its first element is a condition name, e.g. `["condition_alpha", "actor", "mbox"]`. + +The following are properties of condition objects. Condition objects may be a boolean and contain a single `and`, `or`, or `not` property, or they can be statement criteria and contain `path`, `op`, and one of `val` or `ref`. +| Property | Type | Description +| --- | --- | --- +| `path` | Array | Path array. +| `op` | String | String denoting the reaction operation. One of the following: `gt`, `lt`, `gte`, `lte`, `eq`, `noteq`, `like`, `contains`. +| `val` | Scalar | Comparison value; can be a string, boolean, number, or `null`. Type may have additional restrictions depending on the particular location in the statement. +| `ref` | Object | Object that references another condition. +| `and` | Array | Array of condition objects. +| `or` | Array | Array of condition objects. +| `not` | Object | Condition object to be negated. + +The following are properties of the `ref` object: +| Property | Type | Description +| --- | --- | --- +| `condition` | String | Referenced condition name. +| `path` | Path Array | Path array for referenced statement. + +### Example JSON + +The following is the example reaction from [the tutorial](reactions.md), in JSON format: +```json +{ + "id": "019257f4-d533-8c0b-8730-28b82f383982", + "created": "2024-10-04T14:35:16Z", + "modified": "2024-10-04T14:35:16Z", + "title": "reaction_example", + "active": true, + "ruleset": { + "conditions": { + "condition_alpha": { + "and": [ + { + "path": [ "object", "id" ], + "op": "eq", + "val": "https://example.com/alpha" + }, + { + "path": [ "verb", "id" ], + "op": "eq", + "val": "https://example.com/completed" + }, + { + "path": [ "result", "success" ], + "op": "eq", + "val": true + } + ] + }, + "condition_beta": { + "and": [ + { + "path": [ "object", "id" ], + "op": "eq", + "val": "https://example.com/beta" + }, + { + "path": [ "verb", "id" ], + "op": "eq", + "val": "https://example.com/completed" + }, + { + "path": [ "result", "success" ], + "op": "eq", + "val": true + }, + { + "path": [ "timestamp" ], + "op": "gt", + "ref": { + "condition": "condition_alpha", + "path": [ "timestamp" ] + } + } + ] + } + }, + "template": { + "actor": { + "mbox": { + "$templatePath": [ "condition_alpha", "actor", "mbox" ] + } + }, + "verb": { + "id": "https://example.com/completed" + }, + "object": { + "id": "https://example.com/alpha-and-beta", + "objectType": "Activity" + } + }, + "identityPaths": [ + [ "actor", "mbox" ], + [ "actor", "mbox_sha1sum" ], + [ "actor", "openid" ], + [ "actor", "account", "homePage" ], + [ "actor", "account", "name" ] + ] + } +} +``` + +[<- Back to Index](index.md)