diff --git a/.eslintrc.json b/.eslintrc.json index cfb7b4048f9..37500d5dcf0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -47,6 +47,7 @@ "tabWidth": 2, "ignoreComments": true }], + "indent": ["error", 2], "no-unexpected-multiline": "off", "no-unused-vars": "error", "no-useless-escape": "off", diff --git a/.github/workflows/loristest.yml b/.github/workflows/loristest.yml index b6ed076cf69..b48b67cf342 100644 --- a/.github/workflows/loristest.yml +++ b/.github/workflows/loristest.yml @@ -11,7 +11,7 @@ jobs: EEG_VIS_ENABLED: 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install EEG package dependencies # We only need to install protobuf-compiler @@ -36,13 +36,13 @@ jobs: - name: Create node_modules tarball run: tar cfvz node_modules.tar.gz node_modules - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 name: Upload node_modules artifact with: name: node_modules path: node_modules.tar.gz - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 name: Upload lorisjs.tar.gz artifact with: name: lorisjs @@ -52,9 +52,9 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.1', '8.2', '8.3'] + php: ['8.3'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -83,7 +83,7 @@ jobs: - name: Create vendor tarball run: tar cfvz vendor-php${{matrix.php}}.tar.gz vendor - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 name: Upload vendor-php${{matrix.php}}.tar.gz artifact with: name: vendor-php${{matrix.php}} @@ -104,33 +104,34 @@ jobs: strategy: fail-fast: false matrix: - php: ['8.1', '8.2', '8.3'] + php: ['8.3'] apiversion: ['v0.0.3', 'v0.0.4-dev'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 - name: Download node_modules artifact + # Cache node_modules to avoid downloading dependencies if already cached + - name: Cache node_modules + uses: actions/cache@v3 with: - name: node_modules - path: . - - - uses: actions/download-artifact@v3 - name: Download compiled LORIS javascript artifact + path: node_modules + key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + # Download and extract compiled LORIS JavaScript artifact (from build job) + - name: Download compiled LORIS javascript artifact + uses: actions/download-artifact@v4 with: name: lorisjs path: . - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 name: Download PHP dependencies artifact with: name: vendor-php${{matrix.php}} path: . - - name: Extract node_modules - run: tar xfvz node_modules.tar.gz - - name: Extract compiled JS run: tar xfvz lorisjs.tar.gz @@ -209,28 +210,20 @@ jobs: fail-fast: false matrix: testsuite: ['integration'] - php: ['8.1','8.2', '8.3'] + php: ['8.3'] ci_node_index: [0,1,2,3] include: # add a variable but do not display it in the job's name - ci_node_total: 4 - - testsuite: 'static' - php: '8.1' - - testsuite: 'static' - php: '8.2' - testsuite: 'static' php: '8.3' - - testsuite: 'unit' - php: '8.1' - - testsuite: 'unit' - php: '8.2' - testsuite: 'unit' php: '8.3' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index b7e1f8e08e3..36e1e9291e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ core section.*** - ***When possible please provide the number of the pull request(s) containing the changes in the following format: PR #1234*** -## LORIS 26.0 (Release Date: ????-??-??) +## LORIS 26.0 (Release Date: 2024-06-13) ### Core #### Features - Add OpenID Connect authorization support to LORIS (PR #8255) @@ -26,7 +26,7 @@ changes in the following format: PR #1234*** - While proposing a project or editing a project in publications module, prevent indefinite "File to upload" fields from being added if files are browsed then cancelled (PR #9179) - Conflict resolver fixed when Test_name is not equal to table name. This is done be replacing the "TableName" variable with "TestName" everywhere in resolved & unresolved conflicts tables as well as modules (PR #9270) -## LORIS 25.0 (Release Date: ????-??-??) +## LORIS 25.0 (Release Date: 2023-07-17) ### Core #### Features - Added new interface intended to be used for querying module data from PHP (PR #8215) @@ -54,6 +54,7 @@ changes in the following format: PR #1234*** - a default project (default_project) used if createVisit or createCandidate is set to true, or for phantom scans - a default cohort (default_cohort) used if createVisit is set to true (PR #8384) - Help and help editor reactification (PR #8309) +- In document repository: Add Upload / edit permission, add "Edit Categories" tab, create category permission (PR #7103) #### Bug Fixes - Fix a Fatal error on the Genomic Browser tabs (PR #8468) diff --git a/README.md b/README.md index cba522a0958..34cc39dce4c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ LORIS (Longitudinal Online Research and Imaging System) is a self-hosted web app * Try the LORIS demo instance at https://demo.loris.ca. -This Readme covers installation of LORIS version 25.0 on Ubuntu. +This Readme covers installation of LORIS version 26.0 on Ubuntu. ([CentOS Readme also available](docs/wiki/00_SERVER_INSTALL_AND_CONFIGURATION/01_LORIS_Install/CentOS/README.md)). diff --git a/SQL/0000-00-00-schema.sql b/SQL/0000-00-00-schema.sql index f2478b53670..42ca97ff510 100644 --- a/SQL/0000-00-00-schema.sql +++ b/SQL/0000-00-00-schema.sql @@ -98,7 +98,7 @@ CREATE TABLE `users` ( `Phone` varchar(15) default NULL, `Fax` varchar(255) default NULL, `Email` varchar(255) NOT NULL default '', - `Privilege` tinyint(1) NOT NULL default '0', + `Privilege` tinyint(1) NOT NULL default 0, `PSCPI` enum('Y','N') NOT NULL default 'N', `DBAccess` varchar(10) NOT NULL default '', `Active` enum('Y','N') NOT NULL default 'Y', @@ -152,14 +152,14 @@ CREATE TABLE `caveat_options` ( CREATE TABLE `candidate` ( `ID` int(10) unsigned NOT NULL AUTO_INCREMENT, - `CandID` int(6) NOT NULL DEFAULT '0', + `CandID` int(6) NOT NULL, `PSCID` varchar(255) NOT NULL DEFAULT '', `ExternalID` varchar(255) DEFAULT NULL, `DoB` date DEFAULT NULL, `DoD` date DEFAULT NULL, `EDC` date DEFAULT NULL, `Sex` varchar(255) DEFAULT NULL, - `RegistrationCenterID` integer unsigned NOT NULL DEFAULT '0', + `RegistrationCenterID` integer unsigned NOT NULL, `RegistrationProjectID` int(10) unsigned NOT NULL, `Ethnicity` varchar(255) DEFAULT NULL, `Active` enum('Y','N') NOT NULL DEFAULT 'Y', @@ -193,7 +193,7 @@ CREATE TABLE `candidate` ( CREATE TABLE `session` ( `ID` int(10) unsigned NOT NULL AUTO_INCREMENT, - `CandID` int(6) NOT NULL DEFAULT '0', + `CandID` int(6) NOT NULL, `CenterID` integer unsigned NOT NULL, `ProjectID` int(10) unsigned NOT NULL, `VisitNo` smallint(5) unsigned DEFAULT NULL, @@ -276,17 +276,23 @@ CREATE TABLE `instrument_subtests` ( `Test_name` varchar(255) NOT NULL default '', `Subtest_name` varchar(255) NOT NULL default '', `Description` varchar(255) NOT NULL default '', - `Order_number` int(11) NOT NULL default '0', + `Order_number` int(11) NOT NULL default 0, UNIQUE KEY `unique_index` (`Test_name`, `Subtest_name`), PRIMARY KEY (`ID`), KEY `FK_instrument_subtests_1` (`Test_name`), CONSTRAINT `FK_instrument_subtests_1` FOREIGN KEY (`Test_name`) REFERENCES `test_names` (`Test_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `instrument_data` ( + `ID` int(10) unsigned NOT NULL AUTO_INCREMENT, + `Data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`Data`)), + PRIMARY KEY (`ID`) +); + CREATE TABLE `flag` ( `ID` int(10) unsigned NOT NULL auto_increment, - `SessionID` int(10) unsigned NOT NULL default '0', - `Test_name` varchar(255) NOT NULL default '', + `SessionID` int(10) unsigned NOT NULL, + `TestID` int(10) unsigned NOT NULL, `CommentID` varchar(255) NOT NULL default '', `Data_entry` enum('In Progress','Complete') default NULL, `Required_elements_completed` enum('Y','N') NOT NULL default 'N', @@ -295,18 +301,18 @@ CREATE TABLE `flag` ( `Exclusion` enum('Fail','Pass') default NULL, `UserID` varchar(255) default NULL, `Testdate` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, - `Data` TEXT default NULL, + `DataID` int(10) unsigned default NULL, PRIMARY KEY (`CommentID`), KEY `flag_ID` (`ID`), KEY `flag_SessionID` (`SessionID`), - KEY `flag_Test_name` (`Test_name`), KEY `flag_Exclusion` (`Exclusion`), KEY `flag_Data_entry` (`Data_entry`), KEY `flag_Validity` (`Validity`), KEY `flag_Administration` (`Administration`), KEY `flag_UserID` (`UserID`), CONSTRAINT `FK_flag_1` FOREIGN KEY (`SessionID`) REFERENCES `session` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `FK_flag_2` FOREIGN KEY (`Test_name`) REFERENCES `test_names` (`Test_name`) + CONSTRAINT `FK_flag_3` FOREIGN KEY (`DataID`) REFERENCES `instrument_data` (`ID`), + CONSTRAINT `FK_ibfk_1` FOREIGN KEY (`TestID`) REFERENCES `test_names` (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `history` ( @@ -370,13 +376,13 @@ CREATE TABLE `tarchive` ( `DateAcquired` date default NULL, `DateFirstArchived` datetime default NULL, `DateLastArchived` datetime default NULL, - `AcquisitionCount` int(11) NOT NULL default '0', - `NonDicomFileCount` int(11) NOT NULL default '0', - `DicomFileCount` int(11) NOT NULL default '0', + `AcquisitionCount` int(11) NOT NULL default 0, + `NonDicomFileCount` int(11) NOT NULL default 0, + `DicomFileCount` int(11) NOT NULL default 0, `md5sumDicomOnly` varchar(255) default NULL, `md5sumArchive` varchar(255) default NULL, `CreatingUser` varchar(255) NOT NULL default '', - `sumTypeVersion` tinyint(4) NOT NULL default '0', + `sumTypeVersion` tinyint(4) NOT NULL default 0, `tarTypeVersion` tinyint(4) default NULL, `SourceLocation` varchar(255) NOT NULL default '', `ArchiveLocation` varchar(255) default NULL, @@ -385,12 +391,12 @@ CREATE TABLE `tarchive` ( `ScannerSerialNumber` varchar(255) NOT NULL default '', `ScannerSoftwareVersion` varchar(255) NOT NULL default '', `SessionID` int(10) unsigned default NULL, - `uploadAttempt` tinyint(4) NOT NULL default '0', + `uploadAttempt` tinyint(4) NOT NULL default 0, `CreateInfo` text, `AcquisitionMetadata` longtext NOT NULL, `TarchiveID` int(11) NOT NULL auto_increment, `DateSent` datetime DEFAULT NULL, - `PendingTransfer` tinyint(1) NOT NULL DEFAULT '0', + `PendingTransfer` tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (`TarchiveID`), KEY `SessionID` (`SessionID`), CONSTRAINT `FK_tarchive_sessionID` @@ -399,8 +405,8 @@ CREATE TABLE `tarchive` ( CREATE TABLE `tarchive_series` ( `TarchiveSeriesID` int(11) NOT NULL auto_increment, - `TarchiveID` int(11) NOT NULL default '0', - `SeriesNumber` int(11) NOT NULL default '0', + `TarchiveID` int(11) NOT NULL, + `SeriesNumber` int(11) NOT NULL default 0, `SeriesDescription` varchar(255) default NULL, `SequenceName` varchar(255) default NULL, `EchoTime` double default NULL, @@ -408,7 +414,7 @@ CREATE TABLE `tarchive_series` ( `InversionTime` double default NULL, `SliceThickness` double default NULL, `PhaseEncoding` varchar(255) default NULL, - `NumberOfFiles` int(11) NOT NULL default '0', + `NumberOfFiles` int(11) NOT NULL default 0, `SeriesUID` varchar(255) default NULL, `Modality` ENUM ('MR', 'PT') default NULL, PRIMARY KEY (`TarchiveSeriesID`), @@ -418,7 +424,7 @@ CREATE TABLE `tarchive_series` ( CREATE TABLE `tarchive_files` ( `TarchiveFileID` int(11) NOT NULL auto_increment, - `TarchiveID` int(11) NOT NULL default '0', + `TarchiveID` int(11) NOT NULL, `TarchiveSeriesID` INT(11) DEFAULT NULL, `SeriesNumber` int(11) default NULL, `FileNumber` int(11) default NULL, @@ -439,8 +445,8 @@ CREATE TABLE `tarchive_files` ( CREATE TABLE `hrrt_archive` ( `HrrtArchiveID` INT(11) NOT NULL AUTO_INCREMENT, `SessionID` INT(10) unsigned DEFAULT NULL, - `EcatFileCount` INT(11) NOT NULL DEFAULT '0', - `NonEcatFileCount` INT(11) NOT NULL DEFAULT '0', + `EcatFileCount` INT(11) NOT NULL DEFAULT 0, + `NonEcatFileCount` INT(11) NOT NULL DEFAULT 0, `DateAcquired` DATE DEFAULT NULL, `DateArchived` DATETIME DEFAULT NULL, `PatientName` VARCHAR(50) NOT NULL DEFAULT '', @@ -458,7 +464,7 @@ CREATE TABLE `hrrt_archive` ( CREATE TABLE `hrrt_archive_files` ( `HrrtArchiveFileID` INT(11) NOT NULL AUTO_INCREMENT, - `HrrtArchiveID` INT(11) NOT NULL DEFAULT '0', + `HrrtArchiveID` INT(11) NOT NULL, `Blake2bHash` VARCHAR(255) NOT NULL, `FileName` VARCHAR(255) NOT NULL, PRIMARY KEY (`HrrtArchiveFileID`), @@ -502,7 +508,7 @@ CREATE TABLE `mri_processing_protocol` ( `ProtocolFile` varchar(255) NOT NULL DEFAULT '', `FileType` varchar(12) DEFAULT NULL, `Tool` varchar(255) NOT NULL DEFAULT '', - `InsertTime` int(10) unsigned NOT NULL DEFAULT '0', + `InsertTime` int(10) unsigned NOT NULL DEFAULT 0, `md5sum` varchar(32) DEFAULT NULL, PRIMARY KEY (`ProcessProtocolID`), CONSTRAINT `FK_mri_processing_protocol_FileTypes` FOREIGN KEY (`FileType`) REFERENCES `ImagingFileTypes`(`type`) @@ -561,7 +567,7 @@ INSERT INTO `mri_scan_type` VALUES CREATE TABLE `files` ( `FileID` int(10) unsigned NOT NULL auto_increment, - `SessionID` int(10) unsigned NOT NULL default '0', + `SessionID` int(10) unsigned NOT NULL, `File` varchar(255) NOT NULL default '', `SeriesUID` varchar(64) DEFAULT NULL, `EchoTime` double DEFAULT NULL, @@ -572,10 +578,10 @@ CREATE TABLE `files` ( `AcquisitionProtocolID` int(10) unsigned default NULL, `FileType` varchar(12) default NULL, `InsertedByUserID` varchar(255) NOT NULL default '', - `InsertTime` int(10) unsigned NOT NULL default '0', + `InsertTime` int(10) unsigned NOT NULL default 0, `SourcePipeline` varchar(255), `PipelineDate` date, - `SourceFileID` int(10) unsigned DEFAULT '0', + `SourceFileID` int(10) unsigned, `ProcessProtocolID` int(11) unsigned, `Caveat` tinyint(1) default NULL, `TarchiveSource` int(11) default NULL, @@ -709,7 +715,7 @@ CREATE TABLE `mri_upload` ( `UploadDate` DateTime DEFAULT NULL, `UploadLocation` varchar(255) NOT NULL DEFAULT '', `DecompressedLocation` varchar(255) NOT NULL DEFAULT '', - `InsertionComplete` tinyint(1) NOT NULL DEFAULT '0', + `InsertionComplete` tinyint(1) NOT NULL DEFAULT 0, `Inserting` tinyint(1) DEFAULT NULL, `PatientName` varchar(255) NOT NULL DEFAULT '', `number_of_mincInserted` int(11) DEFAULT NULL, @@ -717,7 +723,7 @@ CREATE TABLE `mri_upload` ( `TarchiveID` int(11) DEFAULT NULL, `SessionID` int(10) unsigned DEFAULT NULL, `IsCandidateInfoValidated` tinyint(1) DEFAULT NULL, - `IsTarchiveValidated` tinyint(1) NOT NULL DEFAULT '0', + `IsTarchiveValidated` tinyint(1) NOT NULL DEFAULT 0, `IsPhantom` enum('N','Y') NOT NULL DEFAULT 'N', PRIMARY KEY (`UploadID`), KEY (`SessionID`), @@ -1036,7 +1042,7 @@ CREATE TABLE `mri_protocol_violated_scans` ( CREATE TABLE `document_repository_categories` ( `id` int(3) unsigned NOT NULL AUTO_INCREMENT, `category_name` varchar(255) DEFAULT NULL, - `parent_id` int(3) DEFAULT '0', + `parent_id` int(3), `comments` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -1057,8 +1063,8 @@ CREATE TABLE `document_repository` ( `For_site` int(2) DEFAULT NULL, `comments` text, `multipart` enum('Yes','No') DEFAULT NULL, - `EARLI` tinyint(1) DEFAULT '0', - `hide_video` tinyint(1) DEFAULT '0', + `EARLI` tinyint(1) DEFAULT 0, + `hide_video` tinyint(1) DEFAULT 0, `File_category` int(3) unsigned DEFAULT NULL, PRIMARY KEY (`record_id`), KEY `fk_document_repository_1_idx` (`File_category`), @@ -1073,7 +1079,7 @@ CREATE TABLE `document_repository` ( CREATE TABLE `notification_types` ( `NotificationTypeID` int(11) NOT NULL auto_increment, `Type` varchar(255) NOT NULL default '', - `private` tinyint(1) default '0', + `private` tinyint(1) default 0, `Description` text, PRIMARY KEY (`NotificationTypeID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -1098,8 +1104,8 @@ INSERT INTO `notification_types` (Type,private,Description) VALUES CREATE TABLE `notification_spool` ( `NotificationID` int(11) NOT NULL auto_increment, - `NotificationTypeID` int(11) NOT NULL default '0', - `ProcessID` int(11) NOT NULL DEFAULT '0', + `NotificationTypeID` int(11) NOT NULL, + `ProcessID` int(11) NOT NULL, `TimeSpooled` datetime DEFAULT NULL, `Message` text, `Error` enum('Y','N') default NULL, @@ -1274,7 +1280,7 @@ SET @tmp_val = NULL; CREATE TABLE `participant_status` ( `ID` int(10) unsigned NOT NULL AUTO_INCREMENT, - `CandID` int(6) NOT NULL DEFAULT '0', + `CandID` int(6) NOT NULL, `UserID` varchar(255) DEFAULT NULL, `entry_staff` varchar(255) DEFAULT NULL, `data_entry_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -1313,7 +1319,7 @@ CREATE TABLE `participant_emails` ( CREATE TABLE `participant_status_history` ( `ID` int(10) unsigned NOT NULL AUTO_INCREMENT, - `CandID` int(6) NOT NULL DEFAULT '0', + `CandID` int(6) NOT NULL, `entry_staff` varchar(255) DEFAULT NULL, `data_entry_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `participant_status` int(11) DEFAULT NULL, @@ -1361,7 +1367,7 @@ CREATE TABLE `examiners_psc_rel` ( CREATE TABLE `certification` ( `certID` int(10) unsigned NOT NULL AUTO_INCREMENT, - `examinerID` int(10) unsigned NOT NULL DEFAULT '0', + `examinerID` int(10) unsigned NOT NULL, `date_cert` date DEFAULT NULL, `visit_label` varchar(255) DEFAULT NULL, `testID` int(10) UNSIGNED NOT NULL, @@ -1473,7 +1479,7 @@ CREATE TABLE `media` ( `file_type` varchar(255) DEFAULT NULL, `data_dir` varchar(255) NOT NULL, `uploaded_by` varchar(255) DEFAULT NULL, - `hide_file` tinyint(1) DEFAULT '0', + `hide_file` tinyint(1) DEFAULT 0, `last_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `language_id` int(10) unsigned DEFAULT NULL, PRIMARY KEY (`id`), @@ -1543,7 +1549,7 @@ CREATE TABLE `issues_history` ( `issueHistoryID` int(11) unsigned NOT NULL AUTO_INCREMENT, `newValue` longtext NOT NULL, `dateAdded` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `fieldChanged` enum('assignee','status','comment','sessionID','centerID','title','category','module','lastUpdatedBy','priority','candID', 'description') NOT NULL DEFAULT 'comment', + `fieldChanged` enum('assignee','status','comment','sessionID','centerID','title','category','module','lastUpdatedBy','priority','candID', 'description','watching') NOT NULL DEFAULT 'comment', `issueID` int(11) unsigned NOT NULL, `addedBy` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`issueHistoryID`), @@ -1612,8 +1618,8 @@ CREATE TABLE `parameter_type` ( `SourceField` text, `SourceFrom` VARCHAR(255), `SourceCondition` text, - `Queryable` tinyint(1) default '1', - `IsFile` tinyint(1) default '0', + `Queryable` tinyint(1) default 1, + `IsFile` tinyint(1) default 0, PRIMARY KEY (`ParameterTypeID`), KEY `name` (`Name`), UNIQUE `name_sourceFrom_index` (`Name`, `SourceFrom`) @@ -1757,8 +1763,8 @@ INSERT INTO `parameter_type_category` (Name, Type) VALUES ('Electrophysiology Variables', 'Metavars'); CREATE TABLE `parameter_type_category_rel` ( - `ParameterTypeID` int(11) unsigned NOT NULL default '0', - `ParameterTypeCategoryID` int(11) unsigned NOT NULL default '0', + `ParameterTypeID` int(11) unsigned NOT NULL, + `ParameterTypeCategoryID` int(11) unsigned NOT NULL, PRIMARY KEY (`ParameterTypeCategoryID`,`ParameterTypeID`), KEY `FK_parameter_type_category_rel_1` (`ParameterTypeID`), CONSTRAINT `FK_parameter_type_category_rel_1` FOREIGN KEY (`ParameterTypeID`) REFERENCES `parameter_type` (`ParameterTypeID`) ON DELETE CASCADE, @@ -1772,10 +1778,10 @@ INSERT INTO parameter_type_category_rel (ParameterTypeID,ParameterTypeCategoryID CREATE TABLE `parameter_candidate` ( `ParameterCandidateID` int(10) unsigned NOT NULL auto_increment, - `CandID` int(6) NOT NULL default '0', - `ParameterTypeID` int(10) unsigned NOT NULL default '0', + `CandID` int(6) NOT NULL, + `ParameterTypeID` int(10) unsigned NOT NULL, `Value` varchar(255) default NULL, - `InsertTime` int(10) unsigned NOT NULL default '0', + `InsertTime` int(10) unsigned NOT NULL default 0, PRIMARY KEY (`ParameterCandidateID`), KEY `candidate_type` (`CandID`,`ParameterTypeID`), KEY `parameter_value` (`ParameterTypeID`,`Value`(64)), @@ -1785,10 +1791,10 @@ CREATE TABLE `parameter_candidate` ( CREATE TABLE `parameter_file` ( `ParameterFileID` int(10) unsigned NOT NULL auto_increment, - `FileID` int(10) unsigned NOT NULL default '0', - `ParameterTypeID` int(10) unsigned NOT NULL default '0', + `FileID` int(10) unsigned NOT NULL, + `ParameterTypeID` int(10) unsigned NOT NULL, `Value` longtext, - `InsertTime` int(10) unsigned NOT NULL default '0', + `InsertTime` int(10) unsigned NOT NULL default 0, PRIMARY KEY (`ParameterFileID`), UNIQUE KEY `file_type_uniq` (`FileID`,`ParameterTypeID`), KEY `parameter_value` (`ParameterTypeID`,`Value`(64)), @@ -1798,10 +1804,10 @@ CREATE TABLE `parameter_file` ( CREATE TABLE `parameter_session` ( `ParameterSessionID` int(10) unsigned NOT NULL auto_increment, - `SessionID` int(10) unsigned NOT NULL default '0', - `ParameterTypeID` int(10) unsigned NOT NULL default '0', + `SessionID` int(10) unsigned NOT NULL, + `ParameterTypeID` int(10) unsigned NOT NULL, `Value` varchar(255) default NULL, - `InsertTime` int(10) unsigned NOT NULL default '0', + `InsertTime` int(10) unsigned NOT NULL default 0, PRIMARY KEY (`ParameterSessionID`), KEY `session_type` (`SessionID`,`ParameterTypeID`), KEY `parameter_value` (`ParameterTypeID`,`Value`(64)), @@ -1882,8 +1888,8 @@ CREATE TABLE `SNP` ( CREATE TABLE `SNP_candidate_rel` ( `ID` bigint(20) NOT NULL AUTO_INCREMENT, - `SNPID` bigint(20) NOT NULL DEFAULT '0', - `CandID` int(6) NOT NULL DEFAULT '0', + `SNPID` bigint(20) NOT NULL, + `CandID` int(6) NOT NULL, `AlleleA` enum('A','C','T','G') DEFAULT NULL, `AlleleB` enum('A','C','T','G') DEFAULT NULL, `ArrayReport` enum('Normal','Uncertain','Pending') DEFAULT NULL, @@ -2190,7 +2196,7 @@ INSERT INTO `feedback_mri_comment_types` (CommentName,CommentType,CommentStatusF CREATE TABLE `feedback_mri_predefined_comments` ( `PredefinedCommentID` int(11) unsigned NOT NULL auto_increment, - `CommentTypeID` int(11) unsigned NOT NULL default '0', + `CommentTypeID` int(11) unsigned NOT NULL, `Comment` text NOT NULL, PRIMARY KEY (`PredefinedCommentID`), KEY `CommentType` (`CommentTypeID`), @@ -2246,7 +2252,7 @@ CREATE TABLE `feedback_mri_comments` ( `PhaseEncodingDirection` VARCHAR(3) DEFAULT NULL, `EchoNumber` VARCHAR(20) DEFAULT NULL, `SessionID` int(10) unsigned default NULL, - `CommentTypeID` int(11) unsigned NOT NULL default '0', + `CommentTypeID` int(11) unsigned NOT NULL, `PredefinedCommentID` int(11) unsigned default NULL, `Comment` text, `ChangeTime` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, diff --git a/SQL/0000-00-02-Permission.sql b/SQL/0000-00-02-Permission.sql index aa6d26a03fc..115eb816fd5 100644 --- a/SQL/0000-00-02-Permission.sql +++ b/SQL/0000-00-02-Permission.sql @@ -117,7 +117,7 @@ INSERT INTO `permissions` VALUES (45,'publication_view', 'Publication Projects',(SELECT ID FROM modules WHERE Name='publication'),'View', 2), (46,'publication_propose', 'Propose Publication Projects',(SELECT ID FROM modules WHERE Name='publication'),NULL, 2), (47,'publication_approve', 'Accept/Reject Publication Projects',(SELECT ID FROM modules WHERE Name='publication'),NULL, 2), - (48, 'candidate_dob_edit', 'Dates of Birth',(SELECT ID FROM modules WHERE Name='candidate_parameters'),'Edit', 2), + (48,'candidate_dob_edit', 'Dates of Birth',(SELECT ID FROM modules WHERE Name='candidate_parameters'),'Edit', 2), (49,'electrophysiology_browser_view_allsites', 'EEGs - All Sites',(SELECT ID FROM modules WHERE Name='electrophysiology_browser'),'View', 2), (50,'electrophysiology_browser_view_site', 'EEGs - Own Sites',(SELECT ID FROM modules WHERE Name='electrophysiology_browser'),'View', 2), (51,'battery_manager_view','Battery Entries',(SELECT ID FROM modules WHERE Name='battery_manager'),'View',2), @@ -126,7 +126,7 @@ INSERT INTO `permissions` VALUES (54,'module_manager_edit', 'Installed Modules',(SELECT ID FROM modules WHERE Name='module_manager'),'Edit', 2), (55,'candidate_dod_edit', 'Dates of Death',(SELECT ID FROM modules WHERE Name='candidate_parameters'),'Edit', 2), (56,'violated_scans_view_ownsite','Violated Scans - Own Sites',(SELECT ID FROM modules WHERE Name='mri_violations'),'View','2'), - (57,'document_repository_edit','Documents',(SELECT ID FROM modules WHERE Name='document_repository'),'Edit/Upload','2'), + (57,'document_repository_upload_edit','Documents',(SELECT ID FROM modules WHERE Name='document_repository'),'Edit/Upload','2'), (58,'survey_accounts_view', 'Candidate Surveys',(SELECT ID FROM modules WHERE Name='survey_accounts'),'View', 2), (59,'imaging_quality_control_view','Flagged Imaging Entries',(SELECT ID FROM modules WHERE Name='imaging_qc'),'View','2'), (60,'behavioural_quality_control_view','Flagged Behavioural Entries',(SELECT ID FROM modules WHERE Name='behavioural_qc'),'View','2'), @@ -135,8 +135,10 @@ INSERT INTO `permissions` VALUES (63,'monitor_eeg_uploads','Monitor EEG uploads',(SELECT ID FROM modules WHERE Name='electrophysiology_uploader'),NULL,'2'), (64,'dataquery_admin','Admin dataquery queries',(SELECT ID FROM modules WHERE Name='dataquery'),NULL,'2'), (65,'schedule_module','Schedule Module - edit and delete the appointment',(SELECT ID FROM modules WHERE Name='schedule_module'),'View/Create/Edit','2'), - (66,'consent_view','Consent module',(SELECT ID FROM modules WHERE Name='consent'),'View','2'), - (67,'consent_edit','Consent module',(SELECT ID FROM modules WHERE Name='consent'),'Edit','2'); + (66,'document_repository_categories','Categories',(SELECT ID FROM modules WHERE Name='document_repository'), 'Edit/Upload/Delete', '2'), + (67,'document_repository_hidden','Restricted files',(SELECT ID FROM modules WHERE Name='document_repository'), 'View', '2'), + (68,'consent_view','Consent module',(SELECT ID FROM modules WHERE Name='consent'),'View','2'), + (69,'consent_edit','Consent module',(SELECT ID FROM modules WHERE Name='consent'),'Edit','2'); INSERT INTO `user_perm_rel` (userID, permID) SELECT u.ID, p.permID diff --git a/SQL/0000-00-03-ConfigTables.sql b/SQL/0000-00-03-ConfigTables.sql index f170f9775bf..f4fb22e499b 100644 --- a/SQL/0000-00-03-ConfigTables.sql +++ b/SQL/0000-00-03-ConfigTables.sql @@ -63,6 +63,7 @@ INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, DataType, INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, DataType, Parent, Label, OrderNumber) SELECT 'dateDisplayFormat', 'The date format to use throughout LORIS for displaying date information - formats for date inputs are browser- and locale-dependent.', 1, 0, 'text', ID, 'Date display format', 28 FROM ConfigSettings WHERE Name="study"; INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, DataType, Parent, Label, OrderNumber) SELECT 'adminContactEmail', 'An email address that users can write to in order to report issues or ask question', 1, 0, 'text', ID, 'Administrator Email', 29 FROM ConfigSettings WHERE Name="study"; INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, DataType, Parent, Label, OrderNumber) SELECT 'UserMaximumDaysInactive', 'The maximum number of days since last login before making a user inactive', 1, 0, 'text', ID, 'Maximum Days Before Making User Inactive', 30 FROM ConfigSettings WHERE Name="study"; +INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, DataType, Parent, Label, OrderNumber) SELECT 'useDoB', 'Use DoB (Date of Birth)', 1, 0, 'boolean', ID, 'Use DoB', 31 FROM ConfigSettings WHERE Name="study"; INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, Label, OrderNumber) VALUES ('paths', 'Specify directories where LORIS-related files are stored or created. Take care when editing these fields as changing them incorrectly can cause certain modules to lose functionality.', 1, 0, 'Paths', 2); INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, DataType, Parent, Label, OrderNumber) SELECT 'imagePath', 'Path to images for display in Imaging Browser (e.g. /data/$project/data/) ', 1, 0, 'text', ID, 'Images', 9 FROM ConfigSettings WHERE Name="paths"; @@ -188,6 +189,7 @@ INSERT INTO Config (ConfigID, Value) SELECT ID, "Example Study" FROM ConfigSetti INSERT INTO Config (ConfigID, Value) SELECT ID, "

