Skip to content

Commit

Permalink
Merge branch 'release/3.2.0'
Browse files Browse the repository at this point in the history
pbchase committed Aug 8, 2018
2 parents d14c1b1 + 40df03a commit 671aca3
Showing 10 changed files with 381 additions and 108 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,14 @@
All notable changes to the Form Render Skip Logic module will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [3.2.0] - 2018-08-08
### Added
- Add support for migrating FRSL v2.x.x configurations to FRSL v3.x.x configurations automatically (Dileep Rajput)
- Add support for equations on Advanced control mode (Tiago Bember Simeao)
- Add support to event-relative control fields (Tiago Bember Simeao)
- Add Zenodo DOI to README (Philip Chase)


## [3.1.1] - 2018-06-04
### Changed
- Fixing control field's 2nd column visibility. (Tiago Bember Simeao)
256 changes: 167 additions & 89 deletions ExternalModule.php

Large diffs are not rendered by default.

172 changes: 172 additions & 0 deletions Migration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<?php

namespace FormRenderSkipLogic\Migration;

use ExternalModules\ExternalModules;

class Migration {

private $PREFIX;

function __construct($prefix) {
$this->PREFIX = $prefix;
}

/**
* checks if FRSL settings for specified version exist. Does not support version
* 1. Will return false for any invalid version.
* @param string $version, module version in REDCap format e.g. 'v3.1.1'
* @return boolean, true if they exist, false otherwise
*/
function checkIfVersionSettingsExist($version) {
$module_id = $this->getFRSLModuleId();

$q = "SELECT 1 FROM redcap_external_module_settings WHERE external_module_id='$module_id' AND `key` IN ";

if (preg_match("/v2\.[0-9]+(\.[0-9]+)?/", $version)) {
$q .= "('control_field', 'event_name', 'event_name', 'field_name', 'enabled_before_ctrl_field_is_set', 'target_instruments', 'instrument_name', 'control_field_value')";
} else if (preg_match("/v3\.[0-9]+(\.[0-9]+)?/", $version)) {
$q .= "('control_fields', 'control_mode', 'control_event_id', 'control_field_key', 'control_piping', 'control_default_value', 'branching_logic', 'condition_operator', 'condition_value', 'target_forms', 'target_events_select', 'target_events')";
} else {
return false;
}

$result = ExternalModules::query($q);

//if we got something return true, otherwise false
$settings_exist = !empty($result->fetch_assoc());

return $settings_exist;
}

/**
* gets external module_id for FRSL.
* cannot use ExternalModules::getIdForPrefix() because it is private.
*/
function getFRSLModuleId() {
$q = "SELECT external_module_id FROM redcap_external_modules where directory_prefix = '" . $this->PREFIX . "'" ;
$result = ExternalModules::query($q);
$id = $result->fetch_assoc()['external_module_id'];

return $id;
}

/**
* gets FRSLv2.x.x settings for each as an associative array indexed by
* project_id, where each project setting is an associative array indexed by
* setting name and maps to a setting value
* @return array $settings
*/
function getV2Settings() {
$module_id = $this->getFRSLModuleId();

//get old settings data
$q = "SELECT project_id, `key`, value FROM redcap_external_module_settings WHERE external_module_id='$module_id' AND `key` IN ('control_field', 'event_name', 'event_name', 'field_name', 'enabled_before_ctrl_field_is_set', 'target_instruments', 'instrument_name', 'control_field_value')";
$result = ExternalModules::query($q);

//create data stucture to represent old settings data
$settings = [];
while($row = $result->fetch_assoc()) {
$project_id = $row["project_id"];
$key = $row["key"];
$value = $row["value"];
$settings[$project_id][$key] = $value;
}

return $settings;
}

/**
* converts settings from from FRSL_v2.X to FRSL_v3.X
* @param array $old_settings, array of v2 settings indexed by project_id
* @return array $new_settings, array of v3 settings indexed by project_id
*/
function convertV2SettingsToV3Settings($old_settings) {
$new_settings = [];
foreach ($old_settings as $project_id => $old_setting) {

//generate branching_logic settings using old values
$old_instrument_names = json_decode($old_setting["instrument_name"]);
$old_instrument_count = count($old_instrument_names);
$old_control_field_values = json_decode($old_setting["control_field_value"]);
$branching_logic = [];
$condition_operator = [];
$condition_value = [];
$target_forms = [];
$target_events_select = [];
$target_events = [];

for($i = 0; $i < $old_instrument_count; $i++) {
//check if a branching_logic setting already exists for this instrument
$index = array_search($old_control_field_values[$i], $condition_value);

if($index !== false) {
//append instrument to existing branching_logic configuration
$target_forms[$index][] = $old_instrument_names[$i];
} else {
//create a new branching_logic sub setting
$branching_logic[] = true;
$condition_operator[] = null;
$condition_value[] = $old_control_field_values[$i];
$target_forms[] = [$old_instrument_names[$i]];
$target_events_select[] = false;
$target_events[] = [null];
}
}

//convert to nested JSON-arrays for storage
$branching_logic = "[" . json_encode($branching_logic) . "]";
$condition_operator = "[" . json_encode($condition_operator) . "]";
$condition_value = "[" . json_encode($condition_value) . "]";
$target_forms = "[" . json_encode($target_forms) . "]";
$target_events_select = "[" . json_encode($target_events_select) . "]";
$target_events = "[" . json_encode($target_events) . "]";

//create sub-structure for project setting
$setting = [];

$setting["control_fields"] = $old_setting["control_field"];
$setting["control_mode"] = '["default"]';
$setting["control_event_id"] = $old_setting["event_name"];
$setting["control_field_key"] = $old_setting["field_name"];
$setting["control_piping"] = "[null]";
$setting["control_default_value"] = "[null]";
$setting["branching_logic"] = $branching_logic;
$setting["condition_value"] = $condition_value;
$setting["condition_operator"] = $condition_operator;
$setting["target_forms"] = $target_forms;
$setting["target_events_select"] = $target_events_select;
$setting["target_events"] = $target_events;

//store in main data structure
$new_settings[$project_id] = $setting;
}

return $new_settings;

}

/**
* stores FRSL_v3.X created by convert2XSettingsTo3XSettings method into db
* @param array $settings, array of v3 settings indexed by project_id and
* maps to a array of setting keys pointing to their associated values.
*/
function storeV3Settings($settings) {
$module_id = $this->getFRSLModuleId();

foreach ($settings as $project_id => $setting) {
$q = "INSERT INTO redcap_external_module_settings (external_module_id, project_id, `key`, type, value) VALUES ";

//build query
$values_to_insert = [];
foreach ($setting as $key => $value) {
$values_to_insert[] = "($module_id, $project_id, '$key', 'json-array', '$value')";
}

$q .= join(",", $values_to_insert);
ExternalModules::query($q);
}
}
}

