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.


Table of Contents

  1. Understanding Widget Types
  2. Dashboard Composer and Micro App Library
  3. Widget Presets (Derived Micro Apps)
  4. Building the Main Widget Component

Understanding Widget Types

Reference:

The platform supports three types of widgets, each serving different purposes:

Dashboard Widgets (Type 1)

Dashboard widgets are the most common type of widget. They are displayed on dashboards and make up the main part of business applications. Each dashboard widget is a single-page application (SPA) that can:

  • Display data and visualizations
  • Interact with APIs
  • Share state with other widgets on the dashboard
  • Use the platform's theming system
  • Be configured through widget settings

Examples: KPI Tracker, Chart widgets, Data tables, Analytics widgets

This tutorial focuses on building Dashboard widgets.

Platform Widgets (Type 0)

Platform widgets are structural components outside of the dashboard space that manage core platform functionality. They can:

  • Manage navigation within the platform (side navigation, main menu)
  • Handle authentication flows (login screens)
  • Provide system-level UI components (notifications, error screens)
  • Control access to features (role-based access wrappers)
  • Manage platform initialization (user info, theme settings)

Platform widgets are identified by domain and placement properties and are typically created by platform developers rather than widget developers.

Examples: Side navigation, Login screen, Notification system, Error pages

Learn more: Platform Widgets Documentation

Universal Widgets (Type 2)

Universal widgets can function both as Dashboard widgets and Platform widgets. They are less common and used for special cases where a widget needs to serve both purposes.

Widget Type Configuration

The widget type is configured in widget-config.json:

{
"widgetDetails": {
"name": "KpiTracker",
"id": "kpi-tracker",
"port": 5123,
"widgetType": 1,
...
}
}

widgetType values:

  • 0 - Platform widget
  • 1 - Dashboard widget (most common)
  • 2 - Universal widget

Reference: Widget Store Migrations - Complete widgetType enum documentation


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

  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

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: Configuration → 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/widget.tsx:

import React, { useState, useEffect, useMemo } from "react";
import styled from "styled-components";
import { useQuery } from "react-query";
import { path } from "ramda";
import type { ISharedState } from "@invent/shared-types";
import {
getColor,
getSizeBy,
getFontFamily,
getTypography,
getElevation,
} from "@invent/wl-ui-kit";
import { ProxyApp } from "@invent/proxy-app-components";
import type { IWidgetProps } from "../types/widget-props";

// Shared state selector
function kpiDataSelector(state: ISharedState) {
return path<IKpiData | undefined>(["kpiData"], state);
}

// KPI data interface
interface IKpiData {
revenue: number;
users: number;
conversion: number;
timestamp: number;
}

const KpiTracker: React.FC<IWidgetProps> = ({
platformMeta,
httpClient,
useShareValue,
useSelectSharedValue,
useDeleteSharedValue,
title,
titleTooltip,
apiUrl,
refreshInterval,
showTrend,
enableNotifications,
remoteModule,
showNotification,
useQueryData,
useDashboardNavigationInfo,
getExtension,
installedExtensions,
layout,
dashboardWidgetInstanceId,
}) => {
// State management
const shareValue = useShareValue();
const sharedKpiData = useSelectSharedValue(kpiDataSelector);
const [localKpiData, setLocalKpiData] = useState<IKpiData | null>(null);

// OAuth authentication with ProxyApp
return (
<ProxyApp
httpClient={httpClient}
provider="kpiProvider"
authUrl={apiUrl}
instanceName="kpi-tracker"
>
<KpiTrackerContent
shareValue={shareValue}
sharedKpiData={sharedKpiData}
localKpiData={localKpiData}
setLocalKpiData={setLocalKpiData}
httpClient={httpClient}
apiUrl={apiUrl}
refreshInterval={refreshInterval}
showTrend={showTrend}
enableNotifications={enableNotifications}
title={title}
titleTooltip={titleTooltip}
showNotification={showNotification}
getExtension={getExtension}
dashboardWidgetInstanceId={dashboardWidgetInstanceId}
/>
</ProxyApp>
);
};

