i2b2 Web Client
Space shortcuts
Space Tools

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Anchor
_yu8g2z295iz9
_yu8g2z295iz9
Plugin Development for i2b2 Release v1.8+
Anchor
_3nbydelyh0ex
_3nbydelyh0ex
11st October 2022

Anchor
_uwh7i0fx1m3i_uwh7i0fx1m3i

...

3x8ylnph7ui3
_3x8ylnph7ui3

...

 https://doi.org/10.6084/m9.figshare.21507267


Anchor
_1761tph72oj7
_1761tph72oj7
SUMMARY

This document describes the design of the Plugin Manager component and plugin support libraries that interact with the new "modern" style i2b2 plugins. This new plugin manager does not handle or interact with "legacy plugins'' which are handled by the LEGACYPLUGIN manager.

Anchor
_v7dtrus4lox2
_v7dtrus4lox2
INTRODUCTION TO I2B2

...

The plugin-side support libraries are hosted in the /js-i2b2/cells/PLUGIN/libs directory and are injected into your plugin by the i2b2-loader.js file:
i2b2-sdx.js - This support library makes up the "Standard Data eXchange" (SDX) layer that enables drag drop handling of i2b2 objects.
i2b2-ajax.js - This support library will populate the i2b2.ajax namespace within your plugin with function calls that map to the AJAX calls of all the cells in the web client's main UI.
i2b2-state.js - This support library manages the i2b2.model and i2b2.state namespaces within your plugin. It saves the state of this namespace throughout subsequent reloads of your plugin that happens when the user rearranges UI tabs.
i2b2-auth-tunnel.js - This support library builds the i2b2.authorizedTunnel namespace which gives you the ability to access variables and functions that are within the main UI window.

Anchor
_90hm266neb60
_90hm266neb60
PLUGIN MANAGER

...

All plugins will exist entirely within the /plugins directory. Each plugin will be hosted in a single directory that follows Java-style library paths such as/edu/harvard/catalyst/example with the resulting plugin name being defined as "edu.harvard.catalyst.example". All files and libraries needed by the plugin will be contained solely within its own directory. This also includes a copy of the i2b2-loader.js script file. There will also be a file called plugins.json that exists within the root /plugins directory containing an array of plugin names as shown below.

Wiki Markup*\[*
"edu.harvard.catalyst.example",
"edu.harvard.WeberLab.ExportPatientset"
]
If you are using the i2b2-webclient-proxy service to host your web client UI it will automatically generate the plugins.json file based on the directory structure if the file does not exist. The Github repository for this proxy server is at _https://github.com/hms-dbmi/i2b2-webclient-proxy_

...

All plugins will have a configuration file named plugin.json within the root of the plugin's main directory. This file should at minimum have the following data:

title

A short title for your plugin

description

A description for your plugin

icons.size32x32

The filename of the icon graphic (that exists within the assets directory)

assetDir

The name of the assets directory for your plugin

base

The filename of your plugin's main HTML page


Here is an example file:
{
"title": "Example Plugin",
"description": "This is a simple plugin...",
"category"

Wiki Markup{*}: \[{*}"example"],
"icons": { "size32x32": "exampleplugin_32x32.gif" },
"assetDir": "assets",
"plugin_version" : "1.0",
"base": "index.html"
}
If you need to have access to variables or to execute function calls that exist within the main i2b2 UI then you would want to configure an authorizedTunnel variable. More details are in the section describing the Authorized Tunnel functionality.

...

Before your plugin is activated by an "I2B2_READY" window event, the i2b2-loader.js file injects several support files into your plugin. In doing this it builds the i2b2 namespace as follows:

i2b2.
MSG_TYPES: {AJAX: {…}, STATE: {…}, SDX: {…}, TUNNEL: {…}}
ajax: {PM: {…}, ONT: {…}, CRC: {…}, WORK: {…}}
authorizedTunnel: {authorizedList: {…}, function: …, variable: …}
h: {initPlugin: ƒ, getScript: ƒ}
model: {}
state: {save: ƒ}
sdx: {AttachType: ƒ, setHandlerCustom: ƒ}

i2b2.MSG_TYPES

Contains the "magic strings" used to communicate with the plugin manager in the main UI via Window.postMessage().

i2b2.ajax

Contains Promise-returning functions that return a string of XML which was returned by the i2b2 server for your particular API call.

i2b2.authorizedTunnel


Wiki Markup
Contains the "function\[\]" and "variable\[\]" accessors your plugin can use to programmatically access resources within the main UI.


i2b2.h

Some helper functions used by the i2b2 loader file.

