diff --git a/modules/instrument_manager/jsx/uploadForm.js b/modules/instrument_manager/jsx/uploadForm.js
index 69def8104f1..cc9cb939071 100644
--- a/modules/instrument_manager/jsx/uploadForm.js
+++ b/modules/instrument_manager/jsx/uploadForm.js
@@ -26,7 +26,6 @@ class InstrumentUploadForm extends Component {
this.getInstrumentOptions = this.getInstrumentOptions.bind(this);
this.dataFileSelected = this.dataFileSelected.bind(this);
this.uploadInstrumentData = this.uploadInstrumentData.bind(this);
-
}
/**
@@ -131,10 +130,17 @@ class InstrumentUploadForm extends Component {
});
});
} else {
+ let message = '
';
+ message += `
# Errors: ${data.message.length}
`;
+ data.message.forEach((error) => {
+ message += (error + '
');
+ });
+ message += '
';
+
swal.fire({
title: 'No data uploaded',
- type: 'warn',
- text: data.message,
+ type: 'warning',
+ html: message,
});
}
})
@@ -155,16 +161,17 @@ class InstrumentUploadForm extends Component {
}
-
/**
* Renders the React component.
*
- * @return {JSX} - React markup for the component
+ * @return {JSX.Element} - React markup for the component
*/
render() {
- const uploadInstrumentDisabled = () => this.state.selectedInstrumentFile === null;
+ const uploadInstrumentDisabled
+ = () => this.state.selectedInstrumentFile === null;
const instrumentSelected = this.state.selectedInstrument !== '';
- const uploadDataDisabled = () => !instrumentSelected || this.state.selectedDataFile === null;
+ const uploadDataDisabled
+ = () => !instrumentSelected || this.state.selectedDataFile === null;
return (
<>
@@ -225,7 +232,10 @@ class InstrumentUploadForm extends Component {
Download Expected Template
diff --git a/modules/instrument_manager/php/instrument_manager.class.inc b/modules/instrument_manager/php/instrument_manager.class.inc
index d1fc122b3c0..c46019830d4 100644
--- a/modules/instrument_manager/php/instrument_manager.class.inc
+++ b/modules/instrument_manager/php/instrument_manager.class.inc
@@ -276,7 +276,6 @@ class Instrument_Manager extends \DataFrameworkMenu
if (isset($request_body['instrument'])) {
$instrument = $request_body['instrument'];
if ($this->instrumentExists($instrument)) {
-
// Parse
$dataParser = new InstrumentDataParser(
$instrument,
@@ -293,9 +292,17 @@ class Instrument_Manager extends \DataFrameworkMenu
]
);
+ if (count($validData['errors']) > 0) {
+ return new \LORIS\Http\Response\JSON\OK(
+ [
+ 'message' => $validData['errors'],
+ ]
+ );
+ }
+
$result = $dataParser->insertInstrumentData(
$this->loris,
- $validData
+ $validData['data']
);
return new \LORIS\Http\Response\JSON\Created($result);
diff --git a/php/libraries/InstrumentDataParser.class.inc b/php/libraries/InstrumentDataParser.class.inc
index a62118cbc2b..00a14d1a976 100644
--- a/php/libraries/InstrumentDataParser.class.inc
+++ b/php/libraries/InstrumentDataParser.class.inc
@@ -226,22 +226,27 @@ class InstrumentDataParser extends CSVParser
public function validateData(
array $instrumentData, array $injectedColumns
): array {
- try {
- foreach ($instrumentData as &$dataRow) {
+ $errors = [];
+ foreach ($instrumentData as &$dataRow) {
+ try {
// Validate Session
$sessionID = $this->obtainSessionID(
$dataRow['PSCID'],
$dataRow['Visit_label']
);
$dataRow['SessionID'] = $sessionID;
+
+ // Inject columns
$dataRow = array_merge($dataRow, $injectedColumns);
+ } catch (\Exception $e) {
+ $errors[] = $e->getMessage();
}
- return $instrumentData;
- } catch (\Exception $e) {
- throw new RuntimeException(
- "Invalid data: {$e->getMessage()}"
- );
}
+
+ return [
+ 'data' => $instrumentData,
+ 'errors' => $errors,
+ ];
}
diff --git a/php/libraries/Utility.class.inc b/php/libraries/Utility.class.inc
index 7fa90d90bb2..019472a618d 100644
--- a/php/libraries/Utility.class.inc
+++ b/php/libraries/Utility.class.inc
@@ -263,10 +263,11 @@ class Utility
}
$query = "SELECT DISTINCT Visit_label FROM session s
JOIN candidate c ON (s.CandID = c.Candid)
+ JOIN psc ON (s.CenterID = psc.CenterID)
WHERE c.Active = 'Y'
AND s.Active = 'Y'
AND c.Entity_type != 'Scanner'
- AND s.CenterID!= '1'
+ AND psc.CenterID!= '1'
$ExtraProject_Criteria ORDER BY Visit_label";
$result = $db->pselect($query, $qparams);
// The result has several columns; we only want the visit labels.
@@ -580,18 +581,24 @@ class Utility
most accurate Full_name value and should be replaced by
getDDEInstrumentNamesList() of the NDB_BVL_Instrument class."
);
- $Factory = \NDB_Factory::singleton();
- $DB = $Factory->Database();
- $ddeInstruments = $DB->pselect(
- "SELECT DISTINCT test_battery.Test_name, Full_name
- FROM test_battery
- JOIN test_names ON test_battery.Test_name = test_names.Test_name
- WHERE DoubleDataEntryEnabled = 'Y'",
+ $Factory = \NDB_Factory::singleton();
+ $DB = $Factory->Database();
+ $config = $Factory->config();
+ $instruments_q = $DB->pselect(
+ "SELECT Test_name,Full_name FROM test_names",
[]
);
- $instruments = [];
- foreach ($ddeInstruments as $instrument) {
- $instruments[$instrument['Test_name']] = $instrument['Full_name'];
+ $doubleDataEntryInstruments = $config->getSetting(
+ 'DoubleDataEntryInstruments'
+ );
+
+ $instruments = [];
+ foreach ($instruments_q as $row) {
+ if (isset($row['Test_name']) && isset($row['Full_name'])) {
+ if (in_array($row['Test_name'], $doubleDataEntryInstruments)) {
+ $instruments[$row['Test_name']] = $row['Full_name'];
+ }
+ }
}
return $instruments;
}
@@ -774,10 +781,11 @@ class Utility
"SELECT DISTINCT t.Test_name, t.Full_name
FROM session s
JOIN candidate c ON (c.CandID=s.CandID)
+ JOIN psc ON (s.CenterID=psc.CenterID)
JOIN flag f ON (f.sessionID=s.id)
JOIN test_names t ON (f.test_name=t.Test_name)
WHERE c.Active='Y' AND s.Active='Y' AND s.Visit_label =:vl
- AND s.CenterID != '1' AND c.Entity_type != 'Scanner'
+ AND psc.CenterID != '1' AND c.Entity_type != 'Scanner'
ORDER BY t.Full_name",
['vl' => $visit_label],
'Test_name'
diff --git a/test/unittests/CSVParserTest.php b/test/unittests/CSVParserTest.php
index 300dc541812..edad1825b29 100644
--- a/test/unittests/CSVParserTest.php
+++ b/test/unittests/CSVParserTest.php
@@ -25,30 +25,6 @@
*/
class CSVParserTest extends TestCase
{
- protected const VALID_PASSWORD = 'correct horse battery staple';
-
- /**
- * Test double for NDB_Config object
- *
- * @var \NDB_Config | PHPUnit\Framework\MockObject\MockObject
- */
- private $_configMock;
-
- /**
- * Test double for Database object
- *
- * @var \Database | PHPUnit\Framework\MockObject\MockObject
- */
- private $_dbMock;
-
- /**
- * NDB_Factory used in tests.
- * Test doubles are injected to the factory object.
- *
- * @var NDB_Factory
- */
- private $_factory;
-
/**
* Setup
*
@@ -57,18 +33,6 @@ class CSVParserTest extends TestCase
protected function setUp(): void
{
parent::setUp();
-
- $configMock = $this->getMockBuilder('NDB_Config')->getMock();
- $dbMock = $this->getMockBuilder('Database')->getMock();
- '@phan-var \NDB_Config $configMock';
- '@phan-var \Database $dbMock';
-
- $this->_configMock = $configMock;
- $this->_dbMock = $dbMock;
-
- $this->_factory = NDB_Factory::singleton();
- $this->_factory->setConfig($this->_configMock);
- $this->_factory->setDatabase($this->_dbMock);
}
/**
@@ -80,7 +44,6 @@ protected function setUp(): void
protected function tearDown(): void
{
parent::tearDown();
- $this->_factory->reset();
}
/**
@@ -152,7 +115,7 @@ public function invalidCSVFiles(): array
file_put_contents($invalidExtensionFilePath, '');
$filePaths[] = [$invalidExtensionFilePath];
- $incongruentData = <<