Changing data from a plug-in
The i2 Notebook Web API provides the ability to write data to the chart, either by adding new records or by editing or deleting existing records. The API also allows you to change which records on the chart are selected, and to modify the view state of the chart.
All changes to the chart take place asynchronously, and you must perform them using transactions to ensure that they do not conflict with changes made by other plug-ins or users.
In the i2 Notebook Web API, you use mutations for this purpose. Mutations are transactions that run as a single atomic action, and contain one or more operations that make changes to chart data or application state.
When asked to start a mutation transaction, the application calls a mutation handler that you provide, which receives the following parameters:
A parameter of type
IApplicationContents, which provides read access to the visual and record data for the chart, as well as to its schema.A parameter that implements one of the
...Mutationsinterfaces, which provides access to mutation operations that allow changes to record and application state.
Commit and rollback
Just like database transactions, when your mutation handler finishes, it must indicate whether the mutation operations that it created should be "committed" and applied to the chart and application, or "rolled back" and not applied.
Committing a mutation causes operations in the handler to be applied to the chart. Some operations, such as selection and view changes, are applied immediately. Other operations, such as writing record data, are sent to the server before control returns to the application.
Mutation reports
When you commit or roll back a mutation, you can opt to provide information that is displayed to the user as a notification pop-up. The ISuccessReport and IDetailedReport define objects that you can return from mutation handlers.
Note: The fate of the transaction (commit or rollback) and the report that the user sees (success, failure, or nothing) are independent from each other. Reporting an error to the user is often associated with rolling back the mutation transaction, but you can combine a transaction commit with an error report, or a rollback with a success report.
i2 Notebook adheres to two standard design behaviors - demonstrated most obviously in the built-in Expand functionality - that provide a helpful and unobtrusive user experience when you follow them in your own plug-ins:
It's not always necessary to provide an explicit success report to the user. For a command that adds data to the chart, the success of the operation is often obvious from the fact that data has been added to the chart. Only provide an explicit "success" report if there is something else of value to report to the user.
If a command is supposed to add data to the chart but after starting the mutation you discover that there is no (new) data to add, then roll back the transaction and provide an explicit report to the user to indicate that there was nothing to add.
(In these circumstances, reporting nothing to the user would leave them uncertain whether the command even ran, harming their confidence in the application and your plug-in.)
Deciding whether "nothing was added" is a "success" or a "failure" will depend on factors such as whether the lack of new data is a normal situation or caused by an error.
Tracked and untracked mutations
i2 Notebook supports two different types of mutation:
Tracked mutations, which users can undo, and which you start by calling
IApplication.runTrackedMutations().Your tracked mutation handler receives an
ITrackedMutationsobject, which provides a full set of chart mutation operations.A committed, tracked mutation collects all of its operations into a single entry in the application undo stack. A user can undo all of the changes from a tracked mutation with a single undo action.
Untracked mutations, which users cannot undo, and which you start by calling
IApplication.runUntrackedMutations().Your untracked mutation handler receives an
IUntrackedMutationsobject, which provides a more restricted set of chart mutation operations. You can use an untracked mutation to change chart selection, chart view position and zoom level, and some other visual aspects of entities and links on the chart.The operations in an untracked mutation take place without creating an entry in the application undo stack.
If your command only affects selection or the view of the chart, then consider implementing it with untracked mutations. For all other commands, use tracked mutations.
Cancelation, status messages, and progress
If you implement a mutation handler as an asynchronous function, the handler can be canceled through the ICancelation object that it receives. Supporting cancelation is important for long-running operations: it lets the user abort and move on rather than waiting for an operation they no longer need. Triggering the cancelation automatically rolls back the mutation. If your code sees that cancelation.signal.aborted is true, stop further work and return a rollback result (optionally including a warning or informational report) so the user receives a clear notification.
While a mutation handler is running, the application can show status messages and progress indicators that your handler provides by using the status property of the ITrackedMutations or IUntrackedMutations interface:
status.setMessage()displays a text message alongside the spinner.status.setProgress(done, total)updates numeric progress. Whentotal > 0it shows a determinate progress bar; pass0/0to yield without showing a bar.You should
awaitthe promise returned bysetProgress(). Because JavaScript is single-threaded, a long-running mutation handler blocks the browser — the UI appears to freeze, spinner animations stop, and the browser may show "page unresponsive" warnings. AwaitingsetProgress()yields control back to the browser so it can continue rendering.
For example:
// Use an asynchronous mutation handler so that we can wait for the external data.
// During the fetch, the application shows a spinner, a message, and a cancel button.
const api = await getToolViewApi();
await api.runTrackedMutations(async (application, mutations, cancelation) => {
mutations.status.setMessage('Fetching data');
const response = await fetch('http://some-external-service.com', { signal: cancelation.signal });
const items = await response.json();
for (let i = 0; i < items.length; i++) {
// Check for cancelation before processing each item.
if (cancelation.signal.aborted) {
return {
type: 'rollback',
report: {
type: 'warning',
title: 'Aborted',
details: 'Data fetch aborted'
}
}
}
// Report progress and yield the UI thread to keep the application responsive.
await mutations.status.setProgress(i, items.length);
mutations.addEntityRecord(...);
}
return {
type: 'commit',
actionDisplayName: 'Fetch data'
}
});
Mutation response handling
Both runTrackedMutations() and runUntrackedMutations() return a Promise<IMutationResult> that resolves when the mutation completes. This is useful when you need to perform follow-up work based on what the mutation changed — for example, updating your tool view UI, logging the result, or chaining a second mutation that depends on the first.
try {
const result = await api.runTrackedMutations((application, mutations) => {
mutations.addEntityRecord(...);
return {
type: 'commit',
actionDisplayName: 'Add data',
};
});
// The result is available after the mutation completes.
console.log('Mutation completed:', result.chartChange);
} catch (error) {
// The promise is rejected if there is a problem performing the mutations.
console.error('Mutation failed:', error);
}
The same pattern applies to runUntrackedMutations().
Deprecated callback pattern
Deprecated since 1.9. The
responseHandlercallback parameter onrunTrackedMutations()andrunUntrackedMutations()is deprecated and will be removed in a future release. Use the returned promise instead.
Previously, a responseHandler callback was passed as the second argument to receive the mutation result:
// Deprecated — do not use this pattern in new code.
api.runTrackedMutations(
(application, mutations) => {
mutations.addEntityRecord(...);
return {
type: 'commit',
actionDisplayName: 'Add data',
};
},
(error, result) => {
if (error) {
console.error('Mutation failed:', error);
return;
}
console.log('Mutation completed:', result.chartChange);
}
);
To migrate, remove the responseHandler callback and await the returned promise as shown above.