Files
twenty/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsWithFieldsSelector.ts
T
Charles Bochet 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
2026-03-16 00:38:11 +01:00

96 lines
3.6 KiB
TypeScript

import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
import { fieldMetadataItemsSelector } from '@/metadata-store/states/fieldMetadataItemsSelector';
import { indexMetadataItemsSelector } from '@/metadata-store/states/indexMetadataItemsSelector';
import { flatObjectMetadataItemsSelector } from '@/object-metadata/states/flatObjectMetadataItemsSelector';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getNonReadableFieldMetadataIdsFromObjectPermissions } from '@/object-metadata/utils/getNonReadableFieldMetadataIdsFromObjectPermissions';
import { getNonUpdatableFieldMetadataIdsFromObjectPermissions } from '@/object-metadata/utils/getNonUpdatableFieldMetadataIdsFromObjectPermissions';
import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId';
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
import { type ObjectPermissions } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
export const objectMetadataItemsWithFieldsSelector = createAtomSelector<
ObjectMetadataItem[]
>({
key: 'objectMetadataItemsWithFieldsSelector',
get: ({ get }) => {
const flatObjects = get(flatObjectMetadataItemsSelector);
const allFlatFields = get(fieldMetadataItemsSelector);
const allFlatIndexes = get(indexMetadataItemsSelector);
const currentUserWorkspace = get(currentUserWorkspaceState);
const fieldsByObjectId = new Map<
string,
(typeof allFlatFields)[number][]
>();
for (const field of allFlatFields) {
const existing = fieldsByObjectId.get(field.objectMetadataId);
if (isDefined(existing)) {
existing.push(field);
} else {
fieldsByObjectId.set(field.objectMetadataId, [field]);
}
}
const indexesByObjectId = new Map<
string,
(typeof allFlatIndexes)[number][]
>();
for (const index of allFlatIndexes) {
const existing = indexesByObjectId.get(index.objectMetadataId);
if (isDefined(existing)) {
existing.push(index);
} else {
indexesByObjectId.set(index.objectMetadataId, [index]);
}
}
const objectPermissionsByObjectMetadataId =
currentUserWorkspace?.objectsPermissions.reduce(
(accumulator, objectPermission) => {
accumulator[objectPermission.objectMetadataId] = objectPermission;
return accumulator;
},
{} as Record<string, ObjectPermissions & { objectMetadataId: string }>,
) ?? {};
return flatObjects.map((flatObject) => {
const fields = fieldsByObjectId.get(flatObject.id) ?? [];
const indexMetadatas = indexesByObjectId.get(flatObject.id) ?? [];
const objectPermissions = getObjectPermissionsFromMapByObjectMetadataId({
objectPermissionsByObjectMetadataId,
objectMetadataId: flatObject.id,
});
const nonReadableFieldMetadataIds =
getNonReadableFieldMetadataIdsFromObjectPermissions({
objectPermissions,
});
const nonUpdatableFieldMetadataIds =
getNonUpdatableFieldMetadataIdsFromObjectPermissions({
objectPermissions,
});
return {
...flatObject,
fields,
indexMetadatas,
readableFields: fields.filter(
(field) => !nonReadableFieldMetadataIds.includes(field.id),
),
updatableFields: fields.filter(
(field) => !nonUpdatableFieldMetadataIds.includes(field.id),
),
} satisfies ObjectMetadataItem;
});
},
});