Skip to main content

Dashboard Integration

This section covers how widgets integrate with the platform's Dashboard Composer and how to build your main widget component.

Understanding Widget Types

Widgets come in three types — Platform (0), Dashboard (1), and Universal (2). For what each type is and how widgetType is set, see Widget Overview → Widget Types. This page covers how a Dashboard widget is placed on and integrates with a dashboard.


Dashboard Composer and Micro App Library

The Dashboard Composer (within the Host application) is the interface where new widgets (called Micro Apps within the platform) are added to dashboards, widget settings are configured, and UI positions are set.

Adding Widgets to Dashboard

To edit a dashboard, open Board Settings (bottom-left of the dashboard) and click Edit — this puts the portal into Composer (edit) mode, where you can add and arrange widgets.

Opening Composer mode from the Board Settings menu on a portal

  1. Open the Dashboard Composer in the Host application
  2. Click the "Add micro app" button to open the Micro App Library
  3. Browse or search for widgets in the Micro App Library
  4. Select a widget to add it to your dashboard
  5. Configure widget settings through the settings panel
  6. Position and resize the widget using drag-and-drop
  7. Click Save when you're done to apply your changes

Dashboard Composer Interface

Dashboard Composer Dashboard Composer interface showing widget placement and configuration

Micro App Library

Micro App Library Micro App Library showing available widgets filtered by context rules

The Micro App Library displays widgets that are available based on the current dashboard's URL parameters and each widget's context rules (configured in widget-config.json). Widgets that don't meet the context requirements will not appear in the library.

Learn more about context rules in: Build Setup → Configuring Widget Metadata


Widget Presets (Derived Micro Apps)

What are Presets?

Widgets (also called Micro Apps within the platform) can have preconfigured settings called Presets or Derived Micro Apps. A preset is a widget settings configuration that is preconfigured in the ICP (Invent Control Panel) for a specific portal page.

Creating a Preset

To create a preset (derived micro app):

  1. Open the ICP (Invent Control Panel)
  2. Navigate to the desired portal page
  3. Go to the Micro Apps page
  4. In the micro apps list, click the three-dotted icon (⋮) at the end of the row
  5. Select "Create derived micro app"

Create Derived Micro App Creating a derived micro app in the ICP interface

  1. Configure the preset settings
  2. Save the preset

Using Presets

Once created, the preset will be listed in the Micro App Library within the Dashboard Composer. Users can add preconfigured widgets to their dashboards without manually setting up each configuration option.

Presets are especially useful for:

  • Standard configurations used across multiple dashboards
  • Complex settings that should be consistent
  • Quick deployment of commonly used widget configurations
  • Reducing configuration errors

For programmatic access to presets, see: Advanced Features → Preset Extension


Building the Main Widget Component

Widget Props Interface

Reference: Widget Specifications API

Create a TypeScript interface for your widget props:

src/types/widget-props.ts:

import type {
HttpClientT,
IPlatformMeta,
UseShareValueT,
UseSelectSharedValueT,
UseDeleteSharedValueT,
RemoteModuleT,
ShowNotificationT,
UseQueryDataT,
UseDashboardNavigationInfoT,
} from "@invent/shared-types";

export interface IWidgetProps {
// Platform meta information
platformMeta: IPlatformMeta;

// HTTP client for API calls
httpClient: HttpClientT;

// State sharing hooks
useShareValue: UseShareValueT;
useSelectSharedValue: UseSelectSharedValueT;
useDeleteSharedValue: UseDeleteSharedValueT;

// Widget settings (individual props, not grouped)
title: string;
titleTooltip?: string;
apiUrl: string;
refreshInterval: number;
showTrend: boolean;
enableNotifications: boolean;

// Remote module rendering
remoteModule: RemoteModuleT;

// Notifications
showNotification: ShowNotificationT;

// React Query integration
useQueryData: UseQueryDataT;

// Dashboard navigation
useDashboardNavigationInfo: UseDashboardNavigationInfoT;

// Extensions
getExtension: (name: string) => any;
installedExtensions: any[];

// Layout props
layout?: {
w: number;
h: number;
x: number;
y: number;
};

// Widget instance ID
dashboardWidgetInstanceId: string;
}

IPlatformMeta Interface

Reference: @invent/shared-types

interface IPlatformMeta {
storeWidgetsById: Record<string, IWidgetMetadata<Record<string, unknown>>>;
platformWidgetsById: Record<string, IPlatformWidget>;
currentDashboardId?: string;
currentDashboard: IDashboard | undefined;
}

