Workflow statuses update on record table - use cache instead of web sockets (#16391)

Updated the 3 hooks - activate, deactivate and discard draft
(deleteVersion) - so those updates the cache.
Removed the update listeners.

Also wrapped a few actions to unsure workflowId is not undefined when
received by useWorkflowWithCurrentVersion hook. Otherwise, useFindRecord
with by performed with skip true, disconnecting tmp from the cache and
making it miss a statuses update.



https://github.com/user-attachments/assets/5fc355e8-cf18-4881-855b-744eb253b79e
This commit is contained in:
Thomas Trompette
2025-12-08 17:17:13 +01:00
committed by GitHub
parent ac89b5aff6
commit 1c14567bf1
10 changed files with 348 additions and 89 deletions
@@ -5,11 +5,16 @@ import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { ACTIVATE_WORKFLOW_VERSION } from '@/workflow/graphql/mutations/activateWorkflowVersion';
import { type WorkflowVersion } from '@/workflow/types/Workflow';
import {
type Workflow,
type WorkflowStatus,
type WorkflowVersion,
} from '@/workflow/types/Workflow';
import { isDefined } from 'twenty-shared/utils';
import {
type ActivateWorkflowVersionMutation,
@@ -30,6 +35,10 @@ export const useActivateWorkflowVersion = () => {
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
});
const { objectMetadataItem: objectMetadataItemWorkflow } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Workflow,
});
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
@@ -56,6 +65,7 @@ export const useActivateWorkflowVersion = () => {
});
const cacheSnapshot = apolloCoreClient.cache.extract();
const allWorkflowVersions: Array<WorkflowVersion> = Object.values(
cacheSnapshot,
).filter(
@@ -114,6 +124,37 @@ export const useActivateWorkflowVersion = () => {
upsertRecordsInStore,
});
}
const cachedWorkflow = getRecordFromCache<Workflow>({
objectMetadataItem: objectMetadataItemWorkflow,
cache: apolloCoreClient.cache,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
recordId: workflowId,
});
const newStatuses = new Set(
[...(cachedWorkflow?.statuses ?? []), 'ACTIVE'].filter(
(status) => status !== 'DEACTIVATED',
),
);
if (isDefined(cachedWorkflow)) {
modifyRecordFromCache({
cache: apolloCoreClient.cache,
recordId: workflowId,
objectMetadataItem: objectMetadataItemWorkflow,
fieldModifiers: {
statuses: () => Array.from(newStatuses),
},
});
upsertRecordsInStore([
{
...cachedWorkflow,
statuses: Array.from(newStatuses) as WorkflowStatus[],
},
]);
}
},
});
};
@@ -3,18 +3,23 @@ import { useMutation } from '@apollo/client';
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { DEACTIVATE_WORKFLOW_VERSION } from '@/workflow/graphql/mutations/deactivateWorkflowVersion';
import { type WorkflowVersion } from '@/workflow/types/Workflow';
import {
type Workflow,
type WorkflowStatus,
type WorkflowVersion,
} from '@/workflow/types/Workflow';
import { isDefined } from 'twenty-shared/utils';
import {
type DeactivateWorkflowVersionMutation,
type DeactivateWorkflowVersionMutationVariables,
} from '~/generated-metadata/graphql';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
export const useDeactivateWorkflowVersion = () => {
const apolloCoreClient = useApolloCoreClient();
@@ -29,6 +34,10 @@ export const useDeactivateWorkflowVersion = () => {
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { objectMetadataItem: objectMetadataItemWorkflow } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Workflow,
});
const { objectMetadataItem: objectMetadataItemWorkflowVersion } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
@@ -78,6 +87,37 @@ export const useDeactivateWorkflowVersion = () => {
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
const cachedWorkflow = getRecordFromCache<Workflow>({
objectMetadataItem: objectMetadataItemWorkflow,
cache: apolloCoreClient.cache,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
recordId: workflowVersion.workflowId,
});
const newStatuses = new Set(
[...(cachedWorkflow?.statuses ?? []), 'DEACTIVATED'].filter(
(status) => status !== 'ACTIVE',
),
);
if (isDefined(cachedWorkflow)) {
modifyRecordFromCache({
cache: apolloCoreClient.cache,
recordId: workflowVersion.workflowId,
objectMetadataItem: objectMetadataItemWorkflow,
fieldModifiers: {
statuses: () => Array.from(newStatuses),
},
});
upsertRecordsInStore([
{
...cachedWorkflow,
statuses: Array.from(newStatuses) as WorkflowStatus[],
},
]);
}
},
});
};
@@ -1,21 +1,95 @@
import { useCallback } from 'react';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { type Workflow, type WorkflowVersion } from '@/workflow/types/Workflow';
import { isDefined } from 'twenty-shared/utils';
export const useDeleteOneWorkflowVersion = () => {
const apolloCoreClient = useApolloCoreClient();
const { deleteOneRecord } = useDeleteOneRecord({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
});
const getWorkflowVersionFromCache = useGetRecordFromCache({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
});
const getWorkflowFromCache = useGetRecordFromCache({
objectNameSingular: CoreObjectNameSingular.Workflow,
});
const { objectMetadataItem: objectMetadataItemWorkflow } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Workflow,
});
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const handleUpdate = useCallback(
(workflowVersionId: string) => {
if (!workflowVersionId) {
return;
}
const cache = apolloCoreClient.cache;
const cachedWorkflowVersion =
getWorkflowVersionFromCache<WorkflowVersion>(workflowVersionId);
if (!isDefined(cachedWorkflowVersion)) {
return;
}
const cachedWorkflow = getWorkflowFromCache<Workflow>(
cachedWorkflowVersion.workflowId,
);
if (!isDefined(cachedWorkflow)) {
return;
}
modifyRecordFromCache({
objectMetadataItem: objectMetadataItemWorkflow,
cache,
recordId: cachedWorkflow.id,
fieldModifiers: {
versions: () => {
return cachedWorkflow.versions.filter(
(version) => version.id !== workflowVersionId,
);
},
statuses: () => {
return (
cachedWorkflow.statuses?.filter((status) => status !== 'DRAFT') ??
[]
);
},
},
});
upsertRecordsInStore([
{
...cachedWorkflow,
statuses:
cachedWorkflow.statuses?.filter((status) => status !== 'DRAFT') ??
[],
versions: cachedWorkflow.versions.filter(
(version) => version.id !== workflowVersionId,
),
},
]);
},
[
apolloCoreClient.cache,
getWorkflowFromCache,
getWorkflowVersionFromCache,
objectMetadataItemWorkflow,
upsertRecordsInStore,
],
);
const { deleteOneRecord } = useDeleteOneRecord({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
});
const deleteOneWorkflowVersion = async ({
workflowVersionId,
@@ -23,32 +97,7 @@ export const useDeleteOneWorkflowVersion = () => {
workflowVersionId: string;
}) => {
await deleteOneRecord(workflowVersionId);
const cachedWorkflowVersion =
getWorkflowVersionFromCache<WorkflowVersion>(workflowVersionId);
if (!isDefined(cachedWorkflowVersion)) {
return;
}
const cachedWorkflow = getWorkflowFromCache<Workflow>(
cachedWorkflowVersion.workflowId,
);
if (!isDefined(cachedWorkflow)) {
return;
}
apolloCoreClient.cache.modify({
id: apolloCoreClient.cache.identify(cachedWorkflow),
fields: {
versions: () => {
return cachedWorkflow.versions.filter(
(version) => version.id !== workflowVersionId,
);
},
},
});
handleUpdate(workflowVersionId);
};
return { deleteOneWorkflowVersion };