Skip to main content

Accessing IrisX Analytics Unity Catalog

⚠️ Early Access — Serverside API Extensions are currently in early access. Contact your Trackunit representative for more information.

Use IrisX Analytics Unity Catalog when an IrisX App needs customer-uploaded or app-owned tables that Trackunit's APIs do not serve.

Choose GraphQL first​

Use the Trackunit GraphQL API for Trackunit data, including machine insights, engine metrics, fuel, operating hours, and other telematics. Do not assume the Unity Catalog connection contains the invoking customer's assets.

Use Unity Catalog only when GraphQL cannot serve the data, such as:

  • customer-uploaded tables
  • app-owned analytics tables
  • other confirmed tables in the app creator's IrisX analytics workspace

Architecture​

To avoid leaking IrisX Analytics credentials keep access behind a SERVERSIDE_API_EXTENSION:

The connection is app-owned. It always targets the app creator's IrisX analytics workspace using app-level credentials configured by Trackunit. The invoking user's account is an authorization and row-isolation boundary; it does not select the workspace and the user does not need IrisX access.

Before implementation, identify and confirm the catalog, schema, and table in the app creator's workspace. Use fully qualified table names and keep those identifiers in app-controlled configuration.

Configure the app​

Trackunit provisions these app-level secrets:

  • DATABRICKS_HOST
  • DATABRICKS_SQL_WAREHOUSE_ID
  • DATABRICKS_CLIENT_ID
  • DATABRICKS_CLIENT_SECRET

Never commit these values, expose them to the browser, or write them to logs. If they are missing at runtime, contact Trackunit to have them provisioned.

To find the hostname, open IrisX Analytics from your IrisX Library in Trackunit Manager and copy the hostname from the browser URL. Add that exact hostname to cspHeader["connect-src"]:

cspHeader: {
"connect-src": ["dbc-<workspace-id>.cloud.databricks.com"],
},

For a SERVERSIDE_API_EXTENSION, connect-src also defines the Deno --allow-net network sandbox. A call can work locally but fail after publishing when the hostname is missing. Wildcards are dropped from the serverside allow-list, so do not use *.cloud.databricks.com.

Adding the host does not make direct browser access safe. Databricks calls and credentials must remain in the serverside extension.

Authenticate in the serverside extension​

Use @trackunit/serverside-utils to authenticate the request, enforce scopes declared in the app manifest, derive the account ID from verified context, retrieve secrets, and obtain a short-lived Databricks token.

import {
getAccountId,
getDatabricksAccessToken,
getSecret,
getUserToken,
requireScopes,
} from "@trackunit/serverside-utils";

const requireRead = requireScopes("account.view");
app.use("/items", requireRead);
app.use("/items/*", requireRead);

if (!getUserToken({ context })) {
return context.text("Unauthorized", 401);
}

const { accountId } = getAccountId({ context });
const host = await getSecret({
context,
secretKey: "DATABRICKS_HOST",
});
const warehouseId = await getSecret({
context,
secretKey: "DATABRICKS_SQL_WAREHOUSE_ID",
});
const { databricksAccessToken } = await getDatabricksAccessToken({
context,
databricksHost: host,
});

Choose scopes that match each operation. A read scope does not authorize writes; protect every POST, PUT, PATCH, and DELETE route with the relevant declared manage scope or app-role policy.

Enforce tenant isolation​

For every shared-table query or mutation:

  • derive the account ID from verified request context
  • include that account ID in the SQL tenant predicate
  • derive user identity from the verified token when ownership is user-specific
  • never trust browser-supplied account IDs, user IDs, or ownership fields
  • never select credentials, workspace, catalog, or schema from browser input

Parameterize all values:

const statement = `
SELECT id, title, updated_at
FROM \`${CATALOG}\`.\`${SCHEMA}\`.\`items\`
WHERE account_id = :account_id
ORDER BY updated_at DESC
LIMIT :page_size
`.trim();

const parameters = [
{ name: "account_id", value: accountId, type: "STRING" },
{ name: "page_size", value: String(pageSize), type: "INT" },
];

SQL parameters cannot represent identifiers or keywords. Allow-list any identifier or sort option that must vary, and map validated input to known SQL fragments. Never provide an endpoint that accepts arbitrary SQL.

Bound query time and result size, handle pending statements with a polling deadline, and return stable errors rather than Databricks details or SQL text. Treat retries of writes carefully: cancellation does not prove that a write was not committed.

Call the extension from the UI​

The UI calls the generated invoke route on the current origin and passes the IrisX user token:

const response = await fetch(
`${window.location.origin}/invoke/@<scope>/<app>/<serverside-api>/items`,
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
},
);

The first two path segments form the Iris app ID, such as @trackunit/my-app. The serverside extension verifies the caller, applies tenant isolation, and uses the app-owned Databricks connection.

Use serverside function logs to diagnose published functions, but keep tokens, secrets, authorization headers, raw user data, and complete SQL parameters out of log messages.