i2 Notebook SDK
Search results for

    Show/hide table of contents

    Creating a production-ready plug-in with the Create Notebook Plug-in tool

    This tutorial takes you through creating a production-ready plug-in for the i2 Web Client using a project scaffold created with the create-notebook-plugin tool.

    Prerequisites

    To follow the tutorial, you must install at least version 18 of Node.js on your development machine. For downloads and more information about Node.js, visit the project website at https://nodejs.org.

    This tutorial also requires a running instance of i2 Analyze on which you have permission to use the i2 Web Client.

    Create a plug-in

    Run the following command on the command line:

    npm create @i2analyze/notebook-plugin
    

    This command installs and executes @i2analyze/create-notebook-plugin, a tool designed for the purpose of creating plug-in projects for the i2 Web Client.

    When you run the tool, you'll see a number of options. For now, use the default value for each option by pressing Enter at each prompt.

    $ create-notebook-plugin
    
    Plugin-name: (My plug-in): My plug-in
    Plugin-description: (My plug-in description): My plug-in description
    Server URL: (http://localhost:9082/opal): http://localhost:9082/opal
    Run git init?: (no) No
    Run npm install?: (no) No
    
    $
    

    The tool creates a directory for the plug-in project whose name is derived from the name that you provide. With the default values, the directory is named my-plug-in.

    When the project is ready, run the following commands to install dependencies and start the development server and the plug-in proxy:

    cd my-plug-in
    npm install
    npm run dev
    

    The plug-in project is now running! In your browser, navigate to the Web Client at http://localhost:4000/opal/ and check that the plug-in has loaded correctly. Look for a new button in the application ribbon, and a new message in the developer tools console:

    My plug-in running
    

    If you see this message, then the plug-in is working correctly.

    The Create Notebook Plug-in tool configures the project folder for TypeScript, ESLint, and prettier. It also creates devproxy.json and plugin.json manifests, and adds scripts to allow for development and production builds. The latter use webpack to create a bundle that's deployable to an i2 Analyze server.

    Add a ribbon command to the plug-in

    At this stage, the plug-in sends some basic information to the browser's developer tools console, and adds a button that does nothing to the application ribbon. Next, we'll replace the generated code with some calls to the i2 Web Client API that modify the button to open geospatial locations in Google Maps.

    1. In src/entrypoint.ts, make the following changes.

      • Change the first line from:

        import { NotebookStatic } from '@i2analyze/notebook-sdk';
        

        to:

        import { commands, data, records, NotebookStatic } from '@i2analyze/notebook-sdk';
        
      • Change:

        name: 'My plug-in';
        

        to:

        name: 'View on map';
        
      • In the createCommand() call, change:

        type: 'unscoped';
        

        to:

        type: 'records';
        

        And change:

        onExecute() {
        

        to:

        onExecute(payload: commands.IRecordsContext) {
        
    2. Create this helper function at the top level of the entrypoint.ts file:

      function findGeospatialValue(chartRecords: data.IReadOnlyCollection<records.IChartRecord>) {
        for (const record of chartRecords) {
          for (const propertyType of record.itemType.propertyTypes) {
            if (propertyType.logicalType === 'geospatial') {
              const property = record.getProperty(propertyType);
              if (property !== undefined && !record.isValueUnfetched(property)) {
                return property as data.IGeoPoint;
              }
            }
          }
        }
      
        return undefined;
      }
      

      This function performs a simple scan of the records that it receives, and returns the first non-empty geospatial property that it finds.

    3. We can now use the helper function in the implementation of onExecute(), replacing the default code that the project was created with. We'll take the latitude and longitude to a new Google Maps window:

      onExecute(payload: commands.IRecordsContext) {
        const property = findGeospatialValue(payload.records);
      
        if (!property) {
          return;
        }
      
        window.open(
          `https://www.google.com/maps/@${property.latitude},${property.longitude},18z`,
          "_blank"
        );
      },
      
    4. Reload the Web Client, select an element with a geospatial property, and try the command. Google Maps should open at the relevant location.

    Make the command sensitive to selection

    There's a problem with our command: The button is enabled even if the selection doesn't actually contain a geospatial property. That would be misleading for a user, but we can fix it by taking control of the surfacing of the command in the user interface.

    1. After the onExecute() parameter to createCommand(), replace the onSurface() function definition:

      onSurface(action, eventApi, signal) {
        eventApi.addEventListener(
          "recordscontextchange",
            (context) => {
              action.setEnabled(!!findGeospatialValue(context.records));
            },
            { dispatchNow: true, signal }
        );
      },
      

      There are a few things going on here:

      • The Web Client calls our onSurface() function with an action object that represents the user interface control to which the command is bound each time it is surfaced. A single command might be surfaced in several places, and you can use the action object to react differently in each of them.

      • The function also receives an eventApi object through which it can listen to changes in the records to which the action is being applied; and a signal object that indicates when this particular surfacing of the command is being removed.

      • In our implementation, we subscribe to the recordscontextchange event, which tells us when the current records change. When they do change, we set the enabled state of the action to a value based on whether there is a geospatial value in the current records.

      • Event listeners are normally invoked when the event occurs. However, we don't want to wait for a recordscontextchange event before we run our setEnabled() code, so we specify dispatchNow: true to invoke the callback immediately, without waiting for the event. This in turn causes our action to be enabled or disabled correctly, right away.

      • We forward the signal to the event listener so that it unsubscribes automatically from the recordscontextchange event when the action is unsurfaced.

    2. Reload the Web Client. The button in the application ribbon is now enabled only when a geospatial property exists in the selection.

    Use the command in more than one place

    As well as adding it to the ribbon, we can add exactly the same command to the chart pop-up menu with a single line of code.

    1. After the existing call to surfaceCommands(), add:

      api.commands.chartPopupMenu.surfaceCommands(viewOnMap);
      
    2. Reload the Web Client. The same command, with the same enablement rules, is now present in the chart's pop-up menu.

    Deploy the plug-in

    To deploy our plug-in on the server, we should rebuild it ready for deployment using a production build. We then need to add it to the server configuration, and then redeploy the server.

    Note: If you follow this procedure in a deployment that provides high availability, you must complete each step on every Liberty server in your environment before you move to the next step.

    1. Build a production version of the plug-in by executing the following command:

      npm run build
      

      This compiles the plug-in and bundles it into a dist folder in the project. That folder contains everything that you need to copy to the server.

    2. On the server that hosts the i2 Analyze deployment, find the toolkit/configuration directory, and then navigate to the fragments/opal-services directory that it contains.

    3. If the opal-services directory does not already contain a plugins subdirectory, create one.

    4. Create a directory for your plug-in inside the plugins directory (for example, plugins/plugin-basic), and copy the contents of the dist folder into this folder.

    5. Run the following toolkit commands to update the deployed i2 Analyze server:

      setup -t stopLiberty
      setup -t deployLiberty
      setup -t startLiberty
      
    6. Stop the development proxy, and use the browser to navigate to your real server address. You'll find that your plug-in was successfully deployed.

    Next steps

    By following the procedure in this tutorial, you've used the create-notebook-plugin tool to create an i2 Web Client plug-in that, though basic, is fully integrated with the application. It displays actions in the user interface, it responds to changes in selection, and it sends data from chart records to an external application.

    Most real plug-ins go further than this one by adding a tool view that presents users with a custom interface for performing more complex tasks. You can see the fundamentals of how tool views work by following one of the examples that use popular UI frameworks to implement a tool view.

    You can instruct create-notebook-plugin to create such a plug-in by specifying a template that uses React to implement a skeleton tool view:

    npm create @i2analyze/notebook-plugin --template=plugin-with-toolview
    
    In this article
    Back to top © N. Harris Computer Corporation