?>
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# REDCap Form Render Skip Logic (FRSL)

[![DOI](https://zenodo.org/badge/97271445.svg)](https://zenodo.org/badge/latestdoi/97271445)

This REDCap module hides and shows instruments based on the values of REDCap form fields i.e. a branching logic for instruments.

## Motivation
@@ -24,9 +26,18 @@ See the original functional specification at [https://docs.google.com/document/d
## Configuration
Access **Manage External Modules** section of your project, click on Form Render Skip Logic's configure button, and save settings in order to show or hide instruments according to your needs.

The top level entry in the configuration is a Control Field. A control field is described by either an event name and a field name _or_ a smart variable _or_ piped data. Each control field can govern the display of a set of forms. You can define multiple control fields as long as each controls a separate set of forms.
The top level entry in the configuration is a Control Field. A control field is described by either:

1. a selected event-field pair _or_
2. a text that describes an equation, working as a [calculated field](https://www.ctsi.ufl.edu/files/2017/06/Calculated-Fields-%E2%80%93-REDCap-How.pdf) (you may use Piping and smart variables)

![advanced control field](img/advanced_control_field.png)

Each control field can have multiple conditions. Each condition compares the control field to a string or number. If the condition evaluates as true, the forms listed under the condition will be displayed. If the condition is false and no other true condition displays them, the forms will be hidden. Be careful that the values in the conditions of a control field are mutually exclusive or the results could be unexpected.
Each control field can govern the display of a set of forms. You can define multiple control fields as long as each controls a separate set of forms.

Each control field can have multiple conditions. Each condition compares the control field to a string or number. If the condition evaluates as true, the forms listed under the condition will be displayed. If the condition is false and no other true condition displays them, the forms will be hidden.

Obs.: if multiple conditions are applied to the same instrument, the form is displayed if *at least* one of the conditions is met.

All forms _not_ named under a condition will be displayed at all times. Optionally, each condition can specify a list of events that restrict the behavior of this rule.

@@ -40,10 +51,4 @@ See [Animal Identification Example](samples/Animal_Identification.md) for a work

## Upgrading From Version 2.x - 3.x

Note that version 3.0.0 introduced a breaking change in the configuration. To execute the upgrade you will need to follow these steps:

* Note the projects that use FRSL
* Record the configuration of each project
* Erase the configuration of each project
* Upgrade FRSL to 3.x
* Re-enter the configuration for each project
Note that version 3.0.0 introduced a breaking change in the configuration. When you upgrade to version 3.x all of your old configurations in 2.x will be converted into the 3.x configuration scheme. This migration only occurs the first time you upgrade from 2.x to 3.x . Thereafter, if you decided to switch back and forth between the two versions, your configurations will not transfer. This is to ensure that all of your old 2.x configurations will still be available to you if you decide to go back to version 2.x .
19 changes: 11 additions & 8 deletions config.json
Original file line number Diff line number Diff line change
@@ -3,10 +3,13 @@
"namespace": "FormRenderSkipLogic\\ExternalModule",
"description": "This module hides and shows instruments based on the values of REDCap form fields. Multiple control fields can be defined to control the display of non-overlapping sets of forms.",
"permissions": [
"redcap_every_page_before_render",
"redcap_every_page_top",
"redcap_data_entry_form_top",
"redcap_survey_page_top",
"redcap_save_record"
"redcap_save_record",
"redcap_module_system_change_version",
"redcap_module_system_enable"
],
"authors": [
{
@@ -63,24 +66,24 @@
},
{
"value": "advanced",
"name": "Advanced (Piping)"
"name": "Advanced (Equation with Piping and/or Smart Variables)"
}
]
},
{
"key": "control_event_id",
"key": "control_field_key",
"name": "Field",
"type": "event-list"
"type": "field-list"
},
{
"key": "control_field_key",
"key": "control_event_id",
"name": "Field",
"type": "field-list"
"type": "event-list"
},
{
"key": "control_piping",
"name": "Piping",
"type": "text"
"name": "Equation / Piping",
"type": "textarea"
},
{
"key": "control_default_value",
2 changes: 1 addition & 1 deletion css/config.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#external-modules-configure-modal.frsl tr[field="control_field_key"],
#external-modules-configure-modal.frsl tr[field="control_event_id"],
#external-modules-configure-modal.frsl tr[field="condition_value"] {
position: absolute;
right: 10000px;
7 changes: 7 additions & 0 deletions css/piping-helper.css
Original file line number Diff line number Diff line change
@@ -15,3 +15,10 @@
top: -1px;
margin-right: 2px;
}

.frsl-piping-helper a {
font-weight: normal;
color: #3089d4;
font-size: 11px;
margin: 10px 0 0 10px;
}
Binary file added img/advanced_control_field.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified img/configuration_form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion js/config.js
Original file line number Diff line number Diff line change
@@ -66,7 +66,7 @@ $(document).ready(function() {
$(selectorShow).parent().parent().show();
$(selectorHide).parent().parent().hide();

place2ndColumnField($('[name="control_field_key' + suffix + '"]').parent().parent());
place2ndColumnField($('[name="control_event_id' + suffix + '"]').parent().parent());
};

var $checkboxes = $modal.find('tr[field="target_events_select"] .external-modules-input-element');

0 comments on commit 671aca3

Please sign in to comment.