Activity injection into Apollo cache (#3665)
- Created addRecordInCache to inject a record in Apollo cache and inject single read query on this record - Created createOneRecordInCache and createManyRecordsInCache that uses this addRecordInCache - Created useOpenCreateActivityDrawerV2 hook to create an activity in cache and inject it into all other relevant requests in the app before opening activity drawer - Refactored DEFAULT_SEARCH_REQUEST_LIMIT constant and hardcoded arbitrary request limits - Added Apollo dev logs to see errors in the console when manipulating cache
This commit is contained in:
@ -6,6 +6,7 @@ import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { ApolloProvider } from '@/apollo/components/ApolloProvider';
|
||||
import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider';
|
||||
import { ApolloDevLogEffect } from '@/debug/components/ApolloDevLogEffect';
|
||||
import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver';
|
||||
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
||||
import { ExceptionHandlerProvider } from '@/error-handler/components/ExceptionHandlerProvider';
|
||||
@ -35,6 +36,7 @@ root.render(
|
||||
<RecoilRoot>
|
||||
<AppErrorBoundary>
|
||||
<RecoilDebugObserverEffect />
|
||||
<ApolloDevLogEffect />
|
||||
<BrowserRouter>
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<IconsProvider>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { ActivityTargetObjectRecord } from '@/activities/types/ActivityTargetObject';
|
||||
@ -15,41 +14,40 @@ export const useActivityTargetObjectRecords = ({
|
||||
}) => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const { records: activityTargets } = useFindManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
filter: {
|
||||
activityId: {
|
||||
eq: activityId,
|
||||
const { records: activityTargets, loading: loadingActivityTargets } =
|
||||
useFindManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
filter: {
|
||||
activityId: {
|
||||
eq: activityId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const activityTargetObjectRecords = useMemo(() => {
|
||||
return activityTargets
|
||||
.map<Nullable<ActivityTargetObjectRecord>>((activityTarget) => {
|
||||
const correspondingObjectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
isDefined(activityTarget[objectMetadataItem.nameSingular]) &&
|
||||
!objectMetadataItem.isSystem,
|
||||
);
|
||||
const activityTargetObjectRecords = activityTargets
|
||||
.map<Nullable<ActivityTargetObjectRecord>>((activityTarget) => {
|
||||
const correspondingObjectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
isDefined(activityTarget[objectMetadataItem.nameSingular]) &&
|
||||
!objectMetadataItem.isSystem,
|
||||
);
|
||||
|
||||
if (!correspondingObjectMetadataItem) {
|
||||
return null;
|
||||
}
|
||||
if (!correspondingObjectMetadataItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
activityTargetRecord: activityTarget,
|
||||
targetObjectRecord:
|
||||
activityTarget[correspondingObjectMetadataItem.nameSingular],
|
||||
targetObjectMetadataItem: correspondingObjectMetadataItem,
|
||||
targetObjectNameSingular:
|
||||
correspondingObjectMetadataItem.nameSingular,
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
}, [activityTargets, objectMetadataItems]);
|
||||
return {
|
||||
activityTargetRecord: activityTarget,
|
||||
targetObjectRecord:
|
||||
activityTarget[correspondingObjectMetadataItem.nameSingular],
|
||||
targetObjectMetadataItem: correspondingObjectMetadataItem,
|
||||
targetObjectNameSingular: correspondingObjectMetadataItem.nameSingular,
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
return {
|
||||
activityTargetObjectRecords,
|
||||
loadingActivityTargets,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTargetObjectFilterFieldName';
|
||||
@ -13,17 +15,26 @@ export const useActivityTargets = ({
|
||||
nameSingular: targetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
const { records: activityTargets } = useFindManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
filter: {
|
||||
[targetObjectFieldName]: {
|
||||
eq: targetableObject.id,
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
const { records: activityTargets, loading: loadingActivityTargets } =
|
||||
useFindManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
filter: {
|
||||
[targetObjectFieldName]: {
|
||||
eq: targetableObject.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
skip: !targetableObject.id,
|
||||
});
|
||||
onCompleted: () => {
|
||||
if (!initialized) {
|
||||
setInitialized(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
activityTargets: activityTargets as ActivityTarget[],
|
||||
loadingActivityTargets,
|
||||
initialized,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,76 +0,0 @@
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
|
||||
export const useHandleCheckableActivityTargetChange = ({
|
||||
activityId,
|
||||
currentActivityTargets,
|
||||
}: {
|
||||
activityId: string;
|
||||
currentActivityTargets: any[];
|
||||
}) => {
|
||||
const { createOneRecord: createOneActivityTarget } =
|
||||
useCreateOneRecord<ActivityTarget>({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
const { deleteOneRecord: deleteOneActivityTarget } = useDeleteOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
return async (
|
||||
entityValues: Record<string, boolean>,
|
||||
entitiesToSelect: any,
|
||||
selectedEntities: any,
|
||||
) => {
|
||||
if (!activityId) {
|
||||
return;
|
||||
}
|
||||
const currentActivityTargetRecordIds = currentActivityTargets.map(
|
||||
({ companyId, personId }) => companyId ?? personId ?? '',
|
||||
);
|
||||
|
||||
const idsToAdd = Object.entries(entityValues)
|
||||
.filter(
|
||||
([recordId, value]) =>
|
||||
value && !currentActivityTargetRecordIds.includes(recordId),
|
||||
)
|
||||
.map(([id, _]) => id);
|
||||
|
||||
const idsToDelete = Object.entries(entityValues)
|
||||
.filter(([_, value]) => !value)
|
||||
.map(([id, _]) => id);
|
||||
|
||||
if (idsToAdd.length) {
|
||||
idsToAdd.forEach((id) => {
|
||||
const entityFromToSelect = entitiesToSelect.filter(
|
||||
(entity: any) => entity.id === id,
|
||||
).length
|
||||
? entitiesToSelect.filter((entity: any) => entity.id === id)[0]
|
||||
: null;
|
||||
|
||||
const entityFromSelected = selectedEntities.filter(
|
||||
(entity: any) => entity.id === id,
|
||||
).length
|
||||
? selectedEntities.filter((entity: any) => entity.id === id)[0]
|
||||
: null;
|
||||
|
||||
const entity = entityFromToSelect ?? entityFromSelected;
|
||||
createOneActivityTarget?.({
|
||||
activityId: activityId,
|
||||
companyId: entity.record.__typename === 'Company' ? entity.id : null,
|
||||
personId: entity.record.__typename === 'Person' ? entity.id : null,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (idsToDelete.length) {
|
||||
idsToDelete.forEach((id) => {
|
||||
const currentActivityTargetId = currentActivityTargets.filter(
|
||||
({ companyId, personId }) => companyId === id || personId === id,
|
||||
)[0].id;
|
||||
deleteOneActivityTarget?.(currentActivityTargetId);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,46 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useModifyRecordFromCache } from '@/object-record/cache/hooks/useModifyRecordFromCache';
|
||||
import { getCacheReferenceFromRecord } from '@/object-record/cache/utils/getCacheReferenceFromRecord';
|
||||
|
||||
export const useModifyActivityOnActivityTargetsCache = () => {
|
||||
const { objectMetadataItem: objectMetadataItemActivityTarget } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const modifyActivityTargetFromCache = useModifyRecordFromCache({
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
});
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const modifyActivityOnActivityTargetsCache = ({
|
||||
activityTargetIds,
|
||||
activity,
|
||||
}: {
|
||||
activityTargetIds: string[];
|
||||
activity: Activity;
|
||||
}) => {
|
||||
for (const activityTargetId of activityTargetIds) {
|
||||
modifyActivityTargetFromCache(activityTargetId, {
|
||||
activity: () => {
|
||||
const newActivityReference = getCacheReferenceFromRecord({
|
||||
apolloClient,
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
record: activity,
|
||||
});
|
||||
|
||||
return newActivityReference;
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
modifyActivityOnActivityTargetsCache,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,51 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { CachedObjectRecordConnection } from '@/apollo/types/CachedObjectRecordConnection';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useModifyRecordFromCache } from '@/object-record/cache/hooks/useModifyRecordFromCache';
|
||||
import { getCachedRecordEdgesFromRecords } from '@/object-record/cache/utils/getCachedRecordEdgesFromRecords';
|
||||
|
||||
export const useModifyActivityTargetsOnActivityCache = () => {
|
||||
const { objectMetadataItem: objectMetadataItemActivity } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const modifyActivityFromCache = useModifyRecordFromCache({
|
||||
objectMetadataItem: objectMetadataItemActivity,
|
||||
});
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const modifyActivityTargetsOnActivityCache = ({
|
||||
activityId,
|
||||
activityTargets,
|
||||
}: {
|
||||
activityId: string;
|
||||
activityTargets: ActivityTarget[];
|
||||
}) => {
|
||||
modifyActivityFromCache(activityId, {
|
||||
activityTargets: (
|
||||
activityTargetsCachedConnection: CachedObjectRecordConnection,
|
||||
) => {
|
||||
const newActivityTargetsCachedRecordEdges =
|
||||
getCachedRecordEdgesFromRecords({
|
||||
apolloClient,
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
records: activityTargets,
|
||||
});
|
||||
|
||||
return {
|
||||
...activityTargetsCachedConnection,
|
||||
edges: newActivityTargetsCachedRecordEdges,
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
modifyActivityTargetsOnActivityCache,
|
||||
};
|
||||
};
|
||||
@ -1,8 +1,10 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { ActivityType } from '@/activities/types/Activity';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { ActivityTargetableObject } from '../types/ActivityTargetableEntity';
|
||||
|
||||
@ -27,21 +29,35 @@ export const useOpenCreateActivityDrawerForSelectedRowIds = (
|
||||
getSelectedRowIdsSelector(),
|
||||
);
|
||||
|
||||
let activityTargetableEntityArray: ActivityTargetableObject[] =
|
||||
selectedRowIds.map((id: string) => ({
|
||||
type: 'Custom',
|
||||
targetObjectNameSingular: objectNameSingular,
|
||||
id,
|
||||
}));
|
||||
let activityTargetableObjectArray: ActivityTargetableObject[] =
|
||||
selectedRowIds
|
||||
.map((recordId: string) => {
|
||||
const targetObjectRecord = getSnapshotValue(
|
||||
snapshot,
|
||||
recordStoreFamilyState(recordId),
|
||||
);
|
||||
|
||||
if (!targetObjectRecord) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Custom',
|
||||
targetObjectNameSingular: objectNameSingular,
|
||||
id: recordId,
|
||||
targetObjectRecord,
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
if (relatedEntities) {
|
||||
activityTargetableEntityArray =
|
||||
activityTargetableEntityArray.concat(relatedEntities);
|
||||
activityTargetableObjectArray =
|
||||
activityTargetableObjectArray.concat(relatedEntities);
|
||||
}
|
||||
|
||||
openCreateActivityDrawer({
|
||||
type,
|
||||
targetableObjects: activityTargetableEntityArray,
|
||||
targetableObjects: activityTargetableObjectArray,
|
||||
});
|
||||
},
|
||||
[openCreateActivityDrawer, getSelectedRowIdsSelector],
|
||||
|
||||
@ -0,0 +1,163 @@
|
||||
import { useCallback } from 'react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useActivityTargets } from '@/activities/hooks/useActivityTargets';
|
||||
import { useModifyActivityOnActivityTargetsCache } from '@/activities/hooks/useModifyActivityOnActivityTargetCache';
|
||||
import { useModifyActivityTargetsOnActivityCache } from '@/activities/hooks/useModifyActivityTargetsOnActivityCache';
|
||||
import { useWriteActivityTargetsInCache } from '@/activities/hooks/useWriteActivityTargetsInCache';
|
||||
import { useInjectIntoActivityTargetInlineCellCache } from '@/activities/inline-cell/hooks/useInjectIntoActivityTargetInlineCellCache';
|
||||
import { useInjectIntoTimelineActivitiesQuery } from '@/activities/timeline/hooks/useInjectIntoTimelineActivitiesQuery';
|
||||
import { Activity, ActivityType } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { getActivityTargetsToCreateFromTargetableObjects } from '@/activities/utils/getActivityTargetsToCreateFromTargetableObjects';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateManyRecordsInCache } from '@/object-record/hooks/useCreateManyRecordsInCache';
|
||||
import { useCreateOneRecordInCache } from '@/object-record/hooks/useCreateOneRecordInCache';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { mapToRecordId } from '@/object-record/utils/mapToObjectId';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
|
||||
import { activityTargetableEntityArrayState } from '../states/activityTargetableEntityArrayState';
|
||||
import { viewableActivityIdState } from '../states/viewableActivityIdState';
|
||||
import { ActivityTargetableObject } from '../types/ActivityTargetableEntity';
|
||||
|
||||
export const useOpenCreateActivityDrawerV2 = ({
|
||||
targetableObject,
|
||||
}: {
|
||||
targetableObject: ActivityTargetableObject;
|
||||
}) => {
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
|
||||
const { createManyRecordsInCache: createManyActivityTargetsInCache } =
|
||||
useCreateManyRecordsInCache<ActivityTarget>({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const { createOneRecordInCache: createOneActivityInCache } =
|
||||
useCreateOneRecordInCache<Activity>({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const { record: workspaceMemberRecord } = useFindOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
|
||||
objectRecordId: currentWorkspaceMember?.id,
|
||||
});
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const [, setActivityTargetableEntityArray] = useRecoilState(
|
||||
activityTargetableEntityArrayState,
|
||||
);
|
||||
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
|
||||
|
||||
const { activityTargets } = useActivityTargets({
|
||||
targetableObject,
|
||||
});
|
||||
|
||||
const { injectIntoTimelineActivitiesNextQuery } =
|
||||
useInjectIntoTimelineActivitiesQuery();
|
||||
|
||||
const { injectIntoActivityTargetInlineCellCache } =
|
||||
useInjectIntoActivityTargetInlineCellCache();
|
||||
|
||||
const { injectIntoUseActivityTargets } = useWriteActivityTargetsInCache();
|
||||
|
||||
const { modifyActivityTargetsOnActivityCache } =
|
||||
useModifyActivityTargetsOnActivityCache();
|
||||
|
||||
const { modifyActivityOnActivityTargetsCache } =
|
||||
useModifyActivityOnActivityTargetsCache();
|
||||
|
||||
return useCallback(
|
||||
async ({
|
||||
type,
|
||||
targetableObjects,
|
||||
assigneeId,
|
||||
}: {
|
||||
type: ActivityType;
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
assigneeId?: string;
|
||||
}) => {
|
||||
const activityId = v4();
|
||||
|
||||
const createdActivityInCache = await createOneActivityInCache({
|
||||
id: activityId,
|
||||
author: workspaceMemberRecord,
|
||||
authorId: workspaceMemberRecord?.id,
|
||||
assignee: !assigneeId ? workspaceMemberRecord : undefined,
|
||||
assigneeId:
|
||||
assigneeId ?? isNonEmptyString(workspaceMemberRecord?.id)
|
||||
? workspaceMemberRecord?.id
|
||||
: undefined,
|
||||
type: type,
|
||||
});
|
||||
|
||||
if (!createdActivityInCache) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activityTargetsToCreate =
|
||||
getActivityTargetsToCreateFromTargetableObjects({
|
||||
activityId,
|
||||
targetableObjects,
|
||||
});
|
||||
|
||||
const createdActivityTargetsInCache =
|
||||
await createManyActivityTargetsInCache(activityTargetsToCreate);
|
||||
|
||||
injectIntoUseActivityTargets({
|
||||
targetableObject,
|
||||
activityTargetsToInject: createdActivityTargetsInCache,
|
||||
});
|
||||
|
||||
injectIntoTimelineActivitiesNextQuery({
|
||||
activityTargets,
|
||||
activityToInject: createdActivityInCache,
|
||||
});
|
||||
|
||||
injectIntoActivityTargetInlineCellCache({
|
||||
activityId,
|
||||
activityTargetsToInject: createdActivityTargetsInCache,
|
||||
});
|
||||
|
||||
modifyActivityTargetsOnActivityCache({
|
||||
activityId,
|
||||
activityTargets: createdActivityTargetsInCache,
|
||||
});
|
||||
|
||||
modifyActivityOnActivityTargetsCache({
|
||||
activityTargetIds: createdActivityTargetsInCache.map(mapToRecordId),
|
||||
activity: createdActivityInCache,
|
||||
});
|
||||
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
setViewableActivityId(activityId);
|
||||
setActivityTargetableEntityArray(targetableObjects ?? []);
|
||||
openRightDrawer(RightDrawerPages.CreateActivity);
|
||||
},
|
||||
[
|
||||
openRightDrawer,
|
||||
setActivityTargetableEntityArray,
|
||||
createManyActivityTargetsInCache,
|
||||
setHotkeyScope,
|
||||
setViewableActivityId,
|
||||
createOneActivityInCache,
|
||||
workspaceMemberRecord,
|
||||
activityTargets,
|
||||
targetableObject,
|
||||
injectIntoTimelineActivitiesNextQuery,
|
||||
injectIntoActivityTargetInlineCellCache,
|
||||
injectIntoUseActivityTargets,
|
||||
modifyActivityTargetsOnActivityCache,
|
||||
modifyActivityOnActivityTargetsCache,
|
||||
],
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,84 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTargetObjectFilterFieldName';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
|
||||
export const useWriteActivityTargetsInCache = () => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const {
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
findManyRecordsQuery: findManyActivityTargetsQuery,
|
||||
} = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const injectIntoUseActivityTargets = ({
|
||||
targetableObject,
|
||||
activityTargetsToInject,
|
||||
}: {
|
||||
targetableObject: Pick<
|
||||
ActivityTargetableObject,
|
||||
'id' | 'targetObjectNameSingular'
|
||||
>;
|
||||
activityTargetsToInject: ActivityTarget[];
|
||||
}) => {
|
||||
const targetObjectFieldName = getActivityTargetObjectFieldIdName({
|
||||
nameSingular: targetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
const existingActivityTargetsForTargetableObjectQueryResult =
|
||||
apolloClient.readQuery({
|
||||
query: findManyActivityTargetsQuery,
|
||||
variables: {
|
||||
filter: {
|
||||
[targetObjectFieldName]: {
|
||||
eq: targetableObject.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const existingActivityTargetsForTargetableObject =
|
||||
getRecordsFromRecordConnection({
|
||||
recordConnection: existingActivityTargetsForTargetableObjectQueryResult[
|
||||
objectMetadataItemActivityTarget.namePlural
|
||||
] as ObjectRecordConnection<ActivityTarget>,
|
||||
});
|
||||
|
||||
const newActivityTargetsForTargetableObject = [
|
||||
...existingActivityTargetsForTargetableObject,
|
||||
...activityTargetsToInject,
|
||||
];
|
||||
|
||||
const newActivityTargetsConnection = getRecordConnectionFromRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
records: newActivityTargetsForTargetableObject,
|
||||
});
|
||||
|
||||
apolloClient.writeQuery({
|
||||
query: findManyActivityTargetsQuery,
|
||||
variables: {
|
||||
filter: {
|
||||
[targetObjectFieldName]: {
|
||||
eq: targetableObject.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
[objectMetadataItemActivityTarget.namePlural]:
|
||||
newActivityTargetsConnection,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
injectIntoUseActivityTargets,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,58 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { getRecordConnectionFromEdges } from '@/object-record/cache/utils/getRecordConnectionFromEdges';
|
||||
import { getRecordEdgeFromRecord } from '@/object-record/cache/utils/getRecordEdgeFromRecord';
|
||||
|
||||
export const useInjectIntoActivityTargetInlineCellCache = () => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const {
|
||||
objectMetadataItem: objectMetadataItemActivityTarget,
|
||||
findManyRecordsQuery: findManyActivityTargetsQuery,
|
||||
} = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
});
|
||||
|
||||
const injectIntoActivityTargetInlineCellCache = ({
|
||||
activityId,
|
||||
activityTargetsToInject,
|
||||
}: {
|
||||
activityId: string;
|
||||
activityTargetsToInject: ActivityTarget[];
|
||||
}) => {
|
||||
const newActivityTargetEdgesForCache = activityTargetsToInject.map(
|
||||
(activityTargetToInject) =>
|
||||
getRecordEdgeFromRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
record: activityTargetToInject,
|
||||
}),
|
||||
);
|
||||
|
||||
const newActivityTargetConnectionForCache = getRecordConnectionFromEdges({
|
||||
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
|
||||
edges: newActivityTargetEdgesForCache,
|
||||
});
|
||||
|
||||
apolloClient.writeQuery({
|
||||
query: findManyActivityTargetsQuery,
|
||||
variables: {
|
||||
filter: {
|
||||
activityId: {
|
||||
eq: activityId,
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
[objectMetadataItemActivityTarget.namePlural]:
|
||||
newActivityTargetConnectionForCache,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
injectIntoActivityTargetInlineCellCache,
|
||||
};
|
||||
};
|
||||
@ -14,6 +14,7 @@ export const PageAddTaskButton = ({
|
||||
const { selectedFilter } = useFilterDropdown({
|
||||
filterDropdownId: filterDropdownId,
|
||||
});
|
||||
|
||||
const openCreateActivity = useOpenCreateActivityDrawer();
|
||||
|
||||
const handleClick = () => {
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { ActivityCreateButton } from '@/activities/components/ActivityCreateButton';
|
||||
import { useActivityTargets } from '@/activities/hooks/useActivityTargets';
|
||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { useTimelineActivities } from '@/activities/timeline/hooks/useTimelineActivities';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
|
||||
import { TimelineItemsContainer } from './TimelineItemsContainer';
|
||||
@ -55,26 +50,22 @@ export const Timeline = ({
|
||||
}: {
|
||||
targetableObject: ActivityTargetableObject;
|
||||
}) => {
|
||||
const { activityTargets } = useActivityTargets({ targetableObject });
|
||||
|
||||
const { records: activities } = useFindManyRecords({
|
||||
skip: !activityTargets?.length,
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
filter: {
|
||||
id: {
|
||||
in: activityTargets
|
||||
?.map((activityTarget) => activityTarget.activityId)
|
||||
.filter(isNonEmptyString),
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'AscNullsFirst',
|
||||
},
|
||||
const { activities, initialized } = useTimelineActivities({
|
||||
targetableObject,
|
||||
});
|
||||
|
||||
const openCreateActivity = useOpenCreateActivityDrawer();
|
||||
|
||||
if (!activities.length) {
|
||||
const showEmptyState = initialized && activities.length === 0;
|
||||
|
||||
const showLoadingState = !initialized;
|
||||
|
||||
if (showLoadingState) {
|
||||
// TODO: Display a beautiful loading page
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (showEmptyState) {
|
||||
return (
|
||||
<StyledTimelineEmptyContainer>
|
||||
<StyledEmptyTimelineTitle>No activity yet</StyledEmptyTimelineTitle>
|
||||
@ -99,7 +90,7 @@ export const Timeline = ({
|
||||
|
||||
return (
|
||||
<StyledMainContainer>
|
||||
<TimelineItemsContainer activities={activities as Activity[]} />
|
||||
<TimelineItemsContainer activities={activities} />
|
||||
</StyledMainContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { makeTimelineActivitiesQueryVariables } from '@/activities/timeline/utils/makeTimelineActivitiesQueryVariables';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
|
||||
export const useInjectIntoTimelineActivitiesQuery = () => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const {
|
||||
objectMetadataItem: objectMetadataItemActivity,
|
||||
findManyRecordsQuery: findManyActivitiesQuery,
|
||||
} = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
});
|
||||
|
||||
const injectIntoTimelineActivitiesQuery = ({
|
||||
activityTargets,
|
||||
activityToInject,
|
||||
}: {
|
||||
activityTargets: ActivityTarget[];
|
||||
activityToInject: Activity;
|
||||
}) => {
|
||||
const activityIds = activityTargets
|
||||
?.map((activityTarget) => activityTarget.activityId)
|
||||
.filter(isNonEmptyString);
|
||||
|
||||
const timelineActivitiesQueryVariables =
|
||||
makeTimelineActivitiesQueryVariables({
|
||||
activityIds,
|
||||
});
|
||||
|
||||
const exitistingActivitiesQueryResult = apolloClient.readQuery({
|
||||
query: findManyActivitiesQuery,
|
||||
variables: timelineActivitiesQueryVariables,
|
||||
});
|
||||
|
||||
const extistingActivities = exitistingActivitiesQueryResult
|
||||
? getRecordsFromRecordConnection({
|
||||
recordConnection: exitistingActivitiesQueryResult[
|
||||
objectMetadataItemActivity.namePlural
|
||||
] as ObjectRecordConnection<Activity>,
|
||||
})
|
||||
: [];
|
||||
|
||||
const newActivity = {
|
||||
...activityToInject,
|
||||
__typename: 'Activity',
|
||||
};
|
||||
|
||||
const newActivitiesSortedAsActivitiesQuery = [
|
||||
newActivity,
|
||||
...extistingActivities,
|
||||
];
|
||||
|
||||
const newActivityIdsSortedAsActivityTargetsQuery = [
|
||||
...extistingActivities,
|
||||
newActivity,
|
||||
].map((activity) => activity.id);
|
||||
|
||||
const newTimelineActivitiesQueryVariables =
|
||||
makeTimelineActivitiesQueryVariables({
|
||||
activityIds: newActivityIdsSortedAsActivityTargetsQuery,
|
||||
});
|
||||
|
||||
const newActivityConnectionForCache = getRecordConnectionFromRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
records: newActivitiesSortedAsActivitiesQuery,
|
||||
});
|
||||
|
||||
apolloClient.writeQuery({
|
||||
query: findManyActivitiesQuery,
|
||||
variables: newTimelineActivitiesQueryVariables,
|
||||
data: {
|
||||
[objectMetadataItemActivity.namePlural]: newActivityConnectionForCache,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
injectIntoTimelineActivitiesNextQuery: injectIntoTimelineActivitiesQuery,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,73 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { useActivityTargets } from '@/activities/hooks/useActivityTargets';
|
||||
import { makeTimelineActivitiesQueryVariables } from '@/activities/timeline/utils/makeTimelineActivitiesQueryVariables';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
|
||||
export const useTimelineActivities = ({
|
||||
targetableObject,
|
||||
}: {
|
||||
targetableObject: ActivityTargetableObject;
|
||||
}) => {
|
||||
const {
|
||||
activityTargets,
|
||||
loadingActivityTargets,
|
||||
initialized: initializedActivityTargets,
|
||||
} = useActivityTargets({
|
||||
targetableObject,
|
||||
});
|
||||
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
const [activities, setActivities] = useState<Activity[]>([]);
|
||||
|
||||
const activityIds = activityTargets
|
||||
?.map((activityTarget) => activityTarget.activityId)
|
||||
.filter(isNonEmptyString);
|
||||
|
||||
const timelineActivitiesQueryVariables = makeTimelineActivitiesQueryVariables(
|
||||
{
|
||||
activityIds,
|
||||
},
|
||||
);
|
||||
|
||||
const { records: activitiesFromRequest, loading: loadingActivities } =
|
||||
useFindManyRecords<Activity>({
|
||||
skip: loadingActivityTargets || !isNonEmptyArray(activityTargets),
|
||||
objectNameSingular: CoreObjectNameSingular.Activity,
|
||||
filter: timelineActivitiesQueryVariables.filter,
|
||||
orderBy: timelineActivitiesQueryVariables.orderBy,
|
||||
onCompleted: () => {
|
||||
if (!initialized) {
|
||||
setInitialized(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadingActivities) {
|
||||
setActivities(activitiesFromRequest);
|
||||
}
|
||||
}, [activitiesFromRequest, loadingActivities]);
|
||||
|
||||
const noActivityTargets =
|
||||
initializedActivityTargets && !isNonEmptyArray(activityTargets);
|
||||
|
||||
useEffect(() => {
|
||||
if (noActivityTargets) {
|
||||
setInitialized(true);
|
||||
}
|
||||
}, [noActivityTargets]);
|
||||
|
||||
const loading = loadingActivities || loadingActivityTargets;
|
||||
|
||||
return {
|
||||
activities,
|
||||
loading,
|
||||
initialized,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables';
|
||||
|
||||
export const makeTimelineActivitiesQueryVariables = ({
|
||||
activityIds,
|
||||
}: {
|
||||
activityIds: string[];
|
||||
}): ObjectRecordQueryVariables => {
|
||||
return {
|
||||
filter: {
|
||||
id: {
|
||||
in: activityIds,
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'AscNullsFirst',
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -1,5 +1,8 @@
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export type ActivityTargetableObject = {
|
||||
id: string;
|
||||
targetObjectNameSingular: string;
|
||||
targetObjectRecord: ObjectRecord;
|
||||
relatedTargetableObjects?: ActivityTargetableObject[];
|
||||
};
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ActivityTarget } from '@/activities/types/ActivityTarget';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { flattenTargetableObjectsAndTheirRelatedTargetableObjects } from '@/activities/utils/flattenTargetableObjectsAndTheirRelatedTargetableObjects';
|
||||
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTargetObjectFilterFieldName';
|
||||
|
||||
export const getActivityTargetsToCreateFromTargetableObjects = ({
|
||||
targetableObjects,
|
||||
activityId,
|
||||
}: {
|
||||
targetableObjects: ActivityTargetableObject[];
|
||||
activityId: string;
|
||||
}): Partial<ActivityTarget>[] => {
|
||||
const activityTargetableObjects = targetableObjects
|
||||
? flattenTargetableObjectsAndTheirRelatedTargetableObjects(
|
||||
targetableObjects,
|
||||
)
|
||||
: [];
|
||||
|
||||
const activityTargetsToCreate = activityTargetableObjects.map(
|
||||
(targetableObject) => {
|
||||
const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({
|
||||
nameSingular: targetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
return {
|
||||
[targetableObject.targetObjectNameSingular]:
|
||||
targetableObject.targetObjectRecord,
|
||||
[targetableObjectFieldIdName]: targetableObject.id,
|
||||
activityId,
|
||||
id: v4(),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return activityTargetsToCreate;
|
||||
};
|
||||
@ -1,3 +1,5 @@
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export type CachedObjectRecord = ObjectRecord & { __typename: string };
|
||||
export type CachedObjectRecord<T extends ObjectRecord = ObjectRecord> = T & {
|
||||
__typename: string;
|
||||
};
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { useEffect } from 'react';
|
||||
import { loadDevMessages, loadErrorMessages } from '@apollo/client/dev';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||
|
||||
export const ApolloDevLogEffect = () => {
|
||||
const isDebugMode = useRecoilValue(isDebugModeState);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDebugMode) {
|
||||
loadDevMessages();
|
||||
loadErrorMessages();
|
||||
}
|
||||
}, [isDebugMode]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -10,6 +10,8 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
|
||||
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
|
||||
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
|
||||
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { useModifyRecordFromCache } from '@/object-record/cache/hooks/useModifyRecordFromCache';
|
||||
import { useGenerateCreateManyRecordMutation } from '@/object-record/hooks/useGenerateCreateManyRecordMutation';
|
||||
import { useGenerateCreateOneRecordMutation } from '@/object-record/hooks/useGenerateCreateOneRecordMutation';
|
||||
import { useGenerateDeleteManyRecordMutation } from '@/object-record/hooks/useGenerateDeleteManyRecordMutation';
|
||||
@ -17,8 +19,6 @@ import { useGenerateExecuteQuickActionOnOneRecordMutation } from '@/object-recor
|
||||
import { useGenerateFindManyRecordsQuery } from '@/object-record/hooks/useGenerateFindManyRecordsQuery';
|
||||
import { useGenerateFindOneRecordQuery } from '@/object-record/hooks/useGenerateFindOneRecordQuery';
|
||||
import { useGenerateUpdateOneRecordMutation } from '@/object-record/hooks/useGenerateUpdateOneRecordMutation';
|
||||
import { useGetRecordFromCache } from '@/object-record/hooks/useGetRecordFromCache';
|
||||
import { useModifyRecordFromCache } from '@/object-record/hooks/useModifyRecordFromCache';
|
||||
import { generateDeleteOneRecordMutation } from '@/object-record/utils/generateDeleteOneRecordMutation';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
|
||||
72
packages/twenty-front/src/modules/object-record/cache/hooks/useAddRecordInCache.ts
vendored
Normal file
72
packages/twenty-front/src/modules/object-record/cache/hooks/useAddRecordInCache.ts
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import gql from 'graphql-tag';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useGenerateFindOneRecordQuery } from '@/object-record/hooks/useGenerateFindOneRecordQuery';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const useAddRecordInCache = ({
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const generateFindOneRecordQuery = useGenerateFindOneRecordQuery();
|
||||
|
||||
const findOneRecordQuery = generateFindOneRecordQuery({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
return useRecoilCallback(
|
||||
({ set }) =>
|
||||
(record: ObjectRecord) => {
|
||||
apolloClient.writeFragment({
|
||||
id: `${capitalize(objectMetadataItem.nameSingular)}:${record.id}`,
|
||||
fragment: gql`
|
||||
fragment Create${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}InCache on ${capitalize(objectMetadataItem.nameSingular)} {
|
||||
__typename
|
||||
id
|
||||
${objectMetadataItem.fields
|
||||
.map((field) => mapFieldMetadataToGraphQLQuery(field))
|
||||
.join('\n')}
|
||||
}
|
||||
`,
|
||||
data: {
|
||||
__typename: `${capitalize(objectMetadataItem.nameSingular)}`,
|
||||
...record,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: Turn into injectIntoFindOneRecordQueryCache
|
||||
apolloClient.writeQuery({
|
||||
query: findOneRecordQuery,
|
||||
variables: {
|
||||
objectRecordId: record.id,
|
||||
},
|
||||
data: {
|
||||
[objectMetadataItem.nameSingular]: {
|
||||
__typename: `${capitalize(objectMetadataItem.nameSingular)}`,
|
||||
...record,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: remove this once we get rid of entityFieldsFamilyState
|
||||
set(recordStoreFamilyState(record.id), record);
|
||||
},
|
||||
[
|
||||
objectMetadataItem,
|
||||
apolloClient,
|
||||
mapFieldMetadataToGraphQLQuery,
|
||||
findOneRecordQuery,
|
||||
],
|
||||
);
|
||||
};
|
||||
31
packages/twenty-front/src/modules/object-record/cache/utils/getCacheReferenceFromRecord.ts
vendored
Normal file
31
packages/twenty-front/src/modules/object-record/cache/utils/getCacheReferenceFromRecord.ts
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
import { ApolloClient, makeReference, Reference } from '@apollo/client';
|
||||
|
||||
import { getCachedRecordFromRecord } from '@/object-record/cache/utils/getCachedRecordFromRecord';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const getCacheReferenceFromRecord = <T extends ObjectRecord>({
|
||||
apolloClient,
|
||||
objectNameSingular,
|
||||
record,
|
||||
}: {
|
||||
apolloClient: ApolloClient<object>;
|
||||
objectNameSingular: string;
|
||||
record: T;
|
||||
}): Reference => {
|
||||
const cachedRecord = getCachedRecordFromRecord({
|
||||
objectNameSingular,
|
||||
record,
|
||||
});
|
||||
|
||||
const id = apolloClient.cache.identify(cachedRecord);
|
||||
|
||||
if (!id) {
|
||||
throw new Error(
|
||||
`Could not identify record "${objectNameSingular}", id : "${record.id}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const recordReference = makeReference(id);
|
||||
|
||||
return recordReference;
|
||||
};
|
||||
43
packages/twenty-front/src/modules/object-record/cache/utils/getCachedRecordEdgesFromRecords.ts
vendored
Normal file
43
packages/twenty-front/src/modules/object-record/cache/utils/getCachedRecordEdgesFromRecords.ts
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
import { ApolloClient, makeReference } from '@apollo/client';
|
||||
|
||||
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
|
||||
import { getCachedRecordFromRecord } from '@/object-record/cache/utils/getCachedRecordFromRecord';
|
||||
import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const getCachedRecordEdgesFromRecords = <T extends ObjectRecord>({
|
||||
apolloClient,
|
||||
objectNameSingular,
|
||||
records,
|
||||
}: {
|
||||
apolloClient: ApolloClient<object>;
|
||||
objectNameSingular: string;
|
||||
records: T[];
|
||||
}): CachedObjectRecordEdge[] => {
|
||||
const cachedRecordEdges = records.map((record) => {
|
||||
const cachedRecord = getCachedRecordFromRecord({
|
||||
objectNameSingular,
|
||||
record,
|
||||
});
|
||||
|
||||
const id = apolloClient.cache.identify(cachedRecord);
|
||||
|
||||
if (!id) {
|
||||
throw new Error(
|
||||
`Could not identify record "${objectNameSingular}", id : "${record.id}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const reference = makeReference(id);
|
||||
|
||||
const cachedObjectRecordEdge: CachedObjectRecordEdge = {
|
||||
cursor: '',
|
||||
node: reference,
|
||||
__typename: getEdgeTypename({ objectNameSingular }),
|
||||
};
|
||||
|
||||
return cachedObjectRecordEdge;
|
||||
});
|
||||
|
||||
return cachedRecordEdges;
|
||||
};
|
||||
16
packages/twenty-front/src/modules/object-record/cache/utils/getCachedRecordFromRecord.ts
vendored
Normal file
16
packages/twenty-front/src/modules/object-record/cache/utils/getCachedRecordFromRecord.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
|
||||
import { getNodeTypename } from '@/object-record/cache/utils/getNodeTypename';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const getCachedRecordFromRecord = <T extends ObjectRecord>({
|
||||
objectNameSingular,
|
||||
record,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
record: T;
|
||||
}): CachedObjectRecord<T> => {
|
||||
return {
|
||||
__typename: getNodeTypename({ objectNameSingular }),
|
||||
...record,
|
||||
};
|
||||
};
|
||||
9
packages/twenty-front/src/modules/object-record/cache/utils/getConnectionTypename.ts
vendored
Normal file
9
packages/twenty-front/src/modules/object-record/cache/utils/getConnectionTypename.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const getConnectionTypename = ({
|
||||
objectNameSingular,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
}) => {
|
||||
return `${capitalize(objectNameSingular)}Connection`;
|
||||
};
|
||||
9
packages/twenty-front/src/modules/object-record/cache/utils/getEdgeTypename.ts
vendored
Normal file
9
packages/twenty-front/src/modules/object-record/cache/utils/getEdgeTypename.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const getEdgeTypename = ({
|
||||
objectNameSingular,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
}) => {
|
||||
return `${capitalize(objectNameSingular)}Edge`;
|
||||
};
|
||||
8
packages/twenty-front/src/modules/object-record/cache/utils/getEmptyPageInfo.ts
vendored
Normal file
8
packages/twenty-front/src/modules/object-record/cache/utils/getEmptyPageInfo.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
export const getEmptyPageInfo = () => {
|
||||
return {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
endCursor: null,
|
||||
};
|
||||
};
|
||||
9
packages/twenty-front/src/modules/object-record/cache/utils/getNodeTypename.ts
vendored
Normal file
9
packages/twenty-front/src/modules/object-record/cache/utils/getNodeTypename.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const getNodeTypename = ({
|
||||
objectNameSingular,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
}) => {
|
||||
return capitalize(objectNameSingular);
|
||||
};
|
||||
19
packages/twenty-front/src/modules/object-record/cache/utils/getRecordConnectionFromEdges.ts
vendored
Normal file
19
packages/twenty-front/src/modules/object-record/cache/utils/getRecordConnectionFromEdges.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
import { getConnectionTypename } from '@/object-record/cache/utils/getConnectionTypename';
|
||||
import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
|
||||
|
||||
export const getRecordConnectionFromEdges = <T extends ObjectRecord>({
|
||||
objectNameSingular,
|
||||
edges,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
edges: ObjectRecordEdge<T>[];
|
||||
}) => {
|
||||
return {
|
||||
__typename: getConnectionTypename({ objectNameSingular }),
|
||||
edges: edges,
|
||||
pageInfo: getEmptyPageInfo(),
|
||||
} as ObjectRecordConnection<T>;
|
||||
};
|
||||
24
packages/twenty-front/src/modules/object-record/cache/utils/getRecordConnectionFromRecords.ts
vendored
Normal file
24
packages/twenty-front/src/modules/object-record/cache/utils/getRecordConnectionFromRecords.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
import { getConnectionTypename } from '@/object-record/cache/utils/getConnectionTypename';
|
||||
import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo';
|
||||
import { getRecordEdgeFromRecord } from '@/object-record/cache/utils/getRecordEdgeFromRecord';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
|
||||
export const getRecordConnectionFromRecords = <T extends ObjectRecord>({
|
||||
objectNameSingular,
|
||||
records,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
records: T[];
|
||||
}) => {
|
||||
return {
|
||||
__typename: getConnectionTypename({ objectNameSingular }),
|
||||
edges: records.map((record) => {
|
||||
return getRecordEdgeFromRecord({
|
||||
objectNameSingular,
|
||||
record,
|
||||
});
|
||||
}),
|
||||
pageInfo: getEmptyPageInfo(),
|
||||
} as ObjectRecordConnection<T>;
|
||||
};
|
||||
21
packages/twenty-front/src/modules/object-record/cache/utils/getRecordEdgeFromRecord.ts
vendored
Normal file
21
packages/twenty-front/src/modules/object-record/cache/utils/getRecordEdgeFromRecord.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename';
|
||||
import { getNodeTypename } from '@/object-record/cache/utils/getNodeTypename';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
|
||||
|
||||
export const getRecordEdgeFromRecord = <T extends ObjectRecord>({
|
||||
objectNameSingular,
|
||||
record,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
record: T;
|
||||
}) => {
|
||||
return {
|
||||
__typename: getEdgeTypename({ objectNameSingular }),
|
||||
node: {
|
||||
__typename: getNodeTypename({ objectNameSingular }),
|
||||
...record,
|
||||
},
|
||||
cursor: '',
|
||||
} as ObjectRecordEdge<T>;
|
||||
};
|
||||
10
packages/twenty-front/src/modules/object-record/cache/utils/getRecordsFromRecordConnection.ts
vendored
Normal file
10
packages/twenty-front/src/modules/object-record/cache/utils/getRecordsFromRecordConnection.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
|
||||
export const getRecordsFromRecordConnection = <T extends ObjectRecord>({
|
||||
recordConnection,
|
||||
}: {
|
||||
recordConnection: ObjectRecordConnection<T>;
|
||||
}): T[] => {
|
||||
return recordConnection.edges.map((edge) => edge.node);
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export const DEFAULT_SEARCH_REQUEST_LIMIT = 60;
|
||||
@ -5,7 +5,7 @@ export const query = gql`
|
||||
$filter: PersonFilterInput
|
||||
$orderBy: PersonOrderByInput
|
||||
$lastCursor: String
|
||||
$limit: Float = 30
|
||||
$limit: Float
|
||||
) {
|
||||
people(
|
||||
filter: $filter
|
||||
|
||||
@ -5,7 +5,7 @@ import { act, renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
|
||||
import { useModifyRecordFromCache } from '@/object-record/hooks/useModifyRecordFromCache';
|
||||
import { useModifyRecordFromCache } from '@/object-record/cache/hooks/useModifyRecordFromCache';
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<MockedProvider addTypename={false}>
|
||||
|
||||
@ -3,7 +3,7 @@ import { useApolloClient } from '@apollo/client';
|
||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { useGenerateCachedObjectRecord } from '@/object-record/hooks/useGenerateCachedObjectRecord';
|
||||
import { useGenerateCachedObjectRecord } from '@/object-record/cache/hooks/useGenerateCachedObjectRecord';
|
||||
import { getCreateManyRecordsMutationResponseField } from '@/object-record/hooks/useGenerateCreateManyRecordMutation';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { useAddRecordInCache } from '@/object-record/cache/hooks/useAddRecordInCache';
|
||||
import { useGenerateCachedObjectRecord } from '@/object-record/cache/hooks/useGenerateCachedObjectRecord';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const useCreateManyRecordsInCache = <T extends ObjectRecord>({
|
||||
objectNameSingular,
|
||||
}: ObjectMetadataItemIdentifier) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { generateCachedObjectRecord } = useGenerateCachedObjectRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const addRecordInCache = useAddRecordInCache({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const createManyRecordsInCache = async (data: Partial<T>[]) => {
|
||||
const recordsWithId = data.map((record) => ({
|
||||
...record,
|
||||
id: (record.id as string) ?? v4(),
|
||||
}));
|
||||
|
||||
const createdRecordsInCache = [] as T[];
|
||||
|
||||
for (const record of recordsWithId) {
|
||||
const generatedCachedObjectRecord = generateCachedObjectRecord<T>({
|
||||
...record,
|
||||
});
|
||||
|
||||
if (generatedCachedObjectRecord) {
|
||||
addRecordInCache(generatedCachedObjectRecord);
|
||||
|
||||
createdRecordsInCache.push(generatedCachedObjectRecord);
|
||||
}
|
||||
}
|
||||
|
||||
return createdRecordsInCache;
|
||||
};
|
||||
|
||||
return { createManyRecordsInCache };
|
||||
};
|
||||
@ -2,7 +2,7 @@ import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useGenerateCachedObjectRecord } from '@/object-record/hooks/useGenerateCachedObjectRecord';
|
||||
import { useGenerateCachedObjectRecord } from '@/object-record/cache/hooks/useGenerateCachedObjectRecord';
|
||||
import { getCreateOneRecordMutationResponseField } from '@/object-record/hooks/useGenerateCreateOneRecordMutation';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useAddRecordInCache } from '@/object-record/cache/hooks/useAddRecordInCache';
|
||||
import { useGenerateCachedObjectRecord } from '@/object-record/cache/hooks/useGenerateCachedObjectRecord';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
type useCreateOneRecordInCacheProps = {
|
||||
objectNameSingular: string;
|
||||
};
|
||||
|
||||
export const useCreateOneRecordInCache = <T>({
|
||||
objectNameSingular,
|
||||
}: useCreateOneRecordInCacheProps) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { generateCachedObjectRecord } = useGenerateCachedObjectRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const addRecordInCache = useAddRecordInCache({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const createOneRecordInCache = async (input: ObjectRecord) => {
|
||||
const generatedCachedObjectRecord = generateCachedObjectRecord({
|
||||
createdAt: new Date().toISOString(),
|
||||
...input,
|
||||
});
|
||||
|
||||
addRecordInCache(generatedCachedObjectRecord);
|
||||
|
||||
return generatedCachedObjectRecord as T;
|
||||
};
|
||||
|
||||
return {
|
||||
createOneRecordInCache,
|
||||
};
|
||||
};
|
||||
@ -13,7 +13,6 @@ import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnec
|
||||
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
|
||||
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables';
|
||||
import { filterUniqueRecordEdgesByCursor } from '@/object-record/utils/filterUniqueRecordEdgesByCursor';
|
||||
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/search/hooks/useFilteredSearchEntityQuery';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { logError } from '~/utils/logError';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
@ -28,7 +27,7 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
orderBy,
|
||||
limit = DEFAULT_SEARCH_REQUEST_LIMIT,
|
||||
limit,
|
||||
onCompleted,
|
||||
skip,
|
||||
useRecordsWithoutConnection = false,
|
||||
|
||||
@ -20,7 +20,7 @@ export const useGenerateFindManyRecordsQuery = () => {
|
||||
objectMetadataItem.nameSingular,
|
||||
)}FilterInput, $orderBy: ${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}OrderByInput, $lastCursor: String, $limit: Float = 60) {
|
||||
)}OrderByInput, $lastCursor: String, $limit: Float) {
|
||||
${
|
||||
objectMetadataItem.namePlural
|
||||
}(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
URLFilter,
|
||||
UUIDFilter,
|
||||
} from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
|
||||
import { andFilterVariables } from '@/object-record/utils/andFilterVariables';
|
||||
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { Field } from '~/generated/graphql';
|
||||
|
||||
@ -260,5 +260,5 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
|
||||
}
|
||||
}
|
||||
|
||||
return andFilterVariables(objectRecordFilters);
|
||||
return makeAndFilterVariables(objectRecordFilters);
|
||||
};
|
||||
|
||||
@ -238,16 +238,21 @@ export const RecordShowContainer = ({
|
||||
</>
|
||||
)}
|
||||
</ShowPageLeftContainer>
|
||||
<ShowPageRightContainer
|
||||
targetableObject={{
|
||||
id: record?.id ?? '',
|
||||
targetObjectNameSingular: objectMetadataItem?.nameSingular,
|
||||
}}
|
||||
timeline
|
||||
tasks
|
||||
notes
|
||||
emails
|
||||
/>
|
||||
{record ? (
|
||||
<ShowPageRightContainer
|
||||
targetableObject={{
|
||||
id: record.id,
|
||||
targetObjectNameSingular: objectMetadataItem?.nameSingular,
|
||||
targetObjectRecord: record,
|
||||
}}
|
||||
timeline
|
||||
tasks
|
||||
notes
|
||||
emails
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</ShowPageContainer>
|
||||
</RecoilScope>
|
||||
);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/search/hooks/useFilteredSearchEntityQuery';
|
||||
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { useMultiObjectSearchSelectedItemsQuery } from '@/object-record/relation
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier';
|
||||
|
||||
export const DEFAULT_SEARCH_REQUEST_LIMIT = 5;
|
||||
export const MULTI_OBJECT_SEARCH_REQUEST_LIMIT = 5;
|
||||
|
||||
export type ObjectRecordForSelect = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
|
||||
@ -13,7 +13,7 @@ import {
|
||||
import { SelectedObjectRecordId } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
|
||||
import { useOrderByFieldPerMetadataItem } from '@/object-record/relation-picker/hooks/useOrderByFieldPerMetadataItem';
|
||||
import { useSearchFilterPerMetadataItem } from '@/object-record/relation-picker/hooks/useSearchFilterPerMetadataItem';
|
||||
import { andFilterVariables } from '@/object-record/utils/andFilterVariables';
|
||||
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
@ -69,7 +69,7 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({
|
||||
|
||||
return [
|
||||
`filter${capitalize(nameSingular)}`,
|
||||
andFilterVariables(searchFilters),
|
||||
makeAndFilterVariables(searchFilters),
|
||||
];
|
||||
})
|
||||
.filter(isDefined),
|
||||
|
||||
@ -2,13 +2,12 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { OrderBy } from '@/object-metadata/types/OrderBy';
|
||||
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord';
|
||||
import { getObjectFilterFields } from '@/object-record/select/utils/getObjectFilterFields';
|
||||
import { andFilterVariables } from '@/object-record/utils/andFilterVariables';
|
||||
import { orFilterVariables } from '@/object-record/utils/orFilterVariables';
|
||||
|
||||
export const DEFAULT_SEARCH_REQUEST_LIMIT = 60;
|
||||
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
||||
import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables';
|
||||
|
||||
export const useRecordsForSelect = ({
|
||||
searchFilterText,
|
||||
@ -53,7 +52,7 @@ export const useRecordsForSelect = ({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return orFilterVariables(
|
||||
return makeOrFilterVariables(
|
||||
fieldNames.map((fieldName) => {
|
||||
const [parentFieldName, subFieldName] = fieldName.split('.');
|
||||
|
||||
@ -81,7 +80,7 @@ export const useRecordsForSelect = ({
|
||||
loading: filteredSelectedRecordsLoading,
|
||||
records: filteredSelectedRecordsData,
|
||||
} = useFindManyRecords({
|
||||
filter: andFilterVariables([...searchFilters, selectedIdsFilter]),
|
||||
filter: makeAndFilterVariables([...searchFilters, selectedIdsFilter]),
|
||||
orderBy: orderByField,
|
||||
objectNameSingular,
|
||||
skip: !selectedIds.length,
|
||||
@ -93,7 +92,7 @@ export const useRecordsForSelect = ({
|
||||
: undefined;
|
||||
const { loading: recordsToSelectLoading, records: recordsToSelectData } =
|
||||
useFindManyRecords({
|
||||
filter: andFilterVariables([...searchFilters, notFilter]),
|
||||
filter: makeAndFilterVariables([...searchFilters, notFilter]),
|
||||
limit: limit ?? DEFAULT_SEARCH_REQUEST_LIMIT,
|
||||
orderBy: orderByField,
|
||||
objectNameSingular,
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const generateEmptyFieldValue = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
@ -39,7 +42,21 @@ export const generateEmptyFieldValue = (
|
||||
return true;
|
||||
}
|
||||
case FieldMetadataType.Relation: {
|
||||
return null;
|
||||
if (
|
||||
!isNonEmptyString(
|
||||
fieldMetadataItem.fromRelationMetadata?.toObjectMetadata
|
||||
?.nameSingular,
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
__typename: `${capitalize(
|
||||
fieldMetadataItem.fromRelationMetadata.toObjectMetadata.nameSingular,
|
||||
)}Connection`,
|
||||
edges: [],
|
||||
};
|
||||
}
|
||||
case FieldMetadataType.Currency: {
|
||||
return {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const andFilterVariables = (
|
||||
export const makeAndFilterVariables = (
|
||||
filters: (ObjectRecordQueryFilter | undefined)[],
|
||||
): ObjectRecordQueryFilter | undefined => {
|
||||
const definedFilters = filters.filter(isDefined);
|
||||
@ -1,7 +1,7 @@
|
||||
import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const orFilterVariables = (
|
||||
export const makeOrFilterVariables = (
|
||||
filters: (ObjectRecordQueryFilter | undefined)[],
|
||||
): ObjectRecordQueryFilter | undefined => {
|
||||
const definedFilters = filters.filter(isDefined);
|
||||
@ -0,0 +1,5 @@
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const mapToRecordId = (objectRecord: ObjectRecord) => {
|
||||
return objectRecord.id;
|
||||
};
|
||||
@ -5,7 +5,7 @@ export const query = gql`
|
||||
$filter: PersonFilterInput
|
||||
$orderBy: PersonOrderByInput
|
||||
$lastCursor: String
|
||||
$limit: Float = 30
|
||||
$limit: Float = 60
|
||||
) {
|
||||
people(
|
||||
filter: $filter
|
||||
|
||||
@ -2,18 +2,17 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { OrderBy } from '@/object-metadata/types/OrderBy';
|
||||
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/types/EntitiesForMultipleEntitySelect';
|
||||
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { andFilterVariables } from '@/object-record/utils/andFilterVariables';
|
||||
import { orFilterVariables } from '@/object-record/utils/orFilterVariables';
|
||||
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
||||
import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables';
|
||||
import { assertNotNull } from '~/utils/assert';
|
||||
|
||||
type SearchFilter = { fieldNames: string[]; filter: string | number };
|
||||
|
||||
export const DEFAULT_SEARCH_REQUEST_LIMIT = 60;
|
||||
|
||||
// TODO: use this for all search queries, because we need selectedEntities and entitiesToSelect each time we want to search
|
||||
// Filtered entities to select are
|
||||
|
||||
@ -56,7 +55,7 @@ export const useFilteredSearchEntityQuery = ({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return orFilterVariables(
|
||||
return makeOrFilterVariables(
|
||||
fieldNames.map((fieldName) => {
|
||||
const [parentFieldName, subFieldName] = fieldName.split('.');
|
||||
|
||||
@ -85,7 +84,7 @@ export const useFilteredSearchEntityQuery = ({
|
||||
records: filteredSelectedRecords,
|
||||
} = useFindManyRecords({
|
||||
objectNameSingular,
|
||||
filter: andFilterVariables([...searchFilters, selectedIdsFilter]),
|
||||
filter: makeAndFilterVariables([...searchFilters, selectedIdsFilter]),
|
||||
orderBy: { [orderByField]: sortOrder },
|
||||
skip: !selectedIds.length,
|
||||
});
|
||||
@ -97,7 +96,7 @@ export const useFilteredSearchEntityQuery = ({
|
||||
const { loading: recordsToSelectLoading, records: recordsToSelect } =
|
||||
useFindManyRecords({
|
||||
objectNameSingular,
|
||||
filter: andFilterVariables([...searchFilters, notFilter]),
|
||||
filter: makeAndFilterVariables([...searchFilters, notFilter]),
|
||||
limit: limit ?? DEFAULT_SEARCH_REQUEST_LIMIT,
|
||||
orderBy: { [orderByField]: sortOrder },
|
||||
});
|
||||
|
||||
@ -92,6 +92,7 @@ export const RecordShowPage = () => {
|
||||
entity={{
|
||||
id: record.id,
|
||||
targetObjectNameSingular: objectMetadataItem?.nameSingular,
|
||||
targetObjectRecord: record,
|
||||
}}
|
||||
/>
|
||||
<ShowPageMoreButton
|
||||
|
||||
Reference in New Issue
Block a user