diff --git a/Makefile b/Makefile index 807bc10df02..1758a171f98 100755 --- a/Makefile +++ b/Makefile @@ -131,7 +131,6 @@ locales: msgfmt -o modules/dictionary/locale/hi/LC_MESSAGES/dictionary.mo modules/dictionary/locale/hi/LC_MESSAGES/dictionary.po npx i18next-conv -l hi -s modules/dictionary/locale/hi/LC_MESSAGES/dictionary.po -t modules/dictionary/locale/hi/LC_MESSAGES/dictionary.json msgfmt -o modules/document_repository/locale/ja/LC_MESSAGES/document_repository.mo modules/document_repository/locale/ja/LC_MESSAGES/document_repository.po - msgfmt -o modules/dqt/locale/ja/LC_MESSAGES/dqt.mo modules/dqt/locale/ja/LC_MESSAGES/dqt.po msgfmt -o modules/electrophysiology_browser/locale/ja/LC_MESSAGES/electrophysiology_browser.mo modules/electrophysiology_browser/locale/ja/LC_MESSAGES/electrophysiology_browser.po msgfmt -o modules/electrophysiology_uploader/locale/ja/LC_MESSAGES/electrophysiology_uploader.mo modules/electrophysiology_uploader/locale/ja/LC_MESSAGES/electrophysiology_uploader.po msgfmt -o modules/examiner/locale/ja/LC_MESSAGES/examiner.mo modules/examiner/locale/ja/LC_MESSAGES/examiner.po diff --git a/SQL/0000-00-01-Modules.sql b/SQL/0000-00-01-Modules.sql index 7ee393b9195..aac7757e1c1 100644 --- a/SQL/0000-00-01-Modules.sql +++ b/SQL/0000-00-01-Modules.sql @@ -51,7 +51,6 @@ INSERT INTO modules (Name, Active) VALUES ('survey_accounts', 'Y'); INSERT INTO modules (Name, Active) VALUES ('timepoint_list', 'Y'); INSERT INTO modules (Name, Active) VALUES ('user_accounts', 'Y'); INSERT INTO modules (Name, Active) VALUES ('electrophysiology_browser', 'Y'); -INSERT INTO modules (Name, Active) VALUES ('dqt', 'Y'); INSERT INTO modules (Name, Active) VALUES ('electrophysiology_uploader', 'Y'); INSERT INTO modules (Name, Active) VALUES ('dataquery', 'Y'); INSERT INTO modules (Name, Active) VALUES ('schedule_module', 'Y'); diff --git a/modules/dataquery/php/module.class.inc b/modules/dataquery/php/module.class.inc index 412d7855a13..c686463f2ef 100644 --- a/modules/dataquery/php/module.class.inc +++ b/modules/dataquery/php/module.class.inc @@ -40,7 +40,7 @@ class Module extends \Module */ public function getLongName() : string { - return dgettext("dataquery", "Data Query Tool (Beta)"); + return dgettext("dataquery", "Data Query Tool"); } /** diff --git a/modules/dqt/.gitignore b/modules/dqt/.gitignore deleted file mode 100644 index 3d39906b97e..00000000000 --- a/modules/dqt/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -js/components -js/react.app.js -js/react.fieldselector.js -js/react.filterBuilder.js -js/react.navigationStepper.js -js/react.notice.js -js/react.paginator.js -js/react.savedqueries.js -js/react.sidebar.js -js/react.tabs.js diff --git a/modules/dqt/README.md b/modules/dqt/README.md deleted file mode 100644 index e1be504c461..00000000000 --- a/modules/dqt/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# Data Query Tool - -## Purpose - -The Data Query Tool (“The DQT”) is used to query and export data stored in LORIS. It is located within the Reports drop-down menu. - -A variety of filters allow users to define parameters and query the exact data fields desired. The extracted data can be downloaded as a single CSV file, and imaging files can also be downloaded as a zipped package of files. Users can visualize or see basic statistical measures from a query. Users can load a previously saved query, and can make saved queries visible to other users. - -## Intended Users - -The DQT module is intended for researchers who wish to download data stored in LORIS. Users can access only internal data that belongs to their LORIS instance. - -## Scope - -The module implements a proxy to pass through queries to the Loris CouchDB DQT backend, which must be setup independently (see Configuration section). - -It does NOT alter data in any way other than that required to convert the data from the MySQL to the CouchDB format. In particular, it does no anonymization or filtering of participant data that is already in LORIS. This must be done independently, before distributing data externally. - -## Other Notes - -The most closely related module to the DQT is the Data Release module, which is used to store snapshots of data for any given release. - -## Permissions - -Users must have `dqt_view` permission to use the DQT. - -It’s important to note that granular permissions do not exist for this module. Anyone with the `dqt_view` permission has access to view, query and download data from the DQT, and therefore has access to all data loaded in the DQT from this LORIS instance. It is not possible to restrict user access to a subset of the data. - -## Interactions with LORIS - -Before the DQT can be used, the CouchDB import scripts must be run in order to load data from LORIS's MySQL tables into CouchDB. These scripts are found in the `tools/` directory, named `CouchDB_Import_*.php`. - -It is recommended to set up a cronjob to execute these scripts to refresh the data on a regular basis. - -## Configuration - -Use of the DQT module requires setting up the CouchDB server and credentials: - -[CouchDB](http://couchdb.apache.org) - -Once CouchDB is installed, follow these steps to complete setup: - -1. Create a database on your local CouchDB instance - -2. Clone the code from our server: - -` -curl -H 'Content-Type: application/json' -X POST http://$YOURCOUCHDBADMIN:$YOURCOUCHADMINPASS@$YOURSERVERNAME:5984/_replicate -d '{"source":"http://couchdb.loris.ca:5984/dataquerytool-1_0_0", "target":"$YOURDATABASENAME"}' -` - -3. Amend the `` section of your LORIS `project/config.xml` as follows: - -```xml - - dqg - localhost - 5984 - adminuser - adminpass - -``` - -4. To load the Data Query Tool with data stored in LORIS, run the `CouchDB_Import_*` scripts, in the `tools/` directory: - -`cd $lorisroot/tools` - -###### Import the base candidate data - -`php CouchDB_Import_Demographics.php` - -###### Import the LORIS instrument data - -This step is optional and is required only if instruments are used: - -`php CouchDB_Import_Instruments.php` - -###### Import the LORIS MRI data - -This step is optional and is required only if the MRI portion of LORIS is installed: - -`php CouchDB_Import_MRI.php` - -#### NOTE - -For developer setup tips, see further instructions here: https://github.com/aces/Data-Query-Tool diff --git a/modules/dqt/ajax/DeleteDoc.php b/modules/dqt/ajax/DeleteDoc.php deleted file mode 100644 index 828e98cfcac..00000000000 --- a/modules/dqt/ajax/DeleteDoc.php +++ /dev/null @@ -1,38 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ -header("Content-Type: application/json"); -$config = \NDB_Config::singleton(); -$couchConfig = $config->getSetting('CouchDB'); -$cdb = \NDB_Factory::singleton()->couchDB( - $couchConfig['dbName'], - $couchConfig['hostname'], - intval($couchConfig['port']), - $couchConfig['admin'], - $couchConfig['adminpass'] -); -$is_author = false; -$docID = urlencode($_REQUEST['DocID']); -$user = User::singleton(); -$tmp_author = explode("_", $docID); -$doc_author = str_replace("global:", '', $tmp_author[0]); -if ($doc_author == $user->getUsername()) { - $is_author = true; -} - -if ($user->hasPermission('superuser') || $is_author) { - $results = $cdb->deleteDoc($docID); - print json_encode($results); -} else { - header("HTTP/1.1 403 Forbidden"); -} diff --git a/modules/dqt/ajax/GetDoc.php b/modules/dqt/ajax/GetDoc.php deleted file mode 100644 index bde5ea22e26..00000000000 --- a/modules/dqt/ajax/GetDoc.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ -$user =& User::singleton(); -if (!$user->hasPermission('dqt_view')) { - header("HTTP/1.1 403 Forbidden"); - exit(0); -} -require_once __DIR__ . '/../../../vendor/autoload.php'; -$client = new NDB_Client(); -$client->makeCommandLine(); -$client->initialize(__DIR__ . "/../../../project/config.xml"); -header("Content-Type: application/json"); - -$config = \NDB_Config::singleton(); -$couchConfig = $config->getSetting('CouchDB'); -$cdb = \NDB_Factory::singleton()->couchDB( - $couchConfig['dbName'], - $couchConfig['hostname'], - intval($couchConfig['port']), - $couchConfig['admin'], - $couchConfig['adminpass'] -); -$docID = urlencode($_REQUEST['DocID']); - -$results = $cdb->getDoc( - $docID -); - -print json_encode($results); - diff --git a/modules/dqt/ajax/datadictionary.php b/modules/dqt/ajax/datadictionary.php deleted file mode 100644 index 72f4d7e77cf..00000000000 --- a/modules/dqt/ajax/datadictionary.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ -$user =& User::singleton(); -if (!$user->hasPermission('dqt_view')) { - header("HTTP/1.1 403 Forbidden"); - exit(0); -} -require_once __DIR__ . '/../../../vendor/autoload.php'; -$client = new NDB_Client(); -$client->makeCommandLine(); -$client->initialize(__DIR__ . "/../../../project/config.xml"); - -$config = \NDB_Config::singleton(); -$couchConfig = $config->getSetting('CouchDB'); -$cdb = \NDB_Factory::singleton()->couchDB( - $couchConfig['dbName'], - $couchConfig['hostname'], - intval($couchConfig['port']), - $couchConfig['admin'], - $couchConfig['adminpass'] -); - -$results = []; -if (isset($_REQUEST['category']) && $_REQUEST['category']) { - $category = urlencode($_REQUEST['category']); - - $results = $cdb->queryView( - "DQG-2.0", - "datadictionary", - [ - "reduce" => "false", - "startkey" => "[\"$category\"]", - "endkey" => "[\"$category\", \"ZZZZZZZZ\"]", - ] - ); -} else if (isset($_REQUEST['keys']) && $_REQUEST['keys']) { - $keys = json_decode($_REQUEST['keys']); - foreach (array_keys($keys) as $index) { - $key = explode('%2C', urlencode($keys[$index] ?? '')); - $query = $cdb->queryView( - "DQG-2.0", - "datadictionary", - [ - "reduce" => "false", - "key" => "[\"$key[0]\",\"$key[1]\"]", - ] - ); - - array_push($results, $query[0]); - } -} - -print json_encode($results); diff --git a/modules/dqt/ajax/getAllSessions.php b/modules/dqt/ajax/getAllSessions.php deleted file mode 100644 index ca99d048dbc..00000000000 --- a/modules/dqt/ajax/getAllSessions.php +++ /dev/null @@ -1,47 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ -$user =& User::singleton(); -if (!$user->hasPermission('dqt_view')) { - header("HTTP/1.1 403 Forbidden"); - exit(0); -} -require_once __DIR__ . '/../../../vendor/autoload.php'; -$client = new NDB_Client(); -$client->makeCommandLine(); -$client->initialize(__DIR__ . "/../../../project/config.xml"); -$config = \NDB_Config::singleton(); -$couchConfig = $config->getSetting('CouchDB'); -$cdb = \NDB_Factory::singleton()->couchDB( - $couchConfig['dbName'], - $couchConfig['hostname'], - intval($couchConfig['port']), - $couchConfig['admin'], - $couchConfig['adminpass'] -); -$results = $cdb->queryView( - "DQG-2.0", - "sessions", - [ - "reduce" => "true", - "group" => "true", - ] -); -$sessionResults = array_map( - function ($element) { - return $element['key']; - }, - $results -); -print json_encode($sessionResults); - diff --git a/modules/dqt/ajax/queryContains.php b/modules/dqt/ajax/queryContains.php deleted file mode 100644 index 5975d2a2360..00000000000 --- a/modules/dqt/ajax/queryContains.php +++ /dev/null @@ -1,77 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ -$user =& User::singleton(); -if (!$user->hasPermission('dqt_view')) { - header("HTTP/1.1 403 Forbidden"); - exit(0); -} -require_once __DIR__ . '/../../../vendor/autoload.php'; -$client = new NDB_Client(); -$client->makeCommandLine(); -$client->initialize(__DIR__ . "/../../../project/config.xml"); -$config = \NDB_Config::singleton(); -$couchConfig = $config->getSetting('CouchDB'); -$cdb = \NDB_Factory::singleton()->couchDB( - $couchConfig['dbName'], - $couchConfig['hostname'], - intval($couchConfig['port']), - $couchConfig['admin'], - $couchConfig['adminpass'] -); -$category = $_REQUEST['category']; -$fieldName = $_REQUEST['field']; -$value = $_REQUEST['value']; -$results = $cdb->queryView( - "DQG-2.0", - "search", - [ - "reduce" => "false", - "startkey" => "[\"$category\", \"$fieldName\"]", - "endkey" => "[\"$category\", \"$fieldName\", {} ]", - ] -); -$sessionResults = array_filter( - $results, - function ($element) use ($value) { - /* Element is of the form: - Array - ( - [id] => 929342e78fd78dfe38e2732d7207b82e - [key] => Array - ( - [0] => adi_r_proband - [1] => Administration - [2] => All - ) - [value] => Array - ( - [0] => STL0138 - [1] => V06 - ) - ); - */ - $fieldVal = $element['key'][2]; - return strpos($fieldVal, $value) !== false; - } -); -$sessionResults = array_values( - array_map( - function ($element) { - return $element['value']; - }, - $sessionResults - ) -); -print json_encode($sessionResults); - diff --git a/modules/dqt/ajax/queryEqual.php b/modules/dqt/ajax/queryEqual.php deleted file mode 100644 index b23329ed78d..00000000000 --- a/modules/dqt/ajax/queryEqual.php +++ /dev/null @@ -1,57 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ -$user =& User::singleton(); -if (!$user->hasPermission('dqt_view')) { - header("HTTP/1.1 403 Forbidden"); - exit(0); -} -require_once __DIR__ . '/../../../vendor/autoload.php'; -$client = new NDB_Client(); -$client->makeCommandLine(); -$client->initialize(__DIR__ . "/../../../project/config.xml"); -$config = \NDB_Config::singleton(); -$couchConfig = $config->getSetting('CouchDB'); -$cdb = \NDB_Factory::singleton()->couchDB( - $couchConfig['dbName'], - $couchConfig['hostname'], - intval($couchConfig['port']), - $couchConfig['admin'], - $couchConfig['adminpass'] -); -$category = $_REQUEST['category']; -$fieldName = $_REQUEST['field']; -$value = $_REQUEST['value']; - -if (!is_numeric($value) && $value !== "null") { - $value = "\"$value\""; -} - -$results = $cdb->queryView( - "DQG-2.0", - "search", - [ - "reduce" => "false", - "key" => "[\"$category\", \"$fieldName\", $value]", - ] -); - -$sessionResults = array_map( - function ($element) { - return $element['value']; - }, - $results -); - -print json_encode($sessionResults); - diff --git a/modules/dqt/ajax/queryGreaterThanEqual.php b/modules/dqt/ajax/queryGreaterThanEqual.php deleted file mode 100644 index 371905b04fc..00000000000 --- a/modules/dqt/ajax/queryGreaterThanEqual.php +++ /dev/null @@ -1,56 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ -$user =& User::singleton(); -if (!$user->hasPermission('dqt_view')) { - header("HTTP/1.1 403 Forbidden"); - exit(0); -} -require_once __DIR__ . '/../../../vendor/autoload.php'; -$client = new NDB_Client(); -$client->makeCommandLine(); -$client->initialize(__DIR__ . "/../../../project/config.xml"); - -$config = \NDB_Config::singleton(); -$couchConfig = $config->getSetting('CouchDB'); -$cdb = \NDB_Factory::singleton()->couchDB( - $couchConfig['dbName'], - $couchConfig['hostname'], - intval($couchConfig['port']), - $couchConfig['admin'], - $couchConfig['adminpass'] -); -$category = $_REQUEST['category']; -$fieldName = $_REQUEST['field']; -$value = $_REQUEST['value']; -$value = is_numeric($value) ? $value : "\"$value\""; - -$results = $cdb->queryView( - "DQG-2.0", - "search", - [ - "reduce" => "false", - "startkey" => "[\"$category\", \"$fieldName\", $value]", - "endkey" => "[\"$category\", \"$fieldName\", {}]", - ] -); - -$sessionResults = array_map( - function ($element) { - return $element['value']; - }, - $results -); - -print json_encode($sessionResults); - diff --git a/modules/dqt/ajax/queryLessThanEqual.php b/modules/dqt/ajax/queryLessThanEqual.php deleted file mode 100644 index 5cf9b9795c3..00000000000 --- a/modules/dqt/ajax/queryLessThanEqual.php +++ /dev/null @@ -1,55 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ -$user =& User::singleton(); -if (!$user->hasPermission('dqt_view')) { - header("HTTP/1.1 403 Forbidden"); - exit(0); -} -require_once __DIR__ . '/../../../vendor/autoload.php'; -$client = new NDB_Client(); -$client->makeCommandLine(); -$client->initialize(__DIR__ . "/../../../project/config.xml"); -$config = \NDB_Config::singleton(); -$couchConfig = $config->getSetting('CouchDB'); -$cdb = \NDB_Factory::singleton()->couchDB( - $couchConfig['dbName'], - $couchConfig['hostname'], - intval($couchConfig['port']), - $couchConfig['admin'], - $couchConfig['adminpass'] -); -$category = $_REQUEST['category']; -$fieldName = $_REQUEST['field']; -$value = $_REQUEST['value']; -$value = is_numeric($value) ? $value : "\"$value\""; - -$results = $cdb->queryView( - "DQG-2.0", - "search", - [ - "reduce" => "false", - "startkey" => "[\"$category\", \"$fieldName\"]", - "endkey" => "[\"$category\", \"$fieldName\", $value]", - ] -); - -$sessionResults = array_map( - function ($element) { - return $element['value']; - }, - $results -); - -print json_encode($sessionResults); - diff --git a/modules/dqt/ajax/queryNotEqual.php b/modules/dqt/ajax/queryNotEqual.php deleted file mode 100644 index 9f4d9224d08..00000000000 --- a/modules/dqt/ajax/queryNotEqual.php +++ /dev/null @@ -1,60 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ -$user =& User::singleton(); -if (!$user->hasPermission('dqt_view')) { - header("HTTP/1.1 403 Forbidden"); - exit(0); -} -require_once __DIR__ . '/../../../vendor/autoload.php'; -$client = new NDB_Client(); -$client->makeCommandLine(); -$client->initialize(__DIR__ . "/../../../project/config.xml"); -$config = \NDB_Config::singleton(); -$couchConfig = $config->getSetting('CouchDB'); -$cdb = \NDB_Factory::singleton()->couchDB( - $couchConfig['dbName'], - $couchConfig['hostname'], - intval($couchConfig['port']), - $couchConfig['admin'], - $couchConfig['adminpass'] -); -$category = $_REQUEST['category']; -$fieldName = $_REQUEST['field']; -$value = $_REQUEST['value']; -// There's no way to do "not" in an index, so we need to -// get all the values of the field name, and then iterate -// through them and exclude the ones where the value is -// equal in PHP. -$results = $cdb->queryView( - "DQG-2.0", - "search", - [ - "reduce" => "false", - "startkey" => "[\"$category\", \"$fieldName\"]", - "endkey" => "[\"$category\", \"$fieldName\", {}]", - ] -); -// TODO: Rewrite this using array_filter and array_map. -$sessionResults = []; -foreach ($results as $row) { - if ($row['key'][2] == $value) { - continue; - } - if ($value === "null" && is_null($row['key'][2])) { - continue; - } - $sessionResults[] = $row['value']; -} -print json_encode($sessionResults); - diff --git a/modules/dqt/ajax/queryStartsWith.php b/modules/dqt/ajax/queryStartsWith.php deleted file mode 100644 index 55d02809844..00000000000 --- a/modules/dqt/ajax/queryStartsWith.php +++ /dev/null @@ -1,56 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ -$user =& User::singleton(); -if (!$user->hasPermission('dqt_view')) { - header("HTTP/1.1 403 Forbidden"); - exit(0); -} -require_once __DIR__ . '/../../../vendor/autoload.php'; -$client = new NDB_Client(); -$client->makeCommandLine(); -$client->initialize(__DIR__ . "/../../../project/config.xml"); -$config = \NDB_Config::singleton(); -$couchConfig = $config->getSetting('CouchDB'); -$cdb = \NDB_Factory::singleton()->couchDB( - $couchConfig['dbName'], - $couchConfig['hostname'], - intval($couchConfig['port']), - $couchConfig['admin'], - $couchConfig['adminpass'] -); -$category = $_REQUEST['category']; -$fieldName = $_REQUEST['field']; -$value = $_REQUEST['value']; -$results = $cdb->queryView( - "DQG-2.0", - "search", - [ - "reduce" => "false", - "startkey" => "[\"$category\", \"$fieldName\", \"$value\"]", - "endkey" => "[\"$category\", \"$fieldName\", \"$value" - // PHP doesn't have any /u9999 unicode escaping, so - // we use mb_convert_encoding to embed a high - // unicode character - . mb_convert_encoding('香', 'UTF-8', 'HTML-ENTITIES') - . "\"]", - ] -); -$sessionResults = array_map( - function ($element) { - return $element['value']; - }, - $results -); -print json_encode($sessionResults); - diff --git a/modules/dqt/ajax/retrieveCategoryDocs.php b/modules/dqt/ajax/retrieveCategoryDocs.php deleted file mode 100644 index 11030b659b5..00000000000 --- a/modules/dqt/ajax/retrieveCategoryDocs.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ - - -ini_set("max_input_vars", '10000'); -$user =& User::singleton(); -if (!$user->hasPermission('dqt_view')) { - header("HTTP/1.1 403 Forbidden"); - exit(0); -} -require_once __DIR__ . '/../../../vendor/autoload.php'; -$client = new NDB_Client(); -$client->makeCommandLine(); -$client->initialize(__DIR__ . "/../../../project/config.xml"); -$config = \NDB_Config::singleton(); -$couchConfig = $config->getSetting('CouchDB'); -$cdb = \NDB_Factory::singleton()->couchDB( - $couchConfig['dbName'], - $couchConfig['hostname'], - intval($couchConfig['port']), - $couchConfig['admin'], - $couchConfig['adminpass'] -); -$category = $_REQUEST['DocType']; -$sessions = json_decode($_REQUEST['Sessions']); -$keys = array_map( - function ($row) use ($category) { - return array_merge([$category], $row); - }, - $sessions -); -$results = $cdb->queryView( - "DQG-2.0", - "instruments", - [ - "reduce" => "false", - "include_docs" => "true", - "keys" => $keys, - ], - true -); -$keys = null; -$cdb = null; -$client = null; -//print $results; -/* -$justTheDocs = array_map(function($row) { - return $row['doc']; -}, -$results); -print json_encode($justTheDocs); - */ - diff --git a/modules/dqt/ajax/saveQuery.php b/modules/dqt/ajax/saveQuery.php deleted file mode 100644 index 053139565b3..00000000000 --- a/modules/dqt/ajax/saveQuery.php +++ /dev/null @@ -1,88 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 - * @link https://www.github.com/aces/Loris/ - */ - -ini_set("max_input_vars", '4000'); -$user =& User::singleton(); -if (!$user->hasPermission('dqt_view')) { - header("HTTP/1.1 403 Forbidden"); - exit(0); -} -require_once __DIR__ . '/../../../vendor/autoload.php'; -$client = new NDB_Client(); -$client->makeCommandLine(); -$client->initialize(__DIR__ . "/../../../project/config.xml"); - -$user = User::singleton(); -$config = \NDB_Config::singleton(); -$couchConfig = $config->getSetting('CouchDB'); -$cdb = \NDB_Factory::singleton()->couchDB( - $couchConfig['dbName'], - $couchConfig['hostname'], - intval($couchConfig['port']), - $couchConfig['admin'], - $couchConfig['adminpass'] -); -$qid = $user->getUserName() . "_" . $_REQUEST['QueryName']; -$qid = rawurlencode($qid); - -if ($_REQUEST['SharedQuery'] === "true") { - $qid = "global:" . $qid; -} - -if ($_REQUEST['OverwriteQuery'] === "false") { - $results = $cdb->getDoc( - $qid - ); - - if (!empty($results)) { - error_log($_REQUEST['SharedQuery']); - header("HTTP/1.1 409 Conflict"); - exit(0); - } -} - -$baseDocument = [ - '_id' => $qid, - 'Meta' => [ - 'DocType' => 'SavedQuery', - 'user' => $user->getUserName(), - ], - 'Fields' => [], - 'Conditions' => [], -]; -if (isset($_REQUEST['QueryName'])) { - $baseDocument['Meta']['name'] = $_REQUEST['QueryName']; -} -if ($_REQUEST['SharedQuery'] === "true") { - $baseDocument['Meta']['user'] = 'global'; - $baseDocument['Meta']['name'] = $user->getUserName() . - ': ' . - $_REQUEST['QueryName']; -} -$fields = json_decode(strval($_REQUEST['Fields'])); -$cond = $_REQUEST['Filters']; -$baseDocument['Conditions'] = $cond; -$baseDocument['Fields'] = $fields; - -$query = []; -if ($_REQUEST['OverwriteQuery'] === "true") { - unset($baseDocument['_id']); - $cdb->replaceDoc($qid, $baseDocument); - $query['id'] = $qid; - print json_encode($query); -} else { - $response = $cdb->postDoc($baseDocument); - print json_encode($response); -} - diff --git a/modules/dqt/css/dataquery.css b/modules/dqt/css/dataquery.css deleted file mode 100644 index a69e5e94bf7..00000000000 --- a/modules/dqt/css/dataquery.css +++ /dev/null @@ -1,673 +0,0 @@ -dt:after { - content: ':'; - font-weight: bold; -} - -dd { - margin-left: 2em; -} - -select, option, input { - color: black; -} - -h4 { - color: black; - background: inherit; /*#064785; */ -} - -h4.modal-title { - color: white; -} - -.modal-dialog .close { - color: white; -} - -.search { - padding-top: 20px; -} - -/* - * CSS for the filter builder - */ -.tree li { - list-style-type: none; - margin: 0; - padding: 10px 5px 0 5px; - position: relative -} - -.tree li::before, .tree li::after { - content: ''; - left: -20px; - position: absolute; - right: auto -} - -.tree li::before { - border-left: 1px solid #999; - bottom: 50px; - height: 100%; - top: 0; - width: 1px -} - -.tree li::after { - border-top: 1px solid #999; - height: 20px; - top: 25px; - width: 25px; -} - -.tree > ul > li::before, .tree > ul > li::after { - border: 0; -} - -.tree li:last-child::before { - height: 30px; -} - -.firstUL { - padding-left: 0; -} - -.instrumentLabel { - word-break: break-all; -} - -.switch { - background: white; - border-color: #064785; -} -.btn-primary-filter { - color: #fff; - background: #064785; -} - -/* - * CSS for managed saved query filters - */ -.savedQueryTree { - margin-left: 0; -} - -/* - * CSS for Field List - */ -.fieldItem { - z-index: initial !important; -} - -/* - * CSS for the loading spinner - */ -.spinner { - margin: 100px auto 0; - width: 70px; - text-align: center; -} - -.spinner > div { - width: 18px; - height: 18px; - background-color: #064785; - - border-radius: 100%; - display: inline-block; - -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; - animation: sk-bouncedelay 1.4s infinite ease-in-out both; -} - -.spinner .bounce1 { - -webkit-animation-delay: -0.32s; - animation-delay: -0.32s; -} - -.spinner .bounce2 { - -webkit-animation-delay: -0.16s; - animation-delay: -0.16s; -} - -@-webkit-keyframes sk-bouncedelay { - 0%, 80%, 100% { - -webkit-transform: scale(0); - } - 40% { - -webkit-transform: scale(1.0); - } -} - -@keyframes sk-bouncedelay { - 0%, 80%, 100% { - -webkit-transform: scale(0); - transform: scale(0); - } - 40% { - -webkit-transform: scale(1.0); - transform: scale(1.0); - } -} - -.loading-header { - color: #064785; -} - -.list-group-item.active, .list-group-item.active:hover { - background-color: #4c8ad5; -} - -.save-button { - margin: 0; - border: 0; - color: #fff; - width: 90px; - height: 45px; - display: flex; - cursor: pointer; - font-size: 12pt; - user-select: none; - border-radius: 30px; - align-items: center; - vertical-align: top; - text-decoration: none; - justify-content: center; - -webkit-appearance: none; - -moz-appearance: none; - background-color: #96398C; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), - 0 6px 20px 0 rgba(0, 0, 0, 0.19); - margin: 10px 0; -} -.save-button:focus { - outline: 0; -} - -.action-button .glyphicon { - top: 0; - font-size: 20px; -} -.action-button { - margin: 0; - border: 0; - color: #fff; - width: 45px; - height: 45px; - display: flex; - cursor: pointer; - font-size: 30px; - user-select: none; - border-radius: 50%; - align-items: center; - text-decoration: none; - justify-content: center; - -webkit-appearance: none; - -moz-appearance: none; -} -.action-button:focus { - outline: 0; -} -.action-button.disabled { - background-color: #dddddd; -} -.action-button.disabled { - cursor: not-allowed; - background-color: #DDDDDD; - pointer-events: all !important; - box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2), - 0 2px 10px 0 rgba(0, 0, 0, 0.19); -} -.action-button.add:hover, .pool:hover{ - box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.2), - 0 8px 22px 0 rgba(0, 0, 0, 0.19); -} -.action-button.update, .action-button.delete { - color: #DDDDDD; - background-color: #FFFFFF; - border: 2px solid #DDDDDD; -} -.action-button.update:hover { - border: none; - color: #FFFFFF; - background-color: #093782; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), - 0 6px 20px 0 rgba(0, 0, 0, 0.19); -} -.action-button.delete:hover { - border: none; - color: #FFFFFF; - background-color: #BC1616; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), - 0 6px 20px 0 rgba(0, 0, 0, 0.19); -} - -.import-csv { - border: 0; - right: 20px; - color: #fff; - width: 250px; - height: 45px; - display: flex; - cursor: pointer; - font-size: 12pt; - user-select: none; - position: absolute; - margin: 20px auto 0; - border-radius: 25px; - align-items: center; - justify-content: center; - background-color: #9e0e4b; -} -.import-csv:focus { - outline: 0; -} - -.action-btn { - border: 0; - color: #fff; - width: 230px; - height: 55px; - display: flex; - cursor: pointer; - font-size: 15pt; - user-select: none; - border-radius: 10px; - align-items: center; - justify-content: center; - line-height: 1em; - margin: 10px auto; - padding-right: 10px; - padding-left: 10px; -} - -@media (max-width: 767px) { - .action-btn { - max-width: 200px; - width: 200px; - min-width: 0; - margin: 5px auto; - } - - .action-btn.request-data { - margin-bottom: 20px; - } -} - -.run-query { - background-color: #0f9d58; -} - -.action-btn:focus { - outline: 0; -} - -.action-btn:disabled { - cursor: not-allowed; - background-color: #d5d5d5; - pointer-events: all !important; -} - -.visualized-data { - background-color: #ff9a24; -} - -.request-data { - background-color: #3f79d6; -} - -.center-items { - margin: 0 auto 0; -} - -.flex-row-container { - display: flex; - flex-wrap: wrap; - min-width: 250px; - margin: 0 5% 0 5%; - padding: 30px 0; - align-items: center; - justify-content: center; - border: 1px solid #b7ccd2; - box-shadow: inset 0 1px 1px rgba(0,0,0,0.05); - background-color: #ecf0f7; -} - -.flex-row-container > .flex-row-item { - flex: 1 1 20%; - min-width: 250px; -} - -.flex-row-item { - margin: 0 auto 0; - min-width: 250px; -} - -.flex-row-container-second { - display: flex; - min-width: 250px; - align-items: center; - margin: 0 5% 10px 5%; - flex-flow: row nowrap; - justify-content: center; -} - -/* - * Hide (default) Download Table as CSV - * from StaticDataTable. - */ -.downloadCSV { - display: none; -} -.fieldVisitsRow { - cursor: default; - background: white; - color: #000; - border-radius: 10px; -} - -/*-- Tooltip --*/ -.c3-tooltip { - border-collapse: collapse; - border-spacing: 0; - background-color: #fff; - empty-cells: show; - -webkit-box-shadow: 7px 7px 12px -9px rgb(119, 119, 119); - -moz-box-shadow: 7px 7px 12px -9px rgb(119, 119, 119); - box-shadow: 7px 7px 12px -9px rgb(119, 119, 119); - opacity: 0.9; -} - -.c3-tooltip tr { - border: 1px solid #CCC; -} - -.c3-tooltip th { - background-color: #aaa; - font-size: 14px; - padding: 2px 5px; - text-align: left; - color: #FFF; -} - -.c3-tooltip td { - font-size: 13px; - padding: 3px 6px; - background-color: #fff; - border-left: 1px dotted #999; -} - -.c3-tooltip td > span { - display: inline-block; - width: 10px; - height: 10px; - margin-right: 6px; -} - -.c3-tooltip td.value { - text-align: right; -} - -#hidden_diseases { - display: none; - color: black; - cursor: default; - pointer-events: none; -} -:checked + #hidden_diseases { - display: block; - pointer-events: none; -} -#diseases_label { - color: #074785; - cursor: pointer; - margin: auto; -} -:hover + #diseases_label { - text-decoration: underline; -} - -table.statsReport { - border-collapse: collapse; - width: 100%; -} - -td.statsReport, th.statsReport { - border: 1px solid #f5f5f5; - text-align: left; - padding: 8px; -} - -tr.statsReport:nth-child(even) { - background-color: #f5f5f5; -} - -.tab-pane { - margin-bottom: 30px -} - -.tab-pane button:not(:first-of-type) { - margin-top: -1px -} - -.navigation-button .glyphicon { - padding-left: 5px; -} - -.investigator .btn { - padding: 6px 9px; -} - -@media (max-width: 767px) { - .investigator > div { - margin-bottom: 20px; - } -} - -.modal { - text-align: center; -} - -@media screen and (min-width: 768px) { - .modal:before { - display: inline-block; - vertical-align: middle; - content: " "; - height: 100%; - } -} - -.modal-dialog-save-query { - display: inline-block; - text-align: left; - min-width: 500px; - min-height: 280px; -} - -.modal-dialog { - display: inline-block; - text-align: left; - vertical-align: middle; -} - -.hideScrollbar { - scrollbar-width: none; - -ms-overflow-style: none; -} -.hideScrollbar::-webkit-scrollbar { - width: 0; - height: 0; - background: transparent; -} - -.fieldVisitsRow::-webkit-scrollbar { - -webkit-appearance: none; -} - -.fieldVisitsRow::-webkit-scrollbar:vertical { - width: 11px; -} - -.fieldVisitsRow::-webkit-scrollbar:horizontal { - height: 11px; -} - -.fieldVisitsRow::-webkit-scrollbar-thumb { - border-radius: 8px; - border: 2px solid white; /* should match background, can't be transparent */ - background-color: rgba(0, 0, 0, .5); -} - -.fieldVisitsRow::-webkit-scrollbar-track { - background-color: #fff; - border-radius: 8px; -} - -/* - * Import CSV feature. - */ -.import-csv { - border: 0; - right: 20px; - color: #fff; - width: 250px; - height: 45px; - display: flex; - cursor: pointer; - font-size: 12pt; - user-select: none; - position: absolute; - margin: 20px auto 0; - border-radius: 25px; - align-items: center; - justify-content: center; - background-color: #9e0e4b; -} -.import-csv:focus { - outline: 0; -} -/* - * Load Query table css - */ -.tableNamesCell { - overflow-x: scroll; - max-width: 150px; - min-height: 100px; -} -.tableFieldsCell { - overflow-x: scroll; - max-width: 280px; - min-height: 100px; -} -.tableFiltersCell { - overflow-x: scroll; - max-width: 200px; - min-height: 100px; -} - -.btn-breadcrumb { - background-color: transparent; - z-index: 10; -} -/* ProgressBar CSS */ -.progressBar { - width: 100%; - min-height: 0; - padding: 39px 0 32px 0; -} -.progressBarContainer { - width: 100%; - margin: 0 auto; - display: table; - text-align: center; -} -.progress-bar-loading-text { - margin: 0; - height: 5px; - color: #0b3570; - display: block; - font-size: 8pt; -} -progress { - width: 100%; - height: 2px; - border: none; - max-width: 600px; - -webkit-appearance: none; -} -progress::-moz-progress-bar { - background: #0b4681; -} -progress::-webkit-progress-value { - background: #0b4681; -} -progress::-webkit-progress-bar { - background: #d4d4d4; -} -/* Navigation Stepper CSS */ -.navigationStepper { - position: absolute; - text-align: right; - margin-top: -70px; - z-index: 15; - right: 35px; - opacity: 1; - transition: opacity 3s; -} -.stepperContainer { - padding: 30px 0 0 0; - min-height: 0; - width: 100%; - opacity: 1; - transition: opacity 1s; -} -.stepper { - width: 100%; - margin: 0 auto; - display: table; -} -@media only screen and (max-width: 615px) { - .navigationStepper {} - .stepperContainer { - margin-top: 70px; - } -} -@media screen and ( device-width: 616px ) { - .navigationStepper { - margin-top: -70px; - } - .stepperContainer { - margin-top: 0; - } -} -.navigation-button { - border: 0; - color: #fff; - min-width: 120px; - padding: 0 20px; - height: 45px; - display: flex; - cursor: pointer; - font-size: 12pt; - user-select: none; - border-radius: 30px; - align-items: center; - vertical-align: top; - text-decoration: none; - justify-content: center; - -webkit-appearance: none; - -moz-appearance: none; - background-color: #3f79d6; - box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2), - 0 2px 10px 0 rgba(0, 0, 0, 0.19); - margin: 10px 0; -} -.navigation-button:focus { - outline: 0; -} -.navigation-button .glyphicon { - top: 0; -} -.navigation-button.disabled { - cursor: not-allowed; - background-color: #dddddd; -} diff --git a/modules/dqt/help/dqt.md b/modules/dqt/help/dqt.md deleted file mode 100644 index fbf4b84f60e..00000000000 --- a/modules/dqt/help/dqt.md +++ /dev/null @@ -1,61 +0,0 @@ -# Data Query Tool (DQT) - -This module provides a simple and easy-to-use interface for querying and exporting data from LORIS. - -There are several tabs within this module, including the default **Info** tab, which provides explanations for the other tabs. - -## Define Fields - -This tab allows you to select a data category or instrument, then add specific fields to your query, as well as specifying which visit or timepoint should be queried. To run a previously saved query, use the *Load Saved Query* tab in this module. - -Once you select an instrument or category (such as `mri_data`) from the _Instrument_ dropdown, field names and descriptions will appear in a table for you to select from, or you can start typing in the *Search within Instrument* search bar to find the right field. - -After selecting a field, you'll notice that all applicable visits are selected - you can unselect any visits you don't want to query. This visit selection will be suggested for the next fields selected in this category or instrument. - -As you add fields to your query, you'll notice they are listed on the right side of the page under "Fields". -Once you have your fields defined, you can continue on to the **Define Filters** tab, or directly to the **View Data** tab if your query does not require filters. - -## Define Filters - -This is where you define the criteria to filter the data for your query. You can apply filters on any field in order to narrow the set of data records returned by a query, such as filtering for a specific value, e.g. `Sex` = `Female`. - -Filters are applied at the level of the candidate. You can apply multiple filters using "And/Or" logic, and organize them in multiple levels. - -To add a filter, select an instrument or category using the dropdown. A secondary dropdown will appear, listing all data fields in that category. Once a data field is selected, additional dropdowns to specify the “Operator” and “Value”, and then a "Visits" dropdown will appear. These _Operators_ are: - - = equal to
- != does not equal
- <= less than or equal to
- \>= greater than or equal to
- _startsWith_ : must begin with a specific character or string
- _contains_ : must contain a specific character or string
- _isNull_ : no value is stored in this field
- _isNotNull_ : any value is stored in this field
- -Click **Add Rule** to define your next rule, and then define how your filters will work together using "And" and "Or" conditional logic. Select "And" if both filters must be true, "Or" if only one must be true for a data record to be selected. (The default is "And".) Click **Add Group** to add filters in layers with different And/Or conditions. - -The "Import Population from CSV" button may be used to import multiple PSCID or DCCID from a CSV file. The format of the CSV file is one column of either PSCID or DCCID identifiers but not both. - -Example `candidates.csv` with contents: -``` -MONT123 -MONT456 -``` - -## View Data - -This tab executes your query, displays the query results, and allows you to download data. -Use the "Data" dropdown to organize the results table: _cross-sectional_ by timepoint, or _longitudinal_ across all timepoints. Click **Run Query** to execute the current query on the database, based on the defined fields and filters. - -## Visualized Data - -This panel is found when clicking the "Visualized Data" button in Run Query tab. -The Visualized Data shows basic statistical calculations and scatterplot visualizations of data from your query. - -## Load Saved Query - -This tab lists all previously saved queries. Hover over the tab to reveal the list, and then select your query. - -## Manage Saved Queries - -This tab lets you review and edit saved queries. If you want to save your current query, click **Save Current Query**, then enter your new query name. Specify that it should be saved publicly if you want it to be visible to other users. Click **Save Changes**. Saved queries will show up in the **Load Saved Query** tab. diff --git a/modules/dqt/js/arrayintersect.js b/modules/dqt/js/arrayintersect.js deleted file mode 100644 index 0df562253af..00000000000 --- a/modules/dqt/js/arrayintersect.js +++ /dev/null @@ -1,122 +0,0 @@ -export const arrayEquals = function(array1, array2) { - // Call the Object.prototype.toString function to determine if this is - // an array or not. This is the way that jQuery does it, to it should - // be reliable. - const ostring = Object.prototype.toString; - // First make sure we're dealing with arrays. - if (ostring.call(array1) !== '[object Array]' - || ostring.call(array2) !== '[object Array]') { - // Not arrays, so fall back on the normal equality operator - return array1 === array2; - }; - - // The arrays have different lengths, so they can't be equal - if (array1.length !== array2.length) { - return false; - } - - // Go through each item from one array, and if it isn't equal to the same - // index in the second array, it means the whole arrays aren't equal - for (let i = 0; i < array1.length; i += 1) { - if (array1[i] !== array2[i]) { - return false; - } - } - - // We've verified that they're the same length and compared each - // element, therefore they're the same - return true; -}; - -export const arrayIntersect = function(arrays) { - // Base cases: the intersection of nothing is empty, - // the intersection of a single array is itself. - if (arrays.length === 0) { - return []; - } - if (arrays.length === 1) { - return arrays[0]; - } - - let obj = {}; - let results = []; - - for (let i = 0; i < arrays.length; i++) { - for (let j = 0; j < arrays[i].length; j++) { - if (i === 0) { - obj[arrays[i][j]] = 1; - } else { - if (obj[arrays[i][j]]) { - obj[arrays[i][j]]++; - } - } - } - } - - for (let k in obj) { - if (obj[k] === arrays.length) { - results.push(k); - } - } - - return results; -}; - -const arrayUnion = function(arrays) { - // Base cases: the union of nothing is empty, - // the union of a single array is itself. - if (arrays.length === 0) { - return []; - } - if (arrays.length === 1) { - return arrays[0]; - } - - let results = []; - let obj = {}; - for (let i = 0; i < arrays.length; i++) { - for (let j = 0; j < arrays[i].length; j++) { - obj[arrays[i][j]] = arrays[i][j]; - } - } - for (const [k] of Object.entries(obj)) { - results.push(obj[k]); - } - return results; -}; - -export const getSessions = function(group) { - let sessions = []; - let session = []; - for (let i = 0; i < group.children.length; i++) { - if (group.children[i].session) { - sessions.push(group.children[i].session); - } - } - if (group.activeOperator == 0) { - session = arrayIntersect(sessions); - } else { - session = arrayUnion(sessions); - } - return session; -}; - -export const enumToArray = function(enumString) { - enumString = enumString.substring('enum(\''.length); // remove 'enum(' - enumString = enumString.slice(0, -2); // remove last 2 characters `')` - const tempArray = enumString.split(/\'\s*,\s*\'/); - let array = []; - for (let i = 0; i < tempArray.length; i++) { - // remove "'" from beginning and end of string - array.push(tempArray[i]); - } - return array; -}; - -/* -//For testing: -a = [['a', 'b'], ['d', 'e'], ['f', 'v']]; -b = [['a', 'b'], ['d', 'e'], ['f', 'x']]; - -console.log(arrayIntersect([a, b])); -*/ diff --git a/modules/dqt/js/ui.stats.js b/modules/dqt/js/ui.stats.js deleted file mode 100644 index 17fbaff849c..00000000000 --- a/modules/dqt/js/ui.stats.js +++ /dev/null @@ -1,115 +0,0 @@ -/* global self: false, importScripts: false, jStat: false */ -importScripts(Loris.BaseURL + '/GetJS.php?Module=dqt&file=jstat.js'); -self.addEventListener('message', function(e) { - 'use strict'; - let data = e.data; - let arrayConvertNumbers = function(arr) { - let r = []; - let i; - for (i = 0; i < arr.length; i += 1) { - if (arr[i] instanceof Array) { - r[i] = arrayConvertNumbers(arr[i]); - } else { - if (parseInt(arr[i], 10) == arr[i]) { - r[i] = parseInt(arr[i], 10); - } else if (parseFloat(arr[i], 10) == arr[i]) { - r[i] = parseFloat(arr[i], 10); - } else if (arr[i] === '.') { - r[i] = null; - } else { - r[i] = arr[i]; - } - } - } - return r; - }; - if (self.d === undefined) { - self.d = jStat(arrayConvertNumbers(data.Data)); - } - switch (data.cmd) { - case 'PopulateTable': - self.populateStatsTable(data.Headers); - break; - case 'PopulateHistogram': - self.createHistogramData(data.Columns); - break; - } -}); - -self.populateStatsTable = function(headers) { - 'use strict'; - let d = self.d; - let quartiles = d.quartiles(); - let min = d.min(); - let max = d.max(); - let stdev = d.stdev(); - let mean = d.mean(); - let meandev = d.meandev(); - let meansqerr = d.meansqerr(); - let i; - - for (i = 1; i < headers.length; i += 1) { - quartiles = d.quartiles(); - self.postMessage({ - Cmd: 'TableAddRow', - Header: headers[i], - Index: i, - RowData: { - 'Minimum': min[i], - 'Maximum': max[i], - 'Standard Deviation': stdev[i], - 'Mean': mean[i], - 'Mean Deviation': meandev[i], - 'Mean Squared Error': meansqerr[i], - 'First Quartile': quartiles[i][0], - 'Second Quartile': quartiles[i][1], - 'Third Quartile': quartiles[i][2], - }, - }); - } - self.postMessage({ - Cmd: 'FinishedTable', - }); -}; - -self.createHistogramData = function(SelectedColumns) { - let i = 0; - let idx; - let plots = []; - let yAxis; - let d = self.d; - let val; - for (i = 0; i < SelectedColumns.length; i += 1) { - idx = SelectedColumns[i]; - yAxis = []; - - // Go through each value - for (j = 0; j < d.length; j += 1) { - val = d[j][idx]; - if (yAxis[val]) { - yAxis[val][1] += 1; - } else { - yAxis[val] = [val, 1]; - } - } - - plots.push({ - data: yAxis, - stack: false, - lines: { - show: false, - steps: false, - }, - bars: { - show: true, - barWidth: 0.9, - align: 'center', - }, - }); - } - - self.postMessage({ - Cmd: 'CreateHistogram', - Plots: plots, - }); -}; diff --git a/modules/dqt/js/workers/savecsv.js b/modules/dqt/js/workers/savecsv.js deleted file mode 100644 index 374a521489e..00000000000 --- a/modules/dqt/js/workers/savecsv.js +++ /dev/null @@ -1,44 +0,0 @@ -/* global self: false, Blob: false */ -self.addEventListener('message', function(e) { - 'use strict'; - let i = 0; - let data = e.data.data; // $("#data").dataTable().fnGetData(), - let headers = e.data.headers; // $("#data thead th"), - let identifiers = e.data.identifiers; - let row = []; - let content = ''; // new Blob(), - let escapeQuote = function(val) { - if (val) { - return val.replace(/"/g, '""'); - } - return val; - }; - // let fs; - let contentBlob; - - row = ['Identifiers']; - row = row.concat(headers); - row = row.map(function(val) { - if (val) { - return val.replace('"', '""'); - } - return val; - }); - content += '"' + row.join('","') + '"' + '\r\n'; - for (i = 0; i < data.length; i += 1) { - row = [identifiers[i]]; - row = row.concat( - data[i].map(escapeQuote) - ); - content += '"' + row.join('","') + '"' + '\r\n'; - } - contentBlob = new Blob([content], { - type: 'text/csv', - }); - // fs = saveAs(contentBlob, "data.csv"); - // fs = new FileSaverSync(contentBlob, "data.csv"); - self.postMessage({ - cmd: 'SaveCSV', - message: contentBlob, - }); -}); diff --git a/modules/dqt/js/workers/savezip.js b/modules/dqt/js/workers/savezip.js deleted file mode 100644 index 70446933021..00000000000 --- a/modules/dqt/js/workers/savezip.js +++ /dev/null @@ -1,108 +0,0 @@ -/* // global self: false, Blob: false */ -self.addEventListener('message', function(e) { - 'use strict'; - importScripts(e.data.BaseURL + '/js/jszip/jszip.min.js'); - let i = 0; - let FileList = e.data.Files; - let generateZip = function() { - // let zipVal; - // let dataView; - let val; - // let blobVal; - self.postMessage({ - cmd: 'CreatingZip', - FileNo: self.FileNo, - }); - val = self.Zip.generate({ - base64: false, - type: 'arraybuffer', - }); - - self.postMessage({ - cmd: 'SaveFile', - message: 'Done', - buffer: val, - Filename: 'files-' + self.currentTime + '-' + self.FileNo + '.zip', - FileNo: self.FileNo, - }, [val]); - - self.Zip = new JSZip(); - self.TotalInCurrentZip = 0; - self.FileNo += 1; - }; - let onLoadHandler = function(File, idx) { - return function(data) { - let dataVal = this.response; - let i; - // let zipVal; - let splitFile = File.split('/'); - - // File is currently "files/DocID/URIEncodedFilename" - // we want the decoded filename only, without the DocID - self.Zip.file('files/' + decodeURIComponent(splitFile[2]), dataVal); - self.xhrMask[idx] = true; - self.complete += 1; - - self.TotalInCurrentZip += self.xhr[idx].response.byteLength; - // We don't need the XMLHttpRequest object anymore, let javascript - // garbage collect it if it wants to. - self.xhr[idx] = undefined; - - self.postMessage({ - cmd: 'Progress', - Complete: self.complete, - Total: self.xhrMask.length, - }); - - - // Split into 256MB chunks. Chrome doesn't seem - // to be able to handle 512MB chunks in Blobs - if (self.TotalInCurrentZip > (512*1024*1024)) { - generateZip(); - } - for (i = 0; i < self.xhrMask.length; i += 1) { - if (self.xhrMask[i] !== true) { - // They aren't all finished, so don't - // post the message below - return; - } - } - // Didn't return in the loop above, which means everything - // is finished. We can save the file even if we haven't hit our - // splitting limit. - generateZip(); - self.postMessage({ - cmd: 'Finished', - /* have to subtract 1 from FileNo since - * it was incremented at the end of the - * last generateZip.. */ - NumFiles: (self.FileNo - 1), - }); - }; - }; - self.Zip = new JSZip(); - self.xhr = []; - self.xhrMask = []; - self.complete = 0; - self.FileNo = 1; - self.TotalInCurrentZip = 0; - self.currentTime = new Date().toISOString(); - - for (i = 0; i < FileList.length; i += 1) { - // Not finished - self.xhrMask[i] = false; - self.xhr[i] = new XMLHttpRequest(); - // WebWorker is in script directory, so file is up a level.. - self.xhr[i].open( - 'GET', - '/mri/jiv/get_file.php?file=' + decodeURIComponent( - FileList[i].split('/')[2] - ) - ); - self.xhr[i].timeout = 1000*60*60*5; // 5 hours - self.xhr[i].responseType = 'arraybuffer'; - self.xhr[i].onload = onLoadHandler(FileList[i], i); - // xhr[i].onloadend = function(e) { self.postMessage({ hello: e}) }; - self.xhr[i].send(); - } -}); diff --git a/modules/dqt/jsx/components/expansionpanels.js b/modules/dqt/jsx/components/expansionpanels.js deleted file mode 100644 index 27be26c2b8f..00000000000 --- a/modules/dqt/jsx/components/expansionpanels.js +++ /dev/null @@ -1,98 +0,0 @@ -import React, {useState} from 'react'; -import PropTypes from 'prop-types'; - -const Panel = (props) => { - const [active, setActive] = useState(props.alwaysOpen); - - const styles = { - accordion: { - default: { - width: '100%', - padding: '18px', - outline: 'none', - color: '#246EB6', - fontSize: '15px', - cursor: props.alwaysOpen ? 'default' : 'pointer', - textAlign: 'center', - backgroundColor: '#fff', - border: '1px solid #246EB6', - transition: '0.4s', - }, - active: { - color: '#fff', - textAlign: 'left', - backgroundColor: '#246EB6', - }, - }, - panel: { - default: { - display: 'none', - padding: '20px 18px', - backgroundColor: '#fff', - border: '1px solid #246EB6', - overflow: 'hidden', - }, - active: { - display: 'block', - margin: '0 0 10px 0', - }, - }, - }; - - const handleExpansionClick = () => { - if (props.alwaysOpen) return; - setActive((active) => !active); - }; - - const styleAccordion = { - ...styles.accordion.default, - ...(active ? styles.accordion.active : {}), - }; - - const stylePanel = { - ...styles.panel.default, - ...(active ? styles.panel.active : {}), - }; - - return ( - <> - -
- {props.content} -
- - ); -}; -Panel.propTypes = { - alwaysOpen: PropTypes.bool, - title: PropTypes.string, - content: PropTypes.element, -}; - -const ExpansionPanels = (props) => { - return ( -
- { props.panels.map((panel, index) => ( - - ))} -
- ); -}; -ExpansionPanels.defaultProps = { - alwaysOpen: false, -}; -ExpansionPanels.propTypes = { - panels: PropTypes.array, -}; - -export default ExpansionPanels; diff --git a/modules/dqt/jsx/components/searchabledropdown.js b/modules/dqt/jsx/components/searchabledropdown.js deleted file mode 100644 index 288bfe464fb..00000000000 --- a/modules/dqt/jsx/components/searchabledropdown.js +++ /dev/null @@ -1,179 +0,0 @@ -import React, {useState} from 'react'; -import PropTypes from 'prop-types'; - -/** - * Search Component - * React wrapper for a searchable dropdown - * - * @param {object} props - React Component properties - * @return {JSX} - React markup for the component - */ -const SearchableDropdown = (props) => { - const [currentInput, setCurrentInput] = useState(); - - const getKeyFromValue = (value) => { - const options = props.options; - return Object.keys(options).find(function(o) { - return options[o] === value; - }); - }; - - const handleChange = (e) => { - let value = getKeyFromValue(e.target.value); - // if not in strict mode and key value is undefined (i.e., not in options prop) - // set value equal to e.target.value - if (!props.strictSearch && value === undefined) { - value = e.target.value; - } - setCurrentInput(e.target.value); - props.onUserInput(props.name, value); - }; - - const handleBlur = (e) => { - // null out entry if not present in options in strict mode - if (props.strictSearch) { - let value = e.target.value; - let options = props.options; - if (Object.values(options).indexOf(value) === -1) { - // empty string out both the hidden value as well as the input text - setCurrentInput(''); - props.onUserInput(props.name, ''); - } - } - }; - - let required = props.required ? 'required' : null; - let disabled = props.disabled ? 'disabled' : null; - let sortByValue = props.sortByValue; - let options = props.options; - let strictMessage = 'Entry must be included in provided list of options.'; - let errorMessage = null; - - // Add error message - if (props.errorMessage) { - errorMessage = {props.errorMessage}; - } else if (props.required && props.value === '') { - let msg = 'This field is required!'; - msg += (props.strictSearch ? ' ' + strictMessage : ''); - errorMessage = {msg}; - } else if (props.strictSearch && props.value === '') { - errorMessage = {strictMessage}; - } - - // determine value to place into text input - let value = ''; - // use value in options if valid - if (props.value !== undefined && - Object.keys(options).indexOf(props.value) > -1) { - value = options[props.value]; - // else, use input text value - } else if (currentInput) { - value = currentInput; - } - - let newOptions = {}; - let optionList = []; - if (sortByValue) { - for (let key in options) { - if (options.hasOwnProperty(key)) { - newOptions[options[key]] = key; - } - } - optionList = Object.keys(newOptions).sort().map(function(option) { - return ( -