breaking (soft) - Migrate viewGroup.fieldMetadataId -> view.mainGroupByFieldMetadataId (2/3) (#16277)

Should be merged once https://github.com/twentyhq/twenty/pull/16206 has
been released + command run to prod

In this PR
- Remove usage of viewGroup.fieldMetadataId, both in BE and FE states. 
- But we still need to properly populate it until we fully remove
viewGroup.fieldMetadataId from db and ORM entity (upcoming 3rd PR out of
3). fieldMetadataId was removed from CoreViewGroup type and
CreateViewGroupInput and is determined BE-side based on the associated
view's mainGroupByFieldMetadataId. **I expect this means a downtime on
viewGroup creation, until both FE and BE are deployed and cache is
flushed.** This seems acceptable to me as it only regards viewGroup
creation.
    - this information is replaced by view.mainGroupByFieldMetadataID
- Handle view group creation, update and deletion in the BE as a
side-effect of a view creation, update or deletion. Optimistic effects
are still used
- Add validation at view creation or update regarding
mainGroupByFieldMetadata

Left to do in 3rd PR
- Remove viewGroup.fieldMetadataId from db and ORM entity
- Restore feature allowing to update an existing grouped view's group by
field (already OK on BE side but need to rebuild FE optimistic)
This commit is contained in:
Marie
2025-12-05 15:00:03 +01:00
committed by GitHub
parent 17d88a074b
commit 7ce22d5c7e
87 changed files with 1338 additions and 1067 deletions
@@ -42,7 +42,6 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
({
id: v4(),
__typename: 'ViewGroup',
fieldMetadataId: randomFieldForKanban,
fieldValue: option.value,
isVisible: true,
position: index,
@@ -55,14 +54,15 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
fieldValue: '',
position: viewGroupsToCreate.length,
isVisible: true,
fieldMetadataId: randomFieldForKanban,
} satisfies ViewGroup);
await createViewGroups({
inputs: viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
...viewGroup,
viewId: currentViewId,
})),
createCoreViewGroupInputs: {
inputs: viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
...viewGroup,
viewId: currentViewId,
})),
},
});
return viewGroupsToCreate;
@@ -8,12 +8,14 @@ import { originalDragSelectionComponentState } from '@/object-record/record-drag
import { processGroupDrop } from '@/object-record/record-drag/utils/processGroupDrop';
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { RECORD_INDEX_REMOVE_SORTING_MODAL_ID } from '@/object-record/record-index/constants/RecordIndexRemoveSortingModalId';
import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupFieldMetadataComponentState';
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { selectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsComponentSelector';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
export const useProcessTableWithGroupRecordDrop = () => {
@@ -47,6 +49,10 @@ export const useProcessTableWithGroupRecordDrop = () => {
originalDragSelectionComponentState,
);
const groupFieldMetadata = useRecoilComponentValue(
recordIndexGroupFieldMetadataItemComponentState,
);
const processTableWithGroupRecordDrop = useRecoilCallback(
({ snapshot }) =>
(result: DropResult) => {
@@ -63,7 +69,7 @@ export const useProcessTableWithGroupRecordDrop = () => {
}
const fieldMetadata = objectMetadataItem.fields.find(
(field) => field.id === destinationRecordGroup.fieldMetadataId,
(field) => field.id === groupFieldMetadata?.id,
);
if (!isDefined(fieldMetadata)) {
@@ -110,14 +116,15 @@ export const useProcessTableWithGroupRecordDrop = () => {
});
},
[
currentRecordSortsCallbackState,
objectMetadataItem.fields,
recordIdsByGroupFamilyState,
updateOneRow,
openModal,
selectedRowIdsSelector,
originalDragSelectionCallbackState,
isDraggingRecordCallbackState,
selectedRowIdsSelector,
currentRecordSortsCallbackState,
recordIdsByGroupFamilyState,
groupFieldMetadata?.id,
openModal,
updateOneRow,
],
);
@@ -1,21 +1,25 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupFieldMetadataComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useMemo } from 'react';
import { isDefined } from 'twenty-shared/utils';
export const useRecordGroupFilter = (fields: FieldMetadataItem[]) => {
const currentRecordGroupDefinition = useCurrentRecordGroupDefinition();
const groupFieldMetadata = useRecoilComponentValue(
recordIndexGroupFieldMetadataItemComponentState,
);
const recordGroupFilter = useMemo(() => {
if (isDefined(currentRecordGroupDefinition)) {
const fieldMetadataItem = fields.find(
(fieldMetadataItem) =>
fieldMetadataItem.id === currentRecordGroupDefinition.fieldMetadataId,
(fieldMetadataItem) => fieldMetadataItem.id === groupFieldMetadata?.id,
);
if (!fieldMetadataItem) {
throw new Error(
`Field metadata item with id ${currentRecordGroupDefinition.fieldMetadataId} not found`,
`Field metadata item with id ${groupFieldMetadata?.id} not found`,
);
}
@@ -31,7 +35,7 @@ export const useRecordGroupFilter = (fields: FieldMetadataItem[]) => {
}
return {};
}, [currentRecordGroupDefinition, fields]);
}, [currentRecordGroupDefinition, fields, groupFieldMetadata?.id]);
return { recordGroupFilter };
};
@@ -3,7 +3,9 @@ import { useSetRecordGroups } from '@/object-record/record-group/hooks/useSetRec
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
import { type RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupFieldMetadataComponentState';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useSaveCurrentViewGroups } from '@/views/hooks/useSaveCurrentViewGroups';
import { type ViewType } from '@/views/types/ViewType';
@@ -37,6 +39,10 @@ export const useReorderRecordGroups = ({
const { saveViewGroups } = useSaveCurrentViewGroups();
const groupFieldMetadata = useRecoilComponentValue(
recordIndexGroupFieldMetadataItemComponentState,
);
const reorderRecordGroups = useRecoilCallback(
({ snapshot }) =>
({ fromIndex, toIndex }: ReorderRecordGroupsParams) => {
@@ -80,17 +86,23 @@ export const useReorderRecordGroups = ({
];
}, []);
setRecordGroups(
updatedRecordGroups,
if (!isDefined(groupFieldMetadata?.id)) {
throw new Error('mainGroupByFieldMetadataId is required');
}
setRecordGroups({
mainGroupByFieldMetadataId: groupFieldMetadata?.id,
recordGroups: updatedRecordGroups,
recordIndexId,
objectMetadataItem.id,
);
objectMetadataItemId: objectMetadataItem.id,
});
saveViewGroups(
mapRecordGroupDefinitionsToViewGroups(updatedRecordGroups),
);
},
[
objectMetadataItem.id,
groupFieldMetadata?.id,
recordIndexId,
saveViewGroups,
setRecordGroups,
@@ -16,11 +16,17 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useSetRecordGroups = () => {
const setRecordGroups = useRecoilCallback(
({ snapshot, set }) =>
(
recordGroups: RecordGroupDefinition[],
recordIndexId: string,
objectMetadataItemId: string,
) => {
({
mainGroupByFieldMetadataId,
recordGroups,
recordIndexId,
objectMetadataItemId,
}: {
mainGroupByFieldMetadataId: string;
recordGroups: RecordGroupDefinition[];
recordIndexId: string;
objectMetadataItemId: string;
}) => {
const objectMetadataItems = snapshot
.getLoadable(objectMetadataItemsState)
.getValue();
@@ -40,7 +46,8 @@ export const useSetRecordGroups = () => {
instanceId: recordIndexId,
}),
);
const fieldMetadataId = recordGroups?.[0]?.fieldMetadataId;
const fieldMetadataId = mainGroupByFieldMetadataId;
const fieldMetadata = fieldMetadataId
? objectMetadataItem.fields.find(
(field) => field.id === fieldMetadataId,
@@ -105,26 +112,34 @@ export const useSetRecordGroups = () => {
);
const setRecordGroupsFromViewGroups = useCallback(
(
viewId: string,
viewGroups: ViewGroup[],
objectMetadataItem: ObjectMetadataItem,
) => {
({
viewId,
mainGroupByFieldMetadataId,
viewGroups,
objectMetadataItem,
}: {
viewId: string;
mainGroupByFieldMetadataId: string;
viewGroups: ViewGroup[];
objectMetadataItem: ObjectMetadataItem;
}) => {
const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
objectMetadataItem.namePlural,
viewId,
);
const newGroupDefinitions = mapViewGroupsToRecordGroupDefinitions({
mainGroupByFieldMetadataId,
objectMetadataItem,
viewGroups,
});
setRecordGroups(
newGroupDefinitions,
setRecordGroups({
mainGroupByFieldMetadataId,
recordGroups: newGroupDefinitions,
recordIndexId,
objectMetadataItem.id,
);
objectMetadataItemId: objectMetadataItem.id,
});
},
[setRecordGroups],
);
@@ -6,7 +6,6 @@ export const enum RecordGroupDefinitionType {
export type RecordGroupDefinition = {
id: string;
fieldMetadataId: string;
type: RecordGroupDefinitionType;
title: string;
value: string | null;
@@ -2,17 +2,21 @@ import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { useSetRecordGroups } from '@/object-record/record-group/hooks/useSetRecordGroups';
import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/useLoadRecordIndexStates';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { usePersistView } from '@/views/hooks/internal/usePersistView';
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroup';
import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState';
import { type ViewGroup } from '@/views/types/ViewGroup';
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type CoreView } from '~/generated/graphql';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useHandleRecordGroupField = () => {
const { createViewGroups, deleteViewGroups } = usePersistViewGroupRecords();
const { deleteViewGroups } = usePersistViewGroupRecords();
const currentViewIdCallbackState = useRecoilComponentCallbackState(
contextStoreCurrentViewIdComponentState,
@@ -25,6 +29,7 @@ export const useHandleRecordGroupField = () => {
const { setRecordGroupsFromViewGroups } = useSetRecordGroups();
const { updateView } = usePersistView();
const { loadRecordIndexStates } = useLoadRecordIndexStates();
const handleRecordGroupFieldChange = useRecoilCallback(
({ snapshot }) =>
@@ -50,16 +55,29 @@ export const useHandleRecordGroupField = () => {
return;
}
await updateView({
const updatedViewResult = await updateView({
id: view.id,
input: {
mainGroupByFieldMetadataId: fieldMetadataItem.id,
},
});
if (updatedViewResult.status === 'successful') {
const updatedCoreView = updatedViewResult.response.data
?.updateCoreView as CoreView;
if (isDefined(updatedCoreView)) {
const updatedViewConverted = convertCoreViewToView(updatedCoreView);
await loadRecordIndexStates(
updatedViewConverted,
objectMetadataItem,
);
}
}
const existingGroupKeys = new Set(
view.viewGroups.map(
(group) => `${group.fieldMetadataId}:${group.fieldValue}`,
(group) => `${view.mainGroupByFieldMetadataId}:${group.fieldValue}`,
),
);
@@ -79,7 +97,6 @@ export const useHandleRecordGroupField = () => {
fieldValue: option.value,
isVisible: true,
position: index,
fieldMetadataId: fieldMetadataItem.id,
}) satisfies ViewGroup,
);
@@ -93,45 +110,23 @@ export const useHandleRecordGroupField = () => {
fieldValue: '',
isVisible: true,
position: fieldMetadataItem.options.length,
fieldMetadataId: fieldMetadataItem.id,
} satisfies ViewGroup);
}
const viewGroupsToDelete = view.viewGroups.filter(
(group) => group.fieldMetadataId !== fieldMetadataItem.id,
);
const newViewGroupsList = [
...view.viewGroups.filter(
(group) => group.fieldMetadataId === fieldMetadataItem.id,
(_group) =>
view.mainGroupByFieldMetadataId === fieldMetadataItem.id,
),
...viewGroupsToCreate,
];
setRecordGroupsFromViewGroups(
view.id,
newViewGroupsList,
setRecordGroupsFromViewGroups({
viewId: view.id,
mainGroupByFieldMetadataId: fieldMetadataItem.id,
viewGroups: newViewGroupsList,
objectMetadataItem,
);
if (viewGroupsToCreate.length > 0) {
await createViewGroups({
inputs: viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
...viewGroup,
viewId: view.id,
})),
});
}
if (viewGroupsToDelete.length > 0) {
await deleteViewGroups(
viewGroupsToDelete.map((group) => ({
input: {
id: group.id,
},
})),
);
}
});
},
[
currentViewIdCallbackState,
@@ -139,8 +134,7 @@ export const useHandleRecordGroupField = () => {
updateView,
setRecordGroupsFromViewGroups,
objectMetadataItem,
createViewGroups,
deleteViewGroups,
loadRecordIndexStates,
],
);
@@ -173,7 +167,16 @@ export const useHandleRecordGroupField = () => {
})),
);
setRecordGroupsFromViewGroups(view.id, [], objectMetadataItem);
if (!isDefined(view.mainGroupByFieldMetadataId)) {
throw new Error('mainGroupByFieldMetadataId is required');
}
setRecordGroupsFromViewGroups({
viewId: view.id,
mainGroupByFieldMetadataId: view.mainGroupByFieldMetadataId,
viewGroups: [],
objectMetadataItem,
});
},
[
deleteViewGroups,
@@ -178,11 +178,12 @@ export const useLoadRecordIndexStates = () => {
onViewFieldsChange(view.viewFields, objectMetadataItem);
setRecordGroupsFromViewGroups(
view.id,
view.viewGroups,
setRecordGroupsFromViewGroups({
viewId: view.id,
mainGroupByFieldMetadataId: view.mainGroupByFieldMetadataId ?? '',
viewGroups: view.viewGroups,
objectMetadataItem,
);
});
setContextStoreTargetedRecordsRuleComponentState((prev) => ({
...prev,
@@ -199,9 +200,9 @@ export const useLoadRecordIndexStates = () => {
view.calendarFieldMetadataId ?? null,
);
if (isDefined(view.viewGroups?.[0]?.fieldMetadataId)) {
if (isDefined(view.mainGroupByFieldMetadataId)) {
const recordIndexGroupFieldMetadataItemId =
view.viewGroups?.[0]?.fieldMetadataId;
view.mainGroupByFieldMetadataId;
const { fieldMetadataItem: recordIndexGroupFieldMetadataItem } =
getFieldMetadataItemByIdOrThrow(
@@ -1,9 +1,11 @@
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupFieldMetadataComponentState';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
import { IconPlus } from 'twenty-ui/display';
@@ -17,12 +19,16 @@ export const RecordTableRecordGroupSectionAddNew = () => {
recordGroupDefinitionFamilyState(currentRecordGroupId),
);
const mainGroupByFieldMetadata = useRecoilComponentValue(
recordIndexGroupFieldMetadataItemComponentState,
);
const { createNewIndexRecord } = useCreateNewIndexRecord({
objectMetadataItem,
});
const fieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.id === recordGroup?.fieldMetadataId,
(field) => field.id === mainGroupByFieldMetadata?.id,
);
const objectPermissions = useObjectPermissionsForObject(
@@ -24,7 +24,7 @@ export const VIEW_FRAGMENT = gql`
openRecordIn
kanbanAggregateOperation
kanbanAggregateOperationFieldMetadataId
# mainGroupByFieldMetadataId
mainGroupByFieldMetadataId
anyFieldFilterValue
calendarFieldMetadataId
calendarLayout
@@ -3,7 +3,6 @@ import { gql } from '@apollo/client';
export const VIEW_GROUP_FRAGMENT = gql`
fragment ViewGroupFragment on CoreViewGroup {
id
fieldMetadataId
isVisible
fieldValue
position
@@ -32,9 +32,11 @@ export const usePersistViewGroupRecords = () => {
const { enqueueErrorSnackBar } = useSnackBar();
const createViewGroups = useCallback(
async (
createCoreViewGroupInputs: CreateManyCoreViewGroupsMutationVariables,
): Promise<
async ({
createCoreViewGroupInputs,
}: {
createCoreViewGroupInputs: CreateManyCoreViewGroupsMutationVariables;
}): Promise<
MetadataRequestResult<Awaited<
ReturnType<typeof createManyCoreViewGroupsMutation>
> | null>
@@ -11,9 +11,9 @@ import { usePersistView } from '@/views/hooks/internal/usePersistView';
import { usePersistViewField } from '@/views/hooks/internal/usePersistViewField';
import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilter';
import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroup';
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroup';
import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSort';
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
import { useTriggerViewGroupOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect';
import { isPersistingViewFieldsState } from '@/views/states/isPersistingViewFieldsState';
import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector';
import { type GraphQLView } from '@/views/types/GraphQLView';
@@ -28,7 +28,10 @@ import { mapRecordSortToViewSort } from '@/views/utils/mapRecordSortToViewSort';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { ViewCalendarLayout } from '~/generated-metadata/graphql';
import {
type CoreViewGroup,
ViewCalendarLayout,
} from '~/generated-metadata/graphql';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
@@ -47,7 +50,8 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
const { createViewSorts } = usePersistViewSortRecords();
const { createViewGroups } = usePersistViewGroupRecords();
const { triggerViewGroupOptimisticEffect } =
useTriggerViewGroupOptimisticEffect();
const { createViewFilters } = usePersistViewFilterRecords();
@@ -182,7 +186,10 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
}
if (type === ViewType.Kanban) {
if (!isDefined(mainGroupByFieldMetadataId)) {
if (
!isDefined(mainGroupByFieldMetadataId) ||
mainGroupByFieldMetadataId === ''
) {
throw new Error('Kanban view must have a kanban field');
}
@@ -194,33 +201,38 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
({
id: v4(),
__typename: 'ViewGroup',
fieldMetadataId: mainGroupByFieldMetadataId,
fieldValue: option.value,
isVisible: true,
position: index,
}) satisfies ViewGroup,
) ?? [];
viewGroupsToCreate.push({
__typename: 'ViewGroup',
id: v4(),
fieldValue: '',
position: viewGroupsToCreate.length,
isVisible: true,
fieldMetadataId: mainGroupByFieldMetadataId,
} satisfies ViewGroup);
const groupResult = await createViewGroups({
inputs: viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
...viewGroup,
viewId: newViewId,
})),
});
if (groupResult.status === 'failed') {
set(isPersistingViewFieldsState, false);
return undefined;
if (
objectMetadataItem.fields.find(
(field) => field.id === mainGroupByFieldMetadataId,
)?.isNullable === true
) {
viewGroupsToCreate.push({
__typename: 'ViewGroup',
id: v4(),
fieldValue: '',
position: viewGroupsToCreate.length,
isVisible: true,
} satisfies ViewGroup);
}
triggerViewGroupOptimisticEffect({
createdViewGroups: viewGroupsToCreate.map(
({ __typename, ...viewGroup }) =>
({
...viewGroup,
viewId: newViewId,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
deletedAt: null,
}) as Omit<CoreViewGroup, 'workspaceId'>,
),
});
}
if (shouldCopyFiltersAndSortsAndAggregate === true) {
@@ -292,7 +304,6 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
anyFieldFilterValue,
objectMetadataItem.fields,
objectMetadataItem.id,
createViewGroups,
currentRecordFilterGroups,
currentRecordFilters,
currentRecordSorts,
@@ -300,6 +311,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
createViewFilters,
createViewSorts,
refreshCoreViewsByObjectMetadataId,
triggerViewGroupOptimisticEffect,
],
);
@@ -10,9 +10,13 @@ export const useGetViewGroupsFilters = (): RecordFilter[] => {
currentView?.viewGroups
.filter((recordGroup) => !recordGroup.isVisible)
.map((recordGroup) => {
if (!isDefined(currentView.mainGroupByFieldMetadataId)) {
throw new Error('mainGroupByFieldMetadataId is required');
}
return {
id: recordGroup.id,
fieldMetadataId: recordGroup.fieldMetadataId,
fieldMetadataId: currentView.mainGroupByFieldMetadataId,
value: JSON.stringify([recordGroup.fieldValue]),
operand: ViewFilterOperand.IS_NOT,
displayValue: '',
@@ -24,11 +24,13 @@ export const useRefreshAllCoreViews = (
.getLoadable(coreViewsState)
.getValue();
const coreViewsFromResult = result.data.getCoreViews;
if (
isDefined(result.data?.getCoreViews) &&
!isDeeplyEqual(currentCoreViews, result.data.getCoreViews)
!isDeeplyEqual(currentCoreViews, coreViewsFromResult)
) {
set(coreViewsState, result.data.getCoreViews);
set(coreViewsState, coreViewsFromResult);
}
return result.data?.getCoreViews;
@@ -49,18 +49,18 @@ export const useRefreshCoreViewsByObjectMetadataId = () => {
)
.getValue();
if (
isDeeplyEqual(coreViewsForObjectMetadataId, result.data.getCoreViews)
) {
const coreViewsFromResult = result.data.getCoreViews;
if (isDeeplyEqual(coreViewsForObjectMetadataId, coreViewsFromResult)) {
return;
}
set(
coreViewsByObjectMetadataIdFamilySelector(objectMetadataId),
result.data.getCoreViews,
coreViewsFromResult,
);
for (const coreView of result.data.getCoreViews) {
for (const coreView of coreViewsFromResult) {
const existingView = coreViewsForObjectMetadataId.find(
(coreViewForObjectMetadata) =>
coreViewForObjectMetadata.id === coreView.id,
@@ -45,9 +45,7 @@ export const useSaveCurrentViewGroups = () => {
const existingField = currentViewGroups.find(
(currentViewGroup) =>
currentViewGroup.fieldValue === viewGroupToSave.fieldValue &&
currentViewGroup.fieldMetadataId ===
viewGroupToSave.fieldMetadataId,
currentViewGroup.fieldValue === viewGroupToSave.fieldValue,
);
if (isUndefinedOrNull(existingField)) {
@@ -76,7 +74,6 @@ export const useSaveCurrentViewGroups = () => {
update: {
isVisible: viewGroupToSave.isVisible,
position: viewGroupToSave.position,
fieldMetadataId: viewGroupToSave.fieldMetadataId,
fieldValue: viewGroupToSave.fieldValue,
},
},
@@ -118,9 +115,7 @@ export const useSaveCurrentViewGroups = () => {
.map((viewGroupToSave) => {
const existingField = currentViewGroups.find(
(currentViewGroup) =>
currentViewGroup.fieldValue === viewGroupToSave.fieldValue &&
currentViewGroup.fieldMetadataId ===
viewGroupToSave.fieldMetadataId,
currentViewGroup.fieldValue === viewGroupToSave.fieldValue,
);
if (isUndefinedOrNull(existingField)) {
@@ -148,7 +143,6 @@ export const useSaveCurrentViewGroups = () => {
update: {
isVisible: viewGroupToSave.isVisible,
position: viewGroupToSave.position,
fieldMetadataId: viewGroupToSave.fieldMetadataId,
fieldValue: viewGroupToSave.fieldValue,
},
},
@@ -160,18 +154,22 @@ export const useSaveCurrentViewGroups = () => {
(viewFieldToSave) =>
!currentViewGroups.some(
(currentViewGroup) =>
currentViewGroup.fieldValue === viewFieldToSave.fieldValue &&
currentViewGroup.fieldMetadataId ===
viewFieldToSave.fieldMetadataId,
currentViewGroup.fieldValue === viewFieldToSave.fieldValue,
),
);
if (!isDefined(view.mainGroupByFieldMetadataId)) {
throw new Error('mainGroupByFieldMetadataId is required');
}
await Promise.all([
createViewGroups({
inputs: viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
...viewGroup,
viewId: view.id,
})),
createCoreViewGroupInputs: {
inputs: viewGroupsToCreate.map((viewGroupToCreate) => ({
...viewGroupToCreate,
viewId: view.id,
})),
},
}),
updateViewGroups(viewGroupsToUpdate),
]);
@@ -6,6 +6,7 @@ import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { type CoreViewGroup } from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
type UpdatedDeletedCoreViewGroup = {
createdViewGroups?: Omit<CoreViewGroup, 'workspaceId'>[];
updatedViewGroups?: Omit<CoreViewGroup, 'workspaceId'>[];
@@ -1,7 +1,6 @@
export type ViewGroup = {
__typename: 'ViewGroup';
id: string;
fieldMetadataId: string;
isVisible: boolean;
fieldValue: string;
position: number;
@@ -4,13 +4,12 @@ import { type CoreViewGroup } from '~/generated/graphql';
export const convertCoreViewGroupToViewGroup = (
coreViewGroup: Pick<
CoreViewGroup,
'id' | 'fieldMetadataId' | 'isVisible' | 'fieldValue' | 'position'
'id' | 'isVisible' | 'fieldValue' | 'position'
>,
): ViewGroup => {
return {
__typename: 'ViewGroup',
id: coreViewGroup.id,
fieldMetadataId: coreViewGroup.fieldMetadataId,
isVisible: coreViewGroup.isVisible,
fieldValue: coreViewGroup.fieldValue,
position: coreViewGroup.position,
@@ -8,9 +8,11 @@ import { isDefined } from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const mapViewGroupsToRecordGroupDefinitions = ({
mainGroupByFieldMetadataId,
objectMetadataItem,
viewGroups,
}: {
mainGroupByFieldMetadataId: string;
objectMetadataItem: ObjectMetadataItem;
viewGroups: ViewGroup[];
}): RecordGroupDefinition[] => {
@@ -18,10 +20,10 @@ export const mapViewGroupsToRecordGroupDefinitions = ({
return [];
}
const fieldMetadataId = viewGroups?.[0]?.fieldMetadataId;
const selectFieldMetadataItem = objectMetadataItem.fields.find(
(field) =>
field.id === fieldMetadataId && field.type === FieldMetadataType.SELECT,
field.id === mainGroupByFieldMetadataId &&
field.type === FieldMetadataType.SELECT,
);
if (!selectFieldMetadataItem) {
@@ -36,10 +38,6 @@ export const mapViewGroupsToRecordGroupDefinitions = ({
const recordGroupDefinitionsFromViewGroups = viewGroups
.map((viewGroup) => {
if (viewGroup.fieldMetadataId !== selectFieldMetadataItem.id) {
return null;
}
const selectedOption = selectFieldMetadataItem.options?.find(
(option) => option.value === viewGroup.fieldValue,
);
@@ -50,7 +48,6 @@ export const mapViewGroupsToRecordGroupDefinitions = ({
return {
id: viewGroup.id,
fieldMetadataId: viewGroup.fieldMetadataId,
type: !isDefined(selectedOption)
? RecordGroupDefinitionType.NoValue
: RecordGroupDefinitionType.Value,
@@ -7,7 +7,6 @@ export const recordGroupDefinitionToViewGroup = (
return {
__typename: 'ViewGroup',
id: recordGroup.id,
fieldMetadataId: recordGroup.fieldMetadataId,
position: recordGroup.position,
isVisible: recordGroup.isVisible ?? true,
fieldValue: recordGroup.value ?? '',