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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 1372 additions and 850 deletions

View File

@ -1,7 +1,7 @@
import { useCachedRootQuery } from '@/apollo/hooks/useCachedRootQuery';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { QueryMethodName } from '@/object-metadata/types/QueryMethodName';
import { useCachedRootQuery } from '@/object-record/cache/hooks/useCachedRootQuery';
export const useDefaultHomePagePath = () => {
const { objectMetadataItem: companyObjectMetadataItem } =

View File

@ -69,7 +69,7 @@ export const useActivities = ({
useFindManyRecords<Activity>({
skip: skipActivities,
objectNameSingular: CoreObjectNameSingular.Activity,
depth: 3,
depth: 1,
filter,
orderBy: activitiesOrderByVariables,
onCompleted: useRecoilCallback(

View File

@ -32,7 +32,7 @@ export const useCreateActivityInCache = () => {
const { record: currentWorkspaceMemberRecord } = useFindOneRecord({
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
objectRecordId: currentWorkspaceMember?.id,
depth: 3,
depth: 0,
});
const { injectIntoActivityTargetInlineCellCache } =

View File

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

View File

@ -7,10 +7,6 @@ import { RecoilRoot } from 'recoil';
import { useEventTracker } from '../useEventTracker';
jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({
useMapFieldMetadataToGraphQLQuery: () => () => '\n',
}));
const mocks: MockedResponse[] = [
{
request: {

View File

@ -84,8 +84,77 @@ export const mocks = [
query: gql`
mutation CreateOneFavorite($input: FavoriteCreateInput!) {
createFavorite(data: $input) {
id
__typename
id
companyId
createdAt
personId
person {
__typename
xLink {
label
url
}
id
createdAt
city
email
jobTitle
name {
firstName
lastName
}
phone
linkedinLink {
label
url
}
updatedAt
avatarUrl
companyId
}
position
workspaceMemberId
workspaceMember {
__typename
colorScheme
name {
firstName
lastName
}
locale
userId
avatarUrl
createdAt
updatedAt
id
}
company {
__typename
xLink {
label
url
}
linkedinLink {
label
url
}
domainName
annualRecurringRevenue {
amountMicros
currencyCode
}
createdAt
address
updatedAt
name
accountOwnerId
employees
id
idealCustomerProfile
}
updatedAt
}
}
`,
variables: {
@ -132,8 +201,77 @@ export const mocks = [
$input: FavoriteUpdateInput!
) {
updateFavorite(id: $idToUpdate, data: $input) {
id
__typename
id
companyId
createdAt
personId
person {
__typename
xLink {
label
url
}
id
createdAt
city
email
jobTitle
name {
firstName
lastName
}
phone
linkedinLink {
label
url
}
updatedAt
avatarUrl
companyId
}
position
workspaceMemberId
workspaceMember {
__typename
colorScheme
name {
firstName
lastName
}
locale
userId
avatarUrl
createdAt
updatedAt
id
}
company {
__typename
xLink {
label
url
}
linkedinLink {
label
url
}
domainName
annualRecurringRevenue {
amountMicros
currencyCode
}
createdAt
address
updatedAt
name
accountOwnerId
employees
id
idealCustomerProfile
}
updatedAt
}
}
`,
variables: {

View File

@ -25,10 +25,6 @@ jest.mock('uuid', () => ({
v4: jest.fn(() => mockId),
}));
jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({
useMapFieldMetadataToGraphQLQuery: () => () => '\n',
}));
jest.mock('@/object-record/hooks/useFindManyRecords', () => ({
useFindManyRecords: () => ({ records: initialFavorites }),
}));

View File

@ -1,9 +1,9 @@
import { useLocation, useNavigate } from 'react-router-dom';
import { useCachedRootQuery } from '@/apollo/hooks/useCachedRootQuery';
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { QueryMethodName } from '@/object-metadata/types/QueryMethodName';
import { useCachedRootQuery } from '@/object-record/cache/hooks/useCachedRootQuery';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';

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 =

View File

@ -0,0 +1,329 @@
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
const mockObjectMetadataItems = getObjectMetadataItemsMock();
const formatGQLString = (inputString: string) =>
inputString.replace(/^\s*[\r\n]/gm, '');
const personObjectMetadataItem = mockObjectMetadataItems.find(
(item) => item.nameSingular === 'person',
);
if (!personObjectMetadataItem) {
throw new Error('ObjectMetadataItem not found');
}
describe('mapFieldMetadataToGraphQLQuery', () => {
it('should return fieldName if simpleValue', async () => {
const res = mapFieldMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems,
field: personObjectMetadataItem.fields.find(
(field) => field.name === 'id',
)!,
});
expect(formatGQLString(res)).toEqual('id');
});
it('should return fieldName if composite', async () => {
const res = mapFieldMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems,
field: personObjectMetadataItem.fields.find(
(field) => field.name === 'name',
)!,
});
expect(formatGQLString(res)).toEqual(`name
{
firstName
lastName
}`);
});
it('should not return relation if depth is < 1', async () => {
const res = mapFieldMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems,
relationFieldDepth: 0,
field: personObjectMetadataItem.fields.find(
(field) => field.name === 'company',
)!,
});
expect(formatGQLString(res)).toEqual('');
});
it('should return relation if it matches depth', async () => {
const res = mapFieldMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems,
relationFieldDepth: 1,
field: personObjectMetadataItem.fields.find(
(field) => field.name === 'company',
)!,
});
expect(formatGQLString(res)).toEqual(`company
{
__typename
xLink
{
label
url
}
linkedinLink
{
label
url
}
domainName
annualRecurringRevenue
{
amountMicros
currencyCode
}
createdAt
address
updatedAt
name
accountOwnerId
employees
id
idealCustomerProfile
}`);
});
it('should return relation with all sub relations if it matches depth', async () => {
const res = mapFieldMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems,
relationFieldDepth: 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
pipelineStepId
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 eagerLoaded relations', async () => {
const res = mapFieldMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems,
relationFieldDepth: 2,
relationFieldEagerLoad: { accountOwner: true, people: true },
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
}
domainName
annualRecurringRevenue
{
amountMicros
currencyCode
}
createdAt
address
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
}`);
});
});

View File

@ -0,0 +1,281 @@
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
const mockObjectMetadataItems = getObjectMetadataItemsMock();
const formatGQLString = (inputString: string) =>
inputString.replace(/^\s*[\r\n]/gm, '');
const personObjectMetadataItem = mockObjectMetadataItems.find(
(item) => item.nameSingular === 'person',
);
if (!personObjectMetadataItem) {
throw new Error('ObjectMetadataItem not found');
}
describe('mapObjectMetadataToGraphQLQuery', () => {
it('should return typename if depth < 0', async () => {
const res = mapObjectMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems,
objectMetadataItem: personObjectMetadataItem,
depth: -1,
});
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
pipelineStepId
probability
closeDate
amount
{
amountMicros
currencyCode
}
id
createdAt
}
}
}
xLink
{
label
url
}
id
pointOfContactForOpportunities
{
edges {
node {
__typename
personId
pointOfContactId
updatedAt
companyId
pipelineStepId
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 eager load only specified relations', async () => {
const res = mapObjectMetadataToGraphQLQuery({
objectMetadataItems: mockObjectMetadataItems,
objectMetadataItem: personObjectMetadataItem,
eagerLoadedRelations: { company: true },
depth: 1,
});
expect(formatGQLString(res)).toEqual(`{
__typename
xLink
{
label
url
}
id
createdAt
company
{
__typename
xLink
{
label
url
}
linkedinLink
{
label
url
}
domainName
annualRecurringRevenue
{
amountMicros
currencyCode
}
createdAt
address
updatedAt
name
accountOwnerId
employees
id
idealCustomerProfile
}
city
email
jobTitle
name
{
firstName
lastName
}
phone
linkedinLink
{
label
url
}
updatedAt
avatarUrl
companyId
}`);
});
});

View File

@ -0,0 +1,117 @@
import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueried';
import { FieldMetadataType } from '~/generated-metadata/graphql';
describe('shouldFieldBeQueried', () => {
describe('if field is not relation', () => {
it('should be queried if depth is undefined', () => {
const res = shouldFieldBeQueried({
field: { name: 'fieldName', type: FieldMetadataType.Boolean },
});
expect(res).toBe(true);
});
it('should be queried depth = 0', () => {
const res = shouldFieldBeQueried({
depth: 0,
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);
});
it('should not depends on eagerLoadedRelation', () => {
const res = shouldFieldBeQueried({
depth: 0,
eagerLoadedRelations: {
fieldName: true,
},
field: { name: 'fieldName', type: FieldMetadataType.Boolean },
});
expect(res).toBe(true);
});
});
describe('if field is relation', () => {
it('should be queried if eagerLoadedRelation and depth are undefined', () => {
const res = shouldFieldBeQueried({
field: { name: 'fieldName', type: FieldMetadataType.Relation },
});
expect(res).toBe(true);
});
it('should be queried if eagerLoadedRelation is undefined and depth = 1', () => {
const res = shouldFieldBeQueried({
depth: 1,
field: { name: 'fieldName', type: FieldMetadataType.Relation },
});
expect(res).toBe(true);
});
it('should be queried if eagerLoadedRelation is undefined and depth > 1', () => {
const res = shouldFieldBeQueried({
depth: 2,
field: { name: 'fieldName', type: FieldMetadataType.Relation },
});
expect(res).toBe(true);
});
it('should NOT be queried if eagerLoadedRelation is undefined and depth < 1', () => {
const res = shouldFieldBeQueried({
depth: 0,
field: { name: 'fieldName', type: FieldMetadataType.Relation },
});
expect(res).toBe(false);
});
it('should be queried if eagerLoadedRelation is matching and depth > 1', () => {
const res = shouldFieldBeQueried({
depth: 1,
eagerLoadedRelations: { fieldName: true },
field: { name: 'fieldName', type: FieldMetadataType.Relation },
});
expect(res).toBe(true);
});
it('should NOT be queried if eagerLoadedRelation is matching and depth < 1', () => {
const res = shouldFieldBeQueried({
depth: 0,
eagerLoadedRelations: { fieldName: true },
field: { name: 'fieldName', type: FieldMetadataType.Relation },
});
expect(res).toBe(false);
});
it('should NOT be queried if eagerLoadedRelation is not matching (falsy) and depth < 1', () => {
const res = shouldFieldBeQueried({
depth: 1,
eagerLoadedRelations: { fieldName: false },
field: { name: 'fieldName', type: FieldMetadataType.Relation },
});
expect(res).toBe(false);
});
it('should NOT be queried if eagerLoadedRelation is not matching and depth < 1', () => {
const res = shouldFieldBeQueried({
depth: 0,
eagerLoadedRelations: { anotherFieldName: true },
field: { name: 'fieldName', type: FieldMetadataType.Relation },
});
expect(res).toBe(false);
});
});
});

View File

@ -0,0 +1,112 @@
import { isUndefined } from '@sniptt/guards';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldMetadataItem } from '../types/FieldMetadataItem';
export const mapFieldMetadataToGraphQLQuery = ({
objectMetadataItems,
field,
relationFieldDepth = 0,
relationFieldEagerLoad,
}: {
objectMetadataItems: ObjectMetadataItem[];
field: Pick<
FieldMetadataItem,
'name' | 'type' | 'toRelationMetadata' | 'fromRelationMetadata'
>;
relationFieldDepth?: number;
relationFieldEagerLoad?: Record<string, any>;
}): any => {
const fieldType = field.type;
const fieldIsSimpleValue = (
[
'UUID',
'TEXT',
'PHONE',
'DATE_TIME',
'EMAIL',
'NUMBER',
'BOOLEAN',
'RATING',
'SELECT',
'POSITION',
] as FieldMetadataType[]
).includes(fieldType);
if (fieldIsSimpleValue) {
return field.name;
} else if (
fieldType === 'RELATION' &&
field.toRelationMetadata?.relationType === 'ONE_TO_MANY' &&
relationFieldDepth > 0
) {
const relationMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id ===
(field.toRelationMetadata as any)?.fromObjectMetadata?.id,
);
if (isUndefined(relationMetadataItem)) {
return '';
}
return `${field.name}
${mapObjectMetadataToGraphQLQuery({
objectMetadataItems,
objectMetadataItem: relationMetadataItem,
eagerLoadedRelations: relationFieldEagerLoad,
depth: relationFieldDepth - 1,
})}`;
} else if (
fieldType === 'RELATION' &&
field.fromRelationMetadata?.relationType === 'ONE_TO_MANY' &&
relationFieldDepth > 0
) {
const relationMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id ===
(field.fromRelationMetadata as any)?.toObjectMetadata?.id,
);
if (isUndefined(relationMetadataItem)) {
return '';
}
return `${field.name}
{
edges {
node ${mapObjectMetadataToGraphQLQuery({
objectMetadataItems,
objectMetadataItem: relationMetadataItem,
eagerLoadedRelations: relationFieldEagerLoad,
depth: relationFieldDepth - 1,
})}
}
}`;
} 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 '';
};

View File

@ -0,0 +1,37 @@
import { isUndefined } from '@sniptt/guards';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueried';
export const mapObjectMetadataToGraphQLQuery = ({
objectMetadataItems,
objectMetadataItem,
depth = 1,
eagerLoadedRelations,
}: {
objectMetadataItems: ObjectMetadataItem[];
objectMetadataItem: Pick<ObjectMetadataItem, 'fields'>;
depth?: number;
eagerLoadedRelations?: Record<string, any>;
}): any => {
return `{
__typename
${(objectMetadataItem?.fields ?? [])
.filter((field) => field.isActive)
.filter((field) =>
shouldFieldBeQueried({ field, depth, eagerLoadedRelations }),
)
.map((field) =>
mapFieldMetadataToGraphQLQuery({
objectMetadataItems,
field,
relationFieldDepth: depth,
relationFieldEagerLoad: isUndefined(eagerLoadedRelations)
? undefined
: eagerLoadedRelations[field.name] ?? undefined,
}),
)
.join('\n')}
}`;
};

View File

@ -0,0 +1,36 @@
import { isUndefined } from '@sniptt/guards';
import { FieldType } from '@/object-record/record-field/types/FieldType';
import { FieldMetadataItem } from '../types/FieldMetadataItem';
export const shouldFieldBeQueried = ({
field,
depth,
eagerLoadedRelations,
}: {
field: Pick<FieldMetadataItem, 'name' | 'type'>;
depth?: number;
eagerLoadedRelations?: Record<string, boolean>;
}): any => {
const fieldType = field.type as FieldType;
if (!isUndefined(depth) && depth < 0) {
return false;
}
if (!isUndefined(depth) && depth < 1 && fieldType === 'RELATION') {
return false;
}
if (
fieldType === 'RELATION' &&
!isUndefined(eagerLoadedRelations) &&
(isUndefined(eagerLoadedRelations[field.name]) ||
!eagerLoadedRelations[field.name])
) {
return false;
}
return true;
};

View File

@ -1,10 +1,10 @@
import { useApolloClient } from '@apollo/client';
import gql from 'graphql-tag';
import { useRecoilCallback } from 'recoil';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { MAX_QUERY_DEPTH_FOR_CACHE_INJECTION } from '@/object-record/cache/constants/MaxQueryDepthForCacheInjection';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { useInjectIntoFindOneRecordQueryCache } from '@/object-record/cache/hooks/useInjectIntoFindOneRecordQueryCache';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
@ -15,7 +15,7 @@ export const useAddRecordInCache = ({
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState());
const apolloClient = useApolloClient();
const { injectIntoFindOneRecordQueryCache } =
@ -29,18 +29,12 @@ export const useAddRecordInCache = ({
const fragment = gql`
fragment Create${capitalize(
objectMetadataItem.nameSingular,
)}InCache on ${capitalize(objectMetadataItem.nameSingular)} {
__typename
id
${objectMetadataItem.fields
.map((field) =>
mapFieldMetadataToGraphQLQuery({
field,
depth: MAX_QUERY_DEPTH_FOR_CACHE_INJECTION,
}),
)
.join('\n')}
}
)}InCache on ${capitalize(
objectMetadataItem.nameSingular,
)} ${mapObjectMetadataToGraphQLQuery({
objectMetadataItems,
objectMetadataItem,
})}
`;
const cachedObjectRecord = {
@ -62,8 +56,8 @@ export const useAddRecordInCache = ({
},
[
objectMetadataItem,
objectMetadataItems,
apolloClient,
mapFieldMetadataToGraphQLQuery,
injectIntoFindOneRecordQueryCache,
],
);

View File

@ -1,9 +1,11 @@
import { useApolloClient } from '@apollo/client/react/hooks/useApolloClient';
import gql from 'graphql-tag';
import { useRecoilValue } from 'recoil';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { QueryMethodName } from '@/object-metadata/types/QueryMethodName';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
export const useCachedRootQuery = ({
objectMetadataItem,
@ -12,33 +14,28 @@ export const useCachedRootQuery = ({
objectMetadataItem: ObjectMetadataItem | undefined;
queryMethodName: QueryMethodName;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const apolloClient = useApolloClient();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState());
if (!objectMetadataItem) {
return { cachedRootQuery: null };
}
const buildRecordFieldsFragment = () => {
return objectMetadataItem.fields
.filter((field) => field.type !== 'RELATION')
.map((field) => mapFieldMetadataToGraphQLQuery({ field }))
.join(' \n');
};
const cacheReadFragment = gql`
fragment RootQuery on Query {
${
QueryMethodName.FindMany === queryMethodName
? objectMetadataItem.namePlural
: objectMetadataItem.nameSingular
} {
${QueryMethodName.FindMany === queryMethodName ? 'edges { node { ' : ''}
${buildRecordFieldsFragment()}
${QueryMethodName.FindMany === queryMethodName ? '}}' : ''}
}
}
${QueryMethodName.FindMany === queryMethodName ? '{ edges { node ' : ''}
${mapObjectMetadataToGraphQLQuery({
objectMetadataItems,
objectMetadataItem,
depth: 0,
})}
${QueryMethodName.FindMany === queryMethodName ? '}}' : ''}
}
`;
const cachedRootQuery = apolloClient.readFragment({

View File

@ -1,7 +1,9 @@
import { gql, useApolloClient } from '@apollo/client';
import { useRecoilValue } from 'recoil';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isNullable } from '~/utils/isNullable';
import { capitalize } from '~/utils/string/capitalize';
@ -11,7 +13,8 @@ export const useGetRecordFromCache = ({
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState());
const apolloClient = useApolloClient();
return <CachedObjectRecord extends ObjectRecord = ObjectRecord>(
@ -25,12 +28,12 @@ export const useGetRecordFromCache = ({
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
const cacheReadFragment = gql`
fragment ${capitalizedObjectName}Fragment on ${capitalizedObjectName} {
id
${objectMetadataItem.fields
.map((field) => mapFieldMetadataToGraphQLQuery({ field }))
.join('\n')}
}
fragment ${capitalizedObjectName}Fragment on ${capitalizedObjectName} ${mapObjectMetadataToGraphQLQuery(
{
objectMetadataItems,
objectMetadataItem,
},
)}
`;
const cachedRecordId = cache.identify({

View File

@ -5,72 +5,28 @@ import { Person } from '@/people/types/Person';
export const query = gql`
mutation CreatePeople($data: [PersonCreateInput!]!) {
createPeople(data: $data) {
id
opportunities {
edges {
node {
__typename
id
}
__typename
xLink {
label
url
}
}
xLink {
label
url
}
id
pointOfContactForOpportunities {
edges {
node {
__typename
id
}
}
}
createdAt
company {
__typename
id
}
city
email
activityTargets {
edges {
node {
__typename
id
}
createdAt
city
email
jobTitle
name {
firstName
lastName
}
}
jobTitle
favorites {
edges {
node {
__typename
id
}
phone
linkedinLink {
label
url
}
}
attachments {
edges {
node {
__typename
id
}
}
}
name {
firstName
lastName
}
phone
linkedinLink {
label
url
}
updatedAt
avatarUrl
companyId
updatedAt
avatarUrl
companyId
}
}
`;
@ -86,32 +42,15 @@ const data = [
export const variables = { data };
export const responseData = {
opportunities: {
edges: [],
},
__typeName: '',
xLink: {
label: '',
url: '',
},
pointOfContactForOpportunities: {
edges: [],
},
createdAt: '',
company: {
id: '',
},
city: '',
email: '',
activityTargets: {
edges: [],
},
jobTitle: '',
favorites: {
edges: [],
},
attachments: {
edges: [],
},
name: {
firstName: '',
lastName: '',

View File

@ -3,72 +3,28 @@ import { gql } from '@apollo/client';
export const query = gql`
mutation CreateOnePerson($input: PersonCreateInput!) {
createPerson(data: $input) {
id
opportunities {
edges {
node {
__typename
id
}
__typename
xLink {
label
url
}
}
xLink {
label
url
}
id
pointOfContactForOpportunities {
edges {
node {
__typename
id
}
}
}
createdAt
company {
__typename
id
}
city
email
activityTargets {
edges {
node {
__typename
id
}
createdAt
city
email
jobTitle
name {
firstName
lastName
}
}
jobTitle
favorites {
edges {
node {
__typename
id
}
phone
linkedinLink {
label
url
}
}
attachments {
edges {
node {
__typename
id
}
}
}
name {
firstName
lastName
}
phone
linkedinLink {
label
url
}
updatedAt
avatarUrl
companyId
updatedAt
avatarUrl
companyId
}
}
`;

View File

@ -5,72 +5,28 @@ export { responseData } from './useUpdateOneRecord';
export const query = gql`
mutation ExecuteQuickActionOnOnePerson($idToExecuteQuickActionOn: ID!) {
executeQuickActionOnPerson(id: $idToExecuteQuickActionOn) {
id
opportunities {
edges {
node {
__typename
id
}
__typename
xLink {
label
url
}
}
xLink {
label
url
}
id
pointOfContactForOpportunities {
edges {
node {
__typename
id
}
}
}
createdAt
company {
__typename
id
}
city
email
activityTargets {
edges {
node {
__typename
id
}
createdAt
city
email
jobTitle
name {
firstName
lastName
}
}
jobTitle
favorites {
edges {
node {
__typename
id
}
phone
linkedinLink {
label
url
}
}
attachments {
edges {
node {
__typename
id
}
}
}
name {
firstName
lastName
}
phone
linkedinLink {
label
url
}
updatedAt
avatarUrl
companyId
updatedAt
avatarUrl
companyId
}
}
`;

View File

@ -5,72 +5,28 @@ import { responseData as person } from './useUpdateOneRecord';
export const query = gql`
query FindOnePerson($objectRecordId: UUID!) {
person(filter: { id: { eq: $objectRecordId } }) {
id
opportunities {
edges {
node {
__typename
id
__typename
xLink {
label
url
}
}
}
xLink {
label
url
}
id
pointOfContactForOpportunities {
edges {
node {
__typename
id
id
createdAt
city
email
jobTitle
name {
firstName
lastName
}
}
}
createdAt
company {
__typename
id
}
city
email
activityTargets {
edges {
node {
__typename
id
phone
linkedinLink {
label
url
}
}
}
jobTitle
favorites {
edges {
node {
__typename
id
}
}
}
attachments {
edges {
node {
__typename
id
}
}
}
name {
firstName
lastName
}
phone
linkedinLink {
label
url
}
updatedAt
avatarUrl
companyId
updatedAt
avatarUrl
companyId
}
}
`;

View File

@ -3,72 +3,28 @@ import { gql } from '@apollo/client';
export const query = gql`
mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) {
updatePerson(id: $idToUpdate, data: $input) {
id
opportunities {
edges {
node {
__typename
id
}
__typename
xLink {
label
url
}
}
xLink {
label
url
}
id
pointOfContactForOpportunities {
edges {
node {
__typename
id
}
}
}
createdAt
company {
__typename
id
}
city
email
activityTargets {
edges {
node {
__typename
id
}
createdAt
city
email
jobTitle
name {
firstName
lastName
}
}
jobTitle
favorites {
edges {
node {
__typename
id
}
phone
linkedinLink {
label
url
}
}
attachments {
edges {
node {
__typename
id
}
}
}
name {
firstName
lastName
}
phone
linkedinLink {
label
url
}
updatedAt
avatarUrl
companyId
updatedAt
avatarUrl
companyId
}
}
`;
@ -127,6 +83,6 @@ export const variables = {
};
export const responseData = {
...basePerson,
...{ ...basePerson, __typename: 'Person' },
...connectedObjects,
};

View File

@ -1,8 +1,10 @@
import { gql } from '@apollo/client';
import { useRecoilValue } from 'recoil';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { isNullable } from '~/utils/isNullable';
import { capitalize } from '~/utils/string/capitalize';
@ -15,7 +17,7 @@ export const useGenerateCreateManyRecordMutation = ({
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState());
if (isNullable(objectMetadataItem)) {
return EMPTY_MUTATION;
@ -29,15 +31,9 @@ export const useGenerateCreateManyRecordMutation = ({
mutation Create${capitalize(
objectMetadataItem.namePlural,
)}($data: [${capitalize(objectMetadataItem.nameSingular)}CreateInput!]!) {
${mutationResponseField}(data: $data) {
id
${objectMetadataItem.fields
.map((field) =>
mapFieldMetadataToGraphQLQuery({
field,
}),
)
.join('\n')}
}
${mutationResponseField}(data: $data) ${mapObjectMetadataToGraphQLQuery({
objectMetadataItems,
objectMetadataItem,
})}
}`;
};

View File

@ -1,8 +1,10 @@
import { gql } from '@apollo/client';
import { useRecoilValue } from 'recoil';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { isNullable } from '~/utils/isNullable';
import { capitalize } from '~/utils/string/capitalize';
@ -15,7 +17,7 @@ export const useGenerateCreateOneRecordMutation = ({
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState());
if (isNullable(objectMetadataItem)) {
return EMPTY_MUTATION;
@ -29,16 +31,10 @@ export const useGenerateCreateOneRecordMutation = ({
return gql`
mutation CreateOne${capitalizedObjectName}($input: ${capitalizedObjectName}CreateInput!) {
${mutationResponseField}(data: $input) {
id
${objectMetadataItem.fields
.map((field) =>
mapFieldMetadataToGraphQLQuery({
field,
}),
)
.join('\n')}
}
${mutationResponseField}(data: $input) ${mapObjectMetadataToGraphQLQuery({
objectMetadataItems,
objectMetadataItem,
})}
}
`;
};

View File

@ -1,8 +1,10 @@
import { gql } from '@apollo/client';
import { useRecoilValue } from 'recoil';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { isNullable } from '~/utils/isNullable';
import { capitalize } from '~/utils/string/capitalize';
@ -19,7 +21,7 @@ export const useGenerateExecuteQuickActionOnOneRecordMutation = ({
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState());
if (isNullable(objectMetadataItem)) {
return EMPTY_MUTATION;
@ -34,16 +36,12 @@ export const useGenerateExecuteQuickActionOnOneRecordMutation = ({
return gql`
mutation ExecuteQuickActionOnOne${capitalizedObjectName}($idToExecuteQuickActionOn: ID!) {
${graphQLFieldForExecuteQuickActionOnOneRecordMutation}(id: $idToExecuteQuickActionOn) {
id
${objectMetadataItem.fields
.map((field) =>
mapFieldMetadataToGraphQLQuery({
field,
}),
)
.join('\n')}
}
${graphQLFieldForExecuteQuickActionOnOneRecordMutation}(id: $idToExecuteQuickActionOn) ${mapObjectMetadataToGraphQLQuery(
{
objectMetadataItems,
objectMetadataItem,
},
)}
}
`;
};

View File

@ -1,7 +1,9 @@
import { gql } from '@apollo/client';
import { useRecoilValue } from 'recoil';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { capitalize } from '~/utils/string/capitalize';
export const getFindDuplicateRecordsQueryResponseField = (
@ -9,13 +11,13 @@ export const getFindDuplicateRecordsQueryResponseField = (
) => `${objectNameSingular}Duplicates`;
export const useGenerateFindDuplicateRecordsQuery = () => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState());
return ({
objectMetadataItem,
depth,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItem: Pick<ObjectMetadataItem, 'fields' | 'nameSingular'>;
depth?: number;
}) => gql`
query FindDuplicate${capitalize(objectMetadataItem.nameSingular)}($id: ID) {
@ -23,17 +25,11 @@ export const useGenerateFindDuplicateRecordsQuery = () => {
objectMetadataItem.nameSingular,
)}(id: $id) {
edges {
node {
id
${objectMetadataItem.fields
.map((field) =>
mapFieldMetadataToGraphQLQuery({
field,
depth,
}),
)
.join('\n')}
}
node ${mapObjectMetadataToGraphQLQuery({
objectMetadataItems,
objectMetadataItem,
depth,
})}
cursor
}
pageInfo {

View File

@ -1,7 +1,7 @@
import { gql } from '@apollo/client';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { isNonEmptyArray } from '~/utils/isNonEmptyArray';
import { capitalize } from '~/utils/string/capitalize';
@ -12,8 +12,6 @@ export const useGenerateFindManyRecordsForMultipleMetadataItemsQuery = ({
objectMetadataItems: ObjectMetadataItem[];
depth?: number;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const capitalizedObjectNameSingulars = objectMetadataItems.map(
({ nameSingular }) => capitalize(nameSingular),
);
@ -59,26 +57,22 @@ export const useGenerateFindManyRecordsForMultipleMetadataItemsQuery = ({
) {
${objectMetadataItems
.map(
({ namePlural, nameSingular, fields }) =>
`${namePlural}(filter: $filter${capitalize(
nameSingular,
(objectMetadataItem) =>
`${objectMetadataItem.namePlural}(filter: $filter${capitalize(
objectMetadataItem.nameSingular,
)}, orderBy: $orderBy${capitalize(
nameSingular,
objectMetadataItem.nameSingular,
)}, first: $limit${capitalize(
nameSingular,
)}, after: $lastCursor${capitalize(nameSingular)}){
objectMetadataItem.nameSingular,
)}, after: $lastCursor${capitalize(
objectMetadataItem.nameSingular,
)}){
edges {
node {
id
${fields
.map((field) =>
mapFieldMetadataToGraphQLQuery({
field,
depth,
}),
)
.join('\n')}
}
node ${mapObjectMetadataToGraphQLQuery({
objectMetadataItems,
objectMetadataItem,
depth,
})}
cursor
}
pageInfo {

View File

@ -1,18 +1,25 @@
import { gql } from '@apollo/client';
import { useRecoilValue } from 'recoil';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { capitalize } from '~/utils/string/capitalize';
export const useGenerateFindManyRecordsQuery = () => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState());
return ({
objectMetadataItem,
depth,
eagerLoadedRelations,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItem: Pick<
ObjectMetadataItem,
'fields' | 'nameSingular' | 'namePlural'
>;
depth?: number;
eagerLoadedRelations?: Record<string, any>;
}) => gql`
query FindMany${capitalize(
objectMetadataItem.namePlural,
@ -25,17 +32,12 @@ export const useGenerateFindManyRecordsQuery = () => {
objectMetadataItem.namePlural
}(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
edges {
node {
id
${objectMetadataItem.fields
.map((field) =>
mapFieldMetadataToGraphQLQuery({
field,
depth,
}),
)
.join('\n')}
}
node ${mapObjectMetadataToGraphQLQuery({
objectMetadataItems,
objectMetadataItem,
depth,
eagerLoadedRelations,
})}
cursor
}
pageInfo {

View File

@ -1,17 +1,19 @@
import { gql } from '@apollo/client';
import { useRecoilValue } from 'recoil';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { capitalize } from '~/utils/string/capitalize';
export const useGenerateFindOneRecordQuery = () => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState());
return ({
objectMetadataItem,
depth,
}: {
objectMetadataItem: Pick<ObjectMetadataItem, 'nameSingular' | 'fields'>;
objectMetadataItem: Pick<ObjectMetadataItem, 'fields' | 'nameSingular'>;
depth?: number;
}) => {
return gql`
@ -22,17 +24,11 @@ export const useGenerateFindOneRecordQuery = () => {
id: {
eq: $objectRecordId
}
}){
id
${objectMetadataItem.fields
.map((field) =>
mapFieldMetadataToGraphQLQuery({
field,
depth,
}),
)
.join('\n')}
}
})${mapObjectMetadataToGraphQLQuery({
objectMetadataItems,
objectMetadataItem,
depth,
})}
}
`;
};

View File

@ -1,8 +1,10 @@
import { gql } from '@apollo/client';
import { useRecoilValue } from 'recoil';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { isNullable } from '~/utils/isNullable';
import { capitalize } from '~/utils/string/capitalize';
@ -15,7 +17,7 @@ export const useGenerateUpdateOneRecordMutation = ({
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState());
if (isNullable(objectMetadataItem)) {
return EMPTY_MUTATION;
@ -29,12 +31,12 @@ export const useGenerateUpdateOneRecordMutation = ({
return gql`
mutation UpdateOne${capitalizedObjectName}($idToUpdate: ID!, $input: ${capitalizedObjectName}UpdateInput!) {
${mutationResponseField}(id: $idToUpdate, data: $input) {
id
${objectMetadataItem.fields
.map((field) => mapFieldMetadataToGraphQLQuery({ field }))
.join('\n')}
}
${mutationResponseField}(id: $idToUpdate, data: $input) ${mapObjectMetadataToGraphQLQuery(
{
objectMetadataItems,
objectMetadataItem,
},
)}
}
`;
};

View File

@ -20,7 +20,7 @@ export const useUpdateOneRecord = <
const apolloClient = useApolloClient();
const { objectMetadataItem, updateOneRecordMutation, getRecordFromCache } =
useObjectMetadataItem({ objectNameSingular });
useObjectMetadataItem({ objectNameSingular }, 1);
const { generateObjectRecordOptimisticResponse } =
useGenerateObjectRecordOptimisticResponse({

View File

@ -20,14 +20,31 @@ import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinit
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({
useMapFieldMetadataToGraphQLQuery: () => () => '\n',
}));
const query = gql`
mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) {
updatePerson(id: $idToUpdate, data: $input) {
__typename
xLink {
label
url
}
id
createdAt
city
email
jobTitle
name {
firstName
lastName
}
phone
linkedinLink {
label
url
}
updatedAt
avatarUrl
companyId
}
}
`;

View File

@ -14,10 +14,6 @@ import {
} from '@/object-record/record-field/contexts/FieldContext';
import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput';
jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({
useMapFieldMetadataToGraphQLQuery: () => () => '\n',
}));
const entityId = 'entityId';
const mocks: MockedResponse[] = [
@ -29,7 +25,28 @@ const mocks: MockedResponse[] = [
$input: CompanyUpdateInput!
) {
updateCompany(id: $idToUpdate, data: $input) {
__typename
xLink {
label
url
}
linkedinLink {
label
url
}
domainName
annualRecurringRevenue {
amountMicros
currencyCode
}
createdAt
address
updatedAt
name
accountOwnerId
employees
id
idealCustomerProfile
}
}
`,

View File

@ -6,6 +6,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useMultiObjectSearch } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
import { FieldMetadataType } from '~/generated/graphql';
const query = gql`
query FindManyRecordsMultipleMetadataItems(
@ -22,6 +23,7 @@ const query = gql`
) {
edges {
node {
__typename
id
}
cursor
@ -36,7 +38,7 @@ const query = gql`
`;
const response = {
namePlural: {
edges: [{ node: { id: 'nodeId' }, cursor: 'cursor' }],
edges: [{ node: { __typename: 'Custom', id: 'nodeId' }, cursor: 'cursor' }],
pageInfo: { startCursor: '', hasNextPage: '', endCursor: '' },
},
};
@ -120,7 +122,17 @@ describe('useMultiObjectSearch', () => {
namePlural: 'namePlural',
nameSingular: 'nameSingular',
updatedAt: 'updatedAt',
fields: [],
fields: [
{
id: 'f6a0a73a-5ee6-442e-b764-39b682471240',
name: 'id',
label: 'id',
type: FieldMetadataType.Uuid,
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
isActive: true,
},
],
},
];
act(() => {
@ -144,9 +156,19 @@ describe('useMultiObjectSearch', () => {
namePlural: 'namePlural',
nameSingular: 'nameSingular',
updatedAt: 'updatedAt',
fields: [],
fields: [
{
id: 'f6a0a73a-5ee6-442e-b764-39b682471240',
name: 'id',
label: 'id',
isActive: true,
type: FieldMetadataType.Uuid,
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
},
],
},
record: { id: 'nodeId' },
record: { id: 'nodeId', __typename: 'Custom' },
recordIdentifier: {
id: 'nodeId',
name: '',

View File

@ -86,6 +86,7 @@ export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({
const multiSelectQueryForSelectedIds =
useGenerateFindManyRecordsForMultipleMetadataItemsQuery({
objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery,
depth: 0,
});
const {

View File

@ -87,6 +87,7 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({
const multiSelectQuery =
useGenerateFindManyRecordsForMultipleMetadataItemsQuery({
objectMetadataItems: nonSystemObjectMetadataItems,
depth: 0,
});
const {

View File

@ -16,17 +16,34 @@ jest.mock('uuid', () => ({
v4: jest.fn(() => companyId),
}));
jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => ({
useMapFieldMetadataToGraphQLQuery: () => () => '\n',
}));
const companyMocks = [
{
request: {
query: gql`
mutation CreateCompanies($data: [CompanyCreateInput!]!) {
createCompanies(data: $data) {
__typename
xLink {
label
url
}
linkedinLink {
label
url
}
domainName
annualRecurringRevenue {
amountMicros
currencyCode
}
createdAt
address
updatedAt
name
accountOwnerId
employees
id
idealCustomerProfile
}
}
`,

View File

@ -23,12 +23,6 @@ import { entityCountInCurrentViewScopedState } from '@/views/states/entityCountI
import { viewEditModeScopedState } from '@/views/states/viewEditModeScopedState';
import { viewObjectMetadataIdScopeState } from '@/views/states/viewObjectMetadataIdScopeState';
jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => {
return {
useMapFieldMetadataToGraphQLQuery: jest.fn().mockReturnValue(() => '\n'),
};
});
const mockedUuid = 'mocked-uuid';
jest.mock('uuid');

View File

@ -14,12 +14,6 @@ import { ViewScope } from '@/views/scopes/ViewScope';
import { currentViewFieldsScopedFamilyState } from '@/views/states/currentViewFieldsScopedFamilyState';
import { ViewField } from '@/views/types/ViewField';
jest.mock('@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery', () => {
return {
useMapFieldMetadataToGraphQLQuery: jest.fn().mockReturnValue(() => '\n'),
};
});
const fieldMetadataId = '12ecdf87-506f-44a7-98c6-393e5f05b225';
const fieldDefinition: ColumnDefinition<FieldMetadata> = {
@ -53,7 +47,15 @@ const mocks = [
query: gql`
mutation CreateOneViewField($input: ViewFieldCreateInput!) {
createViewField(data: $input) {
__typename
position
isVisible
fieldMetadataId
viewId
id
size
createdAt
updatedAt
}
}
`,