i2 Notebook SDK
Search results for

    Show/hide table of contents

    Adding item types

    When charting items from datasets, you may encounter situations where the item types available in the i2 Analyze server schema do not fully align with your data. The i2 Notebook SDK allows you to create custom item types and define properties tailored to your specific needs.

    This tutorial demonstrates how to add custom item types to the chart schema and listen for schema change events. It focuses on the core SDK APIs rather than UI implementation. The complete sample includes a simple interface (a textarea pre-populated with sample JSON data and a button) for demonstration purposes, but the tutorial concentrates on the key API usage. In practice, you would adapt these APIs to work with your own data sources and schema structures.

    You can find a full version of the source code for this tutorial in the samples/item-type-plugin folder.

    Prerequisites

    This tutorial requires:

    • A running instance of i2 Analyze version 4.4.4.5 or later
    • i2 Notebook SDK version 1.6 or later

    These versions introduced the functionalities discussed in this tutorial.

    Create a plug-in

    To get started, we'll use the @i2analyze/create-notebook-plugin package to quickly scaffold the plug-in structure. For a detailed walkthrough on creating a plug-in, refer to the Creating a production-ready plug-in with the Create Notebook Plug-in tool tutorial.

    Run the following command to create a plug-in with a tool view:

    npx @i2analyze/create-notebook-plugin --template=plugin-with-toolview
    

    This command initializes the plug-in and tool view, providing a foundation for development.
    Follow the prompts to configure your plug-in. Once the setup is complete, you will have a basic structure ready for further customization.

    Set up the SDK version

    Ensure that your project has the correct version of the SDK installed. In package.json, verify or update the dependency:

    {
      "dependencies": {
        "@i2analyze/notebook-sdk": "^1.6.0"
      }
    }
    

    After updating your dependencies, modify the entrypoint.ts file to request the correct API version:

    const api = await notebook.getEntryPointApi('ac7bcb47-f3d5-45c8-8d41-8ca30cf8ec06', '1.6');
    

    Before proceeding, ensure that your plug-in is functioning correctly.

    Add item types

    To demonstrate how to add item types, we'll use example JSON data based on a wildlife crime seizure shipment scenario. This example shows the complete workflow:

    1. Define custom item types (entity and link types) with their properties
    2. Add these types to the chart schema
    3. Create records using the newly defined types

    The process requires two separate mutation operations: first adding the item types to the schema, then adding the actual records.

    Example JSON data

    {
      "data": [
        {
          "shipmentId": "S1-DEP",
          "typeId": "departure",
          "name": "Departure",
          "icon": "Cargo Plane (Rotary Wing)",
          "properties": [
            {
              "type": "date",
              "name": "Departure Date",
              "value": "2023-10-01"
            },
            {
              "type": "singleLineString",
              "name": "Country",
              "value": "Congo"
            }
          ]
        },
        {
          "shipmentId": "S1-DES",
          "typeId": "destination",
          "name": "Destination",
          "icon": "Place",
          "properties": [
            {
              "type": "date",
              "name": "Expected Arrival",
              "value": "2023-10-04"
            },
            {
              "type": "singleLineString",
              "name": "Country",
              "value": "Vietnam"
            }
          ]
        },
        {
          "typeId": "seizure",
          "name": "Seizure",
          "fromId": "S1-DEP",
          "toId": "S1-DES",
          "properties": [
            {
              "type": "date",
              "name": "Seizure Date",
              "value": "2023-10-03"
            },
            {
              "type": "singleLineString",
              "name": "Seizure Country",
              "value": "Singapore"
            }
          ]
        }
      ]
    }
    

    To add item types, we'll create a function that iterates through each record and determines whether to add an entity or a link. A link type is identified by the presence of fromId and toId fields in the JSON data.

    The sample's ToolView.tsx contains an addItemTypes function that demonstrates this process, filtering out items whose types already exist in the schema to avoid duplicates:

    The helper function isLinkData() checks for the presence of fromId and toId fields. See the complete implementation in the sample code.

    const addItemTypes = (items: (IEntity | ILink)[]) => {
      toolViewApi.runTrackedMutations((application, mutations) => {
        // Filter items whose types are not already in the schema
        const filteredItems = items.filter((item) => {
          const foundItemType = application.chart.schema.itemTypes.find(
            (itemType) => itemType.displayName === item.name
          );
          return !foundItemType;
        });
    
        if (filteredItems.length === 0) {
          return { type: 'rollback' };
        }
    
        for (const item of filteredItems) {
          if (isLinkData(item)) {
            mutations.addLinkType({
              displayName: item.name,
              propertyTypes: item.properties.map((property) => ({
                displayName: property.name,
                logicalType: property.type,
                isLabelPart: true,
              })),
            });
          } else {
            mutations.addEntityType({
              displayName: item.name,
              icon: item.icon,
              propertyTypes: item.properties.map((property) => ({
                displayName: property.name,
                logicalType: property.type,
                isLabelPart: true,
              })),
            });
          }
        }
    
        return {
          type: 'commit',
          actionDisplayName: 'Add item types',
        };
      });
    };
    

    For adding an entity type, we use addEntityType(), which requires a displayName, an icon, and optional propertyTypes. For a link type, we use addLinkType(), which requires a displayName and optional propertyTypes.

    Each property type includes:

    • displayName: The name shown in the UI
    • logicalType: The data type (e.g., 'date', 'singleLineString')
    • isLabelPart: Whether this property appears in the record's label (only applicable for string-based types), non-string properties will throw an error.

    After running this function, you now have two entity types (Departure and Destination) and one link type (Seizure) added to the chart schema.

    Add records

    Once the item types are added, you can create new records using these item types. The following simplified example demonstrates the key concepts for creating both entity and link records:

    const addRecords = (items: (IEntity | ILink)[]) => {
      toolViewApi.runTrackedMutations((application, mutations) => {
        const pendingRecordMap = new Map<string, sdk.app.IPendingRecord>();
    
        for (const item of items) {
          // Find the item type from the schema
          const itemType = application.chart.schema.itemTypes.find(
            (type) => type.displayName === item.name
          );
    
          // Build properties array
          const properties: [sdk.schema.PropertyTypeSpecifier, sdk.data.PropertyValue][] = [];
          item.properties.forEach((property) => {
            const propertyType = itemType.propertyTypes.find((pt) => pt.displayName === property.name);
            if (propertyType) {
              let value: sdk.data.PropertyValue = property.value;
    
              // Convert date strings to LocalDate
              if (propertyType.logicalType === 'date') {
                const date = new Date(property.value);
                value = mutations.valueFactory.createLocalDate(
                  date.getFullYear(),
                  date.getMonth() + 1,
                  date.getDate()
                );
              }
    
              properties.push([propertyType, value]);
            }
          });
    
          if (isLinkData(item)) {
            // Create a link record
            mutations.addLinkRecord({
              itemType,
              fromEnd: pendingRecordMap.get(item.fromId).recordId,
              toEnd: pendingRecordMap.get(item.toId).recordId,
              linkDirection: 'none',
              properties,
            });
          } else {
            // Create an entity record
            const pendingRecord = mutations.addEntityRecord({
              itemType,
              properties,
            });
    
            // Store for link record creation
            pendingRecordMap.set(item.shipmentId, pendingRecord);
          }
        }
    
        return {
          type: 'commit',
          actionDisplayName: 'Add records',
        };
      });
    };
    

    This example focuses on the core SDK APIs: addEntityRecord() for creating entities and addLinkRecord() for creating links between them. For a complete implementation with proper error handling and validation, refer to ToolView.tsx in the sample code.

    Based on the example data, three records will be added to the chart: a Departure entity record, a Destination entity record, and a Seizure link record that connects them.

    Note: In this example, the item type is looked up from the schema by its name. However, it's recommended to use the typeId for lookups, as item types may share the same name. The typeId is always unique.

    When you add an item type, the API returns a schema.IPendingItemType object, which provides access to the typeId. Using this unique identifier ensures reliable and consistent lookups, even if item names are duplicated.

    Listen for chart schema change events

    When using runTrackedMutations(), if the mutation alters the schema, you can listen for chart schema changes in the mutation response handler.

    toolViewApi.runTrackedMutations(
      (application, mutations) => {
        // Perform mutations here
      },
      (_, result) => {
        if (result.chartSchemaChange) {
          // Handle the chart schema change if needed
        }
      }
    );
    

    Alternatively, you can set up an event listener in entrypoint.ts or in any other tool views where you want to track chart schema changes. When you set up an event listener like this, schema changes can be triggered by any part of the application, not just your plug-in. The callback provides the source ID of the triggering plug-in, allowing you to check it against your plug-in ID to determine whether the event originated from your plug-in or another source.

    Here's an example of how to set up a callback to listen for chartschemachange events:

    api.addEventListener('chartschemachange', (chartSchemaChange) => {
      if (chartSchemaChange.pluginId === myPluginId) {
        // Handle changes caused by this plug-in
      } else {
        // Handle changes caused by other plug-ins
      }
    });
    

    myPluginId is your plug-in's unique identifier from the manifest

    This callback allows you to differentiate between schema changes made by your plug-in and those made by other plug-ins, enabling appropriate handling of each scenario.

    In this article
    Back to top © N. Harris Computer Corporation