Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mindlogger] Implementation ML integration POC1 #8989

Open
wants to merge 9 commits into
base: 25.0-release
Choose a base branch
from
1 change: 1 addition & 0 deletions SQL/0000-00-01-Modules.sql
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ INSERT INTO modules (Name, Active) VALUES ('user_accounts', 'Y');
INSERT INTO modules (Name, Active) VALUES ('electrophysiology_browser', 'Y');
INSERT INTO modules (Name, Active) VALUES ('dqt', 'Y');
INSERT INTO modules (Name, Active) VALUES ('electrophysiology_uploader', 'Y');
INSERT INTO modules (Name, Active) VALUES ('mindlogger', 'Y');

ALTER TABLE issues ADD CONSTRAINT `fk_issues_7` FOREIGN KEY (`module`) REFERENCES `modules` (`ID`);
3 changes: 2 additions & 1 deletion SQL/0000-00-02-Permission.sql
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ INSERT INTO `permissions` VALUES
(60,'behavioural_quality_control_view','Flagged Behavioural Entries',(SELECT ID FROM modules WHERE Name='behavioural_qc'),'View','2'),
(61,'api_docs','API documentation',(SELECT ID FROM modules WHERE Name='api_docs'),'View','2'),
(62,'electrophysiology_browser_edit_annotations','Annotations',(SELECT ID FROM modules WHERE Name='electrophysiology_browser'),'Create/Edit','2'),
(63,'monitor_eeg_uploads','Monitor EEG uploads',(SELECT ID FROM modules WHERE Name='electrophysiology_uploader'),NULL,'2');
(63,'monitor_eeg_uploads','Monitor EEG uploads',(SELECT ID FROM modules WHERE Name='electrophysiology_uploader'),NULL,'2'),
(64, 'mindlogger_schema_create', 'Mindlogger applet schemas and instruments thru API', (SELECT ID FROM modules WHERE Name='mindlogger'), 'Create', 2);

INSERT INTO `user_perm_rel` (userID, permID)
SELECT u.ID, p.permID
Expand Down
25 changes: 25 additions & 0 deletions SQL/New_patches/2023-11-21-Mindlogger.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- Enable Mindlogger module
INSERT INTO modules (Name, Active)
VALUES ('mindlogger', 'Y');


-- Add Mindlogger module permissions
INSERT INTO `permissions` (code, description, moduleID, action, categoryID)
VALUES ('mindlogger_schema_create', 'Mindlogger applet schemas and instruments thru API',
(SELECT ID FROM modules WHERE Name = 'mindlogger'), 'Create', 2);


-- Create Mindlogger schema table
CREATE TABLE `instrument_mindlogger_schema`
(
`AppletID` varchar(36) NOT NULL,
`AppletSchema` text NOT NULL,
`CreatedDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`UpdatedDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (AppletId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Add Mindlogger instruments subgroup
INSERT INTO test_subgroups (Subgroup_name)
VALUES ('Mindlogger instruments');

1 change: 0 additions & 1 deletion modules/dictionary/php/datadictrow.class.inc
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ class DataDictRow implements \LORIS\Data\DataInstance,
'datascope' => $scope,
'type' => $this->getDataType(),
'cardinality' => $this->getCardinality(),
'visits' => $this->visits,
];

$itype = $this->item->getDataType();
Expand Down
24 changes: 24 additions & 0 deletions modules/mindlogger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Mindlogger

## Purpose
The API module is intended to provide a stable, versioned REST interface for receiving Mindlogger Applet schemas saving to the DB and creating instruments.

## Intended Users
The module has 1 intended use cases:

1. Interactions with data coming from outside the LORIS core softwar


## Scope

## Permissions

Accessing the `mindlogger` module requires the `mindlogger_schema_create` permission (_"Create Mindlogger applet schemas and instruments thru API"_)

Users with the `mindlogger_schema_create ` permission can create new ML applet schema entries and related instruments in the Loris.

## Configurations


## Interactions with LORIS

71 changes: 71 additions & 0 deletions modules/mindlogger/php/endpoint.class.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php declare(strict_types=1);
/**
* An API endpoint is an endpoint which abstracts away common element
* of different Mindlogger API endpoints.
*
* PHP Version 7
*
* @category Mindlogger
* @package Mindlogger
* @author Dzmitry Yahur <dyagur@scnsoft.com>
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://github.com/aces/Loris
*/

namespace LORIS\mindlogger;

use \Psr\Http\Message\ServerRequestInterface;
use \Psr\Http\Server\RequestHandlerInterface;
use \Psr\Http\Message\ResponseInterface;
use \LORIS\Http\Endpoint as LORISEndpoint;

/**
* An abstract class for common concerns of different API endpoints.
*
* @category Mindlogger
* @package Mindlogger
* @author Dzmitry Yahur <dyagur@scnsoft.com>
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://github.com/aces/Loris
*/
abstract class Endpoint extends LORISEndpoint implements RequestHandlerInterface
{
/**
* Return an array of valid HTTP methods for this endpoint
*
* @return string[] Valid versions
*/
abstract protected function allowedMethods(): array;

/**
* Return a list of Mindlog API versions which this endpoint
* supports.
*
* @return string[] LORIS API Versions
*/
abstract protected function supportedVersions(): array;

/**
* An API endpoint overrides the default Mindlog Endpoint to add checks for
* supported version(s).
*
* @param ServerRequestInterface $request The incoming PSR7 request
* @param RequestHandlerInterface $handler The PSR15 request handler
*
* @return ResponseInterface
*/
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$versions = $this->supportedVersions() ?? [];
$version = $request->getAttribute("Mindlogger-API-Version") ?? "unknown";
if (!in_array($version, $versions)) {
// If it's not supported by a version of the API, that means the
// endpoint for that version should be not found
return new \LORIS\Http\Response\JSON\NotFound('Unsupported version');
}

return parent::process($request, $handler);
}
}
165 changes: 165 additions & 0 deletions modules/mindlogger/php/endpoints/schema.class.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?php declare(strict_types=1);
/**
* This implements the Schema endpoint class
*
* PHP Version 8
*
* @category Mindlogger
* @package Mindlogger
* @author Dzmitry Yahur <dyagur@scnsoft.com>
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://github.com/aces/Loris
*/

