Totara Reactions
What is Totara Reactions?
Totara reactions is a new plugin introduced in Totara 13.0 which provides user reaction functionalities to engage with resources, such as the ability to 'like' something. Currently, we are only implementing LIKE, but Totara Reactions is more future proof and also easy to extend for new and different types of reactions.
To use Totara reaction, users can go to Library and Workspace to 'like' the resources and add comments or replies. Â
How Totara Reactions work
When users 'like' your resources or comment, Totara Reactions will call resolver factory to create the instance for each component and save related component data into a reaction table. Each component just needs to create its own resolver and extend base resolver, override two methods (can_create_reaction() and get_context()).
When users delete your resources, it will trigger the reaction_deleted event that sits in Totara Reactions. Each plugin only needs to observe the event to implement the functionality.
Totara Reactions has already implemented Vue template and graphQL, as each plugin needs to import the Vue template and send a graphQL query to Totara Reactions that will process the code for each plugin.
The diagram below illustrates how this works:
Main API classes and methods
Namespaces and classes
Class | purpose |
---|---|
totara_reaction\entity | Reaction entity that holds the records from reaction table. |
totara_reaction\event | Each plugin can observe the reaction event to customise their own needs based on event. |
totara_reaction\formatter | Format time creation. |
totara_reaction\resolver | Reaction resolve factory to help other components create resolve instance and base reaction resolver. |
totara_reaction\loader\reaction\loader | Load reaction records from DB and map into reaction models, it works as data provider. In other words, if Front end needs to render reaction into page, we call loader to help web API to load the data and return data to Web API through reaction modal. |
totara_reaction\repository | Provide some functions to manipulate reaction table. |
totara_reaction\userdata | Purge all reaction data related to the deleted user. |
totara_reaction\reaction_helper | Reaction helper class to provide static methods and help to create and delete reaction. |
totara_reaction\reaction | Reaction model works like a wrapper for reaction entity that provide CRUD methods. |
totara_reaction\webapi\resolver\mutation totara_reaction\webapi\resolver\query totara_reaction\webapi\resolver\type | Reaction web API to provide an interface to Front end. |
Example of Totara Reactions structure
Name | Type | Note | PK | FK |
---|---|---|---|---|
id | INT | true | ||
userid | INT | The user who clicked like on a component | true | |
area | CHAR(20) | The area name | ||
component | CHAR(20) | The component name | ||
instanceid | INT | The component's instance linked id, so that we know which record user has been liked | ||
timecreated | INT | Timestamp | ||
contextid | INT | user_context | true |
Totara reaction graphQL
Below are some example of common graphQL scripts.
Create reaction
mutation totara_reaction_create_like( $component: param_component! $area: param_area! $instanceid: param_integer! ) { reaction: totara_reaction_create( component: $component area: $area instanceid: $instanceid ) { __typename component area instanceid user { __typename id fullname } } }
Delete reaction
mutation totara_reaction_remove_like( $component: param_component! $area: param_area! $instanceid: param_integer! ) { result: totara_reaction_delete( component: $component area: $area instanceid: $instanceid ) }
Get reaction
query totara_reaction_get_likes( $component: param_component! $area: param_area! $instanceid: param_integer! $page: param_integer ) { count: totara_reaction_total( component: $component area: $area instanceid: $instanceid ) reactions: totara_reaction_reactions( component: $component area: $area instanceid: $instanceid page: $page ) { __typename instanceid user { __typename id fullname profileimagealt profileimageurl profileimageurlsmall } } }
Events and purpose
Events | Purpose |
---|---|
totara_reaction\event\reaction_created | Sends a notification when a user likes your reaction and observe the event to customise requirement. |
totara_reaction\event\reaction_deleted | Observes the event to customise requirement. |
How to add reactions to a new component
Adding reactions to a new component is an easy task and in the simplest case requires specific namespace and one file that needs to extend the base solver. However, you need to add totara_reaction dependency into your component version file. Below is some example code:
namespace engage_article\totara_reaction\resolver; final class article_reaction_resolver extends base_resolver { public function can_create_reaction(int $resourceid, int $userid, string $area): bool { $article = article::from_resource_id($resourceid); $owner = $article->get_userid(); return $owner != $userid; } public function get_context(int $resourceid, string $area): \context { $article = article::from_resource_id($resourceid); return $article->get_context(); } } // version.php $plugin->dependencies = [ 'totara_reaction' => 2019081200 ]
Triggering and observing event (optional)
Observing event
use core\task\manager; use engage_article\totara_engage\resource\article; use totara_engage\task\like_content_task; use totara_reaction\event\reaction_created; /** * Observer for reaction component */ final class reaction_observer { /** * @param reaction_created $event * @return void */ public static function on_reaction_created(reaction_created $event): void { $others = $event->other; if ($others['component'] === article::get_resource_type()) { $liker_id = $event->userid; $article = article::from_resource_id($others['instanceid']); if ($liker_id !== $article->get_userid()) { $task = new like_content_task(); $task->set_custom_data([ 'url' => $article->get_url(), 'liker' => $liker_id, 'owner' => $article->get_userid(), 'name' => $article->get_name(), 'resourcetype' => 'resource' ]); manager::queue_adhoc_task($task); } } } } //events.php $observers = [ [ 'eventname' => '\totara_reaction\event\reaction_created', 'callback' => ['engage_article\observer\reaction_observer', 'on_reaction_created'] ], ];
Fire the event
namespace totara_reaction\event; final class reaction_created extends base_reaction_event { // Standard event code ... } //Fire event $event = reaction_created::instance($reaction); $event->trigger();
Best practices
Components do not need to handle reaction graphQL queries and mutation, all of them will be handled in the totara_reaction. The components just need to make reaction_resolver based on the component requirement for reaction and for decoupling, the components only need to event handler in the callback to satisfy their need.Â