// Widget content component (wrapped by ProxyApp)
const KpiTrackerContent: React.FC<any> = ({
shareValue,
sharedKpiData,
localKpiData,
setLocalKpiData,
httpClient,
apiUrl,
refreshInterval,
showTrend,
enableNotifications,
title,
titleTooltip,
showNotification,
getExtension,
dashboardWidgetInstanceId,
}) => {
// Get KPI data
const kpiEndpoint = `${apiUrl}/kpi`;
const {
data: kpiData,
refetch,
isLoading,
error: queryError,
} = useQuery<IKpiData>(
[kpiEndpoint, dashboardWidgetInstanceId],
async () => {
if (apiUrl) {
return await httpClient(kpiEndpoint, {
traceId: `kpi-tracker-${dashboardWidgetInstanceId}`,
});
}
return null;
},
{
refetchInterval: refreshInterval * 1000,
enabled: !!apiUrl,
}
);

// Update shared state when data changes
useEffect(() => {
if (kpiData) {
shareValue(["kpiData"], kpiData);
setLocalKpiData(kpiData);
}
}, [kpiData, shareValue]);

// WebSocket integration for real-time updates
useEffect(() => {
const wsExt = getExtension("@invent/bff-ws-ext");
const subscriptionId = `kpi-${dashboardWidgetInstanceId}`;

if (wsExt && wsExt.subscribe) {
wsExt.subscribe("KpiUpdate", subscriptionId, (message: any) => {
const updatedKpi = message.message;
setLocalKpiData(updatedKpi);
shareValue(["kpiData"], updatedKpi);

if (enableNotifications) {
showNotification("info", "KPI data updated", {
position: "bottom-right",
autoClose: 3000,
});
}
});
}

return () => {
if (wsExt && wsExt.unsubscribe) {
wsExt.unsubscribe("KpiUpdate", subscriptionId);
}
};
}, [getExtension, dashboardWidgetInstanceId, enableNotifications]);

// Handle errors
useEffect(() => {
if (queryError) {
showNotification("error", "Failed to load KPI data", {
position: "top-center",
autoClose: 5000,
});
}
}, [queryError, showNotification]);

// Loading state
if (isLoading) {
return (
<WidgetContainer>
<LoadingText>Loading KPI data...</LoadingText>
</WidgetContainer>
);
}

const displayData = localKpiData || sharedKpiData;

return (
<WidgetContainer>
<WidgetHeader>
<WidgetTitle title={titleTooltip}>{title}</WidgetTitle>
</WidgetHeader>

<KpiGrid>
<KpiCard>
<KpiLabel>Revenue</KpiLabel>
<KpiValue>${displayData?.revenue.toLocaleString()}</KpiValue>
{showTrend && <TrendIndicator trend="up">12%</TrendIndicator>}
</KpiCard>

<KpiCard>
<KpiLabel>Users</KpiLabel>
<KpiValue>{displayData?.users.toLocaleString()}</KpiValue>
{showTrend && <TrendIndicator trend="up">8%</TrendIndicator>}
</KpiCard>

<KpiCard>
<KpiLabel>Conversion</KpiLabel>
<KpiValue>{displayData?.conversion.toFixed(2)}%</KpiValue>
{showTrend && <TrendIndicator trend="down">3%</TrendIndicator>}
</KpiCard>
</KpiGrid>

<LastUpdate>
Last updated:{" "}
{displayData?.timestamp
? new Date(displayData.timestamp).toLocaleTimeString()
: "N/A"}
</LastUpdate>
</WidgetContainer>
);
};

// Styled components
const WidgetContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
padding: ${getSizeBy(3)};
background-color: ${getColor("surface")};
border-radius: ${(props) => props.theme.borderRadius}px;
box-shadow: ${getElevation(1)};
font-family: ${getFontFamily("primary")};
`;

const WidgetHeader = styled.div`
margin-bottom: ${getSizeBy(2)};
`;

const WidgetTitle = styled.h2`
${getTypography("Heading2")};
color: ${getColor("onSurface")};
margin: 0;
`;

const KpiGrid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: ${getSizeBy(2)};
flex: 1;
`;

const KpiCard = styled.div`
display: flex;
flex-direction: column;
padding: ${getSizeBy(2)};
background-color: ${getColor("tertiary")};
border-radius: ${(props) => props.theme.borderRadius}px;
transition: transform 0.2s;

&:hover {
transform: translateY(-2px);
box-shadow: ${getElevation(2)};
}
`;

const KpiLabel = styled.div`
${getTypography("Caption")};
color: ${getColor("secondary")};
margin-bottom: ${getSizeBy(1)};
`;

const KpiValue = styled.div`
${getTypography("Heading1")};
color: ${getColor("primary")};
margin-bottom: ${getSizeBy(1)};
`;

const TrendIndicator = styled.div<{ trend: "up" | "down" }>`
${getTypography("Caption")};
color: ${(props) =>
props.trend === "up" ? getColor("success") : getColor("danger")};
`;

const LastUpdate = styled.div`
${getTypography("Caption")};
color: ${getColor("onSurface", "mediumEmphasis")};
margin-top: ${getSizeBy(2)};
text-align: right;
`;

const LoadingText = styled.div`
${getTypography("Body1")};
color: ${getColor("onSurface")};
text-align: center;
padding: ${getSizeBy(4)};
`;

export default KpiTracker;

Next Steps

Now that you understand how widgets integrate with the Dashboard Composer and have built your main component, you can:

  1. Configuration - Configure widget metadata, theming, and settings
  2. Core Features - Add platform features like API integration and notifications
  3. Advanced Features - Implement OAuth, state sharing, and WebSockets

Continue to: Configuration