i2b2.model

This is where your plugin should store its data that it wants preserved when the plugin reloads. Use i2b2.state.save() function to save it.

i2b2.state

Contains the save() function used to save the i2b2.model namespace.

i2b2.sdx

Contains two functions used to make HTML elements drop targets for the i2b2 SDX subsystem.


Anchor
_j6csl1jr1xkz
_j6csl1jr1xkz
Accessing Your Plugin's i2b2 Environment

To access the code environment of your i2b2 plugin you should load your plugin from the "Analysis Tools" tab on the right side of the i2b2 web client. After your plugin is loaded and displayed, you should open your browser's debug tools and go to the "Console" tab. Once in the console window you should see a dropdown menu on the left hand side of the screen. Use this dropdown to select your plugin's HTML file that was defined using the "base" variable in the plugin.json file. In this example it is called "index.html".
Image Modified

Once the proper file is selected in the drop down menu you will be able to view the i2b2 namespace and access your plugin's code from the console as shown below.
Image Modified


Anchor
_bggxoh9yc9h6
_bggxoh9yc9h6
Initialization of Your Plugin

Your plugin should wait until the loading and support file injection process is finished before attempting to access anything in the i2b2 namespace. This can be done safely by waiting for a window event called "I2B2_READY". This event occurs after the DOM is ready and after all i2b2 support libraries are also loaded. Some example code follows:
window.addEventListener("I2B2_READY", ()=> {
// i2b2 framework is loaded and ready (including population of i2b2.model)
// populate the UI based on a previously saved state (if it exists, defaults to "")
if (i2b2.model.stateString === undefined) i2b2.model.stateString = "";
document.getElementById("stateString").value = i2b2.model.stateString;
});

Anchor
_9sitddkud3r8
_9sitddkud3r8
During the initialization of the i2b2 framework within your plugin, you can also monitor or react to the initialization of each plugin support library. For each support library, there is an initial configuration event that is sent by the i2b2 loader process to the support library followed by an event signaling that the support library is initialized. The initialization event may contain a data payload for the support library. Both of these custom events are issued via window.dispatchEvent() and can be captured using window.addEventListener().


Support Library

Initialization Event

Initialized Event

SDX (Drag and Drop)

"I2B2_INIT_SDX"

"I2B2_SDX_READY"

AJAX Communication

"I2B2_INIT_AJAX"

"I2B2_AJAX_READY"

State Management

"I2B2_INIT_STATE"

"I2B2_STATE_READY"

Authorized Tunnel

"I2B2_INIT_TUNNEL"

"I2B2_TUNNEL_READY"

Anchor
_szndltkddkv5
_szndltkddkv5

Anchor
_3nq7x6ep47sc
_3nq7x6ep47sc
PLUGIN SUPPORT LIBRARIES


Anchor
_wav4s8yind4x
_wav4s8yind4x
Drag and Drop (SDX)

The Standard Data eXchange layer ("SDX") lives in the i2b2-sdx.js file. When this support library is fully loaded it emits the event "I2B2_SDX_READY". The new i2b2 web client has changed its drag and drop operations to use native browsers' Drag and Drop API functions. All of the complexity of the drag and drop operations has been abstracted away by this library. To simplify adoption, new plugins can use basically the same code as the older plugin style (pre-v1.8) to register a DOM element for drag drop operations. To accept a drag drop you need to: 1) register the DOM element for drops of specific SDX types, 2) register a handler function for the drop operation of that specific SDX type.
window.addEventListener("I2B2_SDX_READY", (event) => {
i2b2.sdx.AttachType("ExamplePlugin-psmaindiv", "PRS"); // Patient Record Set
i2b2.sdx.AttachType("ExamplePlugin-maindiv", "CONCPT"); // Ontology Concept
i2b2.sdx.AttachType("ExamplePlugin-maindiv", "PRS"); // Patient Record Set
i2b2.sdx.AttachType("ExamplePlugin-maindiv", "ENS"); // Encounter Set
i2b2.sdx.AttachType("ExamplePlugin-maindiv", "PRC"); // Patient Record Count
i2b2.sdx.AttachType("ExamplePlugin-maindiv", "QDEF"); // Query Definition
i2b2.sdx.AttachType("ExamplePlugin-maindiv", "QGDEF"); // Query Group Definition
i2b2.sdx.AttachType("ExamplePlugin-maindiv", "QI"); // Query Instance
i2b2.sdx.AttachType("ExamplePlugin-maindiv", "QM"); // Query Master
i2b2.sdx.AttachType("ExamplePlugin-maindiv", "WRK"); // Workspace Entry
// drop event handlers used by this plugin
i2b2.sdx.setHandlerCustom("ExamplePlugin-psmaindiv", "PRS", "DropHandler", ↩
i2b2.ExamplePlugin.prsDropped);
i2b2.sdx.setHandlerCustom("ExamplePlugin-maindiv", "CONCPT", "DropHandler", ↩
i2b2.ExamplePlugin.itemDropped);
i2b2.sdx.setHandlerCustom("ExamplePlugin-maindiv", "PRS", "DropHandler", ↩
i2b2.ExamplePlugin.itemDropped);
i2b2.sdx.setHandlerCustom("ExamplePlugin-maindiv", "ENS", "DropHandler", ↩
i2b2.ExamplePlugin.itemDropped);
i2b2.sdx.setHandlerCustom("ExamplePlugin-maindiv", "PRC", "DropHandler", ↩
i2b2.ExamplePlugin.itemDropped);
i2b2.sdx.setHandlerCustom("ExamplePlugin-maindiv", "QDEF", "DropHandler", ↩
i2b2.ExamplePlugin.itemDropped);
i2b2.sdx.setHandlerCustom("ExamplePlugin-maindiv", "QGDEF", "DropHandler", ↩
i2b2.ExamplePlugin.itemDropped);
i2b2.sdx.setHandlerCustom("ExamplePlugin-maindiv", "QI", "DropHandler", ↩
i2b2.ExamplePlugin.itemDropped);
i2b2.sdx.setHandlerCustom("ExamplePlugin-maindiv", "QM", "DropHandler", ↩
i2b2.ExamplePlugin.itemDropped);
i2b2.sdx.setHandlerCustom("ExamplePlugin-maindiv", "WRK", "DropHandler", ↩
i2b2.ExamplePlugin.itemDropped);
i2b2.sdx.setHandlerCustom("ExamplePlugin-maindiv", "XML", "DropHandler", ↩
i2b2.ExamplePlugin.itemDropped);
});
With the registered DropHandler function receiving a standard SDX data packet as shown below:
i2b2.ExamplePlugin.itemDropped = function(sdxData) {
console.dir("example plugin received item drop " + JSON.stringify(sdxData));
let mainDiv = document.getElementsByClassName("maindiv-content"

...

)

...

[

...

0];
mainDiv.innerHTML= JSON.stringify(sdxData);
};

Anchor
_76u2vsiyra6d
_76u2vsiyra6d
State Management

This functionality lives in the i2b2-state.js file. When this support library is fully loaded it emits the event "I2B2_STATE_READY". The new i2b2 web client uses HTML iframes to fully isolate plugin code from the main UI's environment. This allows plugins to use any libraries they want without having code conflicts with the main UI or other plugins. Unfortunately, browser standards dictate that when an iframe is manipulated within the DOM tree its contents is destroyed and reloaded. This results in destruction of the plugin's internal state. This can occur when a user drags and reorganizes the tabs in the main UI, or if the user expands the plugin to full size, or pops the plugin out into a floating window. To solve this problem, the i2b2 plugin framework can save the i2b2.model namespace to main UI memory. Also, upon loading of the state functionality library, any previous state is retrieved and automatically loaded into the i2b2.model namespace. In order to save the i2b2.model namespace you will need to call the i2b2.state.save() function whenever you update it as shown below:
function saveState() {
i2b2.model.stateString = document.getElementById("stateString").value;
i2b2.state.save();
}
To properly implement state management, you should drive your plugin from the data contained in the i2b2.model namespace. Upon loading your plugin, you should render the screen based on this persistent storage location.
window.addEventListener("I2B2_READY", ()=> {
// i2b2 is loaded and ready (including population of i2b2.model namespace)
if (i2b2.model.stateString === undefined) i2b2.model.stateString = "";
// populate the UI based on a previously saved state
document.getElementById("stateString").value = i2b2.model.stateString;
});

Anchor
_976tj4uyv32b
_976tj4uyv32b

Anchor
_reljepp4idhd
_reljepp4idhd
AJAX Calls to the i2b2 Server

This functionality lives in the i2b2-ajax.js file. When this support library is fully loaded it emits the event "I2B2_AJAX_READY". This library will populate the i2b2.ajax namespace with the loaded cells along with the AJAX calls that can be made to access server-side functionality. These function calls accept the same inputs as their original i2b2 function calls do. However, these functions will return a Javascript Promise that will resolve or reject and can be further processed using a .then() or .catch() as shown below:
function sendTestMsg() {
console.log("sending test message...");
let ajaxData = {
ont_synonym_records: "N",
ont_hidden_records: "N"
};
i2b2.ajax.ONT.GetCategories(ajaxData).then((data)=> {
console.log("Got response base from ONT.GetCategories()");
console.log(data);
}).catch((error) => {
console.error(error);
});
}
There is also a function called _RawSend() which is added to each cell's AJAX namespace and enables you to send a raw XML message to the cell as a string. The format you use to pass the information is:
let ResultPromise = i2b2.ajax.ONT._RawSend(serviceURL, rawMessage);
Where serviceURL is the path to the service endpoint when it is combined with the cell's base path. The variable rawMessage is a string containing the full XML message that is passed to the server. The _RawSend function will automatically populate the following template tokens:

{proxy_info}

Proxy redirection information.

{sec_user}

User that is currently logged in.

{sec_pass_node}

The password/session credential used to authenticate.

{sec_domain}

The domain that the user is logged into.

{sec_project}

The project that the user is logged into.

{header_msg_id}

A unique identifier for the message.

{header_msg_datetime}

The date and time that the message was set at.

{result_wait_time}

How long the request should stay open (defaults to 180 seconds).




The Ontology cell found at i2b2.ajax.ONT contains the following AJAX calls:
GetCategories(), GetChildConcepts(), GetChildModifiers(), GetCodeInfo(), GetModifierCodeInfo(), GetModifierInfo(), GetModifierNameInfo(), GetModifiers(), GetNameInfo(), GetSchemes(), GetTermInfo() and _RawSent().

The CRC cell found at i2b2.ajax.CRC contains the following AJAX calls: deleteQueryMaster(), getIbservationfact_byPrimaryKey(), getNameInfo(), getPDO_fromInputList(), getQRY_getResultType(), getQueryInstanceList_fromQueryMasterId(), getQueryMasterList_fromUserId(), getQueryResultInstanceList_fromQueryInstanceId(), getQueryResultInstanceList_fromQueryResultInstanceId(),
getRequestXml_fromQueryMasterId(), renameQueryMaster(), runQueryInstance_fromQueryDefinition() and _RawSent().

The Workplace cell found at i2b2.ajax.WORK contains the following AJAX calls:
addChild(), annotateChild(), deleteChild(), getChildren(), getFoldersByProject(), getFoldersByUserId(), moveChild(), renameChild() and _RawSent().

The Project Management cell found at i2b2.ajax.PM contains the following AJAX calls:
deleteApproval(), deleteCell(), deleteDBLookup(), deleteGlobal(), deleteHive(), deleteParam(), deleteProject(), deleteRole(), deleteUser(), getAllApproval(), getAllCell(), getAllDBLookup(), getAllGlobal(), getAllHive(), getAllParam(), getAllProject(), getAllProjectRequest(), getAllRole(), getAllRoleUser(), getAllUser(), getApproval(), getCell(), getDBLookup(), getGlobal(), getParam(), getProject(), getProjectRequest(), getUser(), getUserAuth(), setApproval(), setCell(), setDBLookup(), setGlobal(), setHive(), setParam(), setPassword(), setProject(), setProjectRequest(), setRole(), setUser(), and _RawSent().



Anchor
_n5yattpoh3b1
_n5yattpoh3b1
Authorized Tunnel Access

