Improved table flash on reload (#16419)
This PR fixes https://github.com/twentyhq/core-team-issues/issues/1732 There is still some room for improvement but the main goal is reached : removing the flash effect each time the table virtualization has to recompute due to an update after initial loading. # QA ## Create https://github.com/user-attachments/assets/1b4fc307-42ce-4ba6-b557-68ac7fcad40f ## Update with sort https://github.com/user-attachments/assets/e0700f44-8926-4395-8ab8-b32773f21de8 ## Update with filter https://github.com/user-attachments/assets/d325f85a-1a7b-4366-aac3-250331be7575 ## Soft delete https://github.com/user-attachments/assets/2c980183-c637-4aa7-a0ca-244e61396b20 ## Restore and destroy https://github.com/user-attachments/assets/01af9ec7-b442-4686-a1d7-ea1fc543fe62 ## Drag & drop https://github.com/user-attachments/assets/bba76bdb-d4ec-433d-b44e-37fdb9952b06 --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@@ -100,7 +100,7 @@ export const useBatchCreateManyRecords = <
|
||||
|
||||
await refetchAggregateQueries();
|
||||
|
||||
registerObjectOperation(objectNameSingular, { type: 'create-many' });
|
||||
registerObjectOperation(objectMetadataItem, { type: 'create-many' });
|
||||
|
||||
return allCreatedRecords;
|
||||
};
|
||||
|
||||
@@ -243,7 +243,7 @@ export const useCreateManyRecords = <
|
||||
await refetchAggregateQueries();
|
||||
}
|
||||
|
||||
registerObjectOperation(objectNameSingular, { type: 'create-many' });
|
||||
registerObjectOperation(objectMetadataItem, { type: 'create-many' });
|
||||
|
||||
return createdObjects.data?.[mutationResponseField] ?? [];
|
||||
};
|
||||
|
||||
@@ -187,7 +187,7 @@ export const useCreateOneRecord = <
|
||||
|
||||
await refetchAggregateQueries();
|
||||
|
||||
registerObjectOperation(objectNameSingular, { type: 'create-one' });
|
||||
registerObjectOperation(objectMetadataItem, { type: 'create-one' });
|
||||
|
||||
if (!isDefined(createdObject.data?.[mutationResponseField])) {
|
||||
throw new CustomError('Failed to create record');
|
||||
|
||||
@@ -224,7 +224,7 @@ export const useDeleteManyRecords = ({
|
||||
}
|
||||
await refetchAggregateQueries();
|
||||
|
||||
registerObjectOperation(objectNameSingular, {
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'delete-many',
|
||||
});
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ export const useDeleteOneRecord = ({
|
||||
|
||||
await refetchAggregateQueries();
|
||||
|
||||
registerObjectOperation(objectNameSingular, {
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'delete-one',
|
||||
});
|
||||
|
||||
@@ -179,7 +179,6 @@ export const useDeleteOneRecord = ({
|
||||
deleteOneRecordMutation,
|
||||
refetchAggregateQueries,
|
||||
registerObjectOperation,
|
||||
objectNameSingular,
|
||||
mutationResponseField,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
upsertRecordsInStore,
|
||||
|
||||
@@ -145,7 +145,7 @@ export const useDestroyManyRecords = ({
|
||||
|
||||
await refetchAggregateQueries();
|
||||
|
||||
registerObjectOperation(objectNameSingular, {
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'destroy-many',
|
||||
});
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ export const useDestroyOneRecord = ({
|
||||
throw error;
|
||||
});
|
||||
|
||||
registerObjectOperation(objectMetadataItem.nameSingular, {
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'destroy-one',
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -225,7 +225,7 @@ export const useIncrementalDeleteManyRecords = <T>({
|
||||
|
||||
await refetchAggregateQueries();
|
||||
|
||||
registerObjectOperation(objectNameSingular, {
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'delete-many',
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -155,7 +155,7 @@ export const useIncrementalDestroyManyRecords = <T>({
|
||||
|
||||
await refetchAggregateQueries();
|
||||
|
||||
registerObjectOperation(objectNameSingular, {
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'destroy-many',
|
||||
});
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ export const useMergeManyRecords = <
|
||||
|
||||
if (!preview) {
|
||||
await refetchAggregateQueries();
|
||||
registerObjectOperation(objectNameSingular, {
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'merge-records',
|
||||
});
|
||||
}
|
||||
@@ -115,14 +115,13 @@ export const useMergeManyRecords = <
|
||||
}
|
||||
},
|
||||
[
|
||||
objectMetadataItem.namePlural,
|
||||
objectMetadataItem,
|
||||
apolloCoreClient,
|
||||
mergeManyRecordsMutation,
|
||||
findOneRecordQuery,
|
||||
findDuplicateRecordsQuery,
|
||||
registerObjectOperation,
|
||||
objectNameSingular,
|
||||
refetchAggregateQueries,
|
||||
registerObjectOperation,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+14
-17
@@ -1,30 +1,27 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import {
|
||||
objectOperationsState,
|
||||
type ObjectOperationData,
|
||||
objectOperationsByObjectNameSingularFamilyState,
|
||||
} from '@/object-record/states/objectOperationsByObjectNameSingularFamilyState';
|
||||
} from '@/object-record/states/objectOperationsComponentState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const useRegisterObjectOperation = () => {
|
||||
const registerObjectOperation = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(objectNameSingular: string, data: ObjectOperationData) => {
|
||||
set(
|
||||
objectOperationsByObjectNameSingularFamilyState({
|
||||
objectNameSingular,
|
||||
}),
|
||||
(currentValue) => {
|
||||
const newValue = currentValue.concat();
|
||||
(objectMetadataItem: ObjectMetadataItem, data: ObjectOperationData) => {
|
||||
set(objectOperationsState, (currentValue) => {
|
||||
const newValue = currentValue.concat();
|
||||
|
||||
newValue.push({
|
||||
id: v4(),
|
||||
timestamp: +new Date(),
|
||||
data,
|
||||
});
|
||||
newValue.push({
|
||||
id: v4(),
|
||||
timestamp: +new Date(),
|
||||
objectMetadataItemId: objectMetadataItem.id,
|
||||
data,
|
||||
});
|
||||
|
||||
return newValue;
|
||||
},
|
||||
);
|
||||
return newValue;
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
@@ -200,7 +200,7 @@ export const useRestoreManyRecords = ({
|
||||
|
||||
restoredRecords.push(...restoredRecordsForThisBatch);
|
||||
|
||||
registerObjectOperation(objectMetadataItem.nameSingular, {
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'restore-many',
|
||||
});
|
||||
|
||||
|
||||
@@ -230,9 +230,9 @@ export const useUpdateOneRecord = <
|
||||
|
||||
const udpatedRecord = updatedRecord?.data?.[mutationResponseField] ?? null;
|
||||
|
||||
registerObjectOperation(objectNameSingular, {
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'update-one',
|
||||
result: { updatedRecord, updateInput: updateOneRecordInput },
|
||||
result: { updateInput: updateOneRecordInput },
|
||||
});
|
||||
|
||||
return udpatedRecord;
|
||||
|
||||
@@ -238,10 +238,11 @@ export const useUpdateOneRecordV2 = () => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
registerObjectOperation(objectMetadataItem.nameSingular, {
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'update-one',
|
||||
result: { updatedRecord, updateInput: updateOneRecordInput },
|
||||
result: { updateInput: updateOneRecordInput },
|
||||
});
|
||||
|
||||
return updatedRecord?.data?.[mutationResponseField] ?? null;
|
||||
};
|
||||
|
||||
|
||||
+12
-12
@@ -2,6 +2,7 @@ import { type DropResult } from '@hello-pangea/dnd';
|
||||
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
|
||||
import { useTriggerTableWithoutGroupDragAndDropOptimisticUpdate } from '@/object-record/record-drag/hooks/useTriggerTableWithoutGroupDragAndDropOptimisticUpdate';
|
||||
import { originalDragSelectionComponentState } from '@/object-record/record-drag/states/originalDragSelectionComponentState';
|
||||
import { getDragOperationType } from '@/object-record/record-drag/utils/getDragOperationType';
|
||||
import { processMultiDrag } from '@/object-record/record-drag/utils/processMultiDrag';
|
||||
@@ -12,8 +13,6 @@ import { currentRecordSortsComponentState } from '@/object-record/record-sort/st
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { selectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsComponentSelector';
|
||||
import { useResetVirtualizationBecauseDataChanged } from '@/object-record/record-table/virtualization/hooks/useResetVirtualizationBecauseDataChanged';
|
||||
import { useTriggerFetchPages } from '@/object-record/record-table/virtualization/hooks/useTriggerFetchPages';
|
||||
import { type RecordWithPosition } from '@/object-record/utils/computeNewPositionOfDraggedRecord';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
@@ -46,10 +45,8 @@ export const useProcessTableWithoutGroupRecordDrop = () => {
|
||||
|
||||
const { openModal } = useModal();
|
||||
|
||||
const { resetVirtualization } =
|
||||
useResetVirtualizationBecauseDataChanged(objectNameSingular);
|
||||
|
||||
const { triggerFetchPagesWithoutDebounce } = useTriggerFetchPages();
|
||||
const { triggerTableWithoutGroupDragAndDropOptimisticUpdate } =
|
||||
useTriggerTableWithoutGroupDragAndDropOptimisticUpdate();
|
||||
|
||||
const processTableWithoutGroupRecordDrop = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
@@ -115,6 +112,10 @@ export const useProcessTableWithoutGroupRecordDrop = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerTableWithoutGroupDragAndDropOptimisticUpdate([
|
||||
singleDragResult,
|
||||
]);
|
||||
|
||||
updateOneRow({
|
||||
idToUpdate: singleDragResult.id,
|
||||
updateOneRecordInput: {
|
||||
@@ -145,6 +146,10 @@ export const useProcessTableWithoutGroupRecordDrop = () => {
|
||||
isDroppedAfterList,
|
||||
});
|
||||
|
||||
triggerTableWithoutGroupDragAndDropOptimisticUpdate(
|
||||
multiDragResult.recordUpdates,
|
||||
);
|
||||
|
||||
for (const update of multiDragResult.recordUpdates) {
|
||||
updateOneRow({
|
||||
idToUpdate: update.id,
|
||||
@@ -154,10 +159,6 @@ export const useProcessTableWithoutGroupRecordDrop = () => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await resetVirtualization();
|
||||
|
||||
await triggerFetchPagesWithoutDebounce();
|
||||
},
|
||||
[
|
||||
selectedRowIdsSelector,
|
||||
@@ -166,8 +167,7 @@ export const useProcessTableWithoutGroupRecordDrop = () => {
|
||||
currentRecordSorts,
|
||||
originalDragSelectionCallbackState,
|
||||
allRecordIdsWithoutGroupCallbackSelector,
|
||||
resetVirtualization,
|
||||
triggerFetchPagesWithoutDebounce,
|
||||
triggerTableWithoutGroupDragAndDropOptimisticUpdate,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useLoadRecordsToVirtualRows } from '@/object-record/record-table/virtualization/hooks/useLoadRecordsToVirtualRows';
|
||||
import { lastScrollPositionComponentState } from '@/object-record/record-table/virtualization/states/lastScrollPositionComponentState';
|
||||
import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState';
|
||||
import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState';
|
||||
import { getVirtualizationOverscanWindow } from '@/object-record/record-table/virtualization/utils/getVirtualizationOverscanWindow';
|
||||
import { type RecordWithPosition } from '@/object-record/utils/computeNewPositionOfDraggedRecord';
|
||||
import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { findById, isDefined } from 'twenty-shared/utils';
|
||||
import { sortByProperty } from '~/utils/array/sortByProperty';
|
||||
|
||||
// TODO: does not work when scrolling while dragging and does not work if not paired with a network refetch right after
|
||||
// But it's sufficient right now for the main use case
|
||||
export const useTriggerTableWithoutGroupDragAndDropOptimisticUpdate = () => {
|
||||
const recordIdByRealIndexCallbackFamilyState =
|
||||
useRecoilComponentCallbackState(recordIdByRealIndexComponentFamilyState);
|
||||
|
||||
const lastScrollPositionCallbackState = useRecoilComponentCallbackState(
|
||||
lastScrollPositionComponentState,
|
||||
);
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement();
|
||||
const totalNumberOfRecordsToVirtualizeCallbackState =
|
||||
useRecoilComponentCallbackState(
|
||||
totalNumberOfRecordsToVirtualizeComponentState,
|
||||
);
|
||||
|
||||
const { loadRecordsToVirtualRows } = useLoadRecordsToVirtualRows();
|
||||
|
||||
const triggerTableWithoutGroupDragAndDropOptimisticUpdate = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(updatedRecords: RecordWithPosition[]) => {
|
||||
const tableScrollWrapperHeight =
|
||||
scrollWrapperHTMLElement?.clientHeight ?? 0;
|
||||
|
||||
const lastScrollPosition = getSnapshotValue(
|
||||
snapshot,
|
||||
lastScrollPositionCallbackState,
|
||||
);
|
||||
|
||||
const totalNumberOfRecordsToVirtualize =
|
||||
getSnapshotValue(
|
||||
snapshot,
|
||||
totalNumberOfRecordsToVirtualizeCallbackState,
|
||||
) ?? 0;
|
||||
|
||||
const {
|
||||
firstRealIndexInOverscanWindow,
|
||||
lastRealIndexInOverscanWindow,
|
||||
} = getVirtualizationOverscanWindow(
|
||||
lastScrollPosition,
|
||||
tableScrollWrapperHeight,
|
||||
totalNumberOfRecordsToVirtualize,
|
||||
);
|
||||
|
||||
const recordsInOverscanWindowToReorder: RecordWithPosition[] = [];
|
||||
|
||||
for (
|
||||
let realIndex = firstRealIndexInOverscanWindow;
|
||||
realIndex <= lastRealIndexInOverscanWindow;
|
||||
realIndex++
|
||||
) {
|
||||
const recordIdAtRealIndex = getSnapshotValue(
|
||||
snapshot,
|
||||
recordIdByRealIndexCallbackFamilyState({ realIndex }),
|
||||
);
|
||||
|
||||
if (!isDefined(recordIdAtRealIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const correspondingRecordInStore = getSnapshotValue(
|
||||
snapshot,
|
||||
recordStoreFamilyState(recordIdAtRealIndex),
|
||||
);
|
||||
|
||||
if (
|
||||
isDefined(correspondingRecordInStore) &&
|
||||
isDefined(correspondingRecordInStore.position)
|
||||
) {
|
||||
const correspondingDraggedRecord = updatedRecords.find(
|
||||
findById(correspondingRecordInStore.id),
|
||||
);
|
||||
|
||||
const hasRecordBeenDragged = isDefined(correspondingDraggedRecord);
|
||||
|
||||
const positionToUse = hasRecordBeenDragged
|
||||
? correspondingDraggedRecord.position
|
||||
: correspondingRecordInStore.position;
|
||||
|
||||
recordsInOverscanWindowToReorder.push({
|
||||
id: correspondingRecordInStore.id,
|
||||
position: positionToUse,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const shouldReorderRecordsInOverscanWindow =
|
||||
recordsInOverscanWindowToReorder.length > 0;
|
||||
|
||||
if (!shouldReorderRecordsInOverscanWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reorderedRecordsWithPosition =
|
||||
recordsInOverscanWindowToReorder.toSorted(sortByProperty('position'));
|
||||
|
||||
loadRecordsToVirtualRows({
|
||||
records: reorderedRecordsWithPosition as any[],
|
||||
startingRealIndex: firstRealIndexInOverscanWindow,
|
||||
});
|
||||
},
|
||||
[
|
||||
lastScrollPositionCallbackState,
|
||||
loadRecordsToVirtualRows,
|
||||
recordIdByRealIndexCallbackFamilyState,
|
||||
scrollWrapperHTMLElement?.clientHeight,
|
||||
totalNumberOfRecordsToVirtualizeCallbackState,
|
||||
],
|
||||
);
|
||||
|
||||
return { triggerTableWithoutGroupDragAndDropOptimisticUpdate };
|
||||
};
|
||||
+1
@@ -32,6 +32,7 @@ export const RecordTableDraggableTr = ({
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
key={recordId}
|
||||
draggableId={recordId}
|
||||
index={draggableIndex}
|
||||
isDragDisabled={isDragDisabled}
|
||||
|
||||
+78
-7
@@ -1,15 +1,26 @@
|
||||
import { useActiveFieldMetadataItems } from '@/object-metadata/hooks/useActiveFieldMetadataItems';
|
||||
import { useGetObjectMetadataItemById } from '@/object-metadata/hooks/useGetObjectMetadataItemById';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useResetVirtualizationBecauseDataChanged } from '@/object-record/record-table/virtualization/hooks/useResetVirtualizationBecauseDataChanged';
|
||||
import { lastObjectOperationThatResettedVirtualizationComponentState } from '@/object-record/record-table/virtualization/states/lastObjectOperationThatResettedVirtualizationComponentState';
|
||||
import { objectOperationsByObjectNameSingularFamilyState } from '@/object-record/states/objectOperationsByObjectNameSingularFamilyState';
|
||||
import { objectOperationsState } from '@/object-record/states/objectOperationsComponentState';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined, mapById } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
export const RecordTableVirtualizedDataChangedEffect = () => {
|
||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
const { objectNameSingular } = useRecordTableContextOrThrow();
|
||||
|
||||
const { getObjectMetadataItemById } = useGetObjectMetadataItemById();
|
||||
|
||||
const { resetVirtualizationBecauseDataChanged } =
|
||||
useResetVirtualizationBecauseDataChanged(objectNameSingular);
|
||||
|
||||
@@ -20,12 +31,22 @@ export const RecordTableVirtualizedDataChangedEffect = () => {
|
||||
lastObjectOperationThatResettedVirtualizationComponentState,
|
||||
);
|
||||
|
||||
const objectOperationsForObjectNameSingular = useRecoilValue(
|
||||
objectOperationsByObjectNameSingularFamilyState({ objectNameSingular }),
|
||||
const { activeFieldMetadataItems } = useActiveFieldMetadataItems({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const currentRecordSorts = useRecoilComponentValue(
|
||||
currentRecordSortsComponentState,
|
||||
);
|
||||
|
||||
const currentRecordFilters = useRecoilComponentValue(
|
||||
currentRecordFiltersComponentState,
|
||||
);
|
||||
|
||||
const objectOperations = useRecoilValue(objectOperationsState);
|
||||
|
||||
useEffect(() => {
|
||||
const lastObjectOperation = objectOperationsForObjectNameSingular.at(-1);
|
||||
const lastObjectOperation = objectOperations.at(-1);
|
||||
|
||||
if (
|
||||
!isDefined(lastObjectOperationThatResettedVirtualization) &&
|
||||
@@ -41,14 +62,64 @@ export const RecordTableVirtualizedDataChangedEffect = () => {
|
||||
setLastObjectOperationThatResettedVirtualization(lastObjectOperation);
|
||||
|
||||
if (isDefined(lastObjectOperation)) {
|
||||
resetVirtualizationBecauseDataChanged(lastObjectOperation);
|
||||
if (
|
||||
lastObjectOperation.objectMetadataItemId !== objectMetadataItem.id
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastObjectOperation.data.type === 'update-one') {
|
||||
const updatedFieldNames =
|
||||
Object.keys(lastObjectOperation?.data.result.updateInput ?? {}) ??
|
||||
[];
|
||||
|
||||
const updatedFieldMetadataItems = activeFieldMetadataItems.filter(
|
||||
(fieldMetadataItemToFilter) =>
|
||||
updatedFieldNames.includes(fieldMetadataItemToFilter.name) ||
|
||||
(fieldMetadataItemToFilter.type === FieldMetadataType.RELATION &&
|
||||
updatedFieldNames.includes(
|
||||
`${fieldMetadataItemToFilter.name}Id`,
|
||||
)),
|
||||
);
|
||||
|
||||
const updatedFieldMetadataItemIds =
|
||||
updatedFieldMetadataItems.map(mapById);
|
||||
|
||||
const thereIsAnUpdateOnAFilteredField = currentRecordFilters.some(
|
||||
(recordFilter) =>
|
||||
updatedFieldMetadataItemIds.includes(
|
||||
recordFilter.fieldMetadataId,
|
||||
),
|
||||
);
|
||||
|
||||
const thereIsAnUpdateOnASortedField = currentRecordSorts.some(
|
||||
(recordSort) =>
|
||||
updatedFieldMetadataItemIds.includes(recordSort.fieldMetadataId),
|
||||
);
|
||||
|
||||
if (updatedFieldNames.includes('position')) {
|
||||
resetVirtualizationBecauseDataChanged();
|
||||
} else if (
|
||||
thereIsAnUpdateOnAFilteredField ||
|
||||
thereIsAnUpdateOnASortedField
|
||||
) {
|
||||
resetVirtualizationBecauseDataChanged();
|
||||
}
|
||||
} else {
|
||||
resetVirtualizationBecauseDataChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [
|
||||
lastObjectOperationThatResettedVirtualization,
|
||||
objectOperationsForObjectNameSingular,
|
||||
objectOperations,
|
||||
setLastObjectOperationThatResettedVirtualization,
|
||||
resetVirtualizationBecauseDataChanged,
|
||||
activeFieldMetadataItems,
|
||||
currentRecordFilters,
|
||||
currentRecordSorts,
|
||||
getObjectMetadataItemById,
|
||||
objectMetadataItem.id,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const TABLE_VIRTUALIZATION_NUMBER_OF_OVERSCAN_PAGES = 3;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const TABLE_VIRTUALIZATION_NUMBER_OF_RECORDS_PER_PAGE = 10;
|
||||
+58
-54
@@ -3,20 +3,20 @@ import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRec
|
||||
import { useRecordsFieldVisibleGqlFields } from '@/object-record/record-field/hooks/useRecordsFieldVisibleGqlFields';
|
||||
|
||||
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { useTriggerFetchPages } from '@/object-record/record-table/virtualization/hooks/useTriggerFetchPages';
|
||||
import { dataLoadingStatusByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/dataLoadingStatusByRealIndexComponentFamilyState';
|
||||
import { dataPagesLoadedComponentState } from '@/object-record/record-table/virtualization/states/dataPagesLoadedComponentState';
|
||||
import { lastScrollPositionComponentState } from '@/object-record/record-table/virtualization/states/lastScrollPositionComponentState';
|
||||
import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState';
|
||||
import { tableHasAnyFilterOrSortComponentSelector } from '@/object-record/record-table/virtualization/states/tableHasAnyFilterOrSortComponentSelector';
|
||||
import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState';
|
||||
import { type ObjectOperation } from '@/object-record/states/objectOperationsByObjectNameSingularFamilyState';
|
||||
import { getVirtualizationOverscanWindow } from '@/object-record/record-table/virtualization/utils/getVirtualizationOverscanWindow';
|
||||
import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilComponentFamilyCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyCallbackState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
|
||||
export const useResetVirtualizationBecauseDataChanged = (
|
||||
objectNameSingular: string,
|
||||
@@ -26,6 +26,7 @@ export const useResetVirtualizationBecauseDataChanged = (
|
||||
});
|
||||
|
||||
const params = useFindManyRecordIndexTableParams(objectNameSingular);
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement();
|
||||
|
||||
// TODO: we could optimize this by using an aggregate or using only id: true in recordGqlFields
|
||||
const recordGqlFields = useRecordsFieldVisibleGqlFields({
|
||||
@@ -48,6 +49,10 @@ export const useResetVirtualizationBecauseDataChanged = (
|
||||
dataPagesLoadedComponentState,
|
||||
);
|
||||
|
||||
const lastScrollPositionCallbackState = useRecoilComponentCallbackState(
|
||||
lastScrollPositionComponentState,
|
||||
);
|
||||
|
||||
const recordIdByRealIndexCallbackState =
|
||||
useRecoilComponentFamilyCallbackState(
|
||||
recordIdByRealIndexComponentFamilyState,
|
||||
@@ -58,10 +63,6 @@ export const useResetVirtualizationBecauseDataChanged = (
|
||||
dataLoadingStatusByRealIndexComponentFamilyState,
|
||||
);
|
||||
|
||||
const recordIndexAllRecordIdsSelector = useRecoilComponentCallbackState(
|
||||
recordIndexAllRecordIdsComponentSelector,
|
||||
);
|
||||
|
||||
const { triggerFetchPagesWithoutDebounce } = useTriggerFetchPages();
|
||||
|
||||
const resetVirtualization = useRecoilCallback(
|
||||
@@ -69,68 +70,71 @@ export const useResetVirtualizationBecauseDataChanged = (
|
||||
async () => {
|
||||
const { totalCount } = await findManyRecordsLazy();
|
||||
|
||||
const currentRecordIds = getSnapshotValue(
|
||||
const tableScrollWrapperHeight =
|
||||
scrollWrapperHTMLElement?.clientHeight ?? 0;
|
||||
|
||||
const lastScrollPosition = getSnapshotValue(
|
||||
snapshot,
|
||||
recordIndexAllRecordIdsSelector,
|
||||
lastScrollPositionCallbackState,
|
||||
);
|
||||
|
||||
for (const [index] of currentRecordIds.entries()) {
|
||||
set(
|
||||
dataLoadingStatusByRealIndexCallbackState({
|
||||
realIndex: index,
|
||||
}),
|
||||
null,
|
||||
);
|
||||
const totalNumberOfRecordsToVirtualize =
|
||||
getSnapshotValue(
|
||||
snapshot,
|
||||
totalNumberOfRecordsToVirtualizeCallbackState,
|
||||
) ?? 0;
|
||||
|
||||
set(
|
||||
recordIdByRealIndexCallbackState({
|
||||
realIndex: index,
|
||||
}),
|
||||
null,
|
||||
);
|
||||
const {
|
||||
firstRealIndexInOverscanWindow,
|
||||
lastRealIndexInOverscanWindow,
|
||||
} = getVirtualizationOverscanWindow(
|
||||
lastScrollPosition,
|
||||
tableScrollWrapperHeight,
|
||||
totalNumberOfRecordsToVirtualize,
|
||||
);
|
||||
|
||||
for (let i = 0; i < totalNumberOfRecordsToVirtualize; i++) {
|
||||
const indexIsInOverscanWindow =
|
||||
i >= firstRealIndexInOverscanWindow &&
|
||||
i <= lastRealIndexInOverscanWindow;
|
||||
|
||||
if (!indexIsInOverscanWindow) {
|
||||
set(
|
||||
dataLoadingStatusByRealIndexCallbackState({
|
||||
realIndex: i,
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
||||
set(
|
||||
recordIdByRealIndexCallbackState({
|
||||
realIndex: i,
|
||||
}),
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
set(
|
||||
recordIndexAllRecordIdsSelector,
|
||||
currentRecordIds.slice(0, totalCount),
|
||||
);
|
||||
set(dataPagesLoadedCallbackState, []);
|
||||
set(totalNumberOfRecordsToVirtualizeCallbackState, totalCount);
|
||||
},
|
||||
[
|
||||
dataPagesLoadedCallbackState,
|
||||
recordIdByRealIndexCallbackState,
|
||||
dataLoadingStatusByRealIndexCallbackState,
|
||||
recordIndexAllRecordIdsSelector,
|
||||
findManyRecordsLazy,
|
||||
scrollWrapperHTMLElement?.clientHeight,
|
||||
lastScrollPositionCallbackState,
|
||||
totalNumberOfRecordsToVirtualizeCallbackState,
|
||||
dataPagesLoadedCallbackState,
|
||||
dataLoadingStatusByRealIndexCallbackState,
|
||||
recordIdByRealIndexCallbackState,
|
||||
],
|
||||
);
|
||||
|
||||
const tableHasAnyFilterOrSort = useRecoilComponentValue(
|
||||
tableHasAnyFilterOrSortComponentSelector,
|
||||
);
|
||||
const resetVirtualizationBecauseDataChanged = useCallback(async () => {
|
||||
await resetVirtualization();
|
||||
|
||||
const resetVirtualizationBecauseDataChanged = useCallback(
|
||||
async (objectOperation: ObjectOperation) => {
|
||||
if (!objectOperation.data.type.startsWith('update')) {
|
||||
await resetVirtualization();
|
||||
await sleep(50);
|
||||
|
||||
await triggerFetchPagesWithoutDebounce();
|
||||
} else {
|
||||
if (tableHasAnyFilterOrSort) {
|
||||
await resetVirtualization();
|
||||
|
||||
await triggerFetchPagesWithoutDebounce();
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
resetVirtualization,
|
||||
triggerFetchPagesWithoutDebounce,
|
||||
tableHasAnyFilterOrSort,
|
||||
],
|
||||
);
|
||||
await triggerFetchPagesWithoutDebounce();
|
||||
}, [resetVirtualization, triggerFetchPagesWithoutDebounce]);
|
||||
|
||||
return {
|
||||
resetVirtualizationBecauseDataChanged,
|
||||
|
||||
+14
-35
@@ -1,13 +1,14 @@
|
||||
import { useLazyFindManyRecordsWithOffset } from '@/object-record/hooks/useLazyFindManyRecordsWithOffset';
|
||||
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
|
||||
import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { TABLE_VIRTUALIZATION_NUMBER_OF_RECORDS_PER_PAGE } from '@/object-record/record-table/virtualization/constants/TableVirtualizationNumberOfRecordsPerPage';
|
||||
import { useLoadRecordsToVirtualRows } from '@/object-record/record-table/virtualization/hooks/useLoadRecordsToVirtualRows';
|
||||
|
||||
import { dataPagesLoadedComponentState } from '@/object-record/record-table/virtualization/states/dataPagesLoadedComponentState';
|
||||
import { lastScrollPositionComponentState } from '@/object-record/record-table/virtualization/states/lastScrollPositionComponentState';
|
||||
import { lowDetailsActivatedComponentState } from '@/object-record/record-table/virtualization/states/lowDetailsActivatedComponentState';
|
||||
import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState';
|
||||
import { getVirtualizationOverscanWindow } from '@/object-record/record-table/virtualization/utils/getVirtualizationOverscanWindow';
|
||||
import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
@@ -23,9 +24,6 @@ const TIME_BETWEEN_UI_BATCH_UPDATE = 25;
|
||||
const PAGING_FOR_UI_UPDATE = 10;
|
||||
const TIME_BETWEEN_TWO_REQUETS = 25;
|
||||
|
||||
const DATA_PAGE_SIZE = 10;
|
||||
const DATA_PAGE_OVERSCAN = 3;
|
||||
|
||||
export const useTriggerFetchPages = () => {
|
||||
const { objectNameSingular } = useRecordTableContextOrThrow();
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement();
|
||||
@@ -75,40 +73,18 @@ export const useTriggerFetchPages = () => {
|
||||
lastScrollPositionCallbackState,
|
||||
);
|
||||
|
||||
const numberOfRowsDisplayedInTable = Math.min(
|
||||
Math.floor(tableScrollWrapperHeight / (RECORD_TABLE_ROW_HEIGHT + 1)),
|
||||
30,
|
||||
);
|
||||
|
||||
const halfNumberOfRowsVisible = Math.floor(
|
||||
numberOfRowsDisplayedInTable / 2,
|
||||
);
|
||||
|
||||
const realIndexAtTheMiddleOfTheTable =
|
||||
Math.floor(lastScrollPosition / (RECORD_TABLE_ROW_HEIGHT + 1)) +
|
||||
halfNumberOfRowsVisible;
|
||||
|
||||
const pageForRealIndex = Math.ceil(
|
||||
realIndexAtTheMiddleOfTheTable / DATA_PAGE_SIZE,
|
||||
);
|
||||
|
||||
const totalNumberOfRealIndices =
|
||||
const totalNumberOfRecordsToVirtualize =
|
||||
getSnapshotValue(
|
||||
snapshot,
|
||||
totalNumberOfRecordsToVirtualizeCallbackState,
|
||||
) ?? 0;
|
||||
|
||||
const maxPage = Math.ceil(totalNumberOfRealIndices / DATA_PAGE_SIZE);
|
||||
|
||||
const overscanPageAtTop = Math.max(
|
||||
0,
|
||||
pageForRealIndex - DATA_PAGE_OVERSCAN,
|
||||
);
|
||||
|
||||
const overscanPageAtBottom = Math.min(
|
||||
maxPage,
|
||||
pageForRealIndex + DATA_PAGE_OVERSCAN,
|
||||
);
|
||||
const { overscanPageAtBottom, overscanPageAtTop } =
|
||||
getVirtualizationOverscanWindow(
|
||||
lastScrollPosition,
|
||||
tableScrollWrapperHeight,
|
||||
totalNumberOfRecordsToVirtualize,
|
||||
);
|
||||
|
||||
const pagesAlreadyLoaded = getSnapshotValue(
|
||||
snapshot,
|
||||
@@ -150,10 +126,13 @@ export const useTriggerFetchPages = () => {
|
||||
|
||||
if (pagesAreContiguous) {
|
||||
const startingRealIndexToFetch =
|
||||
(pagesToFetch.at(0) ?? 0) * DATA_PAGE_SIZE;
|
||||
(pagesToFetch.at(0) ?? 0) *
|
||||
TABLE_VIRTUALIZATION_NUMBER_OF_RECORDS_PER_PAGE;
|
||||
|
||||
const endingRealIndexToFetch =
|
||||
(pagesToFetch.at(-1) ?? 0) * DATA_PAGE_SIZE + DATA_PAGE_SIZE;
|
||||
(pagesToFetch.at(-1) ?? 0) *
|
||||
TABLE_VIRTUALIZATION_NUMBER_OF_RECORDS_PER_PAGE +
|
||||
TABLE_VIRTUALIZATION_NUMBER_OF_RECORDS_PER_PAGE;
|
||||
|
||||
const numberOfRecordsToFetch =
|
||||
endingRealIndexToFetch - startingRealIndexToFetch;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { type ObjectOperation } from '@/object-record/states/objectOperationsByObjectNameSingularFamilyState';
|
||||
import { type ObjectOperation } from '@/object-record/states/objectOperationsComponentState';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
import { type Nullable } from 'twenty-shared/types';
|
||||
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { recordIdByRealIndexComponentFamilyState } from '@/object-record/record-table/virtualization/states/recordIdByRealIndexComponentFamilyState';
|
||||
import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState';
|
||||
import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const realIndexByRecordIdComponentSelector = createComponentSelector<{
|
||||
realIndexByRecordIdMap: Map<string, number>;
|
||||
}>({
|
||||
key: 'realIndexByRecordIdComponentSelector',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
get:
|
||||
({ instanceId }) =>
|
||||
({ get }) => {
|
||||
const totalNumberOfRecordsToVirtualize = get(
|
||||
totalNumberOfRecordsToVirtualizeComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
const realIndexByRecordIdMap = new Map<string, number>();
|
||||
|
||||
if (!isDefined(totalNumberOfRecordsToVirtualize)) {
|
||||
return { realIndexByRecordIdMap };
|
||||
}
|
||||
|
||||
for (
|
||||
let realIndex = 0;
|
||||
realIndex < totalNumberOfRecordsToVirtualize;
|
||||
realIndex++
|
||||
) {
|
||||
const recordId = get(
|
||||
recordIdByRealIndexComponentFamilyState.atomFamily({
|
||||
instanceId,
|
||||
familyKey: { realIndex },
|
||||
}),
|
||||
);
|
||||
|
||||
if (isDefined(recordId)) {
|
||||
realIndexByRecordIdMap.set(recordId, realIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return { realIndexByRecordIdMap };
|
||||
},
|
||||
});
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight';
|
||||
import { TABLE_VIRTUALIZATION_NUMBER_OF_OVERSCAN_PAGES } from '@/object-record/record-table/virtualization/constants/TableVirtualizationNumberOfOverscanPages';
|
||||
import { TABLE_VIRTUALIZATION_NUMBER_OF_RECORDS_PER_PAGE } from '@/object-record/record-table/virtualization/constants/TableVirtualizationNumberOfRecordsPerPage';
|
||||
|
||||
export const getVirtualizationOverscanWindow = (
|
||||
scrollPosition: number,
|
||||
scrollWrapperHeight: number,
|
||||
totalNumberOfRecordsToVirtualize: number,
|
||||
) => {
|
||||
const numberOfRowsDisplayedInTable = Math.min(
|
||||
Math.floor(scrollWrapperHeight / (RECORD_TABLE_ROW_HEIGHT + 1)),
|
||||
30,
|
||||
);
|
||||
|
||||
const halfNumberOfRowsVisible = Math.floor(numberOfRowsDisplayedInTable / 2);
|
||||
|
||||
const realIndexAtTheMiddleOfTheTable =
|
||||
Math.floor(scrollPosition / (RECORD_TABLE_ROW_HEIGHT + 1)) +
|
||||
halfNumberOfRowsVisible;
|
||||
|
||||
const pageForRealIndex = Math.ceil(
|
||||
realIndexAtTheMiddleOfTheTable /
|
||||
TABLE_VIRTUALIZATION_NUMBER_OF_RECORDS_PER_PAGE,
|
||||
);
|
||||
|
||||
const maxPage = Math.ceil(
|
||||
totalNumberOfRecordsToVirtualize /
|
||||
TABLE_VIRTUALIZATION_NUMBER_OF_RECORDS_PER_PAGE,
|
||||
);
|
||||
|
||||
const overscanPageAtTop = Math.max(
|
||||
0,
|
||||
pageForRealIndex - TABLE_VIRTUALIZATION_NUMBER_OF_OVERSCAN_PAGES,
|
||||
);
|
||||
|
||||
const overscanPageAtBottom = Math.min(
|
||||
maxPage,
|
||||
pageForRealIndex + TABLE_VIRTUALIZATION_NUMBER_OF_OVERSCAN_PAGES,
|
||||
);
|
||||
|
||||
const firstRealIndexInOverscanWindow =
|
||||
overscanPageAtTop * TABLE_VIRTUALIZATION_NUMBER_OF_RECORDS_PER_PAGE;
|
||||
|
||||
const lastRealIndexInOverscanWindow =
|
||||
overscanPageAtBottom * TABLE_VIRTUALIZATION_NUMBER_OF_RECORDS_PER_PAGE;
|
||||
|
||||
return {
|
||||
overscanPageAtTop,
|
||||
overscanPageAtBottom,
|
||||
firstRealIndexInOverscanWindow,
|
||||
lastRealIndexInOverscanWindow,
|
||||
};
|
||||
};
|
||||
+6
-8
@@ -1,18 +1,16 @@
|
||||
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
|
||||
export type ObjectOperationData =
|
||||
| {
|
||||
type: 'update-one';
|
||||
result: {
|
||||
updateInput: any;
|
||||
updatedRecord: any;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: 'update-many';
|
||||
result: {
|
||||
updateInputs: any[];
|
||||
updatedRecords: any[];
|
||||
};
|
||||
}
|
||||
| {
|
||||
@@ -30,12 +28,12 @@ export type ObjectOperationData =
|
||||
|
||||
export type ObjectOperation = {
|
||||
id: string;
|
||||
objectMetadataItemId: string;
|
||||
data: ObjectOperationData;
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
export const objectOperationsByObjectNameSingularFamilyState =
|
||||
createFamilyState<ObjectOperation[], { objectNameSingular: string }>({
|
||||
key: 'objectOperationsByObjectNameSingularFamilyState',
|
||||
defaultValue: [],
|
||||
});
|
||||
export const objectOperationsState = createState<ObjectOperation[]>({
|
||||
key: 'objectOperationsState',
|
||||
defaultValue: [],
|
||||
});
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation';
|
||||
import { useStopWorkflowRunMutation } from '~/generated-metadata/graphql';
|
||||
|
||||
@@ -9,17 +11,20 @@ export const useStopWorkflowRun = () => {
|
||||
});
|
||||
const { registerObjectOperation } = useRegisterObjectOperation();
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkflowRun,
|
||||
});
|
||||
|
||||
const stopWorkflowRun = async (workflowRunId: string) => {
|
||||
const { data } = await mutate({
|
||||
await mutate({
|
||||
variables: {
|
||||
workflowRunId,
|
||||
},
|
||||
});
|
||||
|
||||
registerObjectOperation('workflowRun', {
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'update-one',
|
||||
result: {
|
||||
updatedRecord: data?.stopWorkflowRun,
|
||||
updateInput: { id: workflowRunId },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export const mapById = <T extends { id: string }>(itemToMap: T) => {
|
||||
return itemToMap.id;
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export const mapByProperty =
|
||||
<T extends { id: string }>(propertyName: keyof T) =>
|
||||
(itemToMap: T) => {
|
||||
return itemToMap[propertyName];
|
||||
};
|
||||
@@ -14,6 +14,8 @@ export { findByProperty } from './array/findByProperty';
|
||||
export { findOrThrow } from './array/findOrThrow';
|
||||
export { getContiguousIncrementalValues } from './array/getContiguousIncrementalValues';
|
||||
export { isNonEmptyArray } from './array/isNonEmptyArray';
|
||||
export { mapById } from './array/mapById';
|
||||
export { mapByProperty } from './array/mapByProperty';
|
||||
export { sumByProperty } from './array/sumByProperty';
|
||||
export { upsertIntoArrayOfObjectsComparingId } from './array/upsertIntoArrayOfObjectComparingId';
|
||||
export { assertUnreachable } from './assertUnreachable';
|
||||
|
||||
Reference in New Issue
Block a user