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

6.1 KiB
Raw Permalink Blame History

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:

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

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

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 /agreementsdist/handlers/http/list_agreements

Handler (conceptual):

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 }
  };
}