From 5165a39a25f9137f949dd110bab99630a7b53931 Mon Sep 17 00:00:00 2001 From: Victoria Foing Date: Thu, 19 Jul 2018 18:21:46 -0400 Subject: [PATCH] Create module Battery Manager made sure everything is functional minor format change fixed error in code reverted Modal form to original state fixed user perm rel file --- SQL/0000-00-02-Modules.sql | 1 + ...2018-07-23-battery_manager_permissions.sql | 27 + jsx/FilterableDataTable.js | 1 + jsx/Form.js | 2 +- modules/battery_manager/.gitignore | 1 + modules/battery_manager/README.md | 27 + .../battery_manager/help/battery_manager.md | 116 ++++ .../battery_manager/jsx/batteryManagerForm.js | 143 +++++ .../jsx/batteryManagerIndex.js | 496 ++++++++++++++++++ .../php/battery_manager.class.inc | 91 ++++ modules/battery_manager/php/module.class.inc | 66 +++ modules/battery_manager/php/test.class.inc | 84 +++ .../php/testendpoint.class.inc | 262 +++++++++ .../php/testoptionsendpoint.class.inc | 102 ++++ .../php/testprovisioner.class.inc | 70 +++ .../test/BatteryManagerTest.php | 64 +++ modules/battery_manager/test/TestPlan.md | 104 ++++ raisinbread/RB_files/RB_modules.sql | 1 + raisinbread/RB_files/RB_permissions.sql | 2 + raisinbread/RB_files/RB_user_perm_rel.sql | 2 + webpack.config.js | 1 + 21 files changed, 1662 insertions(+), 1 deletion(-) create mode 100644 SQL/New_patches/2018-07-23-battery_manager_permissions.sql create mode 100644 modules/battery_manager/.gitignore create mode 100644 modules/battery_manager/README.md create mode 100644 modules/battery_manager/help/battery_manager.md create mode 100644 modules/battery_manager/jsx/batteryManagerForm.js create mode 100644 modules/battery_manager/jsx/batteryManagerIndex.js create mode 100644 modules/battery_manager/php/battery_manager.class.inc create mode 100644 modules/battery_manager/php/module.class.inc create mode 100644 modules/battery_manager/php/test.class.inc create mode 100644 modules/battery_manager/php/testendpoint.class.inc create mode 100644 modules/battery_manager/php/testoptionsendpoint.class.inc create mode 100644 modules/battery_manager/php/testprovisioner.class.inc create mode 100644 modules/battery_manager/test/BatteryManagerTest.php create mode 100644 modules/battery_manager/test/TestPlan.md diff --git a/SQL/0000-00-02-Modules.sql b/SQL/0000-00-02-Modules.sql index ebe02de10a7..3cdf4ba0883 100644 --- a/SQL/0000-00-02-Modules.sql +++ b/SQL/0000-00-02-Modules.sql @@ -8,6 +8,7 @@ CREATE TABLE `modules` ( INSERT INTO modules (Name, Active) VALUES ('acknowledgements', 'Y'); INSERT INTO modules (Name, Active) VALUES ('api', 'Y'); +INSERT INTO modules (Name, Active) VALUES ('battery_manager', 'Y'); INSERT INTO modules (Name, Active) VALUES ('behavioural_qc', 'Y'); INSERT INTO modules (Name, Active) VALUES ('brainbrowser', 'Y'); INSERT INTO modules (Name, Active) VALUES ('bvl_feedback', 'Y'); diff --git a/SQL/New_patches/2018-07-23-battery_manager_permissions.sql b/SQL/New_patches/2018-07-23-battery_manager_permissions.sql new file mode 100644 index 00000000000..25cbfdb1869 --- /dev/null +++ b/SQL/New_patches/2018-07-23-battery_manager_permissions.sql @@ -0,0 +1,27 @@ +-- Add view permission for battery manager +INSERT INTO permissions (code, description, categoryID) + VALUES ( + 'battery_manager_view', + 'View Battery Manager', + (SELECT ID FROM permissions_category WHERE Description = 'Permission') + ); + +-- Add edit permission for battery manager +INSERT INTO permissions (code, description, categoryID) + VALUES ( + 'battery_manager_edit', + 'Add, activate, and deactivate entries in Test Battery', + (SELECT ID FROM permissions_category WHERE Description = 'Permission') + ); + +-- Give view permission to admin +INSERT INTO user_perm_rel (userID, permID) +SELECT ID, permID FROM users u JOIN permissions p +WHERE UserID='admin' AND code = 'battery_manager_view'; + +-- Give edit permission to admin +INSERT INTO user_perm_rel (userID, permID) +SELECT ID, permID FROM users u JOIN permissions p +WHERE UserID='admin' AND code = 'battery_manager_edit'; + +INSERT INTO modules (Name, Active) VALUES ('battery_manager', 'Y'); diff --git a/jsx/FilterableDataTable.js b/jsx/FilterableDataTable.js index 7804244b33c..e5484cf0329 100644 --- a/jsx/FilterableDataTable.js +++ b/jsx/FilterableDataTable.js @@ -111,6 +111,7 @@ FilterableDataTable.propTypes = { name: PropTypes.string.isRequired, title: PropTypes.string, data: PropTypes.object.isRequired, + filterPresets: PropTypes.object, fields: PropTypes.object.isRequired, columns: PropTypes.number, getFormattedCell: PropTypes.func, diff --git a/jsx/Form.js b/jsx/Form.js index 441b25fd43f..d67d464bc7f 100644 --- a/jsx/Form.js +++ b/jsx/Form.js @@ -1225,7 +1225,7 @@ class NumericElement extends Component { id={this.props.id} min={this.props.min} max={this.props.max} - value={this.props.value} + value={this.props.value || ''} disabled={disabled} required={required} onChange={this.handleChange} diff --git a/modules/battery_manager/.gitignore b/modules/battery_manager/.gitignore new file mode 100644 index 00000000000..235c1debb3b --- /dev/null +++ b/modules/battery_manager/.gitignore @@ -0,0 +1 @@ +js/batteryManagerIndex.js \ No newline at end of file diff --git a/modules/battery_manager/README.md b/modules/battery_manager/README.md new file mode 100644 index 00000000000..87c7e8c49b7 --- /dev/null +++ b/modules/battery_manager/README.md @@ -0,0 +1,27 @@ +# Battery Manager + +## Purpose +The Battery Manager module allows users to **browse**, **add**, +**edit**, **activate** and **deactivate** entries in the Test Battery. The +Test Battery is used to determine which Instruments are administered at +different timepoints. + +## Intended Users +The Battery Manager module is used by study +administrators. + +## Scope +The Battery Manager module provides a tool for browsing, adding, +editing, activating, and deactivating entries in the the Test Battery. + +#### Interactions with LORIS +Changes, additions and deletion of data in this module affects the test +battery assigned to a candidate at each timepoint + +## Permissions +In order to use the Battery Manager module the user needs +one or both of the following permissions: +- `battery_manager_view`: gives user read-only access to Battery Manager +module (browsing the Test Battery). +- `battery_manager_edit`: gives user edit access to Battery +Manager module (add/edit/activate/deactivate entries in Test Battery). diff --git a/modules/battery_manager/help/battery_manager.md b/modules/battery_manager/help/battery_manager.md new file mode 100644 index 00000000000..c549ed19d17 --- /dev/null +++ b/modules/battery_manager/help/battery_manager.md @@ -0,0 +1,116 @@ +# Battery Manager +The Battery Manager module serves as a front-end for manipulating the Test Battery. +This includes browsing, adding, editing, activating, and deactivating entries. + +## Searching for an entry in the Test Battery +Under the `Browse` tab, use the `Selection Filters` to search for entries by fields such as: +`Instrument`, +`Minimum age (days)`, +`Maximum age (days)`, +`Stage`, +`Subproject`, +`Visit Label`, +`Site`, +`First Visit`, +`Instrument Order`, +`Active`. +As filters are selected, the data table below will dynamically update with relevant results. +Click the **Clear Filters** button to reset all filters. + +Within the data table, results can be sorted in ascending or descending order by +clicking on any column header. + +## Adding an entry to the Test Battery +Under the `Add` tab, you can add a new entry to the Test Battery. +You can specify information about the entry by using the searchable dropdowns, dropdown menus, and numeric text fields. +You will have to fill out the required fields `Instrument`, `Minimum age (days)`, `Maximum age (days)`, and `Stage`. +Finally, press the **Add entry** button to add the entry to the Test Battery. +You cannot add an entry if it has a duplicate entry in the Test Battery. + +*For more information on the behaviour of each parameter refer to the [Behaviour of Parameters](#behaviour-of-parameters) section of this document* + +## Editing an entry to the Test Battery +Under the `Browse` tab, you can edit an entry by clicking on the `Edit` link in the `Edit Metadata` column of the Menu Table. +The link will display a form that is populated with the values of the entry. +You can update information in the form by selecting from the dropdown menus and filling in the numeric text fields. +You will have to fill out the required fields `Instrument`, `Minimum age (days)`, `Maximum age (days)`, `Stage`, and `Active`. +Finally, press the **Edit entry** button to edit the entry in the Test Battery. +You cannot edit an entry if you make no changes in the form. +You cannot edit an entry if it becomes the same as another active entry in the Test Battery. + +*For more information on the behaviour of each parameter refer to the [Behaviour of Parameters](#behaviour-of-parameters) section of this document* + +## Activating/Deactivating an entry in the Test Battery + +### Browse tab (activate/deactivate) +In the `Change Status` column of the Menu Table, you press the **Activate** or +**Deactivate** button to directly change the status of an entry. + +### Add tab (activate) +Under the `Add` tab, you can add an entry that already exists in the Test +Battery but has been deactivated. A pop up will appear that will give you +the option to activate the existing entry. + +### Edit window (activate/deactivate) +Select an entry in the Menu table and click on `Edit`. +In the `Edit` window, edit an entry and make sure the new entry has no duplicate in the Test Battery. +This will add the new entry to the table and deactivate the original one. +Alternatively, edit an entry so that it becomes the same as another deactivated entry in the Test Battery. +A pop up will appear that will give you the option to activate the other entry and deactivate the original one. + +## Behaviour of Parameters + +### Subproject: + - If the test battery entry does NOT have a `subprojectID` + (`subprojectID=NULL`), the instrument gets administered to ALL subprojects. + - If the test battery entry has a `subprojectID` set and the `subprojectID` + matches the one of the timepoint, the instrument is administered only to + that subproject. + +### Stage: + - If the test battery entry has a `stage` set and the `stage` matches the + one of the timepoint, the instrument is administered at that stage. + +### Center: + - If the test battery entry does NOT have a `CenterID` (`CenterID=NULL`), + the instrument gets administered at ALL centers. + - If the test battery entry has a `CenterID` set and the `CenterID` matches + the one of the timepoint, the instrument is administered at that CenterID. + +### AgeMinDays/AgeMaxDays: + - If the test battery entry has `AgeMinDays` and `AgeMaxDays` set and they + are both set to `0`, the instrument gets administered at ALL ages; + - If the test battery entry has `AgeMinDays` and `AgeMaxDays` set and they + are set to any value other than `0`, the instrument gets administered IF AND + ONLY IF the age of the candidate at the timepoint is between `AgeMinDays` + and `AgeMaxDays`; + +### VisitLabel: + - If the test battery entry does NOT have a `Visit_label` + (`Visit_label=NULL`), the instrument gets administered IF AND ONLY IF no + other test battery entries matches the timepoint's visit label and + subproject. + - If the test battery entry has a `Visit_label` set and the `Visit_label` + matches the one of the timepoint, the instrument is administered at that + Visit. + - **NOTE:** *In order to administer an instrument at all visits without + defining each visit individually in the battery, the test battery table + should NOT contain any entries for the subproject/visit_label combination + of the timepoint. If the timepoint's subproject/visit_label combination + has a specified set of instruments defined in the test_battery, all entries + of the battery with no visit labels (`Visit_label=NULL`) will be ignored.* + + ### FirstVisit: + - If `firstVisit` is set to `Y`, the test battery instance is applied + only if it is the first visit. + - If `firstVisit` is to `N`, the test battery instance is applied only if it + *not* the first visit. + - If `firstVisit` is set to null, the test battery instance is applied to any + visit. + - **NOTE:** *The `firstVisit` flag can allow to bypass all other rules in + some instances. In these instances, the `stage`, `CenterID`, `Visit_label`, + `AgeMinDays` and `AgeMaxDays` will not affect in any way the administration + of the instrument. Only the `subprojectID` value will impact if the + instrument gets administered or not; the `subprojectID` value must match + the timepoint's in order for the instrument to be administered in these + instances.* diff --git a/modules/battery_manager/jsx/batteryManagerForm.js b/modules/battery_manager/jsx/batteryManagerForm.js new file mode 100644 index 00000000000..444821d5f38 --- /dev/null +++ b/modules/battery_manager/jsx/batteryManagerForm.js @@ -0,0 +1,143 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; + +/** + * Battery Manager Form + * + * Module component rendering Add tab + * + * @author Victoria Foing + * + */ +class BatteryManagerForm extends Component { + /** + * Render function + * + * @return {*} + */ + render() { + const {test, options, setTest, add} = this.props; + + // Inform users about duplicate entries + const renderHelpText = () => { + if (add) { + return ( + + You cannot add an entry if it has a duplicate entry in the test + battery.
+ If the duplicate entry is inactive, you will be given the option + to activate it. +
+ ); + } else { + return ( + + Editing an entry will deactivate the current entry and create a + new entry.
+ You cannot edit an entry to have the same values as another active + entry.
+ If the duplicate entry is inactive, you will be given the option + to active it. +
+
+
+ ); + } + }; + + return ( + + + + + + + + + + + + + ); + } +} + +BatteryManagerForm.propTypes = { + test: PropTypes.object.isRequired, + setTest: PropTypes.func.isRequired, + options: PropTypes.object.isRequired, +}; + +export default BatteryManagerForm; diff --git a/modules/battery_manager/jsx/batteryManagerIndex.js b/modules/battery_manager/jsx/batteryManagerIndex.js new file mode 100644 index 00000000000..90b137aac49 --- /dev/null +++ b/modules/battery_manager/jsx/batteryManagerIndex.js @@ -0,0 +1,496 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; + +import Loader from 'Loader'; +import FilterableDataTable from 'FilterableDataTable'; +import Modal from 'Modal'; +import swal from 'sweetalert2'; + +import BatteryManagerForm from './batteryManagerForm'; + +/** + * Battery Manager + * + * Main module component rendering tab pane with Browse and Add tabs + * + * @author Victoria Foing + * @author Henri Rabalais + */ +class BatteryManagerIndex extends Component { + /** + * Constructor + * + * @param {object} props + */ + constructor(props) { + super(props); + + this.state = { + tests: {}, + test: {}, + show: {testForm: false, editForm: false}, + error: false, + isLoaded: false, + }; + + this.fetchData = this.fetchData.bind(this); + this.postData = this.postData.bind(this); + this.formatColumn = this.formatColumn.bind(this); + this.addTest = this.addTest.bind(this); + this.setTest = this.setTest.bind(this); + this.clearTest = this.clearTest.bind(this); + this.closeForm = this.closeForm.bind(this); + this.activateTest = this.activateTest.bind(this); + this.deactivateTest = this.deactivateTest.bind(this); + this.updateTest = this.updateTest.bind(this); + } + + /** + * Component did mount lifecycle method. + */ + componentDidMount() { + this.fetchData(this.props.testEndpoint, 'GET', 'tests') + .then(() => this.fetchData(this.props.optionEndpoint, 'GET', 'options')) + .then(() => this.setState({isLoaded: true})); + } + + /** + * Retrieve data from the provided URL and store it in state + * + * @param {string} url + * @param {string} method + * @param {string} state + * + * @return {object} promise + * + * XXX: This should eventually be moved to a library function + */ + fetchData(url, method, state) { + return new Promise((resolve, reject) => { + return fetch(url, {credentials: 'same-origin', method: method}) + .then((resp) => resp.json()) + .then((data) => this.setState({[state]: data}, resolve())) + .catch((error) => { + this.setState({error: true}, reject()); + console.error(error); + }); + }); + } + + /** + * Posts data from the provided URL to the server. + * + * @param {string} url + * @param {object} data + * @param {string} method + * + * @return {object} promise + * + * XXX: This should eventually be moved to a library function + */ + postData(url, data, method) { + return new Promise((resolve, reject) => { + const dataClone = JSON.parse(JSON.stringify(data)); + return fetch(url, { + credentials: 'same-origin', + method: method, + body: JSON.stringify(dataClone), + }) + .then((response) => response.text() + .then((body) => { + body = JSON.parse(body); + const status = response.ok ? 'success' : 'error'; + const message = response.ok ? body.message : body.error; + swal.fire(message, '', status); + response.ok ? resolve() : reject(); + }) + .catch(() => reject())); + }); + } + + /** + * Modify behaviour of specified column cells in the Data Table component + * + * @param {string} column - column name + * @param {string} cell - cell content + * @param {object} row - row content indexed by column + * + * @return {*} a formated table cell for a given column + */ + formatColumn(column, cell, row) { + let result = {cell}; + const testId = row['ID']; + switch (column) { + case 'Instrument': + result = {this.state.options.instruments[cell]}; + break; + case 'Subproject': + result = {this.state.options.subprojects[cell]}; + break; + case 'Site': + result = {this.state.options.sites[cell]}; + break; + case 'Change Status': + if (row.Active === 'Y') { + // Pass ID of row to deactivate function + result = { + this.deactivateTest(testId); + }}/>; + } else if (row.Active === 'N') { + // Pass ID of row to activate function + result = { + this.activateTest(testId); + }}/>; + } + break; + case 'Edit Metadata': + const editButton = { + this.loadTest(testId); + this.show('editForm'); + }}/>; + result = {editButton}; + break; + } + + return result; + } + + /** + * Show a given form based on the passed 'state' + * + * @param {string} item - the item to be shown + */ + show(item) { + let show = this.state.show; + show[item] = true; + this.setState({show}); + } + + /** + * Hige a given form based on the passed 'state' + * + * @param {string} item - the item to be hidden + */ + hide(item) { + let show = this.state.show; + show[item] = false; + this.setState({show}); + } + + /** + * Set the form data based on state values of child elements/components + * + * @param {string} name - name of the selected element + * @param {string} value - selected value for corresponding form element + */ + setTest(name, value) { + const test = this.state.test; + test[name] = value; + this.setState({test}); + } + + /** + * Loads a test into the current state based on the testId + * + * @param {string} testId + */ + loadTest(testId) { + const test = JSON.parse(JSON.stringify(this.state.tests + .find((test) => test.id === testId))); + this.setState({test}); + } + + /** + * Clear the state of the current test + */ + clearTest() { + const test = {}; + this.setState({test}); + } + + /** + * Close the Form + */ + closeForm() { + this.hide('testForm'); + this.hide('editForm'); + this.clearTest(); + } + + /** + * Display popup so user can confirm activation of row + * Refresh page if entry in Test Battery is successfully activated + * + * @param {int} testId + * + * @return {object} + */ + activateTest(testId) { + return new Promise((resolve, reject) => { + const test = this.state.tests.find((test) => test.id === testId); + test.active = 'Y'; + this.updateTest(test) + .then(() => resolve()) + .then(() => reject()); + }); + } + + /** + * Display popup so user can confirm deactivation of row + * Refresh page if entry in Test Battery is successfully deactivated + * + * @param {int} testId + * + * @return {object} + */ + deactivateTest(testId) { + return new Promise((resolve, reject) => { + const test = this.state.tests.find((test) => test.id === testId); + test.active = 'N'; + this.updateTest(test) + .then(() => resolve()) + .catch(() => reject()); + }); + } + + /** + * Updates a previously existing Test with an updated Test. + * + * @param {object} test + * + * @return {object} promise + */ + updateTest(test) { + return new Promise((resolve, reject) => { + this.postData(this.props.testEndpoint+test.id, test, 'PATCH') + .then(() => { + const index = this.state.tests + .findIndex((element) => element.id === test.id); + const tests = this.state.tests; + tests[index] = test; + this.setState({tests}, resolve()); + }) + .catch(() => reject()); + }); + } + + /** + * save test to database + * + * @return {object} promise + */ + addTest() { + return new Promise((resolve, reject) => { + const test = this.state.test; + this.checkDuplicate(test) + .then(() => this.postData(this.props.testEndpoint, test, 'POST')) + .then(() => this.fetchData(this.props.testEndpoint, 'GET', 'tests')) + .then(() => test.id && this.deactivateTest(test.id)) + .then(() => this.closeForm()) + .then(() => resolve()) + .catch(() => reject()); + }); + } + + /** + * Checks whether the Test is a duplicate of an existing Test. + * + * @param {object} test + * + * @return {object} promise + */ + checkDuplicate(test) { + return new Promise((resolve, reject) => { + let duplicate; + this.state.tests.forEach((testCheck) => { + if ( + test.testName === testCheck.testName && + test.ageMinDays === testCheck.ageMinDays && + test.ageMaxDays === testCheck.ageMaxDays && + test.stage === testCheck.stage && + test.subproject === testCheck.subproject && + test.visitLabel === testCheck.visitLabel && + test.centerId === testCheck.centerId && + test.firstVisit === testCheck.firstVisit && + test.instrumentOrder === testCheck.instrumentOrder + ) { + duplicate = testCheck; + } + }); + + if (duplicate) { + if (duplicate.active === 'N') { + swal.fire({ + title: 'Test Duplicate', + text: 'Would you to like activate this test?', + type: 'warning', + confirmButtonText: 'Activate', + showCancelButton: true, + }).then((result) => { + if (result.value) { + this.activateTest(duplicate.id); + if (test.id && (test.id !== duplicate.id)) { + this.deactivateTest(test.id); + } + this.closeForm(); + } + }); + } else if (duplicate.active === 'Y') { + swal.fire( + 'Test Duplicate', 'You cannot duplicate an active test', 'error' + ); + } + reject(); + } else { + resolve(); + } + }); + } + + /** + * Render Method + * + * @return {*} + */ + render() { + // If error occurs, return a message. + // XXX: Replace this with a UI component for 500 errors. + if (this.state.error) { + return

An error occured while loading the page.

; + } + + // Waiting for async data to load + if (!this.state.isLoaded) { + return ; + } + + /** + * XXX: Currently, the order of these fields MUST match the order of the + * queried columns in _setupVariables() in batter_manager.class.inc + */ + const {options, test, tests, show} = this.state; + const {hasPermission} = this.props; + const fields = [ + {label: 'ID', show: false}, + {label: 'Instrument', show: true, filter: { + name: 'testName', + type: 'select', + options: options.instruments, + }}, + {label: 'Minimum Age', show: true, filter: { + name: 'minimumAge', + type: 'text', + }}, + {label: 'Maximum Age', show: true, filter: { + name: 'maximumAge', + type: 'text', + }}, + {label: 'Stage', show: true, filter: { + name: 'stage', + type: 'select', + options: options.stages, + }}, + {label: 'Subproject', show: true, filter: { + name: 'subproject', + type: 'select', + options: options.subprojects, + }}, + {label: 'Visit Label', show: true, filter: { + name: 'visitLabel', + type: 'select', + options: options.visits, + }}, + {label: 'Site', show: true, filter: { + name: 'site', + type: 'select', + options: options.sites, + }}, + {label: 'First Visit', show: true, filter: { + name: 'firstVisit', + type: 'select', + options: options.firstVisit, + }}, + {label: 'Instrument Order', show: true, filter: { + name: 'instrumentOrder', + type: 'text', + }}, + {label: 'Active', show: true, filter: { + name: 'active', + type: 'select', + options: options.active, + }}, + {label: 'Change Status', show: hasPermission('batter_manager_edit')}, + {label: 'Edit Metadata', show: hasPermission('batter_manager_edit')}, + ]; + + const testForm = ( + + + + ); + + const actions = [ + { + label: 'New Test', + action: () => this.show('testForm'), + show: hasPermission('battery_manager_edit'), + }, + ]; + + const testsArray = tests.map((test) => { + return [ + test.id, + test.testName, + test.ageMinDays, + test.ageMaxDays, + test.stage, + test.subproject, + test.visitLabel, + test.centerId, + test.firstVisit, + test.instrumentOrder, + test.active, + ]; + }); + + return ( +
+ {testForm} + +
+ ); + } +} + +BatteryManagerIndex.propTypes = { + dataURL: PropTypes.string.isRequired, + hasPermission: PropTypes.func.isRequired, +}; + +window.addEventListener('load', () => { + ReactDOM.render( + , + document.getElementById('lorisworkspace') + ); +}); diff --git a/modules/battery_manager/php/battery_manager.class.inc b/modules/battery_manager/php/battery_manager.class.inc new file mode 100644 index 00000000000..093c228aca2 --- /dev/null +++ b/modules/battery_manager/php/battery_manager.class.inc @@ -0,0 +1,91 @@ + + * @author Henri Rabalais + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ +namespace LORIS\battery_manager; +use \Psr\Http\Message\ServerRequestInterface; +use \Psr\Http\Message\ResponseInterface; + +/** + * Main class for battery manager module corresponding to /battery_manager/ URL + * Admin section of the LorisMenu. + * + * Displays a list of records in the test battery and control panel to search them + * Allows user to add, activate, and deactivate entries in the test battery + * + * PHP Version 7 + * + * @category Module + * @package Battery_Manager + * @author Victoria Foing + * @author Henri Rabalais + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ +class Battery_Manager extends \NDB_Page +{ + public $skipTemplate = true; + + /** + * Returns true if user has access to this page. + * + * @param \User $user The user whose access is being checked + * + * @return bool + */ + function _hasAccess(\User $user) : bool + { + return $user->hasPermission('battery_manager_view') || + $user->hasPermission('battery_manager_edit'); + } + + /** + * This acts as an Ajax enpoint that handles all action requests from the + * Battery Manager Module. + * + * @param ServerRequestInterface $request The incoming PSR7 request + * + * @return ResponseInterface The outgoing PSR7 response + */ + public function handle(ServerRequestInterface $request) : ResponseInterface + { + $resp = parent::handle($request); + switch ($resp->getStatusCode()) { + case 200: + break; + default: + return $resp; + } + $this->setup(); + return (new \LORIS\Http\Response()) + ->withBody(new \LORIS\Http\StringStream($this->display())); + } + + /** + * Include additional JS files. + * + * @return array of javascript to be inserted + */ + public function getJSDependencies() : array + { + $factory = \NDB_Factory::singleton(); + $baseURL = $factory->settings()->getBaseURL(); + $deps = parent::getJSDependencies(); + return array_merge( + $deps, + array( + $baseURL . "/battery_manager/js/batteryManagerIndex.js", + ) + ); + } +} + diff --git a/modules/battery_manager/php/module.class.inc b/modules/battery_manager/php/module.class.inc new file mode 100644 index 00000000000..ba6ae6a8f72 --- /dev/null +++ b/modules/battery_manager/php/module.class.inc @@ -0,0 +1,66 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris-Trunk/ + */ +namespace LORIS\battery_manager; + +/** + * Class module implements the basic LORIS module functionality + * + * @category Behavioural + * @package Main + * @subpackage Battery_Manager + * @author Victoria Foing + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris-Trunk/ + */ +class Module extends \Module +{ + /** + * {@inheritDoc} + * + * @param \User $user The user whose access is being checked. + * + * @return bool whether access is granted + */ + public function hasAccess(\User $user) : bool + { + return parent::hasAccess($user) && + $user->hasAnyPermission( + [ + 'battery_manager_view', + 'battery_manager_edit' + ] + ); + } + + /** + * {@inheritDoc} + * + * @return string The menu category for this module + */ + public function getMenuCategory() : string + { + return "Tools"; + } + + /** + * {@inheritDoc} + * + * @return string The human readable name for this module + */ + public function getLongName() : string + { + return "Battery Manager"; + } +} diff --git a/modules/battery_manager/php/test.class.inc b/modules/battery_manager/php/test.class.inc new file mode 100644 index 00000000000..245eb483794 --- /dev/null +++ b/modules/battery_manager/php/test.class.inc @@ -0,0 +1,84 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ + +namespace LORIS\battery_manager; + +/** + * A Test represents a row in the Battery Manager menu table. + * + * @category Behavioural + * @package Main + * @subpackage Imaging + * @author Henri Rabalais + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ +class Test implements \LORIS\Data\DataInstance +{ + public $row; + + /** + * Create a new Test Instance. + * + * @param array $row The row (in the same format as \Database::pselectRow returns + * returns + */ + public function __construct(array $row) + { + $this->row = $row; + } + + /** + * Implements \LORIS\Data\DataInstance interface for this row. + * + * @return string the row data. + */ + public function toJSON() : string + { + return json_encode($this->row); + } + + /** + * Returns the CenterID for this row, for filters such as + * \LORIS\Data\Filters\UserSiteMatch to match again. + * + * @return integer The CenterID + */ + public function getCenterID() : int + { + return $this->row['centerId']; + } + + /** + * Convert data from Instance to a format suitable for SQL. + * + * @return array + */ + public function toSQL() : array + { + return array( + 'Test_name' => $this->row['testName'] ?? null, + 'AgeMinDays' => $this->row['ageMinDays'] ?? null, + 'AgeMaxDays' => $this->row['ageMaxDays'] ?? null, + 'Stage' => $this->row['stage'] ?? null, + 'SubprojectID' => $this->row['subproject'] ?? null, + 'Visit_label' => $this->row['visitLabel'] ?? null, + 'CenterID' => $this->row['centerId'] ?? null, + 'firstVisit' => $this->row['firstVisit'] ?? null, + 'instr_order' => $this->row['instrumentOrder'] ?? null, + 'Active' => $this->row['active'] ?? null, + ); + } +} diff --git a/modules/battery_manager/php/testendpoint.class.inc b/modules/battery_manager/php/testendpoint.class.inc new file mode 100644 index 00000000000..5fd5e5635d6 --- /dev/null +++ b/modules/battery_manager/php/testendpoint.class.inc @@ -0,0 +1,262 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ +namespace LORIS\battery_manager; +use \Psr\Http\Message\ServerRequestInterface; +use \Psr\Http\Server\RequestHandlerInterface; +use \Psr\Http\Message\ResponseInterface; + +/** + * Main class for managing Test Instances + * + * Handles requests for retrieving and saving Test Instances. + * Allows users to add, activate, and deactivate entries in the test battery. + * + * PHP Version 7 + * + * @category Module + * @package Battery_Manager + * @author Henri Rabalais + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ +class TestEndpoint implements RequestHandlerInterface +{ + + /** + * Returns true if user has access to this endpoint. + * + * @param \User $user The user whose access is being checked + * + * @return bool + */ + function _hasAccess(\User $user) : bool + { + return true; + } + + /** + * This function passes the request to the handler. This is necessary since + * the Endpoint bypass the Module class. + * + * XXX: This function should be extracted to a parent class. + * + * @param ServerRequestInterface $request The PSR7 request. + * @param RequestHandlerInterface $handler The request handler. + * + * @return ResponseInterface The outgoing PSR7 response. + */ + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ) : ResponseInterface { + return $handler->handle($request); + } + + /** + * This acts as an Ajax enpoint that handles all action requests from the + * Battery Manager Module. + * + * @param ServerRequestInterface $request The incoming PSR7 request + * + * @return ResponseInterface The outgoing PSR7 response + */ + public function handle(ServerRequestInterface $request) : ResponseInterface + { + $this->db = \Database::singleton(); + $this->user = $request->getAttribute('user'); + $method = $request->getMethod(); + + switch($method) { + case 'GET': + return $this->_getInstances(); + case 'POST': + return $this->_postInstance($request); + case 'PATCH': + return $this->_patchInstance($request); + } + } + + /** + * Gets the data source for battery manager tests. + * + * @return \LORIS\Data\Provisioner + */ + private function _getDataProvisioner() : \LORIS\Data\Provisioner + { + $provisioner = new TestProvisioner(); + + if ($this->user->hasPermission('access_all_profiles') == false) { + $provisioner = $provisioner->filter( + new \LORIS\Data\Filters\UserSiteMatch() + ); + } + return $provisioner; + } + + /** + * Updates the status of the given test from Active to Deactive or + * viceversa. + * + * @param ServerRequestInterface $request Test to be saved + * + * @return ResponseInterface response + */ + private function _patchInstance(ServerRequestInterface $request) + { + $testArray = json_decode($request->getBody()->getContents(), true); + $test = new Test($testArray); + + // validate instance; + $errors = $this->_validateInstance($test); + if (!empty($errors)) { + return new \LORIS\Http\Response\JSON\BadRequest( + implode(' ', $errors) + ); + } + + $testArray = $test->toSQL(); + try { + $this->db->update( + 'test_battery', + $testArray, + array('ID' => $testArray['ID']) + ); + return new \LORIS\Http\Response\JSON\OK( + ['message'=>'Availability Updated Successfully'] + ); + } catch (\DatabaseException $e) { + return new \LORIS\Http\Response\JSON\InternalServerError( + 'Could not update entry in the Test Battery' + ); + } + } + + /** + * Adds a new test to the test_battery in the database. + * + * @param ServerRequestInterface $request Test to be posted + * + * @return ResponseInterface response + */ + private function _postInstance(ServerRequestInterface $request) + { + $testArray = json_decode($request->getBody()->getContents(), true); + $test = new Test($testArray); + + if (!$this->user->hasPermission('battery_manager_edit')) { + return new \LORIS\Http\Response\JSON\Forbidden('Edit Permission Denied'); + } + + // validate instance + $errors = $this->_validateInstance($test); + if (!empty($errors)) { + return new \LORIS\Http\Response\JSON\BadRequest( + implode(' ', $errors) + ); + } + + // check if instance is duplicate + if ($this->_isDuplicate($test)) { + return new \LORIS\Http\Response\JSON\Conflict( + 'This Test already exists in the database' + ); + } + + $test->row['active'] = 'Y'; + $testArray = $test->toSQL(); + + try { + $this->db->insert('test_battery', $testArray); + return new \LORIS\Http\Response\JSON\OK( + ['message'=>'Test Submission Successful'] + ); + } catch (\DatabaseException $e) { + return new \LORIS\Http\Response\JSON\InternalServerError( + 'Could not add entry to the Test Battery' + ); + } + } + + /** + * Checks if the entry is an exact duplicate of a previous entry. + * + * @param Test $test Test to be checked. + * + * @return bool + */ + private function _isDuplicate(Test $test) : bool + { + // Build SQL query based on values entered by user + $query = "SELECT Test_name as testName, + AgeMinDays as ageMinDays, + AgeMaxDays as ageMaxDays, + Stage as stage, + SubprojectID as subproject, + Visit_label as visitLabel, + CenterID as centerId, + firstVisit, + instr_order as instrumentOrder + FROM test_battery"; + // Select duplicate entry from Test Battery + $entries = $this->db->pselect($query, array()); + + foreach ($entries as $entry) { + if ($test->row === $entry) { + return true; + } + } + + return false; + } + + /** + * Converts the results of this menu filter to a JSON format. + * + * @return ResponseInterface The outgoing PSR7 with a string of json + * encoded tests as the body. + */ + private function _getInstances() : ResponseInterface + { + $instances = (new \LORIS\Data\Table()) + ->withDataFrom($this->_getDataProvisioner($this->user)) + ->toArray($this->user); + return new \LORIS\Http\Response\JSON\OK($instances); + } + + /** + * Validates the Test Instance and collects in errors in an array. + * + * @param Test $test The Test instance to be validated + * + * @return array $errors An array string errors. + */ + private function _validateInstance(Test $test) : array + { + $errors = []; + if (!isset($test->row['testName'])) { + $errors[] = 'Test Name is a required field.'; + } + if (!isset($test->row['ageMinDays'])) { + $errors[] = 'Minimum age is a required field.'; + } + if (!isset($test->row['ageMaxDays'])) { + $errors[] = 'Maximum age is a required field.'; + } + if (!isset($test->row['stage'])) { + $errors[] = 'Stage is a required field.'; + } + + return $errors; + } +} + diff --git a/modules/battery_manager/php/testoptionsendpoint.class.inc b/modules/battery_manager/php/testoptionsendpoint.class.inc new file mode 100644 index 00000000000..aa14c41f76e --- /dev/null +++ b/modules/battery_manager/php/testoptionsendpoint.class.inc @@ -0,0 +1,102 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ +namespace LORIS\battery_manager; +use \Psr\Http\Message\ServerRequestInterface; +use \Psr\Http\Message\ResponseInterface; + +/** + * Main class for battery manager module corresponding to /battery_manager/ URL + * Admin section of the LorisMenu. + * + * Displays a list of records in the test battery and control panel to search them + * Allows user to add, activate, and deactivate entries in the test battery + * + * PHP Version 7 + * + * @category Module + * @package Battery_Manager + * @author Henri Rabalais + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ +class TestOptionsEndpoint extends \NDB_Page +{ + /** + * This acts as an Ajax enpoint that handles all action requests from the + * Battery Manager Module. + * + * @param ServerRequestInterface $request The incoming PSR7 request + * + * @return ResponseInterface The outgoing PSR7 response + */ + public function handle(ServerRequestInterface $request) : ResponseInterface + { + $method = $request->getMethod(); + + switch($method) { + case 'GET': + return new \LORIS\Http\Response\JSON\OK($this->_getOptions()); + } + + return new \LORIS\Http\Response\JSON\BadRequest('Unspecified Request'); + } + + /** + * Gets the field options for this module. + * + * @return array + */ + private function _getOptions() : array + { + return array( + 'instruments' => \Utility::getAllInstruments(), + 'stages' => $this->_getStageList(), + 'subprojects' => \Utility::getSubprojectList(null), + 'visits' => \Utility::getVisitList(), + 'sites' => \Utility::getSiteList(false), + 'firstVisits' => $this->_getYesNoList(), + 'active' => $this->_getYesNoList(), + ); + } + + /** + * Return associative array of stages. + * + * @return array + */ + private function _getStageList() : array + { + return array( + "Not Started" => 'Not Started', + "Screening" => 'Screening', + "Visit" => 'Visit', + "Approval" => "Approval", + "Subject" => "Subject", + "Recycling Bin" => "Recycling Bin", + ); + } + + /** + * Return associative array of yes and no values. + * + * @return array + */ + private function _getYesNoList() : array + { + return array( + 'Y' => 'Yes', + 'N' => 'No', + ); + } +} + diff --git a/modules/battery_manager/php/testprovisioner.class.inc b/modules/battery_manager/php/testprovisioner.class.inc new file mode 100644 index 00000000000..3dd6ad2ce2e --- /dev/null +++ b/modules/battery_manager/php/testprovisioner.class.inc @@ -0,0 +1,70 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ + +namespace LORIS\battery_manager; + +/** + * This class implements a data provisioner to get all possible tests + * for the battery_manager menu page. + * + * PHP Version 7 + * + * @category Behavioural + * @package Main + * @subpackage Imaging + * @author Henri Rabalais + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ +class TestProvisioner extends \LORIS\Data\Provisioners\DBRowProvisioner +{ + /** + * Create a Test Instance, which get rows fro mthe test_battery table. + */ + public function __construct() + { + parent::__construct( + "SELECT b.id, + t.Test_name as testName, + b.AgeMinDays as ageMinDays, + b.AgeMaxDays as ageMaxDays, + b.Stage as stage, + s.SubprojectID as subproject, + b.Visit_label as visitLabel, + p.CenterID as centerId, + b.firstVisit, + b.instr_order as instrumentOrder, + b.Active as active + FROM test_battery b + JOIN test_names t USING (Test_name) + LEFT JOIN subproject s USING (SubprojectID) + LEFT JOIN psc p USING (CenterID) + ORDER BY t.Test_name", + array() + ); + } + + /** + * Returns an instance of a Test object for a given table row. + * + * @param array $row The database row from the LORIS Database class. + * + * @return \LORIS\Data\DataInstance An instance representing a test. + */ + public function getInstance($row) : \LORIS\Data\DataInstance + { + return new Test($row); + } +} diff --git a/modules/battery_manager/test/BatteryManagerTest.php b/modules/battery_manager/test/BatteryManagerTest.php new file mode 100644 index 00000000000..3b8210f64f7 --- /dev/null +++ b/modules/battery_manager/test/BatteryManagerTest.php @@ -0,0 +1,64 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://github.com/aces/Loris + */ + +require_once __DIR__ . + "/../../../test/integrationtests/LorisIntegrationTest.class.inc"; + +/** + * Battery Manager module automated integration tests + * + * PHP Version 5 + * + * @category Test + * @package Loris + * @author Wang Shen + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://github.com/aces/Loris + */ +class BatteryManagerTest extends LorisIntegrationTest +{ + + /** + * Tests that the page does not load if the user does not have correct + * permissions + * + * @return void + */ + function testLoadsWithPermissionRead() + { + $this->setupPermissions(array("battery_manager_view")); + $this->safeGet($this->url . "/battery_manager/"); + $bodyText = $this->webDriver->findElement( + WebDriverBy::cssSelector("body") + )->getText(); + $this->assertNotContains("You do not have access to this page.", $bodyText); + $this->resetPermissions(); + } + /** + * Tests that the page does not load if the user does not have correct + * permissions + * + * @return void + */ + function testDoesNotLoadWithoutPermission() + { + $this->setupPermissions(array()); + $this->safeGet($this->url . "/battery_manager/"); + $bodyText = $this->webDriver->findElement( + WebDriverBy::cssSelector("body") + )->getText(); + $this->assertContains("You do not have access to this page.", $bodyText); + $this->resetPermissions(); + } + +} diff --git a/modules/battery_manager/test/TestPlan.md b/modules/battery_manager/test/TestPlan.md new file mode 100644 index 00000000000..aa694fa997d --- /dev/null +++ b/modules/battery_manager/test/TestPlan.md @@ -0,0 +1,104 @@ +# Battery Manager Module Test Plan + +## Overview + +Battery Manager module allows users to search for, add, edit, activate, and deactivate entries in the +Test Battery. + +## Features + +1. **Browse** entries in the Test Battery. +2. **Add** new entry to the `test_battery` table. +3. **Edit** entry in the `test_battery` table. +4. **Activate** entry in the `test_battery` table. +5. **Deactivate** entry in the `test_battery` table. + +--- + +## Testing Procedure + +### Permissions + +**Testing with no permissions** [Automation Testing] + 1. Access the module with a regular user (without superuser permissions). + 2. By default, the access to module should be denied. + +**Testing with view permission** [Automation Testing] + 1. Add view permission to the aforementioned user. + 2. Battery Manager module should be accessible and only present with **one** tab (Browse). + 3. The **Change Status** column should not be in the data table. + 4. The **Edit Metadata** column should not be in the data table. + +**Testing with edit permission** [Automation Testing] + 1. Add edit permission. + 2. Battery Manager module should now have **two** tabs (Browse) and (Add). + 3. The **Change Status** column should be in the data table. + 4. The **Edit Metadata** column should be in the data table. + 5. Clicking on Add tab should hide the data table and display a form with the following fields: + `Instrument`, `Minimum age (days)`, `Maximum age(days)`, `Stage`, `Subproject`, `Visit Label`, `Site`, `First Visit`, + and `Instrument Order`. + 6. Clicking on Edit in **Edit Metadata** takes you to a new page with a form with the following fields: + `Instrument`, `Minimum age (days)`, `Maximum age(days)`, `Stage`, `Subproject`, `Visit Label`, `Site`, `First Visit`, + and `Instrument Order`. + +### Add tab + +**Testing add functionality** + 1. Check that you cannot add an entry without filling out the required fields: `Instrument`, `Minimum age (days)`, `Maximum age (days)`, `Stage`. + 2. Check that you can only enter a site that exists. + 3. Check that you can only enter numbers between 0 and 99999 in Minumum age (days) and Maximum age (days). + 4. Check that you can only enter numbers between 0 and 127 in Instrument order. + 5. Check that when you try to add an entry that has an active duplicate in the table (Active = 'Y'), you receive an error message. + 6. Try to add an entry that does not have a duplicate. + - Ensure that a success message appears and the page goes back to the Browse tab. + - Ensure the entry you just added is shown in data table. + +**Testing activate functionality** + 1. Try to add an entry that has an inactive duplicate in the table (Active = 'N'). + - Ensure that you receive a warning message that allows you to activate the duplicate. + - Ensure that when you press "Yes", a success message appears and the page goes back to the Browse tab. + - Ensure the entry you just activated is activated in the data table. + +### Browse tab + +**Testing data table** + 1. After a couple of entries are added, ensure they are properly displayed in the data table. + 2. Ensure that information in the data table corresponds to the information in the `test_battery` table. + 3. Click on **column headers** to ensure sorting functionality is working as expected (Ascending/Descending). + +**Testing Change Status column** + 1. Press the `Deactivate` button in the `Change Status` column on an entry in the data table. + - Ensure that a warning message appears that asks you to confirm the action. + - Ensure that when you press "Yes", a success message appears and the page refreshes. + - Ensure the entry has the new Active status in the data table. + 2. Repeat step 1 using the `Activate` button. + +**Testing Edit Metadata column** + 1. Press the `Edit` link in the `Edit Metadata` column on an entry in the data table to edit. + - Ensure that you are taken to an Edit page with a form that is populated with the entry's values. + +**Test filters** + 1. Under **Browse** tab, a selection filter should be present on top of the page containing the following fields: + - Minimum age, Maximum age, and Instrument Order (as text fields). + - Instrument, Stage, Subproject, Visit Label, Site, First Visit, Instrument Order, and Active (as dropdown fields with blank default option). + 2. Type text in the Minimum age and verify that the table gets filtered as you type. + 3. Type text in the Maximum age and verify that the table gets filtered as you type. + 4. Select values from the dropdown filters (independently and combined) to filter table further. + - The table should update and display filtered records accordingly. + +### Edit window + +**Testing edit (activate/deactivate/add) functionality** + 1. Check that you cannot edit an entry without filling out the required fields: `Instrument`, `Minimum age (days)`, `Maximum age (days)`, `Stage`. + 2. Check that you can only enter a site that exists. + 3. Check that you can only enter numbers between 0 and 99999 in Minumum age (days) and Maximum age (days). + 4. Check that you can only enter numbers between 0 and 127 in Instrument order. + 5. Check that when you try to edit an entry without making changes to the form, you receive an error message. + 6. Check that when the edited entry has the same values as another active entry in the Test Battery, you receive an error message. + 7. Try to edit an entry so that it has the same values as another deactivated entry in the Test Battery. + - Ensure that a warning message appears giving the option to activate the other entry and deactivate the original entry. + - Ensure that when you press "Yes", a success message appears and the page goes back to the Browse tab. + - Ensure the original entry was deactivated and the other duplicate entry has been activated in the data table. + 8. Try to edit an entry so that it does not have a duplicate (i.e. itself or another entry). + - Ensure that a success message appears and the page goes back to the `Browse` tab. + - Ensure the original entry was deactivated and the new entry has been added to the data table. diff --git a/raisinbread/RB_files/RB_modules.sql b/raisinbread/RB_files/RB_modules.sql index aa529c88b62..a803b6326d3 100644 --- a/raisinbread/RB_files/RB_modules.sql +++ b/raisinbread/RB_files/RB_modules.sql @@ -39,5 +39,6 @@ INSERT INTO `modules` (`ID`, `Name`, `Active`) VALUES (35,'statistics','Y'); INSERT INTO `modules` (`ID`, `Name`, `Active`) VALUES (36,'survey_accounts','Y'); INSERT INTO `modules` (`ID`, `Name`, `Active`) VALUES (37,'timepoint_list','Y'); INSERT INTO `modules` (`ID`, `Name`, `Active`) VALUES (38,'user_accounts','Y'); +INSERT INTO `modules` (`ID`, `Name`, `Active`) VALUES (39,'battery_manager','Y'); UNLOCK TABLES; SET FOREIGN_KEY_CHECKS=1; diff --git a/raisinbread/RB_files/RB_permissions.sql b/raisinbread/RB_files/RB_permissions.sql index a182842e420..6dbc2bb6aba 100644 --- a/raisinbread/RB_files/RB_permissions.sql +++ b/raisinbread/RB_files/RB_permissions.sql @@ -57,5 +57,7 @@ INSERT INTO `permissions` (`permID`, `code`, `description`, `categoryID`) VALUES INSERT INTO `permissions` (`permID`, `code`, `description`, `categoryID`) VALUES (55,'publication_approve','Publication - Approve or reject proposed publication projects',2); INSERT INTO `permissions` (`permID`, `code`, `description`, `categoryID`) VALUES (56,'data_release_view','Data Release: View releases',2); INSERT INTO `permissions` (`permID`, `code`, `description`, `categoryID`) VALUES (57,'candidate_dob_edit','Edit dates of birth',2); +INSERT INTO `permissions` (`permID`, `code`, `description`, `categoryID`) VALUES (58,'battery_manager_view','View Battery Manager',2); +INSERT INTO `permissions` (`permID`, `code`, `description`, `categoryID`) VALUES (59,'battery_manager_edit','Add, activate, and deactivate entries in Test Battery',2); UNLOCK TABLES; SET FOREIGN_KEY_CHECKS=1; diff --git a/raisinbread/RB_files/RB_user_perm_rel.sql b/raisinbread/RB_files/RB_user_perm_rel.sql index 616c972b333..c2e3c0b8155 100644 --- a/raisinbread/RB_files/RB_user_perm_rel.sql +++ b/raisinbread/RB_files/RB_user_perm_rel.sql @@ -57,5 +57,7 @@ INSERT INTO `user_perm_rel` (`userID`, `permID`) VALUES (1,53); INSERT INTO `user_perm_rel` (`userID`, `permID`) VALUES (1,54); INSERT INTO `user_perm_rel` (`userID`, `permID`) VALUES (1,55); INSERT INTO `user_perm_rel` (`userID`, `permID`) VALUES (1,56); +INSERT INTO `user_perm_rel` (`userID`, `permID`) VALUES (1,58); +INSERT INTO `user_perm_rel` (`userID`, `permID`) VALUES (1,59); UNLOCK TABLES; SET FOREIGN_KEY_CHECKS=1; diff --git a/webpack.config.js b/webpack.config.js index fa00c58b532..e621887b538 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -18,6 +18,7 @@ const config = [{ './modules/configuration/js/SubprojectRelations.js': './modules/configuration/jsx/SubprojectRelations.js', './modules/conflict_resolver/js/conflictResolverIndex.js': './modules/conflict_resolver/jsx/conflictResolverIndex.js', './modules/conflict_resolver/js/resolvedConflictsIndex.js': './modules/conflict_resolver/jsx/resolvedConflictsIndex.js', + './modules/battery_manager/js/batteryManagerIndex.js': './modules/battery_manager/jsx/batteryManagerIndex.js', './modules/bvl_feedback/js/react.behavioural_feedback_panel.js': './modules/bvl_feedback/jsx/react.behavioural_feedback_panel.js', './modules/behavioural_qc/js/behavioural_qc_module.js': './modules/behavioural_qc/jsx/behavioural_qc_module.js', './modules/candidate_list/js/openProfileForm.js': './modules/candidate_list/jsx/openProfileForm.js',