Totara Reactions

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

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

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

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.