Skip to content

Managing the Graph

Once a schema is active in PuppyGraph, you can inspect and modify it through the Web UI, or move it as JSON to back it up, version-control it, or migrate it between instances. This page covers both flows, plus the Local Table Management surface.

Working with the active schema

After a schema is uploaded or built, the Schema page becomes a working surface: an interactive visualization on the right, and a catalog tree with summary counts on the left. From here you can inspect any node or edge, change it in place, remove it, or add new ones, all without re-uploading the schema.

The Schema page

The Schema page has two panels:

  • The left panel shows the catalog tree, plus summary counts of your current nodes and edges.
  • The right panel shows an interactive visualization of your graph schema.

Click any node or edge in the visualization to open its detail panel.

Inspecting a node or edge

The detail panel shows everything currently configured for the element:

  • The label and any Cypher type aliases.
  • The active data source (external, local, or union).
  • ID columns, attribute columns, and for edges fromKey and toKey.
  • Field mappings between source columns and graph fields.
  • SCD2 configuration, if any.

Editing in place

Inside the detail panel, the same fields are editable:

Field Notes
Type aliases Add, remove, or rename.
Data source Switch between external, local, or union if multiple are configured.
ID columns Add or remove ID columns. Rename or retype them.
Attributes Add or remove attribute columns. Rename or retype.
Field mappings Remap which source columns correspond to which graph fields.
SCD2 Enable, disable, or change the SCD2 configuration.
fromKey and toKey Edges only.

The panel shows an Unsaved Changes badge when you've made edits. Click Save to apply changes (they take effect immediately) or Discard to revert.

Removing a node or edge

To remove a node or edge, click on it in the visualization. The menu has two options:

  • View Details opens the detail panel.
  • Delete removes the element from the graph. You'll be asked to confirm before deletion.

Deletion takes effect immediately.

Adding a node or edge

Adding new elements uses the same flow as the initial Schema Builder. Click a table in the catalog tree, choose Add as Node or Add as Edge, and walk through the wizard. See Building a Graph for the per-step walkthrough.

Reset graph

To start over, click Reset Graph. This clears all nodes, edges, and local tables. You'll be asked to confirm before anything is deleted.

Schema serialization

A PuppyGraph schema is a single JSON document. The same JSON form is used to export the schema (for backup, version control, or migration to another instance) and to import one (to bootstrap a deployment or apply changes), through both the Web UI and a REST API.

Top-level shape

A schema JSON has up to five top-level keys:

{
  "catalog":          [ ],
  "node":             [ ],
  "edge":             [ ],
  "localTable":       [ ],
  "rowLevelSecurity": { }
}

See Building a Graph for nodes and edges, and Data Sources and Local Tables for localTable[]. The optional rowLevelSecurity block is documented separately.

Downloading a schema

From the Schema page, two download options are available:

  • Download Graph Schema includes the graph structure only (node[], edge[], localTable[], and rowLevelSecurity). Use this when sharing the model without exposing connection credentials.
  • Download Graph Schema with Catalog includes the same plus full catalog[] connection details. Use this when migrating between instances or reproducing a setup.

Uploading via the Web UI

The upload dialog auto-detects the file format and adjusts.

Uploading a v1 schema

When you select a v1 schema file, the dialog shows a preview of how many catalogs, local tables, nodes, and edges will be created. If the schema includes local tables, you can choose what happens after upload:

Choice Effect
Cache and switch Start loading data into local tables and read from the cache.
Cache data only Start loading data, but keep reading from external sources.
Do not cache data Define the structure now and load later from the Local Table Management page.

Uploading a legacy (v0) schema

If you upload a schema from PuppyGraph 0.x, the dialog detects it automatically and offers conversion options.

For the data source for nodes and edges, choose one:

  • Use external catalog: query data directly from external sources, with no local tables created.
  • Use local tables: create local tables and optionally cache data.

If you chose local tables, decide whether to load data immediately:

  • Cache data: start loading immediately.
  • Do not cache data: define the structure only.

A progress dialog shows each conversion step with color-coded status. After upload, use Download Graph Schema to inspect the resulting v1 schema.

For details of the conversion, see Migrating from v0. If you want to convert a v0 schema to v1 without applying it (for batch conversion, pre-validation, or scripting), use the Converting via the REST API endpoint instead.

