Skip to content

Migrate storage mappings table to GraphQL - #2031

Open
GregorShear wants to merge 11 commits into
mainfrom
worktree-claude+storage-mappings-gql
Open

Migrate storage mappings table to GraphQL#2031
GregorShear wants to merge 11 commits into
mainfrom
worktree-claude+storage-mappings-gql

Conversation

@GregorShear

@GregorShear GregorShear commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Migrate storage mappings table on the settings page to use the gql list query, and add pagination

@GregorShear
GregorShear requested a review from a team as a code owner July 17, 2026 20:36
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

✅ Code Health

Removes $\textcolor{green}{1}$ dead-code issue, introduces none.

48 Unused files

File imported nowhere — delete (or import) it.

     src/hooks/useDelay.ts
     src/hooks/useDraft.ts
     src/pages/NoGrants.tsx
     src/pages/OAuth.tsx
     src/services/encryption.ts
     src/types/global.ts
     src/types/vitest.ts
     src/components/graphs/TaskHoursByMonthGraph.tsx
     src/components/tables/Link.tsx
     src/hooks/bindings/useBindingDisabled.ts
…and 38 more

66 Unused exports

Exported symbol with no references outside its own file — un-export it, or delete it if unused entirely

     src/context/Theme.tsx : logoColors
     src/context/Theme.tsx : intensifiedOutlineThick
     src/context/Theme.tsx : tableAlternateRowsSx
     src/context/Theme.tsx : draggableChipIconSx
     src/context/Theme.tsx : hiddenButAccessibleInput
     src/context/Theme.tsx : primaryColoredBackground_hovered
     src/context/Theme.tsx : detailsPanelBgColor
     src/context/Theme.tsx : menuBackgroundColor
     src/context/Theme.tsx : flexGrowToSiblingsSx
     src/context/Theme.tsx : shardTableRow
…and 56 more

29 Unused exported types $\textcolor{green}{-1}$

Exported type with no references outside its own file — un-export it, or delete it if unused entirely

$\textcolor{green}{-}$ src/api/gql/useAllPages.ts : Connection
     src/utils/billing-utils.ts : FREE_GB_BY_TIER
     src/types/index.ts : InferredSchemas
     src/types/index.ts : Shard
     src/components/shared/WizardDialog/index.ts : WizardStep
     src/api/dataPlanes.ts : AwsDnsEntry
     src/stores/ShardDetail/types.ts : TaskShardDetailsWithShard
     src/stores/ShardDetail/types.ts : ShardDetails
     src/components/tables/Logs/types.ts : RefreshLogsFunction
     src/types/schemaModels.ts : CollectionSchema
…and 20 more

14 Unused exported enum members

An enum member referenced nowhere

     src/services/supabase.ts : CONNECTOR_TAGS
     src/services/supabase.ts : DRAFTS_EXT
     src/services/supabase.ts : TASKS_BY_DAY
     src/stores/Tables/hooks.ts : accessGrants
     src/stores/Tables/hooks.ts : accessLinks
     src/stores/Tables/hooks.ts : billing
     src/stores/Tables/hooks.ts : connectors
     src/stores/Tables/hooks.ts : entitySelector
     src/stores/Tables/hooks.ts : prefixes
     src/stores/Tables/hooks.ts : prefixAlerts
…and 4 more

5 Unused dependencies

In package.json but never imported

     package.json : @mui/lab
     package.json : @testing-library/jest-dom
     package.json : @urql/exchange-retry
     package.json : logrocket-react
     package.json : stripe

3 Unused devDependencies

In package.json devDependencies but never used

     package.json : @types/logrocket-react
     package.json : @types/react-inspector
     package.json : sharp

@GregorShear GregorShear self-assigned this Jul 20, 2026
Comment thread src/components/tables/StorageMappings.tsx Outdated
Comment thread src/components/tables/StorageMappings/index.tsx Outdated
Replace the PostgREST-backed storage mappings settings table with a
cursor-paginated GraphQL query (usePaginatedStorageMappings).

- Drop the getStorageMappings PostgREST helper and the EntityTable /
  TableHydrator / selectable-table Zustand plumbing it required.
- Render a plain MUI Table with TablePagination driven by
  useCursorPagination; forward pagination only, no search or column
  sorting (the storageMappings query supports neither).
- Retype rows to StorageMappingTableRow (catalogPrefix / spec) and remove
  the "Last Updated" column, which the query does not expose.
- Invalidate the storageMappings query in the URQL cache on
  createStorageMapping / updateStorageMapping, replacing the manual
  useStorageMappingsRefresh mechanism.
