Add useGroupBy hook + update calendar view to use groupBy (#15439)

## Context
Took inspiration from findMany + kanban => Implemented a hook for the
new groupBy API endpoints
Fixed an issue when DnD to an empty day
This commit is contained in:
Weiko
2025-10-31 18:39:22 +01:00
committed by GitHub
parent bb471cb8a5
commit 09c704c8bd
24 changed files with 1218 additions and 39 deletions
@@ -0,0 +1,64 @@
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { doesRecordBelongToGroup } from '../doesRecordBelongToGroup';
describe('doesRecordBelongToGroup', () => {
it('should return true when groupByConfig is undefined', () => {
const record: RecordGqlNode = {
__typename: 'Person',
id: '123',
name: 'John',
};
const result = doesRecordBelongToGroup(record, ['value1'], undefined);
expect(result).toBe(true);
});
it('should return true when groupByConfig is empty', () => {
const record: RecordGqlNode = {
__typename: 'Person',
id: '123',
name: 'John',
};
const result = doesRecordBelongToGroup(record, ['value1'], []);
expect(result).toBe(true);
});
it('should return true when record matches group dimension values', () => {
const record: RecordGqlNode = {
__typename: 'Person',
id: '123',
name: 'John',
};
const result = doesRecordBelongToGroup(record, ['John'], [{ name: true }]);
expect(result).toBe(true);
});
it('should return false when record does not match group dimension values', () => {
const record: RecordGqlNode = {
__typename: 'Person',
id: '123',
name: 'John',
};
const result = doesRecordBelongToGroup(record, ['Jane'], [{ name: true }]);
expect(result).toBe(false);
});
it('should return false when record field is undefined', () => {
const record: RecordGqlNode = {
__typename: 'Person',
id: '123',
};
const result = doesRecordBelongToGroup(record, ['John'], [{ name: true }]);
expect(result).toBe(false);
});
});
@@ -0,0 +1,47 @@
import { normalizeGroupByDimensionValue } from '../normalizeGroupByDimensionValue';
describe('normalizeGroupByDimensionValue', () => {
it('should convert string to string', () => {
const result = normalizeGroupByDimensionValue('test', undefined);
expect(result).toBe('test');
});
it('should convert number to string', () => {
const result = normalizeGroupByDimensionValue(123, undefined);
expect(result).toBe('123');
});
it('should handle date with DAY granularity', () => {
const date = new Date('2024-01-15T10:30:00Z');
const result = normalizeGroupByDimensionValue(date, { granularity: 'DAY' });
expect(result).toBe('2024-01-15');
});
it('should handle date with MONTH granularity', () => {
const date = new Date('2024-01-15T10:30:00Z');
const result = normalizeGroupByDimensionValue(date, {
granularity: 'MONTH',
});
expect(result).toBe('2024-01');
});
it('should handle date with YEAR granularity', () => {
const date = new Date('2024-01-15T10:30:00Z');
const result = normalizeGroupByDimensionValue(date, {
granularity: 'YEAR',
});
expect(result).toBe('2024');
});
it('should handle object with id', () => {
const obj = { id: '123', name: 'test' };
const result = normalizeGroupByDimensionValue(obj, undefined);
expect(result).toBe('123');
});
it('should handle object without id', () => {
const obj = { name: 'test' };
const result = normalizeGroupByDimensionValue(obj, undefined);
expect(result).toBe(JSON.stringify(obj));
});
});
@@ -0,0 +1,121 @@
import { type Reference } from '@apollo/client';
import {
type ReadFieldFunction,
type ToReferenceFunction,
} from '@apollo/client/cache/core/types/common';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { processGroupByConnectionWithRecords } from '../processGroupByConnectionWithRecords';
describe('processGroupByConnectionWithRecords', () => {
const mockObjectMetadataItem: ObjectMetadataItem = {
nameSingular: 'person',
namePlural: 'people',
} as ObjectMetadataItem;
const mockRecord: RecordGqlNode = {
__typename: 'Person',
id: '123',
name: 'John',
deletedAt: null,
};
const mockReference: Reference = {
__ref: 'Person:123',
};
const mockReadField = jest.fn(
(fieldName: any, from: any, ..._args: any[]) => {
if (fieldName === 'id' && from === mockReference) {
return '123';
}
return undefined;
},
) as unknown as ReadFieldFunction;
const mockToReference: ToReferenceFunction = jest.fn(() => mockReference);
it('should return cached data when no records match', () => {
const cachedEdges: RecordGqlRefEdge[] = [];
const cachedPageInfo = {
hasNextPage: false,
hasPreviousPage: false,
};
const result = processGroupByConnectionWithRecords({
cachedEdges,
cachedPageInfo,
records: [],
operation: 'create',
queryFilter: {},
shouldMatchRootQueryFilter: false,
groupByDimensionValues: [],
groupByConfig: undefined,
objectMetadataItem: mockObjectMetadataItem,
readField: mockReadField,
toReference: mockToReference,
});
expect(result.nextEdges).toEqual([]);
expect(result.totalCountDelta).toBe(0);
});
it('should handle create operation', () => {
const cachedEdges: RecordGqlRefEdge[] = [];
const cachedPageInfo = {
hasNextPage: false,
hasPreviousPage: false,
};
const result = processGroupByConnectionWithRecords({
cachedEdges,
cachedPageInfo,
records: [mockRecord],
operation: 'create',
queryFilter: {},
shouldMatchRootQueryFilter: false,
groupByDimensionValues: [],
groupByConfig: undefined,
objectMetadataItem: mockObjectMetadataItem,
readField: mockReadField,
toReference: mockToReference,
});
expect(result.totalCountDelta).toBe(1);
expect(result.nextEdges.length).toBe(1);
});
it('should handle delete operation', () => {
const mockEdge: RecordGqlRefEdge = {
__typename: 'PersonEdge',
node: mockReference,
cursor: 'cursor123',
};
const cachedEdges: RecordGqlRefEdge[] = [mockEdge];
const cachedPageInfo = {
hasNextPage: false,
hasPreviousPage: false,
};
const result = processGroupByConnectionWithRecords({
cachedEdges,
cachedPageInfo,
records: [mockRecord],
operation: 'delete',
queryFilter: {},
shouldMatchRootQueryFilter: false,
groupByDimensionValues: [],
groupByConfig: undefined,
objectMetadataItem: mockObjectMetadataItem,
readField: mockReadField,
toReference: mockToReference,
});
expect(result.totalCountDelta).toBe(-1);
expect(result.nextEdges.length).toBe(0);
});
});
@@ -0,0 +1,92 @@
import { type ApolloCache } from '@apollo/client';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { triggerUpdateGroupByQueriesOptimisticEffect } from '../triggerUpdateGroupByQueriesOptimisticEffect';
describe('triggerUpdateGroupByQueriesOptimisticEffect', () => {
const mockObjectMetadataItem: ObjectMetadataItem = {
nameSingular: 'person',
namePlural: 'people',
} as ObjectMetadataItem;
const mockRecord: RecordGqlNode = {
__typename: 'Person',
id: '123',
name: 'John',
};
it('should call cache.modify with correct field name', () => {
const mockModify = jest.fn();
const mockCache = {
modify: mockModify,
} as unknown as ApolloCache<unknown>;
triggerUpdateGroupByQueriesOptimisticEffect({
cache: mockCache,
objectMetadataItem: mockObjectMetadataItem,
operation: 'create',
records: [mockRecord],
shouldMatchRootQueryFilter: false,
});
expect(mockModify).toHaveBeenCalledWith({
broadcast: false,
fields: expect.objectContaining({
peopleGroupBy: expect.any(Function),
}),
});
});
it('should handle update operation', () => {
const mockModify = jest.fn();
const mockCache = {
modify: mockModify,
} as unknown as ApolloCache<unknown>;
triggerUpdateGroupByQueriesOptimisticEffect({
cache: mockCache,
objectMetadataItem: mockObjectMetadataItem,
operation: 'update',
records: [mockRecord],
shouldMatchRootQueryFilter: false,
});
expect(mockModify).toHaveBeenCalled();
});
it('should handle delete operation', () => {
const mockModify = jest.fn();
const mockCache = {
modify: mockModify,
} as unknown as ApolloCache<unknown>;
triggerUpdateGroupByQueriesOptimisticEffect({
cache: mockCache,
objectMetadataItem: mockObjectMetadataItem,
operation: 'delete',
records: [mockRecord],
shouldMatchRootQueryFilter: false,
});
expect(mockModify).toHaveBeenCalled();
});
it('should handle empty records array', () => {
const mockModify = jest.fn();
const mockCache = {
modify: mockModify,
} as unknown as ApolloCache<unknown>;
triggerUpdateGroupByQueriesOptimisticEffect({
cache: mockCache,
objectMetadataItem: mockObjectMetadataItem,
operation: 'create',
records: [],
shouldMatchRootQueryFilter: false,
});
expect(mockModify).toHaveBeenCalled();
});
});
@@ -0,0 +1,48 @@
import { normalizeGroupByDimensionValue } from '@/apollo/optimistic-effect/group-by/utils/normalizeGroupByDimensionValue';
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { isDefined } from 'twenty-shared/utils';
export const doesRecordBelongToGroup = (
record: RecordGqlNode,
groupByDimensionValues: readonly string[],
groupByConfig?: Array<Record<string, boolean | Record<string, string>>>,
): boolean => {
if (!isDefined(groupByConfig) || groupByConfig.length === 0) {
return true;
}
const groupByFieldNames = groupByConfig.map(
(groupByField) => Object.keys(groupByField)[0],
);
for (let i = 0; i < groupByFieldNames.length; i++) {
const fieldName = groupByFieldNames[i];
const expectedValue = groupByDimensionValues[i];
if (!isDefined(expectedValue)) {
continue;
}
let recordValue = record[fieldName];
if (!isDefined(recordValue)) {
return false;
}
const fieldConfig = groupByConfig[i][fieldName];
const recordValueStr = normalizeGroupByDimensionValue(
recordValue,
fieldConfig,
);
const expectedValueStr = normalizeGroupByDimensionValue(
expectedValue,
fieldConfig,
);
if (recordValueStr !== expectedValueStr) {
return false;
}
}
return true;
};
@@ -0,0 +1,33 @@
import { isDefined } from 'twenty-shared/utils';
export const normalizeGroupByDimensionValue = (
value: any,
fieldConfig: boolean | Record<string, string> | undefined,
): string => {
if (typeof fieldConfig === 'object' && isDefined(fieldConfig.granularity)) {
const dateValue = new Date(value);
const granularity = fieldConfig.granularity;
// TODO: to remove once backend properly returns DATE without time
switch (granularity) {
case 'DAY':
return dateValue.toISOString().split('T')[0];
case 'MONTH':
return dateValue.toISOString().substring(0, 7);
case 'YEAR':
return dateValue.getFullYear().toString();
case 'DAY_OF_THE_WEEK':
return dateValue.getDay().toString();
case 'MONTH_OF_THE_YEAR':
return (dateValue.getMonth() + 1).toString();
default:
return String(value);
}
}
if (typeof value === 'object' && value !== null) {
return value.id ? String(value.id) : JSON.stringify(value);
}
return String(value);
};
@@ -0,0 +1,130 @@
import { doesRecordBelongToGroup } from '@/apollo/optimistic-effect/group-by/utils/doesRecordBelongToGroup';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
import { createCacheEdgeWithRecordRef } from '@/object-record/cache/utils/createCacheEdgeWithRecordRef';
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter';
import {
type ReadFieldFunction,
type ToReferenceFunction,
} from '@apollo/client/cache/core/types/common';
import { isDefined } from 'twenty-shared/utils';
type ProcessGroupByConnectionWithRecordsArgs = {
cachedEdges: readonly RecordGqlRefEdge[];
cachedPageInfo: {
startCursor?: string;
endCursor?: string;
hasNextPage?: boolean;
hasPreviousPage?: boolean;
};
records: RecordGqlNode[];
operation: 'create' | 'update' | 'delete';
queryFilter: any;
shouldMatchRootQueryFilter: boolean;
groupByDimensionValues: readonly string[];
groupByConfig?: Array<Record<string, boolean | Record<string, string>>>;
objectMetadataItem: ObjectMetadataItem;
readField: ReadFieldFunction;
toReference: ToReferenceFunction;
};
export const processGroupByConnectionWithRecords = ({
cachedEdges,
cachedPageInfo,
records,
operation,
queryFilter,
shouldMatchRootQueryFilter,
groupByDimensionValues,
groupByConfig,
objectMetadataItem,
readField,
toReference,
}: ProcessGroupByConnectionWithRecordsArgs): {
nextEdges: RecordGqlRefEdge[];
nextPageInfo: {
startCursor?: string;
endCursor?: string;
hasNextPage?: boolean;
hasPreviousPage?: boolean;
};
totalCountDelta: number;
} => {
const nextEdges = [...cachedEdges];
const nextPageInfo = isDefined(cachedPageInfo) ? { ...cachedPageInfo } : {};
let totalCountDelta = 0;
for (const record of records) {
const recordMatchesFilter = isRecordMatchingFilter({
record,
filter: queryFilter ?? {},
objectMetadataItem,
});
const belongsToGroup = doesRecordBelongToGroup(
record,
groupByDimensionValues,
groupByConfig,
);
const recordReference = toReference(record);
if (!recordReference) {
continue;
}
const recordIndexInEdges = cachedEdges.findIndex(
(cachedEdge) => readField('id', cachedEdge.node) === record.id,
);
const recordExistsInEdges = recordIndexInEdges !== -1;
if (operation === 'create') {
const shouldAdd =
(!shouldMatchRootQueryFilter || recordMatchesFilter) &&
belongsToGroup &&
!recordExistsInEdges;
if (shouldAdd) {
const edge = createCacheEdgeWithRecordRef({
record,
objectMetadataItem,
toReference,
});
if (isDefined(edge)) {
nextEdges.unshift(edge);
nextPageInfo.startCursor = edge.cursor;
totalCountDelta++;
}
}
}
if (operation === 'update') {
const shouldBeInGroup = recordMatchesFilter && belongsToGroup;
if (shouldBeInGroup && !recordExistsInEdges) {
const edge = createCacheEdgeWithRecordRef({
record,
objectMetadataItem,
toReference,
});
if (isDefined(edge)) {
nextEdges.push(edge);
totalCountDelta++;
}
} else if (!shouldBeInGroup && recordExistsInEdges) {
nextEdges.splice(recordIndexInEdges, 1);
totalCountDelta--;
}
}
if (operation === 'delete' && recordExistsInEdges) {
nextEdges.splice(recordIndexInEdges, 1);
totalCountDelta--;
}
}
return { nextEdges, nextPageInfo, totalCountDelta };
};
@@ -0,0 +1,214 @@
import { type ApolloCache, type StoreObject } from '@apollo/client';
import { normalizeGroupByDimensionValue } from '@/apollo/optimistic-effect/group-by/utils/normalizeGroupByDimensionValue';
import { processGroupByConnectionWithRecords } from '@/apollo/optimistic-effect/group-by/utils/processGroupByConnectionWithRecords';
import { type CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
import { createCacheEdgeWithRecordRef } from '@/object-record/cache/utils/createCacheEdgeWithRecordRef';
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { type RecordGqlGroupByConnection } from '@/object-record/graphql/types/RecordGqlOperationGroupByResult';
import { type RecordGqlOperationGroupByVariables } from '@/object-record/graphql/types/RecordGqlOperationGroupByVariables';
import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter';
import { isDefined } from 'twenty-shared/utils';
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
type TriggerUpdateGroupByQueriesOptimisticEffectArgs = {
cache: ApolloCache<unknown>;
objectMetadataItem: ObjectMetadataItem;
operation: 'create' | 'update' | 'delete';
records: RecordGqlNode[];
shouldMatchRootQueryFilter?: boolean;
};
export const triggerUpdateGroupByQueriesOptimisticEffect = ({
cache,
objectMetadataItem,
operation,
records,
shouldMatchRootQueryFilter = false,
}: TriggerUpdateGroupByQueriesOptimisticEffectArgs) => {
const groupByQueryFieldName = `${objectMetadataItem.namePlural}GroupBy`;
cache.modify<StoreObject>({
broadcast: false,
fields: {
[groupByQueryFieldName]: (
cachedGroupByQueryResult,
{ readField, toReference, storeFieldName },
) => {
const cachedGroupByConnections = cachedGroupByQueryResult as
| RecordGqlGroupByConnection[]
| undefined;
if (!Array.isArray(cachedGroupByConnections)) {
return cachedGroupByQueryResult;
}
const { fieldVariables: queryVariables } = parseApolloStoreFieldName<
CachedObjectRecordQueryVariables & RecordGqlOperationGroupByVariables
>(storeFieldName);
const queryFilter = queryVariables?.filter;
const groupByConfig = queryVariables?.groupBy;
const updatedGroupByConnections = cachedGroupByConnections.map(
(groupConnection) => {
const groupByDimensionValues =
readField('groupByDimensionValues', groupConnection) || [];
const cachedEdges =
readField<RecordGqlRefEdge[]>('edges', groupConnection) || [];
const cachedTotalCount = readField<number | undefined>(
'totalCount',
groupConnection,
);
const cachedPageInfo = readField<{
startCursor?: string;
endCursor?: string;
hasNextPage?: boolean;
hasPreviousPage?: boolean;
}>('pageInfo', groupConnection);
const { nextEdges, nextPageInfo, totalCountDelta } =
processGroupByConnectionWithRecords({
cachedEdges,
cachedPageInfo: cachedPageInfo || {},
records,
operation,
queryFilter,
shouldMatchRootQueryFilter,
groupByDimensionValues: Array.isArray(groupByDimensionValues)
? groupByDimensionValues
: [],
groupByConfig,
objectMetadataItem,
readField,
toReference,
});
if (
totalCountDelta === 0 &&
nextEdges.length === cachedEdges.length
) {
return groupConnection;
}
return {
...groupConnection,
edges: nextEdges,
totalCount: isDefined(cachedTotalCount)
? cachedTotalCount + totalCountDelta
: undefined,
pageInfo: nextPageInfo,
};
},
);
if (operation === 'create' || operation === 'update') {
const recordsToAddToNewGroups: Map<
string,
{
dimensionValues: string[];
edges: RecordGqlRefEdge[];
}
> = new Map();
for (const record of records) {
const recordMatchesFilter = isRecordMatchingFilter({
record,
filter: queryFilter ?? {},
objectMetadataItem,
});
if (
shouldMatchRootQueryFilter &&
!recordMatchesFilter &&
operation === 'create'
) {
continue;
}
if (!isDefined(groupByConfig) || groupByConfig.length === 0) {
continue;
}
const groupByFieldNames = groupByConfig.map(
(groupByField) => Object.keys(groupByField)[0],
);
const recordDimensionValues: string[] = [];
for (let i = 0; i < groupByFieldNames.length; i++) {
const fieldName = groupByFieldNames[i];
let recordValue = record[fieldName];
if (!isDefined(recordValue)) {
break;
}
const fieldConfig = groupByConfig[i][fieldName];
const normalizedValue = normalizeGroupByDimensionValue(
recordValue,
fieldConfig,
);
recordDimensionValues.push(normalizedValue);
}
const dimensionKey = recordDimensionValues.join('|');
const dimensionExists = updatedGroupByConnections.some((conn) => {
const connDimensionValues =
readField('groupByDimensionValues', conn) || [];
return (
Array.isArray(connDimensionValues) &&
connDimensionValues.join('|') === dimensionKey
);
});
if (
!dimensionExists &&
recordDimensionValues.length === groupByFieldNames.length
) {
const edge = createCacheEdgeWithRecordRef({
record,
objectMetadataItem,
toReference,
});
if (isDefined(edge)) {
if (!recordsToAddToNewGroups.has(dimensionKey)) {
recordsToAddToNewGroups.set(dimensionKey, {
dimensionValues: recordDimensionValues,
edges: [],
});
}
recordsToAddToNewGroups.get(dimensionKey)!.edges.push(edge);
}
}
}
for (const [_, groupData] of recordsToAddToNewGroups) {
if (groupData.edges.length > 0) {
const newGroupConnection = {
__typename: `${objectMetadataItem.nameSingular}Connection`,
edges: groupData.edges,
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: groupData.edges[0].cursor,
endCursor: groupData.edges[groupData.edges.length - 1].cursor,
},
totalCount: groupData.edges.length,
groupByDimensionValues: groupData.dimensionValues,
};
updatedGroupByConnections.push(newGroupConnection);
}
}
}
return updatedGroupByConnections;
},
},
});
};
@@ -9,6 +9,7 @@ import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/is
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter';
import { triggerUpdateGroupByQueriesOptimisticEffect } from '@/apollo/optimistic-effect/group-by/utils/triggerUpdateGroupByQueriesOptimisticEffect';
import { type CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables';
import { encodeCursor } from '@/apollo/utils/encodeCursor';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
@@ -238,4 +239,12 @@ export const triggerCreateRecordsOptimisticEffect = ({
},
},
});
triggerUpdateGroupByQueriesOptimisticEffect({
cache,
objectMetadataItem,
operation: 'create',
records: recordsToCreate,
shouldMatchRootQueryFilter,
});
};
@@ -1,5 +1,6 @@
import { type ApolloCache, type StoreObject } from '@apollo/client';
import { triggerUpdateGroupByQueriesOptimisticEffect } from '@/apollo/optimistic-effect/group-by/utils/triggerUpdateGroupByQueriesOptimisticEffect';
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
@@ -90,4 +91,11 @@ export const triggerDestroyRecordsOptimisticEffect = ({
cache.evict({ id: cache.identify(recordToDestroy) });
});
triggerUpdateGroupByQueriesOptimisticEffect({
cache,
objectMetadataItem,
operation: 'delete',
records: recordsToDestroy,
});
};
@@ -1,5 +1,6 @@
import { type ApolloCache, type StoreObject } from '@apollo/client';
import { triggerUpdateGroupByQueriesOptimisticEffect } from '@/apollo/optimistic-effect/group-by/utils/triggerUpdateGroupByQueriesOptimisticEffect';
import { sortCachedObjectEdges } from '@/apollo/optimistic-effect/utils/sortCachedObjectEdges';
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
import { type CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables';
@@ -134,4 +135,12 @@ export const triggerUpdateRecordOptimisticEffect = ({
},
},
});
triggerUpdateGroupByQueriesOptimisticEffect({
cache,
objectMetadataItem,
operation: 'update',
records: [updatedRecord],
shouldMatchRootQueryFilter: true,
});
};
@@ -1,5 +1,6 @@
import { type ApolloCache, type StoreObject } from '@apollo/client';
import { triggerUpdateGroupByQueriesOptimisticEffect } from '@/apollo/optimistic-effect/group-by/utils/triggerUpdateGroupByQueriesOptimisticEffect';
import { sortCachedObjectEdges } from '@/apollo/optimistic-effect/utils/sortCachedObjectEdges';
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
import { type CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables';
@@ -141,4 +142,12 @@ export const triggerUpdateRecordOptimisticEffectByBatch = ({
},
},
});
triggerUpdateGroupByQueriesOptimisticEffect({
cache,
objectMetadataItem,
operation: 'update',
records: updatedRecords,
shouldMatchRootQueryFilter: true,
});
};