Various fixes on core views (#14093)

- Fix fragments
- Add id to create inputs
- Refresh core views
- Fix kanban loading
- Fix core views creation from current view

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Raphaël Bosi
2025-08-29 11:20:56 +02:00
committed by GitHub
parent 0e6ce123c9
commit 0bdd1c4803
56 changed files with 504 additions and 341 deletions
@@ -100,7 +100,7 @@ export const usePersistViewFieldRecords = () => {
apolloCoreClient.mutate({
mutation: updateOneRecordMutation,
variables: {
idToUpdate: viewField.id,
id: viewField.id,
input: {
isVisible: viewField.isVisible,
position: viewField.position,
@@ -151,6 +151,7 @@ export const usePersistViewFieldRecords = () => {
mutation: CREATE_CORE_VIEW_FIELD,
variables: {
input: {
id: viewField.id,
fieldMetadataId: viewField.fieldMetadataId,
viewId: view.id,
isVisible: viewField.isVisible,
@@ -191,7 +192,7 @@ export const usePersistViewFieldRecords = () => {
apolloClient.mutate({
mutation: UPDATE_CORE_VIEW_FIELD,
variables: {
idToUpdate: viewField.id,
id: viewField.id,
input: {
isVisible: viewField.isVisible,
position: viewField.position,
@@ -146,7 +146,7 @@ export const usePersistViewFilterGroupRecords = () => {
apolloCoreClient.mutate<{ updateViewFilterGroup: ViewFilterGroup }>({
mutation: updateOneRecordMutation,
variables: {
idToUpdate: viewFilterGroup.id,
id: viewFilterGroup.id,
input: {
parentViewFilterGroupId:
viewFilterGroup.parentViewFilterGroupId,
@@ -194,7 +194,7 @@ export const usePersistViewFilterGroupRecords = () => {
apolloCoreClient.mutate<{ destroyViewFilterGroup: ViewFilterGroup }>({
mutation: destroyOneRecordMutation,
variables: {
idToDestroy: viewFilterGroupId,
id: viewFilterGroupId,
},
update: (cache, { data }) => {
const record = data?.destroyViewFilterGroup;
@@ -231,6 +231,7 @@ export const usePersistViewFilterGroupRecords = () => {
mutation: CREATE_CORE_VIEW_FILTER_GROUP,
variables: {
input: {
id: viewFilterGroup.id,
viewId: view.id,
parentViewFilterGroupId: viewFilterGroup.parentViewFilterGroupId,
logicalOperator: viewFilterGroup.logicalOperator,
@@ -315,7 +316,7 @@ export const usePersistViewFilterGroupRecords = () => {
apolloClient.mutate<{ updateCoreViewFilterGroup: ViewFilterGroup }>({
mutation: UPDATE_CORE_VIEW_FILTER_GROUP,
variables: {
idToUpdate: viewFilterGroup.id,
id: viewFilterGroup.id,
input: {
parentViewFilterGroupId:
viewFilterGroup.parentViewFilterGroupId,
@@ -357,7 +358,7 @@ export const usePersistViewFilterGroupRecords = () => {
apolloClient.mutate<{ destroyCoreViewFilterGroup: ViewFilterGroup }>({
mutation: DESTROY_CORE_VIEW_FILTER_GROUP,
variables: {
idToDestroy: viewFilterGroupId,
id: viewFilterGroupId,
},
update: (cache, { data }) => {
const record = data?.destroyCoreViewFilterGroup;
@@ -106,7 +106,7 @@ export const usePersistViewFilterRecords = () => {
apolloCoreClient.mutate({
mutation: updateOneRecordMutation,
variables: {
idToUpdate: viewFilter.id,
id: viewFilter.id,
input: {
value: viewFilter.value,
displayValue: viewFilter.displayValue,
@@ -155,7 +155,7 @@ export const usePersistViewFilterRecords = () => {
apolloCoreClient.mutate({
mutation: destroyOneRecordMutation,
variables: {
idToDestroy: viewFilterId,
id: viewFilterId,
},
update: (cache, { data }) => {
const record = data?.['destroyViewFilter'];
@@ -197,6 +197,7 @@ export const usePersistViewFilterRecords = () => {
mutation: CREATE_CORE_VIEW_FILTER,
variables: {
input: {
id: viewFilter.id,
fieldMetadataId: viewFilter.fieldMetadataId,
viewId: view.id,
value: viewFilter.value,
@@ -238,7 +239,7 @@ export const usePersistViewFilterRecords = () => {
apolloClient.mutate({
mutation: UPDATE_CORE_VIEW_FILTER,
variables: {
idToUpdate: viewFilter.id,
id: viewFilter.id,
input: {
value: viewFilter.value,
operand: convertViewFilterOperandToCore(viewFilter.operand),
@@ -280,7 +281,7 @@ export const usePersistViewFilterRecords = () => {
apolloClient.mutate({
mutation: DESTROY_CORE_VIEW_FILTER,
variables: {
idToDestroy: viewFilterId,
id: viewFilterId,
},
update: (cache, { data }) => {
const record = data?.['destroyCoreViewFilter'];
@@ -60,7 +60,7 @@ export const usePersistViewGroupRecords = () => {
apolloCoreClient.mutate<{ updateViewGroup: ViewGroup }>({
mutation: updateOneRecordMutation,
variables: {
idToUpdate: viewGroup.id,
id: viewGroup.id,
input: {
isVisible: viewGroup.isVisible,
position: viewGroup.position,
@@ -119,7 +119,10 @@ export const usePersistViewGroupRecords = () => {
mutation: CREATE_CORE_VIEW_GROUP,
variables: {
input: {
id: viewGroup.id,
viewId,
fieldMetadataId: viewGroup.fieldMetadataId,
fieldValue: viewGroup.fieldValue,
isVisible: viewGroup.isVisible,
position: viewGroup.position,
},
@@ -183,7 +186,7 @@ export const usePersistViewGroupRecords = () => {
apolloClient.mutate({
mutation: DESTROY_CORE_VIEW_GROUP,
variables: {
idToDestroy: viewGroup.id,
id: viewGroup.id,
},
}),
),
@@ -100,7 +100,7 @@ export const usePersistViewSortRecords = () => {
apolloCoreClient.mutate({
mutation: updateOneRecordMutation,
variables: {
idToUpdate: viewSort.id,
id: viewSort.id,
input: {
direction: viewSort.direction,
},
@@ -144,7 +144,7 @@ export const usePersistViewSortRecords = () => {
apolloCoreClient.mutate({
mutation: destroyOneRecordMutation,
variables: {
idToDestroy: viewSortId,
id: viewSortId,
},
update: (cache, { data }) => {
const record = data?.['destroyViewSort'];
@@ -185,6 +185,7 @@ export const usePersistViewSortRecords = () => {
mutation: CREATE_CORE_VIEW_SORT,
variables: {
input: {
id: viewSort.id,
fieldMetadataId: viewSort.fieldMetadataId,
viewId: view.id,
direction: convertViewSortDirectionToCore(viewSort.direction),
@@ -222,7 +223,7 @@ export const usePersistViewSortRecords = () => {
apolloClient.mutate({
mutation: UPDATE_CORE_VIEW_SORT,
variables: {
idToUpdate: viewSort.id,
id: viewSort.id,
input: {
direction: convertViewSortDirectionToCore(viewSort.direction),
} satisfies Partial<CoreViewSort>,
@@ -260,7 +261,7 @@ export const usePersistViewSortRecords = () => {
apolloClient.mutate({
mutation: DESTROY_CORE_VIEW_SORT,
variables: {
idToDestroy: viewSortId,
id: viewSortId,
},
update: (cache, { data }) => {
const record = data?.['destroyCoreViewSort'];
@@ -16,7 +16,7 @@ import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePers
import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilterRecords';
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroupRecords';
import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSortRecords';
import { coreViewsState } from '@/views/states/coreViewState';
import { useRefreshCoreViews } from '@/views/hooks/useRefreshCoreViews';
import { isPersistingViewFieldsState } from '@/views/states/isPersistingViewFieldsState';
import { type GraphQLView } from '@/views/types/GraphQLView';
import { type View } from '@/views/types/View';
@@ -33,12 +33,7 @@ import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import {
FeatureFlagKey,
useCreateCoreViewMutation,
useFindManyCoreViewsLazyQuery,
} from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { FeatureFlagKey, useCreateCoreViewMutation } from '~/generated/graphql';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
@@ -70,7 +65,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
const { objectMetadataItem } = useRecordIndexContextOrThrow();
const [findManyCoreViewsLazy] = useFindManyCoreViewsLazyQuery();
const { refreshCoreViews } = useRefreshCoreViews();
const { findManyRecordsLazy } = useLazyFindManyRecords({
objectNameSingular: CoreObjectNameSingular.View,
@@ -135,6 +130,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
const result = await createCoreViewMutation({
variables: {
input: {
id: id ?? v4(),
name: name ?? sourceView.name,
icon: icon ?? sourceView.icon,
key: null,
@@ -258,23 +254,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
}
if (isCoreViewEnabled) {
const result = await findManyCoreViewsLazy({
variables: {
objectMetadataId: objectMetadataItem.id,
},
fetchPolicy: 'network-only',
});
const existingCoreViews = snapshot
.getLoadable(coreViewsState)
.getValue();
if (
isDefined(result.data?.getCoreViews) &&
!isDeeplyEqual(existingCoreViews, result.data.getCoreViews)
) {
set(coreViewsState, result.data.getCoreViews);
}
await refreshCoreViews(objectMetadataItem.id);
} else {
await findManyRecordsLazy();
}
@@ -283,23 +263,23 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
return newViewId;
},
[
anyFieldFilterValue,
currentViewIdCallbackState,
createOneRecord,
isCoreViewEnabled,
createViewFieldRecords,
findManyRecordsLazy,
createCoreViewMutation,
anyFieldFilterValue,
createOneRecord,
objectMetadataItem.fields,
objectMetadataItem.id,
createViewGroupRecords,
createViewSortRecords,
createViewFilterRecords,
createViewFilterGroupRecords,
currentRecordFilterGroups,
currentRecordFilters,
currentRecordSorts,
currentRecordFilterGroups,
isCoreViewEnabled,
createCoreViewMutation,
findManyCoreViewsLazy,
objectMetadataItem.id,
createViewFilterGroupRecords,
createViewFilterRecords,
createViewSortRecords,
refreshCoreViews,
findManyRecordsLazy,
],
);
@@ -0,0 +1,42 @@
import { coreViewsByObjectMetadataIdFamilySelector } from '@/views/states/coreViewsByObjectMetadataIdFamilySelector';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { useFindManyCoreViewsLazyQuery } from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useRefreshCoreViews = () => {
const [findManyCoreViewsLazy] = useFindManyCoreViewsLazyQuery();
const refreshCoreViews = useRecoilCallback(
({ snapshot, set }) =>
async (objectMetadataId: string) => {
const result = await findManyCoreViewsLazy({
variables: {
objectMetadataId,
},
fetchPolicy: 'network-only',
});
const coreViewsForObjectMetadataId = snapshot
.getLoadable(
coreViewsByObjectMetadataIdFamilySelector(objectMetadataId),
)
.getValue();
if (
isDefined(result.data?.getCoreViews) &&
!isDeeplyEqual(coreViewsForObjectMetadataId, result.data.getCoreViews)
) {
set(
coreViewsByObjectMetadataIdFamilySelector(objectMetadataId),
result.data.getCoreViews,
);
}
},
[findManyCoreViewsLazy],
);
return {
refreshCoreViews,
};
};
@@ -33,7 +33,7 @@ export const useSaveCurrentViewFields = () => {
set(isPersistingViewFieldsState, true);
const view = await getViewFromPrefetchState(currentViewId);
const view = getViewFromPrefetchState(currentViewId);
if (isUndefinedOrNull(view)) {
return;
@@ -71,11 +71,11 @@ export const useSaveRecordFilterGroupsToViewFilterGroups = () => {
await deleteViewFilterGroupRecords(viewFilterGroupIdsToDelete);
},
[
createViewFilterGroupRecords,
deleteViewFilterGroupRecords,
updateViewFilterGroupRecords,
currentRecordFilterGroupsCallbackState,
currentView,
currentRecordFilterGroupsCallbackState,
createViewFilterGroupRecords,
updateViewFilterGroupRecords,
deleteViewFilterGroupRecords,
],
);
@@ -65,11 +65,11 @@ export const useSaveRecordFiltersToViewFilters = () => {
await deleteViewFilterRecords(viewFilterIdsToDelete);
},
[
createViewFilterRecords,
deleteViewFilterRecords,
updateViewFilterRecords,
currentRecordFiltersCallbackState,
currentView,
currentRecordFiltersCallbackState,
createViewFilterRecords,
updateViewFilterRecords,
deleteViewFilterRecords,
],
);
@@ -63,11 +63,11 @@ export const useSaveRecordSortsToViewSorts = () => {
await deleteViewSortRecords(viewSortIdsToDelete);
},
[
createViewSortRecords,
deleteViewSortRecords,
updateViewSortRecords,
currentRecordSortsCallbackState,
currentView,
currentRecordSortsCallbackState,
createViewSortRecords,
updateViewSortRecords,
deleteViewSortRecords,
],
);
@@ -1,11 +1,13 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { type GraphQLView } from '@/views/types/GraphQLView';
import { convertUpdateViewInputToCore } from '@/views/utils/convertUpdateViewInputToCore';
import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey, useUpdateCoreViewMutation } from '~/generated/graphql';
import { useRefreshCoreViews } from './useRefreshCoreViews';
export const useUpdateView = () => {
const featureFlagMap = useFeatureFlagsMap();
@@ -17,25 +19,39 @@ export const useUpdateView = () => {
const [updateOneCoreView] = useUpdateCoreViewMutation();
const { refreshCoreViews } = useRefreshCoreViews();
const { objectMetadataItem } = useRecordIndexContextOrThrow();
const updateView = useRecoilCallback(
() => async (view: Partial<GraphQLView>) => {
if (isDefined(view.id)) {
if (isCoreViewEnabled) {
await updateOneCoreView({
variables: {
id: view.id,
input: convertUpdateViewInputToCore(view),
},
});
} else {
await updateOneRecord({
idToUpdate: view.id,
updateOneRecordInput: view,
});
}
if (!isDefined(view.id)) {
return;
}
if (isCoreViewEnabled) {
await updateOneCoreView({
variables: {
id: view.id,
input: convertUpdateViewInputToCore(view),
},
});
await refreshCoreViews(objectMetadataItem.id);
} else {
await updateOneRecord({
idToUpdate: view.id,
updateOneRecordInput: view,
});
}
},
[isCoreViewEnabled, updateOneCoreView, updateOneRecord],
[
isCoreViewEnabled,
objectMetadataItem.id,
refreshCoreViews,
updateOneCoreView,
updateOneRecord,
],
);
return {