- Add the storageMappingsTable.error.loadFailed message.
@GregorShear
GregorShear force-pushed the worktree-claude+storage-mappings-gql branch from 2ebe14d to bbfa3fb Compare August 18, 2026 02:52
The settings table paginates by cursor, and it took `underPrefix` from
`selectedTenant`. The page-level `TenantSelector` can change that tenant while
the table stays mounted, because `Settings` renders the two as siblings and
passes no `key`. A tenant switch therefore sent the old tenant's `after` cursor
against the new prefix. That skips pages, or returns zero edges. A zero-edge
response also hides the pagination footer, so the user loses the Previous
button.

Remove the `by: { underPrefix }` argument and the `pause` gate. The table now
lists every storage mapping the caller can read. The server applies the
caller's catalog-read scope on its own, so an absent filter cannot widen
visibility.

`useStorageMappings`, which feeds the create and update dialogs, keeps its
`underPrefix` scoping.
The pagination footer renders only when the current page has rows, so a
zero-row page removes the Previous control and leaves the user with no way to
reach populated rows. A page can come back empty when the mapping list shrinks
between fetches, or when a cursor points past the end of the list.

Watch for a completed fetch that returns no rows above page 0, then call
`goToPage(currentPage - 1)`. The `currentPage > 0` guard ends the walk back at
the first page. This matches the recovery in `AccessLinksTable` and
`RefreshTokenTable`, which share `useCursorPagination`.
`cursorHistory[i]` holds the cursor that starts page `i + 1`, and a forward
step appended to the list. Forward navigation after a backwards step therefore
revisited a page that already had an entry, and appended a second one. Every
later entry then sat one position too high, so a backwards step read the cursor
of an earlier page while the label showed the page the user asked for.

The sequence `1 -> 2 -> 3 -> 2 -> 3 -> 4 -> 5 -> 4` reaches it: the final
page 4 loaded the rows of page 3.

Write `nextCursor` at index `page - 1` and drop every entry past it, so the
history describes the path to the page now on screen. Four tables share this
hook: storage mappings, access links, refresh tokens, and alert history.

Add tests for the first page, a forward step, a return to the first page, a
forward step carrying no cursor, and the backtracking sequence above.
Tenant scoping is moving into the auth token, so each query no longer carries
its own prefix filter. The server applies the caller's catalog-read scope on
its own, and the schema states that a filter can only narrow that scope, so an
absent filter cannot widen what a caller sees.

Remove `by: { underPrefix }` from `StorageMappingQuery`, along with the
`selectedTenant` reads in `useStorageMappings` and `useStorageMappingService`.
This also removes the last use of the deprecated `by` argument in this file.

The change repairs the edit dialog. The settings table lists every mapping the
caller can read, and a row click asks `UpdateMappingWizard` to resolve that
catalog prefix through this query. A tenant-scoped query held no row from
another prefix, so the dialog took its `notFound` path and closed itself.

`refetchMappings` now runs for every create and update. It previously returned
early whenever no tenant was selected.

Interim note: `PrefixCard` merges these prefixes with `useLiveSpecs`, which is
still scoped to the selected tenant, so its autocomplete draws the two sides
from different scopes until the token change lands.
@GregorShear
GregorShear force-pushed the worktree-claude+storage-mappings-gql branch from ccdfb89 to b6c271a Compare August 19, 2026 14:55
@GregorShear
GregorShear requested a review from SeanWhelan August 19, 2026 14:56
…ash, stale e2e assertion

- useStorageMappings now walks every page via useAllPages instead of taking the server's silent 50-row default page, so the edit dialog can resolve any clicked row and PrefixCard's duplicate/coverage validators see the complete list. A dedupe by catalogPrefix guards against a graphcache invalidation re-appending the refetched page.
- Gate the edit dialog's notFound self-close on the storage mappings loading flag (deep-link race) and on query errors (notFound previously closed the dialog before the error state could render).
- Drop refetchMappings from create/update: graphcache already invalidates every storageMappings query on those mutations, and a first-page-only network refetch is misleading now that the query paginates.
- Guard row.spec.data_planes in the settings table and type it optional: the server omits the field from the spec JSON when the list is empty (serde skip_serializing_if), which crashed the whole Settings section on such a mapping.
- Remove the Playwright assertion on the removed "Last Updated" column. The GraphQL StorageMapping type carries no timestamp field, so the column cannot come back client-side.
- gql-types regenerated for the query change only; the DataPlane.id/IdFilter schema drift stays in #2064.
SeanWhelan
SeanWhelan previously approved these changes Aug 26, 2026

@SeanWhelan SeanWhelan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. 2 cosmetic things, not blocking:

  • storageMappingsTable.filterLabel and .message2 are orphaned in
    AdminPage.ts now that the old table is gone
  • dataPlane.replace('ops/dp/', '') duplicates DATA_PLANE_PREFIX from
    src/settings/dataPlanes.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants