Scripted widgets

This topic describes how to build and publish scripted widgets for dashboards. The description is accompanied by a walkthrough of implementing a sample scripted widget.

Note: Scripted widgets are available as a technical preview.

Overview

Scripted widgets are external actions configured with mode.name: "widget". After they have been deployed, scripted widgets behave like other dashboard widgets:

  • They appear in the dashboard widget gallery using their title and description.

  • They can include one or more configuration pages where users define what the widget should show.

  • The widget is loaded into the dashboard in an iFrame and communicates with OpenText Core Software Delivery Platform through a postMessage event protocol.

  • Widgets can be hosted externally on your server, or uploaded as a bundle (HTML/CSS/JS plus JSON) and served by OpenText Core Software Delivery Platform using {bundle_url}.

Example walkthrough

The topic includes a walkthrough of a Defect Trend Analysis widget that is not provided by the built-in widget gallery. The widget is designed to show how many defects are opened each week, grouped by severity or team. The widget enables drill-down to the list of underlying defects.

The out-of-the-box widgets do not provide this specific functionality. To fill the gap, you can build a Defect Trend Analysis scripted widget.

Sample widget bundle

You can download a sample bundled widget and use its files and structure as a basis for your own scripted widgets. For details, see Sample widget bundle.

Back to top

Process overview

The following table outlines the main stages in creating and deploying a scripted widget:

Stage Description
Design the widget Define the purpose, data sources, interactions, and required configuration options.
Describe the widget in JSON Provide identity, gallery metadata, and page locations (mode.name: "widget", configuration pages, display URL).
Build configuration pages Capture user inputs (scope, filters, time range, grouping) and wire them to OpenText Core Software Delivery Platform with postMessage events.
Implement the widget display Render the widget using OpenText Core Software Delivery Platform REST data and the saved configuration; handle refresh and state.
Add advanced capabilities Support advanced filters, color consistency, and drill-down into entity lists using the event protocol.
Package and upload Bundle JSON plus HTML/CSS/JS, then upload as an external action so it appears in the dashboard gallery under the My Organization category.

The following sections describe these steps in detail.

Back to top

Describe the widget in JSON

Every scripted widget starts with a JSON configuration file. The file defines the widget's identity and makes it available in the dashboard widget gallery. This JSON configuration defines the following:

  • That the current external action is a widget (mode.name: "widget") and that the supported module is dashboard ("modules": ["dashboard"]).

  • The widget's identity (name), display title, and the location of its HTML files.

  • (Optional) A description that appears in the Widget Gallery under the widget's title.

  • (Optional) One or more configuration pages as tabs in the configuration dialog.

  • (Optional) The workspaces that can use the widget.

Minimal JSON properties

Your widget's JSON must include at least:

Property Details
name Unique identifier for the widget, used in logs and errors.
title The widget's display title in the gallery and on the dashboard.
description (Optional) Description shown in the widget gallery under the widget's title.
url URL of the widget's main display HTML file. Use {bundle_url} if uploading as a bundle.
mode.name Must be set to "widget".
mode.modules Must be set to ["dashboard"].
mode.description Shown in the widget gallery. Explain what the widget does.
mode.configuration_pages Array of configuration pages (tabs). Each has a title and url.
workspaces / exclude_workspaces (Optional) Scope the widget to or away from specific workspaces.

Example: Defect Trend Analysis widget JSON

The following example shows a JSON skeleton for the sample widget:

Copy code
ERROR: Invalid Code Highlighting Language

When this JSON is packaged and uploaded as an external action:

  • The widget appears in the dashboard widget gallery with the specified title and mode.description.

  • When users add it to a dashboard and click Configure, OpenText Core Software Delivery Platform opens the configuration pages you defined.

For a full list of JSON options, see JSON properties.

Back to top

Build configuration pages

To allow users to configure the widget, you can design configuration HTML pages in which they can customize the widget's scope and set up its visualization.

These pages run in iFrames and communicate with OpenText Core Software Delivery Platform using an event protocol based on window.parent.postMessage().

In the Defect Trend example, you can allow the user to configure the following:

  • Which entity types / subtypes to include (for example, defects only, or defects and user stories)

  • Which workspaces to pull data from (if cross-workspace is supported)

  • A saved advanced filter for scoping data

  • Time period, grouping, and visual style

All of these choices are saved in a single configuration object per widget instance. Configuration persistence (except for the filter) is handled by OpenText Core Software Delivery Platform. Filter persistence is handled by the external file.

Core configuration events

Configuration pages use three main events:

Event When to use it What it does
octane_read_widget_configuration When the configuration page loads Request the current configuration for this widget instance from OpenText Core Software Delivery Platform.
octane_widget_configuration In response to reads / updates OpenText Core Software Delivery Platform sends the current configuration back to the page.
octane_update_widget_configuration Whenever the user changes a setting Instruct OpenText Core Software Delivery Platform to persist the updated configuration in user settings.