Example Study Description

\r\n

This is a sample description for this study, because it is a new LORIS install that has not yet customized this text.

\r\n

A LORIS administrator can customize this text in the configuration module, under the configuration option labeled \"Study Description\"

\r\n

Useful Links

\r\n " FROM ConfigSettings WHERE Name="StudyDescription"; INSERT INTO Config (ConfigID, Value) SELECT ID, "images/neurorgb_web.jpg" FROM ConfigSettings WHERE Name="studylogo"; INSERT INTO Config (ConfigID, Value) SELECT ID, "false" FROM ConfigSettings WHERE Name="useEDC"; +INSERT INTO Config (ConfigID, Value) SELECT ID, "false" FROM ConfigSettings WHERE Name="useDoB"; INSERT INTO Config (ConfigID, Value) SELECT ID, 8 FROM ConfigSettings WHERE Name="ageMin"; INSERT INTO Config (ConfigID, Value) SELECT ID, 11 FROM ConfigSettings WHERE Name="ageMax"; INSERT INTO Config (ConfigID, Value) SELECT ID, "false" FROM ConfigSettings WHERE Name="useFamilyID"; diff --git a/SQL/Cleanup_patches/2023-01-24-DropPresent.sql b/SQL/Cleanup_patches/2023-01-24-DropPresent.sql new file mode 100644 index 00000000000..ab09d81bac8 --- /dev/null +++ b/SQL/Cleanup_patches/2023-01-24-DropPresent.sql @@ -0,0 +1 @@ +ALTER TABLE acknowledgements DROP COLUMN present; diff --git a/SQL/Cleanup_patches/2024-04-24-InstrumentData_table.sql b/SQL/Cleanup_patches/2024-04-24-InstrumentData_table.sql new file mode 100644 index 00000000000..f93796b648c --- /dev/null +++ b/SQL/Cleanup_patches/2024-04-24-InstrumentData_table.sql @@ -0,0 +1,4 @@ + +ALTER TABLE flag DROP COLUMN Data; +-- Reclaim the space that was used by the column +OPTIMIZE TABLE flag; diff --git a/SQL/Cleanup_patches/2024-09-13-NoTestName.sql b/SQL/Cleanup_patches/2024-09-13-NoTestName.sql new file mode 100644 index 00000000000..d6605cd5ca8 --- /dev/null +++ b/SQL/Cleanup_patches/2024-09-13-NoTestName.sql @@ -0,0 +1,2 @@ +ALTER TABLE flag DROP CONSTRAINT FK_flag_2; +ALTER TABLE flag DROP COLUMN test_name; diff --git a/SQL/New_patches/2020-10-28-Doc-Repo-Updates.sql b/SQL/New_patches/2020-10-28-Doc-Repo-Updates.sql new file mode 100644 index 00000000000..040fa0e05f6 --- /dev/null +++ b/SQL/New_patches/2020-10-28-Doc-Repo-Updates.sql @@ -0,0 +1,29 @@ +-- create new permissions to upload and to hide files +INSERT INTO permissions (code, description, moduleID, action, categoryID) VALUES + ( + 'document_repository_categories', + 'Categories', + (SELECT ID FROM modules WHERE Name='document_repository'), + 'Edit/Upload/Delete', + 2 + ), + ( + 'document_repository_hidden', + 'Restricted files', + (SELECT ID FROM modules WHERE Name='document_repository'), + 'View', + 2 + ); + +UPDATE permissions + SET code='document_repository_upload_edit' WHERE code='document_repository_edit'; + +-- give categories and hide permissions to admin +INSERT INTO user_perm_rel (userID, permID) VALUES + ((SELECT ID FROM users WHERE UserID='admin'), (SELECT permID FROM permissions WHERE code='document_repository_categories')), + ((SELECT ID FROM users WHERE UserID='admin'), (SELECT permID FROM permissions WHERE code='document_repository_hidden')); + + +-- create column to hide files +ALTER TABLE document_repository + ADD COLUMN hidden_file enum('yes','no') DEFAULT NULL; \ No newline at end of file diff --git a/SQL/New_patches/2023-12-08_useDoB.sql b/SQL/New_patches/2023-12-08_useDoB.sql new file mode 100644 index 00000000000..e96a7cf02e7 --- /dev/null +++ b/SQL/New_patches/2023-12-08_useDoB.sql @@ -0,0 +1,2 @@ +INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, DataType, Parent, Label, OrderNumber) SELECT 'useDoB', 'Use DoB (Date of Birth)', 1, 0, 'boolean', ID, 'Use DoB', 31 FROM ConfigSettings WHERE Name="study"; +INSERT INTO Config (ConfigID, Value) SELECT ID, "false" FROM ConfigSettings WHERE Name="useDoB"; diff --git a/SQL/New_patches/2024-04-24-InstrumentData_table.sql b/SQL/New_patches/2024-04-24-InstrumentData_table.sql new file mode 100644 index 00000000000..79048478b02 --- /dev/null +++ b/SQL/New_patches/2024-04-24-InstrumentData_table.sql @@ -0,0 +1,22 @@ +CREATE TABLE `instrument_data` ( + `ID` int(10) unsigned NOT NULL AUTO_INCREMENT, + `Data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`Data`)), + PRIMARY KEY (`ID`) +); + +-- we abuse the ID of the flag column for data migration +-- by making it the same as the id in the new table, even +-- though they're different primary keys +INSERT INTO instrument_data SELECT ID, Data FROM flag WHERE Data IS NOT NULL; + +ALTER TABLE flag ADD COLUMN DataID int(10) unsigned; +ALTER TABLE flag ADD FOREIGN KEY (DataID) REFERENCES instrument_data(ID); + +-- from this point forward they won't necessarily be the same id, but we don't +-- care anymore now that the data is migrated and foreign keys were enforced +UPDATE flag SET DataID=ID WHERE Data IS NOT NULL; + +-- FIXME: Put in a cleanup patch +-- ALTER TABLE flag DROP COLUMN Data; +-- Reclaim the space that was used by the column +-- OPTIMIZE TABLE flag; diff --git a/SQL/New_patches/2024-07-16-CleanDefault.sql b/SQL/New_patches/2024-07-16-CleanDefault.sql new file mode 100644 index 00000000000..188b99c502e --- /dev/null +++ b/SQL/New_patches/2024-07-16-CleanDefault.sql @@ -0,0 +1,148 @@ +-- Drop nonsensical defaults and put use right type for others. + +ALTER TABLE `users` + ALTER `Privilege` SET DEFAULT 0; + +ALTER TABLE `candidate` + ALTER `CandID` DROP DEFAULT; + +ALTER TABLE `candidate` + ALTER `RegistrationCenterID` DROP DEFAULT; + +ALTER TABLE `session` + ALTER `CandID` DROP DEFAULT; + +ALTER TABLE `instrument_subtests` + ALTER `Order_number` SET DEFAULT 0; + +ALTER TABLE `flag` + ALTER `SessionID` DROP DEFAULT; + +ALTER TABLE `tarchive` + ALTER `AcquisitionCount` SET DEFAULT 0; + +ALTER TABLE `tarchive` + ALTER `NonDicomFileCount` SET DEFAULT 0; + +ALTER TABLE `tarchive` + ALTER `DicomFileCount` SET DEFAULT 0; + +ALTER TABLE `tarchive` + ALTER `sumTypeVersion` SET DEFAULT 0; + +ALTER TABLE `tarchive` + ALTER `uploadAttempt` SET DEFAULT 0; + +ALTER TABLE `tarchive` + ALTER `PendingTransfer` SET DEFAULT 0; + +ALTER TABLE `tarchive_series` + ALTER `TarchiveID` DROP DEFAULT; + +ALTER TABLE `tarchive_series` + ALTER `SeriesNumber` SET DEFAULT 0; + +ALTER TABLE `tarchive_series` + ALTER `NumberOfFiles` SET DEFAULT 0; + +ALTER TABLE `tarchive_files` + ALTER `TarchiveID` DROP DEFAULT; + +ALTER TABLE `hrrt_archive` + ALTER `EcatFileCount` SET DEFAULT 0; + +ALTER TABLE `hrrt_archive` + ALTER `NonEcatFileCount` SET DEFAULT 0; + +ALTER TABLE `hrrt_archive_files` + ALTER `HrrtArchiveID` DROP DEFAULT; + +ALTER TABLE `mri_processing_protocol` + ALTER `InsertTime` SET DEFAULT 0; + +ALTER TABLE `files` + ALTER `SessionID` DROP DEFAULT; + +ALTER TABLE `files` + ALTER `InsertTime` SET DEFAULT 0; + +ALTER TABLE `files` + ALTER `SourceFileID` DROP DEFAULT; + +ALTER TABLE `mri_upload` + ALTER `InsertionComplete` SET DEFAULT 0; + +ALTER TABLE `mri_upload` + ALTER `IsTarchiveValidated` SET DEFAULT 0; + +ALTER TABLE `document_repository_categories` + ALTER `parent_id` DROP DEFAULT; + +ALTER TABLE `document_repository` + ALTER `EARLI` SET DEFAULT 0; + +ALTER TABLE `document_repository` + ALTER `hide_video` SET DEFAULT 0; + +ALTER TABLE `notification_types` + ALTER `private` SET DEFAULT 0; + +ALTER TABLE `notification_spool` + ALTER `NotificationTypeID` DROP DEFAULT; + +ALTER TABLE `notification_spool` + ALTER `ProcessID` DROP DEFAULT; + +ALTER TABLE `participant_status` + ALTER `CandID` DROP DEFAULT; + +ALTER TABLE `participant_status_history` + ALTER `CandID` DROP DEFAULT; + +ALTER TABLE `certification` + ALTER `examinerID` DROP DEFAULT; + +ALTER TABLE `media` + ALTER `hide_file` SET DEFAULT 0; + +ALTER TABLE `parameter_type` + ALTER `Queryable` SET DEFAULT 1; + +ALTER TABLE `parameter_type` + ALTER `IsFile` SET DEFAULT 0; + +ALTER TABLE `parameter_type_category_rel` + ALTER `ParameterTypeID` DROP DEFAULT; + +ALTER TABLE `parameter_type_category_rel` + ALTER `ParameterTypeCategoryID` DROP DEFAULT; + +ALTER TABLE `parameter_candidate` + ALTER `CandID` DROP DEFAULT; + +ALTER TABLE `parameter_candidate` + ALTER `ParameterTypeID` DROP DEFAULT; + +ALTER TABLE `parameter_candidate` + ALTER `InsertTime` SET DEFAULT 0; + +ALTER TABLE `parameter_session` + ALTER `SessionID` DROP DEFAULT; + +ALTER TABLE `parameter_session` + ALTER `ParameterTypeID` DROP DEFAULT; + +ALTER TABLE `parameter_session` + ALTER `InsertTime` SET DEFAULT 0; + +ALTER TABLE `SNP_candidate_rel` + ALTER `SNPID` DROP DEFAULT; + +ALTER TABLE `SNP_candidate_rel` + ALTER `CandID` DROP DEFAULT; + +ALTER TABLE `feedback_mri_predefined_comments` + ALTER `CommentTypeID` DROP DEFAULT; + +ALTER TABLE `feedback_mri_comments` + ALTER `CommentTypeID` DROP DEFAULT; diff --git a/SQL/New_patches/2024-08-13-issuetracker-AddWatchingToIssuesHistory.sql b/SQL/New_patches/2024-08-13-issuetracker-AddWatchingToIssuesHistory.sql new file mode 100644 index 00000000000..8eedae2578c --- /dev/null +++ b/SQL/New_patches/2024-08-13-issuetracker-AddWatchingToIssuesHistory.sql @@ -0,0 +1,2 @@ +ALTER TABLE `issues_history` + MODIFY `fieldChanged` enum('assignee','status','comment','sessionID','centerID','title','category','module','lastUpdatedBy','priority','candID', 'description', 'watching') NOT NULL DEFAULT 'comment'; diff --git a/SQL/New_patches/2024-09-13-TestID.sql b/SQL/New_patches/2024-09-13-TestID.sql new file mode 100644 index 00000000000..b2131bf97f4 --- /dev/null +++ b/SQL/New_patches/2024-09-13-TestID.sql @@ -0,0 +1,6 @@ +ALTER TABLE flag ADD COLUMN TestID int(10) unsigned AFTER test_name; +ALTER TABLE flag ADD CONSTRAINT FOREIGN KEY (TestID) REFERENCES test_names(ID); + +UPDATE flag f SET TestID=(SELECT ID FROM test_names tn WHERE f.test_name=tn.test_name); + +ALTER TABLE flag MODIFY COLUMN TestID int(10) unsigned NOT NULL; diff --git a/docker-compose.yml b/docker-compose.yml index 36f6a6d60c8..2b20f2ccfb5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '2' +version: '3.8' services: db: build: @@ -13,12 +13,9 @@ services: - MYSQL_RANDOM_ROOT_PASSWORD=yes selenium: - image: selenium/standalone-firefox-debug:3.141.59-zirconium - volumes: - - /dev/shm:/dev/shm + image: selenium/standalone-firefox:4.25 ports: - "5900:5900" - web: build: context: . @@ -62,58 +59,3 @@ services: - selenium - web entrypoint: /app/test/wait-for-services.sh - - selenium-debug: - image: selenium/standalone-firefox-debug:3.141.59-zirconium - links: - - web-debug:web - ports: - - "5901:5900" - - web-debug: - build: - context: . - dockerfile: Dockerfile.test.php8.debug - volumes: - - ./:/app - - ./test/test_instrument:/app/project/instruments - environment: - - LORIS_DB_CONFIG=/app/test/config.xml - - XDEBUG_CONFIG=remote_host=${XDEBUG_REMOTE_HOST} - - PHP_IDE_CONFIG=serverName=LorisTests - depends_on: - - db - command: php -S 0.0.0.0:8000 -t /app/htdocs /app/htdocs/router.php - - unit-tests-debug: - build: - context: . - dockerfile: Dockerfile.test.php8.debug - volumes: - - ./:/app - working_dir: /app - environment: - - LORIS_DB_CONFIG=test/config.xml - - XDEBUG_CONFIG=remote_host=${XDEBUG_REMOTE_HOST} - - PHP_IDE_CONFIG=serverName=LorisTests - depends_on: - - db - entrypoint: /app/test/wait-for-services.sh - - integration-tests-debug: - build: - context: . - dockerfile: Dockerfile.test.php8.debug - volumes: - - ./:/app - working_dir: /app - environment: - - LORIS_DB_CONFIG=test/config.xml - - SELENIUM_REQUIRED=true - - XDEBUG_CONFIG=remote_host=${XDEBUG_REMOTE_HOST} - - PHP_IDE_CONFIG=serverName=LorisTests - links: - - db - - selenium-debug:selenium - - web-debug:web - entrypoint: /app/test/wait-for-services.sh diff --git a/docs/wiki/00_SERVER_INSTALL_AND_CONFIGURATION/01_LORIS_Install/CentOS/README.md b/docs/wiki/00_SERVER_INSTALL_AND_CONFIGURATION/01_LORIS_Install/CentOS/README.md index 5f4f132ebe2..c77ecdbf548 100644 --- a/docs/wiki/00_SERVER_INSTALL_AND_CONFIGURATION/01_LORIS_Install/CentOS/README.md +++ b/docs/wiki/00_SERVER_INSTALL_AND_CONFIGURATION/01_LORIS_Install/CentOS/README.md @@ -10,9 +10,9 @@ For further details on the install process, please see the LORIS GitHub Wiki Cen # System Requirements - Install dependencies Default dependencies installed by CentOS 7.x may not meet the version requirements for LORIS deployment or development: -* MariaDB 10.3 is supported for LORIS 25. +* MariaDB 10.3 is supported for LORIS 26. -* PHP 8.1 (or higher) is supported for LORIS 25. +* PHP 8.2 (or higher) is supported for LORIS 26. In addition to the above, the following packages should be installed with `yum` and may also differ from the packages referenced in the main (Ubuntu) [LORIS Readme](../../../../../README.md). Detailed command examples are provided below (`sudo` privilege may be required depending on your system). * Apache 2.4 or higher diff --git a/htdocs/AjaxHelper.php b/htdocs/AjaxHelper.php index 09dff46b3db..a9521b74b95 100644 --- a/htdocs/AjaxHelper.php +++ b/htdocs/AjaxHelper.php @@ -1,4 +1,5 @@ - { - let combined; - if (remainder.length == 0) { - combined = value; - } else { - combined = new Uint8Array( - value.length + remainder.length - ); - for (let i = 0; i < remainder.length; i++) { - combined[i] = remainder[i]; - } - for (let i = 0; i < value.length; i++) { - combined[i+remainder.length] = value[i]; - } - } - return processLines(combined, rowcb, endstreamcb); - }).then(({remainder: rem, eos}) => { - chunkcb(eos); - doneLoop = eos; - remainder = rem; - }).catch((err) => { - console.error(err); - doneLoop = true; - }); - } + let remainder = []; + let doneLoop = false; + while (!doneLoop) { + await reader.read().then(({done, value}) => { + let combined; + if (remainder.length == 0) { + combined = value; + } else { + combined = new Uint8Array( + value.length + remainder.length + ); + for (let i = 0; i < remainder.length; i++) { + combined[i] = remainder[i]; + } + for (let i = 0; i < value.length; i++) { + combined[i+remainder.length] = value[i]; + } + } + return processLines(combined, rowcb, endstreamcb); + }).then(({remainder: rem, eos}) => { + chunkcb(eos); + doneLoop = eos; + remainder = rem; + }).catch((err) => { + console.error(err); + doneLoop = true; + }); + } } export default fetchDataStream; diff --git a/jsx/Breadcrumbs.js b/jsx/Breadcrumbs.js index cef326354d6..c5ab758b09b 100644 --- a/jsx/Breadcrumbs.js +++ b/jsx/Breadcrumbs.js @@ -106,9 +106,9 @@ class Breadcrumbs extends Component { } else { breadcrumbs.push( + href={url} + className='btn btn-primary' + onClick={onClick}>
{element.text}
@@ -121,10 +121,10 @@ class Breadcrumbs extends Component { breadcrumbDropdown = (
diff --git a/jsx/CSSGrid.js b/jsx/CSSGrid.js index 5c95e8b8aec..b678901d60a 100644 --- a/jsx/CSSGrid.js +++ b/jsx/CSSGrid.js @@ -17,111 +17,111 @@ import PropTypes from 'prop-types'; * @return {object} - A React component for a CSS grid of cards */ function CSSGrid(props) { - const cardsRef = useRef(null); - const [cardWidth, setCardWidth] = useState(0); - const [panelHeights, setPanelHeights] = useState({}); + const cardsRef = useRef(null); + const [cardWidth, setCardWidth] = useState(0); + const [panelHeights, setPanelHeights] = useState({}); - useEffect(() => { - // Upon load, store the calculated height of every rendered panel - // in state, so that we can use it to dynamically set the heights - // (number of rows spanned) in the CSS grid. - if (cardsRef.current.childNodes.length < 1) { - return; - } + useEffect(() => { + // Upon load, store the calculated height of every rendered panel + // in state, so that we can use it to dynamically set the heights + // (number of rows spanned) in the CSS grid. + if (cardsRef.current.childNodes.length < 1) { + return; + } - // All rows in the width have the same width, so only look - // up the first. - const wSize = cardsRef.current.childNodes[0].clientWidth; + // All rows in the width have the same width, so only look + // up the first. + const wSize = cardsRef.current.childNodes[0].clientWidth; - // Do not change the state unless the width changed to avoid - // infinite re-render loops. - if (wSize == cardWidth) { - return; - } - setCardWidth(wSize); + // Do not change the state unless the width changed to avoid + // infinite re-render loops. + if (wSize == cardWidth) { + return; + } + setCardWidth(wSize); - // Store the height in pixels of each panel. The first node is - // the CSS grid element, the first child is the panel. - // The childNodes are the DOM elements, not the React elements, - // but we make the assumption that they're in the same order - // as props.Cards in the DOM, and any re-arranging was done by - // using the CSS order property. - const heights = Array.from(cardsRef.current.childNodes.values()).map( - (node) => (node.firstChild.clientHeight) - ); - setPanelHeights(heights); - }); - const grid = { - display: 'grid', - gridTemplateColumns: '33% 33% 33%', - gridAutoFlow: 'row dense', - gridRowGap: '1em', - rowGap: '1em', - }; + // Store the height in pixels of each panel. The first node is + // the CSS grid element, the first child is the panel. + // The childNodes are the DOM elements, not the React elements, + // but we make the assumption that they're in the same order + // as props.Cards in the DOM, and any re-arranging was done by + // using the CSS order property. + const heights = Array.from(cardsRef.current.childNodes.values()).map( + (node) => (node.firstChild.clientHeight) + ); + setPanelHeights(heights); + }); + const grid = { + display: 'grid', + gridTemplateColumns: '33% 33% 33%', + gridAutoFlow: 'row dense', + gridRowGap: '1em', + rowGap: '1em', + }; - let orderedCards = []; - for (let i = 0; i < props.Cards.length; i++) { - orderedCards.push(props.Cards[i]); - if (!props.Cards[i].Order) { - orderedCards[i].Order = 1; - } + let orderedCards = []; + for (let i = 0; i < props.Cards.length; i++) { + orderedCards.push(props.Cards[i]); + if (!props.Cards[i].Order) { + orderedCards[i].Order = 1; } - orderedCards.sort((a, b) => (a.Order - b.Order)); + } + orderedCards.sort((a, b) => (a.Order - b.Order)); - let lastLargeCardIdx = 0; - for (let i = 0; i < orderedCards.length; i++) { - if (orderedCards[i].Width >= 2) { - lastLargeCardIdx = i; - } + let lastLargeCardIdx = 0; + for (let i = 0; i < orderedCards.length; i++) { + if (orderedCards[i].Width >= 2) { + lastLargeCardIdx = i; } + } - const cards = orderedCards.map((value, idx) => { - let cardID = 'card' + idx; + const cards = orderedCards.map((value, idx) => { + let cardID = 'card' + idx; - let pSize; - let style = {}; - if (value.Width) { - style.gridColumnEnd = 'span ' + value.Width; - if (value.Width == 1 || value.Width === 3) { - if (idx < lastLargeCardIdx) { - style.gridColumnStart = 1; - } - } else if (value.Width == 2) { - style.gridColumnStart = 2; - } - } - - if (cardWidth != 0) { - const pxHeight = panelHeights[idx]; - let spanHeight = 1; - const hSpan = 100; - if ((pxHeight % hSpan) === 0) { - spanHeight = pxHeight / hSpan; - } else { - spanHeight = Math.floor(pxHeight / hSpan) + 1; - } - style.gridRowEnd = 'span ' + spanHeight; - pSize = spanHeight * hSpan; - } - if (value.Order) { - style.order = value.Order; + let pSize; + let style = {}; + if (value.Width) { + style.gridColumnEnd = 'span ' + value.Width; + if (value.Width == 1 || value.Width === 3) { + if (idx < lastLargeCardIdx) { + style.gridColumnStart = 1; } + } else if (value.Width == 2) { + style.gridColumnStart = 2; + } + } - style.alignSelf = 'stretch'; - return ( - - {value.Content} - - ); - }); + if (cardWidth != 0) { + const pxHeight = panelHeights[idx]; + let spanHeight = 1; + const hSpan = 100; + if ((pxHeight % hSpan) === 0) { + spanHeight = pxHeight / hSpan; + } else { + spanHeight = Math.floor(pxHeight / hSpan) + 1; + } + style.gridRowEnd = 'span ' + spanHeight; + pSize = spanHeight * hSpan; + } + if (value.Order) { + style.order = value.Order; + } + style.alignSelf = 'stretch'; return ( -
{cards}
+ + {value.Content} + ); + }); + + return ( +
{cards}
+ ); } CSSGrid.propTypes = { - Cards: PropTypes.array, + Cards: PropTypes.array, }; export default CSSGrid; diff --git a/jsx/Card.js b/jsx/Card.js index d4cab9d2ec2..de2fb12f593 100644 --- a/jsx/Card.js +++ b/jsx/Card.js @@ -21,8 +21,13 @@ class Card extends Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); + this.state = {hasError: false}; } + static getDerivedStateFromError(error) { + console.error(error); + return {hasError: true}; + } /** * Delegate clicks on the card to the onClick handler * @@ -49,10 +54,10 @@ class Card extends Component { boxSizing: 'border-box', }; if (this.props.style) { - divStyling = {...divStyling, ...this.props.style}; + divStyling = {...divStyling, ...this.props.style}; } if (this.props.cardSize) { - divStyling.height = this.props.cardSize; + divStyling.height = this.props.cardSize; } return (
@@ -60,15 +65,19 @@ class Card extends Component { id={this.props.id} title={this.props.title} initCollapsed={this.props.initCollapsed} - style={{overflow: 'auto'}} - panelSize={this.props.cardSize} + style={{overflow: 'auto'}} + panelSize={this.props.cardSize} collapsing={this.props.collapsing} >
- {this.props.children} + {this.state.hasError ?
+ Something went wrong rendering this panel. + Please open a bug report. +
+ : this.props.children}
diff --git a/jsx/DataTable.js b/jsx/DataTable.js index be7f98c52a3..152f69251fe 100644 --- a/jsx/DataTable.js +++ b/jsx/DataTable.js @@ -21,8 +21,8 @@ class DataTable extends Component { rows: 20, }, sort: { - column: -1, - ascending: true, + column: -1, + ascending: true, }, }; @@ -120,17 +120,17 @@ class DataTable extends Component { // Map cell data to proper values if applicable. if (this.props.getMappedCell) { csvData = csvData - .map((row, i) => this.props.fields - .flatMap((field, j) => this.props.getMappedCell( + .map((row, i) => this.props.fields + .flatMap((field, j) => this.props.getMappedCell( field.label, row[j], row, this.props.fields.map( - (val) => val.label, + (val) => val.label, ), j - )) - ); + )) + ); } let csvworker = new Worker(loris.BaseURL + '/js/workers/savecsv.js'); @@ -175,7 +175,7 @@ class DataTable extends Component { let hasFilters = (filterValuesCount !== 0); if (hasFilters === false) { for (let i = 0; i < tableData.length; i++) { - filteredIndexes.push(i); + filteredIndexes.push(i); } return filteredIndexes; } @@ -206,7 +206,7 @@ class DataTable extends Component { if (headerCount === filterValuesCount && ((useKeyword === true && keywordMatch > 0) || (useKeyword === false && keywordMatch === 0))) { - filteredIndexes.push(i); + filteredIndexes.push(i); } } @@ -338,31 +338,31 @@ class DataTable extends Component { if (typeof filterData === 'string') { searchKey = filterData.toLowerCase(); switch (typeof data) { - case 'object': - // Handles the case where the data is an array (typeof 'object') - // and you want to search through it for - // the string you are filtering by - let searchArray = data.map((e) => e.toLowerCase()); - if (exactMatch) { - result = searchArray.includes(searchKey); - } else { - result = ( - searchArray.find( - (e) => (e.indexOf(searchKey) > -1) - ) - ) !== undefined; - } - break; - default: - searchString = data ? data.toString().toLowerCase() : ''; - if (exactMatch) { - result = (searchString === searchKey); - } else if (opposite) { - result = searchString !== searchKey; - } else { - result = (searchString.indexOf(searchKey) > -1); - } - break; + case 'object': + // Handles the case where the data is an array (typeof 'object') + // and you want to search through it for + // the string you are filtering by + let searchArray = data.map((e) => e.toLowerCase()); + if (exactMatch) { + result = searchArray.includes(searchKey); + } else { + result = ( + searchArray.find( + (e) => (e.indexOf(searchKey) > -1) + ) + ) !== undefined; + } + break; + default: + searchString = data ? data.toString().toLowerCase() : ''; + if (exactMatch) { + result = (searchString === searchKey); + } else if (opposite) { + result = searchString !== searchKey; + } else { + result = (searchString.indexOf(searchKey) > -1); + } + break; } } @@ -459,9 +459,9 @@ class DataTable extends Component { if (this.props.fields[i].freezeColumn === true) { headers.push( { - this.setSortColumn(i); - }}> + onClick={() => { + this.setSortColumn(i); + }}> {this.props.fields[i].label} ); @@ -485,59 +485,59 @@ class DataTable extends Component { // Format each cell for the data table. for (let i = currentPageRow; - (i < filteredCount) && (rows.length < rowsPerPage); - i++ + (i < filteredCount) && (rows.length < rowsPerPage); + i++ ) { - let rowIndex = index[i].RowIdx; - let rowData = this.props.data[rowIndex]; - let curRow = []; - - // Iterates through headers to populate row columns - // with corresponding data - for (let j = 0; j < this.props.fields.length; j += 1) { - if (this.props.fields[j].show === false) { - continue; - } - - let celldata = rowData[j]; - let cell = null; - - let row = {}; - this.props.fields - .forEach((field, k) => row[field.label] = rowData[k]); - - const headers = this.props.fields.map( - (val) => val.label - ); - - // Get custom cell formatting if available - if (this.props.getFormattedCell) { - cell = this.props.getFormattedCell( - this.props.fields[j].label, - celldata, - row, - headers, - j - ); - } else { - cell = {celldata}; - } - if (cell !== null) { - curRow.push(React.cloneElement(cell, {key: 'td_col_' + j})); - } else { - curRow.push(createFragment({celldata})); - } + let rowIndex = index[i].RowIdx; + let rowData = this.props.data[rowIndex]; + let curRow = []; + + // Iterates through headers to populate row columns + // with corresponding data + for (let j = 0; j < this.props.fields.length; j += 1) { + if (this.props.fields[j].show === false) { + continue; } - const rowIndexDisplay = index[i].Content; - rows.push( - - {this.props.hide.defaultColumn === true ? null : ( - {rowIndexDisplay} - )} - {curRow} - + let celldata = rowData[j]; + let cell = null; + + let row = {}; + this.props.fields + .forEach((field, k) => row[field.label] = rowData[k]); + + const headers = this.props.fields.map( + (val) => val.label ); + + // Get custom cell formatting if available + if (this.props.getFormattedCell) { + cell = this.props.getFormattedCell( + this.props.fields[j].label, + celldata, + row, + headers, + j + ); + } else { + cell = {celldata}; + } + if (cell !== null) { + curRow.push(React.cloneElement(cell, {key: 'td_col_' + j})); + } else { + curRow.push(createFragment({celldata})); + } + } + + const rowIndexDisplay = index[i].Content; + rows.push( + + {this.props.hide.defaultColumn === true ? null : ( + {rowIndexDisplay} + )} + {curRow} + + ); } let rowsPerPageDropdown = ( @@ -586,12 +586,12 @@ class DataTable extends Component { }}> {this.renderActions()} {this.props.hide.downloadCSV === true ? '' : ( - ) + ) } {headers} - {this.props.folder} + {this.props.folder} {rows} diff --git a/jsx/Filter.js b/jsx/Filter.js index 42f0122cd4f..e0feb0cde8e 100644 --- a/jsx/Filter.js +++ b/jsx/Filter.js @@ -1,14 +1,14 @@ import React, {useEffect} from 'react'; import PropTypes from 'prop-types'; import { - CheckboxElement, - DateElement, - FieldsetElement, - TimeElement, - FormElement, - NumericElement, - SelectElement, - TextboxElement, + CheckboxElement, + DateElement, + FieldsetElement, + TimeElement, + FormElement, + NumericElement, + SelectElement, + TextboxElement, } from 'jsx/Form'; import DateTimePartialElement from 'jsx/form/DateTimePartialElement'; @@ -49,7 +49,7 @@ function Filter(props) { const type = fields .find((field) => (field.filter||{}).name == name).filter.type; const exactMatch = (!(type === 'text' || type === 'date' - || type === 'datetime')); + || type === 'datetime' || type === 'multiselect')); if (value === null || value === '' || (value.constructor === Array && value.length === 0) || (type === 'checkbox' && value === false)) { @@ -70,47 +70,47 @@ function Filter(props) { if (filter && filter.hide !== true) { let element; switch (filter.type) { - case 'text': - element = ; - break; - case 'select': - element = ( - - ); - break; - case 'multiselect': - element = ( - - ); - break; - case 'numeric': - element = ; + break; + case 'select': + element = ( + ; - break; - case 'date': - element = ; - break; - case 'datetime': - element = ; - break; - case 'checkbox': - element = ; - break; - case 'time': - element = ; - break; - default: - element = ; + sortByValue={filter.sortByValue} + autoSelect={false} + /> + ); + break; + case 'multiselect': + element = ( + + ); + break; + case 'numeric': + element = ; + break; + case 'date': + element = ; + break; + case 'datetime': + element = ; + break; + case 'checkbox': + element = ; + break; + case 'time': + element = ; + break; + default: + element = ; } // The value prop has to default to false if the first two options diff --git a/jsx/FilterableDataTable.js b/jsx/FilterableDataTable.js index be3cb26adeb..2f64dd1f5ee 100644 --- a/jsx/FilterableDataTable.js +++ b/jsx/FilterableDataTable.js @@ -103,28 +103,28 @@ class FilterableDataTable extends Component { * @return {object} */ validFilters() { - let filters = {}; - this.props.fields.forEach((field) => { - if (!field.filter) { - return; - } - const filtername = field.filter.name; - const filterval = this.state.filters[filtername]; - if (!filterval) { - return; - } - - if (field.filter.type !== 'select') { - filters[filtername] = filterval; - return; - } - - if (!(filterval.value in field.filter.options)) { - return; - } + let filters = {}; + this.props.fields.forEach((field) => { + if (!field.filter) { + return; + } + const filtername = field.filter.name; + const filterval = this.state.filters[filtername]; + if (!filterval) { + return; + } + + if (field.filter.type !== 'select') { filters[filtername] = filterval; - }); - return filters; + return; + } + + if (!(filterval.value in field.filter.options)) { + return; + } + filters[filtername] = filterval; + }); + return filters; } /** diff --git a/jsx/Form.js b/jsx/Form.js index 9d089623914..79795f578d6 100644 --- a/jsx/Form.js +++ b/jsx/Form.js @@ -903,20 +903,20 @@ export class TagsElement extends Component { itmTxt = item; } return ( - + + ); }, this); return ( @@ -934,7 +934,7 @@ export class TagsElement extends Component { id={this.props.id + 'Add'} type="button" onClick={this.handleAdd} - > + > {this.props.btnLabel} @@ -1599,13 +1599,13 @@ export class DateElement extends Component { let labelHTML; let classSz = 'col-sm-12'; if (this.props.label) { - labelHTML = ; - classSz = 'col-sm-9'; + labelHTML = ; + classSz = 'col-sm-9'; } return (
@@ -1703,14 +1703,14 @@ export class TimeElement extends Component { requiredHTML = *; } if (this.props.label) { - label = ; - classSz = 'col-sm-9'; + label = ; + classSz = 'col-sm-9'; } else { - classSz = 'col-sm-12'; + classSz = 'col-sm-12'; } return ( @@ -1799,14 +1799,14 @@ export class DateTimeElement extends Component { requiredHTML = *; } if (this.props.label) { - label = ; - classSz = 'col-sm-9'; + label = ; + classSz = 'col-sm-9'; } else { - classSz = 'col-sm-12'; + classSz = 'col-sm-12'; } return ( @@ -1897,13 +1897,13 @@ export class NumericElement extends Component { let labelHTML; let classSz = 'col-sm-12'; if (this.props.label) { - labelHTML = ; - classSz = 'col-sm-9'; + labelHTML = ; + classSz = 'col-sm-9'; } return ( @@ -1998,21 +1998,21 @@ export class FileElement extends Component { if (this.props.value) { switch (typeof this.props.value) { - case 'string': - fileName = this.props.value; - break; - - case 'object': - if (this.props.value instanceof FileList) { - const files = this.props.value; - fileName = Array.from(files).map((file) => file.name).join(', '); - } else { - fileName = this.props.value.name; - } - break; - - default: - break; + case 'string': + fileName = this.props.value; + break; + + case 'object': + if (this.props.value instanceof FileList) { + const files = this.props.value; + fileName = Array.from(files).map((file) => file.name).join(', '); + } else { + fileName = this.props.value.name; + } + break; + + default: + break; } } @@ -2072,13 +2072,13 @@ export class FileElement extends Component { let labelHTML; let classSz; if (this.props.label) { - labelHTML = ; - classSz = 'col-sm-9'; + labelHTML = ; + classSz = 'col-sm-9'; } else { - classSz = 'col-sm-12'; + classSz = 'col-sm-12'; } return ( @@ -2087,7 +2087,7 @@ export class FileElement extends Component {
+ className="form-control file-caption kv-fileinput-caption">
{fileName}
@@ -2488,30 +2488,30 @@ export class CTA extends Component { * * @return {JSX} - React markup for the component */ - render() { - return ( - - ); - } - } - - CTA.propTypes = { - label: PropTypes.string, - buttonClass: PropTypes.string, - onUserInput: PropTypes.func, - }; - - CTA.defaultProps = { - buttonClass: 'btn btn-primary', - onUserInput: function() { - console.warn('onUserInput() callback is not set'); - }, - }; + render() { + return ( + + ); + } +} + +CTA.propTypes = { + label: PropTypes.string, + buttonClass: PropTypes.string, + onUserInput: PropTypes.func, +}; + +CTA.defaultProps = { + buttonClass: 'btn btn-primary', + onUserInput: function() { + console.warn('onUserInput() callback is not set'); + }, +}; /** * Generic form element. @@ -2538,58 +2538,58 @@ export class LorisElement extends Component { let elementHtml =
; switch (elementProps.type) { - case 'text': - elementHtml = (); - break; - case 'email': - elementHtml = (); - break; - case 'password': - elementHtml = (); - break; - case 'tags': - elementHtml = (); - break; - case 'select': - elementHtml = (); - break; - case 'search': - elementHtml = (); - break; - case 'date': - elementHtml = (); - break; - case 'time': - elementHtml = (); - break; - case 'numeric': - elementHtml = (); - break; - case 'textarea': - elementHtml = (); - break; - case 'file': - elementHtml = (); - break; - case 'static': - elementHtml = (); - break; - case 'header': - elementHtml = (); - break; - case 'link': - elementHtml = (); - break; - case 'advcheckbox': - elementHtml = (); - break; - default: - console.warn( - 'Element of type ' + + case 'text': + elementHtml = (); + break; + case 'email': + elementHtml = (); + break; + case 'password': + elementHtml = (); + break; + case 'tags': + elementHtml = (); + break; + case 'select': + elementHtml = (); + break; + case 'search': + elementHtml = (); + break; + case 'date': + elementHtml = (); + break; + case 'time': + elementHtml = (); + break; + case 'numeric': + elementHtml = (); + break; + case 'textarea': + elementHtml = (); + break; + case 'file': + elementHtml = (); + break; + case 'static': + elementHtml = (); + break; + case 'header': + elementHtml = (); + break; + case 'link': + elementHtml = (); + break; + case 'advcheckbox': + elementHtml = (); + break; + default: + console.warn( + 'Element of type ' + elementProps.type + ' is not currently implemented!' - ); - break; + ); + break; } return elementHtml; @@ -2673,7 +2673,7 @@ export class RadioElement extends React.Component { const checked = this.props.checked === key; content.push(
+ style={styleColumn}>
@@ -2699,7 +2699,7 @@ export class RadioElement extends React.Component { layout.push(
+ style={styleRow}> {content}
); @@ -2824,7 +2824,7 @@ export class SliderElement extends React.Component { return (
- ); + return ( +
+ ); } Loader.propTypes = {size: PropTypes.string}; diff --git a/jsx/Modal.js b/jsx/Modal.js index 5b7a42ca6c6..2aacf8cb958 100644 --- a/jsx/Modal.js +++ b/jsx/Modal.js @@ -132,7 +132,7 @@ class Modal extends Component { const submitButton = () => { if (onSubmit) { const submit = () => onSubmit().then(() => this.props.onClose()) - .catch(() => {}); + .catch(() => {}); return (
- ) : null; +
+ ) : null; return ( <>
    + role='menu'> {views}
@@ -81,9 +81,9 @@ const Panel = (props) => { // Add panel header, if title is set const panelHeading = props.title || props.views ? (
+ data-parent={props.parentId + ? `#${props.parentId}` + : null}>

{props.views && props.views[activeView]['title'] ? props.views[activeView]['title'] @@ -94,10 +94,10 @@ const Panel = (props) => { ? + onClick={toggleCollapsed} + data-toggle='collapse' + data-target={`#${props.id}`} + style={{cursor: 'pointer'}}/> : null}

) : ''; @@ -109,16 +109,16 @@ const Panel = (props) => { */ return (
+ style={{height: props.panelSize}}> {panelHeading}
+ className={props.collapsed ? + 'panel-collapse collapse' : + 'panel-collapse collapse in'} + role='tabpanel' + style={{height: 'calc(100% - 3em)'}}>
+ style={{...props.style, height: props.height}}> {content.length > 0 ? content : props.children}
diff --git a/jsx/StaticDataTable.js b/jsx/StaticDataTable.js index 50f3dae0448..fb64bba1238 100644 --- a/jsx/StaticDataTable.js +++ b/jsx/StaticDataTable.js @@ -233,8 +233,8 @@ class StaticDataTable extends Component { let useKeyword = false; let filterMatchCount = 0; let filterValuesCount = (this.props.Filter ? - Object.keys(this.props.Filter).length : - 0 + Object.keys(this.props.Filter).length : + 0 ); let tableData = this.props.Data; let headersData = this.props.Headers; @@ -450,7 +450,7 @@ class StaticDataTable extends Component { if (this.props.Headers[i] === this.props.freezeColumn) { headers.push( + onClick={this.setSortColumn(i).bind(this)}> {this.props.Headers[i]} ); @@ -482,8 +482,8 @@ class StaticDataTable extends Component { // Push rows to data table for (let i = 0; - (i < this.props.Data.length) && (rows.length < rowsPerPage); - i++ + (i < this.props.Data.length) && (rows.length < rowsPerPage); + i++ ) { curRow = []; @@ -549,7 +549,7 @@ class StaticDataTable extends Component { if (matchesFound > currentPageRow) { const rowIndex = index[i].Content; const rowCell = this.state.Hide.defaultColumn !== true ? - {rowIndex} : null; + {rowIndex} : null; rows.push( diff --git a/jsx/Tabs.js b/jsx/Tabs.js index 0ee5d730f47..7d551878e38 100644 --- a/jsx/Tabs.js +++ b/jsx/Tabs.js @@ -103,10 +103,10 @@ class Tabs extends Component { key={tab.id} >
{tab.label} @@ -253,10 +253,10 @@ class VerticalTabs extends Component { key={tab.id} > {tab.label} @@ -282,7 +282,7 @@ class VerticalTabs extends Component { key: key, }); } - }.bind(this)); + }.bind(this)); return tabPanes; } @@ -304,9 +304,9 @@ class VerticalTabs extends Component {
    + className="nav nav-pills nav-stacked" + role="tablist" + style={tabStyle}> {tabs}
@@ -336,11 +336,11 @@ VerticalTabs.defaultProps = { * Used to wrap content for every tab. */ class TabPane extends Component { - /** - * React lifecycle method - * - * @return {object} - */ + /** + * React lifecycle method + * + * @return {object} + */ render() { let classList = 'tab-pane'; let title; diff --git a/modules/acknowledgements/help/acknowledgements.md b/modules/acknowledgements/help/acknowledgements.md index 9abef792c97..e79a9ad7f47 100644 --- a/modules/acknowledgements/help/acknowledgements.md +++ b/modules/acknowledgements/help/acknowledgements.md @@ -2,4 +2,6 @@ This module allows you to view present and past contributors, and to add contributors. This provides a centralized resource when writing publications, including how to cite the individual’s name for publication, as well as the years of their contribution. +The "Present" column in the table allows you to filter for whether a contributor to the project is currently active or past their end date. + Use the *Selection Filter* section to search for a specific contributor or group of contributors. Click **Add Acknowledgement** to add a new contributor or contributors. Then, populate the fields and click **Save**. diff --git a/modules/acknowledgements/jsx/acknowledgementsIndex.js b/modules/acknowledgements/jsx/acknowledgementsIndex.js index 55d5996b7d2..6051ba96c53 100644 --- a/modules/acknowledgements/jsx/acknowledgementsIndex.js +++ b/modules/acknowledgements/jsx/acknowledgementsIndex.js @@ -8,11 +8,11 @@ import Panel from 'Panel'; import Loader from 'Loader'; import FilterableDataTable from 'FilterableDataTable'; import { - SelectElement, - FormElement, - TextboxElement, - DateElement, - ButtonElement, + SelectElement, + FormElement, + TextboxElement, + DateElement, + ButtonElement, } from 'jsx/Form'; /** @@ -161,27 +161,27 @@ class AcknowledgementsIndex extends Component { credentials: 'same-origin', body: formObject, }) - .then((resp) => { - if (resp.ok && resp.status === 200) { - swal.fire( - 'Success!', - 'Acknowledgement added.', - 'success' - ).then((result) => { - if (result.value) { - this.closeModalForm(); - this.fetchData(); - } - }); - } else { - resp.text().then((message) => { - swal.fire('Error!', message, 'error'); - }); - } - }) - .catch((error) => { - console.error(error); - }); + .then((resp) => { + if (resp.ok && resp.status === 200) { + swal.fire( + 'Success!', + 'Acknowledgement added.', + 'success' + ).then((result) => { + if (result.value) { + this.closeModalForm(); + this.fetchData(); + } + }); + } else { + resp.text().then((message) => { + swal.fire('Error!', message, 'error'); + }); + } + }) + .catch((error) => { + console.error(error); + }); } /** @@ -220,16 +220,16 @@ class AcknowledgementsIndex extends Component { let result = {cell}; switch (column) { - case 'Affiliations': - result = {this.parseMultiple(cell, 'affiliationsOptions')}; - break; - case 'Degrees': - result = {this.parseMultiple(cell, 'degreesOptions')}; - break; + case 'Affiliations': + result = {this.parseMultiple(cell, 'affiliationsOptions')}; + break; + case 'Degrees': + result = {this.parseMultiple(cell, 'degreesOptions')}; + break; - case 'Roles': - result = {this.parseMultiple(cell, 'rolesOptions')}; - break; + case 'Roles': + result = {this.parseMultiple(cell, 'rolesOptions')}; + break; } return result; } @@ -275,8 +275,6 @@ class AcknowledgementsIndex extends Component { * @return {JSX} - React markup for the component */ renderAddForm() { - const requireEndDate = (this.state.formData.addPresent === 'No') || false; - const disableEndDate = (this.state.formData.addPresent === 'Yes') || false; return ( -
@@ -398,10 +386,10 @@ class AcknowledgementsIndex extends Component { return ; } - /** - * XXX: Currently, the order of these fields MUST match the order of the - * queried columns in _setupVariables() in acknowledgements.class.inc - */ + /** + * XXX: Currently, the order of these fields MUST match the order of the + * queried columns in _setupVariables() in acknowledgements.class.inc + */ const options = this.state.data.fieldOptions; const fields = [ {label: 'Ordering', show: true}, diff --git a/modules/acknowledgements/php/acknowledgements.class.inc b/modules/acknowledgements/php/acknowledgements.class.inc index 97e5139b3ff..7668216dea8 100644 --- a/modules/acknowledgements/php/acknowledgements.class.inc +++ b/modules/acknowledgements/php/acknowledgements.class.inc @@ -1,4 +1,5 @@ - implode(',', array_filter(explode(',', $roles))), 'start_date' => $values['addStartDate'], 'end_date' => $values['addEndDate'], - 'present' => $values['addPresent'], ]; $DB->insert('acknowledgements', $results); @@ -139,7 +139,12 @@ class Acknowledgements extends \NDB_Menu_Filter_Form 'roles', 'start_date', 'end_date', - 'present', + "IF( + COALESCE( + NOW() BETWEEN start_date AND end_date, true), + 'Yes', + 'No' + ) AS present", ]; $this->query = " FROM acknowledgements WHERE 1=1"; $this->group_by = ''; diff --git a/modules/acknowledgements/php/module.class.inc b/modules/acknowledgements/php/module.class.inc index bdf7893386c..a1d44109cfa 100644 --- a/modules/acknowledgements/php/module.class.inc +++ b/modules/acknowledgements/php/module.class.inc @@ -1,4 +1,5 @@ -safeGet($this->url . "/acknowledgements/"); - $this->safeFindElement( - WebDriverBy::cssSelector(".panel-body .btn-primary:nth-child(1)") - )->click(); - //insert ordering - $this->safeFindElement( - WebDriverBy::Name("addOrdering") - )->sendKeys(self::$newData['ordering']); - //insert Full name - $this->safeFindElement( - WebDriverBy::Name("addFullName") - )->sendKeys(self::$newData['full_name']); - //insert Citation name - $this->safeFindElement( - WebDriverBy::Name("addCitationName") - )->sendKeys(self::$newData['citation_name']); - $this->safeFindElement( - WebDriverBy::Name("addStartDate") - )->sendKeys(self::$newData['start_date']); - $el_dropdown = new WebDriverSelect( - $this->safeFindElement(WebDriverBy::Name("addPresent")) - ); - $el_dropdown->selectByVisibleText("Yes"); - //expecting to find the value,after clicking save button - $this->safeFindElement( - WebDriverBy::cssSelector('button[name="fire_away"]') - )->click(); - $bodyText = $this->safeFindElement( - WebDriverBy::cssSelector("#swal2-title") - )->getText(); - $this->assertStringContainsString("Success!", $bodyText); - } /** * Tests that, can't find Add Acknowledgement button on the page if @@ -255,48 +216,6 @@ function testCantAddNewRecord() $this->assertStringNotContainsString("Add Acknowledgement", $pagetext); } - /** - * Tests that, adding a new record, then this record appears on the page. - * - * @return void - */ - function testCancelAddNewRecord() - { - $this->safeGet($this->url . "/acknowledgements/"); - $this->safeFindElement( - WebDriverBy::cssSelector(".panel-body .btn-primary:nth-child(1)") - )->click(); - //insert ordering - $this->safeFindElement( - WebDriverBy::Name("addOrdering") - )->sendKeys(self::$newData['ordering']); - //insert Full name - $this->safeFindElement( - WebDriverBy::Name("addFullName") - )->sendKeys(self::$newData['full_name']); - //insert Citation name - $this->safeFindElement( - WebDriverBy::Name("addCitationName") - )->sendKeys(self::$newData['citation_name']); - $this->safeFindElement( - WebDriverBy::Name("addStartDate") - )->sendKeys(self::$newData['start_date']); - $el_dropdown = new WebDriverSelect( - $this->safeFindElement(WebDriverBy::Name("addPresent")) - ); - $el_dropdown->selectByVisibleText("Yes"); - //expecting to find the value,after clicking save button - $this->safeFindElement( - WebDriverBy::cssSelector( - '#lorisworkspace > div > div:nth-child(2)'. - ' > div > div:nth-child(1) > span' - ) - )->click(); - $bodyText = $this->safeFindElement( - WebDriverBy::cssSelector("#swal2-title") - )->getText(); - $this->assertStringContainsString("Are You Sure?", $bodyText); - } /** * Tests that, can't find Add Acknowledgement button on the page if * user doesn't have acknowledgements_edit permission diff --git a/modules/api/php/endpoint.class.inc b/modules/api/php/endpoint.class.inc index b7f6055f4ba..c8e8b51f0dc 100644 --- a/modules/api/php/endpoint.class.inc +++ b/modules/api/php/endpoint.class.inc @@ -1,4 +1,5 @@ _candidate->getCandID()) { + if ($visitinfo['CandID'] !== $this->_candidate->getCandID()->__toString()) { return new \LORIS\Http\Response\JSON\BadRequest( 'CandID does not match this candidate' ); @@ -227,7 +228,7 @@ class Visit extends Endpoint implements \LORIS\Middleware\ETagCalculator $centerid = array_search($visitinfo['Site'], \Utility::getSiteList()); if ($centerid === false - || !in_array(new \CenterID("$centerid"), $user->getCenterIDs()) + || !in_array(\CenterID::singleton($centerid), $user->getCenterIDs()) ) { return new \LORIS\Http\Response\JSON\Forbidden( "You can't create or modify candidates visit for the site " . @@ -237,7 +238,7 @@ class Visit extends Endpoint implements \LORIS\Middleware\ETagCalculator // \Utility::getSiteList key was a string. Now that the // validation is done, convert to an object. - $centerid = new \CenterID("$centerid"); + $centerid = \CenterID::singleton($centerid); $cohortid = array_search( $visitinfo['Battery'], diff --git a/modules/api/php/endpoints/candidate/visit/visit_0_0_4_dev.class.inc b/modules/api/php/endpoints/candidate/visit/visit_0_0_4_dev.class.inc index c770afad0da..400fa5401da 100644 --- a/modules/api/php/endpoints/candidate/visit/visit_0_0_4_dev.class.inc +++ b/modules/api/php/endpoints/candidate/visit/visit_0_0_4_dev.class.inc @@ -1,4 +1,5 @@ getCenterIDs() ) ) { @@ -254,7 +255,7 @@ class Visit_0_0_4_Dev extends Endpoint implements \LORIS\Middleware\ETagCalculat } // Now that the validation is done, convert to an object. - $this->_centerID = new \CenterID(strval($centerID)); + $this->_centerID = \CenterID::singleton($centerID); return null; } diff --git a/modules/api/php/endpoints/candidates.class.inc b/modules/api/php/endpoints/candidates.class.inc index a3d2d469880..6c71445a496 100644 --- a/modules/api/php/endpoints/candidates.class.inc +++ b/modules/api/php/endpoints/candidates.class.inc @@ -1,4 +1,5 @@ getFirstRow()["CandID"])); } try { @@ -255,7 +257,7 @@ class Candidates extends Endpoint implements \LORIS\Middleware\ETagCalculator try { $candid = \Candidate::createNew( - new \CenterID("$centerid"), + \CenterID::singleton($centerid), $data['Candidate']['DoB'] ?? null, $data['Candidate']['EDC'] ?? null, $sex, diff --git a/modules/api/php/endpoints/login.class.inc b/modules/api/php/endpoints/login.class.inc index 2e42d8ce995..b91a0645466 100644 --- a/modules/api/php/endpoints/login.class.inc +++ b/modules/api/php/endpoints/login.class.inc @@ -1,4 +1,5 @@ _candid = $row['CandID'] ?? null; $this->_projectname = $row['ProjectName'] ?? null; if ($row['ProjectID'] !== null) { - $this->_projectid = new \ProjectID($row['ProjectID']); + $this->_projectid = \ProjectID::singleton(intval($row['ProjectID'])); } $this->_pscid = $row['PSCID'] ?? null; $this->_sitename = $row['SiteName'] ?? null; $this->_edc = $row['EDC'] ?? null; $this->_dob = $row['DoB'] ?? null; $this->_sex = $row['Sex'] ?? null; - $this->_centerid = new \CenterID($row['CenterID']); + $this->_centerid = \CenterID::singleton(intval($row['CenterID'])); } /** diff --git a/modules/api/php/models/projectimagesrow.class.inc b/modules/api/php/models/projectimagesrow.class.inc index 10b60190895..5b97d9c51f8 100644 --- a/modules/api/php/models/projectimagesrow.class.inc +++ b/modules/api/php/models/projectimagesrow.class.inc @@ -1,4 +1,5 @@ _entitytype = $row['Entity_type'] ?? null; $this->_visitlabel = $row['Visit'] ?? null; $this->_visitdate = $row['Visit_date'] ?? null; - $this->_centerid = new \CenterID($row['CenterID']); + $this->_centerid = \CenterID::singleton(intval($row['CenterID'])); $this->_centername = $row['Site'] ?? null; $this->_filename = $row['File'] ?? null; $this->_inserttime = $row['InsertTime'] ?? null; diff --git a/modules/api/php/models/projectinstrumentsrow.class.inc b/modules/api/php/models/projectinstrumentsrow.class.inc index e93c94b9b27..58bd8634d51 100644 --- a/modules/api/php/models/projectinstrumentsrow.class.inc +++ b/modules/api/php/models/projectinstrumentsrow.class.inc @@ -1,4 +1,5 @@ _id); + return \CenterID::singleton($this->_id); } } diff --git a/modules/api/php/module.class.inc b/modules/api/php/module.class.inc index 54a594e91fb..b4eacea8524 100644 --- a/modules/api/php/module.class.inc +++ b/modules/api/php/module.class.inc @@ -1,4 +1,5 @@ - If the duplicate entry is inactive, you will be given the option to active it. -
+

); @@ -145,7 +145,7 @@ class BatteryManagerForm extends Component { max={127} // max value allowed by default column type of instr_order value={test.instrumentOrder} /> - diff --git a/modules/battery_manager/jsx/batteryManagerIndex.js b/modules/battery_manager/jsx/batteryManagerIndex.js index 794ba742f61..c93e8030d07 100644 --- a/modules/battery_manager/jsx/batteryManagerIndex.js +++ b/modules/battery_manager/jsx/batteryManagerIndex.js @@ -51,8 +51,8 @@ class BatteryManagerIndex extends Component { */ componentDidMount() { this.fetchData(this.props.testEndpoint, 'GET', 'tests') - .then(() => this.fetchData(this.props.optionEndpoint, 'GET', 'options')) - .then(() => this.setState({isLoaded: true})); + .then(() => this.fetchData(this.props.optionEndpoint, 'GET', 'options')) + .then(() => this.setState({isLoaded: true})); } /** @@ -66,12 +66,12 @@ class BatteryManagerIndex extends Component { 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); - }); + .then((resp) => resp.json()) + .then((data) => this.setState({[state]: data}, resolve)) + .catch((error) => { + this.setState({error: true}, reject); + console.error(error); + }); }); } @@ -91,23 +91,23 @@ class BatteryManagerIndex extends Component { method: method, body: JSON.stringify(dataClone), }) - .then((response) => response.text() - .then((body) => { - body = JSON.parse(body); - if (response.ok) { - swal.fire('Submission successful!', body.message, 'success') - .then((result) => { - if (result.value) { - this.closeForm(); - resolve(body.message); + .then((response) => response.text() + .then((body) => { + body = JSON.parse(body); + if (response.ok) { + swal.fire('Submission successful!', body.message, 'success') + .then((result) => { + if (result.value) { + this.closeForm(); + resolve(body.message); + } + }); + } else { + swal.fire(body.error, '', 'error'); + reject(body.error); } - }); - } else { - swal.fire(body.error, '', 'error'); - reject(body.error); - } - }) - .catch((e) => reject(e))); + }) + .catch((e) => reject(e))); }); } @@ -120,28 +120,28 @@ class BatteryManagerIndex extends Component { */ mapColumn(column, value) { switch (column) { - case 'First Visit': - switch (value) { - case 'Y': - return 'Yes'; - case 'N': - return 'No'; - } - break; - case 'Active': - switch (value) { - case 'Y': - return 'Yes'; - case 'N': - return 'No'; - } - break; - case 'Change Status': - return ''; - case 'Edit Metadata': - return ''; - default: - return value; + case 'First Visit': + switch (value) { + case 'Y': + return 'Yes'; + case 'N': + return 'No'; + } + break; + case 'Active': + switch (value) { + case 'Y': + return 'Yes'; + case 'N': + return 'No'; + } + break; + case 'Change Status': + return ''; + case 'Edit Metadata': + return ''; + default: + return value; } } @@ -158,33 +158,33 @@ class BatteryManagerIndex extends Component { let result = {cell}; const testId = row['ID']; switch (column) { - case 'Instrument': - result = {this.state.options.instruments[cell]}; - break; - case 'Cohort': - result = {this.state.options.cohorts[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; + case 'Instrument': + result = {this.state.options.instruments[cell]}; + break; + case 'Cohort': + result = {this.state.options.cohorts[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; @@ -257,15 +257,15 @@ class BatteryManagerIndex extends Component { } }); this.checkDuplicate(test) - .then((test) => this.validateTest(test)) - .then((test) => this.postData( + .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)); + )) + .then(() => this.fetchData(this.props.testEndpoint, 'GET', 'tests')) + .then(() => resolve()) + .catch((e) => reject(e)); }); } @@ -295,52 +295,52 @@ class BatteryManagerIndex extends Component { const fields = [ {label: 'ID', show: false}, {label: 'Instrument', show: true, filter: { - name: 'testName', - type: 'select', - options: options.instruments, - }}, + name: 'testName', + type: 'select', + options: options.instruments, + }}, {label: 'Minimum Age', show: true, filter: { - name: 'minimumAge', - type: 'numeric', - }}, + name: 'minimumAge', + type: 'numeric', + }}, {label: 'Maximum Age', show: true, filter: { - name: 'maximumAge', - type: 'numeric', - }}, + name: 'maximumAge', + type: 'numeric', + }}, {label: 'Stage', show: true, filter: { - name: 'stage', - type: 'select', - options: options.stages, - }}, + name: 'stage', + type: 'select', + options: options.stages, + }}, {label: 'Cohort', show: true, filter: { - name: 'cohort', - type: 'select', - options: options.cohorts, - }}, + name: 'cohort', + type: 'select', + options: options.cohorts, + }}, {label: 'Visit Label', show: true, filter: { - name: 'visitLabel', - type: 'select', - options: options.visits, - }}, + name: 'visitLabel', + type: 'select', + options: options.visits, + }}, {label: 'Site', show: true, filter: { - name: 'site', - type: 'select', - options: options.sites, - }}, + name: 'site', + type: 'select', + options: options.sites, + }}, {label: 'First Visit', show: true, filter: { - name: 'firstVisit', - type: 'select', - options: options.firstVisit, - }}, + name: 'firstVisit', + type: 'select', + options: options.firstVisit, + }}, {label: 'Instrument Order', show: true, filter: { - name: 'instrumentOrder', - type: 'text', - }}, + name: 'instrumentOrder', + type: 'text', + }}, {label: 'Active', show: true, filter: { - name: 'active', - type: 'select', - options: options.active, - }}, + name: 'active', + type: 'select', + options: options.active, + }}, {label: 'Change Status', show: hasPermission('battery_manager_edit')}, {label: 'Edit Metadata', show: hasPermission('battery_manager_edit')}, ]; diff --git a/modules/battery_manager/php/battery_manager.class.inc b/modules/battery_manager/php/battery_manager.class.inc index eea940044a6..884ce9fbf12 100644 --- a/modules/battery_manager/php/battery_manager.class.inc +++ b/modules/battery_manager/php/battery_manager.class.inc @@ -1,4 +1,5 @@ -getBody()->getContents(), true); $test = new Test($this->loris, null, $testArray); $test->row['active'] = 'Y'; - return $this->_saveInstance($test); + return $this->_saveInstance($test, "post"); } /** * Generic save function for Test Instances. * - * @param Test $test The Test Instance to be saved. + * @param Test $test The Test Instance to be saved. + * @param string $method check the request method * * @return ResponseInterface response */ - private function _saveInstance(Test $test) + private function _saveInstance(Test $test , $method=null) { if (!$this->user->hasPermission('battery_manager_edit')) { return new \LORIS\Http\Response\JSON\Forbidden('Edit Permission Denied'); @@ -183,7 +185,7 @@ class TestEndpoint extends \NDB_Page implements RequestHandlerInterface } // check if instance is duplicate - if ($this->_isDuplicate($test)) { + if ($this->_isDuplicate($test) && $method =='post') { return new \LORIS\Http\Response\JSON\Conflict( 'This Test already exists in the database' ); diff --git a/modules/battery_manager/php/testoptionsendpoint.class.inc b/modules/battery_manager/php/testoptionsendpoint.class.inc index 79f6ead9873..77af508917c 100644 --- a/modules/battery_manager/php/testoptionsendpoint.class.inc +++ b/modules/battery_manager/php/testoptionsendpoint.class.inc @@ -1,4 +1,5 @@ -loris, isset($row['centerId']) - ? new \CenterID($row['centerId']) + ? \CenterID::singleton(intval($row['centerId'])) : null, $row ); diff --git a/modules/battery_manager/test/BatteryManagerTest.php b/modules/battery_manager/test/BatteryManagerTest.php index 29257f0bff5..ce7e9327206 100644 --- a/modules/battery_manager/test/BatteryManagerTest.php +++ b/modules/battery_manager/test/BatteryManagerTest.php @@ -1,4 +1,5 @@ - div > div > div:nth-child(1)'; + static $clearFilter = '.nav-tabs a'; /** * Tests that, when loading the BatteryManager module, some * text appears in the body. @@ -68,6 +76,17 @@ function testLoadsWithPermissionRead() "You do not have access to this page.", $bodyText ); + $bodyText = $this->safeFindElement( + WebDriverBy::cssSelector("#dynamictable > thead > tr") + )->getText(); + $this->assertStringNotContainsString( + "Change Status", + $bodyText + ); + $this->assertStringNotContainsString( + "Edit Metadata", + $bodyText + ); $this->resetPermissions(); } /** @@ -89,5 +108,225 @@ function testDoesNotLoadWithoutPermission() ); $this->resetPermissions(); } + /** + * Tests that the page does not load if the user does not have correct + * permissions + * + * @return void + */ + function testLoadsWithPermissionEdit() + { + $this->setupPermissions(["battery_manager_edit"]); + $this->safeGet($this->url . "/battery_manager/"); + $bodyText = $this->safeFindElement( + WebDriverBy::cssSelector("body") + )->getText(); + $this->assertStringNotContainsString( + "You do not have access to this page.", + $bodyText + ); + $bodyText = $this->safeFindElement( + WebDriverBy::cssSelector("#dynamictable > thead > tr") + )->getText(); + $this->assertStringContainsString( + "Change Status", + $bodyText + ); + $this->assertStringContainsString( + "Edit Metadata", + $bodyText + ); + $this->safeClick( + WebDriverBy::cssSelector( + "#dynamictable > tbody > tr:nth-child(1) > td:nth-child(13) > button" + ) + ); + $bodyText = $this->safeFindElement( + WebDriverBy::cssSelector( + "#lorisworkspace > div >". + " div:nth-child(2) > div > div:nth-child(1)" + ) + )->getText(); + $this->assertStringContainsString( + "Edit Test", + $bodyText + ); + + $this->resetPermissions(); + } + /** + * Tests that the page does not load if the user does not have correct + * permissions + * + * @return void + */ + function testEditform() + { + $this->safeGet($this->url . "/battery_manager/"); + $this->safeClick( + WebDriverBy::cssSelector( + "#dynamictable > tbody > tr > td:nth-child(13) > button" + ) + ); + $this->safeClick( + WebDriverBy::cssSelector( + "#lorisworkspace > div>div:nth-child(2)>div>div:nth-child(2)>form>". + " div > div:nth-child(2) > div > div > select > option:nth-child(2)" + ) + ); + $this->safeFindElement( + WebDriverBy::cssSelector( + "#lorisworkspace > div> div:nth-child(2) > div > div:nth-child(2)". + " > form > div > div:nth-child(3) > div > div > input" + ) + )->clear()->sendKeys('0'); + $this->safeFindElement( + WebDriverBy::cssSelector( + "#lorisworkspace>div> div:nth-child(2) > div > div:nth-child(2) ". + "> form > div > div:nth-child(4) > div > div > input" + ) + )->clear()->sendKeys('1'); + $this->safeClick( + WebDriverBy::cssSelector( + "#lorisworkspace>div>div:nth-child(2)>div>div:nth-child(2)>form>". + "div>div:nth-child(5) > div > div > select > option:nth-child(7)" + ) + ); + $this->safeClick( + WebDriverBy::cssSelector( + "#lorisworkspace > div > div:nth-child(2) > div > div:nth-child(2)>". + "form>div>div:nth-child(6) > div > div >select> option:nth-child(2)" + ) + ); + $this->safeClick( + WebDriverBy::cssSelector( + "#lorisworkspace > div >div:nth-child(2) > div > div:nth-child(2) >". + "form>div>div:nth-child(7) >div > div > select > option:nth-child(5)" + ) + ); + + $this->safeClick( + WebDriverBy::cssSelector( + "#lorisworkspace>div>div:nth-child(2)>div>div:nth-child(2)>form>". + " div > div:nth-child(11) > div > div > button" + ) + ); + $bodyText = $this->safeFindElement( + WebDriverBy::cssSelector("#swal2-title") + )->getText(); + $this->assertStringContainsString( + "Submission successful!", + $bodyText + ); + } + + /** + * Tests that the page does not load if the user does not have correct + * permissions + * + * @return void + */ + function testAddNew() + { + $this->safeGet($this->url . "/battery_manager/"); + $this->safeClick( + WebDriverBy::cssSelector( + "#default-panel > div > div > div.table-header >". + " div > div > div:nth-child(2) > button:nth-child(1)" + ) + ); + $this->safeClick( + WebDriverBy::cssSelector( + "#lorisworkspace > div > div:nth-child(2) > div > div:nth-child(2) ". + "> form > div > div:nth-child(2)>div>div>select>option:nth-child(2)" + ) + ); + + $this->safeFindElement( + WebDriverBy::cssSelector( + "#lorisworkspace > div > div:nth-child(2) > div ". + "> div:nth-child(2)>form>div>div:nth-child(3) > div > div > input" + ) + )->clear()->sendKeys('0'); + $this->safeFindElement( + WebDriverBy::cssSelector( + "#lorisworkspace > div > div:nth-child(2) > div >". + " div:nth-child(2)>form > div > div:nth-child(4) > div > div >input" + ) + )->clear()->sendKeys('1'); + $this->safeClick( + WebDriverBy::cssSelector( + "#lorisworkspace > div > div:nth-child(2)>div>div:nth-child(2)>form". + " > div > div:nth-child(5) > div > div >select > option:nth-child(7)" + ) + ); + $this->safeClick( + WebDriverBy::cssSelector( + "#lorisworkspace > div >div:nth-child(2)>div>div:nth-child(2)>form ". + "> div > div:nth-child(11) > div > div > button" + ) + ); + $bodyText = $this->safeFindElement( + WebDriverBy::cssSelector("#swal2-title") + )->getText(); + $this->assertStringContainsString( + "Submission successful!", + $bodyText + ); + } + /** + * Tests that the page does not load if the user does not have correct + * permissions + * + * @return void + */ + function testActivebtn() + { + $this->safeGet($this->url . "/battery_manager/"); + $this->safeClick( + WebDriverBy::cssSelector( + "#dynamictable > tbody > tr:nth-child(1) > td:nth-child(12) > button" + ) + ); + $bodyText = $this->safeFindElement( + WebDriverBy::cssSelector("#swal2-title") + )->getText(); + $this->assertStringContainsString( + "Submission successful!", + $bodyText + ); + } + /** + * Tests filter in the form + * The form should refreash and the data should be gone. + * + * @return void + */ + function testFilter() + { + $this->safeGet($this->url . "/battery_manager/"); + //testing data from RBdata.sql + $this->_filterTest( + self::$instrument, + self::$display, + self::$clearFilter, + 'AOSI', + '4 rows' + ); + $this->_filterTest( + self::$minimumAge, + self::$display, + self::$clearFilter, + '0', + '2 rows' + ); + $this->_filterTest( + self::$maximumAge, + self::$display, + self::$clearFilter, + '0', + '0 row' + ); + } } diff --git a/modules/battery_manager/test/TestPlan.md b/modules/battery_manager/test/TestPlan.md index 4a6973c3be9..f27930762b3 100644 --- a/modules/battery_manager/test/TestPlan.md +++ b/modules/battery_manager/test/TestPlan.md @@ -43,7 +43,7 @@ Test Battery. ### New Test Button -**Testing add functionality** +**Testing add functionality** [Automation Testing] Click the "New Test" button in the upper right corner of the data table. 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 positive numbers (including 0) in Minimum age (days) and Maximum age (days). @@ -76,7 +76,7 @@ Click the "New Test" button in the upper right corner of the data table. 1. Press the `Edit` button 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** +**Test filters** [Automation Testing] 1. 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, Cohort, Visit Label, Site, First Visit, and Active (as dropdown fields with blank default option). @@ -87,7 +87,7 @@ Click the "New Test" button in the upper right corner of the data table. ### Edit window -**Testing edit (activate/deactivate/add) functionality** +**Testing edit (activate/deactivate/add) functionality** [Automation Testing] 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 positive numbers (including 0) in Minimum age (days) and Maximum age (days). 3. Check that you can only enter positive numbers (including 0) in Instrument order. diff --git a/modules/behavioural_qc/jsx/tabs_content/behaviouralFeedback.js b/modules/behavioural_qc/jsx/tabs_content/behaviouralFeedback.js index 5ce9a202c6a..3d7fba2661a 100644 --- a/modules/behavioural_qc/jsx/tabs_content/behaviouralFeedback.js +++ b/modules/behavioural_qc/jsx/tabs_content/behaviouralFeedback.js @@ -82,35 +82,35 @@ class BehaviouralFeedback extends Component { formatColumn(column, cell, rowData, rowHeaders) { let reactElement; switch (column) { - case 'PSCID': - reactElement = ( - - + - {rowData['PSCID']} - - - ); - break; - case 'DCCID': - reactElement = ( - - + {rowData['PSCID']} + + + ); + break; + case 'DCCID': + reactElement = ( + + - {rowData['DCCID']} - - - ); - break; - case 'Feedback Level': - let bvlLink = ''; - let bvlLevel = ''; - if (rowData['Instrument']) { - bvlLink = this.props.baseURL + + }> + {rowData['DCCID']} + + + ); + break; + case 'Feedback Level': + let bvlLink = ''; + let bvlLevel = ''; + if (rowData['Instrument']) { + bvlLink = this.props.baseURL + '/instruments/' + rowData['Test Name'] + '/?candID=' + @@ -119,36 +119,36 @@ class BehaviouralFeedback extends Component { rowData['sessionID'] + '&commentID=' + rowData['commentID']; - // Open feedback panel - bvlLink += '&showFeedback=true'; - bvlLevel ='Instrument : ' + rowData['Instrument']; - } else if (rowData['Visit']) { - bvlLink = this.props.baseURL + + // Open feedback panel + bvlLink += '&showFeedback=true'; + bvlLevel ='Instrument : ' + rowData['Instrument']; + } else if (rowData['Visit']) { + bvlLink = this.props.baseURL + '/instrument_list/' + '?candID=' + rowData['DCCID'] + '&sessionID=' + rowData['sessionID']; - // Open feedback panel - bvlLink += '&showFeedback=true'; - bvlLevel ='Visit : ' + rowData['Visit']; - } else { - bvlLink = this.props.baseURL + + // Open feedback panel + bvlLink += '&showFeedback=true'; + bvlLevel ='Visit : ' + rowData['Visit']; + } else { + bvlLink = this.props.baseURL + '/' + rowData['DCCID']; - // Open feedback panel - bvlLink += '/?showFeedback=true'; - bvlLevel ='Profile : ' + rowData['PSCID']; - } - reactElement = ( - - {bvlLevel} - - ); - break; - default: - reactElement = ( - {cell} - ); + // Open feedback panel + bvlLink += '/?showFeedback=true'; + bvlLevel ='Profile : ' + rowData['PSCID']; + } + reactElement = ( + + {bvlLevel} + + ); + break; + default: + reactElement = ( + {cell} + ); } return reactElement; } @@ -173,8 +173,8 @@ class BehaviouralFeedback extends Component { name: 'Instrument', type: 'select', options: Object.assign({}, ...Object.entries( - {...Object.values(options.instruments)}) - .map(([, b]) => ({[b]: b})) + {...Object.values(options.instruments)}) + .map(([, b]) => ({[b]: b})) ), }, }, diff --git a/modules/behavioural_qc/jsx/tabs_content/dataConflicts.js b/modules/behavioural_qc/jsx/tabs_content/dataConflicts.js index 0bebd905a08..c2b4d061185 100644 --- a/modules/behavioural_qc/jsx/tabs_content/dataConflicts.js +++ b/modules/behavioural_qc/jsx/tabs_content/dataConflicts.js @@ -82,48 +82,48 @@ class DataConflicts extends Component { formatColumn(column, cell, rowData, rowHeaders) { let reactElement = null; switch (column) { - case 'Visit': - reactElement = ( - - + - {rowData['Visit']} - - - ); - break; - case 'PSCID': - reactElement = ( - - + {rowData['Visit']} + + + ); + break; + case 'PSCID': + reactElement = ( + + - {rowData['PSCID']} - - - ); - break; - case 'DCCID': - reactElement = ( - - + {rowData['PSCID']} + + + ); + break; + case 'DCCID': + reactElement = ( + + - {rowData['DCCID']} - - - ); - break; - case 'Instrument': - reactElement = ( - - + {rowData['DCCID']} + + + ); + break; + case 'Instrument': + reactElement = ( + + - {rowData['Instrument']} - - - ); - break; - default: - reactElement = ( - {cell} - ); + }> + {rowData['Instrument']} + + + ); + break; + default: + reactElement = ( + {cell} + ); } return reactElement; } @@ -167,8 +167,8 @@ class DataConflicts extends Component { name: 'Instrument', type: 'select', options: Object.assign({}, ...Object.entries( - {...Object.values(options.instruments)}) - .map(([, b]) => ({[b]: b})) + {...Object.values(options.instruments)}) + .map(([, b]) => ({[b]: b})) ), }, }, diff --git a/modules/behavioural_qc/jsx/tabs_content/incompleteForms.js b/modules/behavioural_qc/jsx/tabs_content/incompleteForms.js index 4e6de6d5c55..8bf0ac9f615 100644 --- a/modules/behavioural_qc/jsx/tabs_content/incompleteForms.js +++ b/modules/behavioural_qc/jsx/tabs_content/incompleteForms.js @@ -82,48 +82,48 @@ class IncompleteForms extends Component { formatColumn(column, cell, rowData, rowHeaders) { let reactElement; switch (column) { - case 'Visit': - reactElement = ( - - + - {rowData['Visit']} - - - ); - break; - case 'PSCID': - reactElement = ( - - + {rowData['Visit']} + + + ); + break; + case 'PSCID': + reactElement = ( + + - {rowData['PSCID']} - - - ); - break; - case 'DCCID': - reactElement = ( - - + {rowData['PSCID']} + + + ); + break; + case 'DCCID': + reactElement = ( + + - {rowData['DCCID']} - - - ); - break; - case 'Instrument': - reactElement = ( - - + {rowData['DCCID']} + + + ); + break; + case 'Instrument': + reactElement = ( + + - {rowData['Instrument']} - - - ); - break; - default: - reactElement = ( - {cell} - ); + }> + {rowData['Instrument']} + + + ); + break; + default: + reactElement = ( + {cell} + ); } return reactElement; } @@ -167,8 +167,8 @@ class IncompleteForms extends Component { name: 'Instrument', type: 'select', options: Object.assign({}, ...Object.entries( - {...Object.values(options.instruments)}) - .map(([, b]) => ({[b]: b})) + {...Object.values(options.instruments)}) + .map(([, b]) => ({[b]: b})) ), }, }, diff --git a/modules/behavioural_qc/php/behavioural_qc.class.inc b/modules/behavioural_qc/php/behavioural_qc.class.inc index 2727cf4612e..c68ac32f41f 100644 --- a/modules/behavioural_qc/php/behavioural_qc.class.inc +++ b/modules/behavioural_qc/php/behavioural_qc.class.inc @@ -1,4 +1,5 @@ -_site)); + return \CenterID::singleton($this->_site); } /** @@ -137,7 +137,7 @@ class BehaviouralDTO implements \LORIS\Data\DataInstance, */ public function getProjectID(): \ProjectID { - return new \ProjectID(strval($this->_project)); + return \ProjectID::singleton($this->_project); } /** diff --git a/modules/behavioural_qc/php/models/conflictsdto.class.inc b/modules/behavioural_qc/php/models/conflictsdto.class.inc index 82564a5e353..7767ea0700f 100644 --- a/modules/behavioural_qc/php/models/conflictsdto.class.inc +++ b/modules/behavioural_qc/php/models/conflictsdto.class.inc @@ -51,9 +51,9 @@ class ConflictsDTO implements \LORIS\Data\DataInstance, /** * The project * - * @var string + * @var int */ - private $_project = ''; + private $_project; /** * The feedback_status @@ -65,9 +65,9 @@ class ConflictsDTO implements \LORIS\Data\DataInstance, /** * The site * - * @var string + * @var int */ - private $_site = ''; + private $_site; /** * The fieldName @@ -112,7 +112,7 @@ class ConflictsDTO implements \LORIS\Data\DataInstance, */ public function getCenterID(): \CenterID { - return new \CenterID(strval($this->_site)); + return \CenterID::singleton($this->_site); } /** @@ -123,7 +123,7 @@ class ConflictsDTO implements \LORIS\Data\DataInstance, */ public function getProjectID(): \ProjectID { - return new \ProjectID(strval($this->_project)); + return \ProjectID::singleton($this->_project); } /** diff --git a/modules/behavioural_qc/php/models/incompletedto.class.inc b/modules/behavioural_qc/php/models/incompletedto.class.inc index a2c1f3b33dc..50889e74c0a 100644 --- a/modules/behavioural_qc/php/models/incompletedto.class.inc +++ b/modules/behavioural_qc/php/models/incompletedto.class.inc @@ -51,9 +51,9 @@ class IncompleteDTO implements \LORIS\Data\DataInstance, /** * The project * - * @var string + * @var int */ - private $_project = ''; + private $_project; /** * The feedback_status @@ -65,9 +65,9 @@ class IncompleteDTO implements \LORIS\Data\DataInstance, /** * The site * - * @var string + * @var int */ - private $_site = ''; + private $_site; /** * The id @@ -112,7 +112,7 @@ class IncompleteDTO implements \LORIS\Data\DataInstance, */ public function getCenterID(): \CenterID { - return new \CenterID(strval($this->_site)); + return \CenterID::singleton($this->_site); } /** @@ -123,7 +123,7 @@ class IncompleteDTO implements \LORIS\Data\DataInstance, */ public function getProjectID(): \ProjectID { - return new \ProjectID(strval($this->_project)); + return \ProjectID::singleton($this->_project); } /** diff --git a/modules/behavioural_qc/php/module.class.inc b/modules/behavioural_qc/php/module.class.inc index 448a816e21a..1e7ce5d4700 100644 --- a/modules/behavioural_qc/php/module.class.inc +++ b/modules/behavioural_qc/php/module.class.inc @@ -1,4 +1,5 @@ - 0) { $ddeInstruments = implode(',', $ddeInstruments); $where = " - AND (f.test_name IN ($ddeInstruments) OR + AND (t.test_name IN ($ddeInstruments) OR f.commentid NOT LIKE 'DDE_%') "; } @@ -50,14 +50,14 @@ class IncompleteProvisioner extends \LORIS\Data\Provisioners\DBObjectProvisioner psc.CenterID AS _site, s.ID AS _id, f.SessionID as _sessionID, - f.test_name as _test_name, + t.test_name as _test_name, f.data_entry as _data_entry, f.commentid as _commentID FROM session s JOIN flag f ON (f.sessionid = s.id) JOIN candidate c ON (c.candid = s.candid) - JOIN test_names t ON (t.Test_name = f.Test_name) + JOIN test_names t ON (t.ID = f.TestID) JOIN psc ON (s.CenterID = psc.CenterID) WHERE s.Active = 'Y' diff --git a/modules/behavioural_qc/test/behavioural_qcTest.php b/modules/behavioural_qc/test/behavioural_qcTest.php index 166202eafa2..07300a02ead 100644 --- a/modules/behavioural_qc/test/behavioural_qcTest.php +++ b/modules/behavioural_qc/test/behavioural_qcTest.php @@ -1,4 +1,5 @@ safeGet($this->url . "/behavioural_qc/"); + $bodyText = $this->safeFindElement( + WebDriverBy::cssSelector( + " #dynamictable >". + " tbody > tr:nth-child(1) > td:nth-child(2) > a" + ) + )->getAttribute('href'); + // check Instrument link + $this->assertStringContainsString( + "radiology_review/?candID=300001", + $bodyText + ); + $bodyText = $this->safeFindElement( + WebDriverBy::cssSelector( + " #dynamictable >". + " tbody > tr:nth-child(1) > td:nth-child(3) > a" + ) + )->getAttribute('href'); + // check Instrument link + $this->assertStringContainsString( + "300001", + $bodyText + ); + $bodyText = $this->safeFindElement( + WebDriverBy::cssSelector( + " #dynamictable >". + " tbody > tr:nth-child(1) > td:nth-child(4) > a" + ) + )->getAttribute('href'); + // check Instrument link + $this->assertStringContainsString( + "300001", + $bodyText + ); + $bodyText = $this->safeFindElement( + WebDriverBy::cssSelector( + " #dynamictable >". + " tbody > tr:nth-child(1) > td:nth-child(5) > a" + ) + )->getAttribute('href'); + // check Instrument link + $this->assertStringContainsString( + "instrument_list/?candID=300001", + $bodyText + ); + } } diff --git a/modules/brainbrowser/js/brainbrowser.loris.js b/modules/brainbrowser/js/brainbrowser.loris.js index 53d37e2449b..b9a0e00a24a 100644 --- a/modules/brainbrowser/js/brainbrowser.loris.js +++ b/modules/brainbrowser/js/brainbrowser.loris.js @@ -13,7 +13,7 @@ function getQueryVariable(variable) { for (i = 0; i < vars.length; i += 1) { pair = vars[i].split('='); if (pair[0] === variable) { - return unescape(pair[1]); + return unescape(pair[1]); } } } @@ -349,7 +349,7 @@ $(function() { value = volume.getVoxelMin(); } value = Math.max(volume.getVoxelMin(), - Math.min(value, volume.getVoxelMax())); + Math.min(value, volume.getVoxelMax())); this.value = value; // Update the slider. @@ -368,7 +368,7 @@ $(function() { value = volume.getVoxelMax(); } value = Math.max(volume.getVoxelMin(), - Math.min(value, volume.getVoxelMax())); + Math.min(value, volume.getVoxelMax())); this.value = value; // Update the slider. @@ -569,44 +569,44 @@ $(function() { fileNameID.tooltip(); $('#filename-'+volID).on('click', function() { - $('#filename-additional-info-'+volID).slideToggle('fast'); - let arrow = $(this).siblings('.arrow'); - if (arrow.hasClass('glyphicon-chevron-down')) { - arrow - .removeClass('glyphicon-chevron-down') - .addClass('glyphicon-chevron-up'); - } else { - arrow - .removeClass('glyphicon-chevron-up') - .addClass('glyphicon-chevron-down'); - } - }); - $('.filename-overlay').on('click', function() { - $('.filename-overlay-additional-info').slideToggle('fast'); - let arrow = $(this).siblings('.arrow'); - if (arrow.hasClass('glyphicon-chevron-down')) { - arrow - .removeClass('glyphicon-chevron-down') - .addClass('glyphicon-chevron-up'); - } else { - arrow - .removeClass('glyphicon-chevron-up') - .addClass('glyphicon-chevron-down'); - } - }); - - $('.arrow').on('click', function() { - $('#filename-additional-info-'+volID).slideToggle('fast'); - if ($('.arrow').hasClass('glyphicon-chevron-down')) { - $('.arrow') - .removeClass('glyphicon-chevron-down') - .addClass('glyphicon-chevron-up'); - } else { - $('.arrow') - .removeClass('glyphicon-chevron-up') - .addClass('glyphicon-chevron-down'); - } - }); + $('#filename-additional-info-'+volID).slideToggle('fast'); + let arrow = $(this).siblings('.arrow'); + if (arrow.hasClass('glyphicon-chevron-down')) { + arrow + .removeClass('glyphicon-chevron-down') + .addClass('glyphicon-chevron-up'); + } else { + arrow + .removeClass('glyphicon-chevron-up') + .addClass('glyphicon-chevron-down'); + } + }); + $('.filename-overlay').on('click', function() { + $('.filename-overlay-additional-info').slideToggle('fast'); + let arrow = $(this).siblings('.arrow'); + if (arrow.hasClass('glyphicon-chevron-down')) { + arrow + .removeClass('glyphicon-chevron-down') + .addClass('glyphicon-chevron-up'); + } else { + arrow + .removeClass('glyphicon-chevron-up') + .addClass('glyphicon-chevron-down'); + } + }); + + $('.arrow').on('click', function() { + $('#filename-additional-info-'+volID).slideToggle('fast'); + if ($('.arrow').hasClass('glyphicon-chevron-down')) { + $('.arrow') + .removeClass('glyphicon-chevron-down') + .addClass('glyphicon-chevron-up'); + } else { + $('.arrow') + .removeClass('glyphicon-chevron-up') + .addClass('glyphicon-chevron-down'); + } + }); // Contrast controls container.find('.contrast-div').each(function() { @@ -760,9 +760,9 @@ $(function() { let fgColor = getContrastYIQ(bgColor); $('#intensity-value-' + volID) - .css('background-color', '#' + bgColor) - .css('color', fgColor) - .html(Math.floor(value)); + .css('background-color', '#' + bgColor) + .css('color', fgColor) + .html(Math.floor(value)); if (volume.header && volume.header.time) { $('#time-slider-' + volID).slider( @@ -812,43 +812,43 @@ $(function() { 'imageinfo?fileids=' + mincIDs + '&fileurls=' + fileUrls, {credentials: 'same-origin', method: 'GET'} ) - .then((resp) => resp.json()) - .then((data) => { - for (const file of data) { + .then((resp) => resp.json()) + .then((data) => { + for (const file of data) { let volume = { - type: file.type, - template: { - element_id: 'volume-ui-template4d', - viewer_insert_class: 'volume-viewer-display', - }, + type: file.type, + template: { + element_id: 'volume-ui-template4d', + viewer_insert_class: 'volume-viewer-display', + }, }; if (file.type == 'nifti1') { - volume.nii_url = file.URL; + volume.nii_url = file.URL; } else { - volume.raw_data_url = file.URL; + volume.raw_data_url = file.URL; } mincVolumes.push(volume); mincFilenames.push(file.Filename); - } - bboptions.volumes = mincVolumes; - - // //////////////////////////// - // Load the default color map and then call - // render only after it's been loaded - // //////////////////////////// - viewer.loadDefaultColorMapFromURL( - colorMapConfig.url, - colorMapConfig.cursor_color, - function() { + } + bboptions.volumes = mincVolumes; + + // //////////////////////////// + // Load the default color map and then call + // render only after it's been loaded + // //////////////////////////// + viewer.loadDefaultColorMapFromURL( + colorMapConfig.url, + colorMapConfig.cursor_color, + function() { // /////////////////// // Load the volumes. // /////////////////// viewer.render(); // start the rendering viewer.loadVolumes(bboptions); // load the volumes - } - ); - }); + } + ); + }); return viewer; }); diff --git a/modules/brainbrowser/jsx/Brainbrowser.js b/modules/brainbrowser/jsx/Brainbrowser.js index ada35b847f4..4415748695b 100644 --- a/modules/brainbrowser/jsx/Brainbrowser.js +++ b/modules/brainbrowser/jsx/Brainbrowser.js @@ -88,9 +88,9 @@ class BrainBrowser extends Component {