# Remote Tools
URL: https://pro.rv-grid.com/guides/pivot/server-side/remote-tools/
Source: src/content/docs/guides/pivot/server-side/remote-tools.mdx
Description: Add diagnostics, source-row drilldown, and saved views to a server-side Pivot screen.
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. In the demo, the remote tools panel is outside the dedicated RevoGrid wrapper so Pivot-owned UI such as the field panel can mount directly beside the grid.

## What This Page Covers

- 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 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.

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?".

## Import The Helpers

All helpers below are exported from the Enterprise Pivot package.

```ts
import {
  PivotDrilldownController,
  createPivotDiagnosticsModel,
  deletePivotSavedView,
  duplicatePivotSavedView,
  loadPivotView,
  renamePivotSavedView,
  savePivotView,
  type PivotConfig,
  type PivotLoadRequest,
  type PivotRemoteStoreHooks,
  type PivotSavedViewRecord,
} from '@revolist/revogrid-enterprise';
```

## Diagnostics Model

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.

```ts
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.

## Drilldown Controller And Table State

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

```ts
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-View Helpers

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, listen for `pivot-config-update` from the field panel and save that latest config. Loading a view should apply the returned config back to the direct Pivot binding (`grid.pivot`, `pivot`, or framework equivalent) so the report changes immediately.

```ts
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',
});

grid.pivot = {
  ...loaded.config,
  engine: {
    mode: 'server',
    remoteStore,
    viewId: 'sales',
    fieldsVersion: 'sha256:fields-v1',
  },
};

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 JSON-serializable Pivot config. It validates common Pivot fields before sending state to the store, which helps catch accidental functions, class instances, or invalid axis values before they become a saved view.

## Remote Store Activity

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

```ts
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.

## Backend Expectations

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.

## Recommended Reading

- [Server-Side Pivot](/guides/pivot/server-side/) - How remote Pivot loading works.
- [Remote API Reference](/guides/pivot/remote-api-reference/) - Request, response, and drilldown contracts.
- [Caching And Cache Keys](/guides/pivot/caching/) - Cache metadata used by diagnostics.