All three events must include the panel ID of the widget instance. OpenText Core Software Delivery Platform passes it as a URL parameter when loading the page (for example, ?panel_id=12345).

Reading configuration on page load

When a configuration page loads, it must retrieve the current configuration from OpenText Core Software Delivery Platform to populate its UI with existing settings.

For most configuration pages, you perform the following steps:

  1. Parse panel_id from the URL.

  2. Send octane_read_widget_configuration to OpenText Core Software Delivery Platform.

  3. Listen for octane_widget_configuration and populate the UI with the received data.

The following example demonstrates the pattern:

Copy code
// Inside your configuration page (data_config.html, for example)

const urlParams = new URLSearchParams(window.location.search);
const panel_id = urlParams.get('panel_id');

window.parent.postMessage({
  event_name: 'octane_read_widget_configuration',
  data: { panel_id }
}, '*');

window.addEventListener('message', (e) => {
  const message = e.data;
  if (!message || message.event_name !== 'octane_widget_configuration') {
    return;
  }

  const config = message.data.config || {};
  // Initialize your UI with config (for example, bind form fields)
});

On first use, config is usually empty. You can decide on defaults in your page.

Persisting configuration changes

When the user modifies a configuration setting, the change must be saved to OpenText Core Software Delivery Platform so it persists across sessions.

Whenever the user changes a setting (selects a filter, adjusts a time range, toggles options), you must:

  • Build a configuration object.

  • Send the object to OpenText Core Software Delivery Platform.

  1. Build a configuration object. Example:

    Copy code
    const config = {
      data: {
        entity_type: 'work_item',
        subtypes: ['defect'],
        filter: {/* advanced filter JSON */}
      },
      display: {
        group_by: 'severity',
        time_bucket: 'week'
      }
    };
  2. Send the object to OpenText Core Software Delivery Platform with octane_update_widget_configuration:

    Copy code
    window.parent.postMessage({
      event_name: 'octane_update_widget_configuration',
      data: {
        panel_id,
        config
      }
    }, '*');

OpenText Core Software Delivery Platform persists this object for the current user and widget instance.

Implementation notes

  • Both configuration pages and the main widget display page are loaded in iFrames.

  • Always include panel_id when communicating with OpenText Core Software Delivery Platform.

  • Configuration is stored per user and survives dashboard refreshes and sessions.

Back to top

Advanced filtering

Most widgets require a method to scope the data – for example, "defects from Program A, Severity = Critical, last 90 days".

Scripted widgets integrate with OpenText Core Software Delivery Platform's Advanced filter UI.

Widgets support multiple advanced filters within the same widget instance. Each filter is identified by a unique filter_id, which is used to differentiate between them.

From a configuration page, you can do the following:

  • Request that OpenText Core Software Delivery Platform open the Advanced filter dialog.

  • Allow the user to define or refine a filter.

  • Have OpenText Core Software Delivery Platform write the resulting filter JSON into a specific property of your configuration object.

Note: Context filters are not supported for scripted widgets.

Requesting the Advanced filter dialog box

You send an event requesting that OpenText Core Software Delivery Platform open the Advanced filter dialog box with specified scope and constraints.

OpenText Core Software Delivery Platform handles the following:

  • Displays the dialog box.

  • Limits the entities and fields to those you specify.

  • Returns the filter into your configuration.

The following example shows a typical request:

Copy code
const message = {
  event_name: 'octane_show_filter_dialog',
  data: {
    panel_id, // from URL params
    filter_id: 'myFilterId', // unique identifier for this filter instance
    entity_type: 'work_item',
    subtypes: ['feature', 'defect'], // optional
    restrict_fields: ['team', 'program'], // optional
    allow_cross_filters: true, // optional
    cross_filter_fields: ['program'], // optional
    bind_to_config_property: 'data.filter' // which property in your config should hold the filter JSON
  }
};

window.parent.postMessage(message, '*');

This request instructs OpenText Core Software Delivery Platform to do the following:

  • Build a filter for features and defects.

  • Allow cross-filters on program.

  • Store the resulting filter JSON under config.data.filter.

Reacting to filter changes

After the user applies a filter in the Advanced filter dialog box, your configuration page should update its UI to reflect the new filter settings.

When the user closes the Advanced filter dialog box, the external widget should listen for the octane_filter_dialog_closed event and retrieve the data.filter object, which contains the filter selected by the user. The widget should then update its configuration accordingly.

Your configuration page listens for the update and refreshes the preview:

