Skip to content

Commit

Permalink
MDL-52425 tool_lp: Competency rules apply when a competency is completed
Browse files Browse the repository at this point in the history
  • Loading branch information
Frederic Massart committed Apr 18, 2016
1 parent d327579 commit 9373acf
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 5 deletions.
95 changes: 95 additions & 0 deletions admin/tool/lp/classes/api.php
Expand Up @@ -2502,7 +2502,11 @@ public static function add_evidence($userid,
}

// Setting the grade and proficiency for the user competency.
$wascompleted = false;
if ($setucgrade == true) {
if (!$usercompetency->get_proficiency() && $ucproficiency) {
$wascompleted = true;
}
$usercompetency->set_grade($ucgrade);
$usercompetency->set_proficiency($ucproficiency);
}
Expand Down Expand Up @@ -2531,9 +2535,100 @@ public static function add_evidence($userid,
$usercompetency->update();
$evidence->create();

// The competency was marked as completed, apply the rules.
if ($wascompleted) {
self::apply_competency_rules_from_usercompetency($usercompetency, $competency);
}

return $evidence;
}

/**
* Apply the competency rules from a user competency.
*
* The user competency passed should be one that was recently marked as complete.
* A user competency is considered 'complete' when it's proficiency value is true.
*
* This method will check if the parent of this usercompetency's competency has any
* rules and if so will see if they match. When matched it will take the required
* step to add evidence and trigger completion, etc...
*
* @param usercompetency $usercompetency The user competency recently completed.
* @param competency|null $competency The competency of the user competency, useful to avoid unnecessary read.
* @return void
*/
protected static function apply_competency_rules_from_usercompetency(user_competency $usercompetency,
competency $competency = null) {

// Perform some basic checks.
if (!$usercompetency->get_proficiency()) {
throw new coding_exception('The user competency passed is not completed.');
}
if ($competency === null) {
$competency = $usercompetency->get_competency();
}
if ($competency->get_id() != $usercompetency->get_competencyid()) {
throw new coding_exception('Mismatch between user competency and competency.');
}

// Fetch the parent.
$parent = $competency->get_parent();
if ($parent === null) {
return;
}

// The parent should have a rule, and a meaningful outcome.
$ruleoutcome = $parent->get_ruleoutcome();
if ($ruleoutcome == competency::OUTCOME_NONE) {
return;
}
$rule = $parent->get_rule_object();
if ($rule === null) {
return;
}

// Fetch or create the user competency for the parent.
$userid = $usercompetency->get_userid();
$parentuc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $parent->get_id()));
if (!$parentuc) {
$parentuc = user_competency::create_relation($userid, $parent->get_id());
$parentuc->create();
}

// Does the rule match?
if (!$rule->matches($parentuc)) {
return;
}

// Figuring out what to do.
$recommend = false;
if ($ruleoutcome == competency::OUTCOME_EVIDENCE) {
$action = evidence::ACTION_LOG;

} else if ($ruleoutcome == competency::OUTCOME_RECOMMEND) {
$action = evidence::ACTION_LOG;
$recommend = true;

} else if ($ruleoutcome == competency::OUTCOME_COMPLETE) {
$action = evidence::ACTION_COMPLETE;

} else {
throw new moodle_exception('Unexpected rule outcome: ' + $ruleoutcome);
}

// Finally add an evidence.
static::add_evidence(
$userid,
$parent,
$parent->get_context()->id,
$action,
'evidence_competencyrule',
'tool_lp',
null,
$recommend
);
}

