Refactor twenty-front metadata api services for v2 (#15360)
# Introduction Please first review this PR initial base https://github.com/twentyhq/twenty/pull/15358 In a nutshell refactored the frontend fetchers to display v2 errors format smoothly Please note that the v2 now finished the whole validation and does fail fast anymore ( summary is hardcoded for the moment ) ```json [ { "extensions": { "code": "BAD_USER_INPUT", "errors": { "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ { "errors": [ { "code": "INVALID_FIELD_INPUT", "message": "Default value should be as quoted string", "value": "", }, { "code": "INVALID_FIELD_INPUT", "message": "Default value "" must be one of the option values", "value": "", }, ], "flatEntityMinimalInformation": { "id": Any<String>, "name": "testField", "objectMetadataId": Any<String>, }, "status": "fail", "type": "create_field", }, ], "index": [], "objectMetadata": [], "routeTrigger": [], "serverlessFunction": [], "view": [], "viewField": [], "viewFilter": [], "viewGroup": [], }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, "invalidIndex": 0, "invalidObjectMetadata": 0, "invalidRouteTrigger": 0, "invalidServerlessFunction": 0, "invalidView": 0, "invalidViewField": 0, "invalidViewFilter": 0, "invalidViewGroup": 0, "totalErrors": 0, }, "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", }, "message": "Multiple validation errors occurred while creating fields", "name": "GraphQLError", }, ] ``` ## What's done - `usePersistView` tool ( CRUD ) - renamed `usePersistViewX` tools accordingly ( no more records or core ) - Now catching a lot of before unhandled exceptions - refactored each services to handle their own exception handlers and return either the response or the error within a discriminated union record ## Result ### Primary entity error When performing an metadata operation on a given metadata, if validation errors occurs we will display each of them in a toast Here while creating an object metadata. <img width="700" height="327" alt="image" src="https://github.com/user-attachments/assets/0c33d13c-c66c-4749-af36-b253abd3449b" /> ### Related entity error Still while creating an object <img width="700" height="327" alt="image" src="https://github.com/user-attachments/assets/52607788-c4e9-470c-ac8c-23437345ee5c" /> ### Translated <img width="700" height="327" alt="image" src="https://github.com/user-attachments/assets/a7198c20-ae82-47a6-910c-761de9594672" /> ## Conclusion This PR is an extract of https://github.com/twentyhq/twenty/pull/15331 close https://github.com/twentyhq/core-team-issues/issues/1776 ## Notes - Not refactor around triggers services as they're not consumed directly by any frontend services
This commit is contained in:
+111
@@ -0,0 +1,111 @@
|
||||
import { type ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import {
|
||||
type AllMetadataName,
|
||||
WorkspaceMigrationV2ExceptionCode,
|
||||
} from 'twenty-shared/metadata';
|
||||
import { classifyMetadataError } from '../utils/classify-metadata-error.util';
|
||||
|
||||
export const useMetadataErrorHandler = () => {
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const TRANSLATED_METADATA_NAME = {
|
||||
objectMetadata: t`object`,
|
||||
fieldMetadata: t`field`,
|
||||
view: t`view`,
|
||||
viewField: t`view field`,
|
||||
viewGroup: t`view group`,
|
||||
viewFilter: t`view filter`,
|
||||
index: t`index`,
|
||||
serverlessFunction: t`serverless function`,
|
||||
cronTrigger: t`cron trigger`,
|
||||
databaseEventTrigger: t`database trigger`,
|
||||
routeTrigger: t`route trigger`,
|
||||
} as const satisfies Record<AllMetadataName, string>;
|
||||
|
||||
const handleMetadataError = useCallback(
|
||||
(
|
||||
error: ApolloError,
|
||||
options: {
|
||||
primaryMetadataName: AllMetadataName;
|
||||
},
|
||||
) => {
|
||||
const classification = classifyMetadataError({
|
||||
error,
|
||||
primaryMetadataName: options.primaryMetadataName,
|
||||
});
|
||||
|
||||
const translatedMetadataName =
|
||||
TRANSLATED_METADATA_NAME[options.primaryMetadataName];
|
||||
|
||||
switch (classification.type) {
|
||||
case 'v1':
|
||||
enqueueErrorSnackBar({ apolloError: classification.error });
|
||||
break;
|
||||
|
||||
case 'v2-validation': {
|
||||
const {
|
||||
extensions,
|
||||
primaryMetadataName,
|
||||
relatedFailingMetadataNames,
|
||||
} = classification;
|
||||
|
||||
const targetErrors = extensions.errors[primaryMetadataName] || [];
|
||||
if (targetErrors.length > 0) {
|
||||
targetErrors.forEach((entityError) => {
|
||||
entityError.errors.forEach((validationError) =>
|
||||
enqueueErrorSnackBar({
|
||||
message:
|
||||
validationError.userFriendlyMessage ??
|
||||
validationError.message,
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
targetErrors.length === 0 &&
|
||||
relatedFailingMetadataNames.length > 0
|
||||
) {
|
||||
const relatedEntityNames = relatedFailingMetadataNames
|
||||
.map((metadataName) => TRANSLATED_METADATA_NAME[metadataName])
|
||||
.join(', ');
|
||||
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to create ${translatedMetadataName}. Related ${relatedEntityNames} validation failed. Please check your configuration and try again.`,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
targetErrors.length === 0 &&
|
||||
relatedFailingMetadataNames.length === 0
|
||||
) {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to create ${translatedMetadataName}. Please try again.`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'v2-internal': {
|
||||
const { code } = classification;
|
||||
const errorMessage =
|
||||
code ===
|
||||
WorkspaceMigrationV2ExceptionCode.BUILDER_INTERNAL_SERVER_ERROR
|
||||
? t`An internal error occurred while validating your changes. Please contact support.`
|
||||
: t`An internal error occurred while applying your changes. Please contact support and try again later.`;
|
||||
|
||||
enqueueErrorSnackBar({ message: errorMessage });
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
[enqueueErrorSnackBar, TRANSLATED_METADATA_NAME],
|
||||
);
|
||||
|
||||
return {
|
||||
handleMetadataError,
|
||||
};
|
||||
};
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
import { type ApolloError } from '@apollo/client';
|
||||
import {
|
||||
type AllMetadataName,
|
||||
type MetadataValidationErrorResponse,
|
||||
WorkspaceMigrationV2ExceptionCode,
|
||||
} from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export type MetadataErrorClassification =
|
||||
| { type: 'v1'; error: ApolloError }
|
||||
| {
|
||||
type: 'v2-validation';
|
||||
extensions: MetadataValidationErrorResponse;
|
||||
primaryMetadataName: AllMetadataName;
|
||||
relatedFailingMetadataNames: AllMetadataName[];
|
||||
}
|
||||
| {
|
||||
type: 'v2-internal';
|
||||
code: WorkspaceMigrationV2ExceptionCode;
|
||||
message: string;
|
||||
};
|
||||
|
||||
const isMetadataValidationError = (
|
||||
extensions: Record<string, unknown>,
|
||||
): extensions is MetadataValidationErrorResponse =>
|
||||
extensions.code === 'METADATA_VALIDATION_FAILED';
|
||||
|
||||
const isMetadataInternalError = (
|
||||
extensions: Record<string, unknown>,
|
||||
): boolean => {
|
||||
return (
|
||||
isDefined(extensions) &&
|
||||
isDefined(extensions.subCode) &&
|
||||
(extensions.subCode ===
|
||||
WorkspaceMigrationV2ExceptionCode.BUILDER_INTERNAL_SERVER_ERROR ||
|
||||
extensions.subCode ===
|
||||
WorkspaceMigrationV2ExceptionCode.RUNNER_INTERNAL_SERVER_ERROR)
|
||||
);
|
||||
};
|
||||
|
||||
type ClassifyMetadataErrorArgs = {
|
||||
error: ApolloError;
|
||||
primaryMetadataName: AllMetadataName;
|
||||
};
|
||||
export const classifyMetadataError = ({
|
||||
error,
|
||||
primaryMetadataName,
|
||||
}: ClassifyMetadataErrorArgs): MetadataErrorClassification => {
|
||||
const extensions = error.graphQLErrors?.[0]?.extensions;
|
||||
|
||||
if (!isDefined(extensions)) {
|
||||
return { type: 'v1', error };
|
||||
}
|
||||
|
||||
if (isMetadataValidationError(extensions)) {
|
||||
const failingMetadataNames = Object.keys(extensions.errors) as [
|
||||
keyof MetadataValidationErrorResponse['errors'],
|
||||
];
|
||||
const relatedFailingMetadataNames = failingMetadataNames.filter(
|
||||
(metadataName) =>
|
||||
extensions.errors[metadataName].length > 0 &&
|
||||
metadataName !== primaryMetadataName,
|
||||
);
|
||||
|
||||
return {
|
||||
type: 'v2-validation',
|
||||
extensions,
|
||||
primaryMetadataName,
|
||||
relatedFailingMetadataNames,
|
||||
};
|
||||
}
|
||||
|
||||
if (isMetadataInternalError(extensions)) {
|
||||
return {
|
||||
type: 'v2-internal',
|
||||
code: extensions.subCode as WorkspaceMigrationV2ExceptionCode,
|
||||
message: (extensions.userFriendlyMessage as string) || error.message,
|
||||
};
|
||||
}
|
||||
|
||||
return { type: 'v1', error };
|
||||
};
|
||||
+5
-4
@@ -10,12 +10,13 @@ import {
|
||||
variables,
|
||||
} from '../__mocks__/useCreateOneObjectMetadataItem';
|
||||
|
||||
import { jestExpectSuccessfulMetadataRequestResult } from '@/object-metadata/hooks/__tests__/utils/jest-expect-metadata-request-status.util';
|
||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import { mockedUserData } from '~/testing/mock-data/users';
|
||||
import {
|
||||
query as findManyObjectMetadataItemsQuery,
|
||||
responseData as findManyObjectMetadataItemsResponseData,
|
||||
} from '../__mocks__/useFindManyObjectMetadataItems';
|
||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import { mockedUserData } from '~/testing/mock-data/users';
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
@@ -90,8 +91,8 @@ describe('useCreateOneObjectMetadataItem', () => {
|
||||
namePlural: 'viewFilters',
|
||||
nameSingular: 'viewFilter',
|
||||
});
|
||||
|
||||
expect(res.data).toEqual({ createOneObject: responseData });
|
||||
jestExpectSuccessfulMetadataRequestResult(res);
|
||||
expect(res.response).toEqual({ data: { createOneObject: responseData } });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+7
-11
@@ -1,7 +1,4 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { type ReactNode } from 'react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { useDeleteOneObjectMetadataItem } from '@/object-metadata/hooks/useDeleteOneObjectMetadataItem';
|
||||
|
||||
@@ -11,8 +8,10 @@ import {
|
||||
variables,
|
||||
} from '../__mocks__/useDeleteOneObjectMetadataItem';
|
||||
|
||||
import { jestExpectSuccessfulMetadataRequestResult } from '@/object-metadata/hooks/__tests__/utils/jest-expect-metadata-request-status.util';
|
||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import { FIND_ALL_CORE_VIEWS } from '@/views/graphql/queries/findAllCoreViews';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { mockedUserData } from '~/testing/mock-data/users';
|
||||
import { mockedCoreViewsData } from '~/testing/mock-data/views';
|
||||
import {
|
||||
@@ -65,13 +64,9 @@ const mocks = [
|
||||
},
|
||||
];
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<RecoilRoot>
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
</RecoilRoot>
|
||||
);
|
||||
const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks: mocks,
|
||||
});
|
||||
|
||||
describe('useDeleteOneObjectMetadataItem', () => {
|
||||
it('should work as expected', async () => {
|
||||
@@ -83,7 +78,8 @@ describe('useDeleteOneObjectMetadataItem', () => {
|
||||
const res =
|
||||
await result.current.deleteOneObjectMetadataItem('idToDelete');
|
||||
|
||||
expect(res.data).toEqual({ deleteOneObject: responseData });
|
||||
jestExpectSuccessfulMetadataRequestResult(res);
|
||||
expect(res.response).toEqual({ data: { deleteOneObject: responseData } });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+31
-13
@@ -15,6 +15,7 @@ import {
|
||||
variables,
|
||||
} from '../__mocks__/useFieldMetadataItem';
|
||||
|
||||
import { jestExpectSuccessfulMetadataRequestResult } from '@/object-metadata/hooks/__tests__/utils/jest-expect-metadata-request-status.util';
|
||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import { mockedUserData } from '~/testing/mock-data/users';
|
||||
import {
|
||||
@@ -25,8 +26,11 @@ import {
|
||||
jest.mock('@/object-metadata/hooks/useUpdateOneFieldMetadataItem', () => ({
|
||||
useUpdateOneFieldMetadataItem: () => ({
|
||||
updateOneFieldMetadataItem: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
updateOneField: responseData.default,
|
||||
status: 'successful',
|
||||
response: {
|
||||
data: {
|
||||
updateOneField: responseData.default,
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
@@ -140,13 +144,17 @@ describe('useFieldMetadataItem', () => {
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const res = await result.current.activateMetadataField(
|
||||
const response = await result.current.activateMetadataField(
|
||||
fieldMetadataItem.id,
|
||||
objectMetadataId,
|
||||
);
|
||||
|
||||
expect(res.data).toEqual({
|
||||
updateOneField: responseData.default,
|
||||
jestExpectSuccessfulMetadataRequestResult(response);
|
||||
|
||||
expect(response.response).toEqual({
|
||||
data: {
|
||||
updateOneField: responseData.default,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -164,9 +172,12 @@ describe('useFieldMetadataItem', () => {
|
||||
name: 'fieldName',
|
||||
isLabelSyncedWithName: true,
|
||||
});
|
||||
jestExpectSuccessfulMetadataRequestResult(res);
|
||||
|
||||
expect(res.data).toEqual({
|
||||
createOneField: responseData.createMetadataField,
|
||||
expect(res.response).toEqual({
|
||||
data: {
|
||||
createOneField: responseData.createMetadataField,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -177,12 +188,13 @@ describe('useFieldMetadataItem', () => {
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const res = await result.current.deactivateMetadataField(
|
||||
const response = await result.current.deactivateMetadataField(
|
||||
fieldMetadataItem.id,
|
||||
objectMetadataId,
|
||||
);
|
||||
|
||||
expect(res.data).toEqual({
|
||||
jestExpectSuccessfulMetadataRequestResult(response);
|
||||
expect(response.response.data).toEqual({
|
||||
updateOneField: responseData.default,
|
||||
});
|
||||
});
|
||||
@@ -198,9 +210,12 @@ describe('useFieldMetadataItem', () => {
|
||||
idToDelete: fieldMetadataItem.id,
|
||||
objectMetadataId,
|
||||
});
|
||||
jestExpectSuccessfulMetadataRequestResult(res);
|
||||
|
||||
expect(res.data).toEqual({
|
||||
deleteOneField: responseData.default,
|
||||
expect(res.response).toEqual({
|
||||
data: {
|
||||
deleteOneField: responseData.default,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -215,9 +230,12 @@ describe('useFieldMetadataItem', () => {
|
||||
idToDelete: fieldRelationMetadataItem.id,
|
||||
objectMetadataId,
|
||||
});
|
||||
jestExpectSuccessfulMetadataRequestResult(res);
|
||||
|
||||
expect(res.data).toEqual({
|
||||
deleteOneField: responseData.fieldRelation,
|
||||
expect(res.response).toEqual({
|
||||
data: {
|
||||
deleteOneField: responseData.fieldRelation,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { expect } from '@jest/globals';
|
||||
|
||||
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
|
||||
import { type SuccessfulMetadataRequestResult } from '@/object-metadata/types/SuccessfulMetadataRequestResult.type';
|
||||
|
||||
type AssertIsSuccessfulMetadataRequestResult = <T>(
|
||||
value: MetadataRequestResult<T>,
|
||||
) => asserts value is SuccessfulMetadataRequestResult<T>;
|
||||
|
||||
export const jestExpectSuccessfulMetadataRequestResult: AssertIsSuccessfulMetadataRequestResult =
|
||||
(value) => {
|
||||
expect(value.status).toBe('successful');
|
||||
};
|
||||
+46
-20
@@ -1,41 +1,67 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
|
||||
import {
|
||||
type CreateFieldInput,
|
||||
type CreateOneFieldMetadataItemMutation,
|
||||
type CreateOneFieldMetadataItemMutationVariables,
|
||||
useCreateOneFieldMetadataItemMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { CREATE_ONE_FIELD_METADATA_ITEM } from '../graphql/mutations';
|
||||
|
||||
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const useCreateOneFieldMetadataItem = () => {
|
||||
const { refreshObjectMetadataItems } =
|
||||
useRefreshObjectMetadataItems('network-only');
|
||||
|
||||
const [mutate] = useMutation<
|
||||
CreateOneFieldMetadataItemMutation,
|
||||
CreateOneFieldMetadataItemMutationVariables
|
||||
>(CREATE_ONE_FIELD_METADATA_ITEM);
|
||||
const [createOneFieldMetadataItemMutation] =
|
||||
useCreateOneFieldMetadataItemMutation();
|
||||
|
||||
const { refreshCoreViewsByObjectMetadataId } =
|
||||
useRefreshCoreViewsByObjectMetadataId();
|
||||
|
||||
const createOneFieldMetadataItem = async (input: CreateFieldInput) => {
|
||||
const result = await mutate({
|
||||
variables: {
|
||||
input: {
|
||||
field: input,
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const createOneFieldMetadataItem = async (
|
||||
input: CreateFieldInput,
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof createOneFieldMetadataItemMutation>>
|
||||
>
|
||||
> => {
|
||||
try {
|
||||
const response = await createOneFieldMetadataItemMutation({
|
||||
variables: {
|
||||
input: {
|
||||
field: input,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await refreshObjectMetadataItems();
|
||||
await refreshObjectMetadataItems();
|
||||
|
||||
await refreshCoreViewsByObjectMetadataId(input.objectMetadataId);
|
||||
return result;
|
||||
await refreshCoreViewsByObjectMetadataId(input.objectMetadataId);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'fieldMetadata',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
+48
-23
@@ -1,15 +1,15 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
|
||||
import {
|
||||
type CreateObjectInput,
|
||||
type CreateOneObjectMetadataItemMutation,
|
||||
type CreateOneObjectMetadataItemMutationVariables,
|
||||
useCreateOneObjectMetadataItemMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { CREATE_ONE_OBJECT_METADATA_ITEM } from '../graphql/mutations';
|
||||
|
||||
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useCreateOneObjectMetadataItem = () => {
|
||||
@@ -18,27 +18,52 @@ export const useCreateOneObjectMetadataItem = () => {
|
||||
const { refreshCoreViewsByObjectMetadataId } =
|
||||
useRefreshCoreViewsByObjectMetadataId();
|
||||
|
||||
const [mutate] = useMutation<
|
||||
CreateOneObjectMetadataItemMutation,
|
||||
CreateOneObjectMetadataItemMutationVariables
|
||||
>(CREATE_ONE_OBJECT_METADATA_ITEM);
|
||||
const [createOneObjectMetadataItemMutation] =
|
||||
useCreateOneObjectMetadataItemMutation();
|
||||
|
||||
const createOneObjectMetadataItem = async (input: CreateObjectInput) => {
|
||||
const createdObjectMetadata = await mutate({
|
||||
variables: {
|
||||
input: { object: input },
|
||||
},
|
||||
});
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
await refreshObjectMetadataItems();
|
||||
const createOneObjectMetadataItem = async (
|
||||
input: CreateObjectInput,
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof createOneObjectMetadataItemMutation>>
|
||||
>
|
||||
> => {
|
||||
try {
|
||||
const createdObjectMetadata = await createOneObjectMetadataItemMutation({
|
||||
variables: {
|
||||
input: { object: input },
|
||||
},
|
||||
});
|
||||
|
||||
if (isDefined(createdObjectMetadata.data?.createOneObject?.id)) {
|
||||
await refreshCoreViewsByObjectMetadataId(
|
||||
createdObjectMetadata.data.createOneObject.id,
|
||||
);
|
||||
await refreshObjectMetadataItems();
|
||||
|
||||
if (isDefined(createdObjectMetadata.data?.createOneObject?.id)) {
|
||||
await refreshCoreViewsByObjectMetadataId(
|
||||
createdObjectMetadata.data.createOneObject.id,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: createdObjectMetadata,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'objectMetadata',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
return createdObjectMetadata;
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
+45
-23
@@ -1,35 +1,35 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
|
||||
import {
|
||||
type DeleteOneFieldMetadataItemMutation,
|
||||
type DeleteOneFieldMetadataItemMutationVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useDeleteOneFieldMetadataItemMutation } from '~/generated-metadata/graphql';
|
||||
|
||||
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
|
||||
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { DELETE_ONE_FIELD_METADATA_ITEM } from '../graphql/mutations';
|
||||
|
||||
export const useDeleteOneFieldMetadataItem = () => {
|
||||
const [mutate] = useMutation<
|
||||
DeleteOneFieldMetadataItemMutation,
|
||||
DeleteOneFieldMetadataItemMutationVariables
|
||||
>(DELETE_ONE_FIELD_METADATA_ITEM);
|
||||
const [deleteOneFieldMetadataItemMutation] =
|
||||
useDeleteOneFieldMetadataItemMutation();
|
||||
|
||||
const { refreshObjectMetadataItems } =
|
||||
useRefreshObjectMetadataItems('network-only');
|
||||
const { refreshCoreViewsByObjectMetadataId } =
|
||||
useRefreshCoreViewsByObjectMetadataId();
|
||||
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const [
|
||||
recordIndexKanbanAggregateOperation,
|
||||
setRecordIndexKanbanAggregateOperation,
|
||||
] = useRecoilState(recordIndexKanbanAggregateOperationState);
|
||||
|
||||
const resetRecordIndexKanbanAggregateOperation = async (
|
||||
idToDelete: DeleteOneFieldMetadataItemMutationVariables['idToDelete'],
|
||||
idToDelete: string,
|
||||
) => {
|
||||
if (recordIndexKanbanAggregateOperation?.fieldMetadataId === idToDelete) {
|
||||
setRecordIndexKanbanAggregateOperation({
|
||||
@@ -43,21 +43,43 @@ export const useDeleteOneFieldMetadataItem = () => {
|
||||
idToDelete,
|
||||
objectMetadataId,
|
||||
}: {
|
||||
idToDelete: DeleteOneFieldMetadataItemMutationVariables['idToDelete'];
|
||||
idToDelete: string;
|
||||
objectMetadataId: string;
|
||||
}) => {
|
||||
const result = await mutate({
|
||||
variables: {
|
||||
idToDelete,
|
||||
},
|
||||
});
|
||||
}): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof deleteOneFieldMetadataItemMutation>>
|
||||
>
|
||||
> => {
|
||||
try {
|
||||
const response = await deleteOneFieldMetadataItemMutation({
|
||||
variables: {
|
||||
idToDelete,
|
||||
},
|
||||
});
|
||||
|
||||
await resetRecordIndexKanbanAggregateOperation(idToDelete);
|
||||
await resetRecordIndexKanbanAggregateOperation(idToDelete);
|
||||
|
||||
await refreshObjectMetadataItems();
|
||||
await refreshCoreViewsByObjectMetadataId(objectMetadataId);
|
||||
await refreshObjectMetadataItems();
|
||||
await refreshCoreViewsByObjectMetadataId(objectMetadataId);
|
||||
|
||||
return result;
|
||||
return {
|
||||
status: 'successful',
|
||||
response,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'fieldMetadata',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
+43
-22
@@ -1,39 +1,60 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
|
||||
import {
|
||||
type DeleteOneObjectMetadataItemMutation,
|
||||
type DeleteOneObjectMetadataItemMutationVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { DELETE_ONE_OBJECT_METADATA_ITEM } from '../graphql/mutations';
|
||||
import { useDeleteOneObjectMetadataItemMutation } from '~/generated-metadata/graphql';
|
||||
|
||||
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useRefreshAllCoreViews } from '@/views/hooks/useRefreshAllCoreViews';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const useDeleteOneObjectMetadataItem = () => {
|
||||
const [mutate] = useMutation<
|
||||
DeleteOneObjectMetadataItemMutation,
|
||||
DeleteOneObjectMetadataItemMutationVariables
|
||||
>(DELETE_ONE_OBJECT_METADATA_ITEM);
|
||||
const [deleteOneObjectMetadataItemMutation] =
|
||||
useDeleteOneObjectMetadataItemMutation();
|
||||
|
||||
const { refreshObjectMetadataItems } =
|
||||
useRefreshObjectMetadataItems('network-only');
|
||||
|
||||
const { refreshAllCoreViews } = useRefreshAllCoreViews();
|
||||
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const deleteOneObjectMetadataItem = async (
|
||||
idToDelete: DeleteOneObjectMetadataItemMutationVariables['idToDelete'],
|
||||
) => {
|
||||
const result = await mutate({
|
||||
variables: {
|
||||
idToDelete,
|
||||
},
|
||||
});
|
||||
idToDelete: string,
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof deleteOneObjectMetadataItemMutation>>
|
||||
>
|
||||
> => {
|
||||
try {
|
||||
const response = await deleteOneObjectMetadataItemMutation({
|
||||
variables: {
|
||||
idToDelete,
|
||||
},
|
||||
});
|
||||
|
||||
await refreshObjectMetadataItems();
|
||||
await refreshAllCoreViews();
|
||||
await refreshObjectMetadataItems();
|
||||
await refreshAllCoreViews();
|
||||
|
||||
return result;
|
||||
return {
|
||||
status: 'successful',
|
||||
response,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'objectMetadata',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
+44
-19
@@ -1,14 +1,15 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
|
||||
import {
|
||||
type UpdateOneFieldMetadataItemMutation,
|
||||
type UpdateOneFieldMetadataItemMutationVariables,
|
||||
useUpdateOneFieldMetadataItemMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { UPDATE_ONE_FIELD_METADATA_ITEM } from '../graphql/mutations';
|
||||
|
||||
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const useUpdateOneFieldMetadataItem = () => {
|
||||
const { refreshObjectMetadataItems } =
|
||||
@@ -17,10 +18,12 @@ export const useUpdateOneFieldMetadataItem = () => {
|
||||
const { refreshCoreViewsByObjectMetadataId } =
|
||||
useRefreshCoreViewsByObjectMetadataId();
|
||||
|
||||
const [mutate] = useMutation<
|
||||
UpdateOneFieldMetadataItemMutation,
|
||||
UpdateOneFieldMetadataItemMutationVariables
|
||||
>(UPDATE_ONE_FIELD_METADATA_ITEM);
|
||||
const [updateOneFieldMetadataItemMutation] =
|
||||
useUpdateOneFieldMetadataItemMutation();
|
||||
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const updateOneFieldMetadataItem = async ({
|
||||
objectMetadataId,
|
||||
@@ -40,18 +43,40 @@ export const useUpdateOneFieldMetadataItem = () => {
|
||||
| 'options'
|
||||
| 'isLabelSyncedWithName'
|
||||
>;
|
||||
}) => {
|
||||
const result = await mutate({
|
||||
variables: {
|
||||
idToUpdate: fieldMetadataIdToUpdate,
|
||||
updatePayload: updatePayload,
|
||||
},
|
||||
});
|
||||
}): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof updateOneFieldMetadataItemMutation>>
|
||||
>
|
||||
> => {
|
||||
try {
|
||||
const response = await updateOneFieldMetadataItemMutation({
|
||||
variables: {
|
||||
idToUpdate: fieldMetadataIdToUpdate,
|
||||
updatePayload: updatePayload,
|
||||
},
|
||||
});
|
||||
|
||||
await refreshObjectMetadataItems();
|
||||
await refreshCoreViewsByObjectMetadataId(objectMetadataId);
|
||||
await refreshObjectMetadataItems();
|
||||
await refreshCoreViewsByObjectMetadataId(objectMetadataId);
|
||||
|
||||
return result;
|
||||
return {
|
||||
status: 'successful',
|
||||
response,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'fieldMetadata',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
+43
-20
@@ -1,22 +1,20 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
|
||||
import {
|
||||
useUpdateOneObjectMetadataItemMutation,
|
||||
type UpdateOneObjectInput,
|
||||
type UpdateOneObjectMetadataItemMutation,
|
||||
type UpdateOneObjectMetadataItemMutationVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { UPDATE_ONE_OBJECT_METADATA_ITEM } from '../graphql/mutations';
|
||||
|
||||
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
// TODO: Slice the Apollo store synchronously in the update function instead of subscribing, so we can use update after read in the same function call
|
||||
export const useUpdateOneObjectMetadataItem = () => {
|
||||
const [mutate, { loading }] = useMutation<
|
||||
UpdateOneObjectMetadataItemMutation,
|
||||
UpdateOneObjectMetadataItemMutationVariables
|
||||
>(UPDATE_ONE_OBJECT_METADATA_ITEM);
|
||||
const [updateOneObjectMetadataItemMutation, { loading }] =
|
||||
useUpdateOneObjectMetadataItemMutation();
|
||||
|
||||
const { refreshObjectMetadataItems } =
|
||||
useRefreshObjectMetadataItems('network-only');
|
||||
@@ -24,24 +22,49 @@ export const useUpdateOneObjectMetadataItem = () => {
|
||||
const { refreshCoreViewsByObjectMetadataId } =
|
||||
useRefreshCoreViewsByObjectMetadataId();
|
||||
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const updateOneObjectMetadataItem = async ({
|
||||
idToUpdate,
|
||||
updatePayload,
|
||||
}: {
|
||||
idToUpdate: UpdateOneObjectInput['id'];
|
||||
updatePayload: UpdateOneObjectInput['update'];
|
||||
}) => {
|
||||
const result = await mutate({
|
||||
variables: {
|
||||
idToUpdate,
|
||||
updatePayload,
|
||||
},
|
||||
});
|
||||
}): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof updateOneObjectMetadataItemMutation>>
|
||||
>
|
||||
> => {
|
||||
try {
|
||||
const response = await updateOneObjectMetadataItemMutation({
|
||||
variables: {
|
||||
idToUpdate,
|
||||
updatePayload,
|
||||
},
|
||||
});
|
||||
|
||||
await refreshObjectMetadataItems();
|
||||
await refreshCoreViewsByObjectMetadataId(idToUpdate);
|
||||
await refreshObjectMetadataItems();
|
||||
await refreshCoreViewsByObjectMetadataId(idToUpdate);
|
||||
|
||||
return result;
|
||||
return {
|
||||
status: 'successful',
|
||||
response,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'objectMetadata',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
export type FailedMetadataRequestResult = { status: 'failed'; error: unknown };
|
||||
@@ -0,0 +1,6 @@
|
||||
import { type FailedMetadataRequestResult } from '@/object-metadata/types/FailedMetadataRequestResult.type';
|
||||
import { type SuccessfulMetadataRequestResult } from '@/object-metadata/types/SuccessfulMetadataRequestResult.type';
|
||||
|
||||
export type MetadataRequestResult<T> =
|
||||
| FailedMetadataRequestResult
|
||||
| SuccessfulMetadataRequestResult<T>;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export type SuccessfulMetadataRequestResult<T> = {
|
||||
status: 'successful';
|
||||
response: T;
|
||||
};
|
||||
+4
-4
@@ -4,7 +4,7 @@ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/useLoadRecordIndexStates';
|
||||
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
|
||||
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroupRecords';
|
||||
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroup';
|
||||
import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { type GraphQLView } from '@/views/types/GraphQLView';
|
||||
@@ -28,7 +28,7 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
|
||||
|
||||
const { loadRecordIndexStates } = useLoadRecordIndexStates();
|
||||
|
||||
const { createViewGroupRecords } = usePersistViewGroupRecords();
|
||||
const { createViewGroups } = usePersistViewGroupRecords();
|
||||
|
||||
const { availableFieldsForCalendar } = useGetAvailableFieldsForCalendar();
|
||||
|
||||
@@ -58,7 +58,7 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
|
||||
fieldMetadataId: randomFieldForKanban,
|
||||
} satisfies ViewGroup);
|
||||
|
||||
await createViewGroupRecords(
|
||||
await createViewGroups(
|
||||
viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
|
||||
input: {
|
||||
...viewGroup,
|
||||
@@ -69,7 +69,7 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => {
|
||||
|
||||
return viewGroupsToCreate;
|
||||
},
|
||||
[objectMetadataItem, createViewGroupRecords],
|
||||
[objectMetadataItem, createViewGroups],
|
||||
);
|
||||
|
||||
const setAndPersistViewType = useRecoilCallback(
|
||||
|
||||
+8
-9
@@ -3,7 +3,7 @@ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { useSetRecordGroups } from '@/object-record/record-group/hooks/useSetRecordGroups';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroupRecords';
|
||||
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroup';
|
||||
import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState';
|
||||
import { type ViewGroup } from '@/views/types/ViewGroup';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
@@ -11,8 +11,7 @@ import { v4 } from 'uuid';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const useHandleRecordGroupField = () => {
|
||||
const { createViewGroupRecords, deleteViewGroupRecords } =
|
||||
usePersistViewGroupRecords();
|
||||
const { createViewGroups, deleteViewGroups } = usePersistViewGroupRecords();
|
||||
|
||||
const currentViewIdCallbackState = useRecoilComponentCallbackState(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
@@ -106,7 +105,7 @@ export const useHandleRecordGroupField = () => {
|
||||
);
|
||||
|
||||
if (viewGroupsToCreate.length > 0) {
|
||||
await createViewGroupRecords(
|
||||
await createViewGroups(
|
||||
viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
|
||||
input: {
|
||||
...viewGroup,
|
||||
@@ -117,7 +116,7 @@ export const useHandleRecordGroupField = () => {
|
||||
}
|
||||
|
||||
if (viewGroupsToDelete.length > 0) {
|
||||
await deleteViewGroupRecords(
|
||||
await deleteViewGroups(
|
||||
viewGroupsToDelete.map((group) => ({
|
||||
input: {
|
||||
id: group.id,
|
||||
@@ -131,8 +130,8 @@ export const useHandleRecordGroupField = () => {
|
||||
currentViewIdCallbackState,
|
||||
getViewFromPrefetchState,
|
||||
setRecordGroupsFromViewGroups,
|
||||
createViewGroupRecords,
|
||||
deleteViewGroupRecords,
|
||||
createViewGroups,
|
||||
deleteViewGroups,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -157,7 +156,7 @@ export const useHandleRecordGroupField = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await deleteViewGroupRecords(
|
||||
await deleteViewGroups(
|
||||
view.viewGroups.map((group) => ({
|
||||
input: {
|
||||
id: group.id,
|
||||
@@ -168,7 +167,7 @@ export const useHandleRecordGroupField = () => {
|
||||
setRecordGroupsFromViewGroups(view.id, [], objectMetadataItem);
|
||||
},
|
||||
[
|
||||
deleteViewGroupRecords,
|
||||
deleteViewGroups,
|
||||
currentViewIdCallbackState,
|
||||
getViewFromPrefetchState,
|
||||
setRecordGroupsFromViewGroups,
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@ import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record
|
||||
import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
|
||||
import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||
import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation';
|
||||
import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords';
|
||||
import { usePersistViewField } from '@/views/hooks/internal/usePersistViewField';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { useContext } from 'react';
|
||||
@@ -24,14 +24,14 @@ export const useViewFieldAggregateOperation = () => {
|
||||
useRefreshCoreViewsByObjectMetadataId();
|
||||
|
||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
const { updateViewFieldRecords } = usePersistViewFieldRecords();
|
||||
const { updateViewFields } = usePersistViewField();
|
||||
const updateViewFieldAggregateOperation = async (
|
||||
aggregateOperation: ExtendedAggregateOperations | null,
|
||||
) => {
|
||||
if (!currentViewField) {
|
||||
throw new Error('ViewField not found');
|
||||
}
|
||||
await updateViewFieldRecords([
|
||||
await updateViewFields([
|
||||
{
|
||||
input: {
|
||||
id: currentViewField.id,
|
||||
|
||||
+7
-2
@@ -4,6 +4,7 @@ import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMe
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
import { SettingsObjectFieldActiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown';
|
||||
import { SettingsObjectFieldInactiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown';
|
||||
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
|
||||
@@ -28,7 +29,6 @@ import { RelationType } from '~/generated-metadata/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { type SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
|
||||
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
import { RELATION_TYPES } from '../../constants/RelationTypes';
|
||||
import { SettingsObjectFieldDataType } from './SettingsObjectFieldDataType';
|
||||
|
||||
@@ -127,11 +127,15 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
return;
|
||||
}
|
||||
|
||||
await deactivateMetadataField(
|
||||
const deactivationResult = await deactivateMetadataField(
|
||||
activeFieldMetadatItem.id,
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
if (deactivationResult.status === 'failed') {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Add optimistic rendering for core views
|
||||
const deletedViewIds: string[] = [];
|
||||
|
||||
@@ -159,6 +163,7 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
if (readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateOneObjectMetadataItem({
|
||||
idToUpdate: objectMetadataItem.id,
|
||||
updatePayload: {
|
||||
|
||||
+30
-54
@@ -1,20 +1,17 @@
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
import { SettingsDataModelObjectAboutForm } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm';
|
||||
import {
|
||||
type SettingsDataModelObjectAboutFormValues,
|
||||
settingsDataModelObjectAboutFormSchema,
|
||||
} from '@/settings/data-model/validation-schemas/settingsDataModelObjectAboutFormSchema';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { ZodError } from 'zod';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { updatedObjectNamePluralState } from '~/pages/settings/data-model/states/updatedObjectNamePluralState';
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
|
||||
type SettingsUpdateDataModelObjectAboutFormProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
@@ -25,7 +22,6 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
|
||||
}: SettingsUpdateDataModelObjectAboutFormProps) => {
|
||||
const readonly = isObjectMetadataReadOnly({ objectMetadataItem });
|
||||
const navigate = useNavigateSettings();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const setUpdatedObjectNamePlural = useSetRecoilState(
|
||||
updatedObjectNamePluralState,
|
||||
);
|
||||
@@ -67,40 +63,41 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
|
||||
const objectNamePluralForRedirection =
|
||||
formValues.namePlural ?? objectMetadataItem.namePlural;
|
||||
|
||||
try {
|
||||
setUpdatedObjectNamePlural(objectNamePluralForRedirection);
|
||||
|
||||
const updatedObject = await updateObjectMetadata(formValues);
|
||||
|
||||
if (formValues.isLabelSyncedWithName !== isLabelSyncedWithName) {
|
||||
formConfig.reset({
|
||||
description,
|
||||
icon: icon ?? undefined,
|
||||
isLabelSyncedWithName: formValues.isLabelSyncedWithName,
|
||||
labelPlural: updatedObject?.data?.updateOneObject.labelPlural,
|
||||
labelSingular: updatedObject?.data?.updateOneObject.labelSingular,
|
||||
namePlural: updatedObject?.data?.updateOneObject.namePlural,
|
||||
nameSingular: updatedObject?.data?.updateOneObject.nameSingular,
|
||||
});
|
||||
} else {
|
||||
formConfig.reset(undefined, { keepValues: true });
|
||||
}
|
||||
|
||||
navigate(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural: objectNamePluralForRedirection,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
if (readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
setUpdatedObjectNamePlural(objectNamePluralForRedirection);
|
||||
const updateResult = await updateObjectMetadata(formValues);
|
||||
|
||||
if (updateResult.status === 'failed') {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedObject = updateResult.response;
|
||||
|
||||
if (formValues.isLabelSyncedWithName !== isLabelSyncedWithName) {
|
||||
formConfig.reset({
|
||||
description,
|
||||
icon: icon ?? undefined,
|
||||
isLabelSyncedWithName: formValues.isLabelSyncedWithName,
|
||||
labelPlural: updatedObject?.data?.updateOneObject.labelPlural,
|
||||
labelSingular: updatedObject?.data?.updateOneObject.labelSingular,
|
||||
namePlural: updatedObject?.data?.updateOneObject.namePlural,
|
||||
nameSingular: updatedObject?.data?.updateOneObject.nameSingular,
|
||||
});
|
||||
} else {
|
||||
formConfig.reset(undefined, { keepValues: true });
|
||||
}
|
||||
|
||||
navigate(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural: objectNamePluralForRedirection,
|
||||
});
|
||||
};
|
||||
|
||||
const updateObjectMetadata = async (
|
||||
formValues: SettingsDataModelObjectAboutFormValues,
|
||||
) => {
|
||||
if (readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatePayload = { ...formValues };
|
||||
|
||||
if (!objectMetadataItem.isCustom) {
|
||||
@@ -123,27 +120,6 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
|
||||
});
|
||||
};
|
||||
|
||||
const handleError = (error: unknown) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
|
||||
if (error instanceof ZodError) {
|
||||
enqueueErrorSnackBar({
|
||||
message: error.issues[0].message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (error instanceof ApolloError) {
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
enqueueErrorSnackBar({});
|
||||
};
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<FormProvider {...formConfig}>
|
||||
|
||||
+7
-3
@@ -1,6 +1,7 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
import { SettingsUpdateDataModelObjectAboutForm } from '@/settings/data-model/object-details/components/SettingsUpdateDataModelObjectAboutForm';
|
||||
import { SettingsDataModelObjectSettingsFormCard } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectSettingsFormCard';
|
||||
import styled from '@emotion/styled';
|
||||
@@ -10,7 +11,6 @@ import { H2Title, IconArchive } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
|
||||
type ObjectSettingsProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
@@ -31,12 +31,16 @@ export const ObjectSettings = ({ objectMetadataItem }: ObjectSettingsProps) => {
|
||||
const readonly = isObjectMetadataReadOnly({ objectMetadataItem });
|
||||
const navigate = useNavigateSettings();
|
||||
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
|
||||
|
||||
const handleDisable = async () => {
|
||||
await updateOneObjectMetadataItem({
|
||||
const result = await updateOneObjectMetadataItem({
|
||||
idToUpdate: objectMetadataItem.id,
|
||||
updatePayload: { isActive: false },
|
||||
});
|
||||
navigate(SettingsPath.Objects);
|
||||
|
||||
if (result.status === 'successful') {
|
||||
navigate(SettingsPath.Objects);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
+10
-24
@@ -1,22 +1,19 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useMemo } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { ZodError, type z } from 'zod';
|
||||
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getActiveFieldMetadataItems } from '@/object-metadata/utils/getActiveFieldMetadataItems';
|
||||
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import styled from '@emotion/styled';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { isLabelIdentifierFieldMetadataTypes } from 'twenty-shared/utils';
|
||||
import { IconCircleOff, IconPlus, useIcons } from 'twenty-ui/display';
|
||||
import { type SelectOption } from 'twenty-ui/input';
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
import { type z } from 'zod';
|
||||
|
||||
export const settingsDataModelObjectIdentifiersFormSchema =
|
||||
objectMetadataItemSchema.pick({
|
||||
@@ -50,7 +47,6 @@ export const SettingsDataModelObjectIdentifiersForm = ({
|
||||
mode: 'onTouched',
|
||||
resolver: zodResolver(settingsDataModelObjectIdentifiersFormSchema),
|
||||
});
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
|
||||
|
||||
const handleSave = async (
|
||||
@@ -60,23 +56,13 @@ export const SettingsDataModelObjectIdentifiersForm = ({
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateOneObjectMetadataItem({
|
||||
idToUpdate: objectMetadataItem.id,
|
||||
updatePayload: formValues,
|
||||
});
|
||||
const result = await updateOneObjectMetadataItem({
|
||||
idToUpdate: objectMetadataItem.id,
|
||||
updatePayload: formValues,
|
||||
});
|
||||
|
||||
if (result.status === 'successful') {
|
||||
formConfig.reset(undefined, { keepValues: true });
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
enqueueErrorSnackBar({
|
||||
message: error.issues[0].message,
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error instanceof ApolloError ? error : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
import { useMutation } from '@apollo/client';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { CREATE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/createOneServerlessFunction';
|
||||
import { FIND_MANY_SERVERLESS_FUNCTIONS } from '@/settings/serverless-functions/graphql/queries/findManyServerlessFunctions';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import {
|
||||
type CreateOneServerlessFunctionItemMutation,
|
||||
type CreateOneServerlessFunctionItemMutationVariables,
|
||||
type CreateServerlessFunctionInput,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useCreateOneServerlessFunction = () => {
|
||||
const apolloMetadataClient = useApolloCoreClient();
|
||||
const [mutate] = useMutation<
|
||||
CreateOneServerlessFunctionItemMutation,
|
||||
CreateOneServerlessFunctionItemMutationVariables
|
||||
>(CREATE_ONE_SERVERLESS_FUNCTION, {
|
||||
client: apolloMetadataClient,
|
||||
});
|
||||
|
||||
const createOneServerlessFunction = async (
|
||||
input: CreateServerlessFunctionInput,
|
||||
) => {
|
||||
return await mutate({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: [getOperationName(FIND_MANY_SERVERLESS_FUNCTIONS) ?? ''],
|
||||
});
|
||||
};
|
||||
|
||||
return { createOneServerlessFunction };
|
||||
};
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { DELETE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/deleteOneServerlessFunction';
|
||||
import { FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE } from '@/settings/serverless-functions/graphql/queries/findOneServerlessFunctionSourceCode';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import {
|
||||
type DeleteOneServerlessFunctionMutation,
|
||||
type DeleteOneServerlessFunctionMutationVariables,
|
||||
type ServerlessFunctionIdInput,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useDeleteOneServerlessFunction = () => {
|
||||
const apolloMetadataClient = useApolloCoreClient();
|
||||
const [mutate] = useMutation<
|
||||
DeleteOneServerlessFunctionMutation,
|
||||
DeleteOneServerlessFunctionMutationVariables
|
||||
>(DELETE_ONE_SERVERLESS_FUNCTION, {
|
||||
client: apolloMetadataClient,
|
||||
});
|
||||
|
||||
const deleteOneServerlessFunction = async (
|
||||
input: ServerlessFunctionIdInput,
|
||||
) => {
|
||||
return await mutate({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: [
|
||||
getOperationName(FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE) ?? '',
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
return { deleteOneServerlessFunction };
|
||||
};
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
|
||||
import { CREATE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/createOneServerlessFunction';
|
||||
import { DELETE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/deleteOneServerlessFunction';
|
||||
import { UPDATE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/updateOneServerlessFunction';
|
||||
import { FIND_MANY_SERVERLESS_FUNCTIONS } from '@/settings/serverless-functions/graphql/queries/findManyServerlessFunctions';
|
||||
import { FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE } from '@/settings/serverless-functions/graphql/queries/findOneServerlessFunctionSourceCode';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError, useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
type CreateOneServerlessFunctionItemMutation,
|
||||
type CreateOneServerlessFunctionItemMutationVariables,
|
||||
type DeleteOneServerlessFunctionMutation,
|
||||
type DeleteOneServerlessFunctionMutationVariables,
|
||||
type UpdateOneServerlessFunctionMutation,
|
||||
type UpdateOneServerlessFunctionMutationVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const usePersistServerlessFunction = () => {
|
||||
const apolloMetadataClient = useApolloCoreClient();
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const [createServerlessFunctionMutation] = useMutation<
|
||||
CreateOneServerlessFunctionItemMutation,
|
||||
CreateOneServerlessFunctionItemMutationVariables
|
||||
>(CREATE_ONE_SERVERLESS_FUNCTION, {
|
||||
client: apolloMetadataClient,
|
||||
});
|
||||
|
||||
const [updateServerlessFunctionMutation] = useMutation<
|
||||
UpdateOneServerlessFunctionMutation,
|
||||
UpdateOneServerlessFunctionMutationVariables
|
||||
>(UPDATE_ONE_SERVERLESS_FUNCTION, {
|
||||
client: apolloMetadataClient,
|
||||
});
|
||||
|
||||
const [deleteServerlessFunctionMutation] = useMutation<
|
||||
DeleteOneServerlessFunctionMutation,
|
||||
DeleteOneServerlessFunctionMutationVariables
|
||||
>(DELETE_ONE_SERVERLESS_FUNCTION, {
|
||||
client: apolloMetadataClient,
|
||||
});
|
||||
|
||||
const createServerlessFunction = useCallback(
|
||||
async (
|
||||
variables: CreateOneServerlessFunctionItemMutationVariables,
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof createServerlessFunctionMutation>>
|
||||
>
|
||||
> => {
|
||||
try {
|
||||
const result = await createServerlessFunctionMutation({
|
||||
variables,
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: [
|
||||
getOperationName(FIND_MANY_SERVERLESS_FUNCTIONS) ?? '',
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: result,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'serverlessFunction',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
createServerlessFunctionMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const updateServerlessFunction = useCallback(
|
||||
async (
|
||||
variables: UpdateOneServerlessFunctionMutationVariables,
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof updateServerlessFunctionMutation>>
|
||||
>
|
||||
> => {
|
||||
try {
|
||||
const result = await updateServerlessFunctionMutation({
|
||||
variables,
|
||||
refetchQueries: [
|
||||
getOperationName(FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE) ?? '',
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: result,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'serverlessFunction',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
updateServerlessFunctionMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const deleteServerlessFunction = useCallback(
|
||||
async (
|
||||
variables: DeleteOneServerlessFunctionMutationVariables,
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof deleteServerlessFunctionMutation>>
|
||||
>
|
||||
> => {
|
||||
try {
|
||||
const result = await deleteServerlessFunctionMutation({
|
||||
variables,
|
||||
awaitRefetchQueries: true,
|
||||
refetchQueries: [
|
||||
getOperationName(FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE) ?? '',
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: result,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'serverlessFunction',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
deleteServerlessFunctionMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
createServerlessFunction,
|
||||
updateServerlessFunction,
|
||||
deleteServerlessFunction,
|
||||
};
|
||||
};
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { UPDATE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/updateOneServerlessFunction';
|
||||
import { FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE } from '@/settings/serverless-functions/graphql/queries/findOneServerlessFunctionSourceCode';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import {
|
||||
type UpdateOneServerlessFunctionMutation,
|
||||
type UpdateOneServerlessFunctionMutationVariables,
|
||||
type UpdateServerlessFunctionInputUpdates,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useUpdateOneServerlessFunction = (
|
||||
serverlessFunctionId: string,
|
||||
) => {
|
||||
const apolloMetadataClient = useApolloCoreClient();
|
||||
const [mutate] = useMutation<
|
||||
UpdateOneServerlessFunctionMutation,
|
||||
UpdateOneServerlessFunctionMutationVariables
|
||||
>(UPDATE_ONE_SERVERLESS_FUNCTION, {
|
||||
client: apolloMetadataClient,
|
||||
});
|
||||
|
||||
const updateOneServerlessFunction = async (
|
||||
update: UpdateServerlessFunctionInputUpdates,
|
||||
) => {
|
||||
return await mutate({
|
||||
variables: {
|
||||
input: { update, id: serverlessFunctionId },
|
||||
},
|
||||
refetchQueries: [
|
||||
getOperationName(FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE) ?? '',
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
return { updateOneServerlessFunction };
|
||||
};
|
||||
+8
-7
@@ -1,17 +1,16 @@
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useUpdateView } from '@/views/hooks/useUpdateView';
|
||||
import { usePersistView } from '@/views/hooks/internal/usePersistView';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { useUpdateViewAggregate } from '../useUpdateViewAggregate';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
|
||||
jest.mock('@/ui/utilities/state/component-state/hooks/useRecoilComponentValue');
|
||||
jest.mock('@/views/hooks/useUpdateView');
|
||||
jest.mock('@/views/hooks/internal/usePersistView');
|
||||
jest.mock('@/views/hooks/useRefreshCoreViewsByObjectMetadataId');
|
||||
jest.mock('recoil');
|
||||
|
||||
describe('useUpdateViewAggregate', () => {
|
||||
const mockCurrentViewId = 'test-view-id';
|
||||
const mockUpdateView = jest.fn();
|
||||
@@ -20,7 +19,7 @@ describe('useUpdateViewAggregate', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(useRecoilComponentValue as jest.Mock).mockReturnValue(mockCurrentViewId);
|
||||
(useUpdateView as jest.Mock).mockReturnValue({
|
||||
(usePersistView as jest.Mock).mockReturnValue({
|
||||
updateView: mockUpdateView,
|
||||
});
|
||||
(useSetRecoilState as jest.Mock).mockReturnValue(
|
||||
@@ -44,8 +43,10 @@ describe('useUpdateViewAggregate', () => {
|
||||
// updateView is called with 'EARLIEST' converted to 'MIN'
|
||||
expect(mockUpdateView).toHaveBeenCalledWith({
|
||||
id: mockCurrentViewId,
|
||||
kanbanAggregateOperationFieldMetadataId: 'test-field-id',
|
||||
kanbanAggregateOperation: AggregateOperations.MIN,
|
||||
input: {
|
||||
kanbanAggregateOperationFieldMetadataId: 'test-field-id',
|
||||
kanbanAggregateOperation: AggregateOperations.MIN,
|
||||
},
|
||||
});
|
||||
|
||||
// setAggregateOperation is called with 'EARLIEST'
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
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 { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
type CreateCoreViewMutationVariables,
|
||||
type DeleteCoreViewMutationVariables,
|
||||
type UpdateCoreViewMutationVariables,
|
||||
useCreateCoreViewMutation,
|
||||
useDeleteCoreViewMutation,
|
||||
useUpdateCoreViewMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const usePersistView = () => {
|
||||
const [createCoreViewMutation] = useCreateCoreViewMutation();
|
||||
const [updateCoreViewMutation] = useUpdateCoreViewMutation();
|
||||
const [deleteCoreViewMutation] = useDeleteCoreViewMutation();
|
||||
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const createView = useCallback(
|
||||
async (
|
||||
variables: CreateCoreViewMutationVariables,
|
||||
): Promise<
|
||||
MetadataRequestResult<Awaited<ReturnType<typeof createCoreViewMutation>>>
|
||||
> => {
|
||||
try {
|
||||
const result = await createCoreViewMutation({
|
||||
variables,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: result,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'view',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[createCoreViewMutation, handleMetadataError, enqueueErrorSnackBar],
|
||||
);
|
||||
|
||||
const updateView = useCallback(
|
||||
async (
|
||||
variables: UpdateCoreViewMutationVariables,
|
||||
): Promise<
|
||||
MetadataRequestResult<Awaited<ReturnType<typeof updateCoreViewMutation>>>
|
||||
> => {
|
||||
try {
|
||||
const result = await updateCoreViewMutation({
|
||||
variables,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: result,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'view',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[updateCoreViewMutation, handleMetadataError, enqueueErrorSnackBar],
|
||||
);
|
||||
|
||||
const deleteView = useCallback(
|
||||
async (
|
||||
variables: DeleteCoreViewMutationVariables,
|
||||
): Promise<
|
||||
MetadataRequestResult<Awaited<ReturnType<typeof deleteCoreViewMutation>>>
|
||||
> => {
|
||||
try {
|
||||
const result = await deleteCoreViewMutation({
|
||||
variables,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: result,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'view',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[deleteCoreViewMutation, handleMetadataError, enqueueErrorSnackBar],
|
||||
);
|
||||
|
||||
return {
|
||||
createView,
|
||||
updateView,
|
||||
deleteView,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,268 @@
|
||||
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 { useTriggerViewFieldOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type CreateCoreViewFieldMutationVariables,
|
||||
type DeleteCoreViewFieldMutationVariables,
|
||||
type DestroyCoreViewFieldMutationVariables,
|
||||
type UpdateCoreViewFieldMutationVariables,
|
||||
useCreateCoreViewFieldMutation,
|
||||
useDeleteCoreViewFieldMutation,
|
||||
useDestroyCoreViewFieldMutation,
|
||||
useUpdateCoreViewFieldMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const usePersistViewField = () => {
|
||||
const { triggerViewFieldOptimisticEffect } =
|
||||
useTriggerViewFieldOptimisticEffect();
|
||||
|
||||
const [createCoreViewFieldMutation] = useCreateCoreViewFieldMutation();
|
||||
const [updateCoreViewFieldMutation] = useUpdateCoreViewFieldMutation();
|
||||
const [deleteCoreViewFieldMutation] = useDeleteCoreViewFieldMutation();
|
||||
const [destroyCoreViewFieldMutation] = useDestroyCoreViewFieldMutation();
|
||||
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const createViewFields = useCallback(
|
||||
async (
|
||||
createCoreViewFieldInputs: CreateCoreViewFieldMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof createCoreViewFieldMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (createCoreViewFieldInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
createCoreViewFieldInputs.map(async (variables) =>
|
||||
createCoreViewFieldMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const createdViewField = data?.createCoreViewField;
|
||||
if (!isDefined(createdViewField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFieldOptimisticEffect({
|
||||
createdViewFields: [createdViewField],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewField',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
triggerViewFieldOptimisticEffect,
|
||||
createCoreViewFieldMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const updateViewFields = useCallback(
|
||||
async (
|
||||
createCoreViewFieldInputs: UpdateCoreViewFieldMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof updateCoreViewFieldMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (createCoreViewFieldInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
createCoreViewFieldInputs.map((variables) =>
|
||||
updateCoreViewFieldMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const updatedViewField = data?.updateCoreViewField;
|
||||
if (!isDefined(updatedViewField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFieldOptimisticEffect({
|
||||
updatedViewFields: [updatedViewField],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewField',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
triggerViewFieldOptimisticEffect,
|
||||
updateCoreViewFieldMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const deleteViewFields = useCallback(
|
||||
async (
|
||||
deleteCoreViewFieldInputs: DeleteCoreViewFieldMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof deleteCoreViewFieldMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (deleteCoreViewFieldInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
deleteCoreViewFieldInputs.map((variables) =>
|
||||
deleteCoreViewFieldMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const deletedViewField = data?.deleteCoreViewField;
|
||||
if (!isDefined(deletedViewField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFieldOptimisticEffect({
|
||||
deletedViewFields: [deletedViewField],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewField',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
triggerViewFieldOptimisticEffect,
|
||||
deleteCoreViewFieldMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const destroyViewFields = useCallback(
|
||||
async (
|
||||
destroyCoreViewFieldInputs: DestroyCoreViewFieldMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof destroyCoreViewFieldMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (destroyCoreViewFieldInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
destroyCoreViewFieldInputs.map((variables) =>
|
||||
destroyCoreViewFieldMutation({
|
||||
variables,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewField',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[destroyCoreViewFieldMutation, handleMetadataError, enqueueErrorSnackBar],
|
||||
);
|
||||
|
||||
return {
|
||||
createViewFields,
|
||||
updateViewFields,
|
||||
deleteViewFields,
|
||||
destroyViewFields,
|
||||
};
|
||||
};
|
||||
@@ -1,129 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useTriggerViewFieldOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type CreateCoreViewFieldMutationVariables,
|
||||
type DeleteCoreViewFieldMutationVariables,
|
||||
type DestroyCoreViewFieldMutationVariables,
|
||||
type UpdateCoreViewFieldMutationVariables,
|
||||
useCreateCoreViewFieldMutation,
|
||||
useDeleteCoreViewFieldMutation,
|
||||
useDestroyCoreViewFieldMutation,
|
||||
useUpdateCoreViewFieldMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const usePersistViewFieldRecords = () => {
|
||||
const { triggerViewFieldOptimisticEffect } =
|
||||
useTriggerViewFieldOptimisticEffect();
|
||||
|
||||
const [createCoreViewFieldMutation] = useCreateCoreViewFieldMutation();
|
||||
const [updateCoreViewFieldMutation] = useUpdateCoreViewFieldMutation();
|
||||
const [deleteCoreViewFieldMutation] = useDeleteCoreViewFieldMutation();
|
||||
const [destroyCoreViewFieldMutation] = useDestroyCoreViewFieldMutation();
|
||||
|
||||
const createCoreViewFieldRecords = useCallback(
|
||||
(createCoreViewFieldInputs: CreateCoreViewFieldMutationVariables[]) => {
|
||||
if (createCoreViewFieldInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
createCoreViewFieldInputs.map(async (variables) =>
|
||||
createCoreViewFieldMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const createdViewField = data?.createCoreViewField;
|
||||
if (!isDefined(createdViewField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFieldOptimisticEffect({
|
||||
createdViewFields: [createdViewField],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[triggerViewFieldOptimisticEffect, createCoreViewFieldMutation],
|
||||
);
|
||||
|
||||
const updateCoreViewFieldRecords = useCallback(
|
||||
(createCoreViewFieldInputs: UpdateCoreViewFieldMutationVariables[]) => {
|
||||
if (createCoreViewFieldInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
createCoreViewFieldInputs.map((variables) =>
|
||||
updateCoreViewFieldMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const updatedViewField = data?.updateCoreViewField;
|
||||
if (!isDefined(updatedViewField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFieldOptimisticEffect({
|
||||
updatedViewFields: [updatedViewField],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[triggerViewFieldOptimisticEffect, updateCoreViewFieldMutation],
|
||||
);
|
||||
|
||||
const deleteCoreViewFieldRecords = useCallback(
|
||||
(deleteCoreViewFieldInputs: DeleteCoreViewFieldMutationVariables[]) => {
|
||||
if (deleteCoreViewFieldInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
deleteCoreViewFieldInputs.map((variables) =>
|
||||
deleteCoreViewFieldMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const deletedViewField = data?.deleteCoreViewField;
|
||||
if (!isDefined(deletedViewField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFieldOptimisticEffect({
|
||||
deletedViewFields: [deletedViewField],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[triggerViewFieldOptimisticEffect, deleteCoreViewFieldMutation],
|
||||
);
|
||||
|
||||
const destroyCoreViewFieldRecords = useCallback(
|
||||
(destroyCoreViewFieldInputs: DestroyCoreViewFieldMutationVariables[]) => {
|
||||
if (destroyCoreViewFieldInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
destroyCoreViewFieldInputs.map((variables) =>
|
||||
destroyCoreViewFieldMutation({
|
||||
variables,
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[destroyCoreViewFieldMutation],
|
||||
);
|
||||
|
||||
return {
|
||||
createViewFieldRecords: createCoreViewFieldRecords,
|
||||
updateViewFieldRecords: updateCoreViewFieldRecords,
|
||||
deleteViewFieldRecords: deleteCoreViewFieldRecords,
|
||||
destroyViewFieldRecords: destroyCoreViewFieldRecords,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,267 @@
|
||||
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 { useTriggerViewFilterOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewFilterOptimisticEffect';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type CreateCoreViewFilterMutationVariables,
|
||||
type DeleteCoreViewFilterMutationVariables,
|
||||
type DestroyCoreViewFilterMutationVariables,
|
||||
type UpdateCoreViewFilterMutationVariables,
|
||||
useCreateCoreViewFilterMutation,
|
||||
useDeleteCoreViewFilterMutation,
|
||||
useDestroyCoreViewFilterMutation,
|
||||
useUpdateCoreViewFilterMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const usePersistViewFilterRecords = () => {
|
||||
const { triggerViewFilterOptimisticEffect } =
|
||||
useTriggerViewFilterOptimisticEffect();
|
||||
const [createCoreViewFilterMutation] = useCreateCoreViewFilterMutation();
|
||||
const [updateCoreViewFilterMutation] = useUpdateCoreViewFilterMutation();
|
||||
const [deleteCoreViewFilterMutation] = useDeleteCoreViewFilterMutation();
|
||||
const [destroyCoreViewFilterMutation] = useDestroyCoreViewFilterMutation();
|
||||
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const createViewFilters = useCallback(
|
||||
async (
|
||||
createCoreViewFilterInputs: CreateCoreViewFilterMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof createCoreViewFilterMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (createCoreViewFilterInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
createCoreViewFilterInputs.map((variables) =>
|
||||
createCoreViewFilterMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const createdViewFilter = data?.createCoreViewFilter;
|
||||
if (!isDefined(createdViewFilter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFilterOptimisticEffect({
|
||||
createdViewFilters: [createdViewFilter],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewFilter',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
triggerViewFilterOptimisticEffect,
|
||||
createCoreViewFilterMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const updateViewFilters = useCallback(
|
||||
async (
|
||||
updateCoreViewFilterInputs: UpdateCoreViewFilterMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof updateCoreViewFilterMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (updateCoreViewFilterInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
updateCoreViewFilterInputs.map((variables) =>
|
||||
updateCoreViewFilterMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const updatedViewFilter = data?.updateCoreViewFilter;
|
||||
if (!isDefined(updatedViewFilter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFilterOptimisticEffect({
|
||||
updatedViewFilters: [updatedViewFilter],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewFilter',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
triggerViewFilterOptimisticEffect,
|
||||
updateCoreViewFilterMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const deleteViewFilters = useCallback(
|
||||
async (
|
||||
deleteCoreViewFilterInputs: DeleteCoreViewFilterMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof deleteCoreViewFilterMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (deleteCoreViewFilterInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
deleteCoreViewFilterInputs.map((variables) =>
|
||||
deleteCoreViewFilterMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const deletedViewFilter = data?.deleteCoreViewFilter;
|
||||
if (!isDefined(deletedViewFilter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFilterOptimisticEffect({
|
||||
deletedViewFilters: [deletedViewFilter],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewFilter',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
triggerViewFilterOptimisticEffect,
|
||||
deleteCoreViewFilterMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const destroyViewFilters = useCallback(
|
||||
async (
|
||||
destroyCoreViewFilterInputs: DestroyCoreViewFilterMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof destroyCoreViewFilterMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (destroyCoreViewFilterInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
destroyCoreViewFilterInputs.map((variables) =>
|
||||
destroyCoreViewFilterMutation({
|
||||
variables,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewFilter',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[destroyCoreViewFilterMutation, handleMetadataError, enqueueErrorSnackBar],
|
||||
);
|
||||
|
||||
return {
|
||||
createViewFilters,
|
||||
updateViewFilters,
|
||||
deleteViewFilters,
|
||||
destroyViewFilters,
|
||||
};
|
||||
};
|
||||
+6
-6
@@ -39,7 +39,7 @@ export const usePersistViewFilterGroupRecords = () => {
|
||||
[apolloClient],
|
||||
);
|
||||
|
||||
const createCoreViewFilterGroupRecords = useCallback(
|
||||
const createViewFilterGroups = useCallback(
|
||||
async (
|
||||
viewFilterGroupsToCreate: ViewFilterGroup[],
|
||||
view: Pick<GraphQLView, 'id'>,
|
||||
@@ -80,7 +80,7 @@ export const usePersistViewFilterGroupRecords = () => {
|
||||
[createCoreViewFilterGroupRecord],
|
||||
);
|
||||
|
||||
const updateCoreViewFilterGroupRecords = useCallback(
|
||||
const updateViewFilterGroups = useCallback(
|
||||
(viewFilterGroupsToUpdate: ViewFilterGroup[]) => {
|
||||
if (!viewFilterGroupsToUpdate.length) return;
|
||||
return Promise.all(
|
||||
@@ -104,7 +104,7 @@ export const usePersistViewFilterGroupRecords = () => {
|
||||
[apolloClient],
|
||||
);
|
||||
|
||||
const deleteCoreViewFilterGroupRecords = useCallback(
|
||||
const deleteViewFilterGroups = useCallback(
|
||||
(viewFilterGroupIdsToDelete: string[]) => {
|
||||
if (!viewFilterGroupIdsToDelete.length) return;
|
||||
return Promise.all(
|
||||
@@ -122,8 +122,8 @@ export const usePersistViewFilterGroupRecords = () => {
|
||||
);
|
||||
|
||||
return {
|
||||
createViewFilterGroupRecords: createCoreViewFilterGroupRecords,
|
||||
updateViewFilterGroupRecords: updateCoreViewFilterGroupRecords,
|
||||
deleteViewFilterGroupRecords: deleteCoreViewFilterGroupRecords,
|
||||
createViewFilterGroups,
|
||||
updateViewFilterGroups,
|
||||
deleteViewFilterGroups,
|
||||
};
|
||||
};
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useTriggerViewFilterOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewFilterOptimisticEffect';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type CreateCoreViewFilterMutationVariables,
|
||||
type DeleteCoreViewFilterMutationVariables,
|
||||
type DestroyCoreViewFilterMutationVariables,
|
||||
type UpdateCoreViewFilterMutationVariables,
|
||||
useCreateCoreViewFilterMutation,
|
||||
useDeleteCoreViewFilterMutation,
|
||||
useDestroyCoreViewFilterMutation,
|
||||
useUpdateCoreViewFilterMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const usePersistViewFilterRecords = () => {
|
||||
const { triggerViewFilterOptimisticEffect } =
|
||||
useTriggerViewFilterOptimisticEffect();
|
||||
const [createCoreViewFilterMutation] = useCreateCoreViewFilterMutation();
|
||||
const [updateCoreViewFilterMutation] = useUpdateCoreViewFilterMutation();
|
||||
const [deleteCoreViewFilterMutation] = useDeleteCoreViewFilterMutation();
|
||||
const [destroyCoreViewFilterMutation] = useDestroyCoreViewFilterMutation();
|
||||
|
||||
const createCoreViewFilterRecords = useCallback(
|
||||
(createCoreViewFilterInputs: CreateCoreViewFilterMutationVariables[]) => {
|
||||
if (createCoreViewFilterInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
createCoreViewFilterInputs.map((variables) =>
|
||||
createCoreViewFilterMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const createdViewFilter = data?.createCoreViewFilter;
|
||||
if (!isDefined(createdViewFilter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFilterOptimisticEffect({
|
||||
createdViewFilters: [createdViewFilter],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[triggerViewFilterOptimisticEffect, createCoreViewFilterMutation],
|
||||
);
|
||||
|
||||
const updateCoreViewFilterRecords = useCallback(
|
||||
(updateCoreViewFilterInputs: UpdateCoreViewFilterMutationVariables[]) => {
|
||||
if (updateCoreViewFilterInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
updateCoreViewFilterInputs.map((variables) =>
|
||||
updateCoreViewFilterMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const updatedViewFilter = data?.updateCoreViewFilter;
|
||||
if (!isDefined(updatedViewFilter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFilterOptimisticEffect({
|
||||
updatedViewFilters: [updatedViewFilter],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[triggerViewFilterOptimisticEffect, updateCoreViewFilterMutation],
|
||||
);
|
||||
|
||||
const deleteCoreViewFilterRecords = useCallback(
|
||||
(deleteCoreViewFilterInputs: DeleteCoreViewFilterMutationVariables[]) => {
|
||||
if (deleteCoreViewFilterInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
deleteCoreViewFilterInputs.map((variables) =>
|
||||
deleteCoreViewFilterMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const deletedViewFilter = data?.deleteCoreViewFilter;
|
||||
if (!isDefined(deletedViewFilter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFilterOptimisticEffect({
|
||||
deletedViewFilters: [deletedViewFilter],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[triggerViewFilterOptimisticEffect, deleteCoreViewFilterMutation],
|
||||
);
|
||||
|
||||
const destroyCoreViewFilterRecords = useCallback(
|
||||
(destroyCoreViewFilterInputs: DestroyCoreViewFilterMutationVariables[]) => {
|
||||
if (destroyCoreViewFilterInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
destroyCoreViewFilterInputs.map((variables) =>
|
||||
destroyCoreViewFilterMutation({
|
||||
variables,
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[destroyCoreViewFilterMutation],
|
||||
);
|
||||
|
||||
return {
|
||||
createViewFilterRecords: createCoreViewFilterRecords,
|
||||
updateViewFilterRecords: updateCoreViewFilterRecords,
|
||||
deleteViewFilterRecords: deleteCoreViewFilterRecords,
|
||||
destroyViewFilterRecords: destroyCoreViewFilterRecords,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,267 @@
|
||||
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 { useTriggerViewGroupOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type CreateCoreViewGroupMutationVariables,
|
||||
type DeleteCoreViewGroupMutationVariables,
|
||||
type DestroyCoreViewGroupMutationVariables,
|
||||
type UpdateCoreViewGroupMutationVariables,
|
||||
useCreateCoreViewGroupMutation,
|
||||
useDeleteCoreViewGroupMutation,
|
||||
useDestroyCoreViewGroupMutation,
|
||||
useUpdateCoreViewGroupMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const usePersistViewGroupRecords = () => {
|
||||
const { triggerViewGroupOptimisticEffect } =
|
||||
useTriggerViewGroupOptimisticEffect();
|
||||
const [createCoreViewGroupMutation] = useCreateCoreViewGroupMutation();
|
||||
const [updateCoreViewGroupMutation] = useUpdateCoreViewGroupMutation();
|
||||
const [deleteCoreViewGroupMutation] = useDeleteCoreViewGroupMutation();
|
||||
const [destroyCoreViewGroupMutation] = useDestroyCoreViewGroupMutation();
|
||||
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const createViewGroups = useCallback(
|
||||
async (
|
||||
createCoreViewGroupInputs: CreateCoreViewGroupMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof createCoreViewGroupMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (createCoreViewGroupInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
createCoreViewGroupInputs.map((variables) =>
|
||||
createCoreViewGroupMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const createdViewGroup = data?.createCoreViewGroup;
|
||||
if (!isDefined(createdViewGroup)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewGroupOptimisticEffect({
|
||||
createdViewGroups: [createdViewGroup],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewGroup',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
triggerViewGroupOptimisticEffect,
|
||||
createCoreViewGroupMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const updateViewGroups = useCallback(
|
||||
async (
|
||||
updateCoreViewGroupInputs: UpdateCoreViewGroupMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof updateCoreViewGroupMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (updateCoreViewGroupInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
updateCoreViewGroupInputs.map((variables) =>
|
||||
updateCoreViewGroupMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const updatedViewGroup = data?.updateCoreViewGroup;
|
||||
if (!isDefined(updatedViewGroup)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewGroupOptimisticEffect({
|
||||
updatedViewGroups: [updatedViewGroup],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewGroup',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
triggerViewGroupOptimisticEffect,
|
||||
updateCoreViewGroupMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const deleteViewGroups = useCallback(
|
||||
async (
|
||||
deleteCoreViewGroupInputs: DeleteCoreViewGroupMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof deleteCoreViewGroupMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (deleteCoreViewGroupInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
deleteCoreViewGroupInputs.map((variables) =>
|
||||
deleteCoreViewGroupMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const deletedViewGroup = data?.deleteCoreViewGroup;
|
||||
if (!isDefined(deletedViewGroup)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewGroupOptimisticEffect({
|
||||
deletedViewGroups: [deletedViewGroup],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewGroup',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[
|
||||
triggerViewGroupOptimisticEffect,
|
||||
deleteCoreViewGroupMutation,
|
||||
handleMetadataError,
|
||||
enqueueErrorSnackBar,
|
||||
],
|
||||
);
|
||||
|
||||
const destroyViewGroups = useCallback(
|
||||
async (
|
||||
destroyCoreViewGroupInputs: DestroyCoreViewGroupMutationVariables[],
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof destroyCoreViewGroupMutation>>[]
|
||||
>
|
||||
> => {
|
||||
if (destroyCoreViewGroupInputs.length === 0) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
destroyCoreViewGroupInputs.map((variables) =>
|
||||
destroyCoreViewGroupMutation({
|
||||
variables,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError) {
|
||||
handleMetadataError(error, {
|
||||
primaryMetadataName: 'viewGroup',
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({ message: t`An error occurred.` });
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'failed',
|
||||
error,
|
||||
};
|
||||
}
|
||||
},
|
||||
[destroyCoreViewGroupMutation, handleMetadataError, enqueueErrorSnackBar],
|
||||
);
|
||||
|
||||
return {
|
||||
createViewGroups,
|
||||
updateViewGroups,
|
||||
deleteViewGroups,
|
||||
destroyViewGroups,
|
||||
};
|
||||
};
|
||||
@@ -1,128 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useTriggerViewGroupOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type CreateCoreViewGroupMutationVariables,
|
||||
type DeleteCoreViewGroupMutationVariables,
|
||||
type DestroyCoreViewGroupMutationVariables,
|
||||
type UpdateCoreViewGroupMutationVariables,
|
||||
useCreateCoreViewGroupMutation,
|
||||
useDeleteCoreViewGroupMutation,
|
||||
useDestroyCoreViewGroupMutation,
|
||||
useUpdateCoreViewGroupMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const usePersistViewGroupRecords = () => {
|
||||
const { triggerViewGroupOptimisticEffect } =
|
||||
useTriggerViewGroupOptimisticEffect();
|
||||
const [createCoreViewGroupMutation] = useCreateCoreViewGroupMutation();
|
||||
const [updateCoreViewGroupMutation] = useUpdateCoreViewGroupMutation();
|
||||
const [deleteCoreViewGroupMutation] = useDeleteCoreViewGroupMutation();
|
||||
const [destroyCoreViewGroupMutation] = useDestroyCoreViewGroupMutation();
|
||||
|
||||
const createCoreViewGroupRecords = useCallback(
|
||||
(createCoreViewGroupInputs: CreateCoreViewGroupMutationVariables[]) => {
|
||||
if (createCoreViewGroupInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
createCoreViewGroupInputs.map((variables) =>
|
||||
createCoreViewGroupMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const createdViewGroup = data?.createCoreViewGroup;
|
||||
if (!isDefined(createdViewGroup)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewGroupOptimisticEffect({
|
||||
createdViewGroups: [createdViewGroup],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[triggerViewGroupOptimisticEffect, createCoreViewGroupMutation],
|
||||
);
|
||||
|
||||
const updateCoreViewGroupRecords = useCallback(
|
||||
(updateCoreViewGroupInputs: UpdateCoreViewGroupMutationVariables[]) => {
|
||||
if (updateCoreViewGroupInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
updateCoreViewGroupInputs.map((variables) =>
|
||||
updateCoreViewGroupMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const updatedViewGroup = data?.updateCoreViewGroup;
|
||||
if (!isDefined(updatedViewGroup)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewGroupOptimisticEffect({
|
||||
updatedViewGroups: [updatedViewGroup],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[triggerViewGroupOptimisticEffect, updateCoreViewGroupMutation],
|
||||
);
|
||||
|
||||
const deleteCoreViewGroupRecords = useCallback(
|
||||
(deleteCoreViewGroupInputs: DeleteCoreViewGroupMutationVariables[]) => {
|
||||
if (deleteCoreViewGroupInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
deleteCoreViewGroupInputs.map((variables) =>
|
||||
deleteCoreViewGroupMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const deletedViewGroup = data?.deleteCoreViewGroup;
|
||||
if (!isDefined(deletedViewGroup)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewGroupOptimisticEffect({
|
||||
deletedViewGroups: [deletedViewGroup],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[triggerViewGroupOptimisticEffect, deleteCoreViewGroupMutation],
|
||||
);
|
||||
|
||||
const destroyCoreViewGroupRecords = useCallback(
|
||||
(destroyCoreViewGroupInputs: DestroyCoreViewGroupMutationVariables[]) => {
|
||||
if (destroyCoreViewGroupInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
destroyCoreViewGroupInputs.map((variables) =>
|
||||
destroyCoreViewGroupMutation({
|
||||
variables,
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[destroyCoreViewGroupMutation],
|
||||
);
|
||||
|
||||
return {
|
||||
createViewGroupRecords: createCoreViewGroupRecords,
|
||||
updateViewGroupRecords: updateCoreViewGroupRecords,
|
||||
deleteViewGroupRecords: deleteCoreViewGroupRecords,
|
||||
destroyViewGroupRecords: destroyCoreViewGroupRecords,
|
||||
};
|
||||
};
|
||||
+6
-6
@@ -16,7 +16,7 @@ export const usePersistViewSortRecords = () => {
|
||||
const { triggerViewSortOptimisticEffect } =
|
||||
useTriggerViewSortOptimisticEffect();
|
||||
|
||||
const createCoreViewSortRecords = useCallback(
|
||||
const createViewSorts = useCallback(
|
||||
(
|
||||
viewSortsToCreate: CoreViewSortEssential[],
|
||||
view: Pick<GraphQLView, 'id'>,
|
||||
@@ -49,7 +49,7 @@ export const usePersistViewSortRecords = () => {
|
||||
[apolloClient, triggerViewSortOptimisticEffect],
|
||||
);
|
||||
|
||||
const updateCoreViewSortRecords = useCallback(
|
||||
const updateViewSorts = useCallback(
|
||||
(viewSortsToUpdate: CoreViewSortEssential[]) => {
|
||||
if (!viewSortsToUpdate.length) return;
|
||||
return Promise.all(
|
||||
@@ -77,7 +77,7 @@ export const usePersistViewSortRecords = () => {
|
||||
[apolloClient, triggerViewSortOptimisticEffect],
|
||||
);
|
||||
|
||||
const deleteCoreViewSortRecords = useCallback(
|
||||
const deleteViewSorts = useCallback(
|
||||
(viewSortsToDelete: Pick<CoreViewSortEssential, 'id' | 'viewId'>[]) => {
|
||||
if (!viewSortsToDelete.length) return;
|
||||
return Promise.all(
|
||||
@@ -100,8 +100,8 @@ export const usePersistViewSortRecords = () => {
|
||||
);
|
||||
|
||||
return {
|
||||
createViewSortRecords: createCoreViewSortRecords,
|
||||
updateViewSortRecords: updateCoreViewSortRecords,
|
||||
deleteViewSortRecords: deleteCoreViewSortRecords,
|
||||
createViewSorts,
|
||||
updateViewSorts,
|
||||
deleteViewSorts,
|
||||
};
|
||||
};
|
||||
@@ -7,11 +7,12 @@ import { currentRecordSortsComponentState } from '@/object-record/record-sort/st
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords';
|
||||
import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroupRecords';
|
||||
import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilterRecords';
|
||||
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroupRecords';
|
||||
import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSortRecords';
|
||||
import { usePersistView } from '@/views/hooks/internal/usePersistView';
|
||||
import { usePersistViewField } from '@/views/hooks/internal/usePersistViewField';
|
||||
import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilter';
|
||||
import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroup';
|
||||
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroup';
|
||||
import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSort';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { isPersistingViewFieldsState } from '@/views/states/isPersistingViewFieldsState';
|
||||
import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector';
|
||||
@@ -28,14 +29,12 @@ import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import { ViewCalendarLayout } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
type CreateCoreViewFieldMutationVariables,
|
||||
useCreateCoreViewMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { type CreateCoreViewFieldMutationVariables } from '~/generated/graphql';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
const [createCoreViewMutation] = useCreateCoreViewMutation();
|
||||
const { createView } = usePersistView();
|
||||
|
||||
const currentViewIdCallbackState = useRecoilComponentCallbackState(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
viewBarComponentId,
|
||||
@@ -45,15 +44,15 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
anyFieldFilterValueComponentState,
|
||||
);
|
||||
|
||||
const { createViewFieldRecords } = usePersistViewFieldRecords();
|
||||
const { createViewFields } = usePersistViewField();
|
||||
|
||||
const { createViewSortRecords } = usePersistViewSortRecords();
|
||||
const { createViewSorts } = usePersistViewSortRecords();
|
||||
|
||||
const { createViewGroupRecords } = usePersistViewGroupRecords();
|
||||
const { createViewGroups } = usePersistViewGroupRecords();
|
||||
|
||||
const { createViewFilterRecords } = usePersistViewFilterRecords();
|
||||
const { createViewFilters } = usePersistViewFilterRecords();
|
||||
|
||||
const { createViewFilterGroupRecords } = usePersistViewFilterGroupRecords();
|
||||
const { createViewFilterGroups } = usePersistViewFilterGroupRecords();
|
||||
|
||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
|
||||
@@ -120,44 +119,49 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
|
||||
const viewType = type ?? sourceView.type;
|
||||
|
||||
const result = await createCoreViewMutation({
|
||||
variables: {
|
||||
input: {
|
||||
id: id ?? v4(),
|
||||
name: name ?? sourceView.name,
|
||||
icon: icon ?? sourceView.icon,
|
||||
key: null,
|
||||
kanbanAggregateOperation: shouldCopyFiltersAndSortsAndAggregate
|
||||
? sourceView.kanbanAggregateOperation
|
||||
const result = await createView({
|
||||
input: {
|
||||
id: id ?? v4(),
|
||||
name: name ?? sourceView.name,
|
||||
icon: icon ?? sourceView.icon,
|
||||
key: null,
|
||||
kanbanAggregateOperation: shouldCopyFiltersAndSortsAndAggregate
|
||||
? sourceView.kanbanAggregateOperation
|
||||
: undefined,
|
||||
kanbanAggregateOperationFieldMetadataId:
|
||||
shouldCopyFiltersAndSortsAndAggregate
|
||||
? sourceView.kanbanAggregateOperationFieldMetadataId
|
||||
: undefined,
|
||||
type: convertViewTypeToCore(viewType),
|
||||
objectMetadataId: sourceView.objectMetadataId,
|
||||
openRecordIn: convertViewOpenRecordInToCore(
|
||||
sourceView.openRecordIn,
|
||||
),
|
||||
anyFieldFilterValue: anyFieldFilterValue,
|
||||
calendarLayout:
|
||||
viewType === ViewType.Calendar
|
||||
? ViewCalendarLayout.MONTH
|
||||
: undefined,
|
||||
calendarFieldMetadataId:
|
||||
viewType === ViewType.Calendar
|
||||
? calendarFieldMetadataId
|
||||
: undefined,
|
||||
kanbanAggregateOperationFieldMetadataId:
|
||||
shouldCopyFiltersAndSortsAndAggregate
|
||||
? sourceView.kanbanAggregateOperationFieldMetadataId
|
||||
: undefined,
|
||||
type: convertViewTypeToCore(viewType),
|
||||
objectMetadataId: sourceView.objectMetadataId,
|
||||
openRecordIn: convertViewOpenRecordInToCore(
|
||||
sourceView.openRecordIn,
|
||||
),
|
||||
anyFieldFilterValue: anyFieldFilterValue,
|
||||
calendarLayout:
|
||||
viewType === ViewType.Calendar
|
||||
? ViewCalendarLayout.MONTH
|
||||
: undefined,
|
||||
calendarFieldMetadataId:
|
||||
viewType === ViewType.Calendar
|
||||
? calendarFieldMetadataId
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
const newViewId = result.data?.createCoreView.id;
|
||||
|
||||
if (result.status === 'failed') {
|
||||
set(isPersistingViewFieldsState, false);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const newViewId = result.response.data?.createCoreView.id;
|
||||
|
||||
if (isUndefinedOrNull(newViewId)) {
|
||||
set(isPersistingViewFieldsState, false);
|
||||
throw new Error('Failed to create view');
|
||||
}
|
||||
|
||||
await createViewFieldRecords(
|
||||
const fieldResult = await createViewFields(
|
||||
sourceView.viewFields.map<CreateCoreViewFieldMutationVariables>(
|
||||
({ __typename, id: _id, ...viewField }) => ({
|
||||
input: { ...viewField, id: v4(), viewId: newViewId },
|
||||
@@ -165,6 +169,11 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
),
|
||||
);
|
||||
|
||||
if (fieldResult.status === 'failed') {
|
||||
set(isPersistingViewFieldsState, false);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (type === ViewType.Kanban) {
|
||||
if (!isDefined(kanbanFieldMetadataId)) {
|
||||
throw new Error('Kanban view must have a kanban field');
|
||||
@@ -194,7 +203,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
fieldMetadataId: kanbanFieldMetadataId,
|
||||
} satisfies ViewGroup);
|
||||
|
||||
await createViewGroupRecords(
|
||||
const groupResult = await createViewGroups(
|
||||
viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
|
||||
input: {
|
||||
...viewGroup,
|
||||
@@ -202,6 +211,11 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
},
|
||||
})),
|
||||
);
|
||||
|
||||
if (groupResult.status === 'failed') {
|
||||
set(isPersistingViewFieldsState, false);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCopyFiltersAndSortsAndAggregate === true) {
|
||||
@@ -232,9 +246,10 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
id: v4(),
|
||||
}));
|
||||
|
||||
await createViewFilterGroupRecords(viewFilterGroupsToCreate, {
|
||||
await createViewFilterGroups(viewFilterGroupsToCreate, {
|
||||
id: newViewId,
|
||||
});
|
||||
|
||||
const createViewFilterInputs = viewFiltersToCreate.map(
|
||||
(viewFilter) => ({
|
||||
input: {
|
||||
@@ -249,8 +264,15 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
await createViewFilterRecords(createViewFilterInputs);
|
||||
await createViewSortRecords(viewSortsToCreate, { id: newViewId });
|
||||
|
||||
const filterResult = await createViewFilters(createViewFilterInputs);
|
||||
|
||||
if (filterResult.status === 'failed') {
|
||||
set(isPersistingViewFieldsState, false);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await createViewSorts(viewSortsToCreate, { id: newViewId });
|
||||
}
|
||||
|
||||
await refreshCoreViewsByObjectMetadataId(objectMetadataItem.id);
|
||||
@@ -260,18 +282,18 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
},
|
||||
[
|
||||
currentViewIdCallbackState,
|
||||
createViewFieldRecords,
|
||||
createCoreViewMutation,
|
||||
createViewFields,
|
||||
createView,
|
||||
anyFieldFilterValue,
|
||||
objectMetadataItem.fields,
|
||||
objectMetadataItem.id,
|
||||
createViewGroupRecords,
|
||||
createViewGroups,
|
||||
currentRecordFilterGroups,
|
||||
currentRecordFilters,
|
||||
currentRecordSorts,
|
||||
createViewFilterGroupRecords,
|
||||
createViewFilterRecords,
|
||||
createViewSortRecords,
|
||||
createViewFilterGroups,
|
||||
createViewFilters,
|
||||
createViewSorts,
|
||||
refreshCoreViewsByObjectMetadataId,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useDeleteCoreViewMutation } from '~/generated/graphql';
|
||||
|
||||
export const useDeleteView = () => {
|
||||
const [deleteCoreViewMutation] = useDeleteCoreViewMutation();
|
||||
const { refreshCoreViewsByObjectMetadataId } =
|
||||
useRefreshCoreViewsByObjectMetadataId();
|
||||
|
||||
const deleteView = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async (viewId: string) => {
|
||||
const currentView = snapshot
|
||||
.getLoadable(
|
||||
coreViewFromViewIdFamilySelector({
|
||||
viewId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
if (!isDefined(currentView)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await deleteCoreViewMutation({
|
||||
variables: {
|
||||
id: viewId,
|
||||
},
|
||||
});
|
||||
|
||||
await refreshCoreViewsByObjectMetadataId(currentView.objectMetadataId);
|
||||
},
|
||||
[deleteCoreViewMutation, refreshCoreViewsByObjectMetadataId],
|
||||
);
|
||||
|
||||
return { deleteView };
|
||||
};
|
||||
@@ -1,12 +1,13 @@
|
||||
import { anyFieldFilterValueComponentState } from '@/object-record/record-filter/states/anyFieldFilterValueComponentState';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { usePersistView } from '@/views/hooks/internal/usePersistView';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { useUpdateView } from '@/views/hooks/useUpdateView';
|
||||
import { convertUpdateViewInputToCore } from '@/views/utils/convertUpdateViewInputToCore';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useSaveAnyFieldFilterToView = () => {
|
||||
const { updateView } = useUpdateView();
|
||||
const { updateView } = usePersistView();
|
||||
|
||||
const { currentView } = useGetCurrentViewOnly();
|
||||
|
||||
@@ -28,10 +29,17 @@ export const useSaveAnyFieldFilterToView = () => {
|
||||
.getValue();
|
||||
|
||||
if (currentAnyFieldFilterValue !== currentViewAnyFieldFilterValue) {
|
||||
await updateView({
|
||||
const formattedCurrentView = convertUpdateViewInputToCore({
|
||||
...currentView,
|
||||
anyFieldFilterValue: currentAnyFieldFilterValue,
|
||||
});
|
||||
await updateView({
|
||||
id: currentView.id,
|
||||
input: {
|
||||
...formattedCurrentView,
|
||||
anyFieldFilterValue: currentAnyFieldFilterValue,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[updateView, anyFieldFilterValueCallbackState, currentView],
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords';
|
||||
import { usePersistViewField } from '@/views/hooks/internal/usePersistViewField';
|
||||
import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState';
|
||||
import { isPersistingViewFieldsState } from '@/views/states/isPersistingViewFieldsState';
|
||||
import { type ViewField } from '@/views/types/ViewField';
|
||||
@@ -15,8 +15,7 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const useSaveCurrentViewFields = () => {
|
||||
const { createViewFieldRecords, updateViewFieldRecords } =
|
||||
usePersistViewFieldRecords();
|
||||
const { createViewFields, updateViewFields } = usePersistViewField();
|
||||
|
||||
const { getViewFromPrefetchState } = useGetViewFromPrefetchState();
|
||||
|
||||
@@ -120,17 +119,17 @@ export const useSaveCurrentViewFields = () => {
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
createViewFieldRecords(viewFieldsToCreate),
|
||||
updateViewFieldRecords(viewFieldsToUpdate),
|
||||
createViewFields(viewFieldsToCreate),
|
||||
updateViewFields(viewFieldsToUpdate),
|
||||
]);
|
||||
|
||||
set(isPersistingViewFieldsState, false);
|
||||
},
|
||||
[
|
||||
createViewFieldRecords,
|
||||
createViewFields,
|
||||
currentViewIdCallbackState,
|
||||
getViewFromPrefetchState,
|
||||
updateViewFieldRecords,
|
||||
updateViewFields,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroupRecords';
|
||||
import { usePersistViewGroupRecords } from '@/views/hooks/internal/usePersistViewGroup';
|
||||
import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState';
|
||||
import { type ViewGroup } from '@/views/types/ViewGroup';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -10,8 +10,7 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const useSaveCurrentViewGroups = () => {
|
||||
const { createViewGroupRecords, updateViewGroupRecords } =
|
||||
usePersistViewGroupRecords();
|
||||
const { createViewGroups, updateViewGroups } = usePersistViewGroupRecords();
|
||||
|
||||
const { getViewFromPrefetchState } = useGetViewFromPrefetchState();
|
||||
|
||||
@@ -62,7 +61,7 @@ export const useSaveCurrentViewGroups = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await updateViewGroupRecords([
|
||||
await updateViewGroups([
|
||||
{
|
||||
input: {
|
||||
id: existingField.id,
|
||||
@@ -76,11 +75,7 @@ export const useSaveCurrentViewGroups = () => {
|
||||
},
|
||||
]);
|
||||
},
|
||||
[
|
||||
currentViewIdCallbackState,
|
||||
getViewFromPrefetchState,
|
||||
updateViewGroupRecords,
|
||||
],
|
||||
[currentViewIdCallbackState, getViewFromPrefetchState, updateViewGroups],
|
||||
);
|
||||
|
||||
const saveViewGroups = useRecoilCallback(
|
||||
@@ -94,7 +89,7 @@ export const useSaveCurrentViewGroups = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const view = await getViewFromPrefetchState(currentViewId);
|
||||
const view = getViewFromPrefetchState(currentViewId);
|
||||
|
||||
if (isUndefinedOrNull(view)) {
|
||||
return;
|
||||
@@ -151,7 +146,7 @@ export const useSaveCurrentViewGroups = () => {
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
createViewGroupRecords(
|
||||
createViewGroups(
|
||||
viewGroupsToCreate.map(({ __typename, ...viewGroup }) => ({
|
||||
input: {
|
||||
...viewGroup,
|
||||
@@ -159,14 +154,14 @@ export const useSaveCurrentViewGroups = () => {
|
||||
},
|
||||
})),
|
||||
),
|
||||
updateViewGroupRecords(viewGroupsToUpdate),
|
||||
updateViewGroups(viewGroupsToUpdate),
|
||||
]);
|
||||
},
|
||||
[
|
||||
createViewGroupRecords,
|
||||
createViewGroups,
|
||||
currentViewIdCallbackState,
|
||||
getViewFromPrefetchState,
|
||||
updateViewGroupRecords,
|
||||
updateViewGroups,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+10
-13
@@ -1,7 +1,7 @@
|
||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroupRecords';
|
||||
import { usePersistViewFilterGroupRecords } from '@/views/hooks/internal/usePersistViewFilterGroup';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { getViewFilterGroupsToCreate } from '@/views/utils/getViewFilterGroupsToCreate';
|
||||
import { getViewFilterGroupsToDelete } from '@/views/utils/getViewFilterGroupsToDelete';
|
||||
@@ -12,9 +12,9 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useSaveRecordFilterGroupsToViewFilterGroups = () => {
|
||||
const {
|
||||
createViewFilterGroupRecords,
|
||||
updateViewFilterGroupRecords,
|
||||
deleteViewFilterGroupRecords,
|
||||
createViewFilterGroups,
|
||||
updateViewFilterGroups,
|
||||
deleteViewFilterGroups,
|
||||
} = usePersistViewFilterGroupRecords();
|
||||
|
||||
const { currentView } = useGetCurrentViewOnly();
|
||||
@@ -63,19 +63,16 @@ export const useSaveRecordFilterGroupsToViewFilterGroups = () => {
|
||||
(viewFilterGroup) => viewFilterGroup.id,
|
||||
);
|
||||
|
||||
await createViewFilterGroupRecords(
|
||||
viewFilterGroupsToCreate,
|
||||
currentView,
|
||||
);
|
||||
await updateViewFilterGroupRecords(viewFilterGroupsToUpdate);
|
||||
await deleteViewFilterGroupRecords(viewFilterGroupIdsToDelete);
|
||||
await createViewFilterGroups(viewFilterGroupsToCreate, currentView);
|
||||
await updateViewFilterGroups(viewFilterGroupsToUpdate);
|
||||
await deleteViewFilterGroups(viewFilterGroupIdsToDelete);
|
||||
},
|
||||
[
|
||||
currentView,
|
||||
currentRecordFilterGroupsCallbackState,
|
||||
createViewFilterGroupRecords,
|
||||
updateViewFilterGroupRecords,
|
||||
deleteViewFilterGroupRecords,
|
||||
createViewFilterGroups,
|
||||
updateViewFilterGroups,
|
||||
deleteViewFilterGroups,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+20
-12
@@ -1,7 +1,7 @@
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilterRecords';
|
||||
import { usePersistViewFilterRecords } from '@/views/hooks/internal/usePersistViewFilter';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { getViewFiltersToCreate } from '@/views/utils/getViewFiltersToCreate';
|
||||
import { getViewFiltersToDelete } from '@/views/utils/getViewFiltersToDelete';
|
||||
@@ -11,11 +11,8 @@ import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useSaveRecordFiltersToViewFilters = () => {
|
||||
const {
|
||||
createViewFilterRecords,
|
||||
updateViewFilterRecords,
|
||||
deleteViewFilterRecords,
|
||||
} = usePersistViewFilterRecords();
|
||||
const { createViewFilters, updateViewFilters, deleteViewFilters } =
|
||||
usePersistViewFilterRecords();
|
||||
|
||||
const { currentView } = useGetCurrentViewOnly();
|
||||
|
||||
@@ -94,16 +91,27 @@ export const useSaveRecordFiltersToViewFilters = () => {
|
||||
}),
|
||||
);
|
||||
|
||||
await createViewFilterRecords(createViewFilterInputs);
|
||||
await updateViewFilterRecords(updateViewFilterInputs);
|
||||
await deleteViewFilterRecords(deleteViewFilterInputs);
|
||||
const createResult = await createViewFilters(createViewFilterInputs);
|
||||
if (createResult.status === 'failed') {
|
||||
return;
|
||||
}
|
||||
|
||||
const updateResult = await updateViewFilters(updateViewFilterInputs);
|
||||
if (updateResult.status === 'failed') {
|
||||
return;
|
||||
}
|
||||
|
||||
const deleteResult = await deleteViewFilters(deleteViewFilterInputs);
|
||||
if (deleteResult.status === 'failed') {
|
||||
return;
|
||||
}
|
||||
},
|
||||
[
|
||||
currentView,
|
||||
currentRecordFiltersCallbackState,
|
||||
createViewFilterRecords,
|
||||
updateViewFilterRecords,
|
||||
deleteViewFilterRecords,
|
||||
createViewFilters,
|
||||
updateViewFilters,
|
||||
deleteViewFilters,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSortRecords';
|
||||
import { usePersistViewSortRecords } from '@/views/hooks/internal/usePersistViewSort';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { getViewSortsToCreate } from '@/views/utils/getViewSortsToCreate';
|
||||
import { getViewSortsToDelete } from '@/views/utils/getViewSortsToDelete';
|
||||
@@ -11,11 +11,8 @@ import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useSaveRecordSortsToViewSorts = () => {
|
||||
const {
|
||||
createViewSortRecords,
|
||||
updateViewSortRecords,
|
||||
deleteViewSortRecords,
|
||||
} = usePersistViewSortRecords();
|
||||
const { createViewSorts, updateViewSorts, deleteViewSorts } =
|
||||
usePersistViewSortRecords();
|
||||
|
||||
const { currentView } = useGetCurrentViewOnly();
|
||||
|
||||
@@ -56,16 +53,16 @@ export const useSaveRecordSortsToViewSorts = () => {
|
||||
newViewSorts,
|
||||
);
|
||||
|
||||
await createViewSortRecords(viewSortsToCreate, currentView);
|
||||
await updateViewSortRecords(viewSortsToUpdate);
|
||||
await deleteViewSortRecords(viewSortsToDelete);
|
||||
await createViewSorts(viewSortsToCreate, currentView);
|
||||
await updateViewSorts(viewSortsToUpdate);
|
||||
await deleteViewSorts(viewSortsToDelete);
|
||||
},
|
||||
[
|
||||
currentView,
|
||||
currentRecordSortsCallbackState,
|
||||
createViewSortRecords,
|
||||
updateViewSortRecords,
|
||||
deleteViewSortRecords,
|
||||
createViewSorts,
|
||||
updateViewSorts,
|
||||
deleteViewSorts,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { type GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { convertUpdateViewInputToCore } from '@/views/utils/convertUpdateViewInputToCore';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useUpdateCoreViewMutation } from '~/generated/graphql';
|
||||
|
||||
export const useUpdateView = () => {
|
||||
const [updateOneCoreView] = useUpdateCoreViewMutation();
|
||||
|
||||
const { refreshCoreViewsByObjectMetadataId } =
|
||||
useRefreshCoreViewsByObjectMetadataId();
|
||||
|
||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
|
||||
const updateView = useRecoilCallback(
|
||||
() => async (view: Partial<GraphQLView>) => {
|
||||
if (!isDefined(view.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await updateOneCoreView({
|
||||
variables: {
|
||||
id: view.id,
|
||||
input: convertUpdateViewInputToCore(view),
|
||||
},
|
||||
});
|
||||
|
||||
await refreshCoreViewsByObjectMetadataId(objectMetadataItem.id);
|
||||
},
|
||||
[
|
||||
objectMetadataItem.id,
|
||||
refreshCoreViewsByObjectMetadataId,
|
||||
updateOneCoreView,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
updateView,
|
||||
};
|
||||
};
|
||||
@@ -3,8 +3,8 @@ import { recordIndexKanbanAggregateOperationState } from '@/object-record/record
|
||||
import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||
import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { usePersistView } from '@/views/hooks/internal/usePersistView';
|
||||
import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId';
|
||||
import { useUpdateView } from '@/views/hooks/useUpdateView';
|
||||
import { useCallback } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -13,7 +13,7 @@ export const useUpdateViewAggregate = () => {
|
||||
const currentViewId = useRecoilComponentValue(
|
||||
contextStoreCurrentViewIdComponentState,
|
||||
);
|
||||
const { updateView } = useUpdateView();
|
||||
const { updateView } = usePersistView();
|
||||
|
||||
const setRecordIndexKanbanAggregateOperationState = useSetRecoilState(
|
||||
recordIndexKanbanAggregateOperationState,
|
||||
@@ -39,10 +39,17 @@ export const useUpdateViewAggregate = () => {
|
||||
kanbanAggregateOperation,
|
||||
)
|
||||
: null;
|
||||
|
||||
if (!isDefined(currentViewId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateView({
|
||||
id: currentViewId,
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
kanbanAggregateOperation: convertedKanbanAggregateOperation,
|
||||
input: {
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
kanbanAggregateOperation: convertedKanbanAggregateOperation,
|
||||
},
|
||||
});
|
||||
|
||||
setRecordIndexKanbanAggregateOperationState({
|
||||
|
||||
+3
-3
@@ -10,10 +10,10 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { usePersistView } from '@/views/hooks/internal/usePersistView';
|
||||
import { useChangeView } from '@/views/hooks/useChangeView';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { useOpenCreateViewDropdown } from '@/views/hooks/useOpenCreateViewDropown';
|
||||
import { useUpdateView } from '@/views/hooks/useUpdateView';
|
||||
import { coreViewsFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector';
|
||||
import { ViewPickerOptionDropdown } from '@/views/view-picker/components/ViewPickerOptionDropdown';
|
||||
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
|
||||
@@ -48,7 +48,7 @@ export const ViewPickerListContent = () => {
|
||||
|
||||
const { setViewPickerMode } = useViewPickerMode();
|
||||
|
||||
const { updateView } = useUpdateView();
|
||||
const { updateView } = usePersistView();
|
||||
const { changeView } = useChangeView();
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
@@ -85,7 +85,7 @@ export const ViewPickerListContent = () => {
|
||||
Promise.all(
|
||||
viewsReordered.map(async (view, index) => {
|
||||
if (view.position !== index) {
|
||||
await updateView({ id: view.id, position: index });
|
||||
await updateView({ id: view.id, input: { position: index } });
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
+3
-3
@@ -3,8 +3,8 @@ import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { usePersistView } from '@/views/hooks/internal/usePersistView';
|
||||
import { useChangeView } from '@/views/hooks/useChangeView';
|
||||
import { useDeleteView } from '@/views/hooks/useDeleteView';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { coreViewsFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector';
|
||||
import { useCloseAndResetViewPicker } from '@/views/view-picker/hooks/useCloseAndResetViewPicker';
|
||||
@@ -43,7 +43,7 @@ export const useDeleteViewFromCurrentState = (viewBarInstanceId?: string) => {
|
||||
|
||||
const { changeView } = useChangeView();
|
||||
|
||||
const { deleteView } = useDeleteView();
|
||||
const { deleteView } = usePersistView();
|
||||
|
||||
const deleteViewFromCurrentState = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
@@ -67,7 +67,7 @@ export const useDeleteViewFromCurrentState = (viewBarInstanceId?: string) => {
|
||||
);
|
||||
}
|
||||
|
||||
await deleteView(viewPickerReferenceViewId);
|
||||
await deleteView({ id: viewPickerReferenceViewId });
|
||||
},
|
||||
[
|
||||
currentView,
|
||||
|
||||
+6
-4
@@ -1,7 +1,7 @@
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { usePersistView } from '@/views/hooks/internal/usePersistView';
|
||||
import { useChangeView } from '@/views/hooks/useChangeView';
|
||||
import { useUpdateView } from '@/views/hooks/useUpdateView';
|
||||
import { useCloseAndResetViewPicker } from '@/views/view-picker/hooks/useCloseAndResetViewPicker';
|
||||
import { viewPickerInputNameComponentState } from '@/views/view-picker/states/viewPickerInputNameComponentState';
|
||||
import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState';
|
||||
@@ -32,7 +32,7 @@ export const useUpdateViewFromCurrentState = () => {
|
||||
const viewPickerReferenceViewIdCallbackState =
|
||||
useRecoilComponentCallbackState(viewPickerReferenceViewIdComponentState);
|
||||
|
||||
const { updateView } = useUpdateView();
|
||||
const { updateView } = usePersistView();
|
||||
const { changeView } = useChangeView();
|
||||
|
||||
const updateViewFromCurrentState = useRecoilCallback(
|
||||
@@ -57,8 +57,10 @@ export const useUpdateViewFromCurrentState = () => {
|
||||
|
||||
await updateView({
|
||||
id: viewPickerReferenceViewId,
|
||||
name: viewPickerInputName,
|
||||
icon: viewPickerSelectedIcon,
|
||||
input: {
|
||||
name: viewPickerInputName,
|
||||
icon: viewPickerSelectedIcon,
|
||||
},
|
||||
});
|
||||
changeView(viewPickerReferenceViewId);
|
||||
},
|
||||
|
||||
+11
-7
@@ -1,7 +1,6 @@
|
||||
import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader';
|
||||
import { useGetAvailablePackages } from '@/settings/serverless-functions/hooks/useGetAvailablePackages';
|
||||
import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
|
||||
import { useUpdateOneServerlessFunction } from '@/settings/serverless-functions/hooks/useUpdateOneServerlessFunction';
|
||||
import { useFullScreenModal } from '@/ui/layout/fullscreen/hooks/useFullScreenModal';
|
||||
import { type BreadcrumbProps } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow';
|
||||
@@ -39,6 +38,7 @@ import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { SOURCE_FOLDER_NAME } from '@/serverless-functions/constants/SourceFolderName';
|
||||
import { computeNewSources } from '@/serverless-functions/utils/computeNewSources';
|
||||
import { usePersistServerlessFunction } from '@/settings/serverless-functions/hooks/usePersistServerlessFunction';
|
||||
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
|
||||
import { CODE_ACTION } from '@/workflow/workflow-steps/workflow-actions/constants/actions/CodeAction';
|
||||
import { type Monaco } from '@monaco-editor/react';
|
||||
@@ -105,8 +105,7 @@ export const WorkflowEditActionServerlessFunction = ({
|
||||
activeTabIdComponentState,
|
||||
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
|
||||
);
|
||||
const { updateOneServerlessFunction } =
|
||||
useUpdateOneServerlessFunction(serverlessFunctionId);
|
||||
const { updateServerlessFunction } = usePersistServerlessFunction();
|
||||
const { getUpdatableWorkflowVersion } =
|
||||
useGetUpdatableWorkflowVersionOrThrow();
|
||||
|
||||
@@ -149,10 +148,15 @@ export const WorkflowEditActionServerlessFunction = ({
|
||||
});
|
||||
|
||||
const handleSave = useDebouncedCallback(async () => {
|
||||
await updateOneServerlessFunction({
|
||||
name: formValues.name,
|
||||
description: formValues.description,
|
||||
code: formValues.code,
|
||||
await updateServerlessFunction({
|
||||
input: {
|
||||
id: serverlessFunctionId,
|
||||
update: {
|
||||
name: formValues.name,
|
||||
description: formValues.description,
|
||||
code: formValues.code,
|
||||
},
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
||||
import { useCreateOneObjectMetadataItem } from '@/object-metadata/hooks/useCreateOneObjectMetadataItem';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
@@ -10,11 +7,11 @@ import {
|
||||
type SettingsDataModelObjectAboutFormValues,
|
||||
settingsDataModelObjectAboutFormSchema,
|
||||
} from '@/settings/data-model/validation-schemas/settingsDataModelObjectAboutFormSchema';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
@@ -24,7 +21,6 @@ import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
export const SettingsNewObject = () => {
|
||||
const { t } = useLingui();
|
||||
const navigate = useNavigateSettings();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { createOneObjectMetadataItem } = useCreateOneObjectMetadataItem();
|
||||
|
||||
@@ -43,25 +39,21 @@ export const SettingsNewObject = () => {
|
||||
const handleSave = async (
|
||||
formValues: SettingsDataModelObjectAboutFormValues,
|
||||
) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const { data: response } = await createOneObjectMetadataItem(formValues);
|
||||
setIsLoading(true);
|
||||
|
||||
const result = await createOneObjectMetadataItem(formValues);
|
||||
|
||||
if (result.status === 'successful') {
|
||||
const response = result.response.data;
|
||||
navigate(
|
||||
response ? SettingsPath.ObjectDetail : SettingsPath.Objects,
|
||||
response
|
||||
? { objectNamePlural: response.createOneObject.namePlural }
|
||||
: undefined,
|
||||
);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error instanceof ApolloError ? error : undefined,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -21,11 +21,9 @@ import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/field
|
||||
import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
|
||||
import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema';
|
||||
import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
import { shouldNavigateBackToMemorizedUrlOnSaveState } from '@/ui/navigation/states/shouldNavigateBackToMemorizedUrlOnSaveState';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { AppPath, SettingsPath } from 'twenty-shared/types';
|
||||
@@ -59,8 +57,6 @@ export const SettingsObjectFieldEdit = () => {
|
||||
setShouldNavigateBackToMemorizedUrlOnSave,
|
||||
] = useRecoilState(shouldNavigateBackToMemorizedUrlOnSaveState);
|
||||
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const { objectNamePlural = '', fieldName = '' } = useParams();
|
||||
const { findObjectMetadataItemByNamePlural } =
|
||||
useFilteredObjectMetadataItems();
|
||||
@@ -127,47 +123,47 @@ export const SettingsObjectFieldEdit = () => {
|
||||
const { dirtyFields } = formConfig.formState;
|
||||
setNewNameDuringSave(formValues.name);
|
||||
|
||||
try {
|
||||
if (
|
||||
formValues.type === FieldMetadataType.RELATION &&
|
||||
'relation' in formValues &&
|
||||
'relation' in dirtyFields
|
||||
) {
|
||||
const { relationFieldMetadataItem } =
|
||||
getRelationMetadata({
|
||||
fieldMetadataItem: fieldMetadataItem,
|
||||
}) ?? {};
|
||||
if (
|
||||
formValues.type === FieldMetadataType.RELATION &&
|
||||
'relation' in formValues &&
|
||||
'relation' in dirtyFields
|
||||
) {
|
||||
const { relationFieldMetadataItem } =
|
||||
getRelationMetadata({
|
||||
fieldMetadataItem: fieldMetadataItem,
|
||||
}) ?? {};
|
||||
|
||||
if (isDefined(relationFieldMetadataItem)) {
|
||||
await updateOneFieldMetadataItem({
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
fieldMetadataIdToUpdate: relationFieldMetadataItem.id,
|
||||
updatePayload: formValues.relation.field,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const otherDirtyFields = omit(dirtyFields, 'relation');
|
||||
|
||||
if (Object.keys(otherDirtyFields).length > 0) {
|
||||
const formattedInput = Object.fromEntries(
|
||||
Object.entries(formatFieldMetadataItemInput(formValues)).filter(
|
||||
([key]) => Object.keys(otherDirtyFields).includes(key),
|
||||
),
|
||||
);
|
||||
|
||||
await updateOneFieldMetadataItem({
|
||||
if (isDefined(relationFieldMetadataItem)) {
|
||||
const result = await updateOneFieldMetadataItem({
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
fieldMetadataIdToUpdate: fieldMetadataItem.id,
|
||||
updatePayload: formattedInput,
|
||||
fieldMetadataIdToUpdate: relationFieldMetadataItem.id,
|
||||
updatePayload: formValues.relation.field,
|
||||
});
|
||||
|
||||
if (result.status === 'failed') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const otherDirtyFields = omit(dirtyFields, 'relation');
|
||||
|
||||
if (Object.keys(otherDirtyFields).length > 0) {
|
||||
const formattedInput = Object.fromEntries(
|
||||
Object.entries(formatFieldMetadataItemInput(formValues)).filter(
|
||||
([key]) => Object.keys(otherDirtyFields).includes(key),
|
||||
),
|
||||
);
|
||||
|
||||
const updateResult = await updateOneFieldMetadataItem({
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
fieldMetadataIdToUpdate: fieldMetadataItem.id,
|
||||
updatePayload: formattedInput,
|
||||
});
|
||||
|
||||
if (updateResult.status === 'successful') {
|
||||
navigateBackOrToSettings();
|
||||
}
|
||||
} catch (error) {
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error instanceof ApolloError ? error : undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -198,10 +194,15 @@ export const SettingsObjectFieldEdit = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await deactivateMetadataField(fieldMetadataItem.id, objectMetadataItem.id);
|
||||
navigateSettings(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural,
|
||||
});
|
||||
const deactivationResult = await deactivateMetadataField(
|
||||
fieldMetadataItem.id,
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
if (deactivationResult.status === 'successful') {
|
||||
navigateSettings(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleActivate = async () => {
|
||||
@@ -209,10 +210,16 @@ export const SettingsObjectFieldEdit = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await activateMetadataField(fieldMetadataItem.id, objectMetadataItem.id);
|
||||
navigateSettings(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural,
|
||||
});
|
||||
const activationResult = await activateMetadataField(
|
||||
fieldMetadataItem.id,
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
if (activationResult.status === 'successful') {
|
||||
navigateSettings(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { useDeleteOneObjectMetadataItem } from '@/object-metadata/hooks/useDeleteOneObjectMetadataItem';
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useCombinedGetTotalCount } from '@/object-record/multiple-objects/hooks/useCombinedGetTotalCount';
|
||||
import {
|
||||
SettingsObjectMetadataItemTableRow,
|
||||
StyledObjectTableRow,
|
||||
} from '@/settings/data-model/object-details/components/SettingsObjectItemTableRow';
|
||||
import { GET_SETTINGS_OBJECT_TABLE_METADATA } from '~/pages/settings/data-model/constants/SettingsObjectTableMetadata';
|
||||
import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { TableSection } from '@/ui/layout/table/components/TableSection';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { SettingsObjectInactiveMenuDropDown } from '@/settings/data-model/objects/components/SettingsObjectInactiveMenuDropDown';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { normalizeSearchText } from '~/utils/normalizeSearchText';
|
||||
import { IconChevronRight, IconSearch } from 'twenty-ui/display';
|
||||
import styled from '@emotion/styled';
|
||||
import { getItemTagInfo } from '@/settings/data-model/utils/getItemTagInfo';
|
||||
import type { SettingsObjectTableItem } from '~/pages/settings/data-model/types/SettingsObjectTableItem';
|
||||
import { useCombinedGetTotalCount } from '@/object-record/multiple-objects/hooks/useCombinedGetTotalCount';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { useDeleteOneObjectMetadataItem } from '@/object-metadata/hooks/useDeleteOneObjectMetadataItem';
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableSection } from '@/ui/layout/table/components/TableSection';
|
||||
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { IconChevronRight, IconSearch } from 'twenty-ui/display';
|
||||
import { GET_SETTINGS_OBJECT_TABLE_METADATA } from '~/pages/settings/data-model/constants/SettingsObjectTableMetadata';
|
||||
import type { SettingsObjectTableItem } from '~/pages/settings/data-model/types/SettingsObjectTableItem';
|
||||
import { normalizeSearchText } from '~/utils/normalizeSearchText';
|
||||
|
||||
const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
|
||||
+66
-57
@@ -14,7 +14,6 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { type View } from '@/views/types/View';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -25,7 +24,7 @@ import {
|
||||
type RelationCreationPayload,
|
||||
SettingsPath,
|
||||
} from 'twenty-shared/types';
|
||||
import { CustomError, getSettingsPath } from 'twenty-shared/utils';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { type z } from 'zod';
|
||||
@@ -122,69 +121,79 @@ export const SettingsObjectNewFieldConfigure = () => {
|
||||
const handleSave = async (
|
||||
formValues: SettingsDataModelNewFieldFormValues,
|
||||
) => {
|
||||
try {
|
||||
setIsSaving(true);
|
||||
if (formValues.type === FieldMetadataType.MORPH_RELATION) {
|
||||
const {
|
||||
morphRelationObjectMetadataIds,
|
||||
targetFieldLabel,
|
||||
iconOnDestination,
|
||||
relationType,
|
||||
} = formValues;
|
||||
if (morphRelationObjectMetadataIds.length > 1) {
|
||||
await createMetadataField({
|
||||
...formValues,
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
objectMetadataId: activeObjectMetadataItem.id,
|
||||
isLabelSyncedWithName: false,
|
||||
morphRelationsCreationPayload: morphRelationObjectMetadataIds.map(
|
||||
(morphRelationObjectMetadataId: string) => ({
|
||||
type: relationType,
|
||||
targetObjectMetadataId: morphRelationObjectMetadataId,
|
||||
targetFieldLabel,
|
||||
targetFieldIcon: iconOnDestination,
|
||||
}),
|
||||
),
|
||||
});
|
||||
} else if (morphRelationObjectMetadataIds.length === 1) {
|
||||
const relationCreationPayload = {
|
||||
type: relationType,
|
||||
targetObjectMetadataId: morphRelationObjectMetadataIds[0],
|
||||
targetFieldLabel,
|
||||
targetFieldIcon: iconOnDestination,
|
||||
} satisfies RelationCreationPayload;
|
||||
setIsSaving(true);
|
||||
|
||||
await createMetadataField({
|
||||
...formValues,
|
||||
type: FieldMetadataType.RELATION,
|
||||
objectMetadataId: activeObjectMetadataItem.id,
|
||||
relationCreationPayload,
|
||||
});
|
||||
} else {
|
||||
throw new CustomError(
|
||||
'Please select at least one destination object for this relation.',
|
||||
'FIELD_METADATA_RELATION_MALFORMED',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await createMetadataField({
|
||||
...formValues,
|
||||
objectMetadataId: activeObjectMetadataItem.id,
|
||||
const createCleanUp = (
|
||||
creationResult: Awaited<ReturnType<typeof createMetadataField>>,
|
||||
) => {
|
||||
if (creationResult.status === 'successful') {
|
||||
navigate(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural,
|
||||
});
|
||||
}
|
||||
setIsSaving(false);
|
||||
};
|
||||
|
||||
navigate(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural,
|
||||
if (formValues.type !== FieldMetadataType.MORPH_RELATION) {
|
||||
const creationResult = await createMetadataField({
|
||||
...formValues,
|
||||
objectMetadataId: activeObjectMetadataItem.id,
|
||||
});
|
||||
|
||||
setIsSaving(false);
|
||||
} catch (error) {
|
||||
setIsSaving(false);
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error instanceof ApolloError ? error : undefined,
|
||||
});
|
||||
return createCleanUp(creationResult);
|
||||
}
|
||||
|
||||
const {
|
||||
morphRelationObjectMetadataIds,
|
||||
targetFieldLabel,
|
||||
iconOnDestination,
|
||||
relationType,
|
||||
} = formValues;
|
||||
|
||||
switch (true) {
|
||||
case morphRelationObjectMetadataIds.length > 1: {
|
||||
const creationResult = await createMetadataField({
|
||||
...formValues,
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
objectMetadataId: activeObjectMetadataItem.id,
|
||||
isLabelSyncedWithName: false,
|
||||
morphRelationsCreationPayload: morphRelationObjectMetadataIds.map(
|
||||
(morphRelationObjectMetadataId: string) => ({
|
||||
type: relationType,
|
||||
targetObjectMetadataId: morphRelationObjectMetadataId,
|
||||
targetFieldLabel,
|
||||
targetFieldIcon: iconOnDestination,
|
||||
}),
|
||||
),
|
||||
});
|
||||
return createCleanUp(creationResult);
|
||||
}
|
||||
case morphRelationObjectMetadataIds.length === 1: {
|
||||
const relationCreationPayload = {
|
||||
type: relationType,
|
||||
targetObjectMetadataId: morphRelationObjectMetadataIds[0],
|
||||
targetFieldLabel,
|
||||
targetFieldIcon: iconOnDestination,
|
||||
} satisfies RelationCreationPayload;
|
||||
|
||||
const creationResult = await createMetadataField({
|
||||
...formValues,
|
||||
type: FieldMetadataType.RELATION,
|
||||
objectMetadataId: activeObjectMetadataItem.id,
|
||||
relationCreationPayload,
|
||||
});
|
||||
|
||||
return createCleanUp(creationResult);
|
||||
}
|
||||
default: {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Please select at least one destination object for this relation.`,
|
||||
});
|
||||
return setIsSaving(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!activeObjectMetadataItem) return null;
|
||||
|
||||
return (
|
||||
|
||||
+16
-12
@@ -1,29 +1,29 @@
|
||||
import { useTestServerlessFunction } from '@/serverless-functions/hooks/useTestServerlessFunction';
|
||||
import { computeNewSources } from '@/serverless-functions/utils/computeNewSources';
|
||||
import { flattenSources } from '@/serverless-functions/utils/flattenSources';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsServerlessFunctionCodeEditorTab } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionCodeEditorTab';
|
||||
import { SettingsServerlessFunctionSettingsTab } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionSettingsTab';
|
||||
import { SettingsServerlessFunctionTestTab } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTestTab';
|
||||
import { SettingsServerlessFunctionTriggersTab } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTriggersTab';
|
||||
import { usePersistServerlessFunction } from '@/settings/serverless-functions/hooks/usePersistServerlessFunction';
|
||||
import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
|
||||
import { useUpdateOneServerlessFunction } from '@/settings/serverless-functions/hooks/useUpdateOneServerlessFunction';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import {
|
||||
IconBolt,
|
||||
IconCode,
|
||||
IconSettings,
|
||||
IconTestPipe,
|
||||
IconBolt,
|
||||
} from 'twenty-ui/display';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useFindOneApplicationQuery } from '~/generated-metadata/graphql';
|
||||
import { computeNewSources } from '@/serverless-functions/utils/computeNewSources';
|
||||
import { flattenSources } from '@/serverless-functions/utils/flattenSources';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
|
||||
const SERVERLESS_FUNCTION_DETAIL_ID = 'serverless-function-detail';
|
||||
|
||||
@@ -45,8 +45,7 @@ export const SettingsServerlessFunctionDetail = () => {
|
||||
activeTabIdComponentState,
|
||||
instanceId,
|
||||
);
|
||||
const { updateOneServerlessFunction } =
|
||||
useUpdateOneServerlessFunction(serverlessFunctionId);
|
||||
const { updateServerlessFunction } = usePersistServerlessFunction();
|
||||
|
||||
const { formValues, setFormValues, serverlessFunction, loading } =
|
||||
useServerlessFunctionUpdateFormState({ serverlessFunctionId });
|
||||
@@ -56,10 +55,15 @@ export const SettingsServerlessFunctionDetail = () => {
|
||||
});
|
||||
|
||||
const handleSave = useDebouncedCallback(async () => {
|
||||
await updateOneServerlessFunction({
|
||||
name: formValues.name,
|
||||
description: formValues.description,
|
||||
code: formValues.code,
|
||||
await updateServerlessFunction({
|
||||
input: {
|
||||
id: serverlessFunctionId,
|
||||
update: {
|
||||
name: formValues.name,
|
||||
description: formValues.description,
|
||||
code: formValues.code,
|
||||
},
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
},
|
||||
"include": [
|
||||
"**/__mocks__/**/*",
|
||||
"**/__tests__/**/*",
|
||||
"jest.config.mjs",
|
||||
"setupTests.ts",
|
||||
"src/**/*.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user