namespace LORIS\mindlogger\Endpoints;

use \Psr\Http\Message\ServerRequestInterface;
use \Psr\Http\Message\ResponseInterface;
use \LORIS\mindlogger\Endpoint;

/**
* A class for handling the /schema endpoint.
*
* @category Mindlogger
* @package Mindlogger
* @author Dzmitry Yahur <dyagur@scnsoft.com>
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://github.com/aces/Loris
*/
class Schema extends Endpoint
{
const INSTRUMENT_SUBGROUP_NAME = "Mindlogger instruments";

/**
* Permission checks
*
* @param \User $user The requesting user
*
* @return boolean true if access is permitted
*/
private function _hasAccess(\User $user)
{
return $user->hasPermission('mindlogger_schema_create');
}

/**
* Return which methods are supported by this endpoint.
*
* @return array supported HTTP methods
*/
protected function allowedMethods(): array
{
return ['POST'];
}

/**
* Versions of the Mindlogger API which are supported by this
* endpoint.
*
* @return array a list of supported API versions.
*/
protected function supportedVersions(): array
{
return ["v1",];
}


/**
* Handles a request starts with /schema
*
* @param ServerRequestInterface $request The incoming PSR7 request
*
* @return ResponseInterface The outgoing PSR7 response
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$user = $request->getAttribute('user');

if ($user instanceof \LORIS\AnonymousUser) {
return new \LORIS\Http\Response\JSON\Unauthorized();
}

if (!$this->_hasAccess($user)) {
return new \LORIS\Http\Response\JSON\Forbidden();
}

switch ($request->getMethod()) {
case 'POST':
return $this->_handlePOST($request);

case 'OPTIONS':
return (new \LORIS\Http\Response())
->withHeader('Allow', $this->allowedMethods());

default:
return new \LORIS\Http\Response\JSON\MethodNotAllowed(
$this->allowedMethods()
);
}
}

/**
* Handle a POST request to the /schema endpoint
*
* @param ServerRequestInterface $request The incoming PSR7 request
*
* @return ResponseInterface The outgoing PSR7 response
*/
private function _handlePOST(ServerRequestInterface $request): ResponseInterface
{
$data = json_decode((string)$request->getBody(), true);
$db = \NDB_Factory::singleton()->database();

$appletId = $data["id"];
$appletSchema = $data;

if ($db->pselectOneInt("SELECT COUNT(*) FROM instrument_mindlogger_schema WHERE AppletID = ?", [$appletId])) {
return new \LORIS\Http\Response\JsonResponse(["status" => "failed", "message" => "Applet already exists"]);
}

try {
$db->beginTransaction();
$db->unsafeInsert(
"instrument_mindlogger_schema",
[
"AppletID" => $appletId,
"AppletSchema" => json_encode($appletSchema),
]
);
$this->makeInstrument($appletId, $appletSchema["displayName"]);
$db->commit();

return new \LORIS\Http\Response\JsonResponse(["status" => "OK"]);
} catch (\Exception $e) {
$db->rollback();
return new \LORIS\Http\Response\JSON\BadRequest(
$e->getMessage()
);
}
}


/**
* Create an instrument in the database
*
* @param string $testName The name of the instrument
* @param string $fullName The full name of the instrument
*
* @return void
*/
protected function makeInstrument(string $testName, string $fullName): void
{
$db = \NDB_Factory::singleton()->database();
$subgroupId = $db->pselectOneInt("SELECT ID FROM test_subgroups WHERE Subgroup_name = ?", [self::INSTRUMENT_SUBGROUP_NAME]);

$db->insert(
"test_names",
[
"Test_name" => $testName,
"Full_name" => $fullName,
"Sub_group" => $subgroupId,
"IsDirectEntry" => 0,
]
);
}
}