diff --git a/modules/conflict_resolver/jsx/.unresolved_filterabledatatable.js.swp b/modules/conflict_resolver/jsx/.unresolved_filterabledatatable.js.swp new file mode 100644 index 00000000000..e559d351889 Binary files /dev/null and b/modules/conflict_resolver/jsx/.unresolved_filterabledatatable.js.swp differ diff --git a/modules/conflict_resolver/jsx/fix_conflict_form.js b/modules/conflict_resolver/jsx/fix_conflict_form.js index 8590304ac6a..2da0e68a949 100644 --- a/modules/conflict_resolver/jsx/fix_conflict_form.js +++ b/modules/conflict_resolver/jsx/fix_conflict_form.js @@ -16,7 +16,7 @@ import PropTypes from 'prop-types'; * dropdown is removed to prevent the user from sending a POST request with an empty * value. * - * Knowned issue: When sorting the datatable, previouly fixed conflicts are + * Known issue: When sorting the datatable, previouly fixed conflicts are * considered unresolved; there is no green checkmark beside the dropdown anymore. */ class FixConflictForm extends Component { @@ -28,7 +28,26 @@ class FixConflictForm extends Component { constructor(props) { super(props); - this.fix = this.fix.bind(this); + this.state = { + success: false, + error: false, + emptyOption: true, + }; + + this.resolveConflict = this.resolveConflict.bind(this); + } + + componentDidUpdate(prevProps, prevState) { + if (this.state.error) { + setTimeout(() => { + this.setState({error: false}); + }, 3000); + } + if (this.state.success) { + setTimeout(() => { + this.setState({success: false}); + }, 3000); + } } /** @@ -41,22 +60,12 @@ class FixConflictForm extends Component { * beside the dropdown. On error, a red cross will be displayed as well as a * sweetalert (swal) with the error message. * - * @param {Event} e - The onChange event. + * @param {string} name + * @param {string} value */ - fix(e) { - const conflictid = e.target.name; - const correctanswer = e.target.value; - - const feedbackicon = e.target.parentElement.getElementsByTagName('span')[0]; + resolveConflict(name, value) { // Hide any previously displayed icon. - feedbackicon.style.display = 'none'; - - try { - // Remove the empty option from the options to prevent sending an empty value. - e.target.querySelector('option[name="0"]').remove(); - } catch (error) { - // TypeError when already removed. Do nothing. - } + this.setState({success: false, error: false, emptyOption: false}); fetch(loris.BaseURL.concat('/conflict_resolver/unresolved'), { method: 'POST', @@ -64,58 +73,49 @@ class FixConflictForm extends Component { headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({conflictid: conflictid, correctanswer: correctanswer}), + body: JSON.stringify({conflictid: name, correctanswer: value}), }) .then((resp) => { return resp.ok ? {} : resp.json(); }) .then((json) => { - if (json.error) { - throw json.error; - } - // Set feedback icon to green checkmark - setTimeout(() => { - feedbackicon.className = 'glyphicon glyphicon-ok-circle'; - feedbackicon.style.color = 'green'; - feedbackicon.style.display = 'inline'; - }, 200); + if (json.error) { + throw json.error; + } + this.setState({success: true}); }) .catch((error) => { swal('Error!', error, 'error'); - // Set feedback icon to red cross - setTimeout(() => { - feedbackicon.className = 'glyphicon glyphicon-remove-sign'; - feedbackicon.style.color = 'red'; - feedbackicon.style.display = 'inline'; - }, 200); + this.setState({error: true}); }); - } + } render() { - const options = [ - , - , - , - ]; + const {success, error, emptyOption} = this.state; + const iconStyle = { + opacity: success || error ? 1 : 0, + color: success ? 'green' : (error ? 'red' : 'white'), + transition: 'opacity 2s, color 2s', + }; + const iconClass = 'glyphicon glyphicon-' + (success ? 'ok' : 'remove') + + '-circle'; return ( -
- - - + + ); } } FixConflictForm.propTypes = { - conflictid: PropTypes.string.isRequired, - values: PropTypes.arrayOf(PropTypes.shape({ - name: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - })).isRequired, + conflictId: PropTypes.string.isRequired, + options: PropTypes.object.isRequired, }; export default FixConflictForm; diff --git a/modules/conflict_resolver/jsx/unresolved_filterabledatatable.js b/modules/conflict_resolver/jsx/unresolved_filterabledatatable.js index 9e58d56904c..86938687f99 100644 --- a/modules/conflict_resolver/jsx/unresolved_filterabledatatable.js +++ b/modules/conflict_resolver/jsx/unresolved_filterabledatatable.js @@ -34,12 +34,15 @@ class UnresolvedFilterableDataTable extends Component { formatColumn(column, cell, rowData, rowHeaders) { switch (column) { case 'Correct Answer': - const values = [ - {name: '1', value: rowData['Value 1']}, - {name: '2', value: rowData['Value 2']}, - ]; + const options = { + 1: rowData['Value 1'], + 2: rowData['Value 2'], + }; return ( - + ); } return ( @@ -52,7 +55,8 @@ class UnresolvedFilterableDataTable extends Component { * @return {object} */ fetchData() { - return fetch(loris.BaseURL.concat('/conflict_resolver/unresolved'), {credentials: 'same-origin'}) + const url = loris.BaseURL.concat('/conflict_resolver/unresolved'); + return fetch(url, {credentials: 'same-origin'}) .then((resp) => resp.json()) .then((json) => { if (json.error) { diff --git a/modules/conflict_resolver/php/conflict_resolver.class.inc b/modules/conflict_resolver/php/conflict_resolver.class.inc index 2e39cd038d5..b3583cdd77c 100644 --- a/modules/conflict_resolver/php/conflict_resolver.class.inc +++ b/modules/conflict_resolver/php/conflict_resolver.class.inc @@ -53,9 +53,9 @@ class Conflict_Resolver extends \NDB_Page $deps = parent::getJSDependencies(); return array_merge( $deps, - array( - $baseURL . '/conflict_resolver/js/conflict_resolver.js', - ) + [ + $baseURL . '/conflict_resolver/js/conflict_resolver.js', + ] ); } } diff --git a/modules/conflict_resolver/php/endpoints/resolved.class.inc b/modules/conflict_resolver/php/endpoints/resolved.class.inc index ecd11434709..769e6cb4d8c 100644 --- a/modules/conflict_resolver/php/endpoints/resolved.class.inc +++ b/modules/conflict_resolver/php/endpoints/resolved.class.inc @@ -85,12 +85,12 @@ class Resolved extends Endpoint implements ETagCalculator $visitlabels = array_values(\Utility::getVisitList()); $projects = array_values(\Utility::getProjectList()); - return array( + return [ 'site' => array_combine($sites, $sites), 'instrument' => \Utility::getAllInstruments(), 'visitLabel' => array_combine($visitlabels, $visitlabels), 'project' => array_combine($projects, $projects), - ); + ]; } /** @@ -133,7 +133,7 @@ class Resolved extends Endpoint implements ETagCalculator $provisioner = (new ResolvedProvisioner()) ->filter( new HasAnyPermissionOrUserSiteMatch( - array('access_all_profiles') + ['access_all_profiles'] ) ) ->filter(new UserProjectMatch()); @@ -143,10 +143,10 @@ class Resolved extends Endpoint implements ETagCalculator ->withDataFrom($provisioner) ->toArray($user); - $body = array( + $body = [ 'data' => $conflicts, 'fieldOptions' => $this->_getFieldOptions(), - ); + ]; $this->_cache = new \LORIS\Http\Response\JsonResponse($body); diff --git a/modules/conflict_resolver/php/endpoints/unresolved.class.inc b/modules/conflict_resolver/php/endpoints/unresolved.class.inc index 786d084aeb1..205b6b7f36e 100644 --- a/modules/conflict_resolver/php/endpoints/unresolved.class.inc +++ b/modules/conflict_resolver/php/endpoints/unresolved.class.inc @@ -77,10 +77,10 @@ class Unresolved extends Endpoint implements ETagCalculator */ protected function allowedMethods() : array { - return array( + return [ 'GET', 'POST', - ); + ]; } /** @@ -94,12 +94,12 @@ class Unresolved extends Endpoint implements ETagCalculator $visitlabels = array_values(\Utility::getVisitList()); $projects = array_values(\Utility::getProjectList()); - return array( + return [ 'site' => array_combine($sites, $sites), 'instrument' => \Utility::getAllInstruments(), 'visitLabel' => array_combine($visitlabels, $visitlabels), 'project' => array_combine($projects, $projects), - ); + ]; } /** @@ -145,7 +145,7 @@ class Unresolved extends Endpoint implements ETagCalculator $provisioner = (new UnresolvedProvisioner()) ->filter( new HasAnyPermissionOrUserSiteMatch( - array('access_all_profiles') + ['access_all_profiles'] ) ) ->filter(new UserProjectMatch()); @@ -155,10 +155,10 @@ class Unresolved extends Endpoint implements ETagCalculator ->withDataFrom($provisioner) ->toArray($user); - $body = array( + $body = [ 'data' => $conflicts, 'fieldOptions' => $this->_getFieldOptions(), - ); + ]; $this->_cache = new \LORIS\Http\Response\JsonResponse($body); @@ -219,10 +219,10 @@ class Unresolved extends Endpoint implements ETagCalculator WHERE ConflictID = :v_conflictid ', - array( + [ 'v_conflictid' => $conflictid, 'v_value' => $correctanswer, - ) + ] ); if (empty($conflict)) { @@ -239,8 +239,9 @@ class Unresolved extends Endpoint implements ETagCalculator false ); - $instrument->_saveValues(array($conflict['FieldName'] => $correctanswer)); + $instrument->_saveValues([$conflict['FieldName'] => $correctanswer]); + // TODO: UNCOMMENT THE FOLLOWING LINES BEFORE SUBMITTING PR. // Using an output buffer because the score function prints to STDOUT ob_start(); $instrument->score(); @@ -313,11 +314,11 @@ class Unresolved extends Endpoint implements ETagCalculator ); $success = $stmt->execute( - array( + [ 'v_username' => $user->getUsername(), 'v_newvalue' => $correctanswer, 'v_conflictid' => $conflictid, - ) + ] ); if (!$success) { @@ -325,7 +326,7 @@ class Unresolved extends Endpoint implements ETagCalculator } // Delete from conflicts_unresolved - $db->delete('conflicts_unresolved', array('ConflictID' => $conflictid)); + $db->delete('conflicts_unresolved', ['ConflictID' => $conflictid]); return new \LORIS\Http\Response(); } diff --git a/modules/conflict_resolver/php/models/resolveddto.class.inc b/modules/conflict_resolver/php/models/resolveddto.class.inc index 2b4284af021..29af31e4adf 100644 --- a/modules/conflict_resolver/php/models/resolveddto.class.inc +++ b/modules/conflict_resolver/php/models/resolveddto.class.inc @@ -36,21 +36,21 @@ class ResolvedDTO implements \LORIS\Data\DataInstance public function jsonSerialize(): array { return [ - 'ResolvedID' => $this->resolvedid, - 'Project' => $this->project, - 'Site' => $this->site, - 'CandID' => $this->candid, - 'PSCID' => $this->pscid, - 'Visit Label' => $this->visitlabel, - 'Instrument' => $this->instrument, - 'Question' => $this->question, - 'Value 1' => $this->value1, - 'Value 2' => $this->value2, - 'Correct Answer' => $this->correctanswer, - 'User 1' => $this->user1, - 'User 2' => $this->user2, - 'Resolver' => $this->resolver, - 'ResolutionTimestamp' => $this->resolutiontimestamp, + 'ResolvedID' => $this->resolvedid, + 'Project' => $this->project, + 'Site' => $this->site, + 'CandID' => $this->candid, + 'PSCID' => $this->pscid, + 'Visit Label' => $this->visitlabel, + 'Instrument' => $this->instrument, + 'Question' => $this->question, + 'Value 1' => $this->value1, + 'Value 2' => $this->value2, + 'Correct Answer' => $this->correctanswer, + 'User 1' => $this->user1, + 'User 2' => $this->user2, + 'Resolver' => $this->resolver, + 'ResolutionTimestamp' => $this->resolutiontimestamp, ]; } diff --git a/modules/conflict_resolver/php/module.class.inc b/modules/conflict_resolver/php/module.class.inc index 1357da51f4e..a4b6b0f2f41 100644 --- a/modules/conflict_resolver/php/module.class.inc +++ b/modules/conflict_resolver/php/module.class.inc @@ -131,18 +131,18 @@ class Module extends \Module 'conflict_resolver' ) ]; - case 'candidate': - $factory = \NDB_Factory::singleton(); - $baseURL = $factory->settings()->getBaseURL(); - $DB = $factory->database(); + case 'candidate': + $factory = \NDB_Factory::singleton(); + $baseURL = $factory->settings()->getBaseURL(); + $DB = $factory->database(); - $candidate = $options['candidate']; - if ($candidate === null) { - return []; - } + $candidate = $options['candidate']; + if ($candidate === null) { + return []; + } - $candID = $candidate->getCandID(); - $results = $DB->pselect( + $candID = $candidate->getCandID(); + $results = $DB->pselect( "SELECT f1.Test_name, s1.Visit_label, COUNT(*) as Conflicts FROM conflicts_unresolved cu LEFT JOIN flag f1 ON (f1.CommentID=cu.CommentId1) @@ -151,29 +151,29 @@ class Module extends \Module LEFT JOIN session s2 ON (f2.SessionID=s2.ID) WHERE (s1.CandID=:cand1 OR s2.CandID=:cand2) GROUP BY f1.Test_name, s1.Visit_label - ORDER BY s1.Visit_label, f1.Test_name", - ['cand1' => $candID, 'cand2' => $candID], - ); - // Do not show the conflicts card if there are no conflicts - if (empty($results)) { - return []; - } + ORDER BY s1.Visit_label, f1.Test_name", + ['cand1' => $candID, 'cand2' => $candID], + ); + // Do not show the conflicts card if there are no conflicts + if (empty($results)) { + return []; + } - $width = 1; - if (count($results) > 10) { - $width = 2; - } - return [ - new \LORIS\candidate_profile\CandidateWidget( - "Unresolved Conflicts", - $baseURL . "/conflict_resolver/js/CandidateConflictsWidget.js", - "lorisjs.conflict_resolver.CandidateConflictsWidget.default", - [ - 'Conflicts' => $results, - ], - $width, - 1, - ) + $width = 1; + if (count($results) > 10) { + $width = 2; + } + return [ + new \LORIS\candidate_profile\CandidateWidget( + "Unresolved Conflicts", + $baseURL . "/conflict_resolver/js/CandidateConflictsWidget.js", + "lorisjs.conflict_resolver.CandidateConflictsWidget.default", + [ + 'Conflicts' => $results, + ], + $width, + 1, + ) ]; } diff --git a/modules/conflict_resolver/php/provisioners/resolvedprovisioner.class.inc b/modules/conflict_resolver/php/provisioners/resolvedprovisioner.class.inc index 24a50a731e0..b0c5f41be2d 100644 --- a/modules/conflict_resolver/php/provisioners/resolvedprovisioner.class.inc +++ b/modules/conflict_resolver/php/provisioners/resolvedprovisioner.class.inc @@ -58,7 +58,7 @@ class ResolvedProvisioner extends \LORIS\Data\Provisioners\DBObjectProvisioner LEFT JOIN psc ON (session.CenterID = psc.CenterID) WHERE session.Active="Y" AND candidate.Active ="Y" ', - array(), + [], '\LORIS\conflict_resolver\Models\ResolvedDTO' ); } diff --git a/modules/conflict_resolver/php/provisioners/unresolvedprovisioner.class.inc b/modules/conflict_resolver/php/provisioners/unresolvedprovisioner.class.inc index 24adfaef385..2da636927e5 100644 --- a/modules/conflict_resolver/php/provisioners/unresolvedprovisioner.class.inc +++ b/modules/conflict_resolver/php/provisioners/unresolvedprovisioner.class.inc @@ -53,7 +53,7 @@ class UnresolvedProvisioner extends \LORIS\Data\Provisioners\DBObjectProvisioner LEFT JOIN psc ON (session.CenterID = psc.CenterID) WHERE session.Active="Y" AND candidate.Active ="Y" ', - array(), + [], '\LORIS\conflict_resolver\Models\UnresolvedDTO' ); } diff --git a/modules/conflict_resolver/test/conflict_resolverTest.php b/modules/conflict_resolver/test/conflict_resolverTest.php index 004918a43a9..51af08c835c 100644 --- a/modules/conflict_resolver/test/conflict_resolverTest.php +++ b/modules/conflict_resolver/test/conflict_resolverTest.php @@ -89,7 +89,7 @@ function tearDown() */ function testConflictResolverPermission() { - $this->setupPermissions(array("conflict_resolver")); + $this->setupPermissions(["conflict_resolver"]); $this->safeGet($this->url . "/conflict_resolver"); $this->webDriver->wait()->until( \WebDriverExpectedCondition::presenceOfElementLocated( @@ -106,7 +106,7 @@ function testConflictResolverPermission() */ function testConflictResolverWithoutPermission() { - $this->setupPermissions(array()); + $this->setupPermissions([]); $this->safeGet($this->url . "/conflict_resolver"); $bodyText = $this->webDriver->findElement( \WebDriverBy::id('lorisworkspace') diff --git a/webpack.config.js b/webpack.config.js index 3ccd8c309cd..0757af9150c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -190,7 +190,10 @@ const config = [ 'ConsentWidget', ]), lorisModule('configuration', ['SubprojectRelations']), - lorisModule('conflict_resolver', ['conflict_resolver']), + lorisModule('conflict_resolver', [ + 'conflict_resolver', + 'CandidateConflictsWidget', + ]), lorisModule('battery_manager', ['batteryManagerIndex']), lorisModule('bvl_feedback', ['react.behavioural_feedback_panel']), lorisModule('behavioural_qc', ['behavioural_qc_module']),