## Query depth deprecation

I'm deprecating depth parameter in our graphql query / cache tooling.
They were obsolete since we introduce the possibility to provide
RecordGqlFields

## Refactor combinedFindManyRecordHook

The hook can now take an array of operationSignatures

## Fix tasks issues

Fix optimistic rendering issue. Note that we still haven't handle
optimisticEffect on creation properly
This commit is contained in:
Charles Bochet
2024-04-29 23:33:23 +02:00
committed by GitHub
parent c946572fde
commit 6a14b1c6d6
187 changed files with 958 additions and 1482 deletions

View File

@ -3,4 +3,5 @@ export type BlocklistItem = {
handle: string; handle: string;
workspaceMemberId: string; workspaceMemberId: string;
createdAt: string; createdAt: string;
__typename: 'BlocklistItem';
}; };

View File

@ -9,4 +9,5 @@ export type CalendarChannel = {
isContactAutoCreationEnabled?: boolean; isContactAutoCreationEnabled?: boolean;
isSyncEnabled?: boolean; isSyncEnabled?: boolean;
visibility: CalendarChannelVisibility; visibility: CalendarChannelVisibility;
__typename: 'CalendarChannel';
}; };

View File

@ -13,4 +13,5 @@ export type ConnectedAccount = {
authFailedAt: Date | null; authFailedAt: Date | null;
messageChannels: MessageChannel[]; messageChannels: MessageChannel[];
calendarChannels: CalendarChannel[]; calendarChannels: CalendarChannel[];
__typename: 'ConnectedAccount';
}; };

View File

@ -7,4 +7,5 @@ export type MessageChannel = {
isSyncEnabled: boolean; isSyncEnabled: boolean;
visibility: InboxSettingsVisibilityValue; visibility: InboxSettingsVisibilityValue;
syncStatus: string; syncStatus: string;
__typename: 'MessageChannel';
}; };

View File

@ -10,7 +10,7 @@ import { sortDesc } from '~/utils/sort';
type CalendarEventGeneric = Omit< type CalendarEventGeneric = Omit<
CalendarEvent, CalendarEvent,
'participants' | 'externalCreatedAt' 'participants' | 'externalCreatedAt' | '__typename'
>; >;
export const useCalendarEvents = <T extends CalendarEventGeneric>( export const useCalendarEvents = <T extends CalendarEventGeneric>(

View File

@ -14,7 +14,6 @@ export const RightDrawerCalendarEvent = () => {
objectNameSingular: CoreObjectNameSingular.CalendarEvent, objectNameSingular: CoreObjectNameSingular.CalendarEvent,
objectRecordId: viewableCalendarEventId ?? '', objectRecordId: viewableCalendarEventId ?? '',
onCompleted: (record) => setRecords([record]), onCompleted: (record) => setRecords([record]),
depth: 2,
}); });
if (!calendarEvent) return null; if (!calendarEvent) return null;

View File

@ -17,4 +17,5 @@ export type CalendarEvent = {
title?: string; title?: string;
visibility: 'METADATA' | 'SHARE_EVERYTHING'; visibility: 'METADATA' | 'SHARE_EVERYTHING';
calendarEventParticipants?: CalendarEventParticipant[]; calendarEventParticipants?: CalendarEventParticipant[];
__typename: 'CalendarEvent';
}; };

View File

@ -161,6 +161,7 @@ export const ActivityBodyEditor = ({
...oldActivity, ...oldActivity,
id: activityId, id: activityId,
body: newStringifiedBody, body: newStringifiedBody,
__typename: 'Activity',
}; };
}); });
@ -192,6 +193,7 @@ export const ActivityBodyEditor = ({
...oldActivity, ...oldActivity,
id: activityId, id: activityId,
title: newTitleFromBody, title: newTitleFromBody,
__typename: 'Activity',
}; };
}); });

View File

@ -134,6 +134,7 @@ export const ActivityTitle = ({ activityId }: ActivityTitleProps) => {
...currentActivity, ...currentActivity,
id: activity.id, id: activity.id,
title: newTitle, title: newTitle,
__typename: activity.__typename,
}; };
}); });

View File

@ -28,7 +28,6 @@ export const useRightDrawerEmailThread = () => {
loading, loading,
fetchMoreRecords, fetchMoreRecords,
} = useFindManyRecords<EmailThreadMessageType>({ } = useFindManyRecords<EmailThreadMessageType>({
depth: 3,
limit: 10, limit: 10,
filter: { filter: {
messageThreadId: { messageThreadId: {

View File

@ -5,4 +5,5 @@ export type EmailThreadMessage = {
text: string; text: string;
receivedAt: string; receivedAt: string;
messageParticipants: EmailThreadMessageParticipant[]; messageParticipants: EmailThreadMessageParticipant[];
__typename: 'EmailThreadMessage';
}; };

View File

@ -1,9 +1,9 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { Attachment } from '@/activities/files/types/Attachment';
import { getFileType } from '@/activities/files/utils/getFileType'; import { getFileType } from '@/activities/files/utils/getFileType';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName'; import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
import { Attachment } from '@/attachments/types/Attachment';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';

View File

@ -8,7 +8,9 @@ export type Attachment = {
activityId: string; activityId: string;
authorId: string; authorId: string;
createdAt: string; createdAt: string;
__typename: string;
}; };
export type AttachmentType = export type AttachmentType =
| 'Archive' | 'Archive'
| 'Audio' | 'Audio'

View File

@ -1,12 +1,11 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
import { QueryKey } from '@/object-record/query-keys/types/QueryKey';
export const FIND_MANY_ACTIVITIES_QUERY_KEY: QueryKey = { export const CREATE_ONE_ACTIVITY_OPERATION_SIGNATURE: RecordGqlOperationSignature =
objectNameSingular: CoreObjectNameSingular.Activity, {
variables: {}, objectNameSingular: CoreObjectNameSingular.Activity,
fieldsFactory: (_objectMetadataItems: ObjectMetadataItem[]) => { variables: {},
return { fields: {
id: true, id: true,
__typename: true, __typename: true,
createdAt: true, createdAt: true,
@ -31,8 +30,5 @@ export const FIND_MANY_ACTIVITIES_QUERY_KEY: QueryKey = {
dueAt: true, dueAt: true,
reminderAt: true, reminderAt: true,
type: true, type: true,
activityTargets: true, },
}; };
},
depth: 2,
};

View File

@ -0,0 +1,45 @@
import { generateActivityTargetMorphFieldKeys } from '@/activities/utils/generateActivityTargetMorphFieldKeys';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
export const findActivitiesOperationSignatureFactory: RecordGqlOperationSignatureFactory =
({ objectMetadataItems }: { objectMetadataItems: ObjectMetadataItem[] }) => ({
objectNameSingular: CoreObjectNameSingular.Activity,
variables: {},
fields: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
author: {
id: true,
name: true,
__typename: true,
},
authorId: true,
assigneeId: true,
assignee: {
id: true,
name: true,
__typename: true,
},
comments: true,
attachments: true,
body: true,
title: true,
completedAt: true,
dueAt: true,
reminderAt: true,
type: true,
activityTargets: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
activity: true,
activityId: true,
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
},
});

View File

@ -1,13 +1,13 @@
import { generateActivityTargetMorphFieldKeys } from '@/activities/utils/generateActivityTargetMorphFieldKeys'; import { generateActivityTargetMorphFieldKeys } from '@/activities/utils/generateActivityTargetMorphFieldKeys';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { QueryKey } from '@/object-record/query-keys/types/QueryKey'; import { RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
export const FIND_MANY_ACTIVITY_TARGETS_QUERY_KEY: QueryKey = { export const findActivityTargetsOperationSignatureFactory: RecordGqlOperationSignatureFactory =
objectNameSingular: CoreObjectNameSingular.ActivityTarget, ({ objectMetadataItems }: { objectMetadataItems: ObjectMetadataItem[] }) => ({
variables: {}, objectNameSingular: CoreObjectNameSingular.ActivityTarget,
fieldsFactory: (objectMetadataItems: ObjectMetadataItem[]) => { variables: {},
return { fields: {
id: true, id: true,
__typename: true, __typename: true,
createdAt: true, createdAt: true,
@ -15,7 +15,5 @@ export const FIND_MANY_ACTIVITY_TARGETS_QUERY_KEY: QueryKey = {
activity: true, activity: true,
activityId: true, activityId: true,
...generateActivityTargetMorphFieldKeys(objectMetadataItems), ...generateActivityTargetMorphFieldKeys(objectMetadataItems),
}; },
}, });
depth: 1,
};

View File

@ -79,7 +79,7 @@ const mocks: MockedResponse[] = [
} }
`, `,
variables: { variables: {
filter: { activityTargetId: { eq: '123' } }, filter: { companyId: { eq: '123' } },
limit: undefined, limit: undefined,
orderBy: undefined, orderBy: undefined,
}, },
@ -180,7 +180,6 @@ describe('useActivities', () => {
activitiesFilters: {}, activitiesFilters: {},
activitiesOrderByVariables: {}, activitiesOrderByVariables: {},
skip: false, skip: false,
skipActivityTargets: false,
}), }),
{ wrapper: Wrapper }, { wrapper: Wrapper },
); );
@ -188,8 +187,6 @@ describe('useActivities', () => {
expect(result.current).toEqual({ expect(result.current).toEqual({
activities: [], activities: [],
loading: false, loading: false,
initialized: true,
noActivities: true,
}); });
}); });
@ -202,12 +199,11 @@ describe('useActivities', () => {
const activities = useActivities({ const activities = useActivities({
targetableObjects: [ targetableObjects: [
{ targetObjectNameSingular: 'activityTarget', id: '123' }, { targetObjectNameSingular: 'company', id: '123' },
], ],
activitiesFilters: {}, activitiesFilters: {},
activitiesOrderByVariables: {}, activitiesOrderByVariables: {},
skip: false, skip: false,
skipActivityTargets: false,
}); });
return { activities, setCurrentWorkspaceMember }; return { activities, setCurrentWorkspaceMember };
}, },
@ -218,18 +214,9 @@ describe('useActivities', () => {
result.current.setCurrentWorkspaceMember(mockWorkspaceMembers[0]); result.current.setCurrentWorkspaceMember(mockWorkspaceMembers[0]);
}); });
expect(result.current.activities.loading).toBe(true); await waitFor(() => {
expect(result.current.activities.loading).toBe(false);
// Wait for activityTargets to complete fetching });
await waitFor(() => !result.current.activities.loading);
expect(result.current.activities.loading).toBe(false);
// Wait for request to fetch activities to be made
await waitFor(() => result.current.activities.loading);
// Wait for activities to complete fetching
await waitFor(() => !result.current.activities.loading);
const { activities } = result.current; const { activities } = result.current;

View File

@ -10,8 +10,8 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode'; import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members'; import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members';
const mockObjectMetadataItems = getObjectMetadataItemsMock(); const mockObjectMetadataItems = getObjectMetadataItemsMock();
const cache = new InMemoryCache(); const cache = new InMemoryCache();
@ -85,7 +85,6 @@ cache.writeFragment({
id id
createdAt createdAt
updatedAt updatedAt
targetObjectNameSingular
personId personId
companyId companyId
company { company {
@ -114,9 +113,11 @@ cache.writeFragment({
const Wrapper = ({ children }: { children: ReactNode }) => ( const Wrapper = ({ children }: { children: ReactNode }) => (
<RecoilRoot> <RecoilRoot>
<MockedProvider cache={cache}> <MockedProvider cache={cache}>
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> <JestObjectMetadataItemSetter>
{children} <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
</SnackBarProviderScope> {children}
</SnackBarProviderScope>
</JestObjectMetadataItemSetter>
</MockedProvider> </MockedProvider>
</RecoilRoot> </RecoilRoot>
); );

View File

@ -82,7 +82,10 @@ describe('useCreateActivityInDB', () => {
}); });
await act(async () => { await act(async () => {
await result.current.createActivityInDB(mockedActivity); await result.current.createActivityInDB({
...mockedActivity,
__typename: 'Activity',
});
}); });
expect(mocks[0].result).toHaveBeenCalled(); expect(mocks[0].result).toHaveBeenCalled();

View File

@ -1,15 +1,14 @@
import { useEffect, useState } from 'react'; import { isNonEmptyString } from '@sniptt/guards';
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { findActivitiesOperationSignatureFactory } from '@/activities/graphql/operation-signatures/factories/findActivitiesOperationSignatureFactory';
import { useActivityTargetsForTargetableObjects } from '@/activities/hooks/useActivityTargetsForTargetableObjects'; import { useActivityTargetsForTargetableObjects } from '@/activities/hooks/useActivityTargetsForTargetableObjects';
import { FIND_MANY_ACTIVITIES_QUERY_KEY } from '@/activities/query-keys/FindManyActivitiesQueryKey';
import { Activity } from '@/activities/types/Activity'; import { Activity } from '@/activities/types/Activity';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { OrderByField } from '@/object-metadata/types/OrderByField'; import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
import { RecordGqlOperationOrderBy } from '@/object-record/graphql/types/RecordGqlOperationOrderBy';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { sortByAscString } from '~/utils/array/sortByAscString'; import { sortByAscString } from '~/utils/array/sortByAscString';
@ -18,26 +17,19 @@ export const useActivities = ({
activitiesFilters, activitiesFilters,
activitiesOrderByVariables, activitiesOrderByVariables,
skip, skip,
skipActivityTargets,
}: { }: {
targetableObjects: ActivityTargetableObject[]; targetableObjects: ActivityTargetableObject[];
activitiesFilters: ObjectRecordQueryFilter; activitiesFilters: RecordGqlOperationFilter;
activitiesOrderByVariables: OrderByField; activitiesOrderByVariables: RecordGqlOperationOrderBy;
skip?: boolean; skip?: boolean;
skipActivityTargets?: boolean;
}) => { }) => {
const [initialized, setInitialized] = useState(false);
const { objectMetadataItems } = useObjectMetadataItems(); const { objectMetadataItems } = useObjectMetadataItems();
const { const { activityTargets, loadingActivityTargets } =
activityTargets, useActivityTargetsForTargetableObjects({
loadingActivityTargets, targetableObjects,
initialized: initializedActivityTargets, skip: skip,
} = useActivityTargetsForTargetableObjects({ });
targetableObjects,
skip: skipActivityTargets || skip,
});
const activityIds = [ const activityIds = [
...new Set( ...new Set(
@ -51,70 +43,40 @@ export const useActivities = ({
), ),
]; ];
const activityTargetsFound = const filter: RecordGqlOperationFilter = {
initializedActivityTargets && isNonEmptyArray(activityTargets); id:
targetableObjects.length > 0
const filter: ObjectRecordQueryFilter = { ? {
id: activityTargetsFound in: activityIds,
? { }
in: activityIds, : undefined,
}
: undefined,
...activitiesFilters, ...activitiesFilters,
}; };
const skipActivities = const FIND_ACTIVITIES_OPERATION_SIGNATURE =
skip || findActivitiesOperationSignatureFactory({ objectMetadataItems });
(!skipActivityTargets &&
(!initializedActivityTargets || !activityTargetsFound));
const { records: activities, loading: loadingActivities } = const { records: activities, loading: loadingActivities } =
useFindManyRecords<Activity>({ useFindManyRecords<Activity>({
skip: skipActivities, skip: skip || loadingActivityTargets,
objectNameSingular: FIND_MANY_ACTIVITIES_QUERY_KEY.objectNameSingular, objectNameSingular:
depth: FIND_MANY_ACTIVITIES_QUERY_KEY.depth, FIND_ACTIVITIES_OPERATION_SIGNATURE.objectNameSingular,
queryFields: recordGqlFields: FIND_ACTIVITIES_OPERATION_SIGNATURE.fields,
FIND_MANY_ACTIVITIES_QUERY_KEY.fieldsFactory?.(objectMetadataItems),
filter, filter,
orderBy: activitiesOrderByVariables, orderBy: activitiesOrderByVariables,
onCompleted: useRecoilCallback( onCompleted: useRecoilCallback(
({ set }) => ({ set }) =>
(activities) => { (activities) => {
if (!initialized) {
setInitialized(true);
}
for (const activity of activities) { for (const activity of activities) {
set(recordStoreFamilyState(activity.id), activity); set(recordStoreFamilyState(activity.id), activity);
} }
}, },
[initialized], [],
), ),
}); });
const loading = loadingActivities || loadingActivityTargets;
const noActivities =
(!activityTargetsFound && !skipActivityTargets && initialized) ||
(initialized && !loading && !isNonEmptyArray(activities));
useEffect(() => {
if (skipActivities || noActivities) {
setInitialized(true);
}
}, [
activities,
initialized,
loading,
noActivities,
skipActivities,
skipActivityTargets,
]);
return { return {
activities, activities,
loading, loading: loadingActivities || loadingActivityTargets,
initialized,
noActivities,
}; };
}; };

View File

@ -1,4 +1,3 @@
import { useState } from 'react';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { ActivityTarget } from '@/activities/types/ActivityTarget'; import { ActivityTarget } from '@/activities/types/ActivityTarget';
@ -16,8 +15,6 @@ export const useActivityTargetsForTargetableObject = ({
nameSingular: targetableObject.targetObjectNameSingular, nameSingular: targetableObject.targetObjectNameSingular,
}); });
const [initialized, setInitialized] = useState(false);
const targetableObjectId = targetableObject.id; const targetableObjectId = targetableObject.id;
const skipRequest = !isNonEmptyString(targetableObjectId); const skipRequest = !isNonEmptyString(targetableObjectId);
@ -34,16 +31,10 @@ export const useActivityTargetsForTargetableObject = ({
eq: targetableObject.id, eq: targetableObject.id,
}, },
}, },
onCompleted: () => {
if (!initialized) {
setInitialized(true);
}
},
}); });
return { return {
activityTargets, activityTargets,
loadingActivityTargets, loadingActivityTargets,
initialized,
}; };
}; };