Uploading via the REST API

PuppyGraph exposes a REST endpoint for uploading the schema.

Requests use HTTP Basic Authentication. The username is your PuppyGraph username (default puppygraph); the password is your PuppyGraph password. If your deployment is exposed under a base path such as /graph, prepend that base path to the endpoints below.

Field Value
Method POST
Endpoint /schema
Headers Content-Type: application/json
Body The schema JSON
curl -XPOST -H "content-type: application/json" \
  --data-binary @./schema.json \
  --user "puppygraph:puppygraph123" \
  "http://localhost:8081/schema?postUploadBehavior=none"

The endpoint accepts v1 JSON. To upload a legacy v0 schema, first convert it with /ui-api/convertSchema, then upload the converted v1 JSON.

Use the raw schema document as the request body. Do not wrap uploads in a schemaJson object. If the schema contains embedded catalog[] entries, the caller must also have catalog write permission. The Web UI uses /ui-api/uploadSchema internally, which shares the same handler as /schema; external automation should use /schema.

Use postUploadBehavior to control local-table loading after the schema is accepted:

Value Effect
none Define the graph and local tables without loading data. This is the default.
load Start loading data into local tables after upload. Graph elements keep their current external read path.
switch Start loading data into local tables and switch graph elements to local tables when loading completes.

The legacy loadData=true query parameter is still accepted for compatibility and maps to postUploadBehavior=switch. Prefer postUploadBehavior for new scripts.

A 400 Bad Request from /schema means the schema was rejected during parsing, validation, or local-table default validation. For legacy schema migrations, call /ui-api/convertSchema first to inspect the converted v1 schema without applying it.

Local Table Management

The Local Table Management page in the main navigation is the operational surface for local tables. Each row is one local table; the row exposes its current load and analyze status, and the row's action menu lets you trigger every operation listed below. Most of these operations are also reachable through REST endpoints under /ui-api/ for automation (loading data, inspecting status, reviewing load history, reviewing partitions, and dropping a partition); the remaining ones, including analyze and the standalone-table actions, are currently Web UI only. All REST endpoints use the same HTTP Basic authentication as /schema.

The operation-specific sections below each cover what the operation does, the Web UI affordance, and the REST endpoint with request and response shapes. The conventions that apply to every endpoint (envelope shape, status casing, int64 serialization, HTTP status codes) are factored out in Response envelope at the end of the section, so you only have to read them once.

Loading data

Loads data into a local table from its external or union source. The load is asynchronous: submission returns immediately with a tracking key, and the actual load runs in the background.

In the Web UI, the row's Import from Source action submits a load. The action is only available when the local table has an external or union source. Standalone local tables use Insert Rows instead. While a load is in progress, the row's Data loading status chip moves through the status vocabulary covered in Inspecting load status below.

The REST endpoint is POST /ui-api/loadLocalTable (RBAC DATA:write). The response returns a scopeKey that groups the submission and any retries. To track the load, poll getLocalTableLoadStatus by localTableName (the only query parameter it accepts) and match the returned tasks[] entries against your scopeKey to find the records for this submission.

Request body fields:

Field Required Default Description
localTableName yes Local table to load. Must have an external or union source.
partitionSpec no Exact partition values as a JSON object, e.g. { "region": "us-east" }.
partitionStartValue, partitionEndValue no Time range for a time-partitioned local table. Provide both together. Start is inclusive, end is exclusive.
timeoutSeconds no 43200 Per-task timeout.
maxRetries no 3 Retry attempts after failure.
forceReload no false Submit a new load even when a matching one may already be active.
maxPartitions no 1440 Cap on the number of partition-load tasks a time range can split into.

Time-range example:

curl -X POST -u "puppygraph:puppygraph123" \
  -H "Content-Type: application/json" \
  -d '{
        "localTableName": "event_local",
        "partitionStartValue": "2025-01-01 00:00:00",
        "partitionEndValue":   "2025-01-02 00:00:00"
      }' \
  http://localhost:8081/ui-api/loadLocalTable

Full-table load (send only localTableName):

curl -X POST -u "puppygraph:puppygraph123" \
  -H "Content-Type: application/json" \
  -d '{"localTableName":"event_local"}' \
  http://localhost:8081/ui-api/loadLocalTable

Success response:

{
  "ok": true,
  "errorMessage": "",
  "scopeKey": "load_event_local_1779917406567_abc"
}

Common errors: 400 with "localTableName is required" when the field is missing from the request; 400 with "local table 'X' not found in schema" when no such local table exists in the active schema; 400 with "LocalTable 'X' does not have an external or union data source" when the named table is standalone and cannot be loaded from the API.

Data loading targets local tables, not node or edge labels. If an older refreshLocalCache payload used viewIds, look up the corresponding localTableName values in the schema (or on the Local Table Management page) and submit one call per local table.

Inspecting load status

Reports the aggregate load state of every local table. This is the same view that drives the chips on the Local Table Management page.

In the Web UI, every row shows its current Data loading status chip. The value summarises across all of the table's partitions and load tasks:

Status Meaning
SUCCESS Data loading completed for every known partition.
LOADING or RUNNING A load is in progress.
PENDING Queued, not yet started.
PARTIAL Some partitions succeeded; others are pending or failed.
FAILED Hover the chip for the error details.
NOT_LOADED No data has been loaded yet.

The REST endpoint is GET /ui-api/localTableStatus (RBAC DATA:read). It takes no query parameters and returns one row per local table in the active schema.

curl -u "puppygraph:puppygraph123" http://localhost:8081/ui-api/localTableStatus

Sample response:

{
  "event_local": {
    "status": "SUCCESS",
    "message": "",
    "detail": ""
  },
  "user_local": {
    "status": "NOT_LOADED",
    "message": "",
    "detail": ""
  }
}

This endpoint never returns an error envelope on the read path. It returns 200 with an empty object if no local tables are defined.

Reviewing load history

Returns the per-task history for one local table, ordered most-recent first. Useful for confirming the outcome of a specific submission (paired with the scopeKey from loadLocalTable) or for backfilling a UI history list.

In the Web UI, the row's View Import History action opens the history table for that local table.

The REST endpoint is GET /ui-api/getLocalTableLoadStatus?localTableName=<name> (RBAC DATA:read).

curl -u "puppygraph:puppygraph123" \
  "http://localhost:8081/ui-api/getLocalTableLoadStatus?localTableName=event_local"

Sample response:

{
  "ok": true,
  "errorMessage": "",
  "tasks": [
    {
      "id": "1",
      "localTableName": "event_local",
      "taskName": "load_event_local_1779917406567_abc_p0",
      "scopeKey": "load_event_local_1779917406567_abc",
      "status": "SUCCESS",
      "submittedAt": "2026-05-27T21:30:06Z",
      "startedAt":   "2026-05-27T21:30:06Z",
      "completedAt": "2026-05-27T21:30:07Z",
      "errorMessage": "",
      "progress": 100,
      "retryCount": 0,
      "maxRetries": 3,
      "partitionSpecJson": "",
      "partitionStartValue": "2025-01-01 00:00:00",
      "partitionEndValue":   "2025-01-02 00:00:00"
    }
  ]
}

Field notes:

  • id is a proto int64 value serialized as a JSON string.
  • progress is a number (0100).
  • status is uppercase, one of PENDING, RUNNING, SUCCESS, FAILED, RETRYING, CANCELLED.

If no records exist for the given localTableName, the endpoint returns 200 with {"ok": true, "tasks": []}.

Common errors: 400 with "localTableName is required" when the query parameter is missing.

Reviewing partition state

Returns the per-partition state for one local table: physical partition name, range, current state, most-recent load status, and last successful load timestamp.

In the Web UI, the row's View Partition Details action opens the partition table for that local table.

The REST endpoint is GET /ui-api/getLocalTablePartitionDetails?localTableName=<name> (RBAC DATA:read).

curl -u "puppygraph:puppygraph123" \
  "http://localhost:8081/ui-api/getLocalTablePartitionDetails?localTableName=event_local"

Sample response (partitioned table):

{
  "ok": true,
  "errorMessage": "",
  "partitions": [
    {
      "partitionName": "p20250101",
      "partitionRange": "2025-01-01 00:00:00 .. 2025-01-02 00:00:00",
      "partitionState": "NORMAL",
      "mostRecentLoadStatus": "SUCCESS",
      "mostRecentProgress": 100,
      "mostRecentErrorMessage": "",
      "lastSuccessTimestamp": "2026-05-27T21:30:07Z",
      "mostRecentTaskId": "2"
    }
  ]
}

mostRecentLoadStatus uses the same uppercase vocabulary as getLocalTableLoadStatus; an empty string means the partition has never been loaded. The partitionRange value is the literal string accepted as input to Dropping a partition below, so preserve it verbatim if you plan to chain the two calls.

For a non-partitioned local table the response carries exactly one row, with partitionName equal to the local-table name and partitionRange: "". Calling dropLocalTablePartition against that row is rejected because the table itself is not partitioned.

Dropping a partition

Removes a single physical partition from a partitioned local table. There is currently no Web UI affordance for this operation; it is REST-only.

The endpoint is POST /ui-api/dropLocalTablePartition (RBAC DATA:write). The partitionName and partitionRange values must match exactly what getLocalTablePartitionDetails returned, so the reliable workflow is to call the partition-details endpoint first, copy both values out of the row you want, and pass them in unmodified.

Request body fields:

Field Required Description
localTableName yes Local table that owns the partition. Must be partitioned.
partitionName yes The exact partition name from getLocalTablePartitionDetails.
partitionRange yes The exact range string from getLocalTablePartitionDetails, of the form "<start> .. <end>".
curl -X POST -u "puppygraph:puppygraph123" \
  -H "Content-Type: application/json" \
  -d '{
        "localTableName": "event_local",
        "partitionName":  "p20250101",
        "partitionRange": "2025-01-01 00:00:00 .. 2025-01-02 00:00:00"
      }' \
  http://localhost:8081/ui-api/dropLocalTablePartition

Success response:

{ "ok": true, "errorMessage": "" }

Common errors:

  • 400 with "localTableName, partitionName, and partitionRange are required" when one of the required fields is missing.
  • 400 with "local table 'X' is not partitioned" when the local table has no partitionBy configuration.
  • 400 with "local table 'X' does not exist" when the named table is not in the active schema.
  • 400 if the partition values don't match a live partition, or if an active load currently targets the partition or the whole table.

Collecting statistics (Analyze)

Triggers a one-time asynchronous statistics-collection run against the local table. The collected stats feed the query planner; you do not need to run analyze for queries to work, but doing so improves cardinality estimates on large local tables.

Analyze is currently Web UI-only. The row's Analyze Table action submits the run, and the Analyzing status chip surfaces the current state: NOT_ANALYZED, PENDING, RUNNING, SUCCESS, or FAILED. There is no REST endpoint for triggering or inspecting analyze runs.

Other Web UI actions

The remaining row actions are inspect-only or apply to standalone tables, and do not have REST equivalents:

Action Description
View Table Details Schema, columns, configuration.
View Source Information The external source and field mappings.
Insert Rows Manually insert rows. Available for standalone local tables without an external source.

Response envelope

Four of the five /ui-api/ endpoints in this section (loadLocalTable, getLocalTableLoadStatus, getLocalTablePartitionDetails, and dropLocalTablePartition) share the same envelope and field conventions, so a client written against one will parse the others with minimal change. GET /ui-api/localTableStatus is the one exception and is covered separately below.

  • Successful responses use "ok": true and include the endpoint-specific payload (scopeKey, tasks, partitions).
  • Non-200 responses use the same envelope with "ok": false and the failure reason in errorMessage. Client errors return 400; transport failures return 500.
  • Field names use camelCase (e.g. localTableName, scopeKey, mostRecentLoadStatus).
  • status and mostRecentLoadStatus are uppercase: one of PENDING, RUNNING, SUCCESS, FAILED, RETRYING, CANCELLED.
  • int64 fields (e.g. id on load tasks) serialize as JSON strings to avoid precision loss on JavaScript clients.
  • Empty repeated fields are emitted as [] rather than omitted, so client parsers can rely on key presence.

GET /ui-api/localTableStatus is the only endpoint in this section that does not use the {ok, errorMessage, …} envelope. It returns the per-table status map directly. Treat that endpoint as a special case in client code.

There is no global local-cache status shaped like {"status":"READY","items":[]}. Status is reported per local table through localTableStatus, and per load task or partition through getLocalTableLoadStatus and getLocalTablePartitionDetails.

For the v0-to-v1 mapping of the legacy /ui-api/*LocalCache* endpoints, see the From local cache to local table management section in Migrating from v0.