Fix view groups update after field metadata update (#10995)
This PR fixes a difficult to reproduce bug, where a race condition appears if we go back to a table with view groups before the update field metadata logic finishes its work. In order to reproduce this bug on localhost, you'll have to simulate a slow network in your browser. The problem was that the view groups are initialized only once by useLoadRecordIndexStates, in an effect component : RecordIndexLoadBaseOnContextStoreEffect. And that this component as an internal state loadedViewId, which prevents subsequent updates of view groups by useLoadRecordIndexStates, because it considers that loading already happened, even if it's actually stale data. So instead of creating other states to burden the effect component with complex state management, the solution was to add the only needed update in a synchronous way directly in updateOneFieldMetadataItem logic. This way we are sure that our boards and tables with view groups get updated eventually, for each field metadata update, even if the requests take time. Fixes https://github.com/twentyhq/twenty/issues/10947 Fixes https://github.com/twentyhq/twenty/issues/10944
This commit is contained in:
@ -13,6 +13,11 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery';
|
||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
|
||||
import { useSetRecordGroups } from '@/object-record/record-group/hooks/useSetRecordGroups';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { useApolloMetadataClient } from './useApolloMetadataClient';
|
||||
|
||||
export const useUpdateOneFieldMetadataItem = () => {
|
||||
@ -21,6 +26,8 @@ export const useUpdateOneFieldMetadataItem = () => {
|
||||
const { refreshObjectMetadataItems } =
|
||||
useRefreshObjectMetadataItems('network-only');
|
||||
|
||||
const { setRecordGroupsFromViewGroups } = useSetRecordGroups();
|
||||
|
||||
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
|
||||
|
||||
const { findManyRecordsQuery: findManyViewsQuery } = useFindManyRecordsQuery({
|
||||
@ -70,12 +77,14 @@ export const useUpdateOneFieldMetadataItem = () => {
|
||||
},
|
||||
});
|
||||
|
||||
await refreshObjectMetadataItems();
|
||||
const objectMetadataItemsRefreshed = await refreshObjectMetadataItems();
|
||||
|
||||
const { data } = await apolloClient.query({ query: GET_CURRENT_USER });
|
||||
setCurrentWorkspace(data?.currentUser?.currentWorkspace);
|
||||
|
||||
await apolloClient.query({
|
||||
const { data: viewConnection } = await apolloClient.query<{
|
||||
views: RecordGqlConnection;
|
||||
}>({
|
||||
query: findManyViewsQuery,
|
||||
variables: {
|
||||
filter: {
|
||||
@ -87,6 +96,25 @@ export const useUpdateOneFieldMetadataItem = () => {
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
|
||||
const viewRecords = getRecordsFromRecordConnection({
|
||||
recordConnection: viewConnection?.views,
|
||||
});
|
||||
|
||||
for (const view of viewRecords) {
|
||||
const correspondingObjectMetadataItemRefreshed =
|
||||
objectMetadataItemsRefreshed?.find(
|
||||
(item) => item.id === objectMetadataId,
|
||||
);
|
||||
|
||||
if (isDefined(correspondingObjectMetadataItemRefreshed)) {
|
||||
setRecordGroupsFromViewGroups(
|
||||
view.id,
|
||||
view.viewGroups,
|
||||
correspondingObjectMetadataItemRefreshed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { OnDragEndResponder } from '@hello-pangea/dnd';
|
||||
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { useSetRecordGroups } from '@/object-record/record-group/hooks/useSetRecordGroups';
|
||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||
@ -24,6 +25,7 @@ export const useRecordGroupReorder = ({
|
||||
viewType,
|
||||
}: UseRecordGroupHandlersParams) => {
|
||||
const { setRecordGroups } = useSetRecordGroups();
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const visibleRecordGroupIdsFamilySelector = useRecoilComponentCallbackStateV2(
|
||||
visibleRecordGroupIdsComponentFamilySelector,
|
||||
@ -78,12 +80,13 @@ export const useRecordGroupReorder = ({
|
||||
];
|
||||
}, []);
|
||||
|
||||
setRecordGroups(updatedRecordGroups, viewBarId);
|
||||
setRecordGroups(updatedRecordGroups, viewBarId, objectMetadataItem.id);
|
||||
saveViewGroups(
|
||||
mapRecordGroupDefinitionsToViewGroups(updatedRecordGroups),
|
||||
);
|
||||
},
|
||||
[
|
||||
objectMetadataItem,
|
||||
saveViewGroups,
|
||||
setRecordGroups,
|
||||
viewBarId,
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
|
||||
@ -16,19 +14,13 @@ import { isDefined } from 'twenty-shared';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const useSetRecordGroups = () => {
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const setRecordGroups = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(recordGroups: RecordGroupDefinition[], recordIndexId: string) => {
|
||||
const objectMetadataItemId = snapshot
|
||||
.getLoadable(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
|
||||
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
(
|
||||
recordGroups: RecordGroupDefinition[],
|
||||
recordIndexId: string,
|
||||
objectMetadataItemId: string,
|
||||
) => {
|
||||
const objectMetadataItems = snapshot
|
||||
.getLoadable(objectMetadataItemsState)
|
||||
.getValue();
|
||||
@ -113,7 +105,11 @@ export const useSetRecordGroups = () => {
|
||||
);
|
||||
|
||||
const setRecordGroupsFromViewGroups = useCallback(
|
||||
(viewId: string, viewGroups: ViewGroup[]) => {
|
||||
(
|
||||
viewId: string,
|
||||
viewGroups: ViewGroup[],
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
) => {
|
||||
const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
|
||||
objectMetadataItem.namePlural,
|
||||
viewId,
|
||||
@ -124,9 +120,13 @@ export const useSetRecordGroups = () => {
|
||||
viewGroups,
|
||||
});
|
||||
|
||||
setRecordGroups(newGroupDefinitions, recordIndexId);
|
||||
setRecordGroups(
|
||||
newGroupDefinitions,
|
||||
recordIndexId,
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
},
|
||||
[objectMetadataItem, setRecordGroups],
|
||||
[setRecordGroups],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { useSetRecordGroups } from '@/object-record/record-group/hooks/useSetRecordGroups';
|
||||
@ -17,6 +18,8 @@ export const useHandleRecordGroupField = () => {
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const { getViewFromPrefetchState } = useGetViewFromPrefetchState();
|
||||
|
||||
const { setRecordGroupsFromViewGroups } = useSetRecordGroups();
|
||||
@ -96,7 +99,11 @@ export const useHandleRecordGroupField = () => {
|
||||
...viewGroupsToCreate,
|
||||
];
|
||||
|
||||
setRecordGroupsFromViewGroups(view.id, newViewGroupsList);
|
||||
setRecordGroupsFromViewGroups(
|
||||
view.id,
|
||||
newViewGroupsList,
|
||||
objectMetadataItem,
|
||||
);
|
||||
|
||||
if (viewGroupsToCreate.length > 0) {
|
||||
await createViewGroupRecords({ viewGroupsToCreate, viewId: view.id });
|
||||
@ -107,6 +114,7 @@ export const useHandleRecordGroupField = () => {
|
||||
}
|
||||
},
|
||||
[
|
||||
objectMetadataItem,
|
||||
currentViewIdCallbackState,
|
||||
getViewFromPrefetchState,
|
||||
setRecordGroupsFromViewGroups,
|
||||
|
||||
@ -176,7 +176,12 @@ export const useLoadRecordIndexStates = () => {
|
||||
.getValue();
|
||||
|
||||
onViewFieldsChange(view.viewFields, objectMetadataItem, recordIndexId);
|
||||
setRecordGroupsFromViewGroups(view.id, view.viewGroups);
|
||||
|
||||
setRecordGroupsFromViewGroups(
|
||||
view.id,
|
||||
view.viewGroups,
|
||||
objectMetadataItem,
|
||||
);
|
||||
|
||||
setContextStoreTargetedRecordsRuleComponentState((prev) => ({
|
||||
...prev,
|
||||
|
||||
Reference in New Issue
Block a user