ba9aa41bba08d0c3ee7a8dda919bc3943e5ed4b9
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ba9aa41bba |
refactor: metadata store cleanup, SSE unification, mock metadata loading & login redirect fix (#18651)
## Summary - **SSE unification**: Replaced 11 individual SSE effect components with a single generic `MetadataStoreSSEEffect` - **Metadata store cleanup**: Merged `metadataCollectionHashesState` into `metadataStoreState` (currentCollectionHash / draftCollectionHash per entity), moved `objectMetadataItemsSelector` to `object-metadata` domain, converted `navigationMenuItemsState` to a derived selector - **Naming clarity**: Renamed `isAppMetadataReadyState` → `isMinimalMetadataReadyState`, `MetadataGater` → `MinimalMetadataGater`, `useIsLogged` → `useHasAccessTokenPair`, `patchMetadataStoreFromSSEEvent` now takes named object params - **Mock metadata loading**: Added `generate-navigation-menu-items.ts` script, rewrote `useLoadMockedMinimalMetadata` to load full objects/fields/indexes/views/navItems from generated mock data, enabling proper sign-in background rendering (table columns, view picker, navigation) - **Login/logout transitions**: `MinimalMetadataLoadEffect` manages mocked↔real metadata transitions based on auth state, `MainContextStoreProvider` computes context on auth pages for view picker support - **Login redirect fix**: `handleLoadWorkspaceAfterAuthentication` now re-enables `isAppEffectRedirectEnabled` after `loadCurrentUser()` completes, fixing the blocked post-login navigation - **Dead code removal**: Deleted `useRefreshPageLayouts`, `useApplyPageLayouts`, `useStaleMetadataEntities`, `metadataCollectionHashesState`, and all individual SSE effects ## Test plan - [x] Login from welcome page redirects to companies page - [x] Logout transitions cleanly to mocked metadata on welcome page - [x] Sign-in background shows table columns, view picker, and navigation items - [x] SSE events still update metadata store entries correctly - [x] Navigation menu items persist across page refreshes - [ ] CI: lint, typecheck, tests pass |
||
|
|
06efee1eef |
feat: hash-based metadata staleness detection (#18649)
## Summary Replace the single `metadataVersion` integer with per-entity-type **collection hashes** for granular metadata staleness detection. The backend already generates a UUID per flat entity map on each cache recompute (`crypto.randomUUID()` in `WorkspaceCacheService`); we now expose these via the minimal metadata endpoint and SSE events so the frontend can compare and know exactly which entity types are stale. ### Key changes **Backend:** - `WorkspaceCacheService.getCacheHashes()` — new public method that reads only `:hash` keys from Redis without fetching full data - `MinimalMetadataDTO` — added `collectionHashes: Record<string, string>` (JSON scalar mapping `AllMetadataName` → collection hash), removed `metadataVersion` - `MetadataEventDTO` — added optional `updatedCollectionHash` field to SSE events - `MetadataEventsToDbListener` — reads the collection hash for the affected entity type after cache invalidation and attaches it to the SSE event before publishing - `MinimalMetadataService` — no longer queries the workspace table; uses `getCacheHashes()` for all flat entity maps and maps cache keys to `AllMetadataName` locally **Frontend:** - `metadataCollectionHashesState` — new Jotai atom with `atomWithStorage` + `getOnInit: true` storing `Partial<Record<MetadataEntityKey, string>>` - `mapAllMetadataNameToEntityKey()` — explicit mapping from backend `AllMetadataName` to frontend `MetadataEntityKey` (23 entries) - `useLoadMinimalMetadata` — stores `collectionHashes` from server, computes `staleEntityKeys` by comparing local vs server hashes - `patchMetadataStoreFromSSEEvent()` — accepts optional `updatedCollectionHash` and updates `metadataCollectionHashesState` - All 11 SSE effect components — pass `eventDetail.updatedCollectionHash` through to the patch function - `useStaleMetadataEntities` — new hook returning entity keys missing from collection hashes (not yet loaded/synced) - `resetMetadataStore()` — also clears collection hashes - Deleted `metadataVersionState` (superseded by collection hashes) ### Design decisions - **No change to hash generation** — existing `crypto.randomUUID()` is sufficient. Hashes are persisted in Redis, survive server restarts, and change only on `invalidateAndRecompute`. - **"Collection hash" naming** — used consistently to clarify the hash represents an entire entity collection (e.g., all views), not a single record. - **Mapping localized** — backend `WorkspaceCacheKeyName` → `AllMetadataName` mapping lives in the minimal metadata service. Frontend `AllMetadataName` → `MetadataEntityKey` mapping lives in a local utility. Nothing in `twenty-shared`. - **Backward compatible** — `collectionHashes` is additive; `updatedCollectionHash` is nullable. |
||
|
|
7a3540788a |
feat: uniformize metadata store with flat types, SSE alignment, presentation endpoint & localStorage (#18647)
## Summary
Uniformizes the metadata store to support **all** backend flat metadata
types, introduces a **minimal metadata endpoint** for fast initial
renders, replaces custom localStorage persistence with **Jotai's
built-in `atomWithStorage`**, and wires up a
**MinimalMetadataLoadEffect** for stale-while-revalidate loading.
### Key changes
- **All flat metadata types**: Added `FlatCommandMenuItem`,
`FlatFrontComponent`, `FlatWebhook`, `FlatRole`, `FlatRoleTarget`,
`FlatAgent`, `FlatSkill`, `FlatRowLevelPermissionPredicate`,
`FlatRowLevelPermissionPredicateGroup` — every entity in the backend
`MetadataEntityTypeMap` now has a corresponding frontend flat type
registered in `ALL_METADATA_ENTITY_KEYS` and `MetadataEntityTypeMap`.
- **Minimal metadata endpoint** (`minimalMetadata` GraphQL query): New
backend module (`MinimalMetadataModule`) returns lightweight object
metadata (names, icons, labels, flags) and basic views (id, type, key,
objectMetadataId) plus a `metadataVersion`. This enables fast first
paint before full metadata loads.
- **Jotai `atomWithStorage` for persistence**: Replaced the custom
`MetadataLocalStorageEffect` with Jotai's built-in `atomWithStorage` on
both `metadataStoreState` (family) and `metadataVersionState`. Added
`localStorageOptions` support to `createAtomFamilyState` for `{
getOnInit: true }` synchronous hydration. Each entity atom auto-persists
under keys like `metadataStoreState__objectMetadataItems`.
- **MinimalMetadataLoadEffect**: New effect mounted before
`MetadataProviderInitialEffects` that checks if the store already has
data (from Jotai localStorage hydration). If empty, it fetches minimal
metadata from the new endpoint. The full metadata load continues in
parallel, eventually enriching the store with complete data.
- **SSE effects alignment**: All metadata entity types now have
corresponding SSE effects that directly patch the metadata store via
`patchMetadataStoreFromSSEEvent`.
- **Existing selectors and joining logic**:
`objectMetadataItemsWithFieldsSelector`, `viewsWithRelationsSelector`,
`pageLayoutsWithRelationsSelector` reconstruct nested data from flat
entities for components that need it.
### Loading flow
```
App mount
→ Jotai atomWithStorage hydrates store from localStorage (sync, getOnInit)
→ MinimalMetadataLoadEffect
→ Store has data? → skip (app renders immediately)
→ Store empty? → fetch minimalMetadata endpoint → populate objects + views
→ MetadataProviderInitialEffects (full metadata load, runs in parallel)
→ LazyMetadataLoadEffect (page layouts, logic functions, nav menu, etc.)
→ IsAppMetadataReadyEffect (sets isAppMetadataReady)
```
## Test plan
- [ ] Verify app loads with empty localStorage (should fetch minimal
metadata, then full)
- [ ] Verify app loads with populated localStorage (should skip minimal
fetch, render immediately)
- [ ] Verify SSE events correctly update metadata store for all entity
types
- [ ] Verify logout clears metadata store (atom reset propagates to
localStorage)
- [ ] Verify all metadata selectors return correct joined data
- [ ] CI: lint, typecheck, tests pass
|