Only fetch steps and trigger for the current workflow version (#15003)

Avoid fetching full steps and trigger for versions that are not the
current version. Because those won't be used anyway. Better for
performances.

Only difficulty was for the `createDraft` mutation. I needed to return
the full created version so I can store it in cache and use it as new
`currentVersion`. Otherwise the current version is considered as
incomplete for a short time, since workflow is fetched separately from
the current version.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Thomas Trompette
2025-10-12 18:06:12 +02:00
committed by GitHub
parent 0fb2d66808
commit 7b84db4708
54 changed files with 485 additions and 259 deletions
@@ -1689,7 +1689,7 @@ export type Mutation = {
createCoreViewGroup: CoreViewGroup;
createCoreViewSort: CoreViewSort;
createDatabaseConfigVariable: Scalars['Boolean'];
createDraftFromWorkflowVersion: WorkflowVersion;
createDraftFromWorkflowVersion: WorkflowVersionDto;
createEmailingDomain: EmailingDomain;
createFile: File;
createOIDCIdentityProvider: SetupSsoOutput;
@@ -4468,9 +4468,16 @@ export type WorkflowStepPositionUpdateInput = {
position: WorkflowStepPositionInput;
};
export type WorkflowVersion = {
__typename?: 'WorkflowVersion';
export type WorkflowVersionDto = {
__typename?: 'WorkflowVersionDTO';
createdAt: Scalars['String'];
id: Scalars['UUID'];
name: Scalars['String'];
status: Scalars['String'];
steps?: Maybe<Scalars['JSON']>;
trigger?: Maybe<Scalars['JSON']>;
updatedAt: Scalars['String'];
workflowId: Scalars['UUID'];
};
export type WorkflowVersionStepChanges = {
@@ -5911,7 +5918,7 @@ export type CreateDraftFromWorkflowVersionMutationVariables = Exact<{
}>;
export type CreateDraftFromWorkflowVersionMutation = { __typename?: 'Mutation', createDraftFromWorkflowVersion: { __typename?: 'WorkflowVersion', id: string } };
export type CreateDraftFromWorkflowVersionMutation = { __typename?: 'Mutation', createDraftFromWorkflowVersion: { __typename?: 'WorkflowVersionDTO', id: string, name: string, status: string, trigger?: any | null, steps?: any | null, createdAt: string, updatedAt: string } };
export type CreateWorkflowVersionEdgeMutationVariables = Exact<{
input: CreateWorkflowVersionEdgeInput;
@@ -13103,6 +13110,12 @@ export const CreateDraftFromWorkflowVersionDocument = gql`
mutation CreateDraftFromWorkflowVersion($input: CreateDraftFromWorkflowVersionInput!) {
createDraftFromWorkflowVersion(input: $input) {
id
name
status
trigger
steps
createdAt
updatedAt
}
}
`;
+10 -3
View File
@@ -1646,7 +1646,7 @@ export type Mutation = {
createCoreViewGroup: CoreViewGroup;
createCoreViewSort: CoreViewSort;
createDatabaseConfigVariable: Scalars['Boolean'];
createDraftFromWorkflowVersion: WorkflowVersion;
createDraftFromWorkflowVersion: WorkflowVersionDto;
createEmailingDomain: EmailingDomain;
createFile: File;
createOIDCIdentityProvider: SetupSsoOutput;
@@ -4296,9 +4296,16 @@ export type WorkflowStepPositionUpdateInput = {
position: WorkflowStepPositionInput;
};
export type WorkflowVersion = {
__typename?: 'WorkflowVersion';
export type WorkflowVersionDto = {
__typename?: 'WorkflowVersionDTO';
createdAt: Scalars['String'];
id: Scalars['UUID'];
name: Scalars['String'];
status: Scalars['String'];
steps?: Maybe<Scalars['JSON']>;
trigger?: Maybe<Scalars['JSON']>;
updatedAt: Scalars['String'];
workflowId: Scalars['UUID'];
};
export type WorkflowVersionStepChanges = {
@@ -9,15 +9,15 @@ type CalendarEventDetailsEffectProps = {
export const CalendarEventDetailsEffect = ({
record,
}: CalendarEventDetailsEffectProps) => {
const { upsertRecords } = useUpsertRecordsInStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
useEffect(() => {
if (!record) {
return;
}
upsertRecords([record]);
}, [record, upsertRecords]);
upsertRecordsInStore([record]);
}, [record, upsertRecordsInStore]);
return <></>;
};
@@ -94,7 +94,7 @@ export const EventCardCalendarEvent = ({
}: {
calendarEventId: string;
}) => {
const { upsertRecords } = useUpsertRecordsInStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { openCalendarEventInCommandMenu } =
useOpenCalendarEventInCommandMenu();
@@ -118,7 +118,7 @@ export const EventCardCalendarEvent = ({
},
},
onCompleted: (data) => {
upsertRecords([data]);
upsertRecordsInStore([data]);
},
});
@@ -59,7 +59,7 @@ export const EventCardMessage = ({
messageId: string;
authorFullName: string;
}) => {
const { upsertRecords } = useUpsertRecordsInStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { openEmailThreadInCommandMenu } = useOpenEmailThreadInCommandMenu();
const {
@@ -80,7 +80,7 @@ export const EventCardMessage = ({
},
},
onCompleted: (data) => {
upsertRecords([data]);
upsertRecordsInStore([data]);
},
});
@@ -1,26 +1,45 @@
import { type ApolloCache, type StoreObject } from '@apollo/client';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { type ObjectPermissions } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
export const triggerAttachRelationOptimisticEffect = ({
cache,
sourceObjectNameSingular,
sourceRecordId,
targetObjectNameSingular,
targetObjectMetadataItem,
fieldNameOnTargetRecord,
targetRecordId,
upsertRecordsInStore,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
}: {
cache: ApolloCache<unknown>;
sourceObjectNameSingular: string;
sourceRecordId: string;
targetObjectNameSingular: string;
targetObjectMetadataItem: Pick<
ObjectMetadataItem,
'fields' | 'nameSingular' | 'id' | 'readableFields'
>;
fieldNameOnTargetRecord: string;
targetRecordId: string;
objectMetadataItems: ObjectMetadataItem[];
objectPermissionsByObjectMetadataId: Record<
string,
ObjectPermissions & { objectMetadataId: string }
>;
upsertRecordsInStore: (records: ObjectRecord[]) => void;
}) => {
const sourceRecordTypeName = capitalize(sourceObjectNameSingular);
const targetRecordTypeName = capitalize(targetObjectNameSingular);
const targetRecordTypeName = capitalize(
targetObjectMetadataItem.nameSingular,
);
const targetRecordCacheId = cache.identify({
id: targetRecordId,
@@ -65,6 +84,19 @@ export const triggerAttachRelationOptimisticEffect = ({
},
];
upsertRecordsInStore([
getRecordFromRecordNode({
recordNode: {
id: targetRecordId,
[fieldNameOnTargetRecord]: {
...targetRecordFieldValue,
edges: nextEdges,
},
__typename: targetRecordTypeName,
},
}),
]);
return {
...targetRecordFieldValue,
edges: nextEdges,
@@ -76,4 +108,18 @@ export const triggerAttachRelationOptimisticEffect = ({
},
},
});
const newCachedRecord = getRecordFromCache({
cache,
objectMetadataItem: targetObjectMetadataItem,
objectMetadataItems,
recordId: targetRecordId,
objectPermissionsByObjectMetadataId,
});
if (!isDefined(newCachedRecord)) {
return;
}
upsertRecordsInStore([newCachedRecord]);
};
@@ -13,6 +13,7 @@ import { type CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObje
import { encodeCursor } from '@/apollo/utils/encodeCursor';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { type ObjectPermissions } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
@@ -33,7 +34,9 @@ type TriggerCreateRecordsOptimisticEffectArgs = {
string,
ObjectPermissions & { objectMetadataId: string }
>;
upsertRecordsInStore: (records: ObjectRecord[]) => void;
};
export const triggerCreateRecordsOptimisticEffect = ({
cache,
objectMetadataItem,
@@ -42,6 +45,7 @@ export const triggerCreateRecordsOptimisticEffect = ({
shouldMatchRootQueryFilter,
checkForRecordInCache = false,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
}: TriggerCreateRecordsOptimisticEffectArgs) => {
const getRecordNodeFromCache = (recordId: string): RecordGqlNode | null => {
const cachedRecord = getRecordFromCache({
@@ -69,6 +73,8 @@ export const triggerCreateRecordsOptimisticEffect = ({
currentSourceRecord,
updatedSourceRecord: record,
objectMetadataItems,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
});
@@ -5,6 +5,8 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI
import { type RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs';
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { type ObjectPermissions } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
export const triggerDestroyRecordsOptimisticEffect = ({
@@ -12,11 +14,18 @@ export const triggerDestroyRecordsOptimisticEffect = ({
objectMetadataItem,
recordsToDestroy,
objectMetadataItems,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
}: {
cache: ApolloCache<unknown>;
objectMetadataItem: ObjectMetadataItem;
recordsToDestroy: RecordGqlNode[];
objectMetadataItems: ObjectMetadataItem[];
upsertRecordsInStore: (records: ObjectRecord[]) => void;
objectPermissionsByObjectMetadataId: Record<
string,
ObjectPermissions & { objectMetadataId: string }
>;
}) => {
cache.modify<StoreObject>({
fields: {
@@ -75,6 +84,8 @@ export const triggerDestroyRecordsOptimisticEffect = ({
currentSourceRecord: recordToDestroy,
updatedSourceRecord: null,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
cache.evict({ id: cache.identify(recordToDestroy) });
@@ -1,24 +1,42 @@
import { type ApolloCache, type StoreObject } from '@apollo/client';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs';
import { capitalize } from 'twenty-shared/utils';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { type ObjectPermissions } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
export const triggerDetachRelationOptimisticEffect = ({
cache,
sourceObjectNameSingular,
sourceRecordId,
targetObjectNameSingular,
targetObjectMetadataItem,
fieldNameOnTargetRecord,
targetRecordId,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
}: {
cache: ApolloCache<unknown>;
sourceObjectNameSingular: string;
sourceRecordId: string;
targetObjectNameSingular: string;
targetObjectMetadataItem: Pick<
ObjectMetadataItem,
'fields' | 'nameSingular' | 'id' | 'readableFields'
>;
fieldNameOnTargetRecord: string;
targetRecordId: string;
objectMetadataItems: ObjectMetadataItem[];
objectPermissionsByObjectMetadataId: Record<
string,
ObjectPermissions & { objectMetadataId: string }
>;
upsertRecordsInStore: (records: ObjectRecord[]) => void;
}) => {
const targetRecordTypeName = capitalize(targetObjectNameSingular);
const targetRecordTypeName = capitalize(
targetObjectMetadataItem.nameSingular,
);
const targetRecordCacheId = cache.identify({
id: targetRecordId,
@@ -58,4 +76,18 @@ export const triggerDetachRelationOptimisticEffect = ({
},
},
});
const newCachedRecord = getRecordFromCache({
cache,
objectMetadataItem: targetObjectMetadataItem,
objectMetadataItems,
recordId: targetRecordId,
objectPermissionsByObjectMetadataId,
});
if (!isDefined(newCachedRecord)) {
return;
}
upsertRecordsInStore([newCachedRecord]);
};
@@ -9,9 +9,10 @@ import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename';
import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs';
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter';
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { type ObjectPermissions } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
// TODO: add extensive unit tests for this function
// That will also serve as documentation
export const triggerUpdateRecordOptimisticEffect = ({
@@ -20,12 +21,19 @@ export const triggerUpdateRecordOptimisticEffect = ({
currentRecord,
updatedRecord,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
}: {
cache: ApolloCache<unknown>;
objectMetadataItem: ObjectMetadataItem;
currentRecord: RecordGqlNode;
updatedRecord: RecordGqlNode;
objectMetadataItems: ObjectMetadataItem[];
objectPermissionsByObjectMetadataId: Record<
string,
ObjectPermissions & { objectMetadataId: string }
>;
upsertRecordsInStore: (records: ObjectRecord[]) => void;
}) => {
triggerUpdateRelationsOptimisticEffect({
cache,
@@ -33,6 +41,8 @@ export const triggerUpdateRecordOptimisticEffect = ({
currentSourceRecord: currentRecord,
updatedSourceRecord: updatedRecord,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
cache.modify<StoreObject>({
@@ -9,8 +9,10 @@ import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename';
import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs';
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter';
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { type ObjectPermissions } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
// TODO: add extensive unit tests for this function
// That will also serve as documentation
@@ -20,12 +22,19 @@ export const triggerUpdateRecordOptimisticEffectByBatch = ({
currentRecords,
updatedRecords,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
}: {
cache: ApolloCache<unknown>;
objectMetadataItem: ObjectMetadataItem;
currentRecords: RecordGqlNode[];
updatedRecords: RecordGqlNode[];
objectMetadataItems: ObjectMetadataItem[];
objectPermissionsByObjectMetadataId: Record<
string,
ObjectPermissions & { objectMetadataId: string }
>;
upsertRecordsInStore: (records: ObjectRecord[]) => void;
}) => {
for (const [index, currentRecord] of currentRecords.entries()) {
triggerUpdateRelationsOptimisticEffect({
@@ -34,6 +43,8 @@ export const triggerUpdateRecordOptimisticEffectByBatch = ({
currentSourceRecord: currentRecord,
updatedSourceRecord: updatedRecords[index],
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
@@ -12,9 +12,10 @@ import { type RecordGqlConnection } from '@/object-record/graphql/types/RecordGq
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { type ApolloCache } from '@apollo/client';
import { isArray } from '@sniptt/guards';
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataType, type ObjectPermissions } from 'twenty-shared/types';
import {
computeMorphRelationFieldName,
CustomError,
@@ -28,13 +29,21 @@ type TriggerUpdateRelationsOptimisticEffectArgs = {
currentSourceRecord: RecordGqlNode | null;
updatedSourceRecord: RecordGqlNode | null;
objectMetadataItems: ObjectMetadataItem[];
objectPermissionsByObjectMetadataId: Record<
string,
ObjectPermissions & { objectMetadataId: string }
>;
upsertRecordsInStore: (records: ObjectRecord[]) => void;
};
export const triggerUpdateRelationsOptimisticEffect = ({
cache,
sourceObjectMetadataItem,
currentSourceRecord,
updatedSourceRecord,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
}: TriggerUpdateRelationsOptimisticEffectArgs) => {
const isDeletion =
isDefined(updatedSourceRecord) &&
@@ -57,6 +66,8 @@ export const triggerUpdateRelationsOptimisticEffect = ({
sourceObjectMetadataItem,
cache,
isDeletion,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
}
@@ -69,6 +80,8 @@ export const triggerUpdateRelationsOptimisticEffect = ({
sourceObjectMetadataItem,
cache,
isDeletion,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
}
});
@@ -82,6 +95,8 @@ const triggerUpdateRelationOptimisticEffect = ({
sourceObjectMetadataItem,
cache,
isDeletion,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
}: {
fieldMetadataItemOnSourceRecord: FieldMetadataItem;
updatedSourceRecord: RecordGqlNode | null;
@@ -90,6 +105,11 @@ const triggerUpdateRelationOptimisticEffect = ({
sourceObjectMetadataItem: ObjectMetadataItem;
cache: ApolloCache<unknown>;
isDeletion: boolean;
upsertRecordsInStore: (records: ObjectRecord[]) => void;
objectPermissionsByObjectMetadataId: Record<
string,
ObjectPermissions & { objectMetadataId: string }
>;
}) => {
const fieldDoesNotExist =
isDefined(updatedSourceRecord) &&
@@ -175,6 +195,8 @@ const triggerUpdateRelationOptimisticEffect = ({
objectMetadataItem: fullTargetObjectMetadataItem,
recordsToDestroy: targetRecordsToDetachFrom,
objectMetadataItems,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
} else if (isDefined(currentSourceRecord)) {
targetRecordsToDetachFrom.forEach((targetRecordToDetachFrom) => {
@@ -183,8 +205,11 @@ const triggerUpdateRelationOptimisticEffect = ({
sourceObjectNameSingular: sourceObjectMetadataItem.nameSingular,
sourceRecordId: currentSourceRecord.id,
fieldNameOnTargetRecord: gqlFieldNameOnTargetRecord,
targetObjectNameSingular: targetObjectMetadata.nameSingular,
targetObjectMetadataItem: fullTargetObjectMetadataItem,
targetRecordId: targetRecordToDetachFrom.id,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
});
}
@@ -201,8 +226,11 @@ const triggerUpdateRelationOptimisticEffect = ({
sourceObjectNameSingular: sourceObjectMetadataItem.nameSingular,
sourceRecordId: updatedSourceRecord.id,
fieldNameOnTargetRecord: gqlFieldNameOnTargetRecord,
targetObjectNameSingular: targetObjectMetadata.nameSingular,
targetObjectMetadataItem: fullTargetObjectMetadataItem,
targetRecordId: targetRecordToAttachTo.id,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
}),
);
}
@@ -216,6 +244,8 @@ const triggerUpdateMorphRelationOptimisticEffect = ({
sourceObjectMetadataItem,
cache,
isDeletion,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
}: {
fieldMetadataItemOnSourceRecord: FieldMetadataItem;
updatedSourceRecord: RecordGqlNode | null;
@@ -224,6 +254,11 @@ const triggerUpdateMorphRelationOptimisticEffect = ({
sourceObjectMetadataItem: ObjectMetadataItem;
cache: ApolloCache<unknown>;
isDeletion: boolean;
upsertRecordsInStore: (records: ObjectRecord[]) => void;
objectPermissionsByObjectMetadataId: Record<
string,
ObjectPermissions & { objectMetadataId: string }
>;
}) => {
const morphRelations = fieldMetadataItemOnSourceRecord.morphRelations;
if (!morphRelations) {
@@ -309,6 +344,8 @@ const triggerUpdateMorphRelationOptimisticEffect = ({
objectMetadataItem: fullTargetObjectMetadataItem,
recordsToDestroy: targetRecordsToDetachFrom,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
} else if (isDefined(currentSourceRecord)) {
targetRecordsToDetachFrom.forEach((targetRecordToDetachFrom) => {
@@ -317,8 +354,11 @@ const triggerUpdateMorphRelationOptimisticEffect = ({
sourceObjectNameSingular: sourceObjectMetadataItem.nameSingular,
sourceRecordId: currentSourceRecord.id,
fieldNameOnTargetRecord: targetFieldMetadata.name,
targetObjectNameSingular: targetObjectMetadata.nameSingular,
targetObjectMetadataItem: fullTargetObjectMetadataItem,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
targetRecordId: targetRecordToDetachFrom.id,
upsertRecordsInStore,
});
});
}
@@ -335,8 +375,11 @@ const triggerUpdateMorphRelationOptimisticEffect = ({
sourceObjectNameSingular: sourceObjectMetadataItem.nameSingular,
sourceRecordId: updatedSourceRecord.id,
fieldNameOnTargetRecord: targetFieldMetadata.name,
targetObjectNameSingular: targetObjectMetadata.nameSingular,
targetObjectMetadataItem: fullTargetObjectMetadataItem,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
targetRecordId: targetRecordToAttachTo.id,
upsertRecordsInStore,
}),
);
}
@@ -8,7 +8,7 @@ import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useU
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
export const CommandMenuCalendarEventPage = () => {
const { upsertRecords } = useUpsertRecordsInStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const viewableRecordId = useRecoilComponentValue(
viewableRecordIdComponentState,
);
@@ -20,7 +20,7 @@ export const CommandMenuCalendarEventPage = () => {
recordGqlFields: FIND_ONE_CALENDAR_EVENT_OPERATION_SIGNATURE.fields,
// TODO: this is not executed on sub-sequent runs, make sure that it is intended
onCompleted: (record) => {
upsertRecords([record]);
upsertRecordsInStore([record]);
},
});
@@ -21,7 +21,7 @@ export const useEmailThreadInCommandMenu = () => {
const viewableRecordId = useRecoilComponentValue(
viewableRecordIdComponentState,
);
const { upsertRecords } = useUpsertRecordsInStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const [lastMessageId, setLastMessageId] = useState<string | null>(null);
const [lastMessageChannelId, setLastMessageChannelId] = useState<
string | null
@@ -35,7 +35,7 @@ export const useEmailThreadInCommandMenu = () => {
id: true,
},
onCompleted: (record) => {
upsertRecords([record]);
upsertRecordsInStore([record]);
},
});
@@ -2,6 +2,8 @@ import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { deleteRecordFromCache } from '@/object-record/cache/utils/deleteRecordFromCache';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
export const useDeleteRecordFromCache = ({
@@ -10,11 +12,12 @@ export const useDeleteRecordFromCache = ({
objectNameSingular: string;
}) => {
const apolloCoreClient = useApolloCoreClient();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { objectMetadataItems } = useObjectMetadataItems();
return (recordToDestroy: ObjectRecord) => {
@@ -23,6 +26,8 @@ export const useDeleteRecordFromCache = ({
objectMetadataItems,
recordToDestroy,
cache: apolloCoreClient.cache,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
};
};
@@ -4,17 +4,25 @@ import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effec
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { type ObjectPermissions } from 'twenty-shared/types';
export const deleteRecordFromCache = ({
objectMetadataItem,
objectMetadataItems,
recordToDestroy,
cache,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItems: ObjectMetadataItem[];
recordToDestroy: ObjectRecord;
cache: ApolloCache<object>;
objectPermissionsByObjectMetadataId: Record<
string,
ObjectPermissions & { objectMetadataId: string }
>;
upsertRecordsInStore: (records: ObjectRecord[]) => void;
}) => {
triggerDestroyRecordsOptimisticEffect({
cache,
@@ -26,5 +34,7 @@ export const deleteRecordFromCache = ({
__typename: getObjectTypename(objectMetadataItem.nameSingular),
},
],
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
};
@@ -12,10 +12,13 @@ import { isEmptyObject } from '~/utils/isEmptyObject';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export type GetRecordFromCacheArgs = {
cache: ApolloCache<object>;
cache: ApolloCache<unknown>;
recordId: string;
objectMetadataItems: ObjectMetadataItem[];
objectMetadataItem: ObjectMetadataItem;
objectMetadataItem: Pick<
ObjectMetadataItem,
'fields' | 'nameSingular' | 'id' | 'readableFields'
>;
recordGqlFields?: RecordGqlFields;
objectPermissionsByObjectMetadataId: Record<
string,
@@ -3,7 +3,7 @@ import { generateDepthRecordGqlFieldsFromFields } from '@/object-record/graphql/
export type GenerateDepthRecordGqlFields = {
objectMetadataItems: ObjectMetadataItem[];
objectMetadataItem: ObjectMetadataItem;
objectMetadataItem: Pick<ObjectMetadataItem, 'fields'>;
depth: 0 | 1;
shouldOnlyLoadRelationIdentifiers?: boolean;
};
@@ -18,6 +18,7 @@ import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation';
import { type FieldActorForInputValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
import { getCreateManyRecordsMutationResponseField } from '@/object-record/utils/getCreateManyRecordsMutationResponseField';
@@ -51,6 +52,7 @@ export const useCreateManyRecords = <
shouldRefetchAggregateQueries = true,
}: useCreateManyRecordsProps) => {
const { registerObjectOperation } = useRegisterObjectOperation();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const apolloCoreClient = useApolloCoreClient();
@@ -171,6 +173,7 @@ export const useCreateManyRecords = <
objectMetadataItems,
shouldMatchRootQueryFilter,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
@@ -208,6 +211,7 @@ export const useCreateManyRecords = <
shouldMatchRootQueryFilter,
checkForRecordInCache: true,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
},
})
@@ -218,6 +222,8 @@ export const useCreateManyRecords = <
objectMetadataItem,
cache: apolloCoreClient.cache,
recordToDestroy,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
});
@@ -226,6 +232,8 @@ export const useCreateManyRecords = <
objectMetadataItem,
recordsToDestroy: recordsCreatedInCache,
objectMetadataItems,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
throw error;
@@ -17,6 +17,7 @@ import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRe
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { computeOptimisticCreateRecordBaseRecordInput } from '@/object-record/utils/computeOptimisticCreateRecordBaseRecordInput';
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
@@ -39,6 +40,7 @@ export const useCreateOneRecord = <
skipPostOptimisticEffect = false,
shouldMatchRootQueryFilter,
}: useCreateOneRecordProps) => {
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { registerObjectOperation } = useRegisterObjectOperation();
const apolloCoreClient = useApolloCoreClient();
const [loading, setLoading] = useState(false);
@@ -123,6 +125,7 @@ export const useCreateOneRecord = <
objectMetadataItems,
shouldMatchRootQueryFilter,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
}
@@ -147,6 +150,7 @@ export const useCreateOneRecord = <
shouldMatchRootQueryFilter,
checkForRecordInCache: true,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
@@ -163,6 +167,8 @@ export const useCreateOneRecord = <
objectMetadataItem,
cache: apolloCoreClient.cache,
recordToDestroy: recordCreatedInCache,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
triggerDestroyRecordsOptimisticEffect({
@@ -170,6 +176,8 @@ export const useCreateOneRecord = <
objectMetadataItem,
recordsToDestroy: [recordCreatedInCache],
objectMetadataItems,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
throw error;
@@ -13,6 +13,7 @@ import { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteMan
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { getDeleteManyRecordsMutationResponseField } from '@/object-record/utils/getDeleteManyRecordsMutationResponseField';
import { useRecoilValue } from 'recoil';
@@ -34,6 +35,7 @@ export const useDeleteManyRecords = ({
objectNameSingular,
}: useDeleteManyRecordProps) => {
const { registerObjectOperation } = useRegisterObjectOperation();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const apiConfig = useRecoilValue(apiConfigState);
const mutationPageSize =
@@ -133,6 +135,8 @@ export const useDeleteManyRecords = ({
currentRecords: cachedRecordsNode,
updatedRecords: computedOptimisticRecordsNode,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
@@ -203,6 +207,8 @@ export const useDeleteManyRecords = ({
currentRecords: computedOptimisticRecordsNode,
updatedRecords: cachedRecordsNode,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
throw error;
@@ -13,6 +13,7 @@ import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRe
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField';
import { isNull } from '@sniptt/guards';
@@ -26,6 +27,7 @@ export const useDeleteOneRecord = ({
objectNameSingular,
}: useDeleteOneRecordProps) => {
const { registerObjectOperation } = useRegisterObjectOperation();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const apolloCoreClient = useApolloCoreClient();
const { objectMetadataItem } = useObjectMetadataItem({
@@ -100,6 +102,8 @@ export const useDeleteOneRecord = ({
currentRecord: cachedRecordNode,
updatedRecord: optimisticRecordNode,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
@@ -121,6 +125,8 @@ export const useDeleteOneRecord = ({
currentRecord: optimisticRecordNode,
updatedRecord: record,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
},
})
@@ -150,6 +156,8 @@ export const useDeleteOneRecord = ({
currentRecord: optimisticRecordNode,
updatedRecord: cachedRecordNode,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
throw error;
@@ -164,16 +172,17 @@ export const useDeleteOneRecord = ({
return deletedRecord.data?.[mutationResponseField] ?? null;
},
[
apolloCoreClient,
deleteOneRecordMutation,
getRecordFromCache,
mutationResponseField,
apolloCoreClient,
objectMetadataItem,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
deleteOneRecordMutation,
refetchAggregateQueries,
objectNameSingular,
registerObjectOperation,
objectNameSingular,
mutationResponseField,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
],
);
@@ -10,6 +10,7 @@ import { useDestroyManyRecordsMutation } from '@/object-record/hooks/useDestroyM
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { getDestroyManyRecordsMutationResponseField } from '@/object-record/utils/getDestroyManyRecordsMutationResponseField';
import { useRecoilValue } from 'recoil';
@@ -31,6 +32,7 @@ export const useDestroyManyRecords = ({
objectNameSingular,
}: useDestroyManyRecordProps) => {
const { registerObjectOperation } = useRegisterObjectOperation();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const apiConfig = useRecoilValue(apiConfigState);
const mutationPageSize =
@@ -112,6 +114,8 @@ export const useDestroyManyRecords = ({
objectMetadataItem,
recordsToDestroy: cachedRecords,
objectMetadataItems,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
},
})
@@ -123,6 +127,7 @@ export const useDestroyManyRecords = ({
recordsToCreate: cachedRecords,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
throw error;
@@ -9,6 +9,7 @@ import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordF
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { getDestroyOneRecordMutationResponseField } from '@/object-record/utils/getDestroyOneRecordMutationResponseField';
import { capitalize, isDefined } from 'twenty-shared/utils';
@@ -21,6 +22,7 @@ export const useDestroyOneRecord = ({
objectNameSingular,
}: useDestroyOneRecordProps) => {
const { registerObjectOperation } = useRegisterObjectOperation();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const apolloCoreClient = useApolloCoreClient();
@@ -67,6 +69,8 @@ export const useDestroyOneRecord = ({
objectMetadataItem,
recordsToDestroy: [cachedRecord],
objectMetadataItems,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
},
})
@@ -78,6 +82,7 @@ export const useDestroyOneRecord = ({
recordsToCreate: [originalRecord],
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
@@ -91,15 +96,16 @@ export const useDestroyOneRecord = ({
return deletedRecord.data?.[mutationResponseField] ?? null;
},
[
getRecordFromCache,
apolloCoreClient,
destroyOneRecordMutation,
getRecordFromCache,
mutationResponseField,
objectMetadataItem,
objectNameSingular,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
registerObjectOperation,
objectMetadataItem,
objectMetadataItems,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
],
);
@@ -17,7 +17,7 @@ export const useLoadSelectedRecordsInContextStore = ({
objectRecordIds,
objectMetadataItemId,
}: UseLoadSelectedRecordsInContextStoreProps) => {
const { upsertRecords } = useUpsertRecordsInStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { findManyRecordsLazy } = useLazyFindManyRecords({
objectNameSingular,
@@ -57,10 +57,15 @@ export const useLoadSelectedRecordsInContextStore = ({
const { records } = await findManyRecordsLazy();
upsertRecords(records ?? []);
upsertRecordsInStore(records ?? []);
};
},
[objectRecordIds, objectMetadataItemId, findManyRecordsLazy, upsertRecords],
[
objectRecordIds,
objectMetadataItemId,
findManyRecordsLazy,
upsertRecordsInStore,
],
);
return {
@@ -116,14 +116,14 @@ export const useMergeManyRecords = <
}
},
[
apolloCoreClient,
findDuplicateRecordsQuery,
findOneRecordQuery,
mergeManyRecordsMutation,
objectMetadataItem.namePlural,
refetchAggregateQueries,
apolloCoreClient,
mergeManyRecordsMutation,
findOneRecordQuery,
findDuplicateRecordsQuery,
registerObjectOperation,
objectNameSingular,
refetchAggregateQueries,
],
);
@@ -11,6 +11,7 @@ import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMu
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation';
import { useRestoreManyRecordsMutation } from '@/object-record/hooks/useRestoreManyRecordsMutation';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { getRestoreManyRecordsMutationResponseField } from '@/object-record/utils/getRestoreManyRecordsMutationResponseField';
import { useRecoilValue } from 'recoil';
@@ -32,6 +33,7 @@ export const useRestoreManyRecords = ({
objectNameSingular,
}: useRestoreManyRecordProps) => {
const { registerObjectOperation } = useRegisterObjectOperation();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const apiConfig = useRecoilValue(apiConfigState);
@@ -122,6 +124,8 @@ export const useRestoreManyRecords = ({
currentRecord: cachedRecordWithConnection,
updatedRecord: optimisticRecordWithConnection,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
});
@@ -182,6 +186,8 @@ export const useRestoreManyRecords = ({
currentRecord: optimisticRecordWithConnection,
updatedRecord: cachedRecordWithConnection,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
});
@@ -13,6 +13,7 @@ import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation';
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField';
@@ -38,6 +39,7 @@ export const useUpdateOneRecord = <
recordGqlFields,
}: useUpdateOneRecordProps) => {
const { registerObjectOperation } = useRegisterObjectOperation();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const apolloCoreClient = useApolloCoreClient();
const { objectMetadataItem } = useObjectMetadataItem({
@@ -138,6 +140,8 @@ export const useUpdateOneRecord = <
currentRecord: cachedRecordWithConnection,
updatedRecord: optimisticRecordWithConnection,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
@@ -167,6 +171,8 @@ export const useUpdateOneRecord = <
currentRecord: computedOptimisticRecord,
updatedRecord: record,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
},
})
@@ -213,6 +219,8 @@ export const useUpdateOneRecord = <
currentRecord: optimisticRecordWithConnection,
updatedRecord: cachedRecordWithConnection,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
throw error;
@@ -11,6 +11,8 @@ import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField';
@@ -29,6 +31,8 @@ type UpdateOneRecordArgs<UpdatedObjectRecord> = {
};
export const useUpdateOneRecordV2 = () => {
const apolloCoreClient = useApolloCoreClient();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { registerObjectOperation } = useRegisterObjectOperation();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
@@ -133,6 +137,8 @@ export const useUpdateOneRecordV2 = () => {
currentRecord: cachedRecordWithConnection,
updatedRecord: optimisticRecordWithConnection,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
@@ -171,6 +177,8 @@ export const useUpdateOneRecordV2 = () => {
currentRecord: computedOptimisticRecord,
updatedRecord: record,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
},
})
@@ -217,11 +225,17 @@ export const useUpdateOneRecordV2 = () => {
currentRecord: optimisticRecordWithConnection,
updatedRecord: cachedRecordWithConnection,
objectMetadataItems,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
throw error;
});
registerObjectOperation(objectMetadataItem.nameSingular, {
type: 'update-one',
result: { updatedRecord, updateInput: updateOneRecordInput },
});
return updatedRecord?.data?.[mutationResponseField] ?? null;
};
@@ -26,7 +26,7 @@ export const RecordIndexCalendarDataLoaderEffect = () => {
const { objectMetadataItem } = useRecordCalendarContextOrThrow();
const { upsertRecords: upsertRecordsInStore } = useUpsertRecordsInStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const setRecordIndexAllRecordIdsSelector = useSetRecoilComponentState(
recordIndexAllRecordIdsComponentSelector,
@@ -24,13 +24,13 @@ export const RecordDetailRelationRecordsListItemEffect = ({
objectRecordId: relationRecordId,
});
const { upsertRecords } = useUpsertRecordsInStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
useEffect(() => {
if (isDefined(record)) {
upsertRecords([record]);
upsertRecordsInStore([record]);
}
}, [record, upsertRecords]);
}, [record, upsertRecordsInStore]);
return null;
};
@@ -30,6 +30,7 @@ import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { isFieldArray } from '@/object-record/record-field/ui/types/guards/isFieldArray';
@@ -42,6 +43,7 @@ import { isFieldRichText } from '@/object-record/record-field/ui/types/guards/is
import { isFieldRichTextV2 } from '@/object-record/record-field/ui/types/guards/isFieldRichTextV2';
import { isFieldRichTextValue } from '@/object-record/record-field/ui/types/guards/isFieldRichTextValue';
import { isFieldRichTextV2Value } from '@/object-record/record-field/ui/types/guards/isFieldRichTextValueV2';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { getForeignKeyNameFromRelationFieldName } from '@/object-record/utils/getForeignKeyNameFromRelationFieldName';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
@@ -72,11 +74,13 @@ export const usePersistField = ({
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular: objectMetadataItem?.nameSingular ?? '',
});
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const persistField = useRecoilCallback(
({ set, snapshot }) =>
({
async ({
recordId,
fieldDefinition,
valueToPersist,
@@ -202,7 +206,7 @@ export const usePersistField = ({
return;
}
updateOneRecord?.({
const newRecord = await updateOneRecord?.({
idToUpdate: recordId,
updateOneRecordInput: {
[getForeignKeyNameFromRelationFieldName(fieldName)]:
@@ -210,10 +214,11 @@ export const usePersistField = ({
},
});
set(
recordStoreFamilySelector({ recordId, fieldName }),
valueToPersist,
);
upsertRecordsInStore([
getRecordFromRecordNode({
recordNode: newRecord,
}),
]);
return;
}
@@ -263,19 +268,17 @@ export const usePersistField = ({
candidateObjectMetadataItem.namePlural,
});
updateOneRecord?.({
const newRecord = await updateOneRecord?.({
idToUpdate: recordId,
updateOneRecordInput: {
[`${computedFieldName}Id`]: valueToPersist.id,
},
});
set(
recordStoreFamilySelector({
recordId,
fieldName: computedFieldName,
upsertRecordsInStore([
getRecordFromRecordNode({
recordNode: newRecord,
}),
valueToPersist,
);
]);
return;
}
}
@@ -310,6 +313,7 @@ export const usePersistField = ({
objectMetadataItems,
objectPermissionsByObjectMetadataId,
updateOneRecord,
upsertRecordsInStore,
],
);
@@ -42,7 +42,7 @@ export const useLoadRecordIndexBoardColumn = ({
objectNameSingular,
});
const { setRecordIdsForColumn } = useSetRecordIdsForColumn(recordBoardId);
const { upsertRecords: upsertRecordsInStore } = useUpsertRecordsInStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const recordGroupDefinition = useRecoilValue(
recordGroupDefinitionFamilyState(columnId),
@@ -31,7 +31,7 @@ export const useMergePreview = ({
const { mergeManyRecords } = useMergeManyRecords({
objectNameSingular,
});
const { upsertRecords } = useUpsertRecordsInStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { isLoading: isLoadingRelationships } = useMergeRecordRelationships({
objectNameSingular,
@@ -56,7 +56,7 @@ export const useMergePreview = ({
}
setMergePreviewRecord(previewRecord);
upsertRecords([previewRecord]);
upsertRecordsInStore([previewRecord]);
} catch {
setMergePreviewRecord(null);
} finally {
@@ -2,9 +2,10 @@ import { useRecoilCallback } from 'recoil';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useUpsertRecordsInStore = () => {
const upsertRecords = useRecoilCallback(
const upsertRecordsInStore = useRecoilCallback(
({ set, snapshot }) =>
(records: ObjectRecord[]) => {
for (const record of records) {
@@ -12,7 +13,7 @@ export const useUpsertRecordsInStore = () => {
.getLoadable(recordStoreFamilyState(record.id))
.getValue();
if (JSON.stringify(currentRecord) !== JSON.stringify(record)) {
if (!isDeeplyEqual(currentRecord, record)) {
set(recordStoreFamilyState(record.id), {
...currentRecord,
...record,
@@ -24,6 +25,6 @@ export const useUpsertRecordsInStore = () => {
);
return {
upsertRecords,
upsertRecordsInStore,
};
};
@@ -1,9 +1,9 @@
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
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 { useAssignRecordsToStore } from '@/object-record/record-table/virtualization/hooks/useAssignRecordsToStore';
import { useLoadRecordsToVirtualRows } from '@/object-record/record-table/virtualization/hooks/useLoadRecordsToVirtualRows';
import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
@@ -34,7 +34,7 @@ export const RecordTableNoRecordGroupAddNew = () => {
);
const { loadRecordsToVirtualRows } = useLoadRecordsToVirtualRows();
const { assignRecordsToStore } = useAssignRecordsToStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const handleButtonClick = useRecoilCallback(
() => async () => {
@@ -42,7 +42,7 @@ export const RecordTableNoRecordGroupAddNew = () => {
position: 'last',
});
assignRecordsToStore({ records: [createdRecord] });
upsertRecordsInStore([createdRecord]);
if (isDefined(totalNumberOfRecordsToVirtualize)) {
loadRecordsToVirtualRows({
@@ -53,7 +53,7 @@ export const RecordTableNoRecordGroupAddNew = () => {
},
[
createNewIndexRecord,
assignRecordsToStore,
upsertRecordsInStore,
loadRecordsToVirtualRows,
totalNumberOfRecordsToVirtualize,
],
@@ -1,32 +0,0 @@
import { useRecoilCallback } from 'recoil';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useAssignRecordsToStore = () => {
const assignRecordsToStore = useRecoilCallback(
({ set, snapshot }) =>
<T extends ObjectRecord>({ records }: { records: T[] }) => {
for (const record of records) {
const currentRecord = snapshot
.getLoadable(recordStoreFamilyState(record.id))
.getValue();
if (!isDeeplyEqual(currentRecord, record)) {
const newRecord = {
...currentRecord,
...record,
};
set(recordStoreFamilyState(record.id), newRecord);
}
}
},
[],
);
return {
assignRecordsToStore,
};
};
@@ -1,7 +1,7 @@
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 { useAssignRecordsToStore } from '@/object-record/record-table/virtualization/hooks/useAssignRecordsToStore';
import { useLoadRecordsToVirtualRows } from '@/object-record/record-table/virtualization/hooks/useLoadRecordsToVirtualRows';
import { dataPagesLoadedComponentState } from '@/object-record/record-table/virtualization/states/dataPagesLoadedComponentState';
@@ -30,7 +30,7 @@ export const useTriggerFetchPages = () => {
const { objectNameSingular } = useRecordTableContextOrThrow();
const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement();
const { assignRecordsToStore } = useAssignRecordsToStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { loadRecordsToVirtualRows } = useLoadRecordsToVirtualRows();
@@ -193,7 +193,7 @@ export const useTriggerFetchPages = () => {
startingRealIndex: startingRealIndexInThisUIPage,
});
assignRecordsToStore({ records: recordsSlice });
upsertRecordsInStore(recordsSlice);
}
}
@@ -210,7 +210,7 @@ export const useTriggerFetchPages = () => {
findManyRecordsLazyWithOffset,
totalNumberOfRecordsToVirtualizeCallbackState,
loadRecordsToVirtualRows,
assignRecordsToStore,
upsertRecordsInStore,
lastScrollPositionCallbackState,
scrollWrapperHTMLElement,
lowDetailsActivatedCallbackState,
@@ -1,5 +1,6 @@
import { useRecordIndexTableFetchMore } from '@/object-record/record-index/hooks/useRecordIndexTableFetchMore';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableHorizontalScrollShadowVisibilityCssVariableName';
import { RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableVerticalScrollShadowVisibilityCssVariableName';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
@@ -8,7 +9,6 @@ import { isRecordTableInitialLoadingComponentState } from '@/object-record/recor
import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState';
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
import { updateRecordTableCSSVariable } from '@/object-record/record-table/utils/updateRecordTableCSSVariable';
import { useAssignRecordsToStore } from '@/object-record/record-table/virtualization/hooks/useAssignRecordsToStore';
import { useLoadRecordsToVirtualRows } from '@/object-record/record-table/virtualization/hooks/useLoadRecordsToVirtualRows';
import { useReapplyRowSelection } from '@/object-record/record-table/virtualization/hooks/useReapplyRowSelection';
import { useResetNumberOfRecordsToVirtualize } from '@/object-record/record-table/virtualization/hooks/useResetNumberOfRecordsToVirtualize';
@@ -92,7 +92,7 @@ export const useTriggerInitialRecordTableDataLoad = () => {
useResetNumberOfRecordsToVirtualize();
const { resetTableFocuses } = useResetTableFocuses(recordTableId);
const { assignRecordsToStore } = useAssignRecordsToStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { loadRecordsToVirtualRows } = useLoadRecordsToVirtualRows();
@@ -165,7 +165,7 @@ export const useTriggerInitialRecordTableDataLoad = () => {
totalCount,
});
assignRecordsToStore({ records });
upsertRecordsInStore(records);
loadRecordsToVirtualRows({
records,
@@ -210,7 +210,7 @@ export const useTriggerInitialRecordTableDataLoad = () => {
dataLoadingStatusByRealIndexCallbackState,
recordIdByRealIndexCallbackState,
resetNumberOfRecordsToVirtualize,
assignRecordsToStore,
upsertRecordsInStore,
loadRecordsToVirtualRows,
reapplyRowSelection,
],
@@ -157,19 +157,6 @@ export const computeOptimisticRecordFromInput = ({
continue;
}
const relationIdFieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.name === relationFieldIdName,
);
if (
!isDefined(relationIdFieldMetadataItem) &&
!isDefined(fieldMetadataItem.settings?.joinColumnName)
) {
throw new Error(
'Should never occur, encountered unknown relationId within relations definitions',
);
}
if (isNull(recordInputFieldIdValue)) {
optimisticRecord[relationFieldIdName] = null;
optimisticRecord[fieldMetadataItem.name] = null;
@@ -8,6 +8,7 @@ import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNo
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useOnDbEvent } from '@/subscription/hooks/useOnDbEvent';
@@ -49,6 +50,7 @@ export const ListenRecordUpdatesEffect = ({
},
[],
);
const { upsertRecordsInStore } = useUpsertRecordsInStore();
useOnDbEvent({
input: { recordId, action: DatabaseEventAction.UPDATED },
@@ -89,6 +91,8 @@ export const ListenRecordUpdatesEffect = ({
currentRecord: cachedRecordNode,
updatedRecord: updatedRecord,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
setRecordInStore(computedOptimisticRecord);
@@ -1,34 +1,15 @@
import { useCallback } from 'react';
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect';
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { CREATE_CORE_VIEW_FILTER_GROUP } from '@/views/graphql/mutations/createCoreViewFilterGroup';
import { DESTROY_CORE_VIEW_FILTER_GROUP } from '@/views/graphql/mutations/destroyCoreViewFilterGroup';
import { UPDATE_CORE_VIEW_FILTER_GROUP } from '@/views/graphql/mutations/updateCoreViewFilterGroup';
import { type GraphQLView } from '@/views/types/GraphQLView';
import { type ViewFilterGroup } from '@/views/types/ViewFilterGroup';
import { useApolloClient } from '@apollo/client';
import { isNull } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { type CoreViewFilterGroup } from '~/generated/graphql';
export const usePersistViewFilterGroupRecords = () => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.ViewFilterGroup,
});
const getRecordFromCache = useGetRecordFromCache({
objectNameSingular: CoreObjectNameSingular.ViewFilterGroup,
});
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const apolloClient = useApolloClient();
const createCoreViewFilterGroupRecord = useCallback(
@@ -47,18 +28,6 @@ export const usePersistViewFilterGroupRecords = () => {
viewFilterGroup.positionInViewFilterGroup,
} satisfies Partial<CoreViewFilterGroup>,
},
update: (cache, { data }) => {
const record = data?.createCoreViewFilterGroup;
if (!isDefined(record)) return;
triggerCreateRecordsOptimisticEffect({
cache,
objectMetadataItem,
recordsToCreate: [record],
objectMetadataItems,
objectPermissionsByObjectMetadataId,
});
},
});
if (!result.data) {
@@ -67,12 +36,7 @@ export const usePersistViewFilterGroupRecords = () => {
return { newRecordId: result.data.createCoreViewFilterGroup.id };
},
[
apolloClient,
objectMetadataItem,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
],
[apolloClient],
);
const createCoreViewFilterGroupRecords = useCallback(
@@ -133,29 +97,11 @@ export const usePersistViewFilterGroupRecords = () => {
viewFilterGroup.positionInViewFilterGroup,
} satisfies Partial<CoreViewFilterGroup>,
},
update: (cache, { data }) => {
const record = data?.updateCoreViewFilterGroup;
if (!isDefined(record)) return;
const cachedRecord = getRecordFromCache<ViewFilterGroup>(
record.id,
cache,
);
if (isNull(cachedRecord)) return;
triggerUpdateRecordOptimisticEffect({
cache,
objectMetadataItem,
currentRecord: cachedRecord,
updatedRecord: record,
objectMetadataItems,
});
},
}),
),
);
},
[apolloClient, getRecordFromCache, objectMetadataItem, objectMetadataItems],
[apolloClient],
);
const deleteCoreViewFilterGroupRecords = useCallback(
@@ -168,25 +114,11 @@ export const usePersistViewFilterGroupRecords = () => {
variables: {
id: viewFilterGroupId,
},
update: (cache, { data }) => {
const record = data?.destroyCoreViewFilterGroup;
if (!isDefined(record)) return;
const cachedRecord = getRecordFromCache(record.id, cache);
if (isNull(cachedRecord)) return;
triggerDestroyRecordsOptimisticEffect({
cache,
objectMetadataItem,
recordsToDestroy: [cachedRecord],
objectMetadataItems,
});
},
}),
),
);
},
[apolloClient, getRecordFromCache, objectMetadataItem, objectMetadataItems],
[apolloClient],
);
return {
@@ -6,6 +6,12 @@ export const CREATE_DRAFT_FROM_WORKFLOW_VERSION = gql`
) {
createDraftFromWorkflowVersion(input: $input) {
id
name
status
trigger
steps
createdAt
updatedAt
}
}
`;
@@ -3,8 +3,11 @@ 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 { 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 { isDefined } from 'twenty-shared/utils';
@@ -21,11 +24,15 @@ export const useActivateWorkflowVersion = () => {
>(ACTIVATE_WORKFLOW_VERSION, {
client: apolloCoreClient,
});
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { objectMetadataItem: objectMetadataItemWorkflowVersion } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
});
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const activateWorkflowVersion = async ({
workflowVersionId,
@@ -75,7 +82,9 @@ export const useActivateWorkflowVersion = () => {
...newlyActiveWorkflowVersion,
status: 'ACTIVE',
},
objectMetadataItems: [objectMetadataItemWorkflowVersion],
objectMetadataItems: objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
@@ -100,7 +109,9 @@ export const useActivateWorkflowVersion = () => {
...workflowVersion,
status: 'ARCHIVED',
},
objectMetadataItems: [objectMetadataItemWorkflowVersion],
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
},
@@ -5,6 +5,8 @@ import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
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 { isDefined } from 'twenty-shared/utils';
@@ -12,9 +14,12 @@ import {
type DeactivateWorkflowVersionMutation,
type DeactivateWorkflowVersionMutationVariables,
} from '~/generated-metadata/graphql';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
export const useDeactivateWorkflowVersion = () => {
const apolloCoreClient = useApolloCoreClient();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { objectMetadataItems } = useObjectMetadataItems();
const [mutate] = useMutation<
DeactivateWorkflowVersionMutation,
DeactivateWorkflowVersionMutationVariables
@@ -22,6 +27,8 @@ export const useDeactivateWorkflowVersion = () => {
client: apolloCoreClient,
});
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { objectMetadataItem: objectMetadataItemWorkflowVersion } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
@@ -67,7 +74,9 @@ export const useDeactivateWorkflowVersion = () => {
...workflowVersion,
status: 'DEACTIVATED',
},
objectMetadataItems: [objectMetadataItemWorkflowVersion],
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
},
});
@@ -11,6 +11,7 @@ import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { computeOptimisticCreateRecordBaseRecordInput } from '@/object-record/utils/computeOptimisticCreateRecordBaseRecordInput';
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
@@ -27,6 +28,7 @@ import {
export const useRunWorkflowVersion = () => {
const apolloCoreClient = useApolloCoreClient();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.WorkflowRun,
@@ -128,6 +130,7 @@ export const useRunWorkflowVersion = () => {
objectMetadataItems,
shouldMatchRootQueryFilter: true,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
setRecordInStore(recordCreatedInCache);
@@ -2,16 +2,21 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import {
type Workflow,
type WorkflowVersion,
type WorkflowWithCurrentVersion,
} from '@/workflow/types/Workflow';
import { useMemo } from 'react';
import { isDefined } from 'twenty-shared/utils';
type WorkflowWithAllVersions = Omit<Workflow, 'versions'> & {
versions: Array<
Pick<WorkflowVersion, 'id' | 'status' | 'name' | 'createdAt'>
>;
};
export const useWorkflowWithCurrentVersion = (
workflowId: string | undefined,
): WorkflowWithCurrentVersion | undefined => {
// TODO: we should only load the data for the current version
const { record: workflow } = useFindOneRecord<Workflow>({
const { record: workflow } = useFindOneRecord<WorkflowWithAllVersions>({
objectNameSingular: CoreObjectNameSingular.Workflow,
objectRecordId: workflowId,
recordGqlFields: {
@@ -23,41 +28,38 @@ export const useWorkflowWithCurrentVersion = (
id: true,
status: true,
name: true,
steps: true,
trigger: true,
workflowId: true,
deletedAt: true,
createdAt: true,
updatedAt: true,
},
},
skip: !isDefined(workflowId),
});
return useMemo(() => {
if (!isDefined(workflow)) {
return undefined;
}
const draftVersion = workflow?.versions.find(
(workflowVersion) => workflowVersion.status === 'DRAFT',
);
const draftVersion = workflow.versions.find(
(workflowVersion) => workflowVersion.status === 'DRAFT',
);
const workflowVersions = [...(workflow?.versions ?? [])];
const workflowVersions = [...workflow.versions];
workflowVersions.sort((a, b) => (a.createdAt > b.createdAt ? -1 : 1));
workflowVersions.sort((a, b) => (a.createdAt > b.createdAt ? -1 : 1));
const latestVersion = workflowVersions[0];
const latestVersion = workflowVersions[0];
const currentVersionWithoutSteps = draftVersion ?? latestVersion;
const currentVersion = draftVersion ?? latestVersion;
const { record: currentVersionWithSteps } = useFindOneRecord<WorkflowVersion>(
{
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
objectRecordId: currentVersionWithoutSteps?.id,
skip: !isDefined(currentVersionWithoutSteps?.id),
},
);
if (!isDefined(currentVersion)) {
return undefined;
}
if (!isDefined(workflow) || !isDefined(currentVersionWithSteps)) {
return undefined;
}
return {
...workflow,
currentVersion,
};
}, [workflow]);
return {
...workflow,
currentVersion: currentVersionWithSteps,
};
};
@@ -131,7 +131,9 @@ export type Workflow = {
__typename: 'Workflow';
id: string;
name: string;
versions: Array<WorkflowVersion>;
versions: Array<
Pick<WorkflowVersion, 'id' | 'status' | 'name' | 'createdAt'>
>;
lastPublishedVersionId: string;
statuses: Array<WorkflowStatus> | null;
};
@@ -38,6 +38,8 @@ export const WorkflowDiagramEffect = () => {
workflowLastCreatedStepIdComponentState,
);
const currentVersion = workflowWithCurrentVersion?.currentVersion;
const computeAndMergeNewWorkflowDiagram = useRecoilCallback(
({ snapshot, set }) => {
return (currentVersion: WorkflowVersion) => {
@@ -84,13 +86,10 @@ export const WorkflowDiagramEffect = () => {
[workflowDiagramState, workflowLastCreatedStepIdState],
);
const currentVersion = workflowWithCurrentVersion?.currentVersion;
useEffect(() => {
if (!isDefined(currentVersion)) {
setFlow(undefined);
setWorkflowDiagram(undefined);
// setFlow(undefined);
// setWorkflowDiagram(undefined);
return;
}
@@ -1,15 +1,15 @@
import { useEffect } from 'react';
import { type Decorator } from '@storybook/react';
import { useEffect } from 'react';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
export const RecordStoreDecorator: Decorator = (Story, context) => {
const { records } = context.parameters;
const { upsertRecords } = useUpsertRecordsInStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
useEffect(() => {
upsertRecords(records);
upsertRecordsInStore(records);
});
return <Story />;
@@ -10,10 +10,10 @@ export const JestRecordStoreSetter = ({
children: ReactNode;
records: ObjectRecord[];
}) => {
const { upsertRecords } = useUpsertRecordsInStore();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
useEffect(() => {
upsertRecords(records);
upsertRecordsInStore(records);
});
return <>{children}</>;
@@ -1,9 +1,35 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import GraphQLJSON from 'graphql-type-json';
@ObjectType('WorkflowVersion')
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
@ObjectType('WorkflowVersionDTO')
export class WorkflowVersionDTO {
@Field(() => UUIDScalarType)
id: string;
@Field(() => String)
name: string;
@Field(() => String)
createdAt: string;
@Field(() => String)
updatedAt: string;
@Field(() => UUIDScalarType)
workflowId: string;
@Field(() => String)
status: WorkflowVersionStatus;
@Field(() => GraphQLJSON, { nullable: true })
trigger: WorkflowTrigger | null;
@Field(() => GraphQLJSON, { nullable: true })
steps: WorkflowAction[] | null;
}
@@ -4,6 +4,7 @@ import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
import { CreateDraftFromWorkflowVersionInput } from 'src/engine/core-modules/workflow/dtos/create-draft-from-workflow-version-input.dto';
import { UpdateWorkflowVersionPositionsInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-version-positions-input.dto';
import { WorkflowVersionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version.dto';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
@@ -13,7 +14,6 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
import { WorkflowVersionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service';
import { UpdateWorkflowVersionPositionsInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-version-positions-input.dto';
@Resolver()
@UsePipes(ResolverValidationPipe)
@@ -40,15 +40,11 @@ export class WorkflowVersionResolver {
workflowVersionIdToCopy,
}: CreateDraftFromWorkflowVersionInput,
): Promise<WorkflowVersionDTO> {
return {
id: await this.workflowVersionWorkspaceService.createDraftFromWorkflowVersion(
{
workspaceId,
workflowId,
workflowVersionIdToCopy,
},
),
};
return this.workflowVersionWorkspaceService.createDraftFromWorkflowVersion({
workspaceId,
workflowId,
workflowVersionIdToCopy,
});
}
@Mutation(() => Boolean)
@@ -115,7 +115,11 @@ export class WorkflowVersionWorkspaceService {
trigger: newWorkflowVersionTrigger,
});
return draftWorkflowVersion.id;
return {
...draftWorkflowVersion,
steps: newWorkflowVersionSteps,
trigger: newWorkflowVersionTrigger,
};
}
async updateWorkflowVersionPositions({