diff --git a/CHANGELOG.md b/CHANGELOG.md index a3cb14a7d6f..1800113ca0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,4 +16,10 @@ changes in the following format: PR #1234*** - Very old instrument relying on QuickForm may have issues due to code changes (PR #4928) #### Modules + +##### Battery Manager + - New module created to manage the entries in the test_battery table of the database. + This allows projects to modify their instrument battery without requiring backend access. + (https://github.com/aces/Loris/pull/4221) + ##### module1 diff --git a/SQL/0000-00-01-Permission.sql b/SQL/0000-00-01-Permission.sql index beaf82f1136..99654c4835b 100644 --- a/SQL/0000-00-01-Permission.sql +++ b/SQL/0000-00-01-Permission.sql @@ -104,7 +104,9 @@ INSERT INTO `permissions` VALUES (56,'publication_approve', 'Publication - Approve or reject proposed publication projects', 2), (57, 'candidate_dob_edit', 'Edit dates of birth', 2), (58,'electrophysiology_browser_view_allsites', 'View all-sites Electrophysiology Browser pages', 2), - (59,'electrophysiology_browser_view_site', 'View own site Electrophysiology Browser pages', 2); + (59,'electrophysiology_browser_view_site', 'View own site Electrophysiology Browser pages', 2), + (60,'battery_manager_view','View Battery Manager',2), + (61,'battery_manager_edit','Add, activate, and deactivate entries in Test Battery',2); INSERT INTO `user_perm_rel` (userID, permID) diff --git a/SQL/0000-00-02-Modules.sql b/SQL/0000-00-02-Modules.sql index 241fe2f4987..2f2bca216fa 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/Filter.js b/jsx/Filter.js index d995eac34fc..26f779fa38f 100644 --- a/jsx/Filter.js +++ b/jsx/Filter.js @@ -71,6 +71,9 @@ class Filter extends Component { case 'multiselect': element = ; break; + case 'numeric': + element = ; + break; case 'date': element = ; break; 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..88668d7fef6 100644 --- a/jsx/Form.js +++ b/jsx/Form.js @@ -1207,12 +1207,19 @@ class NumericElement extends Component { } render() { - let disabled = this.props.disabled ? 'disabled' : null; - let required = this.props.required ? 'required' : null; - let requiredHTML = null; + const {disabled, required} = this.props; + let requiredHTML = required ? * : null; + let errorMessage = null; + let elementClass = 'row form-group'; + + // Add error message + if (this.props.errorMessage) { + errorMessage = {this.props.errorMessage}; + elementClass = 'row form-group has-error'; + } return ( -
+
); 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..678bb080db3 --- /dev/null +++ b/modules/battery_manager/help/battery_manager.md @@ -0,0 +1,117 @@ +# 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 in 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 **Submit** button to edit the entry in the Test Battery. +This will create a new Test with the information supplied, and deactivate the Test that was edited. +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..6efadc822a8 --- /dev/null +++ b/modules/battery_manager/jsx/batteryManagerForm.js @@ -0,0 +1,150 @@ +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, errors} = 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 alter the current 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..22d256a01af --- /dev/null +++ b/modules/battery_manager/jsx/batteryManagerIndex.js @@ -0,0 +1,507 @@ +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: {}, + add: false, + edit: false, + error: false, + errors: {}, + isLoaded: false, + }; + + this.fetchData = this.fetchData.bind(this); + this.postData = this.postData.bind(this); + this.formatColumn = this.formatColumn.bind(this); + this.saveTest = this.saveTest.bind(this); + this.setTest = this.setTest.bind(this); + this.closeForm = this.closeForm.bind(this); + this.validateTest = this.validateTest.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 + */ + 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 + */ + 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); + if (response.ok) { + resolve(body.message); + } else { + swal.fire(body.error, '', 'error'); + reject(body.error); + } + }) + .catch((e) => reject(e))); + }); + } + + /** + * Modify value of specified column cells in the Data Table component + * + * @param {string} column - column name + * @param {string} value - cell value + * + * @return {string} a mapped value for the table cell at a given column + */ + mapColumn(column, value) { + switch (column) { + case 'First Visit': + switch (value) { + case 'Y': + return 'Yes'; + case 'N': + return 'No'; + } + case 'Active': + switch (value) { + case 'Y': + return 'Yes'; + case 'N': + return 'No'; + } + case 'Change Status': + return ''; + case 'Edit Metadata': + return ''; + default: + return value; + } + } + + /** + * 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) { + cell = this.mapColumn(column, cell); + 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') { + result = { + this.deactivateTest(testId); + }}/>; + } else if (row.Active === 'N') { + result = { + this.activateTest(testId); + }}/>; + } + break; + case 'Edit Metadata': + const editButton = { + this.loadTest(testId); + this.setState({edit: true}); + }}/>; + result = {editButton}; + break; + } + + return result; + } + + /** + * 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}); + } + + /** + * Close the Form + */ + closeForm() { + this.setState({add: false, edit: false, test: {}}); + } + + /** + * Activate Test + * + * @param {int} id + */ + activateTest(id) { + const test = this.state.tests.find((test) => test.id === id); + test.active = 'Y'; + this.saveTest(test, 'PUT'); + } + + /** + * Deactivate Test + * + * @param {int} id + */ + deactivateTest(id) { + const test = this.state.tests.find((test) => test.id === id); + test.active = 'N'; + this.saveTest(test, 'PUT'); + } + + /** + * Updates a previously existing Test with an updated Test. + * + * @param {object} test + * @param {string} request + * + * @return {object} promise + */ + saveTest(test, request) { + return new Promise((resolve, reject) => { + Object.keys(test).forEach((key) => { + if (test[key] == '') { + test[key] = null; + } + }); + this.checkDuplicate(test) + .then((test) => this.validateTest(test)) + .then((test) => this.postData( + this.props.testEndpoint+test.id, + test, + request) + ) + .then(() => this.fetchData(this.props.testEndpoint, 'GET', 'tests')) + .then(() => resolve()) + .catch((e) => reject(e)); + }); + } + + /** + * 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, errors, add, edit} = 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: 'numeric', + }}, + {label: 'Maximum Age', show: true, filter: { + name: 'maximumAge', + type: 'numeric', + }}, + {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 actions = [ + { + label: 'New Test', + action: () => this.setState({add: true}), + 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, + ]; + }); + + const modalTitle = edit ? 'Edit Test' : 'Add New Test'; + const request = edit ? 'PUT' : 'POST'; + const handleSubmit = () => this.saveTest(test, request); + + return ( +
+ + + + +
+ ); + } + + /** + * 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 + ) { + duplicate = testCheck; + } + }); + + if (duplicate && duplicate.id !== test.id) { + if (duplicate.active === 'N') { + console.log('Duplicate Not Active'); + const edit = test.id ? 'This will deactivate the current test.' : ''; + swal.fire({ + title: 'Test Duplicate', + text: 'The information provided corresponds with a deactivated '+ + 'test that already exists in the system. Would you to like '+ + 'activate that test? '+edit, + 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') { + console.log('Duplicate Active'); + swal.fire( + 'Test Duplicate', 'You cannot duplicate an active test', 'error' + ); + } + reject(); + } else { + resolve(test); + } + }); + } + + + /** + * Checks that test fields are valide + * + * @param {object} test + * + * @return {object} promise + */ + validateTest(test) { + return new Promise((resolve, reject) => { + const errors = {}; + if (test.testName == null) { + errors.testName = 'This field is required'; + } + if (test.ageMinDays == null) { + errors.ageMinDays = 'This field is required'; + } + if (test.ageMaxDays == null) { + errors.ageMaxDays = 'This field is required'; + } + if (test.stage == null) { + errors.stage = 'This field is required'; + } + + if (Object.entries(errors).length === 0) { + this.setState({errors}, resolve(test)); + } else { + this.setState({errors}, reject()); + } + }); + } +} + +BatteryManagerIndex.propTypes = { + testEndpoint: PropTypes.string.isRequired, + optionEndpoint: 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..189153a37b2 --- /dev/null +++ b/modules/battery_manager/php/test.class.inc @@ -0,0 +1,85 @@ + + * @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( + 'ID' => $this->row['id'] ?? null, + '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..9f543264374 --- /dev/null +++ b/modules/battery_manager/php/testendpoint.class.inc @@ -0,0 +1,256 @@ + + * @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 'PUT': + return $this->_putInstance($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; + } + + /** + * Puts a test to the test_battery in the database. + * + * @param ServerRequestInterface $request Test to be saved + * + * @return ResponseInterface response + */ + private function _putInstance(ServerRequestInterface $request) + { + $testArray = json_decode($request->getBody()->getContents(), true); + $test = new Test($testArray); + return $this->_saveInstance($test); + } + + /** + * Posts a 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); + $test->row['active'] = 'Y'; + return $this->_saveInstance($test); + } + + /** + * Generic save function for Test Instances. + * + * @param Test $test The Test Instance to be saved. + * + * @return ResponseInterface response + */ + private function _saveInstance(Test $test) + { + 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' + ); + } + + try { + $this->db->insertOnDuplicateUpdate('test_battery', $test->toSQL()); + return new \LORIS\Http\Response\JSON\OK(); + } 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 ID, + Test_name, + AgeMinDays, + AgeMaxDays, + Stage, + SubprojectID, + Visit_label, + CenterID, + firstVisit + FROM test_battery"; + // Select duplicate entry from Test Battery + $entries = $this->db->pselect($query, array()); + + $testArray = $test->toSQL(); + foreach ($entries as $entry) { + if ($testArray['ID'] !== $entry['ID'] + && $testArray['Test_name'] == $entry['Test_name'] + && $testArray['AgeMinDays'] == $entry['AgeMinDays'] + && $testArray['AgeMaxDays'] == $entry['AgeMaxDays'] + && $testArray['Stage'] == $entry['Stage'] + && $testArray['SubprojectID'] == $entry['SubprojectID'] + && $testArray['Visit_label'] == $entry['Visit_label'] + && $testArray['CenterID'] == $entry['CenterID'] + && $testArray['firstVisit'] == $entry['firstVisit'] + ) { + 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..fc20c6fde60 --- /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), + 'firstVisit' => $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 7334289f011..001d51461f4 100644 --- a/raisinbread/RB_files/RB_permissions.sql +++ b/raisinbread/RB_files/RB_permissions.sql @@ -57,7 +57,9 @@ 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,'electrophysiology_browser_view_allsites','View all-sites Electrophysiology Browser pages',2); -INSERT INTO `permissions` (`permID`, `code`, `description`, `categoryID`) VALUES (59,'electrophysiology_browser_view_site','View own site Electrophysiology Browser pages',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); +INSERT INTO `permissions` (`permID`, `code`, `description`, `categoryID`) VALUES (60,'electrophysiology_browser_view_allsites','View all-sites Electrophysiology Browser pages',2); +INSERT INTO `permissions` (`permID`, `code`, `description`, `categoryID`) VALUES (61,'electrophysiology_browser_view_site','View own site Electrophysiology Browser pages',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 485223cad8b..534df194ee7 100644 --- a/raisinbread/RB_files/RB_user_perm_rel.sql +++ b/raisinbread/RB_files/RB_user_perm_rel.sql @@ -60,5 +60,7 @@ INSERT INTO `user_perm_rel` (`userID`, `permID`) VALUES (1,56); INSERT INTO `user_perm_rel` (`userID`, `permID`) VALUES (1,57); INSERT INTO `user_perm_rel` (`userID`, `permID`) VALUES (1,58); INSERT INTO `user_perm_rel` (`userID`, `permID`) VALUES (1,59); +INSERT INTO `user_perm_rel` (`userID`, `permID`) VALUES (1,60); +INSERT INTO `user_perm_rel` (`userID`, `permID`) VALUES (1,61); UNLOCK TABLES; SET FOREIGN_KEY_CHECKS=1; diff --git a/webpack.config.js b/webpack.config.js index 198d85cf98f..f52f4d1c003 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',