Feat/error boundaries (#2779)

* - Changed to objectNameSingular always defined
- Added ErrorCatchAll

* - Added mock mode for companies logged out
- Added a proper ErrorBoundary component

* Removed react-error-boundary

* Implemented proper ErrorBoundary

* Fixes

* Change strategy about mocks

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-12-01 22:06:38 +01:00
committed by GitHub
parent a301f451f9
commit 74b077f3ca
75 changed files with 4213 additions and 674 deletions

View File

@ -37,6 +37,10 @@ export const RecordShowPage = () => {
objectRecordId: string;
}>();
if (!objectNameSingular) {
throw new Error(`Object name is not defined`);
}
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
@ -44,7 +48,7 @@ export const RecordShowPage = () => {
const { identifiersMapper } = useRelationPicker();
const { favorites, createFavorite, deleteFavorite } = useFavorites({
objectNamePlural: objectMetadataItem?.namePlural,
objectNamePlural: objectMetadataItem.namePlural,
});
const [, setEntityFields] = useRecoilState(

View File

@ -2,6 +2,7 @@ import styled from '@emotion/styled';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { RecordTable } from '@/ui/object/record-table/components/RecordTable';
import { TableOptionsDropdownId } from '@/ui/object/record-table/constants/TableOptionsDropdownId';
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
@ -29,17 +30,19 @@ export const RecordTableContainer = ({
objectNamePlural: string;
createRecord: () => void;
}) => {
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
{
objectNamePlural,
},
);
const { columnDefinitions } = useColumnDefinitionsFromFieldMetadata(
foundObjectMetadataItem,
);
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { columnDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular: foundObjectMetadataItem?.nameSingular,
objectNameSingular,
});
const recordTableId = objectNamePlural ?? '';

View File

@ -2,6 +2,7 @@ import { useEffect } from 'react';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useRecordTableContextMenuEntries } from '@/object-record/hooks/useRecordTableContextMenuEntries';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
@ -16,18 +17,23 @@ export const RecordTableEffect = ({
viewBarId: string;
}) => {
const {
// Todo: do not infer objectNamePlural from recordTableId
scopeId: objectNamePlural,
setAvailableTableColumns,
setOnEntityCountChange,
setObjectMetadataConfig,
} = useRecordTable({ recordTableScopeId: recordTableId });
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const {
objectMetadataItem,
basePathToShowPage,
labelIdentifierFieldMetadataId,
} = useObjectMetadataItem({
objectNamePlural,
objectNameSingular,
});
const { columnDefinitions, filterDefinitions, sortDefinitions } =

View File

@ -1,11 +1,11 @@
import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { IconBuildingSkyscraper } from '@/ui/display/icon';
import { PageAddButton } from '@/ui/layout/page/PageAddButton';
import { PageBody } from '@/ui/layout/page/PageBody';
@ -25,18 +25,12 @@ const StyledTableContainer = styled.div`
width: 100%;
`;
export type RecordTablePageProps = Pick<
ObjectMetadataItemIdentifier,
'objectNamePlural'
>;
export const RecordTablePage = () => {
const objectNamePlural = useParams().objectNamePlural ?? '';
const { objectMetadataItemNotFound, objectMetadataItem } =
useObjectMetadataItem({
objectNamePlural,
});
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const onboardingStatus = useOnboardingStatus();
@ -44,15 +38,15 @@ export const RecordTablePage = () => {
useEffect(() => {
if (
objectMetadataItemNotFound &&
!isNonEmptyString(objectNamePlural) &&
onboardingStatus === OnboardingStatus.Completed
) {
navigate('/');
}
}, [objectMetadataItemNotFound, navigate, onboardingStatus]);
}, [objectNamePlural, navigate, onboardingStatus]);
const { createOneRecord: createOneObject } = useCreateOneRecord({
objectNameSingular: objectMetadataItem?.nameSingular,
objectNameSingular,
});
const handleAddButtonClick = async () => {

View File

@ -8,42 +8,39 @@ import { capitalize } from '~/utils/string/capitalize';
export const useCreateOneRecord = <T>({
objectNameSingular,
}: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'>) => {
}: ObjectMetadataItemIdentifier) => {
const { triggerOptimisticEffects } = useOptimisticEffect({
objectNameSingular,
});
const {
objectMetadataItem,
objectMetadataItemNotFound,
createOneRecordMutation,
} = useObjectMetadataItem({
objectNameSingular,
});
const { objectMetadataItem, createOneRecordMutation } = useObjectMetadataItem(
{
objectNameSingular,
},
);
// TODO: type this with a minimal type at least with Record<string, any>
const [mutate] = useMutation(createOneRecordMutation);
const createOneRecord = async (input: Record<string, any>) => {
if (!objectMetadataItem || !objectNameSingular) {
return null;
}
const createdRecord = await mutate({
const createdObject = await mutate({
variables: {
input: { ...input, id: v4() },
},
});
triggerOptimisticEffects(
`${capitalize(objectNameSingular)}Edge`,
createdRecord.data[`create${capitalize(objectNameSingular)}`],
`${capitalize(objectMetadataItem.nameSingular)}Edge`,
createdObject.data[
`create${capitalize(objectMetadataItem.nameSingular)}`
],
);
return createdRecord.data[`create${capitalize(objectNameSingular)}`] as T;
return createdObject.data[
`create${capitalize(objectMetadataItem.nameSingular)}`
] as T;
};
return {
createOneRecord,
objectMetadataItemNotFound,
};
};

View File

@ -8,41 +8,40 @@ import { capitalize } from '~/utils/string/capitalize';
export const useDeleteOneRecord = <T>({
objectNameSingular,
}: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'>) => {
}: ObjectMetadataItemIdentifier) => {
const { performOptimisticEvict } = useOptimisticEvict();
const {
objectMetadataItem,
objectMetadataItemNotFound,
deleteOneRecordMutation,
} = useObjectMetadataItem({
objectNameSingular,
});
const { objectMetadataItem, deleteOneRecordMutation } = useObjectMetadataItem(
{
objectNameSingular,
},
);
// TODO: type this with a minimal type at least with Record<string, any>
const [mutate] = useMutation(deleteOneRecordMutation);
const deleteOneRecord = useCallback(
async (idToDelete: string) => {
if (!objectMetadataItem || !objectNameSingular) {
return null;
}
const deletedRecord = await mutate({
variables: {
idToDelete,
},
});
performOptimisticEvict(capitalize(objectNameSingular), 'id', idToDelete);
performOptimisticEvict(
capitalize(objectMetadataItem.nameSingular),
'id',
idToDelete,
);
return deletedRecord.data[`create${capitalize(objectNameSingular)}`] as T;
return deletedRecord.data[
`create${capitalize(objectMetadataItem.nameSingular)}`
] as T;
},
[performOptimisticEvict, objectMetadataItem, mutate, objectNameSingular],
[performOptimisticEvict, objectMetadataItem, mutate],
);
return {
deleteOneRecord,
objectMetadataItemNotFound,
};
};

View File

@ -2,9 +2,10 @@ import { useCallback, useMemo } from 'react';
import { useQuery } from '@apollo/client';
import { isNonEmptyArray } from '@apollo/client/utilities';
import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilState } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
import { getRecordOptimisticEffectDefinition } from '@/object-record/graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition';
@ -27,13 +28,13 @@ import { mapPaginatedRecordsToRecords } from '../utils/mapPaginatedRecordsToReco
export const useFindManyRecords = <
RecordType extends { id: string } & Record<string, any>,
>({
objectNamePlural,
objectNameSingular,
filter,
orderBy,
limit = DEFAULT_SEARCH_REQUEST_LIMIT,
onCompleted,
skip,
}: Pick<ObjectMetadataItemIdentifier, 'objectNamePlural'> & {
}: ObjectMetadataItemIdentifier & {
filter?: any;
orderBy?: any;
limit?: number;
@ -41,7 +42,10 @@ export const useFindManyRecords = <
skip?: boolean;
}) => {
const findManyQueryStateIdentifier =
objectNamePlural + JSON.stringify(filter) + JSON.stringify(orderBy) + limit;
objectNameSingular +
JSON.stringify(filter) +
JSON.stringify(orderBy) +
limit;
const [lastCursor, setLastCursor] = useRecoilState(
cursorFamilyState(findManyQueryStateIdentifier),
@ -55,24 +59,21 @@ export const useFindManyRecords = <
isFetchingMoreRecordsFamilyState(findManyQueryStateIdentifier),
);
const {
objectMetadataItem,
objectMetadataItemNotFound,
findManyRecordsQuery,
} = useObjectMetadataItem({
objectNamePlural,
const { objectMetadataItem, findManyRecordsQuery } = useObjectMetadataItem({
objectNameSingular,
});
const { registerOptimisticEffect } = useOptimisticEffect({
objectNameSingular: objectMetadataItem?.nameSingular,
objectNameSingular,
});
const { enqueueSnackBar } = useSnackBar();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const { data, loading, error, fetchMore } = useQuery<
PaginatedRecordType<RecordType>
>(findManyRecordsQuery, {
skip: skip || !objectMetadataItem || !objectNamePlural,
skip: skip || !objectMetadataItem || !currentWorkspace,
variables: {
filter: filter ?? {},
limit: limit,
@ -92,21 +93,24 @@ export const useFindManyRecords = <
});
}
if (objectNamePlural) {
onCompleted?.(data[objectNamePlural]);
onCompleted?.(data[objectMetadataItem.namePlural]);
if (objectNamePlural && data?.[objectNamePlural]) {
setLastCursor(data?.[objectNamePlural]?.pageInfo.endCursor);
setHasNextPage(data?.[objectNamePlural]?.pageInfo.hasNextPage);
}
if (data?.[objectMetadataItem.namePlural]) {
setLastCursor(
data?.[objectMetadataItem.namePlural]?.pageInfo.endCursor,
);
setHasNextPage(
data?.[objectMetadataItem.namePlural]?.pageInfo.hasNextPage,
);
}
},
onError: (error) => {
logError(
`useFindManyObjectRecords for "${objectNamePlural}" error : ` + error,
`useFindManyRecords for "${objectMetadataItem.namePlural}" error : ` +
error,
);
enqueueSnackBar(
`Error during useFindManyObjectRecords for "${objectNamePlural}", ${error.message}`,
`Error during useFindManyRecords for "${objectMetadataItem.namePlural}", ${error.message}`,
{
variant: 'error',
},
@ -115,7 +119,7 @@ export const useFindManyRecords = <
});
const fetchMoreRecords = useCallback(async () => {
if (objectNamePlural && hasNextPage) {
if (hasNextPage) {
setIsFetchingMoreObjects(true);
try {
@ -126,33 +130,38 @@ export const useFindManyRecords = <
lastCursor: isNonEmptyString(lastCursor) ? lastCursor : undefined,
},
updateQuery: (prev, { fetchMoreResult }) => {
const previousEdges = prev?.[objectNamePlural]?.edges;
const nextEdges = fetchMoreResult?.[objectNamePlural]?.edges;
const previousEdges = prev?.[objectMetadataItem.namePlural]?.edges;
const nextEdges =
fetchMoreResult?.[objectMetadataItem.namePlural]?.edges;
let newEdges: PaginatedRecordTypeEdge<RecordType>[] = [];
if (isNonEmptyArray(previousEdges) && isNonEmptyArray(nextEdges)) {
newEdges = filterUniqueRecordEdgesByCursor([
...prev?.[objectNamePlural]?.edges,
...fetchMoreResult?.[objectNamePlural]?.edges,
...prev?.[objectMetadataItem.namePlural]?.edges,
...fetchMoreResult?.[objectMetadataItem.namePlural]?.edges,
]);
}
return Object.assign({}, prev, {
[objectNamePlural]: {
[objectMetadataItem.namePlural]: {
__typename: `${capitalize(
objectMetadataItem?.nameSingular ?? '',
objectMetadataItem.nameSingular,
)}Connection`,
edges: newEdges,
pageInfo: fetchMoreResult?.[objectNamePlural].pageInfo,
pageInfo:
fetchMoreResult?.[objectMetadataItem.namePlural].pageInfo,
},
} as PaginatedRecordType<RecordType>);
},
});
} catch (error) {
logError(`fetchMoreObjects for "${objectNamePlural}" error : ` + error);
logError(
`fetchMoreObjects for "${objectMetadataItem.namePlural}" error : ` +
error,
);
enqueueSnackBar(
`Error during fetchMoreObjects for "${objectNamePlural}", ${error}`,
`Error during fetchMoreObjects for "${objectMetadataItem.namePlural}", ${error}`,
{
variant: 'error',
},
@ -162,7 +171,6 @@ export const useFindManyRecords = <
}
}
}, [
objectNamePlural,
lastCursor,
fetchMore,
filter,
@ -175,13 +183,11 @@ export const useFindManyRecords = <
const records = useMemo(
() =>
objectNamePlural
? mapPaginatedRecordsToRecords({
pagedRecords: data,
objectNamePlural,
})
: [],
[data, objectNamePlural],
mapPaginatedRecordsToRecords({
pagedRecords: data,
objectNamePlural: objectMetadataItem.namePlural,
}),
[data, objectMetadataItem],
);
return {
@ -189,7 +195,6 @@ export const useFindManyRecords = <
records,
loading,
error,
objectMetadataItemNotFound,
fetchMoreRecords,
};
};

View File

@ -11,19 +11,18 @@ export const useFindOneRecord = <
onCompleted,
depth,
skip,
}: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'> & {
}: ObjectMetadataItemIdentifier & {
objectRecordId: string | undefined;
onCompleted?: (data: ObjectType) => void;
skip?: boolean;
depth?: number;
}) => {
const { objectMetadataItem, objectMetadataItemNotFound, findOneRecordQuery } =
useObjectMetadataItem(
{
objectNameSingular,
},
depth,
);
const { objectMetadataItem, findOneRecordQuery } = useObjectMetadataItem(
{
objectNameSingular,
},
depth,
);
const { data, loading, error } = useQuery<
{ [nameSingular: string]: ObjectType },
@ -34,19 +33,17 @@ export const useFindOneRecord = <
objectRecordId: objectRecordId ?? '',
},
onCompleted: (data) => {
if (onCompleted && objectNameSingular) {
if (onCompleted) {
onCompleted(data[objectNameSingular]);
}
},
});
const record =
objectNameSingular && data ? data[objectNameSingular] : undefined;
const record = data ? data[objectNameSingular] : undefined;
return {
record,
loading,
error,
objectMetadataItemNotFound,
};
};

View File

@ -8,7 +8,7 @@ import { capitalize } from '~/utils/string/capitalize';
export const useGenerateCreateOneRecordMutation = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem | undefined | null;
objectMetadataItem: ObjectMetadataItem;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();

View File

@ -9,7 +9,7 @@ export const useGenerateFindManyRecordsQuery = ({
objectMetadataItem,
depth,
}: {
objectMetadataItem: ObjectMetadataItem | undefined | null;
objectMetadataItem: ObjectMetadataItem;
depth?: number;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();

View File

@ -8,7 +8,7 @@ export const useGenerateFindOneRecordQuery = ({
objectMetadataItem,
depth,
}: {
objectMetadataItem: ObjectMetadataItem | null | undefined;
objectMetadataItem: ObjectMetadataItem;
depth?: number;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();

View File

@ -16,7 +16,7 @@ export const getUpdateOneRecordMutationGraphQLField = ({
export const useGenerateUpdateOneRecordMutation = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem | undefined | null;
objectMetadataItem: ObjectMetadataItem;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();

View File

@ -8,7 +8,7 @@ import { capitalize } from '~/utils/string/capitalize';
export const useGetRecordFromCache = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem | undefined | null;
objectMetadataItem: ObjectMetadataItem;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const apolloClient = useApolloClient();
@ -31,7 +31,7 @@ export const useGetRecordFromCache = ({
const cache = apolloClient.cache;
const cachedRecordId = cache.identify({
__typename: capitalize(objectMetadataItem?.nameSingular ?? ''),
__typename: capitalize(objectMetadataItem.nameSingular),
id: recordId,
});

View File

@ -8,7 +8,7 @@ import { capitalize } from '~/utils/string/capitalize';
export const useModifyRecordFromCache = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem | undefined | null;
objectMetadataItem: ObjectMetadataItem;
}) => {
const apolloClient = useApolloClient();
@ -19,7 +19,7 @@ export const useModifyRecordFromCache = ({
const cache = apolloClient.cache;
const cachedRecordId = cache.identify({
__typename: capitalize(objectMetadataItem?.nameSingular ?? ''),
__typename: capitalize(objectMetadataItem.nameSingular),
id: recordId,
});

View File

@ -2,6 +2,7 @@ import { useRecoilValue } from 'recoil';
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { turnFiltersIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause';
import { turnSortsIntoOrderBy } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates';
@ -14,14 +15,18 @@ import { useFindManyRecords } from './useFindManyRecords';
export const useObjectRecordTable = () => {
const { scopeId: objectNamePlural, setRecordTableData } = useRecordTable();
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
{
objectNamePlural,
objectNameSingular,
},
);
const { registerOptimisticEffect } = useOptimisticEffect({
objectNameSingular: foundObjectMetadataItem?.nameSingular,
objectNameSingular,
});
const { tableFiltersState, tableSortsState } = useRecordTableScopedStates();
@ -39,7 +44,7 @@ export const useObjectRecordTable = () => {
);
const { records, loading, fetchMoreRecords } = useFindManyRecords({
objectNamePlural,
objectNameSingular,
filter,
orderBy,
onCompleted: (data) => {

View File

@ -3,7 +3,7 @@ import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { IconHeart, IconHeartOff, IconTrash } from '@/ui/display/icon';
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
@ -38,7 +38,7 @@ export const useRecordTableContextMenuEntries = (
recordTableScopeId: scopeId,
});
const { objectMetadataItem } = useObjectMetadataItem({
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
@ -67,7 +67,7 @@ export const useRecordTableContextMenuEntries = (
});
const { deleteOneRecord } = useDeleteOneRecord({
objectNameSingular: objectMetadataItem?.nameSingular,
objectNameSingular,
});
const handleDeleteClick = useRecoilCallback(({ snapshot }) => async () => {

View File

@ -7,10 +7,9 @@ import { capitalize } from '~/utils/string/capitalize';
export const useUpdateOneRecord = <T>({
objectNameSingular,
}: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'>) => {
}: ObjectMetadataItemIdentifier) => {
const {
objectMetadataItem,
objectMetadataItemNotFound,
updateOneRecordMutation,
getRecordFromCache,
findManyRecordsQuery,
@ -29,10 +28,6 @@ export const useUpdateOneRecord = <T>({
input: Record<string, any>;
forceRefetch?: boolean;
}) => {
if (!objectMetadataItem || !objectNameSingular) {
return null;
}
const cachedRecord = getRecordFromCache(idToUpdate);
const updatedRecord = await mutateUpdateOneRecord({
@ -43,7 +38,7 @@ export const useUpdateOneRecord = <T>({
},
},
optimisticResponse: {
[`update${capitalize(objectNameSingular)}`]: {
[`update${capitalize(objectMetadataItem.nameSingular)}`]: {
...(cachedRecord ?? {}),
...input,
},
@ -54,11 +49,12 @@ export const useUpdateOneRecord = <T>({
awaitRefetchQueries: forceRefetch,
});
return updatedRecord.data[`update${capitalize(objectNameSingular)}`] as T;
return updatedRecord.data[
`update${capitalize(objectMetadataItem.nameSingular)}`
] as T;
};
return {
updateOneRecord,
objectMetadataItemNotFound,
};
};

View File

@ -1,9 +1,13 @@
export type PaginatedRecordTypeEdge<RecordType extends { id: string }> = {
export type PaginatedRecordTypeEdge<
RecordType extends { id: string } & Record<string, any>,
> = {
node: RecordType;
cursor: string;
};
export type PaginatedRecordTypeResults<RecordType extends { id: string }> = {
export type PaginatedRecordTypeResults<
RecordType extends { id: string } & Record<string, any>,
> = {
__typename?: string;
edges: PaginatedRecordTypeEdge<RecordType>[];
pageInfo: {

View File

@ -7,13 +7,13 @@ import { capitalize } from '~/utils/string/capitalize';
export const generateDeleteOneRecordMutation = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem | undefined | null;
objectMetadataItem: ObjectMetadataItem;
}) => {
if (!objectMetadataItem) {
return EMPTY_MUTATION;
}
const capitalizedObjectName = capitalize(objectMetadataItem?.nameSingular);
const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular);
return gql`
mutation DeleteOne${capitalizedObjectName}($idToDelete: ID!) {