Implement eager load relations on graphqlQueries (#4391)

* Implement eager load relations on graphqlQueries

* Fix tests

* Fixes

* Fixes
This commit is contained in:
Charles Bochet
2024-03-10 23:42:23 +01:00
committed by GitHub
parent 86c0f311f5
commit ec384cc791
42 changed files with 1372 additions and 850 deletions

View File

@ -1,180 +0,0 @@
import { renderHook } from '@testing-library/react';
import { RecoilRoot, useSetRecoilState } from 'recoil';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { RelationMetadataType } from '~/generated/graphql';
const mockObjectMetadataItems = getObjectMetadataItemsMock();
const formatGQLString = (inputString: string) =>
inputString.replace(/^\s*[\r\n]/gm, '');
const getOneToManyRelation = () => {
const objectMetadataItem = mockObjectMetadataItems.find(
(item) => item.nameSingular === 'opportunity',
)!;
return {
field: objectMetadataItem.fields.find((field) => field.name === 'company')!,
res: `company
{
__typename
id
xLink
{
label
url
}
linkedinLink
{
label
url
}
domainName
annualRecurringRevenue
{
amountMicros
currencyCode
}
createdAt
address
updatedAt
name
accountOwnerId
employees
id
idealCustomerProfile
}`,
};
};
const getOneToOneRelationField = () => {
const objectMetadataItem = mockObjectMetadataItems.find(
(item) => item.nameSingular === 'opportunity',
)!;
const oneToManyfield = objectMetadataItem.fields.find(
(field) => field.name === 'company',
)!;
const field: FieldMetadataItem = {
...oneToManyfield,
toRelationMetadata: {
...oneToManyfield.toRelationMetadata!,
relationType: RelationMetadataType.OneToOne,
},
};
return field;
};
const getOneToManyFromRelationField = () => {
const objectMetadataItem = mockObjectMetadataItems.find(
(item) => item.nameSingular === 'person',
)!;
const field = objectMetadataItem.fields.find(
(field) => field.name === 'opportunities',
)!;
return {
field,
res: `opportunities
{
edges {
node {
__typename
id
personId
pointOfContactId
updatedAt
companyId
pipelineStepId
probability
closeDate
amount
{
amountMicros
currencyCode
}
id
createdAt
}
}
}`,
};
};
const getFullNameRelation = () => {
const objectMetadataItem = mockObjectMetadataItems.find(
(item) => item.nameSingular === 'person',
)!;
const field = objectMetadataItem.fields.find(
(field) => field.name === 'name',
)!;
return {
field,
res: `\n name\n {\n firstName\n lastName\n }\n `,
};
};
describe('useMapFieldMetadataToGraphQLQuery', () => {
it('should work as expected', async () => {
const { result } = renderHook(
() => {
const setMetadataItems = useSetRecoilState(objectMetadataItemsState());
setMetadataItems(mockObjectMetadataItems);
return {
mapFieldMetadataToGraphQLQuery: useMapFieldMetadataToGraphQLQuery(),
};
},
{
wrapper: RecoilRoot,
},
);
const oneToManyRelation = getOneToManyRelation();
const { mapFieldMetadataToGraphQLQuery } = result.current;
const oneToManyRelationFieldRes = mapFieldMetadataToGraphQLQuery({
field: oneToManyRelation.field,
});
expect(formatGQLString(oneToManyRelationFieldRes)).toEqual(
oneToManyRelation.res,
);
const oneToOneRelation = getOneToOneRelationField();
const oneToOneRelationFieldRes = mapFieldMetadataToGraphQLQuery({
field: oneToOneRelation,
});
expect(formatGQLString(oneToOneRelationFieldRes)).toEqual(
oneToManyRelation.res,
);
const oneToManyFromRelation = getOneToManyFromRelationField();
const oneToManyFromRelationFieldRes = mapFieldMetadataToGraphQLQuery({
field: oneToManyFromRelation.field,
});
expect(formatGQLString(oneToManyFromRelationFieldRes)).toEqual(
oneToManyFromRelation.res,
);
const fullNameRelation = getFullNameRelation();
const fullNameFieldRes = mapFieldMetadataToGraphQLQuery({
field: fullNameRelation.field,
});
expect(fullNameFieldRes).toEqual(fullNameRelation.res);
});
});

View File

@ -1,151 +0,0 @@
import { useRecoilValue } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { FieldType } from '@/object-record/record-field/types/FieldType';
import { FieldMetadataItem } from '../types/FieldMetadataItem';
export const useMapFieldMetadataToGraphQLQuery = () => {
const objectMetadataItems = useRecoilValue(objectMetadataItemsState());
const mapFieldMetadataToGraphQLQuery = ({
field,
depth = 2,
}: {
field: FieldMetadataItem;
depth?: number;
}): any => {
// TODO: parse
const fieldType = field.type as FieldType;
const fieldIsSimpleValue = (
[
'UUID',
'TEXT',
'PHONE',
'DATE_TIME',
'EMAIL',
'NUMBER',
'BOOLEAN',
'RATING',
'SELECT',
'POSITION',
] as FieldType[]
).includes(fieldType);
if (fieldIsSimpleValue) {
return field.name;
} else if (
fieldType === 'RELATION' &&
field.toRelationMetadata?.relationType === 'ONE_TO_MANY'
) {
const relationMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id ===
(field.toRelationMetadata as any)?.fromObjectMetadata?.id,
);
if (depth > 1) {
return `${field.name}
{
__typename
id
${(relationMetadataItem?.fields ?? [])
.map((field) =>
mapFieldMetadataToGraphQLQuery({
field,
depth: depth - 1,
}),
)
.join('\n')}
}`;
} else {
return '';
}
} else if (
fieldType === 'RELATION' &&
field.toRelationMetadata?.relationType === 'ONE_TO_ONE'
) {
const relationMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id ===
(field.toRelationMetadata as any)?.fromObjectMetadata?.id,
);
if (depth > 1) {
return `${field.name}
{
__typename
id
${(relationMetadataItem?.fields ?? [])
.map((field) =>
mapFieldMetadataToGraphQLQuery({
field,
depth: depth - 1,
}),
)
.join('\n')}
}`;
} else {
return '';
}
} else if (
fieldType === 'RELATION' &&
field.fromRelationMetadata?.relationType === 'ONE_TO_MANY'
) {
const relationMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id ===
(field.fromRelationMetadata as any)?.toObjectMetadata?.id,
);
if (depth > 1) {
return `${field.name}
{
edges {
node {
__typename
id
${(relationMetadataItem?.fields ?? [])
.map((field) =>
mapFieldMetadataToGraphQLQuery({
field,
depth: depth - 1,
}),
)
.join('\n')}
}
}
}`;
} else {
return '';
}
} else if (fieldType === 'LINK') {
return `
${field.name}
{
label
url
}
`;
} else if (fieldType === 'CURRENCY') {
return `
${field.name}
{
amountMicros
currencyCode
}
`;
} else if (fieldType === 'FULL_NAME') {
return `
${field.name}
{
firstName
lastName
}
`;
}
};
return mapFieldMetadataToGraphQLQuery;
};

View File

@ -40,6 +40,7 @@ export const EMPTY_MUTATION = gql`
export const useObjectMetadataItem = (
{ objectNameSingular }: ObjectMetadataItemIdentifier,
depth?: number,
eagerLoadedRelations?: Record<string, any>,
) => {
const currentWorkspace = useRecoilValue(currentWorkspaceState());
@ -90,6 +91,7 @@ export const useObjectMetadataItem = (
const findManyRecordsQuery = generateFindManyRecordsQuery({
objectMetadataItem,
depth,
eagerLoadedRelations,
});
const generateFindDuplicateRecordsQuery =