Sync metadata store after view child entity mutations server response (#23150)

## Summary

- View persist hooks
(`usePerformView(Sort|Field|Filter|Group|FieldGroup|FilterGroup|)APIPersist`)
now write successful mutation results back to the metadata store
(`addToDraft` / `updateInDraft` / `removeFromDraft` + `applyChanges`),
following the existing pattern from `usePerformViewAPIUpdate`.
- Previously the store only updated via SSE, so until that landed, save
flows diffed against stale view data and re-sent creates with the same
id — failing server-side with a duplicate key error.

Fixes TWENTY-SERVER-HQM
This commit is contained in:
Paul Rastoin
2026-07-22 19:05:22 +02:00
committed by GitHub
parent f52643d170
commit 987995636f
14 changed files with 584 additions and 913 deletions
File diff suppressed because one or more lines are too long
@@ -1,11 +0,0 @@
import { VIEW_FIELD_GROUP_FRAGMENT } from '@/views/graphql/fragments/viewFieldGroupFragment';
import { gql } from '@apollo/client';
export const CREATE_MANY_VIEW_FIELD_GROUPS = gql`
${VIEW_FIELD_GROUP_FRAGMENT}
mutation CreateManyViewFieldGroups($inputs: [CreateViewFieldGroupInput!]!) {
createManyViewFieldGroups(inputs: $inputs) {
...ViewFieldGroupFragment
}
}
`;
@@ -1,11 +0,0 @@
import { VIEW_FIELD_GROUP_FRAGMENT } from '@/views/graphql/fragments/viewFieldGroupFragment';
import { gql } from '@apollo/client';
export const DELETE_VIEW_FIELD_GROUP = gql`
${VIEW_FIELD_GROUP_FRAGMENT}
mutation DeleteViewFieldGroup($input: DeleteViewFieldGroupInput!) {
deleteViewFieldGroup(input: $input) {
...ViewFieldGroupFragment
}
}
`;
@@ -1,11 +0,0 @@
import { VIEW_FIELD_GROUP_FRAGMENT } from '@/views/graphql/fragments/viewFieldGroupFragment';
import { gql } from '@apollo/client';
export const UPDATE_VIEW_FIELD_GROUP = gql`
${VIEW_FIELD_GROUP_FRAGMENT}
mutation UpdateViewFieldGroup($input: UpdateViewFieldGroupInput!) {
updateViewFieldGroup(input: $input) {
...ViewFieldGroupFragment
}
}
`;
@@ -1,21 +1,20 @@
import { useCallback } from 'react';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { type FlatView } from '@/metadata-store/types/FlatView';
import { type FlatViewGroup } from '@/metadata-store/types/FlatViewGroup';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { usePerformViewEntityAPIPersistOperation } from '@/views/hooks/internal/usePerformViewEntityAPIPersistOperation';
import { useViewsSideEffectsOnViewGroups } from '@/views/hooks/useViewsSideEffectsOnViewGroups';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
import { t } from '@lingui/core/macro';
import { useMutation } from '@apollo/client/react';
import { CrudOperationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { useMutation } from '@apollo/client/react';
import {
CreateViewDocument,
type CreateViewMutationVariables,
DestroyViewDocument,
type DestroyViewMutationVariables,
ViewType,
CreateViewDocument,
DestroyViewDocument,
} from '~/generated-metadata/graphql';
export const usePerformViewAPIPersist = () => {
@@ -24,8 +23,8 @@ export const usePerformViewAPIPersist = () => {
const { triggerViewGroupOptimisticEffectAtViewCreation } =
useViewsSideEffectsOnViewGroups();
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const { performViewEntityAPIPersistOperation } =
usePerformViewEntityAPIPersistOperation('view');
const performViewAPICreate = useCallback(
async (
@@ -34,59 +33,64 @@ export const usePerformViewAPIPersist = () => {
): Promise<
MetadataRequestResult<Awaited<ReturnType<typeof createViewMutation>>>
> => {
try {
const newViewId = variables.input.id ?? v4();
if (variables.input.type === ViewType.KANBAN) {
triggerViewGroupOptimisticEffectAtViewCreation({
objectMetadataItemId: objectMetadataItemId,
mainGroupByFieldMetadataId:
variables.input.mainGroupByFieldMetadataId,
});
}
const result = await performViewEntityAPIPersistOperation({
persist: () => {
const newViewId = variables.input.id ?? v4();
if (variables.input.type === ViewType.KANBAN) {
triggerViewGroupOptimisticEffectAtViewCreation({
objectMetadataItemId: objectMetadataItemId,
mainGroupByFieldMetadataId:
variables.input.mainGroupByFieldMetadataId,
});
}
const result = await createViewMutation({
variables: {
input: {
...variables.input,
id: newViewId,
return createViewMutation({
variables: {
input: {
...variables.input,
id: newViewId,
},
},
},
});
const newView = result.data?.createView;
if (!isDefined(newView)) {
return {
status: 'failed',
error: new Error('Failed to create view'),
};
}
return {
status: 'successful',
response: result,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'view',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
},
applyResultToDraft: (mutationResult, { addToDraft }) => {
const newView = mutationResult.data?.createView;
return {
status: 'failed',
error,
};
}
if (!isDefined(newView)) {
throw new Error('Failed to create view');
}
const {
__typename,
viewFields: _viewFields,
viewFieldGroups: _viewFieldGroups,
viewFilters: _viewFilters,
viewFilterGroups: _viewFilterGroups,
viewSorts: _viewSorts,
viewGroups,
...flatView
} = newView;
addToDraft({ key: 'views', items: [flatView as FlatView] });
// The server auto-creates viewGroups for Kanban views (mainGroupByFieldMetadataId)
addToDraft({
key: 'viewGroups',
items: viewGroups.map(
({ __typename: _viewGroupTypename, ...viewGroup }) =>
viewGroup as FlatViewGroup,
),
});
},
operationType: CrudOperationType.CREATE,
});
return result;
},
[
createViewMutation,
triggerViewGroupOptimisticEffectAtViewCreation,
handleMetadataError,
enqueueErrorSnackBar,
performViewEntityAPIPersistOperation,
],
);
@@ -95,33 +99,17 @@ export const usePerformViewAPIPersist = () => {
variables: DestroyViewMutationVariables,
): Promise<
MetadataRequestResult<Awaited<ReturnType<typeof destroyViewMutation>>>
> => {
try {
const result = await destroyViewMutation({
variables,
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'view',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[destroyViewMutation, handleMetadataError, enqueueErrorSnackBar],
> =>
performViewEntityAPIPersistOperation({
persist: () =>
destroyViewMutation({
variables,
}),
applyResultToDraft: (_result, { removeFromDraft }) =>
removeFromDraft({ key: 'views', itemIds: [variables.id] }),
operationType: CrudOperationType.DELETE,
}),
[destroyViewMutation, performViewEntityAPIPersistOperation],
);
return {
@@ -0,0 +1,165 @@
import { useCallback } from 'react';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { useUpdateMetadataStoreDraft } from '@/metadata-store/hooks/useUpdateMetadataStoreDraft';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
import { t } from '@lingui/core/macro';
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type CrudOperationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
type MetadataStoreDraftUtils = Pick<
ReturnType<typeof useUpdateMetadataStoreDraft>,
'addToDraft' | 'updateInDraft' | 'removeFromDraft'
>;
type PerformViewEntityAPIPersistOperationArgs<TResponse> = {
persist: () => Promise<TResponse>;
applyResultToDraft: (
response: TResponse,
draftUtils: MetadataStoreDraftUtils,
) => void;
operationType: CrudOperationType;
};
type PerformViewEntityAPIPersistBatchOperationArgs<TInput, TResult> = {
inputs: TInput[];
mutate: (input: TInput) => Promise<TResult>;
applyResultToDraft: (
fulfilledMutations: { input: TInput; result: TResult }[],
draftUtils: MetadataStoreDraftUtils,
) => void;
operationType: CrudOperationType;
};
export const usePerformViewEntityAPIPersistOperation = (
metadataName: AllMetadataName,
) => {
const { addToDraft, updateInDraft, removeFromDraft, applyChanges } =
useUpdateMetadataStoreDraft();
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const handlePersistError = useCallback(
(error: unknown, operationType: CrudOperationType) => {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: metadataName,
operationType,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
},
[handleMetadataError, enqueueErrorSnackBar, metadataName],
);
const performViewEntityAPIPersistOperation = useCallback(
async <TResponse>({
persist,
applyResultToDraft,
operationType,
}: PerformViewEntityAPIPersistOperationArgs<TResponse>): Promise<
MetadataRequestResult<TResponse>
> => {
try {
const response = await persist();
applyResultToDraft(response, {
addToDraft,
updateInDraft,
removeFromDraft,
});
applyChanges();
return {
status: 'successful',
response,
};
} catch (error) {
handlePersistError(error, operationType);
return {
status: 'failed',
error,
};
}
},
[
addToDraft,
updateInDraft,
removeFromDraft,
applyChanges,
handlePersistError,
],
);
const performViewEntityAPIPersistBatchOperation = useCallback(
async <TInput, TResult>({
inputs,
mutate,
applyResultToDraft,
operationType,
}: PerformViewEntityAPIPersistBatchOperationArgs<TInput, TResult>): Promise<
MetadataRequestResult<TResult[]>
> => {
if (inputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
const settledMutations = await Promise.allSettled(inputs.map(mutate));
const fulfilledMutations = settledMutations.flatMap(
(settledMutation, index) =>
settledMutation.status === 'fulfilled'
? [{ input: inputs[index], result: settledMutation.value }]
: [],
);
applyResultToDraft(fulfilledMutations, {
addToDraft,
updateInDraft,
removeFromDraft,
});
applyChanges();
const firstRejectedMutation = settledMutations.find(
(settledMutation) => settledMutation.status === 'rejected',
);
if (isDefined(firstRejectedMutation)) {
const error = firstRejectedMutation.reason;
handlePersistError(error, operationType);
return {
status: 'failed',
error,
};
}
return {
status: 'successful',
response: fulfilledMutations.map(({ result }) => result),
};
},
[
addToDraft,
updateInDraft,
removeFromDraft,
applyChanges,
handlePersistError,
],
);
return {
performViewEntityAPIPersistOperation,
performViewEntityAPIPersistBatchOperation,
};
};
@@ -1,12 +1,11 @@
import { useCallback } from 'react';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { type FlatViewField } from '@/metadata-store/types/FlatViewField';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
import { usePerformViewEntityAPIPersistOperation } from '@/views/hooks/internal/usePerformViewEntityAPIPersistOperation';
import { useMutation } from '@apollo/client/react';
import { CrudOperationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type CreateManyViewFieldsMutationVariables,
type DeleteViewFieldMutationVariables,
@@ -26,8 +25,10 @@ export const usePerformViewFieldAPIPersist = () => {
const [deleteViewFieldMutation] = useMutation(DeleteViewFieldDocument);
const [destroyViewFieldMutation] = useMutation(DestroyViewFieldDocument);
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const {
performViewEntityAPIPersistOperation,
performViewEntityAPIPersistBatchOperation,
} = usePerformViewEntityAPIPersistOperation('viewField');
const performViewFieldAPICreate = useCallback(
async (
@@ -47,79 +48,48 @@ export const usePerformViewFieldAPIPersist = () => {
};
}
try {
const result = await createManyViewFieldsMutation({
variables: createViewFieldInputs,
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewField',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
return performViewEntityAPIPersistOperation({
persist: () =>
createManyViewFieldsMutation({
variables: createViewFieldInputs,
}),
applyResultToDraft: (result, { addToDraft }) =>
addToDraft({
key: 'viewFields',
items: (result.data?.createManyViewFields ?? []).map(
({ __typename, ...viewField }) => viewField as FlatViewField,
),
}),
operationType: CrudOperationType.CREATE,
});
},
[createManyViewFieldsMutation, handleMetadataError, enqueueErrorSnackBar],
[createManyViewFieldsMutation, performViewEntityAPIPersistOperation],
);
const performViewFieldAPIUpdate = useCallback(
async (
createViewFieldInputs: UpdateViewFieldMutationVariables[],
updateViewFieldInputs: UpdateViewFieldMutationVariables[],
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof updateViewFieldMutation>>[]
>
> => {
if (createViewFieldInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
createViewFieldInputs.map((variables) =>
updateViewFieldMutation({
variables,
}),
> =>
performViewEntityAPIPersistBatchOperation({
inputs: updateViewFieldInputs,
mutate: (variables) => updateViewFieldMutation({ variables }),
applyResultToDraft: (fulfilledMutations, { updateInDraft }) =>
updateInDraft(
'viewFields',
fulfilledMutations
.map(({ result }) => result.data?.updateViewField)
.filter(isDefined)
.map(
({ __typename, ...viewField }) => viewField as FlatViewField,
),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewField',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[updateViewFieldMutation, handleMetadataError, enqueueErrorSnackBar],
operationType: CrudOperationType.UPDATE,
}),
[updateViewFieldMutation, performViewEntityAPIPersistBatchOperation],
);
const performViewFieldAPIDelete = useCallback(
@@ -129,44 +99,18 @@ export const usePerformViewFieldAPIPersist = () => {
MetadataRequestResult<
Awaited<ReturnType<typeof deleteViewFieldMutation>>[]
>
> => {
if (deleteViewFieldInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
deleteViewFieldInputs.map((variables) =>
deleteViewFieldMutation({
variables,
}),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewField',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[deleteViewFieldMutation, handleMetadataError, enqueueErrorSnackBar],
> =>
performViewEntityAPIPersistBatchOperation({
inputs: deleteViewFieldInputs,
mutate: (variables) => deleteViewFieldMutation({ variables }),
applyResultToDraft: (fulfilledMutations, { removeFromDraft }) =>
removeFromDraft({
key: 'viewFields',
itemIds: fulfilledMutations.map(({ input }) => input.input.id),
}),
operationType: CrudOperationType.DELETE,
}),
[deleteViewFieldMutation, performViewEntityAPIPersistBatchOperation],
);
const performViewFieldAPIDestroy = useCallback(
@@ -176,44 +120,18 @@ export const usePerformViewFieldAPIPersist = () => {
MetadataRequestResult<
Awaited<ReturnType<typeof destroyViewFieldMutation>>[]
>
> => {
if (destroyViewFieldInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
destroyViewFieldInputs.map((variables) =>
destroyViewFieldMutation({
variables,
}),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewField',
operationType: CrudOperationType.DESTROY,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[destroyViewFieldMutation, handleMetadataError, enqueueErrorSnackBar],
> =>
performViewEntityAPIPersistBatchOperation({
inputs: destroyViewFieldInputs,
mutate: (variables) => destroyViewFieldMutation({ variables }),
applyResultToDraft: (fulfilledMutations, { removeFromDraft }) =>
removeFromDraft({
key: 'viewFields',
itemIds: fulfilledMutations.map(({ input }) => input.input.id),
}),
operationType: CrudOperationType.DESTROY,
}),
[destroyViewFieldMutation, performViewEntityAPIPersistBatchOperation],
);
return {
@@ -1,197 +0,0 @@
import { useCallback } from 'react';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { CREATE_MANY_VIEW_FIELD_GROUPS } from '@/views/graphql/mutations/createManyViewFieldGroups';
import { DELETE_VIEW_FIELD_GROUP } from '@/views/graphql/mutations/deleteViewFieldGroup';
import { UPDATE_VIEW_FIELD_GROUP } from '@/views/graphql/mutations/updateViewFieldGroup';
import { useMutation } from '@apollo/client/react';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type ViewFieldGroup,
type MutationCreateManyViewFieldGroupsArgs,
type MutationDeleteViewFieldGroupArgs,
type MutationUpdateViewFieldGroupArgs,
} from '~/generated-metadata/graphql';
type CreateManyViewFieldGroupsMutationResult = {
createManyViewFieldGroups: ViewFieldGroup[];
};
type UpdateViewFieldGroupMutationResult = {
updateViewFieldGroup: ViewFieldGroup;
};
type DeleteViewFieldGroupMutationResult = {
deleteViewFieldGroup: ViewFieldGroup;
};
export const usePerformViewFieldGroupAPIPersist = () => {
const [createManyViewFieldGroupsMutation] = useMutation<
CreateManyViewFieldGroupsMutationResult,
MutationCreateManyViewFieldGroupsArgs
>(CREATE_MANY_VIEW_FIELD_GROUPS);
const [updateViewFieldGroupMutation] = useMutation<
UpdateViewFieldGroupMutationResult,
MutationUpdateViewFieldGroupArgs
>(UPDATE_VIEW_FIELD_GROUP);
const [deleteViewFieldGroupMutation] = useMutation<
DeleteViewFieldGroupMutationResult,
MutationDeleteViewFieldGroupArgs
>(DELETE_VIEW_FIELD_GROUP);
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const performViewFieldGroupAPICreate = useCallback(
async (
createViewFieldGroupInputs: MutationCreateManyViewFieldGroupsArgs,
): Promise<
MetadataRequestResult<CreateManyViewFieldGroupsMutationResult | null>
> => {
if (
!Array.isArray(createViewFieldGroupInputs.inputs) ||
createViewFieldGroupInputs.inputs.length === 0
) {
return {
status: 'successful',
response: null,
};
}
try {
const result = await createManyViewFieldGroupsMutation({
variables: createViewFieldGroupInputs,
});
return {
status: 'successful',
response: result.data ?? null,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewFieldGroup',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[
createManyViewFieldGroupsMutation,
handleMetadataError,
enqueueErrorSnackBar,
],
);
const performViewFieldGroupAPIUpdate = useCallback(
async (
updateViewFieldGroupInputs: MutationUpdateViewFieldGroupArgs[],
): Promise<
MetadataRequestResult<UpdateViewFieldGroupMutationResult[] | null>
> => {
if (updateViewFieldGroupInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
updateViewFieldGroupInputs.map((variables) =>
updateViewFieldGroupMutation({
variables,
}),
),
);
return {
status: 'successful',
response: results
.map((r) => r.data)
.filter(isDefined) as UpdateViewFieldGroupMutationResult[],
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewFieldGroup',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[updateViewFieldGroupMutation, handleMetadataError, enqueueErrorSnackBar],
);
const performViewFieldGroupAPIDelete = useCallback(
async (
deleteViewFieldGroupInputs: MutationDeleteViewFieldGroupArgs[],
): Promise<
MetadataRequestResult<DeleteViewFieldGroupMutationResult[] | null>
> => {
if (deleteViewFieldGroupInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
deleteViewFieldGroupInputs.map((variables) =>
deleteViewFieldGroupMutation({
variables,
}),
),
);
return {
status: 'successful',
response: results
.map((r) => r.data)
.filter(isDefined) as DeleteViewFieldGroupMutationResult[],
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewFieldGroup',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[deleteViewFieldGroupMutation, handleMetadataError, enqueueErrorSnackBar],
);
return {
performViewFieldGroupAPICreate,
performViewFieldGroupAPIUpdate,
performViewFieldGroupAPIDelete,
};
};
@@ -1,31 +1,42 @@
import { useCallback } from 'react';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { type FlatViewFilter } from '@/metadata-store/types/FlatViewFilter';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
import { usePerformViewEntityAPIPersistOperation } from '@/views/hooks/internal/usePerformViewEntityAPIPersistOperation';
import { useMutation } from '@apollo/client/react';
import { CrudOperationType } from 'twenty-shared/types';
import { convertViewFilterValueToString, isDefined } from 'twenty-shared/utils';
import {
type CreateViewFilterMutationVariables,
type DeleteViewFilterMutationVariables,
type DestroyViewFilterMutationVariables,
type UpdateViewFilterMutationVariables,
type ViewFilterFragmentFragment,
CreateViewFilterDocument,
DeleteViewFilterDocument,
DestroyViewFilterDocument,
UpdateViewFilterDocument,
} from '~/generated-metadata/graphql';
// Normalize value like the fetch path (splitViewWithRelated) so store
// comparisons against string-converted values stay consistent
const toFlatViewFilter = ({
__typename,
...viewFilter
}: ViewFilterFragmentFragment): FlatViewFilter =>
({
...viewFilter,
value: convertViewFilterValueToString(viewFilter.value),
}) as FlatViewFilter;
export const usePerformViewFilterAPIPersist = () => {
const [createViewFilterMutation] = useMutation(CreateViewFilterDocument);
const [updateViewFilterMutation] = useMutation(UpdateViewFilterDocument);
const [deleteViewFilterMutation] = useMutation(DeleteViewFilterDocument);
const [destroyViewFilterMutation] = useMutation(DestroyViewFilterDocument);
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const { performViewEntityAPIPersistBatchOperation } =
usePerformViewEntityAPIPersistOperation('viewFilter');
const performViewFilterAPICreate = useCallback(
async (
@@ -34,44 +45,21 @@ export const usePerformViewFilterAPIPersist = () => {
MetadataRequestResult<
Awaited<ReturnType<typeof createViewFilterMutation>>[]
>
> => {
if (createViewFilterInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
createViewFilterInputs.map((variables) =>
createViewFilterMutation({
variables,
}),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewFilter',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[createViewFilterMutation, handleMetadataError, enqueueErrorSnackBar],
> =>
performViewEntityAPIPersistBatchOperation({
inputs: createViewFilterInputs,
mutate: (variables) => createViewFilterMutation({ variables }),
applyResultToDraft: (fulfilledMutations, { addToDraft }) =>
addToDraft({
key: 'viewFilters',
items: fulfilledMutations
.map(({ result }) => result.data?.createViewFilter)
.filter(isDefined)
.map(toFlatViewFilter),
}),
operationType: CrudOperationType.CREATE,
}),
[createViewFilterMutation, performViewEntityAPIPersistBatchOperation],
);
const performViewFilterAPIUpdate = useCallback(
@@ -81,44 +69,21 @@ export const usePerformViewFilterAPIPersist = () => {
MetadataRequestResult<
Awaited<ReturnType<typeof updateViewFilterMutation>>[]
>
> => {
if (updateViewFilterInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
updateViewFilterInputs.map((variables) =>
updateViewFilterMutation({
variables,
}),
> =>
performViewEntityAPIPersistBatchOperation({
inputs: updateViewFilterInputs,
mutate: (variables) => updateViewFilterMutation({ variables }),
applyResultToDraft: (fulfilledMutations, { updateInDraft }) =>
updateInDraft(
'viewFilters',
fulfilledMutations
.map(({ result }) => result.data?.updateViewFilter)
.filter(isDefined)
.map(toFlatViewFilter),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewFilter',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[updateViewFilterMutation, handleMetadataError, enqueueErrorSnackBar],
operationType: CrudOperationType.UPDATE,
}),
[updateViewFilterMutation, performViewEntityAPIPersistBatchOperation],
);
const performViewFilterAPIDelete = useCallback(
@@ -128,44 +93,18 @@ export const usePerformViewFilterAPIPersist = () => {
MetadataRequestResult<
Awaited<ReturnType<typeof deleteViewFilterMutation>>[]
>
> => {
if (deleteViewFilterInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
deleteViewFilterInputs.map((variables) =>
deleteViewFilterMutation({
variables,
}),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewFilter',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[deleteViewFilterMutation, handleMetadataError, enqueueErrorSnackBar],
> =>
performViewEntityAPIPersistBatchOperation({
inputs: deleteViewFilterInputs,
mutate: (variables) => deleteViewFilterMutation({ variables }),
applyResultToDraft: (fulfilledMutations, { removeFromDraft }) =>
removeFromDraft({
key: 'viewFilters',
itemIds: fulfilledMutations.map(({ input }) => input.input.id),
}),
operationType: CrudOperationType.DELETE,
}),
[deleteViewFilterMutation, performViewEntityAPIPersistBatchOperation],
);
const performViewFilterAPIDestroy = useCallback(
@@ -175,44 +114,18 @@ export const usePerformViewFilterAPIPersist = () => {
MetadataRequestResult<
Awaited<ReturnType<typeof destroyViewFilterMutation>>[]
>
> => {
if (destroyViewFilterInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
destroyViewFilterInputs.map((variables) =>
destroyViewFilterMutation({
variables,
}),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewFilter',
operationType: CrudOperationType.DESTROY,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[destroyViewFilterMutation, handleMetadataError, enqueueErrorSnackBar],
> =>
performViewEntityAPIPersistBatchOperation({
inputs: destroyViewFilterInputs,
mutate: (variables) => destroyViewFilterMutation({ variables }),
applyResultToDraft: (fulfilledMutations, { removeFromDraft }) =>
removeFromDraft({
key: 'viewFilters',
itemIds: fulfilledMutations.map(({ input }) => input.input.id),
}),
operationType: CrudOperationType.DESTROY,
}),
[destroyViewFilterMutation, performViewEntityAPIPersistBatchOperation],
);
return {
@@ -1,23 +1,38 @@
import { useCallback } from 'react';
import { CREATE_VIEW_FILTER_GROUP } from '@/views/graphql/mutations/createViewFilterGroup';
import { DESTROY_VIEW_FILTER_GROUP } from '@/views/graphql/mutations/destroyViewFilterGroup';
import { UPDATE_VIEW_FILTER_GROUP } from '@/views/graphql/mutations/updateViewFilterGroup';
import { type FlatViewFilterGroup } from '@/metadata-store/types/FlatViewFilterGroup';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { usePerformViewEntityAPIPersistOperation } from '@/views/hooks/internal/usePerformViewEntityAPIPersistOperation';
import { type GraphQLView } from '@/views/types/GraphQLView';
import { type ViewFilterGroup } from '@/views/types/ViewFilterGroup';
import { useApolloClient } from '@apollo/client/react';
import { useMutation } from '@apollo/client/react';
import { CrudOperationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type ViewFilterGroup as GqlViewFilterGroup } from '~/generated-metadata/graphql';
import {
CreateViewFilterGroupDocument,
DestroyViewFilterGroupDocument,
UpdateViewFilterGroupDocument,
} from '~/generated-metadata/graphql';
export const usePerformViewFilterGroupAPIPersist = () => {
const apolloClient = useApolloClient();
const [createViewFilterGroupMutation] = useMutation(
CreateViewFilterGroupDocument,
);
const [updateViewFilterGroupMutation] = useMutation(
UpdateViewFilterGroupDocument,
);
const [destroyViewFilterGroupMutation] = useMutation(
DestroyViewFilterGroupDocument,
);
const {
performViewEntityAPIPersistOperation,
performViewEntityAPIPersistBatchOperation,
} = usePerformViewEntityAPIPersistOperation('viewFilterGroup');
const createViewFilterGroupRecord = useCallback(
async (viewFilterGroup: ViewFilterGroup, view: Pick<GraphQLView, 'id'>) => {
const result = await apolloClient.mutate<{
createViewFilterGroup: ViewFilterGroup;
}>({
mutation: CREATE_VIEW_FILTER_GROUP,
const result = await createViewFilterGroupMutation({
variables: {
input: {
id: viewFilterGroup.id,
@@ -26,27 +41,26 @@ export const usePerformViewFilterGroupAPIPersist = () => {
logicalOperator: viewFilterGroup.logicalOperator,
positionInViewFilterGroup:
viewFilterGroup.positionInViewFilterGroup,
} satisfies Partial<GqlViewFilterGroup>,
},
},
});
if (!result.data) {
if (!isDefined(result.data)) {
throw new Error('Failed to create view filter group');
}
return { newRecordId: result.data.createViewFilterGroup.id };
return { newRecord: result.data.createViewFilterGroup };
},
[apolloClient],
[createViewFilterGroupMutation],
);
const performViewFilterGroupAPICreate = useCallback(
async (
viewFilterGroupsToCreate: ViewFilterGroup[],
view: Pick<GraphQLView, 'id'>,
) => {
if (!viewFilterGroupsToCreate.length) return [];
): Promise<MetadataRequestResult<string[]>> => {
const oldToNewId = new Map<string, string>();
const newRecordIds: string[] = [];
for (const viewFilterGroupToCreate of viewFilterGroupsToCreate) {
const newParentViewFilterGroupId = isDefined(
@@ -56,37 +70,50 @@ export const usePerformViewFilterGroupAPIPersist = () => {
viewFilterGroupToCreate.parentViewFilterGroupId)
: undefined;
const { newRecordId } = await createViewFilterGroupRecord(
{
...viewFilterGroupToCreate,
parentViewFilterGroupId: newParentViewFilterGroupId,
const result = await performViewEntityAPIPersistOperation({
persist: () =>
createViewFilterGroupRecord(
{
...viewFilterGroupToCreate,
parentViewFilterGroupId: newParentViewFilterGroupId,
},
view,
),
applyResultToDraft: ({ newRecord }, { addToDraft }) => {
const { __typename, ...viewFilterGroup } = newRecord;
return addToDraft({
key: 'viewFilterGroups',
items: [viewFilterGroup as FlatViewFilterGroup],
});
},
view,
);
operationType: CrudOperationType.CREATE,
});
oldToNewId.set(viewFilterGroupToCreate.id, newRecordId);
if (result.status === 'failed') {
return result;
}
oldToNewId.set(
viewFilterGroupToCreate.id,
result.response.newRecord.id,
);
newRecordIds.push(result.response.newRecord.id);
}
const newRecordIds = viewFilterGroupsToCreate.map((viewFilterGroup) => {
const newId = oldToNewId.get(viewFilterGroup.id);
if (!newId) {
throw new Error('Failed to create view filter group');
}
return newId;
});
return newRecordIds;
return {
status: 'successful',
response: newRecordIds,
};
},
[createViewFilterGroupRecord],
[createViewFilterGroupRecord, performViewEntityAPIPersistOperation],
);
const performViewFilterGroupAPIUpdate = useCallback(
(viewFilterGroupsToUpdate: ViewFilterGroup[]) => {
if (!viewFilterGroupsToUpdate.length) return;
return Promise.all(
viewFilterGroupsToUpdate.map((viewFilterGroup) =>
apolloClient.mutate<{ updateViewFilterGroup: ViewFilterGroup }>({
mutation: UPDATE_VIEW_FILTER_GROUP,
(viewFilterGroupsToUpdate: ViewFilterGroup[]) =>
performViewEntityAPIPersistBatchOperation({
inputs: viewFilterGroupsToUpdate,
mutate: (viewFilterGroup) =>
updateViewFilterGroupMutation({
variables: {
id: viewFilterGroup.id,
input: {
@@ -95,30 +122,43 @@ export const usePerformViewFilterGroupAPIPersist = () => {
logicalOperator: viewFilterGroup.logicalOperator,
positionInViewFilterGroup:
viewFilterGroup.positionInViewFilterGroup,
} satisfies Partial<GqlViewFilterGroup>,
},
},
}),
),
);
},
[apolloClient],
applyResultToDraft: (fulfilledMutations, { updateInDraft }) =>
updateInDraft(
'viewFilterGroups',
fulfilledMutations
.map(({ result }) => result.data?.updateViewFilterGroup)
.filter(isDefined)
.map(
({ __typename, ...viewFilterGroup }) =>
viewFilterGroup as FlatViewFilterGroup,
),
),
operationType: CrudOperationType.UPDATE,
}),
[updateViewFilterGroupMutation, performViewEntityAPIPersistBatchOperation],
);
const performViewFilterGroupAPIDestroy = useCallback(
(viewFilterGroupIdsToDestroy: string[]) => {
if (!viewFilterGroupIdsToDestroy.length) return;
return Promise.all(
viewFilterGroupIdsToDestroy.map((viewFilterGroupId) =>
apolloClient.mutate<{ destroyViewFilterGroup: ViewFilterGroup }>({
mutation: DESTROY_VIEW_FILTER_GROUP,
(viewFilterGroupIdsToDestroy: string[]) =>
performViewEntityAPIPersistBatchOperation({
inputs: viewFilterGroupIdsToDestroy,
mutate: (viewFilterGroupId) =>
destroyViewFilterGroupMutation({
variables: {
id: viewFilterGroupId,
},
}),
),
);
},
[apolloClient],
applyResultToDraft: (fulfilledMutations, { removeFromDraft }) =>
removeFromDraft({
key: 'viewFilterGroups',
itemIds: fulfilledMutations.map(({ input }) => input),
}),
operationType: CrudOperationType.DESTROY,
}),
[destroyViewFilterGroupMutation, performViewEntityAPIPersistBatchOperation],
);
return {
@@ -1,11 +1,9 @@
import { useCallback } from 'react';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { type FlatViewGroup } from '@/metadata-store/types/FlatViewGroup';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
import { usePerformViewEntityAPIPersistOperation } from '@/views/hooks/internal/usePerformViewEntityAPIPersistOperation';
import { useMutation } from '@apollo/client/react';
import { t } from '@lingui/core/macro';
import { isNonEmptyArray } from '@sniptt/guards';
import { CrudOperationType } from 'twenty-shared/types';
import {
@@ -24,8 +22,8 @@ export const usePerformViewGroupAPIPersist = () => {
CreateManyViewGroupsDocument,
);
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const { performViewEntityAPIPersistOperation } =
usePerformViewEntityAPIPersistOperation('viewGroup');
const performViewGroupAPIUpdate = useCallback(
async (
@@ -42,32 +40,22 @@ export const usePerformViewGroupAPIPersist = () => {
};
}
try {
const result = await updateManyViewGroupsMutation({
variables: updateViewGroupInputs,
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewGroup',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
return performViewEntityAPIPersistOperation({
persist: () =>
updateManyViewGroupsMutation({
variables: updateViewGroupInputs,
}),
applyResultToDraft: (result, { updateInDraft }) =>
updateInDraft(
'viewGroups',
(result.data?.updateManyViewGroups ?? []).map(
({ __typename, ...viewGroup }) => viewGroup as FlatViewGroup,
),
),
operationType: CrudOperationType.UPDATE,
});
},
[updateManyViewGroupsMutation, handleMetadataError, enqueueErrorSnackBar],
[updateManyViewGroupsMutation, performViewEntityAPIPersistOperation],
);
const performViewGroupAPICreate = useCallback(
@@ -85,32 +73,22 @@ export const usePerformViewGroupAPIPersist = () => {
};
}
try {
const result = await createManyViewGroupsMutation({
variables: createViewGroupInputs,
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewGroup',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
return performViewEntityAPIPersistOperation({
persist: () =>
createManyViewGroupsMutation({
variables: createViewGroupInputs,
}),
applyResultToDraft: (result, { addToDraft }) =>
addToDraft({
key: 'viewGroups',
items: (result.data?.createManyViewGroups ?? []).map(
({ __typename, ...viewGroup }) => viewGroup as FlatViewGroup,
),
}),
operationType: CrudOperationType.CREATE,
});
},
[createManyViewGroupsMutation, handleMetadataError, enqueueErrorSnackBar],
[createManyViewGroupsMutation, performViewEntityAPIPersistOperation],
);
return {
@@ -1,12 +1,11 @@
import { useCallback } from 'react';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { type FlatViewSort } from '@/metadata-store/types/FlatViewSort';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
import { usePerformViewEntityAPIPersistOperation } from '@/views/hooks/internal/usePerformViewEntityAPIPersistOperation';
import { useMutation } from '@apollo/client/react';
import { CrudOperationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type CreateViewSortMutationVariables,
type DeleteViewSortMutationVariables,
@@ -24,8 +23,8 @@ export const usePerformViewSortAPIPersist = () => {
const [deleteViewSortMutation] = useMutation(DeleteViewSortDocument);
const [destroyViewSortMutation] = useMutation(DestroyViewSortDocument);
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const { performViewEntityAPIPersistBatchOperation } =
usePerformViewEntityAPIPersistOperation('viewSort');
const performViewSortAPICreate = useCallback(
async (
@@ -34,43 +33,21 @@ export const usePerformViewSortAPIPersist = () => {
MetadataRequestResult<
Awaited<ReturnType<typeof createViewSortMutation>>[]
>
> => {
if (createViewSortInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
createViewSortInputs.map((variables) =>
createViewSortMutation({
variables,
}),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewSort',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[createViewSortMutation, handleMetadataError, enqueueErrorSnackBar],
> =>
performViewEntityAPIPersistBatchOperation({
inputs: createViewSortInputs,
mutate: (variables) => createViewSortMutation({ variables }),
applyResultToDraft: (fulfilledMutations, { addToDraft }) =>
addToDraft({
key: 'viewSorts',
items: fulfilledMutations
.map(({ result }) => result.data?.createViewSort)
.filter(isDefined)
.map(({ __typename, ...viewSort }) => viewSort as FlatViewSort),
}),
operationType: CrudOperationType.CREATE,
}),
[createViewSortMutation, performViewEntityAPIPersistBatchOperation],
);
const performViewSortAPIUpdate = useCallback(
@@ -80,44 +57,21 @@ export const usePerformViewSortAPIPersist = () => {
MetadataRequestResult<
Awaited<ReturnType<typeof updateViewSortMutation>>[]
>
> => {
if (updateViewSortInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
updateViewSortInputs.map((variables) =>
updateViewSortMutation({
variables,
}),
> =>
performViewEntityAPIPersistBatchOperation({
inputs: updateViewSortInputs,
mutate: (variables) => updateViewSortMutation({ variables }),
applyResultToDraft: (fulfilledMutations, { updateInDraft }) =>
updateInDraft(
'viewSorts',
fulfilledMutations
.map(({ result }) => result.data?.updateViewSort)
.filter(isDefined)
.map(({ __typename, ...viewSort }) => viewSort as FlatViewSort),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewSort',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred` });
}
return {
status: 'failed',
error,
};
}
},
[updateViewSortMutation, handleMetadataError, enqueueErrorSnackBar],
operationType: CrudOperationType.UPDATE,
}),
[updateViewSortMutation, performViewEntityAPIPersistBatchOperation],
);
const performViewSortAPIDelete = useCallback(
@@ -127,44 +81,18 @@ export const usePerformViewSortAPIPersist = () => {
MetadataRequestResult<
Awaited<ReturnType<typeof deleteViewSortMutation>>[]
>
> => {
if (deleteViewSortInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
deleteViewSortInputs.map((variables) =>
deleteViewSortMutation({
variables,
}),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewSort',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred` });
}
return {
status: 'failed',
error,
};
}
},
[deleteViewSortMutation, handleMetadataError, enqueueErrorSnackBar],
> =>
performViewEntityAPIPersistBatchOperation({
inputs: deleteViewSortInputs,
mutate: (variables) => deleteViewSortMutation({ variables }),
applyResultToDraft: (fulfilledMutations, { removeFromDraft }) =>
removeFromDraft({
key: 'viewSorts',
itemIds: fulfilledMutations.map(({ input }) => input.input.id),
}),
operationType: CrudOperationType.DELETE,
}),
[deleteViewSortMutation, performViewEntityAPIPersistBatchOperation],
);
const performViewSortAPIDestroy = useCallback(
@@ -174,44 +102,18 @@ export const usePerformViewSortAPIPersist = () => {
MetadataRequestResult<
Awaited<ReturnType<typeof destroyViewSortMutation>>[]
>
> => {
if (destroyViewSortInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
destroyViewSortInputs.map((variables) =>
destroyViewSortMutation({
variables,
}),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'viewSort',
operationType: CrudOperationType.DESTROY,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred` });
}
return {
status: 'failed',
error,
};
}
},
[destroyViewSortMutation, handleMetadataError, enqueueErrorSnackBar],
> =>
performViewEntityAPIPersistBatchOperation({
inputs: destroyViewSortInputs,
mutate: (variables) => destroyViewSortMutation({ variables }),
applyResultToDraft: (fulfilledMutations, { removeFromDraft }) =>
removeFromDraft({
key: 'viewSorts',
itemIds: fulfilledMutations.map(({ input }) => input.input.id),
}),
operationType: CrudOperationType.DESTROY,
}),
[destroyViewSortMutation, performViewEntityAPIPersistBatchOperation],
);
return {
@@ -207,9 +207,16 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
id: v4(),
}));
await performViewFilterGroupAPICreate(viewFilterGroupsToCreate, {
id: newViewId,
});
const filterGroupResult = await performViewFilterGroupAPICreate(
viewFilterGroupsToCreate,
{
id: newViewId,
},
);
if (filterGroupResult.status === 'failed') {
return undefined;
}
const createViewFilterInputs = viewFiltersToCreate.map(
(viewFilter) => ({
@@ -84,11 +84,20 @@ export const useSaveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters =
(viewFilterGroup) => viewFilterGroup.id,
);
await performViewFilterGroupAPICreate(
const createFilterGroupResult = await performViewFilterGroupAPICreate(
viewFilterGroupsToCreate,
currentView,
);
await performViewFilterGroupAPIUpdate(viewFilterGroupsToUpdate);
if (createFilterGroupResult.status === 'failed') {
return;
}
const updateFilterGroupResult = await performViewFilterGroupAPIUpdate(
viewFilterGroupsToUpdate,
);
if (updateFilterGroupResult.status === 'failed') {
return;
}
const currentViewFilters = currentView.viewFilters ?? [];
const currentRecordFilters = store.get(
@@ -181,7 +190,12 @@ export const useSaveRecordFiltersAndGroupFiltersToViewFiltersAndGroupFilters =
return;
}
await performViewFilterGroupAPIDestroy(viewFilterGroupIdsToDestroy);
const destroyFilterGroupResult = await performViewFilterGroupAPIDestroy(
viewFilterGroupIdsToDestroy,
);
if (destroyFilterGroupResult.status === 'failed') {
return;
}
// Mirror the DB cascade: remove cascade-deleted viewFilters from the store
if (viewFilterGroupIdsToDestroy.length > 0) {