This functionality lives in the i2b2-auth-tunnel.js file. When this support library is fully loaded it emits the event "I2B2_TUNNEL_READY". The new i2b2 web client uses HTML iframes to fully isolate the plugin code environment from the code environment of the main i2b2 UI. In some situations, you may need to access a variable or execute a function that exists within the main UI. A logical tunnel was created to accomplish this which provides secure access to these resources.
In order to access a variable or function within the main UI you will first need to add a configuration to your plugin's plugin.json file as shown below:
{
"title": "Example Plugin",
"description": "This is a simple plugin that gives examples of how to do drag/drop, communications, and state management",
"category"

...

Wiki Markup
: \[

: ["example"],
"icons": { "size32x32": "exampleplugin_32x32.gif" },
"assetDir": "assets",
"base": "index.html",
"authorizedTunnel": {
"variables" :{
"i2b2.PM.model.isAdmin": "R",
"i2b2.ONT.cfg": "RO"
},
"functions"

...

:

...

[

...

 

"i2b2.h.getDomain",

"i2b2.h.getProject",
"i2b2.ONT.view.nav.doRefreshAll"
]
}
}
Within the authorizedTunnel namespace exists an object named variables having its keys be the path used to access the variables within the main i2b2 UI code environment and its values being a string that contains the access permissions to that variable. The access string consists of a combination of the following 3 characters in any order: "R" signifying the read permission, "W" signifying the write permission, and "O" signifying that the targeted variable is an object to be read or written. If the targeted variable is found to be an object but the "O" flag is not used then a security error is thrown in the main UI and the request is ignored.
Within the authorizedTunnel namespace also exists an array named functions containing strings which are the path used to access the functions within the main i2b2 UI code environment.

...



To

...

*read

...

a

...

variable's

...

value*

...

you

...

will

...

need

...

to

...

use

...

i2b2.authorizedTunnel.variable

...

as

...

shown

...

below.

...

Notice

...

how

...

the

...

accessor

...

method

...

uses

...

square

...

brackets [] to access the variable and not parentheses as in a function. Also, due to the asynchronous nature of window.postMessage()

...

communications

...

a

...

Javascript

...

Promise

...

is

...

returned

...

instead

...

of

...

the

...

actual

...

value

...

of

...

the

...

variable.

...

This

...

Promise

...

can

...

be

...

rejected

...

and

...

an

...

error

...

thrown

...

in

...

the

...

main

...

UI

...

if

...

unauthorized/unconfigured

...

access

...

is

...

attempted.



i2b2.authorizedTunnel.variable

...

[

...

"i2b2.PM.model.isAdmin"].then((isReallyAnAdmin) => {
if (isReallyAnAdmin) {
alert("You ARE logged in as an Administrator");
} else {
alert("You are NOT logged in as an Administrator");
}
});
To set a variable's value you will also use i2b2.authorizedTunnel.variable as but you will simply use it on the left side of an assignment statement as shown below. If your plugin has not been configured or is otherwise not allowed write permissions to the variable your plugin will not be notified that the value was not assigned however an error will be thrown in the main UI.
i2b2.authorizedTunnel.variable

...

[

...

"i2b2.PM.model.isAdmin"] =true;

...


To

...

*access

...

a

...

function*

...

you

...

will

...

use

...

i2b2.authorizedTunnel.function.

...

Once

...

again,

...

you

...

will

...

use

...

square

...

brackets

...

\[

...

\]

...

to

...

access

...

the

...

function

...

as

...

shown

...

below.

...

When

...

accessing

...

the

...

function

...

an

...

anonymous

...

function

...

will

...

be

...

returned

...

which

...

accepts

...

any

...

number

...

of

...

parameters

...

that

...

you

...

can

...

send

...

to

...

the

...

real

...

function

...

within

...

the

...

main

...

UI.

...

When

...

this

...

anonymous

...

function

...

is

...

executed

...

it

...

also

...

returns

...

a

...

Javascript

...

Promise

...

which

...

will

...

resolve

...

with

...

the

...

value

...

returned

...

by

...

the

...

function,

...

or

...

it

...

may

...

be

...

rejected

...

and

...

an

...

error

...

is

...

thrown

...

within

...

the

...

main

...

UI.

let mainUI_function = i2b2.authorizedTunnel.function

...

[

...

"i2b2.h.getDomain"];
let mainUI_promise = mainUI_function(my, parameters, here);
mainUI_promise.then((domain) => {
alert("The i2b2 domain is: "+domain);
});
This can be written more succinctly as follows:
i2b2.authorizedTunnel.function

...

[

...

"i2b2.h.getDomain"](my,parameters,here).then(
(domain) => {
alert("The i2b2 domain is: "+domain);
}
);


Anchor
_66xfw7awz2lp
_66xfw7awz2lp
i2b2 Web Client Repository

https://github.com/hms-dbmi/i2b2v2-webclient





Anchor
_bkpn9nbdcped
_bkpn9nbdcped

Anchor
_ob4kzuprcoqd
_ob4kzuprcoqd

Anchor
_58wr84e2wijr
_58wr84e2wijr

Anchor
_i8hjc4uhqr5e
_i8hjc4uhqr5e

Anchor
_55tpjuswpqci
_55tpjuswpqci

Anchor
_r0rv0wj5d61j
_r0rv0wj5d61j

Anchor
_seppwjy98ybb
_seppwjy98ybb

Anchor
_5y1gc7u2uqx1
_5y1gc7u2uqx1

Anchor
_2hjgg4k1w3ec
_2hjgg4k1w3ec

Anchor
_qrvsxe4i2xo6
_qrvsxe4i2xo6
Acknowledgments

Nick Benik, Marc-Danie Nazaire, Marc Ciriello, Anupama Maram, Perminder Singh, Griffin Weber