View File

@ -1,7 +1,6 @@
import { useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { FIND_MANY_ACTIVITY_TARGETS_QUERY_KEY } from '@/activities/query-keys/FindManyActivityTargetsQueryKey'; import { findActivityTargetsOperationSignatureFactory } from '@/activities/graphql/operation-signatures/factories/findActivityTargetsOperationSignatureFactory';
import { ActivityTarget } from '@/activities/types/ActivityTarget'; import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { getActivityTargetsFilter } from '@/activities/utils/getActivityTargetsFilter'; import { getActivityTargetsFilter } from '@/activities/utils/getActivityTargetsFilter';
@ -11,12 +10,14 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
export const useActivityTargetsForTargetableObjects = ({ export const useActivityTargetsForTargetableObjects = ({
targetableObjects, targetableObjects,
skip, skip,
onCompleted,
}: { }: {
targetableObjects: Pick< targetableObjects: Pick<
ActivityTargetableObject, ActivityTargetableObject,
'id' | 'targetObjectNameSingular' 'id' | 'targetObjectNameSingular'
>[]; >[];
skip?: boolean; skip?: boolean;
onCompleted?: (activityTargets: ActivityTarget[]) => void;
}) => { }) => {
const activityTargetsFilter = getActivityTargetsFilter({ const activityTargetsFilter = getActivityTargetsFilter({
targetableObjects: targetableObjects, targetableObjects: targetableObjects,
@ -24,7 +25,8 @@ export const useActivityTargetsForTargetableObjects = ({
const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const [initialized, setInitialized] = useState(false); const FIND_ACTIVITY_TARGETS_OPERATION_SIGNATURE =
findActivityTargetsOperationSignatureFactory({ objectMetadataItems });
// TODO: We want to optimistically remove from this request // TODO: We want to optimistically remove from this request
// If we are on a show page and we remove the current show page object corresponding activity target // If we are on a show page and we remove the current show page object corresponding activity target
@ -33,22 +35,14 @@ export const useActivityTargetsForTargetableObjects = ({
useFindManyRecords<ActivityTarget>({ useFindManyRecords<ActivityTarget>({
skip, skip,
objectNameSingular: objectNameSingular:
FIND_MANY_ACTIVITY_TARGETS_QUERY_KEY.objectNameSingular, FIND_ACTIVITY_TARGETS_OPERATION_SIGNATURE.objectNameSingular,
filter: activityTargetsFilter, filter: activityTargetsFilter,
queryFields: recordGqlFields: FIND_ACTIVITY_TARGETS_OPERATION_SIGNATURE.fields,
FIND_MANY_ACTIVITY_TARGETS_QUERY_KEY.fieldsFactory?.( onCompleted,
objectMetadataItems,
),
onCompleted: () => {
if (!initialized) {
setInitialized(true);
}
},
}); });
return { return {
activityTargets, activityTargets,
loadingActivityTargets, loadingActivityTargets,
initialized,
}; };
}; };

View File

@ -34,7 +34,6 @@ export const useCreateActivityInCache = () => {
const { record: currentWorkspaceMemberRecord } = useFindOneRecord({ const { record: currentWorkspaceMemberRecord } = useFindOneRecord({
objectNameSingular: CoreObjectNameSingular.WorkspaceMember, objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
objectRecordId: currentWorkspaceMember?.id, objectRecordId: currentWorkspaceMember?.id,
depth: 0,
}); });
const { objectMetadataItem: objectMetadataItemActivity } = const { objectMetadataItem: objectMetadataItemActivity } =
@ -66,6 +65,7 @@ export const useCreateActivityInCache = () => {
const createdActivityInCache = createOneActivityInCache({ const createdActivityInCache = createOneActivityInCache({
id: activityId, id: activityId,
__typename: 'Activity',
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
author: currentWorkspaceMemberRecord, author: currentWorkspaceMemberRecord,

View File

@ -1,6 +1,6 @@
import { isNonEmptyArray } from '@sniptt/guards'; import { isNonEmptyArray } from '@sniptt/guards';
import { CREATE_ONE_ACTIVITY_QUERY_KEY } from '@/activities/query-keys/CreateOneActivityQueryKey'; import { CREATE_ONE_ACTIVITY_OPERATION_SIGNATURE } from '@/activities/graphql/operation-signatures/CreateOneActivityOperationSignature';
import { ActivityForEditor } from '@/activities/types/ActivityForEditor'; import { ActivityForEditor } from '@/activities/types/ActivityForEditor';
import { ActivityTarget } from '@/activities/types/ActivityTarget'; import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
@ -9,9 +9,9 @@ import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
export const useCreateActivityInDB = () => { export const useCreateActivityInDB = () => {
const { createOneRecord: createOneActivity } = useCreateOneRecord({ const { createOneRecord: createOneActivity } = useCreateOneRecord({
objectNameSingular: CREATE_ONE_ACTIVITY_QUERY_KEY.objectNameSingular, objectNameSingular:
queryFields: CREATE_ONE_ACTIVITY_QUERY_KEY.fields, CREATE_ONE_ACTIVITY_OPERATION_SIGNATURE.objectNameSingular,
depth: CREATE_ONE_ACTIVITY_QUERY_KEY.depth, recordGqlFields: CREATE_ONE_ACTIVITY_OPERATION_SIGNATURE.fields,
}); });
const { createManyRecords: createManyActivityTargets } = const { createManyRecords: createManyActivityTargets } =

View File

@ -1,6 +1,6 @@
import { useApolloClient } from '@apollo/client'; import { useApolloClient } from '@apollo/client';
import { FIND_MANY_ACTIVITIES_QUERY_KEY } from '@/activities/query-keys/FindManyActivitiesQueryKey'; import { findActivitiesOperationSignatureFactory } from '@/activities/graphql/operation-signatures/factories/findActivitiesOperationSignatureFactory';
import { Activity } from '@/activities/types/Activity'; import { Activity } from '@/activities/types/Activity';
import { ActivityTarget } from '@/activities/types/ActivityTarget'; import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
@ -104,15 +104,16 @@ export const usePrepareFindManyActivitiesQuery = () => {
return a.createdAt > b.createdAt ? -1 : 1; return a.createdAt > b.createdAt ? -1 : 1;
}); });
const FIND_ACTIVITIES_OPERATION_SIGNATURE =
findActivitiesOperationSignatureFactory({ objectMetadataItems });
upsertFindManyActivitiesInCache({ upsertFindManyActivitiesInCache({
objectRecordsToOverwrite: filteredActivities, objectRecordsToOverwrite: filteredActivities,
queryVariables: { queryVariables: {
...nextFindManyActivitiesQueryFilter, ...nextFindManyActivitiesQueryFilter,
orderBy: { createdAt: 'DescNullsFirst' }, orderBy: { createdAt: 'DescNullsFirst' },
}, },
depth: FIND_MANY_ACTIVITIES_QUERY_KEY.depth, recordGqlFields: FIND_ACTIVITIES_OPERATION_SIGNATURE.fields,
queryFields:
FIND_MANY_ACTIVITIES_QUERY_KEY.fieldsFactory?.(objectMetadataItems),
computeReferences: true, computeReferences: true,
}); });
}; };

View File

@ -27,14 +27,10 @@ export const Notes = ({
}: { }: {
targetableObject: ActivityTargetableObject; targetableObject: ActivityTargetableObject;
}) => { }) => {
const { notes, initialized } = useNotes(targetableObject); const { notes } = useNotes(targetableObject);
const openCreateActivity = useOpenCreateActivityDrawer(); const openCreateActivity = useOpenCreateActivityDrawer();
if (!initialized) {
return <></>;
}
if (notes?.length === 0) { if (notes?.length === 0) {
return ( return (
<AnimatedPlaceholderEmptyContainer> <AnimatedPlaceholderEmptyContainer>

View File

@ -6,7 +6,6 @@ import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableE
jest.mock('@/activities/hooks/useActivities', () => ({ jest.mock('@/activities/hooks/useActivities', () => ({
useActivities: jest.fn(() => ({ useActivities: jest.fn(() => ({
activities: [{ id: '1', content: 'Example Note' }], activities: [{ id: '1', content: 'Example Note' }],
initialized: true,
loading: false, loading: false,
})), })),
})); }));
@ -29,7 +28,7 @@ jest.mock('recoil', () => {
}); });
describe('useNotes', () => { describe('useNotes', () => {
it('should return notes, initialized, and loading as expected', () => { it('should return notes, and loading as expected', () => {
const mockTargetableObject: ActivityTargetableObject = { const mockTargetableObject: ActivityTargetableObject = {
id: '1', id: '1',
targetObjectNameSingular: 'Example Target', targetObjectNameSingular: 'Example Target',
@ -39,7 +38,6 @@ describe('useNotes', () => {
expect(result.current.notes).toEqual([ expect(result.current.notes).toEqual([
{ id: '1', content: 'Example Note' }, { id: '1', content: 'Example Note' },
]); ]);
expect(result.current.initialized).toBe(true);
expect(result.current.loading).toBe(false); expect(result.current.loading).toBe(false);
}); });
}); });

View File

@ -5,7 +5,7 @@ import { useActivities } from '@/activities/hooks/useActivities';
import { currentNotesQueryVariablesState } from '@/activities/notes/states/currentNotesQueryVariablesState'; import { currentNotesQueryVariablesState } from '@/activities/notes/states/currentNotesQueryVariablesState';
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FindManyTimelineActivitiesOrderBy'; import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FindManyTimelineActivitiesOrderBy';
import { Note } from '@/activities/types/Note'; import { Note } from '@/activities/types/Note';
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables'; import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { ActivityTargetableObject } from '../../types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '../../types/ActivityTargetableEntity';
@ -18,11 +18,11 @@ export const useNotes = (targetableObject: ActivityTargetableObject) => {
type: { eq: 'Note' }, type: { eq: 'Note' },
}, },
orderBy: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY, orderBy: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
}) as ObjectRecordQueryVariables, }) as RecordGqlOperationVariables,
[], [],
); );
const { activities, initialized, loading } = useActivities({ const { activities, loading } = useActivities({
activitiesFilters: notesQueryVariables.filter ?? {}, activitiesFilters: notesQueryVariables.filter ?? {},
activitiesOrderByVariables: notesQueryVariables.orderBy ?? {}, activitiesOrderByVariables: notesQueryVariables.orderBy ?? {},
targetableObjects: [targetableObject], targetableObjects: [targetableObject],
@ -44,7 +44,6 @@ export const useNotes = (targetableObject: ActivityTargetableObject) => {
return { return {
notes: activities as Note[], notes: activities as Note[],
initialized,
loading, loading,
}; };
}; };

View File

@ -1,9 +1,9 @@
import { atom } from 'recoil'; import { atom } from 'recoil';
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables'; import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
export const currentNotesQueryVariablesState = export const currentNotesQueryVariablesState =
atom<ObjectRecordQueryVariables | null>({ atom<RecordGqlOperationVariables | null>({
default: null, default: null,
key: 'currentNotesQueryVariablesState', key: 'currentNotesQueryVariablesState',
}); });

View File

@ -1,34 +0,0 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { QueryKey } from '@/object-record/query-keys/types/QueryKey';
export const CREATE_ONE_ACTIVITY_QUERY_KEY: QueryKey = {
objectNameSingular: CoreObjectNameSingular.Activity,
variables: {},
fields: {
id: true,
__typename: true,
createdAt: true,
updatedAt: true,
author: {
id: true,
name: true,
__typename: true,
},
authorId: true,
assigneeId: true,
assignee: {
id: true,
name: true,
__typename: true,
},
comments: true,
attachments: true,
body: true,
title: true,
completedAt: true,
dueAt: true,
reminderAt: true,
type: true,
},
depth: 1,
};

View File

@ -19,7 +19,6 @@ import { useDeleteRecordFromCache } from '@/object-record/cache/hooks/useDeleteR
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { getChildRelationArray } from '@/object-record/utils/getChildRelationArray';
import { mapToRecordId } from '@/object-record/utils/mapToObjectId'; import { mapToRecordId } from '@/object-record/utils/mapToObjectId';
import { IconButton } from '@/ui/input/button/components/IconButton'; import { IconButton } from '@/ui/input/button/components/IconButton';
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState'; import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
@ -85,10 +84,6 @@ export const ActivityActionBar = () => {
.getLoadable(recordStoreFamilyState(activityIdInDrawer)) .getLoadable(recordStoreFamilyState(activityIdInDrawer))
.getValue() as Activity; .getValue() as Activity;
const activityTargets = getChildRelationArray({
childRelation: activity.activityTargets,
});
setIsRightDrawerOpen(false); setIsRightDrawerOpen(false);
if (!isNonEmptyString(viewableActivityId)) { if (!isNonEmptyString(viewableActivityId)) {
@ -103,10 +98,10 @@ export const ActivityActionBar = () => {
if (isNonEmptyString(activityIdInDrawer)) { if (isNonEmptyString(activityIdInDrawer)) {
const activityTargetIdsToDelete: string[] = const activityTargetIdsToDelete: string[] =
activityTargets.map(mapToRecordId) ?? []; activity.activityTargets.map(mapToRecordId) ?? [];
deleteActivityFromCache(activity); deleteActivityFromCache(activity);
activityTargets.forEach((activityTarget: ActivityTarget) => { activity.activityTargets.forEach((activityTarget: ActivityTarget) => {
deleteActivityTargetFromCache(activityTarget); deleteActivityTargetFromCache(activityTarget);
}); });

View File

@ -18,7 +18,6 @@ export const CurrentUserDueTaskCountEffect = () => {
const { records: tasks } = useFindManyRecords<Activity>({ const { records: tasks } = useFindManyRecords<Activity>({
objectNameSingular: CoreObjectNameSingular.Activity, objectNameSingular: CoreObjectNameSingular.Activity,
depth: 0,
filter: { filter: {
type: { eq: 'Task' }, type: { eq: 'Task' },
completedAt: { is: 'NULL' }, completedAt: { is: 'NULL' },

View File

@ -40,7 +40,6 @@ export const TaskGroups = ({
upcomingTasks, upcomingTasks,
unscheduledTasks, unscheduledTasks,
completedTasks, completedTasks,
initialized,
} = useTasks({ } = useTasks({
filterDropdownId: filterDropdownId, filterDropdownId: filterDropdownId,
targetableObjects: targetableObjects ?? [], targetableObjects: targetableObjects ?? [],
@ -51,10 +50,6 @@ export const TaskGroups = ({
const { activeTabIdState } = useTabList(TASKS_TAB_LIST_COMPONENT_ID); const { activeTabIdState } = useTabList(TASKS_TAB_LIST_COMPONENT_ID);
const activeTabId = useRecoilValue(activeTabIdState); const activeTabId = useRecoilValue(activeTabIdState);
if (!initialized) {
return <></>;
}
if ( if (
(activeTabId !== 'done' && (activeTabId !== 'done' &&
todayOrPreviousTasks?.length === 0 && todayOrPreviousTasks?.length === 0 &&

View File

@ -49,7 +49,6 @@ const useActivitiesMock = jest.fn(
activities: isCompletedFilter activities: isCompletedFilter
? completedTasks ? completedTasks
: [...todayOrPreviousTasks, ...unscheduledTasks], : [...todayOrPreviousTasks, ...unscheduledTasks],
initialized: true,
}; };
}, },
); );
@ -79,7 +78,6 @@ describe('useTasks', () => {
upcomingTasks: [], upcomingTasks: [],
unscheduledTasks, unscheduledTasks,
completedTasks, completedTasks,
initialized: true,
}); });
}); });
}); });

View File

@ -1,5 +1,4 @@
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { isNonEmptyArray } from '@sniptt/guards';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
@ -9,8 +8,8 @@ import { currentIncompleteTaskQueryVariablesState } from '@/activities/tasks/sta
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FindManyTimelineActivitiesOrderBy'; import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FindManyTimelineActivitiesOrderBy';
import { Activity } from '@/activities/types/Activity'; import { Activity } from '@/activities/types/Activity';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables';
import { parseDate } from '~/utils/date-utils'; import { parseDate } from '~/utils/date-utils';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
@ -41,8 +40,6 @@ export const useTasks = ({
[selectedFilter], [selectedFilter],
); );
const skipActivityTargets = !isNonEmptyArray(targetableObjects);
const completedQueryVariables = useMemo( const completedQueryVariables = useMemo(
() => () =>
({ ({
@ -52,7 +49,7 @@ export const useTasks = ({
...assigneeIdFilter, ...assigneeIdFilter,
}, },
orderBy: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY, orderBy: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
}) as ObjectRecordQueryVariables, }) as RecordGqlOperationVariables,
[assigneeIdFilter], [assigneeIdFilter],
); );
@ -65,7 +62,7 @@ export const useTasks = ({
...assigneeIdFilter, ...assigneeIdFilter,
}, },
orderBy: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY, orderBy: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
}) as ObjectRecordQueryVariables, }) as RecordGqlOperationVariables,
[assigneeIdFilter], [assigneeIdFilter],
); );
@ -110,24 +107,16 @@ export const useTasks = ({
setCurrentIncompleteTaskQueryVariables, setCurrentIncompleteTaskQueryVariables,
]); ]);
const { const { activities: completeTasksData } = useActivities({
activities: completeTasksData,
initialized: initializedCompleteTasks,
} = useActivities({
targetableObjects, targetableObjects,
activitiesFilters: completedQueryVariables.filter ?? {}, activitiesFilters: completedQueryVariables.filter ?? {},
activitiesOrderByVariables: completedQueryVariables.orderBy ?? {}, activitiesOrderByVariables: completedQueryVariables.orderBy ?? {},
skipActivityTargets,
}); });
const { const { activities: incompleteTaskData } = useActivities({
activities: incompleteTaskData,
initialized: initializedIncompleteTasks,
} = useActivities({
targetableObjects, targetableObjects,
activitiesFilters: incompleteQueryVariables.filter ?? {}, activitiesFilters: incompleteQueryVariables.filter ?? {},
activitiesOrderByVariables: incompleteQueryVariables.orderBy ?? {}, activitiesOrderByVariables: incompleteQueryVariables.orderBy ?? {},
skipActivityTargets,
}); });
const todayOrPreviousTasks = incompleteTaskData?.filter((task) => { const todayOrPreviousTasks = incompleteTaskData?.filter((task) => {
@ -159,6 +148,5 @@ export const useTasks = ({
upcomingTasks: (upcomingTasks ?? []) as Activity[], upcomingTasks: (upcomingTasks ?? []) as Activity[],
unscheduledTasks: (unscheduledTasks ?? []) as Activity[], unscheduledTasks: (unscheduledTasks ?? []) as Activity[],
completedTasks: (completedTasks ?? []) as Activity[], completedTasks: (completedTasks ?? []) as Activity[],
initialized: initializedCompleteTasks && initializedIncompleteTasks,
}; };
}; };

View File

@ -1,9 +1,9 @@
import { atom } from 'recoil'; import { atom } from 'recoil';
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables'; import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
export const currentCompletedTaskQueryVariablesState = export const currentCompletedTaskQueryVariablesState =
atom<ObjectRecordQueryVariables | null>({ atom<RecordGqlOperationVariables | null>({
default: null, default: null,
key: 'currentCompletedTaskQueryVariablesState', key: 'currentCompletedTaskQueryVariablesState',
}); });

View File

@ -1,9 +1,9 @@
import { atom } from 'recoil'; import { atom } from 'recoil';
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables'; import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
export const currentIncompleteTaskQueryVariablesState = export const currentIncompleteTaskQueryVariablesState =
atom<ObjectRecordQueryVariables | null>({ atom<RecordGqlOperationVariables | null>({
default: null, default: null,
key: 'currentIncompleteTaskQueryVariablesState', key: 'currentIncompleteTaskQueryVariablesState',
}); });

View File

@ -2,7 +2,7 @@ import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup'; import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
import { timelineActivitiesNetworkingState } from '@/activities/timeline/states/timelineActivitiesNetworkingState'; import { timelineActivitiesForGroupState } from '@/activities/timeline/states/timelineActivitiesForGroupState';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder'; import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
import { import {
@ -32,20 +32,11 @@ export const Timeline = ({
}: { }: {
targetableObject: ActivityTargetableObject; targetableObject: ActivityTargetableObject;
}) => { }) => {
const { initialized, noActivities } = useRecoilValue( const timelineActivitiesForGroup = useRecoilValue(
timelineActivitiesNetworkingState, timelineActivitiesForGroupState,
); );
const showEmptyState = noActivities; if (timelineActivitiesForGroup.length === 0) {
const showLoadingState = !initialized;
if (showLoadingState) {
// TODO: Display a beautiful loading page
return <></>;
}
if (showEmptyState) {
return ( return (
<AnimatedPlaceholderEmptyContainer> <AnimatedPlaceholderEmptyContainer>
<AnimatedPlaceholder type="emptyTimeline" /> <AnimatedPlaceholder type="emptyTimeline" />

View File

@ -6,7 +6,6 @@ import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/co
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectIdState'; import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectIdState';
import { timelineActivitiesFammilyState } from '@/activities/timeline/states/timelineActivitiesFamilyState'; import { timelineActivitiesFammilyState } from '@/activities/timeline/states/timelineActivitiesFamilyState';
import { timelineActivitiesForGroupState } from '@/activities/timeline/states/timelineActivitiesForGroupState'; import { timelineActivitiesForGroupState } from '@/activities/timeline/states/timelineActivitiesForGroupState';
import { timelineActivitiesNetworkingState } from '@/activities/timeline/states/timelineActivitiesNetworkingState';
import { timelineActivityWithoutTargetsFamilyState } from '@/activities/timeline/states/timelineActivityWithoutTargetsFamilyState'; import { timelineActivityWithoutTargetsFamilyState } from '@/activities/timeline/states/timelineActivityWithoutTargetsFamilyState';
import { Activity } from '@/activities/types/Activity'; import { Activity } from '@/activities/types/Activity';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
@ -27,16 +26,13 @@ export const TimelineQueryEffect = ({
setTimelineTargetableObject(targetableObject); setTimelineTargetableObject(targetableObject);
}, [targetableObject, setTimelineTargetableObject]); }, [targetableObject, setTimelineTargetableObject]);
const { activities, initialized, noActivities } = useActivities({ const { activities } = useActivities({
targetableObjects: [targetableObject], targetableObjects: [targetableObject],
activitiesFilters: {}, activitiesFilters: {},
activitiesOrderByVariables: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY, activitiesOrderByVariables: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
skip: !isDefined(targetableObject), skip: !isDefined(targetableObject),
}); });
const [timelineActivitiesNetworking, setTimelineActivitiesNetworking] =
useRecoilState(timelineActivitiesNetworkingState);
const [timelineActivitiesForGroup, setTimelineActivitiesForGroup] = const [timelineActivitiesForGroup, setTimelineActivitiesForGroup] =
useRecoilState(timelineActivitiesForGroupState); useRecoilState(timelineActivitiesForGroupState);
@ -49,6 +45,7 @@ export const TimelineQueryEffect = ({
...activities.map((activity) => ({ ...activities.map((activity) => ({
id: activity.id, id: activity.id,
createdAt: activity.createdAt, createdAt: activity.createdAt,
__typename: activity.__typename,
})), })),
].sort(sortObjectRecordByDateField('createdAt', 'DescNullsLast')); ].sort(sortObjectRecordByDateField('createdAt', 'DescNullsLast'));
@ -59,23 +56,9 @@ export const TimelineQueryEffect = ({
if (!isDeeplyEqual(activitiesForGroup, timelineActivitiesForGroupSorted)) { if (!isDeeplyEqual(activitiesForGroup, timelineActivitiesForGroupSorted)) {
setTimelineActivitiesForGroup(activitiesForGroup); setTimelineActivitiesForGroup(activitiesForGroup);
} }
if (
!isDeeplyEqual(timelineActivitiesNetworking.initialized, initialized) ||
!isDeeplyEqual(timelineActivitiesNetworking.noActivities, noActivities)
) {
setTimelineActivitiesNetworking({
initialized,
noActivities,
});
}
}, [ }, [
activities, activities,
initialized,
noActivities,
setTimelineActivitiesNetworking,
targetableObject, targetableObject,
timelineActivitiesNetworking,
timelineActivitiesForGroup, timelineActivitiesForGroup,
setTimelineActivitiesForGroup, setTimelineActivitiesForGroup,
]); ]);

View File

@ -1,5 +1,6 @@
import { OrderByField } from '@/object-metadata/types/OrderByField'; import { RecordGqlOperationOrderBy } from '@/object-record/graphql/types/RecordGqlOperationOrderBy';
export const FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY: OrderByField = { export const FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY: RecordGqlOperationOrderBy =
createdAt: 'DescNullsFirst', {
}; createdAt: 'DescNullsFirst',
};

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'; import { useEffect } from 'react';
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
import { useRecoilCallback, useRecoilState } from 'recoil'; import { useRecoilCallback, useRecoilState } from 'recoil';
@ -28,15 +28,10 @@ export const useTimelineActivities = ({
} }
}, [targetableObject, setObjectShowPageTargetableObject]); }, [targetableObject, setObjectShowPageTargetableObject]);
const { const { activityTargets, loadingActivityTargets } =
activityTargets, useActivityTargetsForTargetableObject({
loadingActivityTargets, targetableObject,
initialized: initializedActivityTargets, });
} = useActivityTargetsForTargetableObject({
targetableObject,
});
const [initialized, setInitialized] = useState(false);
const activityIds = Array.from( const activityIds = Array.from(
new Set( new Set(
@ -65,33 +60,18 @@ export const useTimelineActivities = ({
onCompleted: useRecoilCallback( onCompleted: useRecoilCallback(
({ set }) => ({ set }) =>
(activities) => { (activities) => {
if (!initialized) {
setInitialized(true);
}
for (const activity of activities) { for (const activity of activities) {
set(recordStoreFamilyState(activity.id), activity); set(recordStoreFamilyState(activity.id), activity);
} }
}, },
[initialized], [],
), ),
depth: 3,
}); });
const noActivityTargets =
initializedActivityTargets && !isNonEmptyArray(activityTargets);
useEffect(() => {
if (noActivityTargets) {
setInitialized(true);
}
}, [noActivityTargets]);
const loading = loadingActivities || loadingActivityTargets; const loading = loadingActivities || loadingActivityTargets;
return { return {
activities, activities,
loading, loading,
initialized,
}; };
}; };

View File

@ -1,12 +0,0 @@
import { createState } from 'twenty-ui';
export const timelineActivitiesNetworkingState = createState<{
initialized: boolean;
noActivities: boolean;
}>({
key: 'timelineActivitiesNetworkingState',
defaultValue: {
initialized: false,
noActivities: false,
},
});

View File

@ -1,7 +1,10 @@
import { Activity } from '@/activities/types/Activity'; import { Activity } from '@/activities/types/Activity';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
export type ActivityForActivityGroup = Pick<Activity, 'id' | 'createdAt'>; export type ActivityForActivityGroup = Pick<
Activity,
'id' | 'createdAt' | '__typename'
>;
export type ActivityGroup = { export type ActivityGroup = {
month: number; month: number;

View File

@ -1,11 +1,12 @@
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables'; import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
import { sortByAscString } from '~/utils/array/sortByAscString'; import { sortByAscString } from '~/utils/array/sortByAscString';
// Todo: this should be replace by the operationSignatureFactory pattern
export const makeTimelineActivitiesQueryVariables = ({ export const makeTimelineActivitiesQueryVariables = ({
activityIds, activityIds,
}: { }: {
activityIds: string[]; activityIds: string[];
}): ObjectRecordQueryVariables => { }): RecordGqlOperationVariables => {
return { return {
filter: { filter: {
id: { id: {

View File

@ -12,4 +12,5 @@ export type TimelineActivity = {
linkedRecordCachedName: string; linkedRecordCachedName: string;
linkedRecordId: string; linkedRecordId: string;
linkedObjectMetadataId: string; linkedObjectMetadataId: string;
__typename: 'TimelineActivity';
}; };

View File

@ -5,7 +5,14 @@ import { WorkspaceMember } from '~/generated-metadata/graphql';
export type ActivityForEditor = Pick< export type ActivityForEditor = Pick<
Activity, Activity,
'id' | 'title' | 'body' | 'type' | 'completedAt' | 'dueAt' | 'updatedAt' | 'id'
| 'title'
| 'body'
| 'type'
| 'completedAt'
| 'dueAt'
| 'updatedAt'
| '__typename'
> & { > & {
comments?: Comment[]; comments?: Comment[];
} & { } & {

View File

@ -12,4 +12,5 @@ export type ActivityTarget = {
person?: Pick<Person, 'id' | 'name' | 'avatarUrl'> | null; person?: Pick<Person, 'id' | 'name' | 'avatarUrl'> | null;
company?: Pick<Company, 'id' | 'name' | 'domainName'> | null; company?: Pick<Company, 'id' | 'name' | 'domainName'> | null;
[key: string]: any; [key: string]: any;
__typename: 'ActivityTarget';
}; };

View File

@ -7,4 +7,5 @@ export type Comment = {
updatedAt: string; updatedAt: string;
activityId: string; activityId: string;
author: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'>; author: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'>;
__typename: 'Comment';
}; };

View File

@ -1,9 +1,9 @@
import { Reference, StoreObject } from '@apollo/client'; import { Reference, StoreObject } from '@apollo/client';
import { ReadFieldFunction } from '@apollo/client/cache/core/types/common'; import { ReadFieldFunction } from '@apollo/client/cache/core/types/common';
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
import { OrderBy } from '@/object-metadata/types/OrderBy'; import { OrderBy } from '@/object-metadata/types/OrderBy';
import { OrderByField } from '@/object-metadata/types/OrderByField'; import { RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
import { RecordGqlOperationOrderBy } from '@/object-record/graphql/types/RecordGqlOperationOrderBy';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { sortAsc, sortDesc, sortNullsFirst, sortNullsLast } from '~/utils/sort'; import { sortAsc, sortDesc, sortNullsFirst, sortNullsLast } from '~/utils/sort';
@ -12,8 +12,8 @@ export const sortCachedObjectEdges = ({
orderBy, orderBy,
readCacheField, readCacheField,
}: { }: {
edges: CachedObjectRecordEdge[]; edges: RecordGqlRefEdge[];
orderBy: OrderByField; orderBy: RecordGqlOperationOrderBy;
readCacheField: ReadFieldFunction; readCacheField: ReadFieldFunction;
}) => { }) => {
const [orderByFieldName, orderByFieldValue] = Object.entries(orderBy)[0]; const [orderByFieldName, orderByFieldValue] = Object.entries(orderBy)[0];
@ -23,7 +23,7 @@ export const sortCachedObjectEdges = ({
: Object.entries(orderByFieldValue)[0]; : Object.entries(orderByFieldValue)[0];
const readFieldValueToSort = ( const readFieldValueToSort = (
edge: CachedObjectRecordEdge, edge: RecordGqlRefEdge,
): string | number | null => { ): string | number | null => {
const recordFromCache = edge.node; const recordFromCache = edge.node;
const fieldValue = const fieldValue =

View File

@ -1,6 +1,6 @@
import { ApolloCache, StoreObject } from '@apollo/client'; import { ApolloCache, StoreObject } from '@apollo/client';
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge'; import { RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs'; import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { capitalize } from '~/utils/string/capitalize'; import { capitalize } from '~/utils/string/capitalize';
@ -48,7 +48,7 @@ export const triggerAttachRelationOptimisticEffect = ({
} }
if (fieldValueisObjectRecordConnectionWithRefs) { if (fieldValueisObjectRecordConnectionWithRefs) {
const nextEdges: CachedObjectRecordEdge[] = [ const nextEdges: RecordGqlRefEdge[] = [
...targetRecordFieldValue.edges, ...targetRecordFieldValue.edges,
{ {
__typename: `${sourceRecordTypeName}Edge`, __typename: `${sourceRecordTypeName}Edge`,

View File

@ -2,11 +2,11 @@ import { ApolloCache, StoreObject } from '@apollo/client';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect'; import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename'; import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename';
import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs'; import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs';
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
/* /*
TODO: for now new records are added to all cached record lists, no matter what the variables (filters, orderBy, etc.) are. TODO: for now new records are added to all cached record lists, no matter what the variables (filters, orderBy, etc.) are.
@ -21,7 +21,7 @@ export const triggerCreateRecordsOptimisticEffect = ({
}: { }: {
cache: ApolloCache<unknown>; cache: ApolloCache<unknown>;
objectMetadataItem: ObjectMetadataItem; objectMetadataItem: ObjectMetadataItem;
recordsToCreate: CachedObjectRecord[]; recordsToCreate: RecordGqlNode[];
objectMetadataItems: ObjectMetadataItem[]; objectMetadataItems: ObjectMetadataItem[];
}) => { }) => {
recordsToCreate.forEach((record) => recordsToCreate.forEach((record) =>
@ -56,7 +56,7 @@ export const triggerCreateRecordsOptimisticEffect = ({
const rootQueryCachedObjectRecordConnection = rootQueryCachedResponse; const rootQueryCachedObjectRecordConnection = rootQueryCachedResponse;
const rootQueryCachedRecordEdges = readField<CachedObjectRecordEdge[]>( const rootQueryCachedRecordEdges = readField<RecordGqlRefEdge[]>(
'edges', 'edges',
rootQueryCachedObjectRecordConnection, rootQueryCachedObjectRecordConnection,
); );

View File

@ -1,11 +1,11 @@
import { ApolloCache, StoreObject } from '@apollo/client'; import { ApolloCache, StoreObject } from '@apollo/client';
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect'; import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
import { CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables'; import { CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs'; import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs';
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName'; import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
@ -17,7 +17,7 @@ export const triggerDeleteRecordsOptimisticEffect = ({
}: { }: {
cache: ApolloCache<unknown>; cache: ApolloCache<unknown>;
objectMetadataItem: ObjectMetadataItem; objectMetadataItem: ObjectMetadataItem;
recordsToDelete: CachedObjectRecord[]; recordsToDelete: RecordGqlNode[];
objectMetadataItems: ObjectMetadataItem[]; objectMetadataItems: ObjectMetadataItem[];
}) => { }) => {
cache.modify<StoreObject>({ cache.modify<StoreObject>({
@ -45,7 +45,7 @@ export const triggerDeleteRecordsOptimisticEffect = ({
const recordIdsToDelete = recordsToDelete.map(({ id }) => id); const recordIdsToDelete = recordsToDelete.map(({ id }) => id);
const cachedEdges = readField<CachedObjectRecordEdge[]>( const cachedEdges = readField<RecordGqlRefEdge[]>(
'edges', 'edges',
rootQueryCachedObjectRecordConnection, rootQueryCachedObjectRecordConnection,
); );

View File

@ -2,12 +2,12 @@ import { ApolloCache, StoreObject } from '@apollo/client';
import { sortCachedObjectEdges } from '@/apollo/optimistic-effect/utils/sortCachedObjectEdges'; import { sortCachedObjectEdges } from '@/apollo/optimistic-effect/utils/sortCachedObjectEdges';
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect'; import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
import { CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables'; import { CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename'; import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename';
import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs'; import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/isObjectRecordConnectionWithRefs';
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter'; import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName'; import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
@ -23,8 +23,8 @@ export const triggerUpdateRecordOptimisticEffect = ({
}: { }: {
cache: ApolloCache<unknown>; cache: ApolloCache<unknown>;
objectMetadataItem: ObjectMetadataItem; objectMetadataItem: ObjectMetadataItem;
currentRecord: CachedObjectRecord; currentRecord: RecordGqlNode;
updatedRecord: CachedObjectRecord; updatedRecord: RecordGqlNode;
objectMetadataItems: ObjectMetadataItem[]; objectMetadataItems: ObjectMetadataItem[];
}) => { }) => {
triggerUpdateRelationsOptimisticEffect({ triggerUpdateRelationsOptimisticEffect({
@ -58,8 +58,7 @@ export const triggerUpdateRecordOptimisticEffect = ({
); );
const rootQueryCurrentEdges = const rootQueryCurrentEdges =
readField<CachedObjectRecordEdge[]>('edges', rootQueryConnection) ?? readField<RecordGqlRefEdge[]>('edges', rootQueryConnection) ?? [];
[];
let rootQueryNextEdges = [...rootQueryCurrentEdges]; let rootQueryNextEdges = [...rootQueryCurrentEdges];

View File

@ -4,12 +4,13 @@ import { getRelationDefinition } from '@/apollo/optimistic-effect/utils/getRelat
import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect'; import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect'; import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect';
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
import { CORE_OBJECT_NAMES_TO_DELETE_ON_TRIGGER_RELATION_DETACH } from '@/apollo/types/coreObjectNamesToDeleteOnRelationDetach'; import { CORE_OBJECT_NAMES_TO_DELETE_ON_TRIGGER_RELATION_DETACH } from '@/apollo/types/coreObjectNamesToDeleteOnRelationDetach';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isObjectRecordConnection } from '@/object-record/cache/utils/isObjectRecordConnection'; import { isObjectRecordConnection } from '@/object-record/cache/utils/isObjectRecordConnection';
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection'; import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
@ -23,8 +24,8 @@ export const triggerUpdateRelationsOptimisticEffect = ({
}: { }: {
cache: ApolloCache<unknown>; cache: ApolloCache<unknown>;
sourceObjectMetadataItem: ObjectMetadataItem; sourceObjectMetadataItem: ObjectMetadataItem;
currentSourceRecord: CachedObjectRecord | null; currentSourceRecord: ObjectRecord | null;
updatedSourceRecord: CachedObjectRecord | null; updatedSourceRecord: ObjectRecord | null;
objectMetadataItems: ObjectMetadataItem[]; objectMetadataItems: ObjectMetadataItem[];
}) => { }) => {
return sourceObjectMetadataItem.fields.forEach( return sourceObjectMetadataItem.fields.forEach(
@ -56,13 +57,13 @@ export const triggerUpdateRelationsOptimisticEffect = ({
relationDefinition; relationDefinition;
const currentFieldValueOnSourceRecord: const currentFieldValueOnSourceRecord:
| ObjectRecordConnection | RecordGqlConnection
| CachedObjectRecord | RecordGqlNode
| null = currentSourceRecord?.[fieldMetadataItemOnSourceRecord.name]; | null = currentSourceRecord?.[fieldMetadataItemOnSourceRecord.name];
const updatedFieldValueOnSourceRecord: const updatedFieldValueOnSourceRecord:
| ObjectRecordConnection | RecordGqlConnection
| CachedObjectRecord | RecordGqlNode
| null = updatedSourceRecord?.[fieldMetadataItemOnSourceRecord.name]; | null = updatedSourceRecord?.[fieldMetadataItemOnSourceRecord.name];
if ( if (
@ -85,7 +86,7 @@ export const triggerUpdateRelationsOptimisticEffect = ({
const targetRecordsToDetachFrom = const targetRecordsToDetachFrom =
currentFieldValueOnSourceRecordIsARecordConnection currentFieldValueOnSourceRecordIsARecordConnection
? currentFieldValueOnSourceRecord.edges.map( ? currentFieldValueOnSourceRecord.edges.map(
({ node }) => node as CachedObjectRecord, ({ node }) => node as RecordGqlNode,
) )
: [currentFieldValueOnSourceRecord].filter(isDefined); : [currentFieldValueOnSourceRecord].filter(isDefined);
@ -98,7 +99,7 @@ export const triggerUpdateRelationsOptimisticEffect = ({
const targetRecordsToAttachTo = const targetRecordsToAttachTo =
updatedFieldValueOnSourceRecordIsARecordConnection updatedFieldValueOnSourceRecordIsARecordConnection
? updatedFieldValueOnSourceRecord.edges.map( ? updatedFieldValueOnSourceRecord.edges.map(
({ node }) => node as CachedObjectRecord, ({ node }) => node as RecordGqlNode,
) )
: [updatedFieldValueOnSourceRecord].filter(isDefined); : [updatedFieldValueOnSourceRecord].filter(isDefined);

View File

@ -1,5 +0,0 @@
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
export type CachedObjectRecord<T extends ObjectRecord = ObjectRecord> = T & {
__typename: string;
};

View File

@ -1,9 +0,0 @@
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
export type CachedObjectRecordConnection = Omit<
ObjectRecordConnection,
'edges'
> & {
edges: CachedObjectRecordEdge[];
};

View File

@ -1,7 +0,0 @@
import { Reference } from '@apollo/client';
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
export type CachedObjectRecordEdge = Omit<ObjectRecordEdge, 'node'> & {
node: Reference;
};

View File

@ -1,6 +1,6 @@
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables'; import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
export type CachedObjectRecordQueryVariables = Omit< export type CachedObjectRecordQueryVariables = Omit<
ObjectRecordQueryVariables, RecordGqlOperationVariables,
'limit' 'limit'
> & { first?: ObjectRecordQueryVariables['limit'] }; > & { first?: RecordGqlOperationVariables['limit'] };

View File

@ -1,6 +0,0 @@
export type Attachment = {
id: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
};

View File

@ -9,4 +9,5 @@ export type Favorite = {
avatarType: AvatarType; avatarType: AvatarType;
link: string; link: string;
recordId: string; recordId: string;
__typename: 'Favorite';
}; };

View File

@ -4,6 +4,7 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserState } from '@/auth/states/currentUserState';
import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems'; import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@ -19,10 +20,18 @@ export const ObjectMetadataItemsLoadEffect = () => {
); );
useEffect(() => { useEffect(() => {
if (!isDeeplyEqual(objectMetadataItems, newObjectMetadataItems)) { const toSetObjectMetadataItems = isUndefinedOrNull(currentUser)
setObjectMetadataItems(newObjectMetadataItems); ? getObjectMetadataItemsMock()
: newObjectMetadataItems;
if (!isDeeplyEqual(objectMetadataItems, toSetObjectMetadataItems)) {
setObjectMetadataItems(toSetObjectMetadataItems);
} }
}, [newObjectMetadataItems, objectMetadataItems, setObjectMetadataItems]); }, [
currentUser,
newObjectMetadataItems,
objectMetadataItems,
setObjectMetadataItems,
]);
return <></>; return <></>;
}; };

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { ObjectMetadataItemsLoadEffect } from '@/object-metadata/components/ObjectMetadataItemsLoadEffect'; import { ObjectMetadataItemsLoadEffect } from '@/object-metadata/components/ObjectMetadataItemsLoadEffect';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope'; import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
@ -10,10 +9,8 @@ export const ObjectMetadataItemsProvider = ({
children, children,
}: React.PropsWithChildren) => { }: React.PropsWithChildren) => {
const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const shouldDisplayChildren = const shouldDisplayChildren = objectMetadataItems.length > 0;
objectMetadataItems.length > 0 || !currentWorkspaceMember;
return ( return (
<> <>

View File

@ -5,7 +5,7 @@ import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilte
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { GraphQLView } from '@/views/types/GraphQLView'; import { View } from '@/views/types/View';
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews'; import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
export const ObjectMetadataNavItems = () => { export const ObjectMetadataNavItems = () => {
@ -14,9 +14,7 @@ export const ObjectMetadataNavItems = () => {
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const currentPath = useLocation().pathname; const currentPath = useLocation().pathname;
const { records: views } = usePrefetchedData<GraphQLView>( const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
PrefetchKey.AllViews,
);
return ( return (
<> <>

View File

@ -1,7 +1,7 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { OrderBy } from '@/object-metadata/types/OrderBy'; import { OrderBy } from '@/object-metadata/types/OrderBy';
import { OrderByField } from '@/object-metadata/types/OrderByField';
import { getOrderByFieldForObjectMetadataItem } from '@/object-metadata/utils/getObjectOrderByField'; import { getOrderByFieldForObjectMetadataItem } from '@/object-metadata/utils/getObjectOrderByField';
import { RecordGqlOperationOrderBy } from '@/object-record/graphql/types/RecordGqlOperationOrderBy';
export const useGetObjectOrderByField = ({ export const useGetObjectOrderByField = ({
objectNameSingular, objectNameSingular,
@ -12,7 +12,9 @@ export const useGetObjectOrderByField = ({
objectNameSingular, objectNameSingular,
}); });
const getObjectOrderByField = (orderBy: OrderBy): OrderByField => { const getObjectOrderByField = (
orderBy: OrderBy,
): RecordGqlOperationOrderBy => {
return getOrderByFieldForObjectMetadataItem(objectMetadataItem, orderBy); return getOrderByFieldForObjectMetadataItem(objectMetadataItem, orderBy);
}; };

View File

@ -14,6 +14,7 @@ export const useObjectMetadataItem = ({
}: ObjectMetadataItemIdentifier) => { }: ObjectMetadataItemIdentifier) => {
const currentWorkspace = useRecoilValue(currentWorkspaceState); const currentWorkspace = useRecoilValue(currentWorkspaceState);
// Todo: deprecate this logic as mocked objectMetadataItems are laod in ObjectMetadataItemsLoadEffect anyway
const mockObjectMetadataItems = getObjectMetadataItemsMock(); const mockObjectMetadataItems = getObjectMetadataItemsMock();
let objectMetadataItem = useRecoilValue( let objectMetadataItem = useRecoilValue(

View File

@ -37,21 +37,10 @@ describe('mapFieldMetadataToGraphQLQuery', () => {
lastName lastName
}`); }`);
}); });
it('should not return relation if depth is < 1', async () => {
const res = mapFieldMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems,
depth: 0,
field: personObjectMetadataItem.fields.find(
(field) => field.name === 'company',
)!,
});
expect(formatGQLString(res)).toEqual('');
});
it('should return relation if it matches depth', async () => { it('should return non relation subFields if relation', async () => {
const res = mapFieldMetadataToGraphQLQuery({ const res = mapFieldMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems, objectMetadataItems: mockObjectMetadataItems,
depth: 1,
field: personObjectMetadataItem.fields.find( field: personObjectMetadataItem.fields.find(
(field) => field.name === 'company', (field) => field.name === 'company',
)!, )!,
@ -83,168 +72,14 @@ accountOwnerId
employees employees
id id
idealCustomerProfile idealCustomerProfile
}`);
});
it('should return relation with all sub relations if it matches depth', async () => {
const res = mapFieldMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems,
depth: 2,
field: personObjectMetadataItem.fields.find(
(field) => field.name === 'company',
)!,
});
expect(formatGQLString(res)).toEqual(`company
{
__typename
xLink
{
label
url
}
accountOwner
{
__typename
colorScheme
name
{
firstName
lastName
}
locale
userId
avatarUrl
createdAt
updatedAt
id
}
linkedinLink
{
label
url
}
attachments
{
edges {
node {
__typename
updatedAt
createdAt
name
personId
activityId
companyId
id
authorId
type
fullPath
}
}
}
domainName
opportunities
{
edges {
node {
__typename
personId
pointOfContactId
updatedAt
companyId
probability
closeDate
amount
{
amountMicros
currencyCode
}
id
createdAt
}
}
}
annualRecurringRevenue
{
amountMicros
currencyCode
}
createdAt
address
updatedAt
activityTargets
{
edges {
node {
__typename
updatedAt
createdAt
personId
activityId
companyId
id
}
}
}
favorites
{
edges {
node {
__typename
id
companyId
createdAt
personId
position
workspaceMemberId
updatedAt
}
}
}
people
{
edges {
node {
__typename
xLink
{
label
url
}
id
createdAt
city
email
jobTitle
name
{
firstName
lastName
}
phone
linkedinLink
{
label
url
}
updatedAt
avatarUrl
companyId
}
}
}
name
accountOwnerId
employees
id
idealCustomerProfile
}`); }`);
}); });
it('should return GraphQL fields based on queryFields', async () => { it('should return only return relation subFields that are in recordGqlFields', async () => {
const res = mapFieldMetadataToGraphQLQuery({ const res = mapFieldMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems, objectMetadataItems: mockObjectMetadataItems,
depth: 2, relationrecordFields: {
queryFields: { accountOwner: { id: true, name: true },
accountOwner: true,
people: true, people: true,
xLink: true, xLink: true,
linkedinLink: true, linkedinLink: true,
@ -274,17 +109,11 @@ xLink
accountOwner accountOwner
{ {
__typename __typename
colorScheme
name name
{ {
firstName firstName
lastName lastName
} }
locale
userId
avatarUrl
createdAt
updatedAt
id id
} }
linkedinLink linkedinLink

View File

@ -15,209 +15,11 @@ if (!personObjectMetadataItem) {
} }
describe('mapObjectMetadataToGraphQLQuery', () => { describe('mapObjectMetadataToGraphQLQuery', () => {
it('should return typename if depth < 0', async () => { it('should query only specified recordGqlFields', async () => {
const res = mapObjectMetadataToGraphQLQuery({ const res = mapObjectMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems, objectMetadataItems: mockObjectMetadataItems,
objectMetadataItem: personObjectMetadataItem, objectMetadataItem: personObjectMetadataItem,
depth: -1, recordGqlFields: {
});
expect(formatGQLString(res)).toEqual(`{
__typename
}`);
});
it('should return depth 0 if depth = 0', async () => {
const res = mapObjectMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems,
objectMetadataItem: personObjectMetadataItem,
depth: 0,
});
expect(formatGQLString(res)).toEqual(`{
__typename
xLink
{
label
url
}
id
createdAt
city
email
jobTitle
name
{
firstName
lastName
}
phone
linkedinLink
{
label
url
}
updatedAt
avatarUrl
companyId
}`);
});
it('should return depth 1 if depth = 1', async () => {
const res = mapObjectMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems,
objectMetadataItem: personObjectMetadataItem,
depth: 1,
});
expect(formatGQLString(res)).toEqual(`{
__typename
opportunities
{
edges {
node {
__typename
personId
pointOfContactId
updatedAt
companyId
probability
closeDate
amount
{
amountMicros
currencyCode
}
id
createdAt
}
}
}
xLink
{
label
url
}
id
pointOfContactForOpportunities
{
edges {
node {
__typename
personId
pointOfContactId
updatedAt
companyId
probability
closeDate
amount
{
amountMicros
currencyCode
}
id
createdAt
}
}
}
createdAt
company
{
__typename
xLink
{
label
url
}
linkedinLink
{
label
url
}
domainName
annualRecurringRevenue
{
amountMicros
currencyCode
}
createdAt
address
updatedAt
name
accountOwnerId
employees
id
idealCustomerProfile
}
city
email
activityTargets
{
edges {
node {
__typename
updatedAt
createdAt
personId
activityId
companyId
id
}
}
}
jobTitle
favorites
{
edges {
node {
__typename
id
companyId
createdAt
personId
position
workspaceMemberId
updatedAt
}
}
}
attachments
{
edges {
node {
__typename
updatedAt
createdAt
name
personId
activityId
companyId
id
authorId
type
fullPath
}
}
}
name
{
firstName
lastName
}
phone
linkedinLink
{
label
url
}
updatedAt
avatarUrl
companyId
}`);
});
it('should query only specified queryFields', async () => {
const res = mapObjectMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems,
objectMetadataItem: personObjectMetadataItem,
queryFields: {
company: true, company: true,
xLink: true, xLink: true,
id: true, id: true,
@ -232,7 +34,6 @@ companyId
avatarUrl: true, avatarUrl: true,
companyId: true, companyId: true,
}, },
depth: 1,
}); });
expect(formatGQLString(res)).toEqual(`{ expect(formatGQLString(res)).toEqual(`{
__typename __typename
@ -291,12 +92,11 @@ companyId
}`); }`);
}); });
it('should load only specified query fields', async () => { it('should load only specified operation fields nested', async () => {
const res = mapObjectMetadataToGraphQLQuery({ const res = mapObjectMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems, objectMetadataItems: mockObjectMetadataItems,
objectMetadataItem: personObjectMetadataItem, objectMetadataItem: personObjectMetadataItem,
queryFields: { company: true, id: true, name: true }, recordGqlFields: { company: { id: true }, id: true, name: true },
depth: 1,
}); });
expect(formatGQLString(res)).toEqual(`{ expect(formatGQLString(res)).toEqual(`{
__typename __typename
@ -304,30 +104,7 @@ id
company company
{ {
__typename __typename
xLink
{
label
url
}
linkedinLink
{
label
url
}
domainName
annualRecurringRevenue
{
amountMicros
currencyCode
}
createdAt
address
updatedAt
name
accountOwnerId
employees
id id
idealCustomerProfile
} }
name name
{ {

View File

@ -2,113 +2,50 @@ import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueri
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
describe('shouldFieldBeQueried', () => { describe('shouldFieldBeQueried', () => {
describe('if field is not relation', () => { describe('if recordGqlFields is absent, we query all except relations', () => {
it('should be queried if depth is undefined', () => { it('should be queried if the field is not a relation', () => {
const res = shouldFieldBeQueried({ const res = shouldFieldBeQueried({
field: { name: 'fieldName', type: FieldMetadataType.Boolean }, field: { name: 'fieldName', type: FieldMetadataType.Boolean },
}); });
expect(res).toBe(true); expect(res).toBe(true);
}); });
it('should be queried depth = 0', () => { it('should not be queried if the field is a relation', () => {
const res = shouldFieldBeQueried({ const res = shouldFieldBeQueried({
depth: 0, field: { name: 'fieldName', type: FieldMetadataType.Relation },
field: { name: 'fieldName', type: FieldMetadataType.Boolean },
});
expect(res).toBe(true);
});
it('should be queried depth > 0', () => {
const res = shouldFieldBeQueried({
depth: 1,
field: { name: 'fieldName', type: FieldMetadataType.Boolean },
});
expect(res).toBe(true);
});
it('should NOT be queried depth < 0', () => {
const res = shouldFieldBeQueried({
depth: -1,
field: { name: 'fieldName', type: FieldMetadataType.Boolean },
}); });
expect(res).toBe(false); expect(res).toBe(false);
}); });
it('should not depends on queryFields', () => {
const res = shouldFieldBeQueried({
depth: 0,
queryFields: {
fieldName: true,
},
field: { name: 'fieldName', type: FieldMetadataType.Boolean },
});
expect(res).toBe(true);
});
}); });
describe('if field is relation', () => { describe('if recordGqlFields is present, we respect it', () => {
it('should be queried if queryFields and depth are undefined', () => { it('should be queried if true', () => {
const res = shouldFieldBeQueried({ const res = shouldFieldBeQueried({
recordGqlFields: { fieldName: true },
field: { name: 'fieldName', type: FieldMetadataType.Relation }, field: { name: 'fieldName', type: FieldMetadataType.Relation },
}); });
expect(res).toBe(true); expect(res).toBe(true);
}); });
it('should be queried if queryFields is undefined and depth = 1', () => { it('should be queried if object', () => {
const res = shouldFieldBeQueried({ const res = shouldFieldBeQueried({
depth: 1, recordGqlFields: { fieldName: { subFieldName: false } },
field: { name: 'fieldName', type: FieldMetadataType.Relation }, field: { name: 'fieldName', type: FieldMetadataType.Relation },
}); });
expect(res).toBe(true); expect(res).toBe(true);
}); });
it('should be queried if queryFields is undefined and depth > 1', () => { it('should not be queried if false', () => {
const res = shouldFieldBeQueried({ const res = shouldFieldBeQueried({
depth: 2, recordGqlFields: { fieldName: false },
field: { name: 'fieldName', type: FieldMetadataType.Relation },
});
expect(res).toBe(true);
});
it('should NOT be queried if queryFields is undefined and depth < 1', () => {
const res = shouldFieldBeQueried({
depth: 0,
field: { name: 'fieldName', type: FieldMetadataType.Relation }, field: { name: 'fieldName', type: FieldMetadataType.Relation },
}); });
expect(res).toBe(false); expect(res).toBe(false);
}); });
it('should be queried if queryFields is matching and depth > 1', () => { it('should not be queried if absent', () => {
const res = shouldFieldBeQueried({ const res = shouldFieldBeQueried({
depth: 1, recordGqlFields: { otherFieldName: false },
queryFields: { fieldName: true },
field: { name: 'fieldName', type: FieldMetadataType.Relation },
});
expect(res).toBe(true);
});
it('should NOT be queried if queryFields is matching and depth < 1', () => {
const res = shouldFieldBeQueried({
depth: 0,
queryFields: { fieldName: true },
field: { name: 'fieldName', type: FieldMetadataType.Relation },
});
expect(res).toBe(false);
});
it('should NOT be queried if queryFields is not matching (falsy) and depth < 1', () => {
const res = shouldFieldBeQueried({
depth: 1,
queryFields: { fieldName: false },
field: { name: 'fieldName', type: FieldMetadataType.Relation },
});
expect(res).toBe(false);
});
it('should NOT be queried if queryFields is not matching and depth < 1', () => {
const res = shouldFieldBeQueried({
depth: 0,
queryFields: { anotherFieldName: true },
field: { name: 'fieldName', type: FieldMetadataType.Relation }, field: { name: 'fieldName', type: FieldMetadataType.Relation },
}); });
expect(res).toBe(false); expect(res).toBe(false);

View File

@ -1,14 +1,14 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { OrderBy } from '@/object-metadata/types/OrderBy'; import { OrderBy } from '@/object-metadata/types/OrderBy';
import { OrderByField } from '@/object-metadata/types/OrderByField';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem'; import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
import { RecordGqlOperationOrderBy } from '@/object-record/graphql/types/RecordGqlOperationOrderBy';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
export const getOrderByFieldForObjectMetadataItem = ( export const getOrderByFieldForObjectMetadataItem = (
objectMetadataItem: ObjectMetadataItem, objectMetadataItem: ObjectMetadataItem,
orderBy?: OrderBy | null, orderBy?: OrderBy | null,
): OrderByField => { ): RecordGqlOperationOrderBy => {
const labelIdentifierFieldMetadata = const labelIdentifierFieldMetadata =
getLabelIdentifierFieldMetadataItem(objectMetadataItem); getLabelIdentifierFieldMetadataItem(objectMetadataItem);

View File

@ -10,8 +10,7 @@ import { FieldMetadataItem } from '../types/FieldMetadataItem';
export const mapFieldMetadataToGraphQLQuery = ({ export const mapFieldMetadataToGraphQLQuery = ({
objectMetadataItems, objectMetadataItems,
field, field,
depth = 0, relationrecordFields,
queryFields,
computeReferences = false, computeReferences = false,
}: { }: {
objectMetadataItems: ObjectMetadataItem[]; objectMetadataItems: ObjectMetadataItem[];
@ -19,8 +18,7 @@ export const mapFieldMetadataToGraphQLQuery = ({
FieldMetadataItem, FieldMetadataItem,
'name' | 'type' | 'toRelationMetadata' | 'fromRelationMetadata' 'name' | 'type' | 'toRelationMetadata' | 'fromRelationMetadata'
>; >;
depth?: number; relationrecordFields?: Record<string, any>;
queryFields?: Record<string, any>;
computeReferences?: boolean; computeReferences?: boolean;
}): any => { }): any => {
const fieldType = field.type; const fieldType = field.type;
@ -47,8 +45,7 @@ export const mapFieldMetadataToGraphQLQuery = ({
return field.name; return field.name;
} else if ( } else if (
fieldType === 'RELATION' && fieldType === 'RELATION' &&
field.toRelationMetadata?.relationType === 'ONE_TO_MANY' && field.toRelationMetadata?.relationType === 'ONE_TO_MANY'
depth > 0
) { ) {
const relationMetadataItem = objectMetadataItems.find( const relationMetadataItem = objectMetadataItems.find(
(objectMetadataItem) => (objectMetadataItem) =>
@ -64,15 +61,13 @@ export const mapFieldMetadataToGraphQLQuery = ({
${mapObjectMetadataToGraphQLQuery({ ${mapObjectMetadataToGraphQLQuery({
objectMetadataItems, objectMetadataItems,
objectMetadataItem: relationMetadataItem, objectMetadataItem: relationMetadataItem,
depth: depth - 1, recordGqlFields: relationrecordFields,
queryFields,
computeReferences: computeReferences, computeReferences: computeReferences,
isRootLevel: false, isRootLevel: false,
})}`; })}`;
} else if ( } else if (
fieldType === 'RELATION' && fieldType === 'RELATION' &&
field.fromRelationMetadata?.relationType === 'ONE_TO_MANY' && field.fromRelationMetadata?.relationType === 'ONE_TO_MANY'
depth > 0
) { ) {
const relationMetadataItem = objectMetadataItems.find( const relationMetadataItem = objectMetadataItems.find(
(objectMetadataItem) => (objectMetadataItem) =>
@ -90,8 +85,7 @@ ${mapObjectMetadataToGraphQLQuery({
node ${mapObjectMetadataToGraphQLQuery({ node ${mapObjectMetadataToGraphQLQuery({
objectMetadataItems, objectMetadataItems,
objectMetadataItem: relationMetadataItem, objectMetadataItem: relationMetadataItem,
depth: depth - 1, recordGqlFields: relationrecordFields,
queryFields,
computeReferences, computeReferences,
isRootLevel: false, isRootLevel: false,
})} })}

View File

@ -5,15 +5,13 @@ import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueri
export const mapObjectMetadataToGraphQLQuery = ({ export const mapObjectMetadataToGraphQLQuery = ({
objectMetadataItems, objectMetadataItems,
objectMetadataItem, objectMetadataItem,
depth = 1, recordGqlFields,
queryFields,
computeReferences = false, computeReferences = false,
isRootLevel = true, isRootLevel = true,
}: { }: {
objectMetadataItems: ObjectMetadataItem[]; objectMetadataItems: ObjectMetadataItem[];
objectMetadataItem: Pick<ObjectMetadataItem, 'nameSingular' | 'fields'>; objectMetadataItem: Pick<ObjectMetadataItem, 'nameSingular' | 'fields'>;
depth?: number; recordGqlFields?: Record<string, any>;
queryFields?: Record<string, any>;
computeReferences?: boolean; computeReferences?: boolean;
isRootLevel?: boolean; isRootLevel?: boolean;
}): any => { }): any => {
@ -23,8 +21,7 @@ export const mapObjectMetadataToGraphQLQuery = ({
.filter((field) => .filter((field) =>
shouldFieldBeQueried({ shouldFieldBeQueried({
field, field,
depth, recordGqlFields,
queryFields,
}), }),
) ?? []; ) ?? [];
@ -37,18 +34,17 @@ export const mapObjectMetadataToGraphQLQuery = ({
return `{ return `{
__typename __typename
${fieldsThatShouldBeQueried ${fieldsThatShouldBeQueried
.map((field) => .map((field) => {
mapFieldMetadataToGraphQLQuery({ return mapFieldMetadataToGraphQLQuery({
objectMetadataItems, objectMetadataItems,
field, field,
depth, relationrecordFields:
queryFields: typeof recordGqlFields?.[field.name] === 'boolean'
typeof queryFields?.[field.name] === 'boolean'
? undefined ? undefined
: queryFields?.[field.name], : recordGqlFields?.[field.name],
computeReferences, computeReferences,
}), });
) })
.join('\n')} .join('\n')}
}`; }`;
}; };

View File

@ -1,36 +1,32 @@
import { isUndefined } from '@sniptt/guards'; import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { FieldMetadataItem } from '../types/FieldMetadataItem'; import { FieldMetadataItem } from '../types/FieldMetadataItem';
export const shouldFieldBeQueried = ({ export const shouldFieldBeQueried = ({
field, field,
depth, recordGqlFields,
queryFields,
}: { }: {
field: Pick<FieldMetadataItem, 'name' | 'type'>; field: Pick<FieldMetadataItem, 'name' | 'type'>;
depth?: number;
objectRecord?: ObjectRecord; objectRecord?: ObjectRecord;
queryFields?: Record<string, any>; recordGqlFields?: RecordGqlOperationGqlRecordFields;
}): any => { }): any => {
if (!isUndefined(depth) && depth < 0) {
return false;
}
if ( if (
!isUndefined(depth) && isUndefinedOrNull(recordGqlFields) &&
depth < 1 && field.type !== FieldMetadataType.Relation
field.type === FieldMetadataType.Relation
) { ) {
return false; return true;
}
if (
isDefined(recordGqlFields) &&
isDefined(recordGqlFields[field.name]) &&
recordGqlFields[field.name] !== false
) {
return true;
} }
if (isDefined(queryFields) && !queryFields[field.name]) { return false;
return false;
}
return true;
}; };

View File

@ -1 +0,0 @@
export const MAX_QUERY_DEPTH_FOR_CACHE_INJECTION = 1;

View File

@ -7,6 +7,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { prefillRecord } from '@/object-record/utils/prefillRecord'; import { prefillRecord } from '@/object-record/utils/prefillRecord';
import { capitalize } from '~/utils/string/capitalize'; import { capitalize } from '~/utils/string/capitalize';
@ -32,13 +33,15 @@ export const useCreateOneRecordInCache = <T extends ObjectRecord>({
objectMetadataItems, objectMetadataItems,
objectMetadataItem, objectMetadataItem,
computeReferences: true, computeReferences: true,
recordGqlFields: generateDepthOneRecordGqlFields({
objectMetadataItem,
}),
})} })}
`; `;
const prefilledRecord = prefillRecord({ const prefilledRecord = prefillRecord({
objectMetadataItem, objectMetadataItem,
input: record, input: record,
depth: 1,
}); });
const recordToCreateWithNestedConnections = getRecordNodeFromRecord({ const recordToCreateWithNestedConnections = getRecordNodeFromRecord({

View File

@ -5,33 +5,46 @@ import { useRecoilValue } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache'; import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
export const useGetRecordFromCache = ({ export const useGetRecordFromCache = ({
objectNameSingular, objectNameSingular,
recordGqlFields,
}: { }: {
objectNameSingular: string; objectNameSingular: string;
recordGqlFields?: RecordGqlFields;
}) => { }) => {
const { objectMetadataItem } = useObjectMetadataItem({ const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular, objectNameSingular,
}); });
const appliedRecordGqlFields =
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
return useCallback( return useCallback(
<CachedObjectRecord extends ObjectRecord = ObjectRecord>( <T extends ObjectRecord = ObjectRecord>(
recordId: string, recordId: string,
cache = apolloClient.cache, cache = apolloClient.cache,
) => { ) => {
return getRecordFromCache<CachedObjectRecord>({ return getRecordFromCache<T>({
cache, cache,
recordId, recordId,
objectMetadataItems, objectMetadataItems,
objectMetadataItem, objectMetadataItem,
recordGqlFields: appliedRecordGqlFields,
}); });
}, },
[objectMetadataItem, objectMetadataItems, apolloClient], [
apolloClient.cache,
objectMetadataItems,
objectMetadataItem,
appliedRecordGqlFields,
],
); );
}; };

View File

@ -3,9 +3,9 @@ import { useApolloClient } from '@apollo/client';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection'; import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ObjectRecordQueryResult } from '@/object-record/types/ObjectRecordQueryResult';
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables';
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery'; import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
@ -22,32 +22,28 @@ export const useReadFindManyRecordsQueryInCache = ({
T extends ObjectRecord = ObjectRecord, T extends ObjectRecord = ObjectRecord,
>({ >({
queryVariables, queryVariables,
queryFields, recordGqlFields,
depth,
}: { }: {
queryVariables: ObjectRecordQueryVariables; queryVariables: RecordGqlOperationVariables;
queryFields?: Record<string, any>; recordGqlFields?: Record<string, any>;
depth?: number;
}) => { }) => {
const findManyRecordsQueryForCacheRead = generateFindManyRecordsQuery({ const findManyRecordsQueryForCacheRead = generateFindManyRecordsQuery({
objectMetadataItem, objectMetadataItem,
objectMetadataItems, objectMetadataItems,
queryFields, recordGqlFields,
depth,
}); });
const existingRecordsQueryResult = apolloClient.readQuery< const existingRecordsQueryResult =
ObjectRecordQueryResult<T> apolloClient.readQuery<RecordGqlOperationFindManyResult>({
>({ query: findManyRecordsQueryForCacheRead,
query: findManyRecordsQueryForCacheRead, variables: queryVariables,
variables: queryVariables, });
});
const existingRecordConnection = const existingRecordConnection =
existingRecordsQueryResult?.[objectMetadataItem.namePlural]; existingRecordsQueryResult?.[objectMetadataItem.namePlural];
const existingObjectRecords = isDefined(existingRecordConnection) const existingObjectRecords = isDefined(existingRecordConnection)
? getRecordsFromRecordConnection({ ? getRecordsFromRecordConnection<T>({
recordConnection: existingRecordConnection, recordConnection: existingRecordConnection,
}) })
: []; : [];

View File

@ -3,10 +3,9 @@ import { useRecoilValue } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { MAX_QUERY_DEPTH_FOR_CACHE_INJECTION } from '@/object-record/cache/constants/MaxQueryDepthForCacheInjection';
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords'; import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ObjectRecordQueryVariables } from '@/object-record/types/ObjectRecordQueryVariables';
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery'; import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
export const useUpsertFindManyRecordsQueryInCache = ({ export const useUpsertFindManyRecordsQueryInCache = ({
@ -22,22 +21,19 @@ export const useUpsertFindManyRecordsQueryInCache = ({
T extends ObjectRecord = ObjectRecord, T extends ObjectRecord = ObjectRecord,
>({ >({
queryVariables, queryVariables,
depth = MAX_QUERY_DEPTH_FOR_CACHE_INJECTION,
objectRecordsToOverwrite, objectRecordsToOverwrite,
queryFields, recordGqlFields,
computeReferences = false, computeReferences = false,
}: { }: {
queryVariables: ObjectRecordQueryVariables; queryVariables: RecordGqlOperationVariables;
depth?: number;
objectRecordsToOverwrite: T[]; objectRecordsToOverwrite: T[];
queryFields?: Record<string, any>; recordGqlFields?: Record<string, any>;
computeReferences?: boolean; computeReferences?: boolean;
}) => { }) => {
const findManyRecordsQueryForCacheOverwrite = generateFindManyRecordsQuery({ const findManyRecordsQueryForCacheOverwrite = generateFindManyRecordsQuery({
objectMetadataItem, objectMetadataItem,
objectMetadataItems, objectMetadataItems,
depth, recordGqlFields,
queryFields,
computeReferences, computeReferences,
}); });
@ -45,7 +41,7 @@ export const useUpsertFindManyRecordsQueryInCache = ({
objectMetadataItems: objectMetadataItems, objectMetadataItems: objectMetadataItems,
objectMetadataItem: objectMetadataItem, objectMetadataItem: objectMetadataItem,
records: objectRecordsToOverwrite, records: objectRecordsToOverwrite,
queryFields, recordGqlFields,
computeReferences, computeReferences,
}); });

View File

@ -0,0 +1,6 @@
import { RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
export type RecordGqlRefConnection = Omit<RecordGqlConnection, 'edges'> & {
edges: RecordGqlRefEdge[];
};

View File

@ -0,0 +1,6 @@
import { RecordGqlRefNode } from '@/object-record/cache/types/RecordGqlRefNode';
import { RecordGqlEdge } from '@/object-record/graphql/types/RecordGqlEdge';
export type RecordGqlRefEdge = Omit<RecordGqlEdge, 'node'> & {
node: RecordGqlRefNode;
};

View File

@ -0,0 +1,3 @@
import { Reference } from '@apollo/client';
export type RecordGqlRefNode = Reference;

View File

@ -2,18 +2,18 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getConnectionTypename } from '@/object-record/cache/utils/getConnectionTypename'; import { getConnectionTypename } from '@/object-record/cache/utils/getConnectionTypename';
import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo'; import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo';
import { getRecordEdgeFromRecord } from '@/object-record/cache/utils/getRecordEdgeFromRecord'; import { getRecordEdgeFromRecord } from '@/object-record/cache/utils/getRecordEdgeFromRecord';
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
export const getRecordConnectionFromRecords = <T extends ObjectRecord>({ export const getRecordConnectionFromRecords = <T extends ObjectRecord>({
objectMetadataItems, objectMetadataItems,
objectMetadataItem, objectMetadataItem,
records, records,
queryFields, recordGqlFields,
withPageInfo = true, withPageInfo = true,
computeReferences = false, computeReferences = false,
isRootLevel = true, isRootLevel = true,
depth = 1,
}: { }: {
objectMetadataItems: ObjectMetadataItem[]; objectMetadataItems: ObjectMetadataItem[];
objectMetadataItem: Pick< objectMetadataItem: Pick<
@ -21,11 +21,10 @@ export const getRecordConnectionFromRecords = <T extends ObjectRecord>({
'fields' | 'namePlural' | 'nameSingular' 'fields' | 'namePlural' | 'nameSingular'
>; >;
records: T[]; records: T[];
queryFields?: Record<string, any>; recordGqlFields?: RecordGqlOperationGqlRecordFields;
withPageInfo?: boolean; withPageInfo?: boolean;
isRootLevel?: boolean; isRootLevel?: boolean;
computeReferences?: boolean; computeReferences?: boolean;
depth?: number;
}) => { }) => {
return { return {
__typename: getConnectionTypename(objectMetadataItem.nameSingular), __typename: getConnectionTypename(objectMetadataItem.nameSingular),
@ -33,14 +32,13 @@ export const getRecordConnectionFromRecords = <T extends ObjectRecord>({
return getRecordEdgeFromRecord({ return getRecordEdgeFromRecord({
objectMetadataItems, objectMetadataItems,
objectMetadataItem, objectMetadataItem,
queryFields, recordGqlFields,
record, record,
isRootLevel, isRootLevel,
computeReferences, computeReferences,
depth,
}); });
}), }),
...(withPageInfo && { pageInfo: getEmptyPageInfo() }), ...(withPageInfo && { pageInfo: getEmptyPageInfo() }),
...(withPageInfo && { totalCount: records.length }), ...(withPageInfo && { totalCount: records.length }),
} as ObjectRecordConnection<T>; } as RecordGqlConnection;
}; };

View File

@ -1,13 +1,13 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename'; import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { RecordGqlEdge } from '@/object-record/graphql/types/RecordGqlEdge';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
export const getRecordEdgeFromRecord = <T extends ObjectRecord>({ export const getRecordEdgeFromRecord = <T extends ObjectRecord>({
objectMetadataItems, objectMetadataItems,
objectMetadataItem, objectMetadataItem,
queryFields, recordGqlFields,
record, record,
computeReferences = false, computeReferences = false,
isRootLevel = false, isRootLevel = false,
@ -17,10 +17,9 @@ export const getRecordEdgeFromRecord = <T extends ObjectRecord>({
ObjectMetadataItem, ObjectMetadataItem,
'fields' | 'namePlural' | 'nameSingular' 'fields' | 'namePlural' | 'nameSingular'
>; >;
queryFields?: Record<string, any>; recordGqlFields?: Record<string, any>;
computeReferences?: boolean; computeReferences?: boolean;
isRootLevel?: boolean; isRootLevel?: boolean;
depth?: number;
record: T; record: T;
}) => { }) => {
return { return {
@ -29,13 +28,12 @@ export const getRecordEdgeFromRecord = <T extends ObjectRecord>({
...getRecordNodeFromRecord({ ...getRecordNodeFromRecord({
objectMetadataItems, objectMetadataItems,
objectMetadataItem, objectMetadataItem,
queryFields, recordGqlFields,
record, record,
computeReferences, computeReferences,
isRootLevel, isRootLevel,
depth: 1,
}), }),
}, },
cursor: '', cursor: '',
} as ObjectRecordEdge<T>; } as RecordGqlEdge;
}; };

View File

@ -1,9 +1,10 @@
import { ApolloCache, gql } from '@apollo/client'; import { ApolloCache, gql } from '@apollo/client';
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode'; import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { capitalize } from '~/utils/string/capitalize'; import { capitalize } from '~/utils/string/capitalize';
@ -13,16 +14,21 @@ export const getRecordFromCache = <T extends ObjectRecord = ObjectRecord>({
objectMetadataItems, objectMetadataItems,
cache, cache,
recordId, recordId,
recordGqlFields,
}: { }: {
cache: ApolloCache<object>; cache: ApolloCache<object>;
recordId: string; recordId: string;
objectMetadataItems: ObjectMetadataItem[]; objectMetadataItems: ObjectMetadataItem[];
objectMetadataItem: ObjectMetadataItem; objectMetadataItem: ObjectMetadataItem;
recordGqlFields?: RecordGqlFields;
}) => { }) => {
if (isUndefinedOrNull(objectMetadataItem)) { if (isUndefinedOrNull(objectMetadataItem)) {
return null; return null;
} }
const appliedRecordGqlFields =
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular); const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
const cacheReadFragment = gql` const cacheReadFragment = gql`
@ -30,6 +36,7 @@ export const getRecordFromCache = <T extends ObjectRecord = ObjectRecord>({
{ {
objectMetadataItems, objectMetadataItems,
objectMetadataItem, objectMetadataItem,
recordGqlFields: appliedRecordGqlFields,
}, },
)} )}
`; `;
@ -49,7 +56,7 @@ export const getRecordFromCache = <T extends ObjectRecord = ObjectRecord>({
return null; return null;
} }
return getRecordFromRecordNode({ return getRecordFromRecordNode<T>({
recordNode: record, recordNode: record,
}) as CachedObjectRecord<T>; });
}; };

View File

@ -1,4 +1,5 @@
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection'; import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@ -6,7 +7,7 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const getRecordFromRecordNode = <T extends ObjectRecord>({ export const getRecordFromRecordNode = <T extends ObjectRecord>({
recordNode, recordNode,
}: { }: {
recordNode: T; recordNode: RecordGqlNode;
}): T => { }): T => {
return { return {
...Object.fromEntries( ...Object.fromEntries(
@ -32,5 +33,6 @@ export const getRecordFromRecordNode = <T extends ObjectRecord>({
}), }),
), ),
id: recordNode.id, id: recordNode.id,
__typename: recordNode.__typename,
} as T; } as T;
}; };

View File

@ -1,10 +1,10 @@
import { isNull, isUndefined } from '@sniptt/guards'; import { isNull, isUndefined } from '@sniptt/guards';
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getNodeTypename } from '@/object-record/cache/utils/getNodeTypename'; import { getNodeTypename } from '@/object-record/cache/utils/getNodeTypename';
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename'; import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords'; import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { import {
FieldMetadataType, FieldMetadataType,
@ -16,22 +16,20 @@ import { lowerAndCapitalize } from '~/utils/string/lowerAndCapitalize';
export const getRecordNodeFromRecord = <T extends ObjectRecord>({ export const getRecordNodeFromRecord = <T extends ObjectRecord>({
objectMetadataItems, objectMetadataItems,
objectMetadataItem, objectMetadataItem,
queryFields, recordGqlFields,
record, record,
computeReferences = true, computeReferences = true,
isRootLevel = true, isRootLevel = true,
depth = 1,
}: { }: {
objectMetadataItems: ObjectMetadataItem[]; objectMetadataItems: ObjectMetadataItem[];
objectMetadataItem: Pick< objectMetadataItem: Pick<
ObjectMetadataItem, ObjectMetadataItem,
'fields' | 'namePlural' | 'nameSingular' 'fields' | 'namePlural' | 'nameSingular'
>; >;
queryFields?: Record<string, any>; recordGqlFields?: Record<string, any>;
computeReferences?: boolean; computeReferences?: boolean;
isRootLevel?: boolean; isRootLevel?: boolean;
record: T | null; record: T | null;
depth?: number;
}) => { }) => {
if (isNull(record)) { if (isNull(record)) {
return null; return null;
@ -42,13 +40,13 @@ export const getRecordNodeFromRecord = <T extends ObjectRecord>({
if (!isRootLevel && computeReferences) { if (!isRootLevel && computeReferences) {
return { return {
__ref: `${nodeTypeName}:${record.id}`, __ref: `${nodeTypeName}:${record.id}`,
} as unknown as CachedObjectRecord<T>; // Todo Fix typing } as unknown as RecordGqlNode; // Fix typing: we want a Reference in computeReferences mode
} }
const nestedRecord = Object.fromEntries( const nestedRecord = Object.fromEntries(
Object.entries(record) Object.entries(record)
.map(([fieldName, value]) => { .map(([fieldName, value]) => {
if (isDefined(queryFields) && !queryFields[fieldName]) { if (isDefined(recordGqlFields) && !recordGqlFields[fieldName]) {
return undefined; return undefined;
} }
@ -60,14 +58,6 @@ export const getRecordNodeFromRecord = <T extends ObjectRecord>({
return undefined; return undefined;
} }
if (
!isUndefined(depth) &&
depth < 1 &&
field.type === FieldMetadataType.Relation
) {
return undefined;
}
if ( if (
field.type === FieldMetadataType.Relation && field.type === FieldMetadataType.Relation &&
field.relationDefinition?.direction === field.relationDefinition?.direction ===
@ -87,15 +77,14 @@ export const getRecordNodeFromRecord = <T extends ObjectRecord>({
objectMetadataItems, objectMetadataItems,
objectMetadataItem: oneToManyObjectMetadataItem, objectMetadataItem: oneToManyObjectMetadataItem,
records: value as ObjectRecord[], records: value as ObjectRecord[],
queryFields: recordGqlFields:
queryFields?.[fieldName] === true || recordGqlFields?.[fieldName] === true ||
isUndefined(queryFields?.[fieldName]) isUndefined(recordGqlFields?.[fieldName])
? undefined ? undefined
: queryFields?.[fieldName], : recordGqlFields?.[fieldName],
withPageInfo: false, withPageInfo: false,
isRootLevel: false, isRootLevel: false,
computeReferences, computeReferences,
depth: depth - 1,
}), }),
]; ];
} }
@ -159,8 +148,5 @@ export const getRecordNodeFromRecord = <T extends ObjectRecord>({
.filter(isDefined), .filter(isDefined),
) as T; // Todo fix typing once we have investigated apollo edges / nodes removal ) as T; // Todo fix typing once we have investigated apollo edges / nodes removal
return { return { ...nestedRecord, __typename: nodeTypeName };
__typename: getNodeTypename(objectMetadataItem.nameSingular),
...nestedRecord,
};
}; };

View File

@ -1,11 +1,11 @@
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode'; import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
export const getRecordsFromRecordConnection = <T extends ObjectRecord>({ export const getRecordsFromRecordConnection = <T extends ObjectRecord>({
recordConnection, recordConnection,
}: { }: {
recordConnection: ObjectRecordConnection<T>; recordConnection: RecordGqlConnection;
}): T[] => { }): T[] => {
return recordConnection.edges.map((edge) => return recordConnection.edges.map((edge) =>
getRecordFromRecordNode<T>({ recordNode: edge.node }), getRecordFromRecordNode<T>({ recordNode: edge.node }),

View File

@ -1,12 +1,12 @@
import { z } from 'zod'; import { z } from 'zod';
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection'; import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
import { capitalize } from '~/utils/string/capitalize'; import { capitalize } from '~/utils/string/capitalize';
export const isObjectRecordConnection = ( export const isObjectRecordConnection = (
objectNameSingular: string, objectNameSingular: string,
value: unknown, value: unknown,
): value is ObjectRecordConnection => { ): value is RecordGqlConnection => {
const objectConnectionTypeName = `${capitalize( const objectConnectionTypeName = `${capitalize(
objectNameSingular, objectNameSingular,
)}Connection`; )}Connection`;

View File

@ -1,13 +1,13 @@
import { StoreValue } from '@apollo/client'; import { StoreValue } from '@apollo/client';
import { z } from 'zod'; import { z } from 'zod';
import { CachedObjectRecordConnection } from '@/apollo/types/CachedObjectRecordConnection'; import { RecordGqlRefConnection } from '@/object-record/cache/types/RecordGqlRefConnection';
import { capitalize } from '~/utils/string/capitalize'; import { capitalize } from '~/utils/string/capitalize';
export const isObjectRecordConnectionWithRefs = ( export const isObjectRecordConnectionWithRefs = (
objectNameSingular: string, objectNameSingular: string,
storeValue: StoreValue, storeValue: StoreValue,
): storeValue is CachedObjectRecordConnection => { ): storeValue is RecordGqlRefConnection => {
const objectConnectionTypeName = `${capitalize( const objectConnectionTypeName = `${capitalize(
objectNameSingular, objectNameSingular,
)}Connection`; )}Connection`;

View File

@ -4,6 +4,7 @@ import gql from 'graphql-tag';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { capitalize } from '~/utils/string/capitalize'; import { capitalize } from '~/utils/string/capitalize';
@ -44,14 +45,13 @@ export const updateRecordFromCache = <T extends ObjectRecord>({
objectMetadataItems, objectMetadataItems,
objectMetadataItem, objectMetadataItem,
record, record,
depth: 1,
}); });
if (isUndefinedOrNull(recordWithConnection)) { if (isUndefinedOrNull(recordWithConnection)) {
return; return;
} }
cache.writeFragment<T & { __typename: string }>({ cache.writeFragment<RecordGqlNode>({
id: cachedRecordId, id: cachedRecordId,
fragment: cacheWriteFragment, fragment: cacheWriteFragment,
data: recordWithConnection, data: recordWithConnection,

View File

@ -1,11 +1,10 @@
import { Nullable } from 'twenty-ui'; import { Nullable } from 'twenty-ui';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { RecordGqlEdge } from '@/object-record/graphql/types/RecordGqlEdge';
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
export type ObjectRecordConnection<T extends ObjectRecord = ObjectRecord> = { export type RecordGqlConnection = {
__typename?: string; __typename?: string;
edges: ObjectRecordEdge<T>[]; edges: RecordGqlEdge[];
pageInfo: { pageInfo: {
hasNextPage?: boolean; hasNextPage?: boolean;
hasPreviousPage?: boolean; hasPreviousPage?: boolean;

View File

@ -0,0 +1,7 @@
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
export type RecordGqlEdge = {
__typename: string;
node: RecordGqlNode;
cursor: string;
};

View File

@ -0,0 +1 @@
export type RecordGqlFields = Record<string, any>;

View File

@ -0,0 +1,5 @@
export type RecordGqlNode = {
id: string;
[key: string]: any;
__typename: string;
};

View File

@ -93,22 +93,22 @@ export type LeafFilter =
| undefined; | undefined;
export type AndObjectRecordFilter = { export type AndObjectRecordFilter = {
and?: ObjectRecordQueryFilter[]; and?: RecordGqlOperationFilter[];
}; };
export type OrObjectRecordFilter = { export type OrObjectRecordFilter = {
or?: ObjectRecordQueryFilter[] | ObjectRecordQueryFilter; or?: RecordGqlOperationFilter[] | RecordGqlOperationFilter;
}; };
export type NotObjectRecordFilter = { export type NotObjectRecordFilter = {
not?: ObjectRecordQueryFilter; not?: RecordGqlOperationFilter;
}; };
export type LeafObjectRecordFilter = { export type LeafObjectRecordFilter = {
[fieldName: string]: LeafFilter; [fieldName: string]: LeafFilter;
}; };
export type ObjectRecordQueryFilter = export type RecordGqlOperationFilter =
| LeafObjectRecordFilter | LeafObjectRecordFilter
| AndObjectRecordFilter | AndObjectRecordFilter
| OrObjectRecordFilter | OrObjectRecordFilter

View File

@ -0,0 +1,5 @@
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
export type RecordGqlOperationFindManyResult = {
[objectNamePlural: string]: RecordGqlConnection;
};

View File

@ -0,0 +1,5 @@
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
export type RecordGqlOperationFindOneResult = {
[objectNameSingular: string]: RecordGqlNode;
};

View File

@ -0,0 +1,3 @@
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
export type RecordGqlOperationGqlRecordFields = RecordGqlFields;

View File

@ -1,5 +1,5 @@
import { OrderBy } from '@/object-metadata/types/OrderBy'; import { OrderBy } from '@/object-metadata/types/OrderBy';
export type OrderByField = { export type RecordGqlOperationOrderBy = {
[fieldName: string]: OrderBy | { [subFieldName: string]: OrderBy }; [fieldName: string]: OrderBy | { [subFieldName: string]: OrderBy };
}; };

Some files were not shown because too many files have changed in this diff Show More