Feat/activity optimistic activities (#4009)
* Fix naming * Fixed cache.evict bug for relation target deletion * Fixed cascade delete activity targets * Working version * Fix * fix * WIP * Fixed optimistic effect target inline cell * Removed openCreateActivityDrawer v1 * Ok for timeline * Removed console.log * Fix update record optimistic effect * Refactored activity queries into useActivities for everything * Fixed bugs * Cleaned * Fix lint --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -43,12 +43,19 @@ export const useGenerateObjectRecordOptimisticResponse = ({
|
||||
);
|
||||
const relationRecordId = result[relationIdFieldName] as string | null;
|
||||
|
||||
const relationRecord = input[fieldMetadataItem.name] as
|
||||
| ObjectRecord
|
||||
| undefined;
|
||||
|
||||
return {
|
||||
...result,
|
||||
[fieldMetadataItem.name]: relationRecordId
|
||||
? {
|
||||
__typename: relationRecordTypeName,
|
||||
id: relationRecordId,
|
||||
// TODO: there are too many bugs if we don't include the entire relation record
|
||||
// See if we can find a way to work only with the id and typename
|
||||
...relationRecord,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { MAX_QUERY_DEPTH_FOR_CACHE_INJECTION } from '@/object-record/cache/constants/MaxQueryDepthForCacheInjection';
|
||||
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
|
||||
@ -28,11 +27,11 @@ export const useUpsertFindManyRecordsQueryInCache = ({
|
||||
}) => {
|
||||
const findManyRecordsQueryForCacheOverwrite = generateFindManyRecordsQuery({
|
||||
objectMetadataItem,
|
||||
depth: MAX_QUERY_DEPTH_FOR_CACHE_INJECTION,
|
||||
depth: MAX_QUERY_DEPTH_FOR_CACHE_INJECTION, // TODO: fix this
|
||||
});
|
||||
|
||||
const newObjectRecordConnection = getRecordConnectionFromRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
records: objectRecordsToOverwrite,
|
||||
});
|
||||
|
||||
|
||||
@ -20,5 +20,6 @@ export const getRecordConnectionFromRecords = <T extends ObjectRecord>({
|
||||
});
|
||||
}),
|
||||
pageInfo: getEmptyPageInfo(),
|
||||
totalCount: records.length,
|
||||
} as ObjectRecordConnection<T>;
|
||||
};
|
||||
|
||||
@ -12,6 +12,10 @@ type useDeleteOneRecordProps = {
|
||||
refetchFindManyQuery?: boolean;
|
||||
};
|
||||
|
||||
type DeleteManyRecordsOptions = {
|
||||
skipOptimisticEffect?: boolean;
|
||||
};
|
||||
|
||||
export const useDeleteManyRecords = ({
|
||||
objectNameSingular,
|
||||
}: useDeleteOneRecordProps) => {
|
||||
@ -26,34 +30,41 @@ export const useDeleteManyRecords = ({
|
||||
objectMetadataItem.namePlural,
|
||||
);
|
||||
|
||||
const deleteManyRecords = async (idsToDelete: string[]) => {
|
||||
const deleteManyRecords = async (
|
||||
idsToDelete: string[],
|
||||
options?: DeleteManyRecordsOptions,
|
||||
) => {
|
||||
const deletedRecords = await apolloClient.mutate({
|
||||
mutation: deleteManyRecordsMutation,
|
||||
variables: {
|
||||
filter: { id: { in: idsToDelete } },
|
||||
},
|
||||
optimisticResponse: {
|
||||
[mutationResponseField]: idsToDelete.map((idToDelete) => ({
|
||||
__typename: capitalize(objectNameSingular),
|
||||
id: idToDelete,
|
||||
})),
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
const records = data?.[mutationResponseField];
|
||||
optimisticResponse: options?.skipOptimisticEffect
|
||||
? undefined
|
||||
: {
|
||||
[mutationResponseField]: idsToDelete.map((idToDelete) => ({
|
||||
__typename: capitalize(objectNameSingular),
|
||||
id: idToDelete,
|
||||
})),
|
||||
},
|
||||
update: options?.skipOptimisticEffect
|
||||
? undefined
|
||||
: (cache, { data }) => {
|
||||
const records = data?.[mutationResponseField];
|
||||
|
||||
if (!records?.length) return;
|
||||
if (!records?.length) return;
|
||||
|
||||
const cachedRecords = records
|
||||
.map((record) => getRecordFromCache(record.id, cache))
|
||||
.filter(isDefined);
|
||||
const cachedRecords = records
|
||||
.map((record) => getRecordFromCache(record.id, cache))
|
||||
.filter(isDefined);
|
||||
|
||||
triggerDeleteRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
recordsToDelete: cachedRecords,
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
triggerDeleteRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
recordsToDelete: cachedRecords,
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return deletedRecords.data?.[mutationResponseField] ?? null;
|
||||
|
||||
@ -4,6 +4,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
// TODO: fix connection in relation => automatically change to an array
|
||||
export const useFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectNameSingular,
|
||||
objectRecordId = '',
|
||||
|
||||
@ -79,7 +79,8 @@ export type LeafFilter =
|
||||
| CurrencyFilter
|
||||
| URLFilter
|
||||
| FullNameFilter
|
||||
| BooleanFilter;
|
||||
| BooleanFilter
|
||||
| undefined;
|
||||
|
||||
export type AndObjectRecordFilter = {
|
||||
and?: ObjectRecordQueryFilter[];
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const sortByObjectRecordId = (a: ObjectRecord, b: ObjectRecord) => {
|
||||
return a.id.localeCompare(b.id);
|
||||
};
|
||||
@ -0,0 +1,77 @@
|
||||
import { OrderBy } from '@/object-metadata/types/OrderBy';
|
||||
|
||||
import { sortObjectRecordByDateField } from './sortObjectRecordByDateField';
|
||||
|
||||
describe('sortByObjectRecordByCreatedAt', () => {
|
||||
const recordOldest = { id: '', createdAt: '2022-01-01T00:00:00.000Z' };
|
||||
const recordNewest = { id: '', createdAt: '2022-01-02T00:00:00.000Z' };
|
||||
const recordNull1 = { id: '', createdAt: null };
|
||||
const recordNull2 = { id: '', createdAt: null };
|
||||
|
||||
it('should sort in ascending order with null values first', () => {
|
||||
const sortDirection = 'AscNullsFirst' satisfies OrderBy;
|
||||
const sortedArray = [
|
||||
recordNull2,
|
||||
recordNewest,
|
||||
recordNull1,
|
||||
recordOldest,
|
||||
].sort(sortObjectRecordByDateField('createdAt', sortDirection));
|
||||
|
||||
expect(sortedArray).toEqual([
|
||||
recordNull1,
|
||||
recordNull2,
|
||||
recordOldest,
|
||||
recordNewest,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should sort in descending order with null values first', () => {
|
||||
const sortDirection = 'DescNullsFirst' satisfies OrderBy;
|
||||
const sortedArray = [
|
||||
recordNull2,
|
||||
recordOldest,
|
||||
recordNewest,
|
||||
recordNull1,
|
||||
].sort(sortObjectRecordByDateField('createdAt', sortDirection));
|
||||
|
||||
expect(sortedArray).toEqual([
|
||||
recordNull2,
|
||||
recordNull1,
|
||||
recordNewest,
|
||||
recordOldest,
|
||||
]);
|
||||
});
|
||||
it('should sort in ascending order with null values last', () => {
|
||||
const sortDirection = 'AscNullsLast' satisfies OrderBy;
|
||||
const sortedArray = [
|
||||
recordOldest,
|
||||
recordNull2,
|
||||
recordNewest,
|
||||
recordNull1,
|
||||
].sort(sortObjectRecordByDateField('createdAt', sortDirection));
|
||||
|
||||
expect(sortedArray).toEqual([
|
||||
recordOldest,
|
||||
recordNewest,
|
||||
recordNull1,
|
||||
recordNull2,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should sort in descending order with null values last', () => {
|
||||
const sortDirection = 'DescNullsLast' satisfies OrderBy;
|
||||
const sortedArray = [
|
||||
recordNull1,
|
||||
recordOldest,
|
||||
recordNewest,
|
||||
recordNull2,
|
||||
].sort(sortObjectRecordByDateField('createdAt', sortDirection));
|
||||
|
||||
expect(sortedArray).toEqual([
|
||||
recordNewest,
|
||||
recordOldest,
|
||||
recordNull1,
|
||||
recordNull2,
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,68 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { OrderBy } from '@/object-metadata/types/OrderBy';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const SORT_BEFORE = -1;
|
||||
const SORT_AFTER = 1;
|
||||
const SORT_EQUAL = 0;
|
||||
|
||||
export const sortObjectRecordByDateField =
|
||||
<T extends ObjectRecord>(dateField: keyof T, sortDirection: OrderBy) =>
|
||||
(a: T, b: T) => {
|
||||
const aDate = a[dateField];
|
||||
const bDate = b[dateField];
|
||||
|
||||
if (!isDefined(aDate) && !isDefined(bDate)) {
|
||||
return SORT_EQUAL;
|
||||
}
|
||||
|
||||
if (!isDefined(aDate)) {
|
||||
if (sortDirection === 'AscNullsFirst') {
|
||||
return SORT_BEFORE;
|
||||
} else if (sortDirection === 'DescNullsFirst') {
|
||||
return SORT_BEFORE;
|
||||
} else if (sortDirection === 'AscNullsLast') {
|
||||
return SORT_AFTER;
|
||||
} else if (sortDirection === 'DescNullsLast') {
|
||||
return SORT_AFTER;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid sortDirection: ${sortDirection}`);
|
||||
}
|
||||
|
||||
if (!isDefined(bDate)) {
|
||||
if (sortDirection === 'AscNullsFirst') {
|
||||
return SORT_AFTER;
|
||||
} else if (sortDirection === 'DescNullsFirst') {
|
||||
return SORT_AFTER;
|
||||
} else if (sortDirection === 'AscNullsLast') {
|
||||
return SORT_BEFORE;
|
||||
} else if (sortDirection === 'DescNullsLast') {
|
||||
return SORT_BEFORE;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid sortDirection: ${sortDirection}`);
|
||||
}
|
||||
|
||||
const differenceInMs = DateTime.fromISO(aDate)
|
||||
.diff(DateTime.fromISO(bDate))
|
||||
.as('milliseconds');
|
||||
|
||||
if (differenceInMs === 0) {
|
||||
return SORT_EQUAL;
|
||||
} else if (
|
||||
sortDirection === 'AscNullsFirst' ||
|
||||
sortDirection === 'AscNullsLast'
|
||||
) {
|
||||
return differenceInMs > 0 ? SORT_AFTER : SORT_BEFORE;
|
||||
} else if (
|
||||
sortDirection === 'DescNullsFirst' ||
|
||||
sortDirection === 'DescNullsLast'
|
||||
) {
|
||||
return differenceInMs > 0 ? SORT_BEFORE : SORT_AFTER;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid sortDirection: ${sortDirection}`);
|
||||
};
|
||||
Reference in New Issue
Block a user