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
@@ -1,6 +1,9 @@
import { sortFavorites } from '@/favorites/utils/sortFavorites';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { coreViewsState } from '@/views/states/coreViewState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRecoilValue } from 'recoil';
import { FeatureFlagKey } from '~/generated/graphql';
import { useFavoritesMetadata } from './useFavoritesMetadata';
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
import { usePrefetchedFavoritesFoldersData } from './usePrefetchedFavoritesFoldersData';
@@ -15,6 +18,13 @@ export const useFavoritesByFolder = () => {
} = useFavoritesMetadata();
const prefetchViews = useRecoilValue(prefetchViewsState);
const coreViews = useRecoilValue(coreViewsState);
const isCoreViewEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_CORE_VIEW_ENABLED,
);
const views = isCoreViewEnabled ? coreViews : prefetchViews;
const favoritesByFolder = favoriteFolders.map((folder) => ({
folderId: folder.id,
@@ -24,7 +34,7 @@ export const useFavoritesByFolder = () => {
favoriteRelationFields,
getObjectRecordIdentifierByNameSingular,
true,
prefetchViews,
views,
objectMetadataItems,
),
}));
@@ -5,14 +5,23 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { coreViewsState } from '@/views/states/coreViewState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import {
FeatureFlagKey,
FieldMetadataType,
} from '~/generated-metadata/graphql';
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
export const useWorkspaceFavorites = () => {
const { workspaceFavorites } = usePrefetchedFavoritesData();
const isCoreViewEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_CORE_VIEW_ENABLED,
);
const prefetchViews = useRecoilValue(prefetchViewsState);
const coreViews = useRecoilValue(coreViewsState);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectMetadataItem: favoriteObjectMetadataItem } =
useObjectMetadataItem({
@@ -32,6 +41,8 @@ export const useWorkspaceFavorites = () => {
[favoriteObjectMetadataItem.fields],
);
const views = isCoreViewEnabled ? coreViews : prefetchViews;
const sortedWorkspaceFavorites = useMemo(
() =>
sortFavorites(
@@ -39,14 +50,14 @@ export const useWorkspaceFavorites = () => {
favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular,
false,
prefetchViews,
views,
objectMetadataItems,
),
[
workspaceFavorites,
favoriteRelationFieldMetadataItems,
getObjectRecordIdentifierByNameSingular,
prefetchViews,
views,
objectMetadataItems,
],
);
@@ -56,7 +67,7 @@ export const useWorkspaceFavorites = () => {
);
const favoriteViewObjectMetadataIds = new Set(
prefetchViews.reduce<string[]>((acc, view) => {
views.reduce<string[]>((acc, view) => {
if (workspaceFavoriteIds.has(view.id)) {
acc.push(view.objectMetadataId);
}
@@ -1,13 +1,28 @@
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { coreViewsState } from '@/views/states/coreViewState';
import { type View } from '@/views/types/View';
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
import { extractFeatureFlagMapFromWorkspace } from '@/workspace/utils/extractFeatureFlagMapFromWorkspace';
import { selector } from 'recoil';
import { FeatureFlagKey } from '~/generated/graphql';
export const favoriteViewsWithMinimalDataSelector = selector<
Pick<View, 'id' | 'name' | 'objectMetadataId' | 'icon'>[]
>({
key: 'favoriteViewsWithMinimalDataSelector',
get: ({ get }) => {
const views = get(prefetchViewsState);
const prefetchedViews = get(prefetchViewsState);
const coreViews = get(coreViewsState);
const currentWorkspace = get(currentWorkspaceState);
const featureFlags = extractFeatureFlagMapFromWorkspace(currentWorkspace);
const isCoreViewEnabled = featureFlags[FeatureFlagKey.IS_CORE_VIEW_ENABLED];
const views = isCoreViewEnabled
? coreViews.map(convertCoreViewToView)
: prefetchedViews;
return views.map((view) => ({
id: view.id,
name: view.name,
@@ -27,10 +27,7 @@ export const sortFavorites = (
): ProcessedFavorite[] => {
return favorites
.map((favorite) => {
if (
isDefined(favorite.viewId) &&
isDefined(favorite.forWorkspaceMemberId)
) {
if (isDefined(favorite.viewId)) {
const view = views.find((view) => view.id === favorite.viewId);
if (!isDefined(view)) {
@@ -16,15 +16,17 @@ const ORDERED_STANDARD_OBJECTS = [
'note',
];
type NavigationDrawerSectionForObjectMetadataItemsProps = {
sectionTitle: string;
isRemote: boolean;
objectMetadataItems: ObjectMetadataItem[];
};
export const NavigationDrawerSectionForObjectMetadataItems = ({
sectionTitle,
isRemote,
objectMetadataItems,
}: {
sectionTitle: string;
isRemote: boolean;
objectMetadataItems: ObjectMetadataItem[];
}) => {
}: NavigationDrawerSectionForObjectMetadataItemsProps) => {
const { toggleNavigationSection, isNavigationSectionOpenState } =
useNavigationSection('Objects' + (isRemote ? 'Remote' : 'Workspace'));
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
@@ -95,8 +95,8 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
}
const previouslySelectedKanbanField = availableFieldsForKanban.find(
(fieldsForKanban) =>
// TODO: replace with viewGroups.fieldMetadataId
fieldsForKanban.id === currentView.kanbanFieldMetadataId,
fieldsForKanban.id ===
currentView.viewGroups[0].fieldMetadataId,
);
const kanbanField = isDefined(previouslySelectedKanbanField)
@@ -104,8 +104,8 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
: availableFieldsForKanban[0];
if (!isDefined(previouslySelectedKanbanField)) {
// TODO: replace with viewGroups.fieldMetadataId
updateCurrentViewParams.kanbanFieldMetadataId = kanbanField.id;
updateCurrentViewParams.kanbanFieldMetadataId =
currentView.viewGroups[0].fieldMetadataId;
}
const hasViewGroups = currentView.viewGroups.some(
@@ -180,8 +180,7 @@ export const useLoadRecordIndexStates = () => {
setRecordIndexViewType(view.type);
setRecordIndexOpenRecordIn(view.openRecordIn);
setRecordIndexViewKanbanFieldMetadataIdState(
// TODO: replace with viewGroups.fieldMetadataId
view.kanbanFieldMetadataId,
view.viewGroups?.[0]?.fieldMetadataId,
);
const kanbanAggregateOperationFieldMetadataType =
objectMetadataItem.fields?.find(
@@ -1,6 +1,7 @@
import styled from '@emotion/styled';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@@ -15,6 +16,7 @@ import { useAreViewFiltersDifferentFromRecordFilters } from '@/views/hooks/useAr
import { useAreViewSortsDifferentFromRecordSorts } from '@/views/hooks/useAreViewSortsDifferentFromRecordSorts';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import { useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter } from '@/views/hooks/useIsViewAnyFieldFilterDifferentFromCurrentAnyFieldFilter';
import { useRefreshCoreViews } from '@/views/hooks/useRefreshCoreViews';
import { useSaveCurrentViewFiltersAndSorts } from '@/views/hooks/useSaveCurrentViewFiltersAndSorts';
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
@@ -34,6 +36,10 @@ const StyledContainer = styled.div`
export const UpdateViewButtonGroup = () => {
const { saveCurrentViewFilterAndSorts } = useSaveCurrentViewFiltersAndSorts();
const { refreshCoreViews } = useRefreshCoreViews();
const { objectMetadataItem } = useRecordIndexContextOrThrow();
const { setViewPickerMode } = useViewPickerMode();
const currentViewId = useRecoilComponentValue(
@@ -72,6 +78,7 @@ export const UpdateViewButtonGroup = () => {
const handleUpdateViewClick = async () => {
await saveCurrentViewFilterAndSorts();
await refreshCoreViews(objectMetadataItem.id);
};
const { hasFiltersQueryParams } = useViewFromQueryParams();
@@ -9,7 +9,5 @@ export const VIEW_FIELD_FRAGMENT = gql`
position
size
aggregateOperation
createdAt
updatedAt
}
`;
@@ -10,7 +10,5 @@ export const VIEW_FILTER_FRAGMENT = gql`
positionInViewFilterGroup
subFieldName
viewId
createdAt
updatedAt
}
`;
@@ -7,7 +7,5 @@ export const VIEW_FILTER_GROUP_FRAGMENT = gql`
logicalOperator
positionInViewFilterGroup
viewId
createdAt
updatedAt
}
`;
@@ -24,8 +24,6 @@ export const VIEW_FRAGMENT = gql`
openRecordIn
kanbanAggregateOperation
kanbanAggregateOperationFieldMetadataId
createdAt
updatedAt
anyFieldFilterValue
viewFields {
...ViewFieldFragment
@@ -8,7 +8,5 @@ export const VIEW_GROUP_FRAGMENT = gql`
fieldValue
position
viewId
createdAt
updatedAt
}
`;
@@ -6,7 +6,5 @@ export const VIEW_SORT_FRAGMENT = gql`
fieldMetadataId
direction
viewId
createdAt
updatedAt
}
`;
@@ -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 {
@@ -0,0 +1,41 @@
import { selectorFamily } from 'recoil';
import { coreViewsState } from '@/views/states/coreViewState';
import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations';
export const coreViewsByObjectMetadataIdFamilySelector = selectorFamily<
CoreViewWithRelations[],
string
>({
key: 'coreViewsByObjectMetadataIdFamilySelector',
get:
(objectMetadataId: string) =>
({ get }) => {
const coreViews = get(coreViewsState);
return coreViews.filter(
(view) => view.objectMetadataId === objectMetadataId,
);
},
set:
(objectMetadataId: string) =>
({ get, set }, newViewsForObject) => {
const currentCoreViews = get(coreViewsState);
const viewsFromOtherObjects = currentCoreViews.filter(
(view) => view.objectMetadataId !== objectMetadataId,
);
let updatedViewsForObject: CoreViewWithRelations[];
if (Array.isArray(newViewsForObject)) {
updatedViewsForObject = newViewsForObject;
} else {
updatedViewsForObject = [];
}
const newCoreViews = [...viewsFromOtherObjects, ...updatedViewsForObject];
set(coreViewsState, newCoreViews);
},
});
@@ -17,11 +17,17 @@ export type CoreViewWithRelations = {
key?: ViewKey | null;
objectMetadataId: string;
isCompact: boolean;
viewFields: Omit<CoreViewField, 'workspaceId'>[];
viewGroups: Omit<CoreViewGroup, 'workspaceId'>[];
viewFilters: Omit<CoreViewFilter, 'workspaceId'>[];
viewFilterGroups?: Omit<CoreViewFilterGroup, 'workspaceId'>[];
viewSorts: Omit<CoreViewSort, 'workspaceId'>[];
viewFields: Omit<CoreViewField, 'workspaceId' | 'createdAt' | 'updatedAt'>[];
viewGroups: Omit<CoreViewGroup, 'workspaceId' | 'createdAt' | 'updatedAt'>[];
viewFilters: Omit<
CoreViewFilter,
'workspaceId' | 'createdAt' | 'updatedAt'
>[];
viewFilterGroups?: Omit<
CoreViewFilterGroup,
'workspaceId' | 'createdAt' | 'updatedAt'
>[];
viewSorts: Omit<CoreViewSort, 'workspaceId' | 'createdAt' | 'updatedAt'>[];
kanbanAggregateOperation?: AggregateOperations | null;
kanbanAggregateOperationFieldMetadataId?: string | null;
position: number;
@@ -4,7 +4,15 @@ import { type ViewField } from '@/views/types/ViewField';
import { type CoreViewField } from '~/generated/graphql';
export const convertCoreViewFieldToViewField = (
coreViewField: Omit<CoreViewField, 'workspaceId'>,
coreViewField: Pick<
CoreViewField,
| 'id'
| 'fieldMetadataId'
| 'position'
| 'isVisible'
| 'size'
| 'aggregateOperation'
>,
): ViewField => {
const viewField: ViewField = {
__typename: 'ViewField',
@@ -3,7 +3,14 @@ import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLog
import { type CoreViewFilterGroup } from '~/generated/graphql';
export const convertCoreViewFilterGroupToViewFilterGroup = (
coreViewFilterGroup: Omit<CoreViewFilterGroup, 'workspaceId'>,
coreViewFilterGroup: Pick<
CoreViewFilterGroup,
| 'id'
| 'viewId'
| 'parentViewFilterGroupId'
| 'logicalOperator'
| 'positionInViewFilterGroup'
>,
): ViewFilterGroup => {
return {
__typename: 'ViewFilterGroup',
@@ -1,9 +1,19 @@
import { type CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
import { type ViewFilter } from '@/views/types/ViewFilter';
import { type CoreViewFilter } from '~/generated/graphql';
import { convertViewFilterOperandFromCore } from '../utils/convertViewFilterOperandFromCore';
export const convertCoreViewFilterToViewFilter = (
coreViewFilter: Omit<CoreViewFilter, 'workspaceId'>,
coreViewFilter: Pick<
CoreViewFilter,
| 'id'
| 'fieldMetadataId'
| 'operand'
| 'value'
| 'viewFilterGroupId'
| 'positionInViewFilterGroup'
| 'subFieldName'
>,
): ViewFilter => {
return {
__typename: 'ViewFilter',
@@ -18,11 +28,8 @@ export const convertCoreViewFilterToViewFilter = (
typeof coreViewFilter.value === 'string'
? coreViewFilter.value
: JSON.stringify(coreViewFilter.value ?? ''),
createdAt: coreViewFilter.createdAt,
updatedAt: coreViewFilter.updatedAt,
viewId: coreViewFilter.viewId,
viewFilterGroupId: coreViewFilter.viewFilterGroupId ?? undefined,
positionInViewFilterGroup: coreViewFilter.positionInViewFilterGroup ?? null,
subFieldName: (coreViewFilter.subFieldName as any) ?? null,
viewFilterGroupId: coreViewFilter.viewFilterGroupId,
positionInViewFilterGroup: coreViewFilter.positionInViewFilterGroup,
subFieldName: coreViewFilter.subFieldName as CompositeFieldSubFieldName,
};
};
@@ -2,7 +2,10 @@ import { type ViewGroup } from '@/views/types/ViewGroup';
import { type CoreViewGroup } from '~/generated/graphql';
export const convertCoreViewGroupToViewGroup = (
coreViewGroup: Omit<CoreViewGroup, 'workspaceId'>,
coreViewGroup: Pick<
CoreViewGroup,
'id' | 'fieldMetadataId' | 'isVisible' | 'fieldValue' | 'position'
>,
): ViewGroup => {
return {
__typename: 'ViewGroup',
@@ -2,7 +2,7 @@ import { type ViewSort } from '@/views/types/ViewSort';
import { type CoreViewSort, ViewSortDirection } from '~/generated/graphql';
export const convertCoreViewSortToViewSort = (
coreViewSort: Omit<CoreViewSort, 'workspaceId'>,
coreViewSort: Pick<CoreViewSort, 'id' | 'fieldMetadataId' | 'direction'>,
): ViewSort => {
return {
__typename: 'ViewSort',
@@ -6,7 +6,7 @@ import { isDefined } from 'twenty-shared/utils';
import { type UpdateViewInput } from '~/generated-metadata/graphql';
export const convertUpdateViewInputToCore = (
view: Partial<GraphQLView>,
view: Partial<GraphQLView & { __typename?: string }>,
): UpdateViewInput => {
const {
key,
@@ -17,6 +17,9 @@ export const convertUpdateViewInputToCore = (
viewFilterGroups: _viewFilterGroups,
viewGroups: _viewGroups,
viewSorts: _viewSorts,
kanbanFieldMetadataId: _kanbanFieldMetadataId,
id: _id,
__typename: _typename,
...rest
} = view;
@@ -1,6 +1,6 @@
import { type ViewFilter } from '@/views/types/ViewFilter';
import { compareStrictlyExceptForNullAndUndefined } from '~/utils/compareStrictlyExceptForNullAndUndefined';
import { isDefined } from 'twenty-shared/utils';
import { compareStrictlyExceptForNullAndUndefined } from '~/utils/compareStrictlyExceptForNullAndUndefined';
export const getViewFiltersToCreate = (
currentViewFilters: ViewFilter[],
@@ -1,7 +1,7 @@
import { type ViewFilter } from '@/views/types/ViewFilter';
import { areViewFiltersEqual } from '@/views/utils/areViewFiltersEqual';
import { compareStrictlyExceptForNullAndUndefined } from '~/utils/compareStrictlyExceptForNullAndUndefined';
import { isDefined } from 'twenty-shared/utils';
import { compareStrictlyExceptForNullAndUndefined } from '~/utils/compareStrictlyExceptForNullAndUndefined';
export const getViewFiltersToUpdate = (
currentViewFilters: ViewFilter[],