Various SDK improvements (#18115)
## Summary - **Refactor frontend metadata loading architecture**: Split the monolithic `EagerMetadataLoadEffect` into focused provider effects (`UserMetadataProviderEffect`, `ObjectMetadataProviderEffect`, `ViewMetadataProviderEffect`) orchestrated by `MetadataProviderEffects`. Replaced `UserProvider` + `ObjectMetadataItemsProvider` with a single `MetadataGater` that gates rendering on `isAppMetadataReadyState`. The metadata store now validates view-object consistency before promoting views, and `updateDraft` skips no-op updates via deep equality checks. - **SDK CLI improvements**: Added `app:typecheck` command, improved error handling in API sync (extracts GraphQL error messages), added `serializeError` utility for human-readable error output, added `error` file status to dev mode orchestrator with UI support, and fixed ClickHouse migration/seed commands to use `transpile-only`.
This commit is contained in:
-45
@@ -1,45 +0,0 @@
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const ObjectMetadataItemsLoadEffect = () => {
|
||||
const currentUser = useRecoilValueV2(currentUserState);
|
||||
const currentWorkspace = useRecoilValueV2(currentWorkspaceState);
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems();
|
||||
const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems();
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loadObjectMetadata = async () => {
|
||||
if (
|
||||
isUndefinedOrNull(currentUser) ||
|
||||
!isWorkspaceActiveOrSuspended(currentWorkspace)
|
||||
) {
|
||||
await loadMockedObjectMetadataItems();
|
||||
} else {
|
||||
await refreshObjectMetadataItems();
|
||||
}
|
||||
setIsInitialized(true);
|
||||
};
|
||||
|
||||
loadObjectMetadata();
|
||||
}, [
|
||||
currentUser,
|
||||
currentWorkspace,
|
||||
loadMockedObjectMetadataItems,
|
||||
refreshObjectMetadataItems,
|
||||
isInitialized,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PreComputedChipGeneratorsProvider } from '@/object-metadata/components/PreComputedChipGeneratorsProvider';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader';
|
||||
|
||||
export const ObjectMetadataItemsProvider = ({
|
||||
children,
|
||||
}: React.PropsWithChildren) => {
|
||||
const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState);
|
||||
|
||||
const shouldAppBeLoading = useRecoilValueV2(shouldAppBeLoadingState);
|
||||
|
||||
const shouldDisplayChildren =
|
||||
!shouldAppBeLoading && objectMetadataItems.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{shouldDisplayChildren ? (
|
||||
<PreComputedChipGeneratorsProvider>
|
||||
{children}
|
||||
</PreComputedChipGeneratorsProvider>
|
||||
) : (
|
||||
<UserOrMetadataLoader />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+16
-11
@@ -8,18 +8,11 @@ import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-met
|
||||
import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector';
|
||||
import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useColumnDefinitionsFromObjectMetadata = (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
) => {
|
||||
const activeFieldMetadataItems = objectMetadataItem.readableFields.filter(
|
||||
(field) =>
|
||||
field.isActive &&
|
||||
// Allow label identifier field (e.g. id for junction tables) even if it's a hidden system field
|
||||
(!isHiddenSystemField(field) ||
|
||||
field.id === objectMetadataItem.labelIdentifierFieldMetadataId),
|
||||
);
|
||||
|
||||
const filterableFieldMetadataItems = useFamilySelectorValueV2(
|
||||
availableFieldMetadataItemsForFilterFamilySelector,
|
||||
{
|
||||
@@ -34,10 +27,16 @@ export const useColumnDefinitionsFromObjectMetadata = (
|
||||
},
|
||||
);
|
||||
|
||||
const restrictedFieldMetadataIds: string[] = [];
|
||||
const columnDefinitions: ColumnDefinition<FieldMetadata>[] = useMemo(() => {
|
||||
const activeFieldMetadataItems =
|
||||
objectMetadataItem.readableFields.filter(
|
||||
(field) =>
|
||||
field.isActive &&
|
||||
(!isHiddenSystemField(field) ||
|
||||
field.id === objectMetadataItem.labelIdentifierFieldMetadataId),
|
||||
);
|
||||
|
||||
const columnDefinitions: ColumnDefinition<FieldMetadata>[] =
|
||||
activeFieldMetadataItems
|
||||
return activeFieldMetadataItems
|
||||
.map((field, index) =>
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
position: index,
|
||||
@@ -47,6 +46,7 @@ export const useColumnDefinitionsFromObjectMetadata = (
|
||||
)
|
||||
.filter(filterAvailableTableColumns)
|
||||
.filter((column) => {
|
||||
const restrictedFieldMetadataIds: string[] = [];
|
||||
return !restrictedFieldMetadataIds.includes(column.fieldMetadataId);
|
||||
})
|
||||
.map((column) => {
|
||||
@@ -65,6 +65,11 @@ export const useColumnDefinitionsFromObjectMetadata = (
|
||||
isSortable: existsInSortDefinitions,
|
||||
};
|
||||
});
|
||||
}, [
|
||||
filterableFieldMetadataItems,
|
||||
sortableFieldMetadataItems,
|
||||
objectMetadataItem,
|
||||
]);
|
||||
|
||||
return {
|
||||
columnDefinitions,
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
|
||||
|
||||
export const shouldAppBeLoadingState = createStateV2<boolean>({
|
||||
key: 'shouldAppBeLoadingState',
|
||||
defaultValue: false,
|
||||
});
|
||||
Reference in New Issue
Block a user