Copy code
window.addEventListener('message', (e) => {
  const message = e.data;

  if (!message) {
    return;
  }

  if (message.event_name === 'octane_filter_dialog_closed') {
    const filter = message.data.filter;
    // Update config with the new filter
    // Re-render preview using the updated filter
  }
});

Convert filter to query string

Scripted widgets can convert filter objects to query strings for use in REST API calls. This is accomplished through a dedicated request/response event pair.

Request event: octane_convert_filter_to_query

Sent from the external action to OpenText Core Software Delivery Platform to request conversion of a filter to a query string.

Copy code
{
  event_name: 'octane_convert_filter_to_query',
  workspace: this.params.workspace,
  shared_space: this.params.shared_space,
  data: {
    panel_id: this.params.panel_id,
    filter_id: filterId,
    filter: filter,
    entity_type: 'work_item'
  }
}

Response event: octane_filter_converted_to_query

Sent from OpenText Core Software Delivery Platform to the external action as a response, providing the converted query string.

Copy code
{
  data: {
    panel_id: panelId,
    filter_id: filterId,
    query: query
  }
}

Back to top

Build the main widget display

After implementing the configuration pages, implement the main widget display page referenced by url in the JSON.

The workflow in the display page is similar to that of the configuration pages.

  1. Extract panel_id from the URL.

  2. Request the widget configuration from OpenText Core Software Delivery Platform.

  3. Fetch data from OpenText Core Software Delivery Platform REST APIs based on that configuration.

  4. Render your chart or component.

  5. Optionally, respond to configuration changes while the widget is open.

Reading configuration on widget load

The display page must retrieve its configuration when it loads to determine what data to fetch and how to render it.

On page load, implement the following pattern:

Copy code
const urlParams = new URLSearchParams(window.location.search);
const panel_id = urlParams.get('panel_id');

window.parent.postMessage({
  event_name: 'octane_read_widget_configuration',
  data: { panel_id }
}, '*');

window.addEventListener('message', async (e) => {
  const message = e.data;

  if (!message || message.event_name !== 'octane_widget_configuration') {
    return;
  }

  const config = message.data.config || {};

  try {
    showLoading();

    // Use config to drive your data calls and visualization.
    // For example:
    //   - config.data.entity_type / subtypes
    //   - config.data.filter (advanced filter JSON)
    //   - config.display.time_bucket, group_by, etc.

    const data = await fetchDataFromOctane(config);
    renderChart(data, config);
  } catch (err) {
    showError(err);
  } finally {
    hideLoading();
  }
});

Responding to configuration updates

Support dynamic reconfiguration of the widget without requiring a full page reload, improving the user experience.

If the user reconfigures the widget while it is visible on the dashboard, your display page may receive another octane_widget_configuration or a dedicated configuration-changed event.

You can reuse the same handler to:

  • Re-fetch data if relevant settings changed.

  • Update the chart without a full page reload.

Handling UX edge cases

  • Loading states: Indicate that data is being fetched.

  • Empty states: For example, "No defects match the selected filter".

  • Error states: Display clear messages for network or API errors.

  • Performance: Avoid excessive data fetching; use pagination or aggregation where appropriate.

Back to top

Maintain color consistency

To ensure your visualizations appear native to OpenText Core Software Delivery Platform, the chart series or categories can use the same colors as OpenText Core Software Delivery Platform's UI.

Scripted widgets can request item colors for a given list of entity types and their corresponding entity IDs (for example, { entity_type: 'metaphase', entity_id: 'metaphase.work_item.new' }). These colors are then applied to charts, boards, or any other visualization.

Note: The exact event names and payloads are still being finalized. The examples below use placeholders – check the implementation contract before going to general availability.

Requesting colors for a given list of entity types and their corresponding IDs

In a case where your trend widget displays a stacked bar chart, you can send a request to retrieve colors for specific entities.

Example:

Copy code
const request = {
  event_name: 'octane_get_entity_color',
  data: 
  {
    panel_id,
    entities: [
      { entity_type: 'metaphase', entity_id: 'metaphase.work_item.new' },
      { entity_type: 'metaphase', entity_id: 'metaphase.work_item.intesting' },
      { entity_type: 'list_node', entity_id: 'list_node.test_level.unit' }
    ]
  }
};

window.parent.postMessage(request, '*');

window.addEventListener('message', (e) => {
  const message = e.data;

  if (!message || message.event_name !== 'octane_entity_colors') {
    return;
  }

  const entities = message.data.entities;
  entities.forEach(item => {
    const colorHex = item.color.hex; // #1fad2dff
    // Apply to your chart or visualization
  });
});

Back to top

Drill down

You can make dashboards more useful by enabling users to drill down.

Scripted widgets can open an entity list dialog box with a specific filter and optional column layout using the octane_display_entity_list event. You can trigger this from a click handler, for example, when the user clicks a bar in your trend chart.

Drill-down event structure

This event allows users to navigate from a high-level visualization to a detailed list of entities that comprise a data point.

The drill-down message structure is as follows:

Field Details
entity_type / subtypes Base entity type (for example, work_item) or specific subtypes (for example, defect).
title Optional dialog title.
description Optional description shown as inline text.
query REST query string that filters the entities displayed.
visible_columns Optional list of field names to show as columns.

The following example demonstrates the usage:

Copy code
const message = {
  event_name: 'octane_display_entity_list',
  data: {
    panel_id,
    entity_type: 'defect',
    title: 'Open critical defects',
    description: 'Filtered by Severity = Critical',
    query:
      '?query=' +
      encodeURIComponent(
        'severity EQ {list_node}^list_node.severity.critical; phase EQ {list_node}^list_node.defect_phase.new'
      ),
    visible_columns: ['id', 'name', 'severity', 'owner']
  }
};

window.parent.postMessage(message, '*');

Tip: Use {query} and related URL tokens to build robust queries when selections may exceed 500 items. See External actions.

Back to top

Upload and deploy the widget

After your JSON, configuration pages, and display page are ready, you package them into a bundle and upload them as an external action.

Prepare the widget bundle

  1. Place your JSON configuration file at the root of a folder – for example, defect-trend-widget.json.

  2. Under that folder, organize your code (HTML, CSS, JS), for example:

    • my_external_widget/index.html

    • my_external_widget/index.js

    • my_external_widget/data_config.html

    • my_external_widget/display_config.html

  3. Create a zip archive with the JSON file at the root of the zip.

Upload in External action editor

As a shared space admin:

  1. Open Settings > Management > External action editor.

  2. Click Upload Bundle.

  3. Choose Upload New Action and select your zip file.

  4. Click Save.

Users can add the widget from the widget gallery to their dashboards and configure its settings.

For complete upload details, see External actions.

Back to top

Sample widget bundle

A sample scripted widget bundle is available that illustrates end-to-end implementation of a backlog analysis widget. This sample demonstrates how to design, configure, and render a complete widget with all core features.

Getting started with the sample

  1. Download the sample bundle.

  2. Review action-configuration.json to understand the JSON contract and widget properties.

  3. Examine the configuration page to observe postMessage event patterns for reading and persisting configuration.

  4. Study the widget display page to understand how data is fetched from OpenText Core Software Delivery Platform and rendered.

  5. Customize the bundle for your use case by modifying entity types, filters, and visualization logic.

Bundle structure

Copy code
backlog-widget/
├── action-configuration.json       # Widget metadata and configuration
└── external_action_widget/
    ├── index.html                   # Main widget display
    ├── index.js                     # Widget logic
    ├── index.css                    # Widget styling
    └── configuration_pages/
        └── data_page/
            ├── data_page.html       # Configuration form
            ├── data_page.js         # Configuration event handling
            └── data_page.css        # Configuration styling

The sample demonstrates the following:

  • JSON configuration (action-configuration.json) that defines widget identity, display title, dashboard integration, and configuration page layout

  • Data configuration page (configuration_pages/data_page/data_page.html) with:

    • Entity type selector (User Story or Defect)

    • Advanced filter integration button for scoping data

    • Event-driven communication with OpenText Core Software Delivery Platform (reading and updating configuration)

    • Local storage for persisting user settings

  • Widget display page (index.html and index.js) that:

    • Reads configuration from OpenText Core Software Delivery Platform on load

    • Parses URL parameters and configuration settings

    • Renders a priority-based visualization of backlog items

    • Implements drill-down functionality to open item lists

    • Applies color coding from OpenText Core Software Delivery Platform for visual consistency

    • Handles configuration updates without page reload

Back to top

Best practices and checklist

When building scripted widgets, treat them as first-class dashboard components.

Security

  • Validate all user input in configuration pages.

  • Use HTTPS for external resources.

  • Although widgets run in a sandbox environment, you should still follow standard web security guidelines.

Performance

  • Avoid excessive, repeated calls; prefer server-side aggregation where possible.

  • Use pagination for large datasets.

  • Cache data where it is safe and beneficial.

User experience

  • Provide clear descriptions and names so users understand when to use your widget.

  • Display loading indicators while data is being fetched.

  • Provide helpful empty states and actionable error messages.

Configuration design

  • Maintain configuration simplicity and intuitiveness.

  • Use clear labels, hints, and help text on configuration pages.

  • Structure your configuration object logically (for example, data versus display sections) to facilitate future evolution.

Testing

  • Test with different data volumes, filters, and workspaces.

  • Test network and API failure scenarios.

  • Verify that drill-down, color usage, and advanced filters work together as expected.

Back to top

See also: