Skip to content

Remote Tools

Remote tools are small UI and application-layer helpers for server-side Pivot. They help you show request diagnostics, load source rows for a summarized cell, track remote activity, and manage saved Pivot layouts that can be restored into the grid.

These helpers are not a required backend. They sit around a PivotRemoteStore or PivotEngineAdapter that your app already uses for remote Pivot loading. The focused examples below expose each contract separately. The full demo places its application-owned tools outside the grid wrapper.

Open the full demo and framework source.

Use aggregationSemantics: 'server' to pin plugin/controller execution to the trusted backend. A pin prevents accidental engine switching; it does not guarantee identical local/server results. Choose null, count, distinct, totals and precision policies in the backend contract and validate them against independent facts. For local analysis, use the V2 numeric/scalar aggregation configuration.

  • Diagnostics model: converts remote response metadata into rows and chips your UI can render.
  • Drilldown controller: turns a summarized Pivot cell into table-ready source rows.
  • Field panel: lets users reorder and move row, column, value, and filter fields before saving a layout. The full demo keeps unused fields in the Filters area so they can be moved into the active layout.
  • Saved-view helpers: serialize, load, rename, duplicate, and delete user-owned Pivot configs that can be written back to grid.pivot.
  • Remote store hooks: expose request activity such as load, drilldown, saveState, and loadState for support panels or audit trails.

Mental Model

The grid loads aggregated data through the remote store. Your surrounding app UI can reuse the same store to ask follow-up questions: “How fast was that request?”, “Which source rows produced this number?”, “Which saved layout should this user load?”, or “What remote operation just ran?”.

All helpers below are exported from @revolist/pivot.

import {
PivotDrilldownController,
createPivotDiagnosticsModel,
deletePivotSavedView,
duplicatePivotSavedView,
loadPivotView,
renamePivotSavedView,
savePivotView,
type PivotConfig,
type PivotLoadRequest,
type PivotRemoteStoreHooks,
type PivotSavedViewRecord,
} from '@revolist/pivot';
Interactive example

Remote tools

Display cache/version/metadata/status diagnostics

The example loads when visible.

Remote Pivot responses can include meta data such as elapsed time, cache status, generated timestamp, and warnings. createPivotDiagnosticsModel converts that raw response into a simple display model.

Use it when you want a side panel, footer, debug drawer, or support view that explains what the last remote request did.

const response = await remoteStore.load({
requestId: `pivot-diagnostics-${Date.now()}`,
viewId: 'sales',
fieldsVersion: 'sha256:fields-v1',
loadOptions: {
rows: [{ selector: 'region' }],
columns: [{ selector: 'quarter' }],
totalSummary: [{ selector: 'sales', summaryType: 'sum' }],
groupSummary: [{ selector: 'sales', summaryType: 'sum' }],
},
viewport: {
rowAxis: { offset: 0, limit: 50 },
columnAxis: { offset: 0, limit: 12 },
},
uiState: {
collapsedByDefault: true,
rowHeaderLayout: 'tree',
},
} satisfies PivotLoadRequest);
const diagnostics = createPivotDiagnosticsModel(response, {
formatGeneratedAt: (value) => new Date(value).toLocaleString(),
});
for (const row of diagnostics.rows) {
console.log(row.label, row.value);
}
for (const chip of diagnostics.chips) {
console.log(chip.label, chip.tone);
}

The returned model has:

  • rows: stable rows such as elapsed time, generated time, visible rows, and visible columns.
  • chips: compact status items such as cache status or warnings.
  • warnings: warning strings from the response metadata.
  • hasWarnings: a boolean for showing warning states in your UI.

The helper does not make network requests. It only formats a PivotLoadResponse that your remote store already returned. In the live demo, the diagnostics panel refreshes against the currently selected layout so the visible rows and columns reflect that saved view.

Aggregated Pivot cells often need a “show source rows” action. PivotDrilldownController wraps the remote drilldown contract and keeps table state for you.

The controller owns:

  • current status: idle, loading, success, or error
  • table columns inferred from returned rows or customColumns
  • source rows for the selected summary cell
  • total matching row count
  • request cancellation when a newer drilldown request starts
const drilldown = new PivotDrilldownController(remoteStore, {
defaultLimit: 100,
});
const unsubscribe = drilldown.subscribe((state) => {
renderDrilldownTable({
loading: state.loading,
columns: state.columns,
rows: state.rows,
totalCount: state.totalCount,
error: state.error,
});
});
await drilldown.load({
viewId: 'sales',
fieldsVersion: 'sha256:fields-v1',
cell: {
rowPath: ['North', 'Jane'],
columnPath: [2024, 'Q1'],
},
customColumns: ['region', 'rep', 'year', 'quarter', 'sales'],
offset: 0,
limit: 25,
});
const state = drilldown.getState();
console.log(state.status, state.rows.length, state.totalCount);
unsubscribe();
drilldown.abort();

The cell value should identify the analytical cell, not the visible DOM cell. Use Pivot paths like rowPath and columnPath so your backend can apply the same grouping context and return matching fact rows. The controller state is table-ready: render state.columns, state.rows, state.totalCount, and state.status directly in your surrounding UI.

Saved views are user-owned Pivot configurations. They are useful when users build several layouts, such as “Revenue by Region” and “Quarterly Margin”.

The helpers use store.saveState() and store.loadState() for persistence. Rename, duplicate, and delete operate on your app’s list of saved-view records, so you can combine them with your own sync logic, menus, permissions, or undo UI. In an interactive screen, subscribe to pivotPlugin.getAuthoringController() and save only newly committed, ready revisions. Read the accepted config from pivotPlugin.getRuntimeSnapshot()?.config; keep engine handles and callbacks in application-owned bindings. pivot-config-update is a cancelable proposal that can later fail or be superseded, so it is unsuitable as an autosave trigger. See Plugin Lifecycle for the committed subscription pattern.

// pivotPlugin is the installed PivotPlugin instance obtained through grid.getPlugins().
const USER_ID = 'user-123';
let views: PivotSavedViewRecord[] = [];
const config: Partial<PivotConfig> = {
dimensions: [
{ prop: 'region' },
{ prop: 'rep' },
{ prop: 'quarter' },
{ prop: 'sales' },
],
rows: ['region', 'rep'],
columns: ['quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
totals: { grandTotal: true, subtotals: true },
};
const saved = await savePivotView(remoteStore, {
userId: USER_ID,
viewId: 'north-sales',
name: 'North Sales',
config,
});
views = [...views.filter((view) => view.viewId !== saved.viewId), saved];
const loaded = await loadPivotView(remoteStore, {
userId: USER_ID,
viewId: 'north-sales',
});
const accepted = await pivotPlugin.updatePivotConfig({
...loaded.config,
engine: {
mode: 'server',
remoteStore,
viewId: 'sales',
fieldsVersion: 'sha256:fields-v1',
},
});
if (!accepted) showMessage('Saved view was not committed');
views = renamePivotSavedView(views, {
userId: USER_ID,
viewId: 'north-sales',
name: 'North Sales by Quarter',
});
views = duplicatePivotSavedView(views, {
userId: USER_ID,
viewId: 'north-sales',
newViewId: 'north-sales-copy',
name: 'North Sales Copy',
});
views = deletePivotSavedView(views, {
userId: USER_ID,
viewId: 'north-sales',
});

savePivotView stores a detached, JSON-serializable Pivot config using the same validation as JSON state export. Functions, unsupported class instances, non-finite numbers, circular references, and sparse or undefined array entries are rejected before the store is called. Custom aggregator callbacks and runtime engine handles must stay in application-owned runtime configuration; passing them directly is an error rather than silently dropping their behavior.

Loading accepts the current saved-view envelope, the supported JSON state envelope, or an unenveloped native config. Unknown envelope kinds and unsupported schema versions are rejected. The remote response’s version is backend record metadata, separate from an envelope’s schema version.

HttpPivotRemoteStore accepts lifecycle hooks for telemetry. They are useful for request logs, support drawers, cache badges, and lightweight audit trails near the grid.

const hooks: PivotRemoteStoreHooks = {
requestStarted: ({ type }) => {
activity.unshift(`${type}: started`);
},
requestSucceeded: ({ type, cacheStatus }) => {
activity.unshift(`${type}: succeeded${cacheStatus ? ` (${cacheStatus})` : ''}`);
},
requestFailed: ({ type }) => {
activity.unshift(`${type}: failed`);
},
cacheStatusChanged: ({ cacheStatus }) => {
activity.unshift(`cache: ${cacheStatus}`);
},
};

The live example uses those hooks with the same mocked HttpPivotRemoteStore that powers the grid. Running diagnostics, loading source rows, saving a view, and loading a view all update the activity log.

These helpers assume the remote store supports the operation you call:

  • Diagnostics needs load() responses with useful meta, rowAxis, and columnAxis data.
  • Drilldown needs an adapter or store with a drilldown() method.
  • Saved views need saveState() and loadState().
  • Activity logs need store hooks or equivalent instrumentation around your request layer.

You can implement those methods against any backend: REST, GraphQL, local storage for a demo, or an internal state service. RevoGrid does not require a specific saved-view backend.