PSA/ee/docs/extension-system/datatable-integration-guide.md
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

178 lines
6.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DataTable Integration Guide (Iframe UI + UI Kit)
This guide shows how to use the Alga UI Kit DataTable inside an extensions iframe app, fetching data via the gateway (`/api/ext/[extensionId]/[[...path]]`) and following best practices for performance, security, and UX. In the v2 architecture:
- All server calls go through the API Gateway and are executed by the Runner (`POST /v1/execute`).
- UI assets are served by the Runner at `${RUNNER_PUBLIC_BASE}/ext-ui/{extensionId}/{content_hash}/[...]`.
- The host constructs the iframe URL via [buildExtUiSrc()](../../../server/src/lib/extensions/ui/iframeBridge.ts:38) and initializes with [bootstrapIframe()](../../../server/src/lib/extensions/ui/iframeBridge.ts:45).
- Reference gateway scaffold: [server/src/app/api/ext/[extensionId]/[[...path]]/route.ts](../../../server/src/app/api/ext/%5BextensionId%5D/%5B%5B...path%5D%5D/route.ts)
## Overview
- UI renders in a sandboxed iframe
- Use `@alga/ui-kit` components and `@alga/extension-iframe-sdk` for host integration
- All server calls go through the gateway and are executed by the Runner
## Prerequisites
- An iframe app (React recommended) scaffolded with Vite/Next
- Installed SDKs:
- `@alga/extension-iframe-sdk`
- `@alga/ui-kit`
## Basic Table
```tsx
import React from 'react';
import { DataTable } from '@alga/ui-kit';
import { useEffect, useMemo, useState } from 'react';
import { useExtension } from '@alga/extension-iframe-sdk';
export default function AgreementsTable() {
const { context } = useExtension(); // provides tenant/extension context, auth bridge
const [rows, setRows] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(25);
const [total, setTotal] = useState(0);
const columns = useMemo(() => [
{ key: 'name', header: 'Agreement Name', sortable: true },
{ key: 'vendor', header: 'Vendor', sortable: true },
{ key: 'status', header: 'Status', sortable: true },
{ key: 'amount', header: 'Amount' }
], []);
useEffect(() => {
const abort = new AbortController();
async function load() {
setLoading(true);
try {
const qp = new URLSearchParams({ page: String(page), limit: String(pageSize) });
const url = `${context.gatewayBase}/api/ext/${context.extensionId}/agreements?${qp}`;
const res = await fetch(url, { signal: abort.signal, headers: context.authHeaders });
if (!res.ok) throw new Error('Request failed');
const data = await res.json();
const list = Array.isArray(data) ? data : data.data ?? [];
setRows(list);
setTotal((data.meta && data.meta.total) || list.length);
} finally {
setLoading(false);
}
}
load();
return () => abort.abort();
}, [context.extensionId, context.gatewayBase, context.authHeaders, page, pageSize]);
return (
<DataTable
columns={columns}
rows={rows}
loading={loading}
pagination={{
page,
pageSize,
total,
onPageChange: setPage,
onPageSizeChange: setPageSize,
pageSizeOptions: [10, 25, 50, 100]
}}
onSortChange={(sort) => {
// Optionally refetch with sort params
}}
/>
);
}
```
Notes:
- `context.gatewayBase` and `context.authHeaders` are provided by the SDK bridge
- The gateway applies header allowlists and enforces timeouts; the Runner executes the handler
## Custom Cells and Actions
```tsx
import { Button, Badge } from '@alga/ui-kit';
const columns = [
{
key: 'name',
header: 'Agreement Name',
sortable: true,
cell: (row: any) => (
<a className="text-blue-600 hover:underline" href={`#/agreements/${row.id}`}>{row.name}</a>
)
},
{
key: 'status',
header: 'Status',
cell: (row: any) => (
<Badge variant={row.status === 'active' ? 'success' : row.status === 'pending' ? 'warning' : 'secondary'}>
{row.status}
</Badge>
)
},
{
key: 'amount',
header: 'Amount',
cell: (row: any) => (
<span className="font-medium">{row.currency} {Number(row.amount).toLocaleString()}</span>
)
},
{
key: 'actions',
header: '',
cell: (row: any) => (
<Button variant="ghost" size="sm" onClick={() => console.log('Actions for', row.id)}>Actions</Button>
)
}
];
```
## Data Fetching Patterns
- Always go through the gateway: `/api/ext/${extensionId}/...`
- Expect either an array or `{ success, data, meta }`
- Propagate only SDKprovided auth headers; do not attach enduser tokens directly
## Performance Tips
- Serverside: perform data transformation in handlers executed by the Runner
- Clientside: memoize column definitions; avoid heavy computations in cell renderers
- Use pagination and serverside filtering/sorting when lists are large
## Error and Loading States
- Display loading indicators while fetching
- Show concise error messages; avoid leaking internal details
- Consider retry UI for transient errors (e.g., 502 from Runner)
## Security Considerations
- Do not attempt crossorigin requests; route everything through `/api/ext/...`
- Avoid evaluating code or templates at runtime
- Keep sizes small; large responses may be rejected by gateway caps
## Example Handler (Runner)
Manifest v2 declares an endpoint, e.g.: `GET /agreements``dist/handlers/http/list_agreements`
Handler (conceptual):
```ts
export async function list_agreements(ctx) {
// Use host APIs via ctx: storage, http.fetch, secrets, log, metrics
const items = await ctx.storage.list({ namespace: 'agreements' });
return {
status: 200,
headers: { 'content-type': 'application/json' },
body: { data: items }
};
}
```
## Related References
- Gateway route scaffold: [server/src/app/api/ext/[extensionId]/[[...path]]/route.ts](../../../server/src/app/api/ext/%5BextensionId%5D/%5B%5B...path%5D%5D/route.ts)
- Iframe bootstrap and src builder: [server/src/lib/extensions/ui/iframeBridge.ts](../../../server/src/lib/extensions/ui/iframeBridge.ts:38)
- Runner overview: [runner.md](runner.md)
- Manifest and signing: [manifest_schema.md](manifest_schema.md), [security_signing.md](security_signing.md)