[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:
File diff suppressed because one or more lines are too long
@@ -881,6 +881,11 @@ export type DeleteTwoFactorAuthenticationMethodOutput = {
|
||||
success: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type DeleteViewFieldInput = {
|
||||
/** The id of the view field to delete. */
|
||||
id: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type DeleteWebhookDto = {
|
||||
id: Scalars['UUID'];
|
||||
};
|
||||
@@ -901,6 +906,11 @@ export type DeletedWorkspaceMember = {
|
||||
userWorkspaceId?: Maybe<Scalars['UUID']>;
|
||||
};
|
||||
|
||||
export type DestroyViewFieldInput = {
|
||||
/** The id of the view field to destroy. */
|
||||
id: Scalars['UUID'];
|
||||
};
|
||||
|
||||
/** Schema update on a table */
|
||||
export enum DistantTableUpdate {
|
||||
COLUMNS_ADDED = 'COLUMNS_ADDED',
|
||||
@@ -1402,7 +1412,7 @@ export type Mutation = {
|
||||
deactivateWorkflowVersion: Scalars['Boolean'];
|
||||
deleteApprovedAccessDomain: Scalars['Boolean'];
|
||||
deleteCoreView: Scalars['Boolean'];
|
||||
deleteCoreViewField: Scalars['Boolean'];
|
||||
deleteCoreViewField: CoreViewField;
|
||||
deleteCoreViewFilter: Scalars['Boolean'];
|
||||
deleteCoreViewFilterGroup: Scalars['Boolean'];
|
||||
deleteCoreViewGroup: Scalars['Boolean'];
|
||||
@@ -1427,7 +1437,7 @@ export type Mutation = {
|
||||
deleteWorkflowVersionStep: WorkflowVersionStepChanges;
|
||||
deleteWorkspaceInvitation: Scalars['String'];
|
||||
destroyCoreView: Scalars['Boolean'];
|
||||
destroyCoreViewField: Scalars['Boolean'];
|
||||
destroyCoreViewField: CoreViewField;
|
||||
destroyCoreViewFilter: Scalars['Boolean'];
|
||||
destroyCoreViewFilterGroup: Scalars['Boolean'];
|
||||
destroyCoreViewGroup: Scalars['Boolean'];
|
||||
@@ -1713,7 +1723,7 @@ export type MutationDeleteCoreViewArgs = {
|
||||
|
||||
|
||||
export type MutationDeleteCoreViewFieldArgs = {
|
||||
id: Scalars['String'];
|
||||
input: DeleteViewFieldInput;
|
||||
};
|
||||
|
||||
|
||||
@@ -1828,7 +1838,7 @@ export type MutationDestroyCoreViewArgs = {
|
||||
|
||||
|
||||
export type MutationDestroyCoreViewFieldArgs = {
|
||||
id: Scalars['String'];
|
||||
input: DestroyViewFieldInput;
|
||||
};
|
||||
|
||||
|
||||
@@ -2079,7 +2089,6 @@ export type MutationUpdateCoreViewArgs = {
|
||||
|
||||
|
||||
export type MutationUpdateCoreViewFieldArgs = {
|
||||
id: Scalars['String'];
|
||||
input: UpdateViewFieldInput;
|
||||
};
|
||||
|
||||
@@ -3442,6 +3451,13 @@ export type UpdateServerlessFunctionInput = {
|
||||
};
|
||||
|
||||
export type UpdateViewFieldInput = {
|
||||
/** The id of the view field to update */
|
||||
id: Scalars['UUID'];
|
||||
/** The view field to update */
|
||||
update: UpdateViewFieldInputUpdates;
|
||||
};
|
||||
|
||||
export type UpdateViewFieldInputUpdates = {
|
||||
aggregateOperation?: InputMaybe<AggregateOperations>;
|
||||
isVisible?: InputMaybe<Scalars['Boolean']>;
|
||||
position?: InputMaybe<Scalars['Float']>;
|
||||
@@ -3923,13 +3939,13 @@ export type OnDbEventSubscriptionVariables = Exact<{
|
||||
|
||||
export type OnDbEventSubscription = { __typename?: 'Subscription', onDbEvent: { __typename?: 'OnDbEventDTO', eventDate: string, action: DatabaseEventAction, objectNameSingular: string, updatedFields?: Array<string> | null, record: any } };
|
||||
|
||||
export type ViewFieldFragmentFragment = { __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null };
|
||||
export type ViewFieldFragmentFragment = { __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null };
|
||||
|
||||
export type ViewFilterFragmentFragment = { __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any };
|
||||
|
||||
export type ViewFilterGroupFragmentFragment = { __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any };
|
||||
|
||||
export type ViewFragmentFragment = { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> };
|
||||
export type ViewFragmentFragment = { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> };
|
||||
|
||||
export type ViewGroupFragmentFragment = { __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any };
|
||||
|
||||
@@ -3940,14 +3956,14 @@ export type CreateCoreViewMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateCoreViewMutation = { __typename?: 'Mutation', createCoreView: { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> } };
|
||||
export type CreateCoreViewMutation = { __typename?: 'Mutation', createCoreView: { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> } };
|
||||
|
||||
export type CreateCoreViewFieldMutationVariables = Exact<{
|
||||
input: CreateViewFieldInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateCoreViewFieldMutation = { __typename?: 'Mutation', createCoreViewField: { __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null } };
|
||||
export type CreateCoreViewFieldMutation = { __typename?: 'Mutation', createCoreViewField: { __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null } };
|
||||
|
||||
export type CreateCoreViewFilterMutationVariables = Exact<{
|
||||
input: CreateViewFilterInput;
|
||||
@@ -3985,11 +4001,11 @@ export type DeleteCoreViewMutationVariables = Exact<{
|
||||
export type DeleteCoreViewMutation = { __typename?: 'Mutation', deleteCoreView: boolean };
|
||||
|
||||
export type DeleteCoreViewFieldMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
input: DeleteViewFieldInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type DeleteCoreViewFieldMutation = { __typename?: 'Mutation', deleteCoreViewField: boolean };
|
||||
export type DeleteCoreViewFieldMutation = { __typename?: 'Mutation', deleteCoreViewField: { __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null } };
|
||||
|
||||
export type DeleteCoreViewFilterMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
@@ -4027,11 +4043,11 @@ export type DestroyCoreViewMutationVariables = Exact<{
|
||||
export type DestroyCoreViewMutation = { __typename?: 'Mutation', destroyCoreView: boolean };
|
||||
|
||||
export type DestroyCoreViewFieldMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
input: DestroyViewFieldInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type DestroyCoreViewFieldMutation = { __typename?: 'Mutation', destroyCoreViewField: boolean };
|
||||
export type DestroyCoreViewFieldMutation = { __typename?: 'Mutation', destroyCoreViewField: { __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null } };
|
||||
|
||||
export type DestroyCoreViewFilterMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
@@ -4067,15 +4083,14 @@ export type UpdateCoreViewMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateCoreViewMutation = { __typename?: 'Mutation', updateCoreView: { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> } };
|
||||
export type UpdateCoreViewMutation = { __typename?: 'Mutation', updateCoreView: { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> } };
|
||||
|
||||
export type UpdateCoreViewFieldMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
input: UpdateViewFieldInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateCoreViewFieldMutation = { __typename?: 'Mutation', updateCoreViewField: { __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null } };
|
||||
export type UpdateCoreViewFieldMutation = { __typename?: 'Mutation', updateCoreViewField: { __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null } };
|
||||
|
||||
export type UpdateCoreViewFilterMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
@@ -4114,7 +4129,7 @@ export type FindManyCoreViewFieldsQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type FindManyCoreViewFieldsQuery = { __typename?: 'Query', getCoreViewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null }> };
|
||||
export type FindManyCoreViewFieldsQuery = { __typename?: 'Query', getCoreViewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> };
|
||||
|
||||
export type FindManyCoreViewFilterGroupsQueryVariables = Exact<{
|
||||
viewId?: InputMaybe<Scalars['String']>;
|
||||
@@ -4149,21 +4164,21 @@ export type FindManyCoreViewsQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type FindManyCoreViewsQuery = { __typename?: 'Query', getCoreViews: Array<{ __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> }> };
|
||||
export type FindManyCoreViewsQuery = { __typename?: 'Query', getCoreViews: Array<{ __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> }> };
|
||||
|
||||
export type FindOneCoreViewQueryVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type FindOneCoreViewQuery = { __typename?: 'Query', getCoreView?: { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> } | null };
|
||||
export type FindOneCoreViewQuery = { __typename?: 'Query', getCoreView?: { __typename?: 'CoreView', id: any, name: string, objectMetadataId: any, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: any | null, anyFieldFilterValue?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: any, fieldMetadataId: any, operand: ViewFilterOperand, value: any, viewFilterGroupId?: any | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: any }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: any, parentViewFilterGroupId?: any | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: any }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: any, fieldMetadataId: any, direction: ViewSortDirection, viewId: any }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: any, fieldMetadataId: any, isVisible: boolean, fieldValue: string, position: number, viewId: any }> } | null };
|
||||
|
||||
export type FindOneCoreViewFieldQueryVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type FindOneCoreViewFieldQuery = { __typename?: 'Query', getCoreViewField?: { __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null } | null };
|
||||
export type FindOneCoreViewFieldQuery = { __typename?: 'Query', getCoreViewField?: { __typename?: 'CoreViewField', id: any, fieldMetadataId: any, viewId: any, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null } | null };
|
||||
|
||||
export type FindOneCoreViewFilterQueryVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
@@ -4202,6 +4217,9 @@ export const ViewFieldFragmentFragmentDoc = gql`
|
||||
position
|
||||
size
|
||||
aggregateOperation
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
}
|
||||
`;
|
||||
export const ViewFilterFragmentFragmentDoc = gql`
|
||||
@@ -4603,10 +4621,12 @@ export type DeleteCoreViewMutationHookResult = ReturnType<typeof useDeleteCoreVi
|
||||
export type DeleteCoreViewMutationResult = Apollo.MutationResult<DeleteCoreViewMutation>;
|
||||
export type DeleteCoreViewMutationOptions = Apollo.BaseMutationOptions<DeleteCoreViewMutation, DeleteCoreViewMutationVariables>;
|
||||
export const DeleteCoreViewFieldDocument = gql`
|
||||
mutation DeleteCoreViewField($id: String!) {
|
||||
deleteCoreViewField(id: $id)
|
||||
mutation DeleteCoreViewField($input: DeleteViewFieldInput!) {
|
||||
deleteCoreViewField(input: $input) {
|
||||
...ViewFieldFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
${ViewFieldFragmentFragmentDoc}`;
|
||||
export type DeleteCoreViewFieldMutationFn = Apollo.MutationFunction<DeleteCoreViewFieldMutation, DeleteCoreViewFieldMutationVariables>;
|
||||
|
||||
/**
|
||||
@@ -4622,7 +4642,7 @@ export type DeleteCoreViewFieldMutationFn = Apollo.MutationFunction<DeleteCoreVi
|
||||
* @example
|
||||
* const [deleteCoreViewFieldMutation, { data, loading, error }] = useDeleteCoreViewFieldMutation({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
@@ -4789,10 +4809,12 @@ export type DestroyCoreViewMutationHookResult = ReturnType<typeof useDestroyCore
|
||||
export type DestroyCoreViewMutationResult = Apollo.MutationResult<DestroyCoreViewMutation>;
|
||||
export type DestroyCoreViewMutationOptions = Apollo.BaseMutationOptions<DestroyCoreViewMutation, DestroyCoreViewMutationVariables>;
|
||||
export const DestroyCoreViewFieldDocument = gql`
|
||||
mutation DestroyCoreViewField($id: String!) {
|
||||
destroyCoreViewField(id: $id)
|
||||
mutation DestroyCoreViewField($input: DestroyViewFieldInput!) {
|
||||
destroyCoreViewField(input: $input) {
|
||||
...ViewFieldFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
${ViewFieldFragmentFragmentDoc}`;
|
||||
export type DestroyCoreViewFieldMutationFn = Apollo.MutationFunction<DestroyCoreViewFieldMutation, DestroyCoreViewFieldMutationVariables>;
|
||||
|
||||
/**
|
||||
@@ -4808,7 +4830,7 @@ export type DestroyCoreViewFieldMutationFn = Apollo.MutationFunction<DestroyCore
|
||||
* @example
|
||||
* const [destroyCoreViewFieldMutation, { data, loading, error }] = useDestroyCoreViewFieldMutation({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
@@ -4978,8 +5000,8 @@ export type UpdateCoreViewMutationHookResult = ReturnType<typeof useUpdateCoreVi
|
||||
export type UpdateCoreViewMutationResult = Apollo.MutationResult<UpdateCoreViewMutation>;
|
||||
export type UpdateCoreViewMutationOptions = Apollo.BaseMutationOptions<UpdateCoreViewMutation, UpdateCoreViewMutationVariables>;
|
||||
export const UpdateCoreViewFieldDocument = gql`
|
||||
mutation UpdateCoreViewField($id: String!, $input: UpdateViewFieldInput!) {
|
||||
updateCoreViewField(id: $id, input: $input) {
|
||||
mutation UpdateCoreViewField($input: UpdateViewFieldInput!) {
|
||||
updateCoreViewField(input: $input) {
|
||||
...ViewFieldFragment
|
||||
}
|
||||
}
|
||||
@@ -4999,7 +5021,6 @@ export type UpdateCoreViewFieldMutationFn = Apollo.MutationFunction<UpdateCoreVi
|
||||
* @example
|
||||
* const [updateCoreViewFieldMutation, { data, loading, error }] = useUpdateCoreViewFieldMutation({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
|
||||
+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);
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export class FlatEntityMapsException extends CustomException {
|
||||
code: FlatEntityMapsExceptionCode;
|
||||
|
||||
constructor(message: string, code: FlatEntityMapsExceptionCode) {
|
||||
super(message, code);
|
||||
}
|
||||
}
|
||||
|
||||
export enum FlatEntityMapsExceptionCode {
|
||||
ENTITY_ALREADY_EXISTS = 'ENTITY_ALREADY_EXISTS',
|
||||
ENTITY_NOT_FOUND = 'ENTITY_NOT_FOUND',
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export class FlatEntityException extends CustomException {
|
||||
code: FlatEntityExceptionCode;
|
||||
|
||||
constructor(message: string, code: FlatEntityExceptionCode) {
|
||||
super(message, code);
|
||||
}
|
||||
}
|
||||
|
||||
export enum FlatEntityExceptionCode {
|
||||
ENTITY_ALREADY_EXISTS = 'ENTITY_ALREADY_EXISTS',
|
||||
ENTITY_NOT_FOUND = 'ENTITY_NOT_FOUND',
|
||||
}
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
import { isDefined } from 'class-validator';
|
||||
|
||||
import {
|
||||
FlatEntityException,
|
||||
FlatEntityExceptionCode,
|
||||
} from 'src/engine/core-modules/common/exceptions/flat-entity.exception';
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/core-modules/common/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
|
||||
import { type FlatEntity } from 'src/engine/core-modules/common/types/flat-entity.type';
|
||||
|
||||
@@ -17,9 +17,9 @@ export const addFlatEntityToFlatEntityMapsOrThrow = <T extends FlatEntity>({
|
||||
flatEntityMaps,
|
||||
}: AddFlatEntityToFlatEntityMapsOrThrowArgs<T>): FlatEntityMaps<T> => {
|
||||
if (isDefined(flatEntityMaps.byId[flatEntity.id])) {
|
||||
throw new FlatEntityException(
|
||||
throw new FlatEntityMapsException(
|
||||
'addFlatEntityToFlatEntityMapsOrThrow: flat entity to add already exists',
|
||||
FlatEntityExceptionCode.ENTITY_ALREADY_EXISTS,
|
||||
FlatEntityMapsExceptionCode.ENTITY_ALREADY_EXISTS,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -1,11 +1,11 @@
|
||||
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/core-modules/common/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
|
||||
import { type FlatEntity } from 'src/engine/core-modules/common/types/flat-entity.type';
|
||||
import {
|
||||
FlatEntityException,
|
||||
FlatEntityExceptionCode,
|
||||
} from 'src/engine/core-modules/common/exceptions/flat-entity.exception';
|
||||
|
||||
export type DeleteFlatEntityFromFlatEntityMapsOrThrowArgs<
|
||||
T extends FlatEntity,
|
||||
@@ -21,9 +21,9 @@ export const deleteFlatEntityFromFlatEntityMapsOrThrow = <
|
||||
entityToDeleteId,
|
||||
}: DeleteFlatEntityFromFlatEntityMapsOrThrowArgs<T>): FlatEntityMaps<T> => {
|
||||
if (!isDefined(flatEntityMaps.byId[entityToDeleteId])) {
|
||||
throw new FlatEntityException(
|
||||
throw new FlatEntityMapsException(
|
||||
'deleteFlatEntityFromFlatEntityMapsOrThrow: entity to delete not found',
|
||||
FlatEntityExceptionCode.ENTITY_NOT_FOUND,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/core-modules/common/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
|
||||
import { type FlatEntity } from 'src/engine/core-modules/common/types/flat-entity.type';
|
||||
|
||||
export const findFlatEntityByIdInFlatEntityMapsOrThrow = <
|
||||
T extends FlatEntity,
|
||||
>({
|
||||
flatEntityMaps,
|
||||
flatEntityId,
|
||||
}: {
|
||||
flatEntityMaps: FlatEntityMaps<T>;
|
||||
flatEntityId: string;
|
||||
}): T => {
|
||||
const flatEntity = flatEntityMaps.byId[flatEntityId];
|
||||
|
||||
if (!isDefined(flatEntity)) {
|
||||
throw new FlatEntityMapsException(
|
||||
t`Could not find flat entity in maps`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return flatEntity;
|
||||
};
|
||||
+1
-1
@@ -75,7 +75,7 @@ export class ViewFieldController {
|
||||
@Patch(':id')
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() input: UpdateViewFieldInput,
|
||||
@Body() input: UpdateViewFieldInput['update'],
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<ViewFieldEntity> {
|
||||
const updatedViewField = await this.viewFieldService.update(
|
||||
|
||||
+20
@@ -1,28 +1,48 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class CreateViewFieldInput {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
id?: string;
|
||||
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
fieldMetadataId: string;
|
||||
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
viewId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@Field({ nullable: true, defaultValue: true })
|
||||
isVisible?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Field({ nullable: true, defaultValue: 0 })
|
||||
size?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Field({ nullable: true, defaultValue: 0 })
|
||||
position?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(AggregateOperations)
|
||||
@Field(() => AggregateOperations, { nullable: true })
|
||||
aggregateOperation?: AggregateOperations;
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class DeleteViewFieldInput {
|
||||
@IDField(() => UUIDScalarType, {
|
||||
description: 'The id of the view field to delete.',
|
||||
})
|
||||
@IsUUID()
|
||||
id: string;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class DestroyViewFieldInput {
|
||||
@IDField(() => UUIDScalarType, {
|
||||
description: 'The id of the view field to destroy.',
|
||||
})
|
||||
@IsUUID()
|
||||
id: string;
|
||||
}
|
||||
+38
-1
@@ -1,18 +1,55 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsUUID,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class UpdateViewFieldInput {
|
||||
class UpdateViewFieldInputUpdates {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@Field({ nullable: true })
|
||||
isVisible?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Field({ nullable: true })
|
||||
size?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Field({ nullable: true })
|
||||
position?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(AggregateOperations)
|
||||
@Field(() => AggregateOperations, { nullable: true })
|
||||
aggregateOperation?: AggregateOperations;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
export class UpdateViewFieldInput {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field(() => UUIDScalarType, {
|
||||
description: 'The id of the view field to update',
|
||||
})
|
||||
id: string;
|
||||
|
||||
@Type(() => UpdateViewFieldInputUpdates)
|
||||
@ValidateNested()
|
||||
@Field(() => UpdateViewFieldInputUpdates, {
|
||||
description: 'The view field to update',
|
||||
})
|
||||
update: UpdateViewFieldInputUpdates;
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { v4 } from 'uuid';
|
||||
import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils';
|
||||
|
||||
import { type CreateViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/create-view-field.input';
|
||||
import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
|
||||
|
||||
export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({
|
||||
createViewFieldInput: rawCreateViewFieldInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
createViewFieldInput: CreateViewFieldInput;
|
||||
workspaceId: string;
|
||||
}): FlatViewField => {
|
||||
const { fieldMetadataId, viewId, ...createViewFieldInput } =
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
|
||||
rawCreateViewFieldInput,
|
||||
['aggregateOperation', 'fieldMetadataId', 'id', 'viewId'],
|
||||
);
|
||||
|
||||
const createdAt = new Date();
|
||||
const viewFieldId = createViewFieldInput.id ?? v4();
|
||||
|
||||
return {
|
||||
id: viewFieldId,
|
||||
fieldMetadataId,
|
||||
viewId,
|
||||
workspaceId,
|
||||
createdAt: createdAt,
|
||||
updatedAt: createdAt,
|
||||
deletedAt: null,
|
||||
universalIdentifier: viewFieldId,
|
||||
isVisible: createViewFieldInput.isVisible ?? true,
|
||||
size: createViewFieldInput.size ?? 0,
|
||||
position: createViewFieldInput.position ?? 0,
|
||||
aggregateOperation: createViewFieldInput.aggregateOperation ?? null,
|
||||
};
|
||||
};
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
extractAndSanitizeObjectStringFields,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { type DeleteViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/delete-view-field.input';
|
||||
import {
|
||||
ViewFieldException,
|
||||
ViewFieldExceptionCode,
|
||||
} from 'src/engine/core-modules/view/exceptions/view-field.exception';
|
||||
import { type FlatViewFieldMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-field-maps.type';
|
||||
import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
|
||||
|
||||
export const fromDeleteViewFieldInputToFlatViewFieldOrThrow = ({
|
||||
deleteViewFieldInput: rawDeleteViewFieldInput,
|
||||
flatViewFieldMaps,
|
||||
}: {
|
||||
deleteViewFieldInput: DeleteViewFieldInput;
|
||||
flatViewFieldMaps: FlatViewFieldMaps;
|
||||
}): FlatViewField => {
|
||||
const { id: viewFieldId } = extractAndSanitizeObjectStringFields(
|
||||
rawDeleteViewFieldInput,
|
||||
['id'],
|
||||
);
|
||||
|
||||
const existingFlatViewFieldToDelete = flatViewFieldMaps.byId[viewFieldId];
|
||||
|
||||
if (!isDefined(existingFlatViewFieldToDelete)) {
|
||||
throw new ViewFieldException(
|
||||
t`View field to delete not found`,
|
||||
ViewFieldExceptionCode.VIEW_FIELD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...existingFlatViewFieldToDelete,
|
||||
deletedAt: new Date(),
|
||||
};
|
||||
};
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
extractAndSanitizeObjectStringFields,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { type DestroyViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/destroy-view-field.input';
|
||||
import {
|
||||
ViewFieldException,
|
||||
ViewFieldExceptionCode,
|
||||
} from 'src/engine/core-modules/view/exceptions/view-field.exception';
|
||||
import { type FlatViewFieldMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-field-maps.type';
|
||||
import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
|
||||
|
||||
export const fromDestroyViewFieldInputToFlatViewFieldOrThrow = ({
|
||||
destroyViewFieldInput: rawDeleteDestroyViewInput,
|
||||
flatViewFieldMaps,
|
||||
}: {
|
||||
destroyViewFieldInput: DestroyViewFieldInput;
|
||||
flatViewFieldMaps: FlatViewFieldMaps;
|
||||
}): FlatViewField => {
|
||||
const { id: viewFieldId } = extractAndSanitizeObjectStringFields(
|
||||
rawDeleteDestroyViewInput,
|
||||
['id'],
|
||||
);
|
||||
|
||||
const existingFlatViewFieldToDestroy = flatViewFieldMaps.byId[viewFieldId];
|
||||
|
||||
if (!isDefined(existingFlatViewFieldToDestroy)) {
|
||||
throw new ViewFieldException(
|
||||
t`View field to destroy not found`,
|
||||
ViewFieldExceptionCode.VIEW_FIELD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return existingFlatViewFieldToDestroy;
|
||||
};
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
|
||||
|
||||
export const fromPartialFlatViewFieldToFlatViewFieldWithDefault = (
|
||||
partialFlatViewField: Partial<FlatViewField>,
|
||||
): FlatViewField => {
|
||||
const createdAt = new Date();
|
||||
const viewFieldId = partialFlatViewField.id ?? v4();
|
||||
|
||||
return {
|
||||
...partialFlatViewField,
|
||||
id: viewFieldId,
|
||||
fieldMetadataId: partialFlatViewField.fieldMetadataId ?? '',
|
||||
isVisible: partialFlatViewField.isVisible ?? true,
|
||||
size: partialFlatViewField.size ?? 0,
|
||||
position: partialFlatViewField.position ?? 0,
|
||||
aggregateOperation: partialFlatViewField.aggregateOperation ?? null,
|
||||
viewId: partialFlatViewField.viewId ?? '',
|
||||
workspaceId: partialFlatViewField.workspaceId ?? '',
|
||||
createdAt: createdAt,
|
||||
updatedAt: createdAt,
|
||||
deletedAt: null,
|
||||
universalIdentifier:
|
||||
partialFlatViewField.universalIdentifier ?? viewFieldId,
|
||||
};
|
||||
};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
extractAndSanitizeObjectStringFields,
|
||||
isDefined,
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { type UpdateViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/update-view-field.input';
|
||||
import {
|
||||
ViewFieldException,
|
||||
ViewFieldExceptionCode,
|
||||
} from 'src/engine/core-modules/view/exceptions/view-field.exception';
|
||||
import { FLAT_VIEW_FIELD_PROPERTIES_TO_COMPARE } from 'src/engine/core-modules/view/flat-view/constants/flat-view-field-properties-to-compare.constant';
|
||||
import { type FlatViewFieldMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-field-maps.type';
|
||||
import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
|
||||
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
|
||||
|
||||
export const fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow = ({
|
||||
updateViewFieldInput: rawUpdateViewFieldInput,
|
||||
flatViewFieldMaps,
|
||||
}: {
|
||||
updateViewFieldInput: UpdateViewFieldInput;
|
||||
flatViewFieldMaps: FlatViewFieldMaps;
|
||||
}): FlatViewField => {
|
||||
const { id: viewFieldToUpdateId } =
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
|
||||
rawUpdateViewFieldInput,
|
||||
['id'],
|
||||
);
|
||||
|
||||
const existingFlatViewFieldToUpdate =
|
||||
flatViewFieldMaps.byId[viewFieldToUpdateId];
|
||||
|
||||
if (!isDefined(existingFlatViewFieldToUpdate)) {
|
||||
throw new ViewFieldException(
|
||||
t`View field to update not found`,
|
||||
ViewFieldExceptionCode.VIEW_FIELD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
const updatedEditableFieldProperties = extractAndSanitizeObjectStringFields(
|
||||
rawUpdateViewFieldInput.update,
|
||||
FLAT_VIEW_FIELD_PROPERTIES_TO_COMPARE,
|
||||
);
|
||||
|
||||
return mergeUpdateInExistingRecord({
|
||||
existing: existingFlatViewFieldToUpdate,
|
||||
properties: FLAT_VIEW_FIELD_PROPERTIES_TO_COMPARE,
|
||||
update: updatedEditableFieldProperties,
|
||||
});
|
||||
};
|
||||
+88
-27
@@ -1,12 +1,15 @@
|
||||
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { CreateViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/create-view-field.input';
|
||||
import { DeleteViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/delete-view-field.input';
|
||||
import { DestroyViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/destroy-view-field.input';
|
||||
import { UpdateViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/update-view-field.input';
|
||||
import { ViewFieldDTO } from 'src/engine/core-modules/view/dtos/view-field.dto';
|
||||
import { type ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity';
|
||||
import { ViewFieldV2Service } from 'src/engine/core-modules/view/services/view-field-v2.service';
|
||||
import { ViewFieldService } from 'src/engine/core-modules/view/services/view-field.service';
|
||||
import { ViewGraphqlApiExceptionFilter } from 'src/engine/core-modules/view/utils/view-graphql-api-exception.filter';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -17,7 +20,11 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
@UseFilters(ViewGraphqlApiExceptionFilter)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
export class ViewFieldResolver {
|
||||
constructor(private readonly viewFieldService: ViewFieldService) {}
|
||||
constructor(
|
||||
private readonly viewFieldService: ViewFieldService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly viewFieldV2Service: ViewFieldV2Service,
|
||||
) {}
|
||||
|
||||
@Query(() => [ViewFieldDTO])
|
||||
async getCoreViewFields(
|
||||
@@ -37,47 +44,101 @@ export class ViewFieldResolver {
|
||||
|
||||
@Mutation(() => ViewFieldDTO)
|
||||
async updateCoreViewField(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@Args('input') input: UpdateViewFieldInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<ViewFieldEntity> {
|
||||
return this.viewFieldService.update(id, workspace.id, input);
|
||||
@Args('input') updateViewFieldInput: UpdateViewFieldInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
): Promise<ViewFieldDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
return await this.viewFieldV2Service.updateOne({
|
||||
updateViewFieldInput,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
return this.viewFieldService.update(
|
||||
updateViewFieldInput.id,
|
||||
workspaceId,
|
||||
updateViewFieldInput.update,
|
||||
);
|
||||
}
|
||||
|
||||
@Mutation(() => ViewFieldDTO)
|
||||
async createCoreViewField(
|
||||
@Args('input') input: CreateViewFieldInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<ViewFieldEntity> {
|
||||
@Args('input') createViewFieldInput: CreateViewFieldInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
): Promise<ViewFieldDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
return await this.viewFieldV2Service.createOne({
|
||||
createViewFieldInput,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
return this.viewFieldService.create({
|
||||
...input,
|
||||
workspaceId: workspace.id,
|
||||
...createViewFieldInput,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@Mutation(() => ViewFieldDTO)
|
||||
async deleteCoreViewField(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<boolean> {
|
||||
@Args('input') deleteViewFieldInput: DeleteViewFieldInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
): Promise<ViewFieldDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
return await this.viewFieldV2Service.deleteOne({
|
||||
deleteViewFieldInput,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
const deletedViewField = await this.viewFieldService.delete(
|
||||
id,
|
||||
workspace.id,
|
||||
deleteViewFieldInput.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return isDefined(deletedViewField);
|
||||
return deletedViewField;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@Mutation(() => ViewFieldDTO)
|
||||
async destroyCoreViewField(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<boolean> {
|
||||
@Args('input') destroyViewFieldInput: DestroyViewFieldInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
): Promise<ViewFieldDTO> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
return await this.viewFieldV2Service.destroyOne({
|
||||
destroyViewFieldInput,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
const deletedViewField = await this.viewFieldService.destroy(
|
||||
id,
|
||||
workspace.id,
|
||||
destroyViewFieldInput.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return isDefined(deletedViewField);
|
||||
return deletedViewField;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-14
@@ -150,19 +150,6 @@ describe('ViewFieldService', () => {
|
||||
size: 100,
|
||||
};
|
||||
|
||||
it('should create a view field successfully', async () => {
|
||||
jest.spyOn(viewFieldRepository, 'create').mockReturnValue(mockViewField);
|
||||
jest.spyOn(viewFieldRepository, 'save').mockResolvedValue(mockViewField);
|
||||
|
||||
const result = await viewFieldService.create(validViewFieldData);
|
||||
|
||||
expect(viewFieldRepository.create).toHaveBeenCalledWith(
|
||||
validViewFieldData,
|
||||
);
|
||||
expect(viewFieldRepository.save).toHaveBeenCalledWith(mockViewField);
|
||||
expect(result).toEqual(mockViewField);
|
||||
});
|
||||
|
||||
it('should throw exception when workspaceId is missing', async () => {
|
||||
const invalidData = { ...validViewFieldData, workspaceId: undefined };
|
||||
|
||||
@@ -308,7 +295,7 @@ describe('ViewFieldService', () => {
|
||||
|
||||
expect(viewFieldService.findById).toHaveBeenCalledWith(id, workspaceId);
|
||||
expect(viewFieldRepository.delete).toHaveBeenCalledWith(id);
|
||||
expect(result).toEqual(true);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+126
-106
@@ -1,24 +1,24 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
import { Equal, Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
|
||||
import { CreateViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/create-view-field.input';
|
||||
import { DeleteViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/delete-view-field.input';
|
||||
import { DestroyViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/destroy-view-field.input';
|
||||
import { UpdateViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/update-view-field.input';
|
||||
import { ViewFieldDTO } from 'src/engine/core-modules/view/dtos/view-field.dto';
|
||||
import { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity';
|
||||
import {
|
||||
ViewFieldException,
|
||||
ViewFieldExceptionCode,
|
||||
ViewFieldExceptionMessageKey,
|
||||
generateViewFieldExceptionMessage,
|
||||
generateViewFieldUserFriendlyExceptionMessage,
|
||||
} from 'src/engine/core-modules/view/exceptions/view-field.exception';
|
||||
import { VIEW_FIELD_ENTITY_RELATION_PROPERTIES } from 'src/engine/core-modules/view/flat-view/constants/view-field-entity-relation-properties.constant';
|
||||
import { FlatViewFieldMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-field-maps.type';
|
||||
import { fromPartialFlatViewFieldToFlatViewFieldWithDefault } from 'src/engine/core-modules/view/flat-view/utils/from-partial-flat-view-field-to-flat-view-field-with-default.util';
|
||||
import { fromCreateViewFieldInputToFlatViewFieldToCreate } from 'src/engine/core-modules/view/flat-view/utils/from-create-view-field-input-to-flat-view-field-to-create.util';
|
||||
import { fromDeleteViewFieldInputToFlatViewFieldOrThrow } from 'src/engine/core-modules/view/flat-view/utils/from-delete-view-field-input-to-flat-view-field-or-throw.util';
|
||||
import { fromDestroyViewFieldInputToFlatViewFieldOrThrow } from 'src/engine/core-modules/view/flat-view/utils/from-destroy-view-field-input-to-flat-view-field-or-throw.util';
|
||||
import { fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow } from 'src/engine/core-modules/view/flat-view/utils/from-update-view-field-input-to-flat-view-field-to-update-or-throw.util';
|
||||
import { fromViewFieldEntityToFlatViewField } from 'src/engine/core-modules/view/flat-view/utils/from-view-field-entity-to-flat-view-field.util';
|
||||
import { WorkspaceMigrationOrchestratorException } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-orchestrator-exception';
|
||||
import { WorkspaceMigrationBuildOrchestratorService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service';
|
||||
@@ -32,7 +32,7 @@ export class ViewFieldV2Service {
|
||||
) {}
|
||||
|
||||
// TODO: move to cache service
|
||||
private async getExistingFlatViewFieldMaps(
|
||||
private async getExistingFlatViewFieldMapsFromCache(
|
||||
workspaceId: string,
|
||||
): Promise<FlatViewFieldMaps> {
|
||||
const existingViewFields = await this.viewFieldRepository.find({
|
||||
@@ -56,38 +56,26 @@ export class ViewFieldV2Service {
|
||||
return flatViewFieldMaps;
|
||||
}
|
||||
|
||||
async createOne(
|
||||
viewFieldData: Partial<ViewFieldEntity>,
|
||||
): Promise<ViewFieldEntity> {
|
||||
if (!isDefined(viewFieldData.workspaceId)) {
|
||||
throw new ViewFieldException(
|
||||
generateViewFieldExceptionMessage(
|
||||
ViewFieldExceptionMessageKey.WORKSPACE_ID_REQUIRED,
|
||||
),
|
||||
ViewFieldExceptionCode.INVALID_VIEW_FIELD_DATA,
|
||||
{
|
||||
userFriendlyMessage: generateViewFieldUserFriendlyExceptionMessage(
|
||||
ViewFieldExceptionMessageKey.WORKSPACE_ID_REQUIRED,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
async createOne({
|
||||
createViewFieldInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
createViewFieldInput: CreateViewFieldInput;
|
||||
workspaceId: string;
|
||||
}): Promise<ViewFieldDTO> {
|
||||
const existingFlatViewFieldMaps =
|
||||
await this.getExistingFlatViewFieldMapsFromCache(workspaceId);
|
||||
|
||||
const existingFlatViewFieldMaps = await this.getExistingFlatViewFieldMaps(
|
||||
viewFieldData.workspaceId,
|
||||
);
|
||||
|
||||
const flatViewFieldFromCreateInput =
|
||||
fromPartialFlatViewFieldToFlatViewFieldWithDefault({
|
||||
...viewFieldData,
|
||||
universalIdentifier: viewFieldData.universalIdentifier ?? v4(),
|
||||
const flatViewFieldToCreate =
|
||||
fromCreateViewFieldInputToFlatViewFieldToCreate({
|
||||
createViewFieldInput,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const toFlatViewFieldMaps: FlatViewFieldMaps =
|
||||
addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: flatViewFieldFromCreateInput,
|
||||
flatEntityMaps: existingFlatViewFieldMaps,
|
||||
});
|
||||
const toFlatViewFieldMaps = addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: flatViewFieldToCreate,
|
||||
flatEntityMaps: existingFlatViewFieldMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationOrchestratorService.buildWorkspaceMigrations(
|
||||
@@ -102,7 +90,7 @@ export class ViewFieldV2Service {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingEntities: false,
|
||||
},
|
||||
workspaceId: viewFieldData.workspaceId,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -112,55 +100,35 @@ export class ViewFieldV2Service {
|
||||
);
|
||||
}
|
||||
|
||||
const [createdViewField] = await this.viewFieldRepository.find({
|
||||
where: {
|
||||
id: flatViewFieldFromCreateInput.id,
|
||||
},
|
||||
});
|
||||
const recomputedExistingFlatViewFieldMaps =
|
||||
await this.getExistingFlatViewFieldMapsFromCache(workspaceId);
|
||||
|
||||
return createdViewField;
|
||||
return findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatViewFieldToCreate.id,
|
||||
flatEntityMaps: recomputedExistingFlatViewFieldMaps,
|
||||
});
|
||||
}
|
||||
|
||||
async updateOne(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
updateData: Partial<ViewFieldEntity>,
|
||||
): Promise<ViewFieldEntity> {
|
||||
async updateOne({
|
||||
updateViewFieldInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
updateViewFieldInput: UpdateViewFieldInput;
|
||||
}): Promise<ViewFieldDTO> {
|
||||
const existingFlatViewFieldMaps =
|
||||
await this.getExistingFlatViewFieldMaps(workspaceId);
|
||||
await this.getExistingFlatViewFieldMapsFromCache(workspaceId);
|
||||
|
||||
const existingViewField = existingFlatViewFieldMaps.byId[id];
|
||||
|
||||
if (!isDefined(existingViewField)) {
|
||||
throw new ViewFieldException(
|
||||
generateViewFieldExceptionMessage(
|
||||
ViewFieldExceptionMessageKey.VIEW_FIELD_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
ViewFieldExceptionCode.VIEW_FIELD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const existingViewFieldToUpdate = removePropertiesFromRecord(
|
||||
{
|
||||
...existingViewField,
|
||||
...updateData,
|
||||
},
|
||||
VIEW_FIELD_ENTITY_RELATION_PROPERTIES,
|
||||
);
|
||||
|
||||
const flatViewFieldFromUpdateInput =
|
||||
fromPartialFlatViewFieldToFlatViewFieldWithDefault({
|
||||
...existingViewFieldToUpdate,
|
||||
universalIdentifier:
|
||||
existingViewFieldToUpdate.universalIdentifier ?? '',
|
||||
const optimisticallyUpdatedFlatView =
|
||||
fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow({
|
||||
flatViewFieldMaps: existingFlatViewFieldMaps,
|
||||
updateViewFieldInput,
|
||||
});
|
||||
|
||||
const toFlatViewFieldMaps: FlatViewFieldMaps =
|
||||
replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: flatViewFieldFromUpdateInput,
|
||||
flatEntityMaps: existingFlatViewFieldMaps,
|
||||
});
|
||||
const toFlatViewFieldMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: optimisticallyUpdatedFlatView,
|
||||
flatEntityMaps: existingFlatViewFieldMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationOrchestratorService.buildWorkspaceMigrations(
|
||||
@@ -185,37 +153,89 @@ export class ViewFieldV2Service {
|
||||
);
|
||||
}
|
||||
|
||||
const [updatedViewField] = await this.viewFieldRepository.find({
|
||||
where: {
|
||||
id: Equal(id),
|
||||
},
|
||||
});
|
||||
const recomputedExistingFlatViewFieldMaps =
|
||||
await this.getExistingFlatViewFieldMapsFromCache(workspaceId);
|
||||
|
||||
return updatedViewField;
|
||||
return findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: optimisticallyUpdatedFlatView.id,
|
||||
flatEntityMaps: recomputedExistingFlatViewFieldMaps,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteOne(id: string, workspaceId: string): Promise<boolean> {
|
||||
async deleteOne({
|
||||
deleteViewFieldInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
deleteViewFieldInput: DeleteViewFieldInput;
|
||||
workspaceId: string;
|
||||
}): Promise<ViewFieldDTO> {
|
||||
const existingFlatViewFieldMaps =
|
||||
await this.getExistingFlatViewFieldMaps(workspaceId);
|
||||
await this.getExistingFlatViewFieldMapsFromCache(workspaceId);
|
||||
|
||||
const existingViewFieldToDelete = existingFlatViewFieldMaps.byId[id];
|
||||
const optimisticallyUpdatedFlatViewWithDeletedAt =
|
||||
fromDeleteViewFieldInputToFlatViewFieldOrThrow({
|
||||
flatViewFieldMaps: existingFlatViewFieldMaps,
|
||||
deleteViewFieldInput,
|
||||
});
|
||||
|
||||
if (!isDefined(existingViewFieldToDelete)) {
|
||||
throw new ViewFieldException(
|
||||
generateViewFieldExceptionMessage(
|
||||
ViewFieldExceptionMessageKey.VIEW_FIELD_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
ViewFieldExceptionCode.VIEW_FIELD_NOT_FOUND,
|
||||
const toFlatViewFieldMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: optimisticallyUpdatedFlatViewWithDeletedAt,
|
||||
flatEntityMaps: existingFlatViewFieldMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationOrchestratorService.buildWorkspaceMigrations(
|
||||
{
|
||||
entityMaps: {
|
||||
viewField: {
|
||||
fromFlatViewFieldMaps: existingFlatViewFieldMaps,
|
||||
toFlatViewFieldMaps,
|
||||
},
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingEntities: false,
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationOrchestratorException(
|
||||
'Multiple validation errors occurred while updating view field',
|
||||
);
|
||||
}
|
||||
|
||||
const toFlatViewFieldMaps: FlatViewFieldMaps =
|
||||
deleteFlatEntityFromFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: existingFlatViewFieldMaps,
|
||||
entityToDeleteId: existingViewFieldToDelete.id,
|
||||
const recomputedExistingFlatViewFieldMaps =
|
||||
await this.getExistingFlatViewFieldMapsFromCache(workspaceId);
|
||||
|
||||
return findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: optimisticallyUpdatedFlatViewWithDeletedAt.id,
|
||||
flatEntityMaps: recomputedExistingFlatViewFieldMaps,
|
||||
});
|
||||
}
|
||||
|
||||
async destroyOne({
|
||||
destroyViewFieldInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
destroyViewFieldInput: DestroyViewFieldInput;
|
||||
workspaceId: string;
|
||||
}): Promise<ViewFieldDTO> {
|
||||
const existingFlatViewFieldMaps =
|
||||
await this.getExistingFlatViewFieldMapsFromCache(workspaceId);
|
||||
|
||||
const existingViewFieldToDelete =
|
||||
fromDestroyViewFieldInputToFlatViewFieldOrThrow({
|
||||
destroyViewFieldInput,
|
||||
flatViewFieldMaps: existingFlatViewFieldMaps,
|
||||
});
|
||||
|
||||
const toFlatViewFieldMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: existingFlatViewFieldMaps,
|
||||
entityToDeleteId: existingViewFieldToDelete.id,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationOrchestratorService.buildWorkspaceMigrations(
|
||||
{
|
||||
@@ -239,6 +259,6 @@ export class ViewFieldV2Service {
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
return existingViewFieldToDelete;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,27 @@ export class ViewFieldService {
|
||||
try {
|
||||
const viewField = this.viewFieldRepository.create(viewFieldData);
|
||||
|
||||
return await this.viewFieldRepository.save(viewField);
|
||||
const savedViewField = await this.viewFieldRepository.save(viewField);
|
||||
const createdViewField = await this.findById(
|
||||
savedViewField.id,
|
||||
viewFieldData.workspaceId,
|
||||
);
|
||||
|
||||
if (!isDefined(createdViewField)) {
|
||||
throw new ViewFieldException(
|
||||
generateViewFieldExceptionMessage(
|
||||
ViewFieldExceptionMessageKey.VIEW_FIELD_NOT_FOUND,
|
||||
),
|
||||
ViewFieldExceptionCode.VIEW_FIELD_NOT_FOUND,
|
||||
{
|
||||
userFriendlyMessage: generateViewFieldUserFriendlyExceptionMessage(
|
||||
ViewFieldExceptionMessageKey.VIEW_FIELD_NOT_FOUND,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return createdViewField;
|
||||
} catch (error) {
|
||||
if (
|
||||
error.message.includes('duplicate key value violates unique constraint')
|
||||
@@ -175,7 +195,7 @@ export class ViewFieldService {
|
||||
return viewField;
|
||||
}
|
||||
|
||||
async destroy(id: string, workspaceId: string): Promise<boolean> {
|
||||
async destroy(id: string, workspaceId: string): Promise<ViewFieldEntity> {
|
||||
const viewField = await this.findById(id, workspaceId);
|
||||
|
||||
if (!isDefined(viewField)) {
|
||||
@@ -190,6 +210,6 @@ export class ViewFieldService {
|
||||
|
||||
await this.viewFieldRepository.delete(id);
|
||||
|
||||
return true;
|
||||
return viewField;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { I18nModule } from 'src/engine/core-modules/i18n/i18n.module';
|
||||
import { ViewCacheModule } from 'src/engine/core-modules/view/cache/services/view-cache.module';
|
||||
import { ViewFieldController } from 'src/engine/core-modules/view/controllers/view-field.controller';
|
||||
@@ -45,6 +46,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
|
||||
ViewSortEntity,
|
||||
]),
|
||||
I18nModule,
|
||||
FeatureFlagModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
WorkspaceMetadataCacheModule,
|
||||
WorkspaceMigrationV2Module,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export const mergeUpdateInExistingRecord = <
|
||||
TExisting,
|
||||
P extends keyof TExisting,
|
||||
TUpdate extends Partial<TExisting>,
|
||||
>({
|
||||
existing,
|
||||
properties,
|
||||
update,
|
||||
}: {
|
||||
existing: TExisting;
|
||||
update: TUpdate;
|
||||
properties: P[];
|
||||
}) =>
|
||||
properties.reduce((acc, property) => {
|
||||
const isPropertyUpdated = update[property] !== undefined;
|
||||
|
||||
return {
|
||||
...acc,
|
||||
...(isPropertyUpdated ? { [property]: update[property] } : {}),
|
||||
};
|
||||
}, existing);
|
||||
+17
-6
@@ -15,13 +15,13 @@ import {
|
||||
} from 'test/integration/graphql/utils/view-data-factory.util';
|
||||
import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-graphql.util';
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import {
|
||||
assertViewFieldStructure,
|
||||
cleanupViewRecords,
|
||||
} from 'test/integration/utils/view-test.util';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
|
||||
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import {
|
||||
@@ -201,8 +201,10 @@ describe('View Field Resolver', () => {
|
||||
size: 300,
|
||||
});
|
||||
const updateOperation = updateViewFieldOperationFactory({
|
||||
viewFieldId: viewField.id,
|
||||
data: updateInput,
|
||||
input: {
|
||||
id: viewField.id,
|
||||
update: updateInput,
|
||||
},
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(updateOperation);
|
||||
|
||||
@@ -217,7 +219,12 @@ describe('View Field Resolver', () => {
|
||||
|
||||
it('should throw an error when updating non-existent view field', async () => {
|
||||
const operation = updateViewFieldOperationFactory({
|
||||
viewFieldId: TEST_NOT_EXISTING_VIEW_FIELD_ID,
|
||||
input: {
|
||||
id: TEST_NOT_EXISTING_VIEW_FIELD_ID,
|
||||
update: {
|
||||
position: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
@@ -249,7 +256,9 @@ describe('View Field Resolver', () => {
|
||||
const response = await makeGraphqlAPIRequest(deleteOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
expect(response.body.data.deleteCoreViewField).toBe(true);
|
||||
expect(response.body.data.deleteCoreViewField).toMatchObject({
|
||||
id: viewField.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when deleting non-existent view field', async () => {
|
||||
@@ -286,7 +295,9 @@ describe('View Field Resolver', () => {
|
||||
const response = await makeGraphqlAPIRequest(destroyOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
expect(response.body.data.destroyCoreViewField).toBe(true);
|
||||
expect(response.body.data.destroyCoreViewField).toMatchObject({
|
||||
id: viewField.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when destroying non-existent view field', async () => {
|
||||
|
||||
+8
-3
@@ -1,16 +1,21 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { VIEW_FIELD_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
|
||||
|
||||
export const deleteViewFieldOperationFactory = ({
|
||||
viewFieldId,
|
||||
gqlFields = VIEW_FIELD_GQL_FIELDS,
|
||||
}: {
|
||||
gqlFields?: string;
|
||||
viewFieldId: string;
|
||||
}) => ({
|
||||
query: gql`
|
||||
mutation DeleteCoreViewField($id: String!) {
|
||||
deleteCoreViewField(id: $id)
|
||||
mutation DeleteCoreViewField($input: DeleteViewFieldInput!) {
|
||||
deleteCoreViewField(input: $input) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: viewFieldId,
|
||||
input: { id: viewFieldId },
|
||||
},
|
||||
});
|
||||
|
||||
+8
-3
@@ -1,16 +1,21 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { VIEW_FIELD_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
|
||||
|
||||
export const destroyViewFieldOperationFactory = ({
|
||||
viewFieldId,
|
||||
gqlFields = VIEW_FIELD_GQL_FIELDS,
|
||||
}: {
|
||||
gqlFields?: string;
|
||||
viewFieldId: string;
|
||||
}) => ({
|
||||
query: gql`
|
||||
mutation DestroyCoreViewField($id: String!) {
|
||||
destroyCoreViewField(id: $id)
|
||||
mutation DestroyCoreViewField($input: DestroyViewFieldInput!) {
|
||||
destroyCoreViewField(input: $input){
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: viewFieldId,
|
||||
input: { id: viewFieldId },
|
||||
},
|
||||
});
|
||||
|
||||
+7
-8
@@ -1,24 +1,23 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { VIEW_FIELD_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
|
||||
|
||||
import { type UpdateViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/update-view-field.input';
|
||||
|
||||
export const updateViewFieldOperationFactory = ({
|
||||
gqlFields = VIEW_FIELD_GQL_FIELDS,
|
||||
viewFieldId,
|
||||
data = {},
|
||||
input,
|
||||
}: {
|
||||
gqlFields?: string;
|
||||
viewFieldId: string;
|
||||
data?: object;
|
||||
input: UpdateViewFieldInput;
|
||||
}) => ({
|
||||
query: gql`
|
||||
mutation UpdateCoreViewField($id: String!, $input: UpdateViewFieldInput!) {
|
||||
updateCoreViewField(id: $id, input: $input) {
|
||||
mutation UpdateCoreViewField($input: UpdateViewFieldInput!) {
|
||||
updateCoreViewField(input: $input) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: viewFieldId,
|
||||
input: data,
|
||||
input,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { type UpdateViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/update-view-field.input';
|
||||
import { type ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity';
|
||||
import { type ViewFilterGroupEntity } from 'src/engine/core-modules/view/entities/view-filter-group.entity';
|
||||
import { type ViewFilterEntity } from 'src/engine/core-modules/view/entities/view-filter.entity';
|
||||
@@ -40,7 +41,7 @@ export const createViewFieldData = (
|
||||
});
|
||||
|
||||
export const updateViewFieldData = (
|
||||
overrides: Partial<ViewFieldEntity> = {},
|
||||
overrides: Partial<UpdateViewFieldInput['update']> = {},
|
||||
) => ({
|
||||
position: 5,
|
||||
isVisible: false,
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
TEST_VIEW_1_ID,
|
||||
} from 'test/integration/constants/test-view-ids.constants';
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util';
|
||||
import {
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
cleanupViewRecords,
|
||||
} from 'test/integration/utils/view-test.util';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
|
||||
import {
|
||||
generateViewFieldExceptionMessage,
|
||||
@@ -37,6 +37,7 @@ describe('View Field REST API', () => {
|
||||
createOneObject: { id: objectMetadataId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: 'myTestObject',
|
||||
namePlural: 'myTestObjects',
|
||||
@@ -61,6 +62,7 @@ describe('View Field REST API', () => {
|
||||
createOneField: { id: fieldMetadataId },
|
||||
},
|
||||
} = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: createFieldInput,
|
||||
gqlFields: `
|
||||
id
|
||||
@@ -75,6 +77,7 @@ describe('View Field REST API', () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ export type HasAllProperties<T, U> = [T] extends [new (...args: any[]) => any]
|
||||
: [U] extends [new (...args: any[]) => any]
|
||||
? HasAllProperties<T, InstanceType<U>>
|
||||
: {
|
||||
[K in keyof U]: K extends keyof T ? Equal<T[K], U[K]> : false;
|
||||
[K in keyof U]-?: K extends keyof T ? Equal<T[K], U[K]> : false;
|
||||
}[keyof U] extends true
|
||||
? true
|
||||
: false;
|
||||
@@ -51,6 +51,7 @@ type BasicTests = [
|
||||
|
||||
Expect<HasAllProperties<{ a: string; b: number }, { a: string }>>,
|
||||
Expect<HasAllProperties<{ a: string; b: number }, { a: string; b: number }>>,
|
||||
Expect<HasAllProperties<{ a?: string; b: number }, { a?: string; b: number }>>,
|
||||
|
||||
Expect<HasAllProperties<{ a: never; b: never }, { a: never }>>,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user