/**
* Observe when a course is marked as completed.
*
Expand Down
8 changes: 4 additions & 4 deletions admin/tool/lp/classes/competency.php
Expand Up @@ -302,7 +302,7 @@ public function get_related_competencies() {
public function get_rule_object() {
$rule = $this->get_ruletype();

if (!$rule || !is_subclass_of($rule, '\tool_lp\competency_rule')) {
if (!$rule || !is_subclass_of($rule, 'tool_lp\\competency_rule')) {
// Double check that the rule is extending the right class to avoid bad surprises.
return null;
}
Expand Down Expand Up @@ -688,10 +688,10 @@ public static function share_same_framework(array $ids) {
* @return array Keys are the class names, values is an object containing name and amd.
*/
public static function get_available_rules() {
// Fully qualified class names withough leading slashes because get_class() does not add them either.
// Fully qualified class names without leading slashes because get_class() does not add them either.
$rules = array(
'tool_lp\competency_rule_all' => (object) array(),
'tool_lp\competency_rule_points' => (object) array(),
'tool_lp\\competency_rule_all' => (object) array(),
'tool_lp\\competency_rule_points' => (object) array(),
);
foreach ($rules as $class => $rule) {
$rule->name = $class::get_name();
Expand Down
2 changes: 1 addition & 1 deletion admin/tool/lp/classes/competency_rule_all.php
Expand Up @@ -47,7 +47,7 @@ public function matches(user_competency $usercompetency) {
global $DB;

// TODO Improve performance here, perhaps the caller could already provide records.
$children = competency::get_records(array('parentid' => $usercompetency->get_competencyid()));
$children = competency::get_records(array('parentid' => $this->competency->get_id()));

if (empty($children)) {
// Leaves are not compatible with this rule.
Expand Down
1 change: 1 addition & 0 deletions admin/tool/lp/lang/en/tool_lp.php
Expand Up @@ -85,6 +85,7 @@
$string['editthisplan'] = 'Edit this learning plan';
$string['editthisuserevidence'] = 'Edit this evidence';
$string['edituserevidence'] = 'Edit evidence';
$string['evidence_competencyrule'] = 'The rule of the competency was met.';
$string['evidence_coursecompleted'] = 'The course \'{$a}\' was completed.';
$string['evidence_evidenceofpriorlearninglinked'] = 'The evidence of prior learning \'{$a}\' was linked.';
$string['evidence_evidenceofpriorlearningunlinked'] = 'The evidence of prior learning \'{$a}\' was unlinked.';
Expand Down
100 changes: 100 additions & 0 deletions admin/tool/lp/tests/api_test.php
Expand Up @@ -26,6 +26,7 @@
global $CFG;

use tool_lp\api;
use tool_lp\evidence;
use tool_lp\user_competency;

/**
Expand Down Expand Up @@ -1425,6 +1426,105 @@ public function test_add_evidence_no_existing_user_competency() {
$this->assertEquals(0, $uc->get_proficiency());
}

public function test_add_evidence_applies_competency_rules() {
$this->resetAfterTest(true);
$dg = $this->getDataGenerator();
$lpg = $dg->get_plugin_generator('tool_lp');
$syscontext = context_system::instance();
$ctxid = $syscontext->id;

$u1 = $dg->create_user();

// Setting up the framework.
$f1 = $lpg->create_framework();
$c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id()));
$c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id(), 'parentid' => $c1->get_id()));
$c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id(), 'parentid' => $c1->get_id()));
$c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id()));
$c2a = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id(), 'parentid' => $c2->get_id()));
$c3 = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id()));
$c3a = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id(), 'parentid' => $c3->get_id()));
$c4 = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id()));
$c4a = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id(), 'parentid' => $c4->get_id()));
$c5 = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id()));

// Setting up the rules.
$c1->set_ruletype('tool_lp\\competency_rule_all');
$c1->set_ruleoutcome(\tool_lp\competency::OUTCOME_COMPLETE);
$c1->update();
$c2->set_ruletype('tool_lp\\competency_rule_all');
$c2->set_ruleoutcome(\tool_lp\competency::OUTCOME_RECOMMEND);
$c2->update();
$c3->set_ruletype('tool_lp\\competency_rule_all');
$c3->set_ruleoutcome(\tool_lp\competency::OUTCOME_EVIDENCE);
$c3->update();
$c4->set_ruletype('tool_lp\\competency_rule_all');
$c4->set_ruleoutcome(\tool_lp\competency::OUTCOME_NONE);
$c4->update();

// Confirm the current data.
$this->assertEquals(0, user_competency::count_records());
$this->assertEquals(0, evidence::count_records());

// Let's do this!
// First let's confirm that evidence not marking a completion have no impact.
api::add_evidence($u1->id, $c1a, $ctxid, evidence::ACTION_LOG, 'commentincontext', 'core');
$uc1a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1a->get_id()));
$this->assertSame(null, $uc1a->get_proficiency());
$this->assertFalse(user_competency::record_exists_select('userid = ? AND competencyid = ?', array($u1->id, $c1->get_id())));

api::add_evidence($u1->id, $c2a, $ctxid, evidence::ACTION_SUGGEST, 'commentincontext', 'core', null, false, null, 1);
$uc2a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2a->get_id()));
$this->assertSame(null, $uc2a->get_proficiency());
$this->assertFalse(user_competency::record_exists_select('userid = ? AND competencyid = ?', array($u1->id, $c2->get_id())));

// Now let's try complete a competency but the rule won't match (not all children are complete).
// The parent (the thing with the rule) will be created but won't have any evidence attached, and not
// not be marked as completed.
api::add_evidence($u1->id, $c1a, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core');
$uc1a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1a->get_id()));
$this->assertEquals(true, $uc1a->get_proficiency());
$uc1 = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get_id()));
$this->assertSame(null, $uc1->get_proficiency());
$this->assertEquals(0, evidence::count_records(array('usercompetencyid' => $uc1->get_id())));

// Now we complete the other child. That will mark the parent as complete with an evidence.
api::add_evidence($u1->id, $c1b, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core');
$uc1b = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1b->get_id()));
$this->assertEquals(true, $uc1a->get_proficiency());
$uc1 = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get_id()));
$this->assertEquals(true, $uc1->get_proficiency());
$this->assertEquals(user_competency::STATUS_IDLE, $uc1->get_status());
$this->assertEquals(1, evidence::count_records(array('usercompetencyid' => $uc1->get_id())));

// Check rule recommending.
api::add_evidence($u1->id, $c2a, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core');
$uc2a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2a->get_id()));
$this->assertEquals(true, $uc1a->get_proficiency());
$uc2 = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2->get_id()));
$this->assertSame(null, $uc2->get_proficiency());
$this->assertEquals(user_competency::STATUS_WAITING_FOR_REVIEW, $uc2->get_status());
$this->assertEquals(1, evidence::count_records(array('usercompetencyid' => $uc2->get_id())));

// Check rule evidence.
api::add_evidence($u1->id, $c3a, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core');
$uc3a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c3a->get_id()));
$this->assertEquals(true, $uc1a->get_proficiency());
$uc3 = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c3->get_id()));
$this->assertSame(null, $uc3->get_proficiency());
$this->assertEquals(user_competency::STATUS_IDLE, $uc3->get_status());
$this->assertEquals(1, evidence::count_records(array('usercompetencyid' => $uc3->get_id())));

// Check rule nothing.
api::add_evidence($u1->id, $c4a, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core');
$uc4a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c4a->get_id()));
$this->assertEquals(true, $uc1a->get_proficiency());
$this->assertFalse(user_competency::record_exists_select('userid = ? AND competencyid = ?', array($u1->id, $c4->get_id())));

// Check marking on something that has no parent. This just checks that nothing breaks.
api::add_evidence($u1->id, $c5, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core');
}

public function test_observe_course_completed() {
$this->resetAfterTest(true);
$dg = $this->getDataGenerator();
Expand Down

0 comments on commit 9373acf

Please sign in to comment.