[BUG][PROD] Fix ViewGroup creation optimistic cache (#10014)
# Introduction When we create a new `view` from record table that has relation such as opportunities. Encountered invariant conditions: ## Unknown fiel `__typename` `Should never occur, encountered unknown fields __typename in objectMetadaItem viewGroup`, ### Fixed by ignoring unknown internal fields ## Provided both relation `view` and `viewId` `Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: view` ### Fixed by sending only `viewId` to `createManyRecords` in `usePersistViewGroupRecords.ts`
This commit is contained in:
@ -0,0 +1,4 @@
|
|||||||
|
import { BaseObjectRecord } from '@/object-record/types/BaseObjectRecord';
|
||||||
|
|
||||||
|
export const OBJECT_RECORD_TYPENAME_KEY =
|
||||||
|
'__typename' satisfies keyof BaseObjectRecord;
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
export type BaseObjectRecord = {
|
||||||
|
id: string;
|
||||||
|
__typename: string;
|
||||||
|
};
|
||||||
@ -1,4 +1,3 @@
|
|||||||
export type ObjectRecord = Record<string, any> & {
|
import { BaseObjectRecord } from '@/object-record/types/BaseObjectRecord';
|
||||||
id: string;
|
|
||||||
__typename: string;
|
export type ObjectRecord = Record<string, any> & BaseObjectRecord;
|
||||||
};
|
|
||||||
|
|||||||
@ -3,17 +3,34 @@ import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeO
|
|||||||
import { InMemoryCache } from '@apollo/client';
|
import { InMemoryCache } from '@apollo/client';
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
|
|
||||||
|
const getPersonObjectMetadaItem = () => {
|
||||||
|
const personObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||||
|
(item) => item.nameSingular === 'person',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!personObjectMetadataItem) {
|
||||||
|
throw new Error('Person object metadata item not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return personObjectMetadataItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCompanyObjectMetadataItem = () => {
|
||||||
|
const companyObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||||
|
(item) => item.nameSingular === 'company',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!companyObjectMetadataItem) {
|
||||||
|
throw new Error('Company object metadata item not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return companyObjectMetadataItem;
|
||||||
|
};
|
||||||
|
|
||||||
describe('computeOptimisticRecordFromInput', () => {
|
describe('computeOptimisticRecordFromInput', () => {
|
||||||
it('should generate correct optimistic record if no relation field is present', () => {
|
it('should generate correct optimistic record if no relation field is present', () => {
|
||||||
const cache = new InMemoryCache();
|
const cache = new InMemoryCache();
|
||||||
|
const personObjectMetadataItem = getPersonObjectMetadaItem();
|
||||||
const personObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
|
||||||
(item) => item.nameSingular === 'person',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!personObjectMetadataItem) {
|
|
||||||
throw new Error('Person object metadata item not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = computeOptimisticRecordFromInput({
|
const result = computeOptimisticRecordFromInput({
|
||||||
objectMetadataItems: generatedMockObjectMetadataItems,
|
objectMetadataItems: generatedMockObjectMetadataItems,
|
||||||
@ -31,14 +48,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
|
|
||||||
it('should generate correct optimistic record if relation field is present but cache is empty', () => {
|
it('should generate correct optimistic record if relation field is present but cache is empty', () => {
|
||||||
const cache = new InMemoryCache();
|
const cache = new InMemoryCache();
|
||||||
|
const personObjectMetadataItem = getPersonObjectMetadaItem();
|
||||||
const personObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
|
||||||
(item) => item.nameSingular === 'person',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!personObjectMetadataItem) {
|
|
||||||
throw new Error('Person object metadata item not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = computeOptimisticRecordFromInput({
|
const result = computeOptimisticRecordFromInput({
|
||||||
objectMetadataItems: generatedMockObjectMetadataItems,
|
objectMetadataItems: generatedMockObjectMetadataItems,
|
||||||
@ -54,23 +64,48 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate correct optimistic record even if recordInput contains field __typename', () => {
|
||||||
|
const cache = new InMemoryCache();
|
||||||
|
const personObjectMetadataItem = getPersonObjectMetadaItem();
|
||||||
|
const companyObjectMetadataItem = getCompanyObjectMetadataItem();
|
||||||
|
|
||||||
|
const companyRecord = {
|
||||||
|
id: '123',
|
||||||
|
__typename: 'Company',
|
||||||
|
};
|
||||||
|
|
||||||
|
updateRecordFromCache({
|
||||||
|
objectMetadataItems: generatedMockObjectMetadataItems,
|
||||||
|
objectMetadataItem: {
|
||||||
|
...companyObjectMetadataItem,
|
||||||
|
fields: companyObjectMetadataItem.fields.filter(
|
||||||
|
(field) => field.name === 'id',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
cache,
|
||||||
|
record: companyRecord,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = computeOptimisticRecordFromInput({
|
||||||
|
objectMetadataItems: generatedMockObjectMetadataItems,
|
||||||
|
objectMetadataItem: personObjectMetadataItem,
|
||||||
|
recordInput: {
|
||||||
|
companyId: '123',
|
||||||
|
__typename: 'test',
|
||||||
|
},
|
||||||
|
cache,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toStrictEqual({
|
||||||
|
companyId: '123',
|
||||||
|
company: companyRecord,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should generate correct optimistic record if relation field is present and cache is not empty', () => {
|
it('should generate correct optimistic record if relation field is present and cache is not empty', () => {
|
||||||
const cache = new InMemoryCache();
|
const cache = new InMemoryCache();
|
||||||
const personObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
const personObjectMetadataItem = getPersonObjectMetadaItem();
|
||||||
(item) => item.nameSingular === 'person',
|
const companyObjectMetadataItem = getCompanyObjectMetadataItem();
|
||||||
);
|
|
||||||
|
|
||||||
if (!personObjectMetadataItem) {
|
|
||||||
throw new Error('Person object metadata item not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const companyObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
|
||||||
(item) => item.nameSingular === 'company',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!companyObjectMetadataItem) {
|
|
||||||
throw new Error('Company object metadata item not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const companyRecord = {
|
const companyRecord = {
|
||||||
id: '123',
|
id: '123',
|
||||||
@ -106,14 +141,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
|
|
||||||
it('should generate correct optimistic record if relation field is null and cache is empty', () => {
|
it('should generate correct optimistic record if relation field is null and cache is empty', () => {
|
||||||
const cache = new InMemoryCache();
|
const cache = new InMemoryCache();
|
||||||
|
const personObjectMetadataItem = getPersonObjectMetadaItem();
|
||||||
const personObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
|
||||||
(item) => item.nameSingular === 'person',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!personObjectMetadataItem) {
|
|
||||||
throw new Error('Person object metadata item not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = computeOptimisticRecordFromInput({
|
const result = computeOptimisticRecordFromInput({
|
||||||
objectMetadataItems: generatedMockObjectMetadataItems,
|
objectMetadataItems: generatedMockObjectMetadataItems,
|
||||||
@ -130,14 +158,9 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if recordInput contains fiels unrelated to the current objectMetadata', () => {
|
it('should throw an error if recordInput contains fields unrelated to the current objectMetadata', () => {
|
||||||
const cache = new InMemoryCache();
|
const cache = new InMemoryCache();
|
||||||
const personObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
const personObjectMetadataItem = getPersonObjectMetadaItem();
|
||||||
(item) => item.nameSingular === 'person',
|
|
||||||
);
|
|
||||||
if (!personObjectMetadataItem) {
|
|
||||||
throw new Error('Person object metadata item not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
computeOptimisticRecordFromInput({
|
computeOptimisticRecordFromInput({
|
||||||
@ -158,12 +181,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
|
|
||||||
it('should throw an error if recordInput contains both the relationFieldId and relationField', () => {
|
it('should throw an error if recordInput contains both the relationFieldId and relationField', () => {
|
||||||
const cache = new InMemoryCache();
|
const cache = new InMemoryCache();
|
||||||
const personObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
const personObjectMetadataItem = getPersonObjectMetadaItem();
|
||||||
(item) => item.nameSingular === 'person',
|
|
||||||
);
|
|
||||||
if (!personObjectMetadataItem) {
|
|
||||||
throw new Error('Person object metadata item not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
computeOptimisticRecordFromInput({
|
computeOptimisticRecordFromInput({
|
||||||
@ -176,18 +194,13 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
cache,
|
cache,
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingInlineSnapshot(
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"Should never provide relation mutation through anything else than the fieldId e.g companyId"`,
|
`"Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: company"`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if recordInput contains both the relationFieldId and relationField even if null', () => {
|
it('should throw an error if recordInput contains both the relationFieldId and relationField even if null', () => {
|
||||||
const cache = new InMemoryCache();
|
const cache = new InMemoryCache();
|
||||||
const personObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
const personObjectMetadataItem = getPersonObjectMetadaItem();
|
||||||
(item) => item.nameSingular === 'person',
|
|
||||||
);
|
|
||||||
if (!personObjectMetadataItem) {
|
|
||||||
throw new Error('Person object metadata item not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
computeOptimisticRecordFromInput({
|
computeOptimisticRecordFromInput({
|
||||||
@ -200,7 +213,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
cache,
|
cache,
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingInlineSnapshot(
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"Should never provide relation mutation through anything else than the fieldId e.g companyId"`,
|
`"Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: company"`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
getRecordFromCache,
|
getRecordFromCache,
|
||||||
GetRecordFromCacheArgs,
|
GetRecordFromCacheArgs,
|
||||||
} from '@/object-record/cache/utils/getRecordFromCache';
|
} from '@/object-record/cache/utils/getRecordFromCache';
|
||||||
|
import { OBJECT_RECORD_TYPENAME_KEY } from '@/object-record/constants/ObjectRecordTypename';
|
||||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||||
import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid';
|
import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
@ -24,9 +25,13 @@ export const computeOptimisticRecordFromInput = ({
|
|||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
}: ComputeOptimisticCacheRecordInputArgs) => {
|
}: ComputeOptimisticCacheRecordInputArgs) => {
|
||||||
const unknownRecordInputFields = Object.keys(recordInput).filter(
|
const unknownRecordInputFields = Object.keys(recordInput).filter(
|
||||||
(fieldName) =>
|
(fieldName) => {
|
||||||
objectMetadataItem.fields.find(({ name }) => name === fieldName) ===
|
const isUnknownMetadataItemField =
|
||||||
undefined,
|
objectMetadataItem.fields.find(({ name }) => name === fieldName) ===
|
||||||
|
undefined;
|
||||||
|
const isTypenameField = fieldName === OBJECT_RECORD_TYPENAME_KEY;
|
||||||
|
return isUnknownMetadataItemField && !isTypenameField;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (unknownRecordInputFields.length > 0) {
|
if (unknownRecordInputFields.length > 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -93,7 +98,7 @@ export const computeOptimisticRecordFromInput = ({
|
|||||||
|
|
||||||
if (!isUndefined(recordInputFieldValue)) {
|
if (!isUndefined(recordInputFieldValue)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Should never provide relation mutation through anything else than the fieldId e.g companyId',
|
`Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: ${fieldMetadataItem.name}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,14 +26,12 @@ export const usePersistViewGroupRecords = () => {
|
|||||||
|
|
||||||
const createViewGroupRecords = useCallback(
|
const createViewGroupRecords = useCallback(
|
||||||
(viewGroupsToCreate: ViewGroup[], view: GraphQLView) => {
|
(viewGroupsToCreate: ViewGroup[], view: GraphQLView) => {
|
||||||
if (!viewGroupsToCreate.length) return;
|
if (viewGroupsToCreate.length === 0) return;
|
||||||
|
|
||||||
return createManyRecords(
|
return createManyRecords(
|
||||||
viewGroupsToCreate.map((viewGroup) => ({
|
viewGroupsToCreate.map((viewGroup) => ({
|
||||||
...viewGroup,
|
...viewGroup,
|
||||||
view: {
|
viewId: view.id,
|
||||||
id: view.id,
|
|
||||||
},
|
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user