mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 13:37:22 +00:00
feat: migrate objectMetadata reads to granular metadata store (#18643)
## Summary
Consolidates `objectMetadataItems` onto the metadata store as the
**single source of truth**, replacing the previous dual-store approach
(separate `objectMetadataItemsState` atom + untyped
`metadataStoreState`).
### Architecture: three-layer design
```
┌─────────────────────────────────────────────────────────┐
│ Store Layer (granular, typed) │
│ objectMetadataItems → FlatObjectMetadataItem[] │
│ fieldMetadataItems → FlatFieldMetadataItem[] │
│ indexMetadataItems → FlatIndexMetadataItem[] │
└────────────────┬────────────────────────────────────────┘
│ .current (never draft)
┌────────────────▼────────────────────────────────────────┐
│ Selectors (typed read-only) │
│ objectMetadataItemsSelector │
│ fieldMetadataItemsSelector │
│ indexMetadataItemsSelector │
│ metadataStoreStatusFamilySelector │
│ isSystemObjectByNameSingularFamilySelector (narrow) │
│ activeObjectNameSingularsSelector (narrow) │
└────────────────┬────────────────────────────────────────┘
│ joins objects + fields + indexes + permissions
┌────────────────▼────────────────────────────────────────┐
│ Joining Selector │
│ objectMetadataItemsWithFieldsSelector │
│ → produces full ObjectMetadataItem[] with │
│ readableFields / updatableFields from permissions │
│ → 12 existing selectors repointed here │
└─────────────────────────────────────────────────────────┘
```
### Key changes
- **Granular flat types** (`FlatObjectMetadataItem`,
`FlatFieldMetadataItem`, `FlatIndexMetadataItem`) — objects stored
without embedded fields/indexes, matching backend "Flat" naming
convention
- **Typed write API** — `updateDraft` is now generic via
`MetadataEntityTypeMap`, giving compile-time safety on what data shape
goes to each key
- **Write path refactored** — fetch → split into flat entities via
`splitObjectMetadataItemWithRelated` → write to metadata store directly.
No more dual-write through `objectMetadataItemsState`. Permissions
enrichment moved from write path into the joining selector.
- **SSE effects write directly** — `ObjectMetadataItemSSEEffect` and
`FieldMetadataSSEEffect` now patch the store from the SSE event payload
(create/update/delete) instead of triggering a full re-fetch
- **`objectMetadataItemsState` bridge** — converted from writable
`createAtomState` to read-only `createAtomSelector` that delegates to
the joining selector. All 100+ existing consumers continue to work
without code changes.
- **All selectors use Twenty state API** — `createAtomSelector` /
`createAtomFamilySelector` throughout, no raw `atom()`
- **Narrow selectors** for hot paths —
`isSystemObjectByNameSingularFamilySelector` and
`activeObjectNameSingularsSelector` read from flat objects only,
avoiding re-renders when fields/indexes/permissions change. Placed in
`object-metadata/states/` as higher-level business selectors.
- **Test helper** — `setTestObjectMetadataItemsInMetadataStore` for
tests that need to set up composite object metadata through the store
(clearly named as a testing utility)
### Naming conventions
- `ObjectMetadataItemWithRelated` — type for objects with embedded
fields/indexes (input to split utility)
- `FlatObjectMetadataItem` / `FlatFieldMetadataItem` /
`FlatIndexMetadataItem` — granular store types
- Selector names don't expose "Current" — that's an internal detail of
the metadata store API
### Future work
- Optimistic update API (`updateCurrentOptimistically` with rollback)
- Migrate remaining entities (views, pageLayouts, etc.) to the same
pattern
- Gradually remove `objectMetadataItemsState` bridge once all direct
imports are replaced
## Test plan
- [x] `npx nx typecheck twenty-front` passes
- [x] `npx nx lint:diff-with-main twenty-front` passes
- [ ] Verify app loads correctly with metadata from the store
- [ ] Verify SSE updates (object/field changes) propagate correctly
- [ ] Run existing test suites to confirm no regressions
This commit is contained in:
parent
48172d60fd
commit
40ff109179
64 changed files with 657 additions and 315 deletions
|
|
@ -6,8 +6,8 @@ import { Provider as JotaiProvider } from 'jotai';
|
|||
|
||||
import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext';
|
||||
|
|
@ -140,8 +140,8 @@ describe('useActivityTargetObjectRecords', () => {
|
|||
}),
|
||||
);
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsSelector } from '@/metadata-store/states/objectMetadataItemsSelector';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
|
||||
export const objectPermissionsFamilySelector = createAtomFamilySelector<
|
||||
|
|
@ -14,7 +14,7 @@ export const objectPermissionsFamilySelector = createAtomFamilySelector<
|
|||
({ objectNameSingular }) =>
|
||||
({ get }) => {
|
||||
const currentUserWorkspace = get(currentUserWorkspaceState);
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
const objectMetadataItems = get(objectMetadataItemsSelector);
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === objectNameSingular,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
type Meta,
|
||||
type StoryObj,
|
||||
} from '@storybook/react-vite';
|
||||
import { expect, userEvent, within } from 'storybook/test';
|
||||
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
||||
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
|
|
@ -152,8 +152,10 @@ export const LimitedPermissions: Story = {
|
|||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
expect(await canvas.findByText('Go to People')).toBeVisible();
|
||||
expect(canvas.queryByText('Go to Opportunities')).not.toBeInTheDocument();
|
||||
expect(canvas.queryByText('Go to Tasks')).not.toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(canvas.queryByText('Go to Opportunities')).not.toBeInTheDocument();
|
||||
expect(canvas.queryByText('Go to Tasks')).not.toBeInTheDocument();
|
||||
});
|
||||
expect(await canvas.findByText('Go to Settings')).toBeVisible();
|
||||
expect(await canvas.findByText('Go to Notes')).toBeVisible();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent';
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
|
||||
import { type FlatFieldMetadataItem } from '@/metadata-store/types/FlatFieldMetadataItem';
|
||||
import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery';
|
||||
import { useStore } from 'jotai';
|
||||
import { AllMetadataName } from '~/generated-metadata/graphql';
|
||||
|
|
@ -11,9 +10,6 @@ export const FieldMetadataSSEEffect = () => {
|
|||
|
||||
const store = useStore();
|
||||
|
||||
const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems();
|
||||
const { updateDraft, applyChanges } = useMetadataStore();
|
||||
|
||||
useListenToEventsForQuery({
|
||||
queryId,
|
||||
operationSignature: {
|
||||
|
|
@ -24,12 +20,52 @@ export const FieldMetadataSSEEffect = () => {
|
|||
|
||||
useListenToMetadataOperationBrowserEvent({
|
||||
metadataName: AllMetadataName.fieldMetadata,
|
||||
onMetadataOperationBrowserEvent: async () => {
|
||||
await refreshObjectMetadataItems();
|
||||
onMetadataOperationBrowserEvent: (eventDetail) => {
|
||||
const entry = store.get(
|
||||
metadataStoreState.atomFamily('fieldMetadataItems'),
|
||||
);
|
||||
const currentFields = entry.current as FlatFieldMetadataItem[];
|
||||
|
||||
const loadedObjects = store.get(objectMetadataItemsState.atom);
|
||||
updateDraft('objectMetadataItems', loadedObjects);
|
||||
applyChanges();
|
||||
switch (eventDetail.operation.type) {
|
||||
case 'create': {
|
||||
const createdField = eventDetail.operation
|
||||
.createdRecord as unknown as FlatFieldMetadataItem;
|
||||
|
||||
store.set(metadataStoreState.atomFamily('fieldMetadataItems'), {
|
||||
...entry,
|
||||
current: [...currentFields, createdField],
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'update': {
|
||||
const updatedField = eventDetail.operation
|
||||
.updatedRecord as unknown as FlatFieldMetadataItem;
|
||||
|
||||
store.set(metadataStoreState.atomFamily('fieldMetadataItems'), {
|
||||
...entry,
|
||||
current: currentFields.map((field) =>
|
||||
field.id === updatedField.id
|
||||
? { ...field, ...updatedField }
|
||||
: field,
|
||||
),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'delete': {
|
||||
const deletedFieldId = eventDetail.operation
|
||||
.deletedRecordId as string;
|
||||
|
||||
store.set(metadataStoreState.atomFamily('fieldMetadataItems'), {
|
||||
...entry,
|
||||
current: currentFields.filter(
|
||||
(field) => field.id !== deletedFieldId,
|
||||
),
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent';
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
|
||||
import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem';
|
||||
import { navigationMenuItemsState } from '@/navigation-menu-item/states/navigationMenuItemsState';
|
||||
import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery';
|
||||
import { useStore } from 'jotai';
|
||||
|
|
@ -18,9 +17,6 @@ export const ObjectMetadataItemSSEEffect = () => {
|
|||
const store = useStore();
|
||||
const client = useApolloClient();
|
||||
|
||||
const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems();
|
||||
const { updateDraft, applyChanges } = useMetadataStore();
|
||||
|
||||
useListenToEventsForQuery({
|
||||
queryId,
|
||||
operationSignature: {
|
||||
|
|
@ -31,12 +27,52 @@ export const ObjectMetadataItemSSEEffect = () => {
|
|||
|
||||
useListenToMetadataOperationBrowserEvent({
|
||||
metadataName: AllMetadataName.objectMetadata,
|
||||
onMetadataOperationBrowserEvent: async () => {
|
||||
await refreshObjectMetadataItems();
|
||||
onMetadataOperationBrowserEvent: async (eventDetail) => {
|
||||
const entry = store.get(
|
||||
metadataStoreState.atomFamily('objectMetadataItems'),
|
||||
);
|
||||
const currentObjects = entry.current as FlatObjectMetadataItem[];
|
||||
|
||||
const loadedObjects = store.get(objectMetadataItemsState.atom);
|
||||
updateDraft('objectMetadataItems', loadedObjects);
|
||||
applyChanges();
|
||||
switch (eventDetail.operation.type) {
|
||||
case 'create': {
|
||||
const createdObject = eventDetail.operation
|
||||
.createdRecord as unknown as FlatObjectMetadataItem;
|
||||
|
||||
store.set(metadataStoreState.atomFamily('objectMetadataItems'), {
|
||||
...entry,
|
||||
current: [...currentObjects, createdObject],
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'update': {
|
||||
const updatedObject = eventDetail.operation
|
||||
.updatedRecord as unknown as FlatObjectMetadataItem;
|
||||
|
||||
store.set(metadataStoreState.atomFamily('objectMetadataItems'), {
|
||||
...entry,
|
||||
current: currentObjects.map((object) =>
|
||||
object.id === updatedObject.id
|
||||
? { ...object, ...updatedObject }
|
||||
: object,
|
||||
),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'delete': {
|
||||
const deletedObjectId = eventDetail.operation
|
||||
.deletedRecordId as string;
|
||||
|
||||
store.set(metadataStoreState.atomFamily('objectMetadataItems'), {
|
||||
...entry,
|
||||
current: currentObjects.filter(
|
||||
(object) => object.id !== deletedObjectId,
|
||||
),
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
const navigationMenuItemsResult = await client.query({
|
||||
query: FindManyNavigationMenuItemsDocument,
|
||||
|
|
|
|||
|
|
@ -1,23 +1,18 @@
|
|||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState';
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useStore } from 'jotai';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
|
||||
|
||||
export const ObjectMetadataProviderInitialEffect = () => {
|
||||
const isCurrentUserLoaded = useAtomStateValue(isCurrentUserLoadedState);
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
const store = useStore();
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems();
|
||||
const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems();
|
||||
const { updateDraft, applyChanges } = useMetadataStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized) {
|
||||
|
|
@ -36,9 +31,6 @@ export const ObjectMetadataProviderInitialEffect = () => {
|
|||
await loadMockedObjectMetadataItems();
|
||||
}
|
||||
|
||||
const loadedItems = store.get(objectMetadataItemsState.atom);
|
||||
updateDraft('objectMetadataItems', loadedItems);
|
||||
applyChanges();
|
||||
setIsInitialized(true);
|
||||
};
|
||||
|
||||
|
|
@ -49,9 +41,6 @@ export const ObjectMetadataProviderInitialEffect = () => {
|
|||
currentWorkspace,
|
||||
refreshObjectMetadataItems,
|
||||
loadMockedObjectMetadataItems,
|
||||
store,
|
||||
updateDraft,
|
||||
applyChanges,
|
||||
]);
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { useSetIndexViews } from '@/metadata-store/hooks/useSetIndexViews';
|
||||
import { type View } from '@/views/types/View';
|
||||
import { useCallback } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useApolloClient } from '@apollo/client/react';
|
||||
|
|
@ -24,7 +25,8 @@ export const useFetchAndLoadIndexViews = () => {
|
|||
|
||||
if (isDefined(result.data?.getCoreViews)) {
|
||||
setIndexViews(result.data.getCoreViews);
|
||||
updateDraft('views', result.data.getCoreViews);
|
||||
// TODO: align generated ViewType with app ViewType to remove this cast
|
||||
updateDraft('views', result.data.getCoreViews as unknown as View[]);
|
||||
applyChanges();
|
||||
}
|
||||
}, [client, setIndexViews, updateDraft, applyChanges]);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
type MetadataEntityKey,
|
||||
type MetadataStoreItem,
|
||||
} from '@/metadata-store/states/metadataStoreState';
|
||||
import { type MetadataEntityTypeMap } from '@/metadata-store/types/MetadataEntityTypeMap';
|
||||
import { useStore, type createStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
|
@ -55,7 +56,7 @@ export const useMetadataStore = () => {
|
|||
const store = useStore();
|
||||
|
||||
const updateDraft = useCallback(
|
||||
(key: MetadataEntityKey, data: object[]) => {
|
||||
<K extends MetadataEntityKey>(key: K, data: MetadataEntityTypeMap[K][]) => {
|
||||
const currentEntry = store.get(metadataStoreState.atomFamily(key));
|
||||
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { type View } from '@/views/types/View';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
|
@ -16,11 +16,9 @@ export const useReloadWorkspaceMetadata = () => {
|
|||
resetMetadataStore();
|
||||
|
||||
await refreshObjectMetadataItems();
|
||||
const loadedObjects = store.get(objectMetadataItemsState.atom);
|
||||
updateDraft('objectMetadataItems', loadedObjects);
|
||||
applyChanges();
|
||||
|
||||
const loadedViews = store.get(coreViewsState.atom);
|
||||
// TODO: align generated ViewType with app ViewType to remove this cast
|
||||
const loadedViews = store.get(coreViewsState.atom) as unknown as View[];
|
||||
updateDraft('views', loadedViews);
|
||||
applyChanges();
|
||||
}, [
|
||||
|
|
@ -35,16 +33,7 @@ export const useReloadWorkspaceMetadata = () => {
|
|||
resetMetadataStore();
|
||||
|
||||
await loadMockedObjectMetadataItems();
|
||||
const loadedObjects = store.get(objectMetadataItemsState.atom);
|
||||
updateDraft('objectMetadataItems', loadedObjects);
|
||||
applyChanges();
|
||||
}, [
|
||||
resetMetadataStore,
|
||||
loadMockedObjectMetadataItems,
|
||||
store,
|
||||
updateDraft,
|
||||
applyChanges,
|
||||
]);
|
||||
}, [resetMetadataStore, loadMockedObjectMetadataItems]);
|
||||
|
||||
return { reloadWorkspaceMetadata, resetToMockedMetadata };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
|
||||
import { type FlatFieldMetadataItem } from '@/metadata-store/types/FlatFieldMetadataItem';
|
||||
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
|
||||
|
||||
export const fieldMetadataItemsSelector = createAtomSelector<
|
||||
FlatFieldMetadataItem[]
|
||||
>({
|
||||
key: 'fieldMetadataItemsSelector',
|
||||
get: ({ get }) => {
|
||||
const storeItem = get(metadataStoreState, 'fieldMetadataItems');
|
||||
|
||||
return storeItem.current as FlatFieldMetadataItem[];
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
|
||||
import { type FlatIndexMetadataItem } from '@/metadata-store/types/FlatIndexMetadataItem';
|
||||
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
|
||||
|
||||
export const indexMetadataItemsSelector = createAtomSelector<
|
||||
FlatIndexMetadataItem[]
|
||||
>({
|
||||
key: 'indexMetadataItemsSelector',
|
||||
get: ({ get }) => {
|
||||
const storeItem = get(metadataStoreState, 'indexMetadataItems');
|
||||
|
||||
return storeItem.current as FlatIndexMetadataItem[];
|
||||
},
|
||||
});
|
||||
|
|
@ -8,6 +8,7 @@ export type MetadataEntityStoreStatus =
|
|||
export const ALL_METADATA_ENTITY_KEYS = [
|
||||
'objectMetadataItems',
|
||||
'fieldMetadataItems',
|
||||
'indexMetadataItems',
|
||||
'views',
|
||||
'viewFields',
|
||||
'viewFilters',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import {
|
||||
metadataStoreState,
|
||||
type MetadataEntityKey,
|
||||
type MetadataEntityStoreStatus,
|
||||
} from '@/metadata-store/states/metadataStoreState';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
|
||||
export const metadataStoreStatusFamilySelector = createAtomFamilySelector<
|
||||
MetadataEntityStoreStatus,
|
||||
MetadataEntityKey
|
||||
>({
|
||||
key: 'metadataStoreStatusFamilySelector',
|
||||
get:
|
||||
(entityKey: MetadataEntityKey) =>
|
||||
({ get }) => {
|
||||
const storeItem = get(metadataStoreState, entityKey);
|
||||
|
||||
return storeItem.status;
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
|
||||
import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem';
|
||||
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
|
||||
|
||||
export const objectMetadataItemsSelector = createAtomSelector<
|
||||
FlatObjectMetadataItem[]
|
||||
>({
|
||||
key: 'objectMetadataItemsSelector',
|
||||
get: ({ get }) => {
|
||||
const storeItem = get(metadataStoreState, 'objectMetadataItems');
|
||||
|
||||
return storeItem.current as FlatObjectMetadataItem[];
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
|
||||
export type FlatFieldMetadataItem = FieldMetadataItem & {
|
||||
objectMetadataId: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { type IndexMetadataItem } from '@/object-metadata/types/IndexMetadataItem';
|
||||
|
||||
export type FlatIndexMetadataItem = IndexMetadataItem & {
|
||||
objectMetadataId: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export type FlatObjectMetadataItem = Omit<
|
||||
ObjectMetadataItem,
|
||||
'fields' | 'readableFields' | 'updatableFields' | 'indexMetadatas'
|
||||
>;
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { type FlatFieldMetadataItem } from '@/metadata-store/types/FlatFieldMetadataItem';
|
||||
import { type FlatIndexMetadataItem } from '@/metadata-store/types/FlatIndexMetadataItem';
|
||||
import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { type LogicFunction } from '@/settings/logic-functions/states/logicFunctionsState';
|
||||
import { type View } from '@/views/types/View';
|
||||
import { type ViewField } from '@/views/types/ViewField';
|
||||
import { type ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { type ViewSort } from '@/views/types/ViewSort';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
export type MetadataEntityTypeMap = {
|
||||
objectMetadataItems: FlatObjectMetadataItem;
|
||||
fieldMetadataItems: FlatFieldMetadataItem;
|
||||
indexMetadataItems: FlatIndexMetadataItem;
|
||||
views: View;
|
||||
viewFields: ViewField;
|
||||
viewFilters: ViewFilter;
|
||||
viewSorts: ViewSort;
|
||||
pageLayouts: PageLayout;
|
||||
logicFunctions: LogicFunction;
|
||||
navigationMenuItems: NavigationMenuItem;
|
||||
};
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import { type FlatFieldMetadataItem } from '@/metadata-store/types/FlatFieldMetadataItem';
|
||||
import { type FlatIndexMetadataItem } from '@/metadata-store/types/FlatIndexMetadataItem';
|
||||
import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export type ObjectMetadataItemWithRelated = Omit<
|
||||
ObjectMetadataItem,
|
||||
'readableFields' | 'updatableFields'
|
||||
>;
|
||||
|
||||
type SplitResult = {
|
||||
flatObjects: FlatObjectMetadataItem[];
|
||||
flatFields: FlatFieldMetadataItem[];
|
||||
flatIndexes: FlatIndexMetadataItem[];
|
||||
};
|
||||
|
||||
export const splitObjectMetadataItemWithRelated = (
|
||||
objectMetadataItemsWithRelated: ObjectMetadataItemWithRelated[],
|
||||
): SplitResult => {
|
||||
const flatObjects: FlatObjectMetadataItem[] = [];
|
||||
const flatFields: FlatFieldMetadataItem[] = [];
|
||||
const flatIndexes: FlatIndexMetadataItem[] = [];
|
||||
|
||||
for (const objectMetadataItemWithRelated of objectMetadataItemsWithRelated) {
|
||||
const {
|
||||
fields = [],
|
||||
indexMetadatas = [],
|
||||
...objectProperties
|
||||
} = objectMetadataItemWithRelated;
|
||||
|
||||
flatObjects.push(objectProperties);
|
||||
|
||||
for (const field of fields) {
|
||||
flatFields.push({
|
||||
...field,
|
||||
objectMetadataId: objectMetadataItemWithRelated.id,
|
||||
});
|
||||
}
|
||||
|
||||
for (const index of indexMetadatas) {
|
||||
flatIndexes.push({
|
||||
...index,
|
||||
objectMetadataId: objectMetadataItemWithRelated.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { flatObjects, flatFields, flatIndexes };
|
||||
};
|
||||
|
|
@ -4,8 +4,8 @@ import { Provider as JotaiProvider } from 'jotai';
|
|||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
|
|
@ -29,8 +29,8 @@ const renderHooks = ({
|
|||
withCurrentUser: boolean;
|
||||
withExistingView: boolean;
|
||||
}) => {
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import {
|
|||
variables,
|
||||
} from '@/object-metadata/hooks/__mocks__/useFilteredObjectMetadataItems';
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
|
||||
|
|
@ -28,8 +28,8 @@ const mocks = [
|
|||
];
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => {
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { renderHook } from '@testing-library/react';
|
|||
import { Provider as JotaiProvider } from 'jotai';
|
||||
|
||||
import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
|
|
@ -12,8 +12,8 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|||
|
||||
describe('useGetObjectRecordIdentifierByNameSingular', () => {
|
||||
beforeEach(() => {
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { type ReactNode } from 'react';
|
|||
import { Provider as JotaiProvider } from 'jotai';
|
||||
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
|
|
@ -16,8 +16,8 @@ const Wrapper = ({ children }: { children: ReactNode }) => (
|
|||
|
||||
describe('useGetRelationMetadata', () => {
|
||||
beforeEach(() => {
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
});
|
||||
|
|
@ -50,11 +50,11 @@ describe('useGetRelationMetadata', () => {
|
|||
(field) => field.name === 'pointOfContact',
|
||||
);
|
||||
|
||||
expect(relationObjectMetadataItem).toEqual(
|
||||
expectedRelationObjectMetadataItem,
|
||||
expect(relationObjectMetadataItem).toMatchObject(
|
||||
expectedRelationObjectMetadataItem!,
|
||||
);
|
||||
expect(relationFieldMetadataItem).toEqual(
|
||||
expectedRelationFieldMetadataItem,
|
||||
expect(relationFieldMetadataItem).toMatchObject(
|
||||
expectedRelationFieldMetadataItem!,
|
||||
);
|
||||
expect(relationType).toBe('ONE_TO_MANY');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { useMemo } from 'react';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
|
||||
export const useFilteredObjectMetadataItems = () => {
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsState);
|
||||
const objectMetadataItemsWithFields = useAtomStateValue(
|
||||
objectMetadataItemsWithFieldsSelector,
|
||||
);
|
||||
const objectMetadataItems = objectMetadataItemsWithFields;
|
||||
|
||||
const activeNonSystemObjectMetadataItems = useMemo(
|
||||
() =>
|
||||
|
|
|
|||
|
|
@ -1,32 +1,30 @@
|
|||
import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useCallback } from 'react';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { splitObjectMetadataItemWithRelated } from '@/metadata-store/utils/splitObjectMetadataItemWithRelated';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useLoadMockedObjectMetadataItems = () => {
|
||||
const store = useStore();
|
||||
const { updateDraft, applyChanges } = useMetadataStore();
|
||||
|
||||
const loadMockedObjectMetadataItems = useCallback(async () => {
|
||||
const { generatedMockObjectMetadataItems } = await import(
|
||||
'~/testing/utils/generatedMockObjectMetadataItems'
|
||||
);
|
||||
|
||||
if (
|
||||
!isDeeplyEqual(
|
||||
store.get(objectMetadataItemsState.atom),
|
||||
generatedMockObjectMetadataItems,
|
||||
)
|
||||
) {
|
||||
store.set(
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
}
|
||||
const { flatObjects, flatFields, flatIndexes } =
|
||||
splitObjectMetadataItemWithRelated(generatedMockObjectMetadataItems);
|
||||
|
||||
updateDraft('objectMetadataItems', flatObjects);
|
||||
updateDraft('fieldMetadataItems', flatFields);
|
||||
updateDraft('indexMetadataItems', flatIndexes);
|
||||
applyChanges();
|
||||
|
||||
if (store.get(isAppEffectRedirectEnabledState.atom) === false) {
|
||||
store.set(isAppEffectRedirectEnabledState.atom, true);
|
||||
}
|
||||
}, [store]);
|
||||
}, [store, updateDraft, applyChanges]);
|
||||
|
||||
return {
|
||||
loadMockedObjectMetadataItems,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError';
|
||||
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
|
||||
|
|
@ -18,7 +18,10 @@ export const useObjectMetadataItem = ({
|
|||
},
|
||||
);
|
||||
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsState);
|
||||
const objectMetadataItemsWithFields = useAtomStateValue(
|
||||
objectMetadataItemsWithFieldsSelector,
|
||||
);
|
||||
const objectMetadataItems = objectMetadataItemsWithFields;
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
throw new ObjectMetadataItemNotFoundError(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
|
||||
export const useObjectMetadataItems = () => {
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsState);
|
||||
const objectMetadataItemsWithFields = useAtomStateValue(
|
||||
objectMetadataItemsWithFieldsSelector,
|
||||
);
|
||||
const objectMetadataItems = objectMetadataItemsWithFields;
|
||||
|
||||
return {
|
||||
objectMetadataItems,
|
||||
|
|
|
|||
|
|
@ -1,75 +1,22 @@
|
|||
import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState';
|
||||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { splitObjectMetadataItemWithRelated } from '@/metadata-store/utils/splitObjectMetadataItemWithRelated';
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { enrichObjectMetadataItemsWithPermissions } from '@/object-metadata/utils/enrichObjectMetadataItemsWithPermissions';
|
||||
import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '@/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems';
|
||||
import { type FetchPolicy } from '@apollo/client';
|
||||
import { useApolloClient } from '@apollo/client/react';
|
||||
import { useCallback } from 'react';
|
||||
import { type ObjectPermissions } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
import { type ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useRefreshObjectMetadataItems = (
|
||||
fetchPolicy: FetchPolicy = 'network-only',
|
||||
) => {
|
||||
const store = useStore();
|
||||
const client = useApolloClient();
|
||||
const { updateDraft, applyChanges } = useMetadataStore();
|
||||
|
||||
const replaceObjectMetadataItemIfDifferent = useCallback(
|
||||
(
|
||||
toSetObjectMetadataItems: Omit<
|
||||
ObjectMetadataItem,
|
||||
'readableFields' | 'updatableFields'
|
||||
>[],
|
||||
) => {
|
||||
const currentUserWorkspace = store.get(currentUserWorkspaceState.atom);
|
||||
|
||||
if (!isDefined(currentUserWorkspace)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const objectPermissionsByObjectMetadataId =
|
||||
currentUserWorkspace.objectsPermissions.reduce(
|
||||
(acc, objectPermission) => {
|
||||
acc[objectPermission.objectMetadataId] = objectPermission;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<
|
||||
string,
|
||||
ObjectPermissions & { objectMetadataId: string }
|
||||
>,
|
||||
);
|
||||
|
||||
const newObjectMetadataItems = enrichObjectMetadataItemsWithPermissions({
|
||||
objectMetadataItems: toSetObjectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
if (
|
||||
!isDeeplyEqual(
|
||||
store.get(objectMetadataItemsState.atom),
|
||||
newObjectMetadataItems,
|
||||
) &&
|
||||
newObjectMetadataItems.length > 0
|
||||
) {
|
||||
store.set(objectMetadataItemsState.atom, newObjectMetadataItems);
|
||||
}
|
||||
|
||||
if (store.get(isAppEffectRedirectEnabledState.atom) === false) {
|
||||
store.set(isAppEffectRedirectEnabledState.atom, true);
|
||||
}
|
||||
|
||||
return newObjectMetadataItems;
|
||||
},
|
||||
[store],
|
||||
);
|
||||
|
||||
const refreshObjectMetadataItems = async () => {
|
||||
const refreshObjectMetadataItems = useCallback(async () => {
|
||||
const objectMetadataItemsResult =
|
||||
await client.query<ObjectMetadataItemsQuery>({
|
||||
query: FIND_MANY_OBJECT_METADATA_ITEMS,
|
||||
|
|
@ -77,13 +24,23 @@ export const useRefreshObjectMetadataItems = (
|
|||
fetchPolicy,
|
||||
});
|
||||
|
||||
const objectMetadataItems =
|
||||
const compositeObjects =
|
||||
mapPaginatedObjectMetadataItemsToObjectMetadataItems({
|
||||
pagedObjectMetadataItems: objectMetadataItemsResult.data,
|
||||
});
|
||||
|
||||
return replaceObjectMetadataItemIfDifferent(objectMetadataItems);
|
||||
};
|
||||
const { flatObjects, flatFields, flatIndexes } =
|
||||
splitObjectMetadataItemWithRelated(compositeObjects);
|
||||
|
||||
updateDraft('objectMetadataItems', flatObjects);
|
||||
updateDraft('fieldMetadataItems', flatFields);
|
||||
updateDraft('indexMetadataItems', flatIndexes);
|
||||
applyChanges();
|
||||
|
||||
if (store.get(isAppEffectRedirectEnabledState.atom) === false) {
|
||||
store.set(isAppEffectRedirectEnabledState.atom, true);
|
||||
}
|
||||
}, [client, fetchPolicy, store, updateDraft, applyChanges]);
|
||||
|
||||
return {
|
||||
refreshObjectMetadataItems,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import { objectMetadataItemsSelector } from '@/metadata-store/states/objectMetadataItemsSelector';
|
||||
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
|
||||
|
||||
export const activeObjectNameSingularsSelector = createAtomSelector<string[]>({
|
||||
key: 'activeObjectNameSingularsSelector',
|
||||
get: ({ get }) => {
|
||||
const flatObjects = get(objectMetadataItemsSelector);
|
||||
|
||||
return flatObjects
|
||||
.filter((flatObject) => flatObject.isActive)
|
||||
.map((flatObject) => flatObject.nameSingular);
|
||||
},
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { getFilterFilterableFieldMetadataItems } from '@/object-metadata/utils/getFilterFilterableFieldMetadataItems';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
|
|
@ -17,7 +17,7 @@ export const availableFieldMetadataItemsForFilterFamilySelector =
|
|||
({ objectMetadataItemId }: { objectMetadataItemId: string }) =>
|
||||
({ get }) => {
|
||||
const currentWorkspace = get(currentWorkspaceState);
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector);
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === objectMetadataItemId,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { filterSortableFieldMetadataItems } from '@/object-metadata/utils/filterSortableFieldMetadataItems';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
|
|
@ -13,7 +13,7 @@ export const availableFieldMetadataItemsForSortFamilySelector =
|
|||
get:
|
||||
({ objectMetadataItemId }: { objectMetadataItemId: string }) =>
|
||||
({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector);
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === objectMetadataItemId,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
import { findById, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ export const fieldMetadataItemByIdSelector = createAtomFamilySelector({
|
|||
get:
|
||||
({ fieldMetadataItemId }: { fieldMetadataItemId: string }) =>
|
||||
({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector);
|
||||
const flattenedFieldMetadataItems = get(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ export const flattenedFieldMetadataItemsSelector = createAtomSelector<
|
|||
>({
|
||||
key: 'flattenedFieldMetadataItemsSelector',
|
||||
get: ({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector);
|
||||
|
||||
return objectMetadataItems.flatMap(
|
||||
(objectMetadataItem) => objectMetadataItem.fields,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ export const flattenedReadableFieldMetadataItemsSelector = createAtomSelector<
|
|||
>({
|
||||
key: 'flattenedReadableFieldMetadataItemsSelector',
|
||||
get: ({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector);
|
||||
|
||||
return objectMetadataItems.flatMap(
|
||||
(objectMetadataItem) => objectMetadataItem.readableFields,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
|
||||
import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector';
|
||||
import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
import { findById, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ export const isFieldMetadataItemFilterableAndSortableSelector =
|
|||
get:
|
||||
({ fieldMetadataItemId }: { fieldMetadataItemId: string }) =>
|
||||
({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector);
|
||||
const flattenedFieldMetadataItems = get(
|
||||
flattenedFieldMetadataItemsSelector,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
import { findById, isDefined } from 'twenty-shared/utils';
|
||||
|
|
@ -9,7 +9,7 @@ export const isFieldMetadataItemLabelIdentifierSelector =
|
|||
get:
|
||||
({ fieldMetadataItemId }: { fieldMetadataItemId: string }) =>
|
||||
({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector);
|
||||
|
||||
const foundObjectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { objectMetadataItemsSelector } from '@/metadata-store/states/objectMetadataItemsSelector';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
|
||||
export const isSystemObjectByNameSingularFamilySelector =
|
||||
createAtomFamilySelector<boolean, string>({
|
||||
key: 'isSystemObjectByNameSingularFamilySelector',
|
||||
get:
|
||||
(nameSingular: string) =>
|
||||
({ get }) => {
|
||||
const flatObjects = get(objectMetadataItemsSelector);
|
||||
|
||||
return (
|
||||
flatObjects.find(
|
||||
(flatObject) => flatObject.nameSingular === nameSingular,
|
||||
)?.isSystem ?? false
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
|
@ -9,7 +9,7 @@ export const labelIdentifierFieldMetadataItemSelector =
|
|||
get:
|
||||
({ objectMetadataItemId }: { objectMetadataItemId: string }) =>
|
||||
({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector);
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ export const objectMetadataItemFamilySelector = createAtomFamilySelector<
|
|||
get:
|
||||
({ objectNameType, objectName }: ObjectMetadataItemSelector) =>
|
||||
({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector);
|
||||
|
||||
if (objectNameType === 'singular') {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ export const objectMetadataItemsByNamePluralMapSelector = createAtomSelector<
|
|||
>({
|
||||
key: 'objectMetadataItemsByNamePluralMapSelector',
|
||||
get: ({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector);
|
||||
|
||||
return new Map(
|
||||
objectMetadataItems.map((objectMetadataItem) => [
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ export const objectMetadataItemsByNameSingularMapSelector = createAtomSelector<
|
|||
>({
|
||||
key: 'objectMetadataItemsByNameSingularMapSelector',
|
||||
get: ({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector);
|
||||
|
||||
return new Map(
|
||||
objectMetadataItems.map((objectMetadataItem) => [
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ export const objectMetadataItemsBySingularNameSelector =
|
|||
get:
|
||||
(objectNameSingulars: string[]) =>
|
||||
({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
const objectMetadataItems = get(objectMetadataItemsWithFieldsSelector);
|
||||
|
||||
return objectNameSingulars.flatMap((objectNameSingular) => {
|
||||
const found = objectMetadataItems.find(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { objectMetadataItemsWithFieldsSelector } from '@/object-metadata/states/objectMetadataItemsWithFieldsSelector';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
|
||||
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
|
||||
|
||||
export const objectMetadataItemsState = createAtomState<ObjectMetadataItem[]>({
|
||||
export const objectMetadataItemsState = createAtomSelector<
|
||||
ObjectMetadataItem[]
|
||||
>({
|
||||
key: 'objectMetadataItemsState',
|
||||
defaultValue: [],
|
||||
get: ({ get }) => get(objectMetadataItemsWithFieldsSelector),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||
import { fieldMetadataItemsSelector } from '@/metadata-store/states/fieldMetadataItemsSelector';
|
||||
import { indexMetadataItemsSelector } from '@/metadata-store/states/indexMetadataItemsSelector';
|
||||
import { objectMetadataItemsSelector } from '@/metadata-store/states/objectMetadataItemsSelector';
|
||||
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(objectMetadataItemsSelector);
|
||||
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;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
import { renderHook } from '@testing-library/react';
|
||||
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
|
||||
jotaiStore.set(objectMetadataItemsState.atom, generatedMockObjectMetadataItems);
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
|
||||
const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks: [],
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { gql } from '@apollo/client';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { type RecordGqlOperationSignature } from 'twenty-shared/types';
|
||||
import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords';
|
||||
import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery';
|
||||
|
|
@ -123,8 +123,8 @@ const renderUseCombinedFindManyRecordsHook = async ({
|
|||
},
|
||||
];
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { ObjectOptionsDropdownContent } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownContent';
|
||||
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
|
||||
import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
|
||||
|
|
@ -32,8 +32,8 @@ const meta: Meta<typeof ObjectOptionsDropdownContent> = {
|
|||
decorators: [
|
||||
(Story) => {
|
||||
useEffect(() => {
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { splitObjectMetadataItemWithRelated } from '@/metadata-store/utils/splitObjectMetadataItemWithRelated';
|
||||
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
|
||||
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
|
|
@ -15,7 +16,6 @@ import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
|||
|
||||
import { labelIdentifierFieldMetadataItemSelector } from '@/object-metadata/states/labelIdentifierFieldMetadataItemSelector';
|
||||
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
|
||||
|
|
@ -53,7 +53,7 @@ const RelationFieldValueSetterEffect = () => {
|
|||
'recordTableId',
|
||||
);
|
||||
|
||||
const [, setObjectMetadataItems] = useAtomState(objectMetadataItemsState);
|
||||
const { updateDraft, applyChanges } = useMetadataStore();
|
||||
|
||||
useEffect(() => {
|
||||
setRecordStore(mockPerformance.entityValue);
|
||||
|
|
@ -71,11 +71,18 @@ const RelationFieldValueSetterEffect = () => {
|
|||
),
|
||||
);
|
||||
|
||||
setObjectMetadataItems(generatedMockObjectMetadataItems);
|
||||
const { flatObjects, flatFields, flatIndexes } =
|
||||
splitObjectMetadataItemWithRelated(generatedMockObjectMetadataItems);
|
||||
|
||||
updateDraft('objectMetadataItems', flatObjects);
|
||||
updateDraft('fieldMetadataItems', flatFields);
|
||||
updateDraft('indexMetadataItems', flatIndexes);
|
||||
applyChanges();
|
||||
}, [
|
||||
setRecordStore,
|
||||
setRelationRecordStore,
|
||||
setObjectMetadataItems,
|
||||
updateDraft,
|
||||
applyChanges,
|
||||
setCurrentRecordFields,
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { act, renderHook } from '@testing-library/react';
|
||||
import { Provider as JotaiProvider } from 'jotai';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
|
||||
import { textfieldDefinition } from '@/object-record/record-field/ui/__mocks__/fieldDefinitions';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
|
|
@ -26,8 +26,8 @@ import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockO
|
|||
const recordTableId = 'record-table-id';
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { act, renderHook } from '@testing-library/react';
|
||||
import { Provider as JotaiProvider } from 'jotai';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
|
||||
import { textfieldDefinition } from '@/object-record/record-field/ui/__mocks__/fieldDefinitions';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
|
|
@ -26,8 +26,8 @@ import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockO
|
|||
const recordTableId = 'record-table-id';
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { type ReactNode } from 'react';
|
|||
import { useIcons } from 'twenty-ui/display';
|
||||
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type IndexMetadataItem } from '@/object-metadata/types/IndexMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
|
@ -34,7 +34,7 @@ describe('useBuildSpreadSheetImportFields', () => {
|
|||
getIcons: () => ({}),
|
||||
});
|
||||
jest.clearAllMocks();
|
||||
jotaiStore.set(objectMetadataItemsState.atom, []);
|
||||
setTestObjectMetadataItemsInMetadataStore(jotaiStore, []);
|
||||
});
|
||||
|
||||
const createMockFieldMetadataItem = (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
import {
|
||||
|
|
@ -33,8 +33,8 @@ const meta: Meta<typeof DashboardWidgetPlaceholder> = {
|
|||
component: DashboardWidgetPlaceholder,
|
||||
decorators: [
|
||||
(Story) => {
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import { CatalogDecorator, type CatalogStory } from 'twenty-ui/testing';
|
|||
|
||||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||
import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
|
|
@ -277,8 +277,8 @@ export const WithNumberChart: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -364,8 +364,8 @@ export const WithGaugeChart: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -455,8 +455,8 @@ export const WithBarChart: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -549,8 +549,8 @@ export const SmallWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -647,8 +647,8 @@ export const MediumWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -745,8 +745,8 @@ export const LargeWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -839,8 +839,8 @@ export const WideWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -937,8 +937,8 @@ export const TallWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1031,8 +1031,8 @@ export const WithManyToOneRelationFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1150,8 +1150,8 @@ export const WithOneToManyRelationFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1261,8 +1261,8 @@ export const OneToManyRelationFieldWidgetWithSeeAllButton: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1399,8 +1399,8 @@ export const OnMobile: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1497,8 +1497,8 @@ export const InSidePanel: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1656,8 +1656,8 @@ export const Catalog: CatalogStory<Story, typeof WidgetRenderer> = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import { MemoryRouter } from 'react-router-dom';
|
|||
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
||||
|
||||
import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
|
@ -356,8 +356,8 @@ export const TextFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -450,8 +450,8 @@ export const AddressFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -547,8 +547,8 @@ export const NumberFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -641,8 +641,8 @@ export const LinkFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -735,8 +735,8 @@ export const ManyToOneRelationFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -839,8 +839,8 @@ export const OneToManyRelationFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -935,8 +935,8 @@ export const BooleanFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1028,8 +1028,8 @@ export const CurrencyFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1121,8 +1121,8 @@ export const EmailsFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1215,8 +1215,8 @@ export const PhonesFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1309,8 +1309,8 @@ export const SelectFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1404,8 +1404,8 @@ export const MultiSelectFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1502,8 +1502,8 @@ export const TimelineActivityRelationFieldWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1601,8 +1601,8 @@ export const ManyToOneRelationCardWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1713,8 +1713,8 @@ export const OneToManyRelationCardWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1808,8 +1808,8 @@ export const TimelineActivityRelationCardWidget: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -1969,8 +1969,8 @@ export const OneToManyRelationCardWidgetWithProgressiveLoading: Story = {
|
|||
deletedAt: null,
|
||||
};
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import { expect, waitFor, within } from 'storybook/test';
|
|||
|
||||
import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState';
|
||||
import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
|
|
@ -321,8 +321,8 @@ export const WithViewFieldGroups: Story = {
|
|||
companyObjectMetadataItem.id,
|
||||
);
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -434,8 +434,8 @@ export const WithInlineViewFields: Story = {
|
|||
companyObjectMetadataItem.id,
|
||||
);
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
@ -526,8 +526,8 @@ export const Empty: Story = {
|
|||
companyObjectMetadataItem.id,
|
||||
);
|
||||
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { Provider as JotaiProvider } from 'jotai';
|
|||
import { type ChartConfiguration } from '@/side-panel/pages/page-layout/types/ChartConfiguration';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/side-panel/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type TypedBarChartConfiguration } from '@/side-panel/pages/page-layout/types/TypedBarChartConfiguration';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import {
|
||||
AggregateOperations,
|
||||
AxisNameDisplay,
|
||||
|
|
@ -80,7 +80,9 @@ const buildBarChartConfiguration = (
|
|||
}) as TypedBarChartConfiguration;
|
||||
|
||||
const renderUseChartSettingsValues = (configuration: ChartConfiguration) => {
|
||||
jotaiStore.set(objectMetadataItemsState.atom, [mockObjectMetadataItem]);
|
||||
setTestObjectMetadataItemsInMetadataStore(jotaiStore, [
|
||||
mockObjectMetadataItem,
|
||||
]);
|
||||
|
||||
return renderHook(
|
||||
() =>
|
||||
|
|
@ -411,7 +413,9 @@ describe('useChartSettingsValues', () => {
|
|||
it('should handle missing objectMetadataItem gracefully', () => {
|
||||
const config = buildBarChartConfiguration({});
|
||||
|
||||
jotaiStore.set(objectMetadataItemsState.atom, [mockObjectMetadataItem]);
|
||||
setTestObjectMetadataItemsInMetadataStore(jotaiStore, [
|
||||
mockObjectMetadataItem,
|
||||
]);
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { useEffect } from 'react';
|
|||
import { expect, within } from 'storybook/test';
|
||||
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||
|
|
@ -56,8 +56,8 @@ const meta: Meta<typeof NavigationDrawer> = {
|
|||
currentWorkspaceMemberState,
|
||||
);
|
||||
useEffect(() => {
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
setCurrentWorkspaceMember(mockedWorkspaceMemberData);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-rec
|
|||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown } from '@/views/hooks/useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import {
|
||||
jotaiStore,
|
||||
resetJotaiStore,
|
||||
|
|
@ -26,6 +25,7 @@ import { ViewBarFilterDropdownIds } from '@/views/constants/ViewBarFilterDropdow
|
|||
import { getFilterTypeFromFieldType } from 'twenty-shared/utils';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
|
||||
const mockPushFocusItemToFocusStack = jest.fn();
|
||||
|
||||
|
|
@ -46,8 +46,8 @@ const personCreatedAtFieldMetadataItemMock =
|
|||
);
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
jotaiStore.set(
|
||||
objectMetadataItemsState.atom,
|
||||
setTestObjectMetadataItemsInMetadataStore(
|
||||
jotaiStore,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState';
|
||||
import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useGetAvailableFieldsForCalendar';
|
||||
|
|
@ -22,7 +22,7 @@ const createMockObjectMetadataItem = (fields: any[]) => ({
|
|||
|
||||
const createWrapper = (objectMetadataItems: any[]) => {
|
||||
return ({ children }: { children: ReactNode }) => {
|
||||
jotaiStore.set(objectMetadataItemsState.atom, objectMetadataItems);
|
||||
setTestObjectMetadataItemsInMetadataStore(jotaiStore, objectMetadataItems);
|
||||
jotaiStore.set(
|
||||
viewObjectMetadataIdComponentState.atomFamily({
|
||||
instanceId: mockViewInstanceId,
|
||||
|
|
@ -80,7 +80,7 @@ describe('useGetAvailableFieldsForCalendar', () => {
|
|||
});
|
||||
|
||||
expect(result.current.availableFieldsForCalendar).toHaveLength(2);
|
||||
expect(result.current.availableFieldsForCalendar).toEqual([
|
||||
expect(result.current.availableFieldsForCalendar).toMatchObject([
|
||||
{
|
||||
id: '1',
|
||||
type: FieldMetadataType.DATE,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState';
|
||||
import { useGetAvailableFieldsToGroupRecordsBy } from '@/views/view-picker/hooks/useGetAvailableFieldsToGroupRecordsBy';
|
||||
|
|
@ -22,7 +22,7 @@ const createMockObjectMetadataItem = (fields: any[]) => ({
|
|||
|
||||
const createWrapper = (objectMetadataItems: any[]) => {
|
||||
return ({ children }: { children: ReactNode }) => {
|
||||
jotaiStore.set(objectMetadataItemsState.atom, objectMetadataItems);
|
||||
setTestObjectMetadataItemsInMetadataStore(jotaiStore, objectMetadataItems);
|
||||
jotaiStore.set(
|
||||
viewObjectMetadataIdComponentState.atomFamily({
|
||||
instanceId: mockViewInstanceId,
|
||||
|
|
|
|||
|
|
@ -10,9 +10,7 @@ import { SubTitle } from '@/auth/components/SubTitle';
|
|||
import { Title } from '@/auth/components/Title';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useFetchAndLoadIndexViews } from '@/metadata-store/hooks/useFetchAndLoadIndexViews';
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
||||
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
|
|
@ -22,7 +20,7 @@ import { useLoadCurrentUser } from '@/users/hooks/useLoadCurrentUser';
|
|||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useStore } from 'jotai';
|
||||
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
|
|
@ -73,9 +71,7 @@ export const CreateWorkspace = () => {
|
|||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const setNextOnboardingStatus = useSetNextOnboardingStatus();
|
||||
const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems();
|
||||
const { updateDraft, applyChanges } = useMetadataStore();
|
||||
const { fetchAndLoadIndexViews } = useFetchAndLoadIndexViews();
|
||||
const store = useStore();
|
||||
|
||||
const { loadCurrentUser } = useLoadCurrentUser();
|
||||
const [activateWorkspace] = useMutation(ActivateWorkspaceDocument);
|
||||
|
|
@ -132,10 +128,6 @@ export const CreateWorkspace = () => {
|
|||
|
||||
await refreshObjectMetadataItems();
|
||||
|
||||
const loadedObjects = store.get(objectMetadataItemsState.atom);
|
||||
updateDraft('objectMetadataItems', loadedObjects);
|
||||
applyChanges();
|
||||
|
||||
await fetchAndLoadIndexViews();
|
||||
|
||||
await loadCurrentUser();
|
||||
|
|
@ -153,9 +145,6 @@ export const CreateWorkspace = () => {
|
|||
enqueueErrorSnackBar,
|
||||
loadCurrentUser,
|
||||
refreshObjectMetadataItems,
|
||||
updateDraft,
|
||||
applyChanges,
|
||||
store,
|
||||
fetchAndLoadIndexViews,
|
||||
setNextOnboardingStatus,
|
||||
t,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { type ReactNode, useEffect, useState } from 'react';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { splitObjectMetadataItemWithRelated } from '@/metadata-store/utils/splitObjectMetadataItemWithRelated';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
|
||||
export const JestObjectMetadataItemSetter = ({
|
||||
|
|
@ -12,15 +12,20 @@ export const JestObjectMetadataItemSetter = ({
|
|||
children: ReactNode;
|
||||
objectMetadataItems?: ObjectMetadataItem[];
|
||||
}) => {
|
||||
const setObjectMetadataItems = useSetAtomState(objectMetadataItemsState);
|
||||
const { updateDraft, applyChanges } = useMetadataStore();
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setObjectMetadataItems(
|
||||
objectMetadataItems ?? generatedMockObjectMetadataItems,
|
||||
);
|
||||
const items = objectMetadataItems ?? generatedMockObjectMetadataItems;
|
||||
const { flatObjects, flatFields, flatIndexes } =
|
||||
splitObjectMetadataItemWithRelated(items);
|
||||
|
||||
updateDraft('objectMetadataItems', flatObjects);
|
||||
updateDraft('fieldMetadataItems', flatFields);
|
||||
updateDraft('indexMetadataItems', flatIndexes);
|
||||
applyChanges();
|
||||
setIsLoaded(true);
|
||||
}, [objectMetadataItems, setObjectMetadataItems]);
|
||||
}, [objectMetadataItems, updateDraft, applyChanges]);
|
||||
|
||||
return isLoaded ? <>{children}</> : null;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
|
||||
import { splitObjectMetadataItemWithRelated } from '@/metadata-store/utils/splitObjectMetadataItemWithRelated';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { type createStore } from 'jotai';
|
||||
|
||||
type JotaiStore = ReturnType<typeof createStore>;
|
||||
|
||||
export const setTestObjectMetadataItemsInMetadataStore = (
|
||||
store: JotaiStore,
|
||||
objectMetadataItems: ObjectMetadataItem[],
|
||||
) => {
|
||||
const { flatObjects, flatFields, flatIndexes } =
|
||||
splitObjectMetadataItemWithRelated(objectMetadataItems);
|
||||
|
||||
store.set(metadataStoreState.atomFamily('objectMetadataItems'), {
|
||||
current: flatObjects,
|
||||
draft: [],
|
||||
status: 'up-to-date',
|
||||
});
|
||||
|
||||
store.set(metadataStoreState.atomFamily('fieldMetadataItems'), {
|
||||
current: flatFields,
|
||||
draft: [],
|
||||
status: 'up-to-date',
|
||||
});
|
||||
|
||||
store.set(metadataStoreState.atomFamily('indexMetadataItems'), {
|
||||
current: flatIndexes,
|
||||
draft: [],
|
||||
status: 'up-to-date',
|
||||
});
|
||||
};
|
||||
|
|
@ -88,8 +88,12 @@ export default defineConfig(({ mode }) => {
|
|||
include: [path.resolve(__dirname, 'src') + '/**/*.{ts,tsx}'],
|
||||
exclude: [
|
||||
'**/generated-metadata/**',
|
||||
'**/testing/mock-data/generated/**',
|
||||
'**/testing/**',
|
||||
'**/testing/mock-data/**',
|
||||
'**/testing/jest/**',
|
||||
'**/testing/hooks/**',
|
||||
'**/testing/utils/**',
|
||||
'**/testing/constants/**',
|
||||
'**/testing/cache/**',
|
||||
'**/*.test.{ts,tsx}',
|
||||
'**/*.spec.{ts,tsx}',
|
||||
'**/*.stories.{ts,tsx}',
|
||||
|
|
|
|||
Loading…
Reference in a new issue