[CoreViewField BREAKING_CHANGES] Refactor view field service v2 and resolver (#14396)
## Introduction ### Twenty-sever Standardizing resolver input and transpilation models + return type on destroy and delete ~~Finally~~ Did plug everything under a feature flag and add coverage Next will do same for the view resolver and service v2 ### Twenty-front Refactored view field service in order to use codegenerated strictly typed mutations and adapt to new api contract
This commit is contained in:
+12
-5
@@ -32,11 +32,18 @@ export const useViewFieldAggregateOperation = () => {
|
||||
}
|
||||
await updateViewFieldRecords([
|
||||
{
|
||||
...currentViewField,
|
||||
aggregateOperation:
|
||||
convertExtendedAggregateOperationToAggregateOperation(
|
||||
aggregateOperation,
|
||||
),
|
||||
input: {
|
||||
id: currentViewField.id,
|
||||
update: {
|
||||
isVisible: currentViewField.isVisible,
|
||||
position: currentViewField.position,
|
||||
size: currentViewField.size,
|
||||
aggregateOperation:
|
||||
convertExtendedAggregateOperationToAggregateOperation(
|
||||
aggregateOperation,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -9,5 +9,8 @@ export const VIEW_FIELD_FRAGMENT = gql`
|
||||
position
|
||||
size
|
||||
aggregateOperation
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { VIEW_FIELD_FRAGMENT } from '@/views/graphql/fragments/viewFieldFragment';
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const DELETE_CORE_VIEW_FIELD = gql`
|
||||
mutation DeleteCoreViewField($id: String!) {
|
||||
deleteCoreViewField(id: $id)
|
||||
${VIEW_FIELD_FRAGMENT}
|
||||
mutation DeleteCoreViewField($input: DeleteViewFieldInput!) {
|
||||
deleteCoreViewField(input: $input) {
|
||||
...ViewFieldFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { VIEW_FIELD_FRAGMENT } from '@/views/graphql/fragments/viewFieldFragment';
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const DESTROY_CORE_VIEW_FIELD = gql`
|
||||
mutation DestroyCoreViewField($id: String!) {
|
||||
destroyCoreViewField(id: $id)
|
||||
${VIEW_FIELD_FRAGMENT}
|
||||
mutation DestroyCoreViewField($input: DestroyViewFieldInput!) {
|
||||
destroyCoreViewField(input: $input) {
|
||||
...ViewFieldFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -3,8 +3,8 @@ import { gql } from '@apollo/client';
|
||||
|
||||
export const UPDATE_CORE_VIEW_FIELD = gql`
|
||||
${VIEW_FIELD_FRAGMENT}
|
||||
mutation UpdateCoreViewField($id: String!, $input: UpdateViewFieldInput!) {
|
||||
updateCoreViewField(id: $id, input: $input) {
|
||||
mutation UpdateCoreViewField($input: UpdateViewFieldInput!) {
|
||||
updateCoreViewField(input: $input) {
|
||||
...ViewFieldFragment
|
||||
}
|
||||
}
|
||||
|
||||
+87
-49
@@ -1,90 +1,128 @@
|
||||
import { useCallback } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { CREATE_CORE_VIEW_FIELD } from '@/views/graphql/mutations/createCoreViewField';
|
||||
import { UPDATE_CORE_VIEW_FIELD } from '@/views/graphql/mutations/updateCoreViewField';
|
||||
import { useTriggerViewFieldOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect';
|
||||
import { type GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { type ViewField } from '@/views/types/ViewField';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type CoreViewField } from '~/generated/graphql';
|
||||
import {
|
||||
type CreateCoreViewFieldMutationVariables,
|
||||
type DeleteCoreViewFieldMutationVariables,
|
||||
type DestroyCoreViewFieldMutationVariables,
|
||||
type UpdateCoreViewFieldMutationVariables,
|
||||
useCreateCoreViewFieldMutation,
|
||||
useDeleteCoreViewFieldMutation,
|
||||
useDestroyCoreViewFieldMutation,
|
||||
useUpdateCoreViewFieldMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const usePersistViewFieldRecords = () => {
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { triggerViewFieldOptimisticEffect } =
|
||||
useTriggerViewFieldOptimisticEffect();
|
||||
const [createCoreViewFieldMutation] = useCreateCoreViewFieldMutation();
|
||||
const [updateCoreViewFieldMutation] = useUpdateCoreViewFieldMutation();
|
||||
const [deleteCoreViewFieldMutation] = useDeleteCoreViewFieldMutation();
|
||||
const [destroyCoreViewFieldMutation] = useDestroyCoreViewFieldMutation();
|
||||
|
||||
const createCoreViewFieldRecords = useCallback(
|
||||
(
|
||||
viewFieldsToCreate: Omit<ViewField, 'definition'>[],
|
||||
view: Pick<GraphQLView, 'id'>,
|
||||
) => {
|
||||
if (!viewFieldsToCreate.length) return;
|
||||
(createCoreViewFieldInputs: CreateCoreViewFieldMutationVariables[]) => {
|
||||
if (createCoreViewFieldInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
viewFieldsToCreate.map((viewField) =>
|
||||
apolloClient.mutate({
|
||||
mutation: CREATE_CORE_VIEW_FIELD,
|
||||
variables: {
|
||||
input: {
|
||||
id: v4(),
|
||||
fieldMetadataId: viewField.fieldMetadataId,
|
||||
viewId: view.id,
|
||||
isVisible: viewField.isVisible,
|
||||
position: viewField.position,
|
||||
size: viewField.size,
|
||||
} satisfies Partial<CoreViewField>,
|
||||
},
|
||||
createCoreViewFieldInputs.map(async (variables) =>
|
||||
createCoreViewFieldMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const record = data?.['createCoreViewField'];
|
||||
if (!record) return;
|
||||
const createdViewField = data?.createCoreViewField;
|
||||
if (!isDefined(createdViewField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFieldOptimisticEffect({
|
||||
createdViewFields: [record],
|
||||
createdViewFields: [createdViewField],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[apolloClient, triggerViewFieldOptimisticEffect],
|
||||
[triggerViewFieldOptimisticEffect, createCoreViewFieldMutation],
|
||||
);
|
||||
|
||||
const updateCoreViewFieldRecords = useCallback(
|
||||
(viewFieldsToUpdate: Omit<ViewField, 'definition'>[]) => {
|
||||
if (!viewFieldsToUpdate.length) return;
|
||||
(createCoreViewFieldInputs: UpdateCoreViewFieldMutationVariables[]) => {
|
||||
if (createCoreViewFieldInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
viewFieldsToUpdate.map((viewField) =>
|
||||
apolloClient.mutate({
|
||||
mutation: UPDATE_CORE_VIEW_FIELD,
|
||||
variables: {
|
||||
id: viewField.id,
|
||||
input: {
|
||||
isVisible: viewField.isVisible,
|
||||
position: viewField.position,
|
||||
size: viewField.size,
|
||||
aggregateOperation: viewField.aggregateOperation,
|
||||
} satisfies Partial<CoreViewField>,
|
||||
},
|
||||
createCoreViewFieldInputs.map((variables) =>
|
||||
updateCoreViewFieldMutation({
|
||||
variables,
|
||||
update: (_cache, { data }) => {
|
||||
const record = data?.['updateCoreViewField'];
|
||||
if (!isDefined(record)) return;
|
||||
const updatedViewField = data?.updateCoreViewField;
|
||||
if (!isDefined(updatedViewField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerViewFieldOptimisticEffect({
|
||||
updatedViewFields: [record],
|
||||
updatedViewFields: [updatedViewField],
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[apolloClient, triggerViewFieldOptimisticEffect],
|
||||
[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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -28,7 +28,10 @@ import { mapRecordSortToViewSort } from '@/views/utils/mapRecordSortToViewSort';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import { useCreateCoreViewMutation } from '~/generated/graphql';
|
||||
import {
|
||||
type CreateCoreViewFieldMutationVariables,
|
||||
useCreateCoreViewMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
@@ -138,7 +141,13 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
|
||||
throw new Error('Failed to create view');
|
||||
}
|
||||
|
||||
await createViewFieldRecords(sourceView.viewFields, { id: newViewId });
|
||||
await createViewFieldRecords(
|
||||
sourceView.viewFields.map<CreateCoreViewFieldMutationVariables>(
|
||||
({ __typename, ...viewField }) => ({
|
||||
input: { ...viewField, viewId: newViewId },
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
if (type === ViewType.Kanban) {
|
||||
if (!isDefined(kanbanFieldMetadataId)) {
|
||||
|
||||
+12
-2
@@ -11,7 +11,7 @@ import { mapViewFieldToRecordField } from '@/views/utils/mapViewFieldToRecordFie
|
||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
import { useFindManyCoreViewsLazyQuery } from '~/generated/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
@@ -71,7 +71,17 @@ export const useRefreshCoreViewsByObjectMetadataId = () => {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isDeeplyEqual(coreView.viewFields, existingView.viewFields)) {
|
||||
if (
|
||||
!isDeeplyEqual(
|
||||
coreView.viewFields.map((viewField) =>
|
||||
removePropertiesFromRecord(viewField, [
|
||||
'updatedAt',
|
||||
'createdAt',
|
||||
]),
|
||||
),
|
||||
existingView.viewFields,
|
||||
)
|
||||
) {
|
||||
const view = convertCoreViewToView(coreView);
|
||||
set(
|
||||
currentRecordFieldsComponentState.atomFamily({
|
||||
|
||||
@@ -6,7 +6,11 @@ import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistVie
|
||||
import { useGetViewFromPrefetchState } from '@/views/hooks/useGetViewFromPrefetchState';
|
||||
import { isPersistingViewFieldsState } from '@/views/states/isPersistingViewFieldsState';
|
||||
import { type ViewField } from '@/views/types/ViewField';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type CreateCoreViewFieldMutationVariables,
|
||||
type CreateViewFieldInput,
|
||||
type UpdateCoreViewFieldMutationVariables,
|
||||
} from '~/generated/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
@@ -41,50 +45,82 @@ export const useSaveCurrentViewFields = () => {
|
||||
|
||||
const currentViewFields = view.viewFields;
|
||||
|
||||
const viewFieldsToUpdate = viewFieldsToSave
|
||||
.map((viewFieldToSave) => {
|
||||
const existingField = currentViewFields.find(
|
||||
(currentViewField) =>
|
||||
currentViewField.fieldMetadataId ===
|
||||
viewFieldToSave.fieldMetadataId,
|
||||
);
|
||||
const { viewFieldsToCreate, viewFieldsToUpdate } =
|
||||
viewFieldsToSave.reduce<{
|
||||
viewFieldsToCreate: CreateCoreViewFieldMutationVariables[];
|
||||
viewFieldsToUpdate: UpdateCoreViewFieldMutationVariables[];
|
||||
}>(
|
||||
(
|
||||
{ viewFieldsToCreate, viewFieldsToUpdate },
|
||||
{ __typename, ...viewFieldToCreateOrUpdate },
|
||||
) => {
|
||||
const createViewFieldInput: CreateViewFieldInput = {
|
||||
...viewFieldToCreateOrUpdate,
|
||||
viewId: currentViewId,
|
||||
};
|
||||
const existingField = currentViewFields.find(
|
||||
(currentViewField) =>
|
||||
currentViewField.fieldMetadataId ===
|
||||
createViewFieldInput.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (isUndefinedOrNull(existingField)) {
|
||||
return undefined;
|
||||
}
|
||||
if (isUndefinedOrNull(existingField)) {
|
||||
return {
|
||||
viewFieldsToCreate: [
|
||||
...viewFieldsToCreate,
|
||||
{ input: createViewFieldInput },
|
||||
],
|
||||
viewFieldsToUpdate,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
isDeeplyEqual(
|
||||
{
|
||||
position: existingField.position,
|
||||
size: existingField.size,
|
||||
isVisible: existingField.isVisible,
|
||||
},
|
||||
{
|
||||
position: viewFieldToSave.position,
|
||||
size: viewFieldToSave.size,
|
||||
isVisible: viewFieldToSave.isVisible,
|
||||
},
|
||||
)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
isDeeplyEqual(
|
||||
{
|
||||
position: existingField.position,
|
||||
size: existingField.size,
|
||||
isVisible: existingField.isVisible,
|
||||
},
|
||||
{
|
||||
position: createViewFieldInput.position,
|
||||
size: createViewFieldInput.size,
|
||||
isVisible: createViewFieldInput.isVisible,
|
||||
},
|
||||
)
|
||||
) {
|
||||
return {
|
||||
viewFieldsToCreate,
|
||||
viewFieldsToUpdate,
|
||||
};
|
||||
}
|
||||
|
||||
return { ...viewFieldToSave, id: existingField.id };
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
const viewFieldsToCreate = viewFieldsToSave.filter(
|
||||
(viewFieldToSave) =>
|
||||
!currentViewFields.some(
|
||||
(currentViewField) =>
|
||||
currentViewField.fieldMetadataId ===
|
||||
viewFieldToSave.fieldMetadataId,
|
||||
),
|
||||
);
|
||||
return {
|
||||
viewFieldsToCreate,
|
||||
viewFieldsToUpdate: [
|
||||
...viewFieldsToUpdate,
|
||||
{
|
||||
input: {
|
||||
id: createViewFieldInput.id,
|
||||
update: {
|
||||
aggregateOperation:
|
||||
createViewFieldInput.aggregateOperation,
|
||||
isVisible: createViewFieldInput.isVisible,
|
||||
position: createViewFieldInput.position,
|
||||
size: createViewFieldInput.size,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
{
|
||||
viewFieldsToUpdate: [],
|
||||
viewFieldsToCreate: [],
|
||||
},
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
createViewFieldRecords(viewFieldsToCreate, view),
|
||||
createViewFieldRecords(viewFieldsToCreate),
|
||||
updateViewFieldRecords(viewFieldsToUpdate),
|
||||
]);
|
||||
|
||||
|
||||
+2
-2
@@ -19,8 +19,8 @@ export const useTriggerViewFieldOptimisticEffect = () => {
|
||||
updatedViewFields = [],
|
||||
deletedViewFields = [],
|
||||
}: {
|
||||
createdViewFields?: CoreViewField[];
|
||||
updatedViewFields?: CoreViewField[];
|
||||
createdViewFields?: Omit<CoreViewField, 'workspaceId'>[];
|
||||
updatedViewFields?: Omit<CoreViewField, 'workspaceId'>[];
|
||||
deletedViewFields?: Pick<CoreViewField, 'id' | 'viewId'>[];
|
||||
}) => {
|
||||
const coreViews = getSnapshotValue(snapshot, coreViewsState);
|
||||
|
||||
Reference in New Issue
Block a user