eLabConnect

Use eLabconnect with your add-on

Use eLabConnect with your add-on

Lab instruments and devices are used on a daily basis in almost every workflow in the lab. These devices allow scientists to analyse, store, handle samples, and collect data during the experimentation process.

As a result, it is crucial to fully track any event that occurs during an experiment and be aware of those events when the need arises. Read more

Prerequisites

In order to support eLabConnect in your add-on the following line has to be added to your add-on code context.hasFileMonitoringSupport = true;. The implementation can be found in the example code below.

Local development

During normal production conditions, plugins that want to use eLABConnect need to own a unique pluginID. This plugin ID is automatically issued upon installation into production. This poses a challenge during development, as no pluginID has been issued yet and the script is being side-loaded.

To solve this issue, the sandbox environment has the eLabSync plugin installed. This plugin should be made available to your account. If this is not the case, please ask your contact within eLabNext to enable. You can review installed plugins by going to the eLabNext Marketplace. This plugin has the pluginID 320 and can be re-used in your own plugin during development.

This is simply done by making sure to use pluginID 320 during the fetching of files from the API. The sample plugin contained in the example below includes boiler-plate code to demonstrate how that is done.

Steps to demonstrate the upload and fetching of files using eLABConnect

  1. Install eLABConnect at the workstation that uploads your files Download
  2. Login to the sandbox environment
  3. Go to eLabNext Marketplace
  4. Follow the prompts in eLABConnect to choose the folder to monitor for automatic uploads
  5. Place a plain text file with at least 20 lines of text in the folder (ending with .txt). eLABConnect will start
    uploading
  6. Side-load the example code below and run your code to fetch the files. For sandbox.elabjournal.com, use 320 as
    the forcePluginID parameter, which is the ID of the eLABConnect Placeholder for development plugin. This allows
    you to use the side-loading of your plugin that doesn’t have a pluginID on itself
  7. Monitor the Console Logger of your browser. You should see an XML requests with a response from the API that
    includes the fields
  8. The example code below should alert the contents of the last text file uploaded

Remember to remove the ‘forcePluginID’ from your plugin before releasing into production to make sure it works as
intended

Example code

/*
@rootVar: FileParseExample
@name: File Parse Example
@version: 1.0.0
@description: Plugin that works together with eLABConnect
@author: Awesome Developer
*/

var FileParseExample = {};
(function (namespace) {
    namespace.hasFileMonitoringSupport = true;
    namespace.configurationSchema = function () {
        return {
            "max_file_age": {
                "title": "Maximum File Age (hours)",
                "type": "integer",
                "default": 168
            }
        };
    },

    context.init = function (config) {
        document.addEventListener('DOMContentLoaded', () => {
            context.myExampleAddon = new context.myExampleAddon(config);
        });
    };
    
    namespace.myExampleAddon = new Class({
        Implements: [Options, Events],
        options:{
            content: 'data that will be logged to the console',
            forcePluginID: 'Override pluginID for testing/sandbox purposes to make eLABConnect work without an installed instance of this plugin'
        },
        initialize: function (config) {
            var defaultConfig = {
                content: 'content',
                forcePluginID: null,
                max_file_age: 1000
            };
            config = $.extend(true, defaultConfig, config);
            this.setOptions(config);
            this.render();
        },
        render: function (config) {
            const self = this;
            const content= this.options.content;
            self.getFiles(function (sampleFiles) {
                // Load the first file (if any) and output to console
                if (sampleFiles.length > 0) {
                    const pluginFileID = sampleFiles[0].sdkPluginFileID;
                    self.loadDataFromFile(pluginFileID, function (content) {
                        console.log(content);
                        Dialog.show({
                            width: '450',
                            title: 'Plugin loaded',
                            content: ('<div style="background-color:#fff;padding:5px;margin-top:10px;"> File contents:<br/> ' + content + '</div>'),
                            btnOkLabel: 'Close',
                            btnCancel: false
                        });
                    });

                } else {
                    Dialog.alert('No files found yet', 'No uploaded files found yet');
                }
            });

        },
        destroy: function(){
            Dialog.close();
        },
        returnValidData: function (sampleFiles, maxFileAge) {
            if(!sampleFiles.length) return [];
            return sampleFiles.filter(s => new Date(s.lastModified) > maxFileAge);
        },
        getFiles: function (callback) {
            const _self = this;
            const sdkPluginID = _self.options.forcePluginID !== null ? _self.options.forcePluginID : _self.options.pluginID;
            const maxFileAge = _self.options.max_file_age;
            const thisFileAge = new Date(new Date() - maxFileAge * 1000 * 3600);

            fetch(`/api/v1/plugins/${sdkPluginID}/files?$sort=lastModified%20desc&$expand=meta`)
                .then(response => {
                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }
                    return response.json();
                })
                .then(data => {
                    const sampleFiles = [];
                    if (typeof data.data !== 'undefined' && data.data.length > 0) {
                        data.data.forEach(o => {
                            sampleFiles.push(o);
                        });
                    }
                    if (location.hostname.split('.')[0] !== 'sandbox') {
                        sampleFiles = _self.returnValidData(sampleFiles, thisFileAge);
                    }
                    callback(sampleFiles);
                })
                .catch(error => {
                    console.error('Error:', error);
                });

        },
        loadDataFromFile: function (pluginFileID, callBack) {
            var _self = this;
            if (pluginFileID === 0) {
                Dialog.close();
                Dialog.show({
                    title: '<i class="fa fa-exclamation-circle" style="color:red;margin-right:5px;font-size:14px;"></i>Invalid file or filename',
                    content: 'The file or filename that was given is invalid or does not exist.',
                    btnOkLabel: 'Close',
                    btnCancel: false
                });
            } else {
                fetch(`/api/v1/plugins/files/${pluginFileID}/download`)
                    .then(response => {
                        if (!response.ok) {
                            if (response.status === 404) {
                                // Handle 404 status
                            } else {
                                throw new Error('Network response was not ok');
                            }
                        }
                        return response.json();
                    })
                    .then(data => {
                        callBack(data);
                    })
                    .catch(error => {
                        console.error('Error:', error);
                    });
            }
        }
    });
    // This need to be called to make the add-on active via the developer tab inside eLab. When the add-on is added to the marketplace, this is not needed anymore.
    context.init({
        forcePluginID: 320 // Please remove in production 
    });
})(FileParseExample);