Main Widget Component Example

src/{widget-name}/{widget-name}.tsx:

import React, { useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import { path } from 'ramda';
import { WidgetRootWrapper, WidgetHeader, WidgetLoading } from './components';
import { Card, CardContent } from '@invent/wl-ui-kit-next/ui';
import type { ICloudProps, ISharedState } from '@invent/shared-types';

interface IKpiData {
revenue: number;
users: number;
conversion: number;
timestamp: number;
}

// Read this widget's data out of shared dashboard state
const kpiDataSelector = (state: ISharedState) => path<IKpiData | undefined>(['kpiData'], state);

export interface IKpiTrackerProps extends ICloudProps<unknown> {
apiUrl: string;
title?: string;
titleTooltip?: string;
refreshInterval?: number;
showTrend?: boolean;
enableNotifications?: boolean;
}

const KpiTrackerInner = ({
httpClient,
apiUrl,
title,
titleTooltip,
refreshInterval = 30,
showTrend,
enableNotifications,
useShareValue,
useSelectSharedValue,
showNotification,
getExtension,
dashboardWidgetInstanceId
}: IKpiTrackerProps) => {
const shareValue = useShareValue();
const sharedKpiData = useSelectSharedValue(kpiDataSelector);

const { data, isLoading, error } = useQuery({
queryKey: [`${apiUrl}/kpi`, dashboardWidgetInstanceId],
queryFn: () =>
httpClient(`${apiUrl}/kpi`, { traceId: `kpi-tracker-${dashboardWidgetInstanceId}` }),
enabled: Boolean(apiUrl),
refetchInterval: refreshInterval * 1000
});

// Publish to shared state so other widgets can read it
useEffect(() => {
if (data) shareValue(['kpiData'], data);
}, [data, shareValue]);

// Real-time updates over the platform websocket extension
useEffect(() => {
const wsExt = getExtension('@invent/bff-ws-ext');
const id = `kpi-${dashboardWidgetInstanceId}`;
wsExt?.subscribe?.('KpiUpdate', id, (msg: { message: IKpiData }) => {
shareValue(['kpiData'], msg.message);
if (enableNotifications) showNotification('info', 'KPI data updated');
});
return () => wsExt?.unsubscribe?.('KpiUpdate', id);
}, [getExtension, dashboardWidgetInstanceId, enableNotifications]);

useEffect(() => {
if (error) showNotification('error', 'Failed to load KPI data');
}, [error, showNotification]);

if (isLoading) return <WidgetLoading />;

const kpi = (data as IKpiData) ?? sharedKpiData;
const metrics = [
{ label: 'Revenue', value: `$${kpi?.revenue.toLocaleString() ?? '—'}`, trend: '↑ 12%', up: true },
{ label: 'Users', value: kpi?.users.toLocaleString() ?? '—', trend: '↑ 8%', up: true },
{ label: 'Conversion', value: `${kpi?.conversion.toFixed(2) ?? '—'}%`, trend: '↓ 3%', up: false }
];

return (
<div className="flex h-full w-full flex-col bg-background text-foreground">
<WidgetHeader title={title} titleTooltip={titleTooltip} />
<div className="grid flex-1 gap-3 p-4 [grid-template-columns:repeat(auto-fit,minmax(150px,1fr))]">
{metrics.map((m) => (
<Card key={m.label}>
<CardContent className="flex flex-col gap-1 p-4">
<span className="text-xs text-muted-foreground">{m.label}</span>
<span className="text-2xl font-bold text-primary">{m.value}</span>
{showTrend && (
<span className={m.up ? 'text-sm text-success' : 'text-sm text-destructive'}>
{m.trend}
</span>
)}
</CardContent>
</Card>
))}
</div>
<p className="px-4 pb-2 text-right text-xs text-muted-foreground">
Last updated: {kpi?.timestamp ? new Date(kpi.timestamp).toLocaleTimeString() : 'N/A'}
</p>
</div>
);
};

const KpiTracker = (props: IKpiTrackerProps) => (
<WidgetRootWrapper>
<KpiTrackerInner {...props} />
</WidgetRootWrapper>
);

export default KpiTracker;

Platform features in this example

FeatureHowLearn more
Data fetchinghttpClient + React QueryCore Features
Shared stateuseShareValue / useSelectSharedValueAdvanced Features
Real-time updatesgetExtension (WebSocket)Advanced Features
NotificationsshowNotificationCore Features
OAuth authenticationproxy-app-setup AI skillAI Assisted Dev → Adding OAuth