Validation And Debugging
Understand SDK validation, execution diagnostics, and common Data App errors.
Data Apps get feedback at two points:
- Validation checks every query spec. The SDK checks it in the browser before sending, and Semaphor checks it again on the server before running it.
- Execution diagnostics explain what happened during governed server-side execution.
Validation catches malformed specs early. Diagnostics tell you how Semaphor resolved data, relationships, filters, and result status.
Start With Query Status
Every query hook returns a status:
const result = useSemaphorQuery(revenueByCampaign);
if (result.status === 'loading' && !result.isStale) return <Loading />;
if (result.status === 'error') return <Error error={result.error} />;
if (result.isPartial) return <PartialResultWarning result={result} />;
if (result.status === 'success') {
return (
<>
{result.isStale ? <RefreshingBadge /> : null}
<Chart rows={result.records} />
</>
);
}Do not render zeros for loading data. A zero should mean the query succeeded and returned zero. During a refetch, isStale can be true while the hook keeps the previous successful payload available for rendering.
Read Validation Errors
When a spec fails validation, result.status is 'error' and result.error is a structured error with the issues that caused it:
if (result.status === 'error') {
return (
<ErrorPanel
message={result.error?.message ?? 'Query failed'}
issues={result.validation?.errors ?? result.error?.validationIssues ?? []}
/>
);
}The same issues appear in DevTools, next to the query that produced them.
Validate Specs Locally
To catch problems before you run the app, for example in a unit test or CI step, use the react-semaphor/data-app-sdk/validation entry point:
import { validateSemaphorDataAppQuerySpecs } from 'react-semaphor/data-app-sdk/validation';
import { revenueByCampaign, revenueKpi } from './queries';
test('Data App queries are valid', () => {
const result = validateSemaphorDataAppQuerySpecs({
queries: [
{ queryHook: 'useSemaphorQuery', spec: revenueKpi },
{ queryHook: 'useSemaphorQuery', spec: revenueByCampaign },
],
});
expect(result.diagnostics).toEqual([]);
expect(result.ok).toBe(true);
});Local checks cover spec shape, field roles, and source refs. Whether a dataset, field, or relationship exists in your project is confirmed when the query runs.
Use DevTools
Turn on the provider's debug prop in development and mount SemaphorDevtools to see every query on the page: its kind, source, fields, input bindings, pagination, execution status, and diagnostics. See Setup for the wiring.
Keep debug off in production so your users never see development details.
Inspect Execution Result
Governed queries may return executionResult.
const execution = result.executionResult;
console.log(execution?.status);
console.log(execution?.rowCount);
console.log(execution?.relationshipDiagnostics);Use this when a query is partial, missing a relationship, or returning unexpected rows.
For analysis results, executionResult is authoritative. Top-level fields are display conveniences. Full field lineage, including validated derived-field definitions, belongs under executionResult.fieldsUsed; compact top-level fieldsUsed does not carry derivedField.
Common Validation Problems
| Problem | What it usually means | Fix |
|---|---|---|
| Unknown source | The source ref does not match a visible semantic dataset. | Use the domainId and datasetName returned by Semaphor discovery; include datasetId when available. |
| Missing field | A field name does not exist on the source. | Check the dataset schema. |
| Wrong field role | A measure was authored as a dimension, or a dimension as a measure. | Use semaphor.field.measure, dimension, date, or id correctly. |
| Missing measure | KPI/analysis query has no measure. | Add measures and primaryMeasure. |
| Unsupported joined projection | A related field is not projection-safe. | Use a dimension/date/id field or model the relationship safely. |
| Invalid option population | related_population is missing a valid base source. | Add population: { kind: 'related_population', baseSource }. |
Relationship Diagnostics
Relationship-aware queries can return relationship diagnostics.
const diagnostics = result.executionResult?.relationshipDiagnostics;relationshipDiagnostics.status uses the shared analytics protocol enum:
not_required
resolved
missing
ambiguous
fanout_risk
unknownSpecific causes, such as missing option population, incomplete composite keys, or temporal relationship requirements, are surfaced through diagnostic messages, warnings, and repair hints. Example: if fact_orders can reach dim_customer through buyer, bill-to, and ship-to relationships, "revenue by customer segment" should return status: 'ambiguous' instead of guessing.
"Failed To Fetch"
Failed to fetch usually means the browser could not complete the request. Check:
- Is
SemaphorDataAppProvidermounted? - Does the provider have a token?
- Does the token include the expected API base URL?
- Is the API server reachable?
- Is a proxy, VPN, or firewall blocking the Semaphor host?
- Did too many queries fire at once during local development?
When the request reaches Semaphor and fails inside governed execution, prefer the structured error and executionResult fields over the browser fetch message.
Row Rendering Gotchas
Do not use column labels as object keys. Use columns[].key.
{result.columns?.map((column) => (
<td key={column.key}>{String(row[column.key] ?? '')}</td>
))}Joined results can contain duplicate field names such as name, id, or status from different datasets. Semaphor returns stable column keys to preserve both values.
Relationship Debug Checklist
If a joined filter or joined projection fails:
- Confirm the base query source.
- Confirm every field has the right
source. - Confirm the related source is in the same semantic domain.
- Confirm the semantic model has an active relationship path.
- Check if there are multiple valid paths and add a relationship id hint only if needed.
- Check fanout diagnostics before showing joined measures.
- Use SQL fallback only when semantic builders cannot express the request.
Before You Ship
- Every visual has loading, empty, and error states.
- Every query uses semantic builders unless SQL is required.
- Tables use bounded limits or server-side pagination.
- Filter options come from option queries, not client-side lists of rows.
- Relationship-aware filters and projections return the diagnostics you expect.
- No project secrets, dashboard secrets, or long-lived tokens appear in client code.
- DevTools and
debugare off in production. - You tested with the same token scope your users will have.