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

Classpurpose
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\reactionReaction 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

idINT

true


useridINTThe user who clicked like on a  component
true
areaCHAR(20)

The area name



componentCHAR(20)The component name 

instanceidINTThe component's instance linked id, so that we know which record user has been liked

timecreatedINTTimestamp

contextidINTuser_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

EventsPurpose
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_deletedObserves 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.