## 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -37,21 +37,10 @@ describe('mapFieldMetadataToGraphQLQuery', () => {
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({
objectMetadataItems: mockObjectMetadataItems,
depth: 1,
field: personObjectMetadataItem.fields.find(
(field) => field.name === 'company',
)!,
@ -83,168 +72,14 @@ accountOwnerId
employees
id
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({
objectMetadataItems: mockObjectMetadataItems,
depth: 2,
queryFields: {
accountOwner: true,
relationrecordFields: {
accountOwner: { id: true, name: true },
people: true,
xLink: true,
linkedinLink: true,
@ -274,17 +109,11 @@ xLink
accountOwner
{
__typename
colorScheme
name
{
firstName
lastName
}
locale
userId
avatarUrl
createdAt
updatedAt
id
}
linkedinLink

View File

@ -15,209 +15,11 @@ if (!personObjectMetadataItem) {
}
describe('mapObjectMetadataToGraphQLQuery', () => {
it('should return typename if depth < 0', async () => {
it('should query only specified recordGqlFields', 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
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: {
recordGqlFields: {
company: true,
xLink: true,
id: true,
@ -232,7 +34,6 @@ companyId
avatarUrl: true,
companyId: true,
},
depth: 1,
});
expect(formatGQLString(res)).toEqual(`{
__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({
objectMetadataItems: mockObjectMetadataItems,
objectMetadataItem: personObjectMetadataItem,
queryFields: { company: true, id: true, name: true },
depth: 1,
recordGqlFields: { company: { id: true }, id: true, name: true },
});
expect(formatGQLString(res)).toEqual(`{
__typename
@ -304,30 +104,7 @@ id
company
{
__typename
xLink
{
label
url
}
linkedinLink
{
label
url
}
domainName
annualRecurringRevenue
{
amountMicros
currencyCode
}
createdAt
address
updatedAt
name
accountOwnerId
employees
id
idealCustomerProfile
}
name
{

View File

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

View File

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

View File

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

View File

@ -5,15 +5,13 @@ import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueri
export const mapObjectMetadataToGraphQLQuery = ({
objectMetadataItems,
objectMetadataItem,
depth = 1,
queryFields,
recordGqlFields,
computeReferences = false,
isRootLevel = true,
}: {
objectMetadataItems: ObjectMetadataItem[];
objectMetadataItem: Pick<ObjectMetadataItem, 'nameSingular' | 'fields'>;
depth?: number;
queryFields?: Record<string, any>;
recordGqlFields?: Record<string, any>;
computeReferences?: boolean;
isRootLevel?: boolean;
}): any => {
@ -23,8 +21,7 @@ export const mapObjectMetadataToGraphQLQuery = ({
.filter((field) =>
shouldFieldBeQueried({
field,
depth,
queryFields,
recordGqlFields,
}),
) ?? [];
@ -37,18 +34,17 @@ export const mapObjectMetadataToGraphQLQuery = ({
return `{
__typename
${fieldsThatShouldBeQueried
.map((field) =>
mapFieldMetadataToGraphQLQuery({
.map((field) => {
return mapFieldMetadataToGraphQLQuery({
objectMetadataItems,
field,
depth,
queryFields:
typeof queryFields?.[field.name] === 'boolean'
relationrecordFields:
typeof recordGqlFields?.[field.name] === 'boolean'
? undefined
: queryFields?.[field.name],
: recordGqlFields?.[field.name],
computeReferences,
}),
)
});
})
.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 { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { FieldMetadataItem } from '../types/FieldMetadataItem';
export const shouldFieldBeQueried = ({
field,
depth,
queryFields,
recordGqlFields,
}: {
field: Pick<FieldMetadataItem, 'name' | 'type'>;
depth?: number;
objectRecord?: ObjectRecord;
queryFields?: Record<string, any>;
recordGqlFields?: RecordGqlOperationGqlRecordFields;
}): any => {
if (!isUndefined(depth) && depth < 0) {
return false;
}
if (
!isUndefined(depth) &&
depth < 1 &&
field.type === FieldMetadataType.Relation
isUndefinedOrNull(recordGqlFields) &&
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 true;
return false;
};