-
Notifications
You must be signed in to change notification settings - Fork 176
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
[redcap] new module #9474
base: main
Are you sure you want to change the base?
[redcap] new module #9474
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally finished reviewing the code, will test it now.
Okay, I finished reviewing the code, I am now in the process of testing it with MPN. Congratulations ! The module seems to work quite well and provides really convenient configuration options like multi-instance and multi-project integration. In addition you also use statically typed models instead of dictionaries directly, which I really like.
I wrote a bunch of small code comments, which you can also ignore if you do not agree with them, especially those marked with SUBJECTIVE.
On a broader level, I would have the following architectural comments:
- I think we should use
readonly
properties for the configuration and model classes, which would help to remove a lot of boilerplate code. - I would prefer to avoid abbreviated variable names like
pid
,iname
andrc
, and instead use longer, more descriptive, names likeproject_id
,instance_name
andredcap_client
. I know this does not play well with the 85-character line limit, but well, the problem is the limit here, not the longer names. I would also prefer more precise names likeinstrument_name
,event_name
andrecord_id
instead ofinstrument
,event
andrecord
when that makes sense. - In variable and type names, REDCap is sometimes written
Redcap
and sometimesREDCap
, I think we should stick with one (IMO the simplerRedcap
for code, and the "official"REDCap
for documentation). - The configuration parser can be simplified. The current code builds the global configuration first, and then mutates it to add the instance and project configurations. It would be simpler to descend the XML tree, gather the instance configurations, and only then build the global configuration (same reasoning applies for instance and project configurations).
- I need to have an additional REDCap project configuration parameter for MPN that changes the behavior of the pipeline. However, the configuration is currently not accessible from the REDCap notification handler. I think maybe there should be one configuration object accessible from there that contains all the configuration information for the specific project and instance of this call (instead of having a tree).
- There are several singletons and factories, but I am not sure if these are really needed. If they do not persist across requests they should be removed IMO.
- Same thing with the REDCap HTTP client cache. Is there a specific case that needs it ? If not, I think the abstraction should either be factorized so that it does not need to be manually read/written in each HTTP request call, or removed.
- Same thing with the REDCap HTTP client handler. I am not sure managing different clients is necessary. Probably what should happen is: 1. Get the REDCap instance and project from the DET notification, 2. Get the REDCap configuration for that instance and project from the configuration file, 3. instantiate a REDCap HTTP client from that configuration and pass it as an argument to the REDCap notification handler, without necessarily bookkeeping instantiated clients like now (unless there is a good reason to do so).
- The
RedcapNotification
class should be part of the REDCap HTTP client models, as it is structured JSON data obtained by HTTP from REDCap.
modules/redcap/php/configurations/redcapconfiguration.class.inc
Outdated
Show resolved
Hide resolved
modules/redcap/php/configurations/redcapconfiguration.class.inc
Outdated
Show resolved
Hide resolved
modules/redcap/php/configurations/redcapconfiguration.class.inc
Outdated
Show resolved
Hide resolved
modules/redcap/php/configurations/redcapconfiguration.class.inc
Outdated
Show resolved
Hide resolved
modules/redcap/php/configurations/redcapconfiguration.class.inc
Outdated
Show resolved
Hide resolved
modules/redcap/php/configurations/redcapconfigurationparser.class.inc
Outdated
Show resolved
Hide resolved
modules/redcap/php/configurations/redcapconfigurationparser.class.inc
Outdated
Show resolved
Hide resolved
61d5c00
to
28f3733
Compare
UPDATE: Since Regis is taking (well-deserved) time off, I am working on this PR myself and adding the required configuration options for MPN (that are hopefully also useful for other projects !). A few things I noticed during my tests:
|
c72c69b
to
3472533
Compare
Okay, I made some changes to the module for it to be usable for MPN, and made a few refactors that make the code (IMO) cleaner. Still a few TODOs notably regarding error handling, but I think that's a nice progress. List of sifnificant changes:
With all these changes, I removed almost all of my previous code comments except for a few ones I am still interested about. Notably TODOs:
CI seems broken because of an APT dependency, this is unrelated to the changes made here. |
3472533
to
5223553
Compare
@maximemulder happy new year, and thanks for adding more to this PR while I was away!
Todo:
CI issue -> not related to phan type checks? I will look into that for next week and I would like to have a quick meeting too so we can align on the next steps. |
db54f90
to
3478e29
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have done all the changes I wanted to do on the REDCap module (I am just finishing to update the tests), and it now seems to cover all my needs for MPN.
I have two comments here. While I refactored the configuration, I removed the instance name, but you can add it back if you want (IMO using the URL instead of the name is enough), it should be easy to add such options with the refactored configuration and parser.
if (isset($instrument_values['dtt'])) { | ||
$dt = \DateTime::createFromFormat( | ||
'Y-m-d H:i:s', | ||
$instrument_values['dtt'], | ||
); | ||
|
||
if (!$dt) { | ||
error_log( | ||
"[redcap] Could not parse 'dtt': " | ||
. $instrument_values['dtt'] | ||
); | ||
} else { | ||
$instrument_values['Date_taken'] = $dt->format('Y-m-d'); | ||
} | ||
} | ||
|
||
// if null/empty, try getting that based on the timestamp | ||
if (isset($instrument_values['Date_taken']) | ||
|| empty($instrument_values['Date_taken']) | ||
) { | ||
if (isset($instrument_values['timestamp'])) { | ||
$dt = \DateTime::createFromFormat( | ||
'Y-m-d H:i:s', | ||
$instrument_values['timestamp'] | ||
); | ||
if (!$dt) { | ||
error_log( | ||
"[redcap] Could not parse 'timestamp': " | ||
. $instrument_values['timestamp'] | ||
); | ||
} else { | ||
$instrument_values['Date_taken'] = $dt->format('Y-m-d'); | ||
} | ||
} | ||
} | ||
|
||
// if null/empty, try getting that based on the timestamp_start | ||
if (isset($instrument_values['Date_taken']) | ||
|| empty($instrument_values['Date_taken']) | ||
) { | ||
if (isset($instrument_values['timestamp_start'])) { | ||
$dt = \DateTime::createFromFormat( | ||
'Y-m-d H:i:s', | ||
$instrument_values['timestamp_start'] | ||
); | ||
if (!$dt) { | ||
error_log( | ||
"[redcap] Could not parse 'timestamp_start': " | ||
. $instrument_values['timestamp_start'] | ||
); | ||
} else { | ||
$instrument_values['Date_taken'] = $dt->format('Y-m-d'); | ||
} | ||
} | ||
} | ||
|
||
// if still null/empty, get the current date | ||
if (isset($instrument_values['Date_taken']) | ||
|| empty($instrument_values['Date_taken']) | ||
) { | ||
$dtNow = new \DateTimeImmutable(); | ||
$instrument_values['Date_taken'] = $dtNow->format('Y-m-d'); | ||
} | ||
|
||
// add the timestamp_stop in the values based on the last timestamp | ||
if (isset($instrument_values['timestamp']) | ||
&& !empty($instrument_values['timestamp']) | ||
) { | ||
// rename var to uniformize with other LORIS instruments | ||
// Duration will be calculated when _saveValues is called. | ||
$instrument_values['timestamp_stop'] = $instrument_values['timestamp']; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure I understand all this code. It seems rather hacky and has been a little fragile in my experience (I had to move some things around for it to work on MPN). It would be best to refactor it IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HBCD case, most of the timestamps were not certain to be there at some point, but we were still in need of a data of administration for each instrument Date_taken
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quickly tested, I have a consistent 500 error that might not be related. Trying to debug that.
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 | ||
* @link https://www.github.com/aces/Loris/ | ||
*/ | ||
class Queries |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used in notification handler only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so, but it is not intended and it could be extended with other queries to be used elsewhere if wanted.
TBH it might be good to split the notification handler into several classes for different tasks (get LORIS instrument, validate record dictionary, update instrument...) but I did not have the time for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the Query idea but same here, a lack of time.
Yes, we talked about splitting tasks and I wanted to it, but again, lack of time.
If it is working, I guess a refactoring can be done later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, the module is currently ok in terms of code, can be improved but nothing blocking if it works IMO.
Brief summary of changes (WIP)
This PR adds the REDCap interoperability module.
This module opens an API endpoint to receive notifications from REDCap servers through the use of the
Data Entry Trigger (DET)
. REDCap notifications are parsed and REDCap records are imported into LORIS.If an error happens during this notification handling process, an error is generated in the issue tracker.
The module is imported from HBCD, and even if the core logic is the same, REDCap objects are now their own models.
This version also supports connection to multiple REDCap instances.
Features
The main goal was to replicate HBCD data import process based on
REDCap DET
.redcap2linst
: script to import REDCap forms as LORIS LINST instruments.fetch button
: UI button on instrument panel to trigger data import (could be done later)State
Testing instructions (if applicable)
Notification process:
config.xml
following the structure given inRB/config.xml
file.Front-end > Admin > Configuration > REDCap > redcap_importable_instrument
./redcap/notifications
) to simulate the reception of a DET notification. The payload should be the following (seeredcap/notifications/redcapnotification.class.inc
):REDCap LINST instrument process:
tools/redcap2linst.php
usage.Link(s) to related issue(s)
Links to related PRs