[BREAKING_CHANGE_VIEW_SORT] Refactor view sort to v2 (#17609)

Fixes https://github.com/twentyhq/core-team-issues/issues/2187

---------

Co-authored-by: prastoin <paul@twenty.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
BOHEUS
2026-03-04 12:48:58 +00:00
committed by GitHub
parent 906a0aed38
commit c97d872b9f
135 changed files with 3262 additions and 815 deletions
@@ -237,6 +237,7 @@ export enum AllMetadataName {
viewFilter = 'viewFilter',
viewFilterGroup = 'viewFilterGroup',
viewGroup = 'viewGroup',
viewSort = 'viewSort',
webhook = 'webhook'
}
@@ -1372,6 +1373,11 @@ export type DeleteViewGroupInput = {
id: Scalars['UUID'];
};
export type DeleteViewSortInput = {
/** The id of the view sort to delete. */
id: Scalars['UUID'];
};
export type DeletedWorkspaceMember = {
__typename?: 'DeletedWorkspaceMember';
avatarUrl?: Maybe<Scalars['String']>;
@@ -1401,6 +1407,11 @@ export type DestroyViewGroupInput = {
id: Scalars['UUID'];
};
export type DestroyViewSortInput = {
/** The id of the view sort to destroy. */
id: Scalars['UUID'];
};
export type DomainRecord = {
__typename?: 'DomainRecord';
key: Scalars['String'];
@@ -2744,7 +2755,7 @@ export type MutationDeleteCoreViewGroupArgs = {
export type MutationDeleteCoreViewSortArgs = {
id: Scalars['String'];
input: DeleteViewSortInput;
};
@@ -2870,7 +2881,7 @@ export type MutationDestroyCoreViewGroupArgs = {
export type MutationDestroyCoreViewSortArgs = {
id: Scalars['String'];
input: DestroyViewSortInput;
};
@@ -3156,7 +3167,6 @@ export type MutationUpdateCoreViewGroupArgs = {
export type MutationUpdateCoreViewSortArgs = {
id: Scalars['String'];
input: UpdateViewSortInput;
};
@@ -5022,10 +5032,14 @@ export type UpdateViewInput = {
};
export type UpdateViewSortInput = {
/** The id of the view sort to update */
id: Scalars['UUID'];
/** The view sort to update */
update: UpdateViewSortInputUpdates;
};
export type UpdateViewSortInputUpdates = {
direction?: InputMaybe<ViewSortDirection>;
fieldMetadataId?: InputMaybe<Scalars['UUID']>;
id?: InputMaybe<Scalars['UUID']>;
viewId?: InputMaybe<Scalars['UUID']>;
};
export type UpdateWebhookInput = {
@@ -6272,7 +6286,7 @@ export type UpsertFieldsWidgetMutationVariables = Exact<{
}>;
export type UpsertFieldsWidgetMutation = { __typename?: 'Mutation', upsertFieldsWidget: { __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> } };
export type UpsertFieldsWidgetMutation = { __typename?: 'Mutation', upsertFieldsWidget: { __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string, createdAt: string, deletedAt?: string | null, updatedAt: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> } };
export type FindAllRecordPageLayoutsQueryVariables = Exact<{ [key: string]: never; }>;
@@ -6844,18 +6858,18 @@ export type ViewFilterFragmentFragment = { __typename?: 'CoreViewFilter', id: st
export type ViewFilterGroupFragmentFragment = { __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string };
export type ViewFragmentFragment = { __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> };
export type ViewFragmentFragment = { __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string, createdAt: string, deletedAt?: string | null, updatedAt: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> };
export type ViewGroupFragmentFragment = { __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null };
export type ViewSortFragmentFragment = { __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string };
export type ViewSortFragmentFragment = { __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string, createdAt: string, deletedAt?: string | null, updatedAt: string };
export type CreateCoreViewMutationVariables = Exact<{
input: CreateViewInput;
}>;
export type CreateCoreViewMutation = { __typename?: 'Mutation', createCoreView: { __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> } };
export type CreateCoreViewMutation = { __typename?: 'Mutation', createCoreView: { __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string, createdAt: string, deletedAt?: string | null, updatedAt: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> } };
export type CreateCoreViewFieldMutationVariables = Exact<{
input: CreateViewFieldInput;
@@ -6897,7 +6911,7 @@ export type CreateCoreViewSortMutationVariables = Exact<{
}>;
export type CreateCoreViewSortMutation = { __typename?: 'Mutation', createCoreViewSort: { __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string } };
export type CreateCoreViewSortMutation = { __typename?: 'Mutation', createCoreViewSort: { __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string, createdAt: string, deletedAt?: string | null, updatedAt: string } };
export type CreateManyCoreViewFieldGroupsMutationVariables = Exact<{
inputs: Array<CreateViewFieldGroupInput> | CreateViewFieldGroupInput;
@@ -6963,7 +6977,7 @@ export type DeleteCoreViewGroupMutationVariables = Exact<{
export type DeleteCoreViewGroupMutation = { __typename?: 'Mutation', deleteCoreViewGroup: { __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null } };
export type DeleteCoreViewSortMutationVariables = Exact<{
id: Scalars['String'];
input: DeleteViewSortInput;
}>;
@@ -7005,7 +7019,7 @@ export type DestroyCoreViewGroupMutationVariables = Exact<{
export type DestroyCoreViewGroupMutation = { __typename?: 'Mutation', destroyCoreViewGroup: { __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null } };
export type DestroyCoreViewSortMutationVariables = Exact<{
id: Scalars['String'];
input: DestroyViewSortInput;
}>;
@@ -7017,7 +7031,7 @@ export type UpdateCoreViewMutationVariables = Exact<{
}>;
export type UpdateCoreViewMutation = { __typename?: 'Mutation', updateCoreView: { __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> } };
export type UpdateCoreViewMutation = { __typename?: 'Mutation', updateCoreView: { __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string, createdAt: string, deletedAt?: string | null, updatedAt: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> } };
export type UpdateCoreViewFieldMutationVariables = Exact<{
input: UpdateViewFieldInput;
@@ -7056,26 +7070,25 @@ export type UpdateCoreViewGroupMutationVariables = Exact<{
export type UpdateCoreViewGroupMutation = { __typename?: 'Mutation', updateCoreViewGroup: { __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null } };
export type UpdateCoreViewSortMutationVariables = Exact<{
id: Scalars['String'];
input: UpdateViewSortInput;
}>;
export type UpdateCoreViewSortMutation = { __typename?: 'Mutation', updateCoreViewSort: { __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string } };
export type UpdateCoreViewSortMutation = { __typename?: 'Mutation', updateCoreViewSort: { __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string, createdAt: string, deletedAt?: string | null, updatedAt: string } };
export type FindAllCoreViewsQueryVariables = Exact<{
viewTypes?: InputMaybe<Array<ViewType> | ViewType>;
}>;
export type FindAllCoreViewsQuery = { __typename?: 'Query', getCoreViews: Array<{ __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> }> };
export type FindAllCoreViewsQuery = { __typename?: 'Query', getCoreViews: Array<{ __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string, createdAt: string, deletedAt?: string | null, updatedAt: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> }> };
export type FindFieldsWidgetCoreViewsQueryVariables = Exact<{
viewTypes?: InputMaybe<Array<ViewType> | ViewType>;
}>;
export type FindFieldsWidgetCoreViewsQuery = { __typename?: 'Query', getCoreViews: Array<{ __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> }> };
export type FindFieldsWidgetCoreViewsQuery = { __typename?: 'Query', getCoreViews: Array<{ __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string, createdAt: string, deletedAt?: string | null, updatedAt: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> }> };
export type FindManyCoreViewFieldsQueryVariables = Exact<{
viewId: Scalars['String'];
@@ -7110,7 +7123,7 @@ export type FindManyCoreViewSortsQueryVariables = Exact<{
}>;
export type FindManyCoreViewSortsQuery = { __typename?: 'Query', getCoreViewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string }> };
export type FindManyCoreViewSortsQuery = { __typename?: 'Query', getCoreViewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string, createdAt: string, deletedAt?: string | null, updatedAt: string }> };
export type FindManyCoreViewsQueryVariables = Exact<{
objectMetadataId?: InputMaybe<Scalars['String']>;
@@ -7118,14 +7131,14 @@ export type FindManyCoreViewsQueryVariables = Exact<{
}>;
export type FindManyCoreViewsQuery = { __typename?: 'Query', getCoreViews: Array<{ __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> }> };
export type FindManyCoreViewsQuery = { __typename?: 'Query', getCoreViews: Array<{ __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string, createdAt: string, deletedAt?: string | null, updatedAt: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> }> };
export type FindOneCoreViewQueryVariables = Exact<{
id: Scalars['String'];
}>;
export type FindOneCoreViewQuery = { __typename?: 'Query', getCoreView?: { __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> } | null };
export type FindOneCoreViewQuery = { __typename?: 'Query', getCoreView?: { __typename?: 'CoreView', id: string, name: string, objectMetadataId: string, type: ViewType, key?: ViewKey | null, icon: string, position: number, isCompact: boolean, openRecordIn: ViewOpenRecordIn, kanbanAggregateOperation?: AggregateOperations | null, kanbanAggregateOperationFieldMetadataId?: string | null, mainGroupByFieldMetadataId?: string | null, shouldHideEmptyGroups: boolean, anyFieldFilterValue?: string | null, calendarFieldMetadataId?: string | null, calendarLayout?: ViewCalendarLayout | null, visibility: ViewVisibility, createdByUserWorkspaceId?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFieldGroups: Array<{ __typename?: 'CoreViewFieldGroup', id: string, name: string, position: number, isVisible: boolean, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null, viewFields: Array<{ __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }> }>, viewFilters: Array<{ __typename?: 'CoreViewFilter', id: string, fieldMetadataId: string, operand: ViewFilterOperand, value: any, viewFilterGroupId?: string | null, positionInViewFilterGroup?: number | null, subFieldName?: string | null, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }>, viewFilterGroups: Array<{ __typename?: 'CoreViewFilterGroup', id: string, parentViewFilterGroupId?: string | null, logicalOperator: ViewFilterGroupLogicalOperator, positionInViewFilterGroup?: number | null, viewId: string }>, viewSorts: Array<{ __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string, createdAt: string, deletedAt?: string | null, updatedAt: string }>, viewGroups: Array<{ __typename?: 'CoreViewGroup', id: string, isVisible: boolean, fieldValue: string, position: number, viewId: string, createdAt: string, updatedAt: string, deletedAt?: string | null }> } | null };
export type FindOneCoreViewFieldQueryVariables = Exact<{
id: Scalars['String'];
@@ -7160,7 +7173,7 @@ export type FindOneCoreViewSortQueryVariables = Exact<{
}>;
export type FindOneCoreViewSortQuery = { __typename?: 'Query', getCoreViewSort?: { __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string } | null };
export type FindOneCoreViewSortQuery = { __typename?: 'Query', getCoreViewSort?: { __typename?: 'CoreViewSort', id: string, fieldMetadataId: string, direction: ViewSortDirection, viewId: string, createdAt: string, deletedAt?: string | null, updatedAt: string } | null };
export type DeleteWorkspaceInvitationMutationVariables = Exact<{
appTokenId: Scalars['String'];
@@ -8250,6 +8263,9 @@ export const ViewSortFragmentFragmentDoc = gql`
fieldMetadataId
direction
viewId
createdAt
deletedAt
updatedAt
}
`;
export const ViewGroupFragmentFragmentDoc = gql`
@@ -15601,8 +15617,8 @@ export type DeleteCoreViewGroupMutationHookResult = ReturnType<typeof useDeleteC
export type DeleteCoreViewGroupMutationResult = Apollo.MutationResult<DeleteCoreViewGroupMutation>;
export type DeleteCoreViewGroupMutationOptions = Apollo.BaseMutationOptions<DeleteCoreViewGroupMutation, DeleteCoreViewGroupMutationVariables>;
export const DeleteCoreViewSortDocument = gql`
mutation DeleteCoreViewSort($id: String!) {
deleteCoreViewSort(id: $id)
mutation DeleteCoreViewSort($input: DeleteViewSortInput!) {
deleteCoreViewSort(input: $input)
}
`;
export type DeleteCoreViewSortMutationFn = Apollo.MutationFunction<DeleteCoreViewSortMutation, DeleteCoreViewSortMutationVariables>;
@@ -15620,7 +15636,7 @@ export type DeleteCoreViewSortMutationFn = Apollo.MutationFunction<DeleteCoreVie
* @example
* const [deleteCoreViewSortMutation, { data, loading, error }] = useDeleteCoreViewSortMutation({
* variables: {
* id: // value for 'id'
* input: // value for 'input'
* },
* });
*/
@@ -15793,8 +15809,8 @@ export type DestroyCoreViewGroupMutationHookResult = ReturnType<typeof useDestro
export type DestroyCoreViewGroupMutationResult = Apollo.MutationResult<DestroyCoreViewGroupMutation>;
export type DestroyCoreViewGroupMutationOptions = Apollo.BaseMutationOptions<DestroyCoreViewGroupMutation, DestroyCoreViewGroupMutationVariables>;
export const DestroyCoreViewSortDocument = gql`
mutation DestroyCoreViewSort($id: String!) {
destroyCoreViewSort(id: $id)
mutation DestroyCoreViewSort($input: DestroyViewSortInput!) {
destroyCoreViewSort(input: $input)
}
`;
export type DestroyCoreViewSortMutationFn = Apollo.MutationFunction<DestroyCoreViewSortMutation, DestroyCoreViewSortMutationVariables>;
@@ -15812,7 +15828,7 @@ export type DestroyCoreViewSortMutationFn = Apollo.MutationFunction<DestroyCoreV
* @example
* const [destroyCoreViewSortMutation, { data, loading, error }] = useDestroyCoreViewSortMutation({
* variables: {
* id: // value for 'id'
* input: // value for 'input'
* },
* });
*/
@@ -16024,8 +16040,8 @@ export type UpdateCoreViewGroupMutationHookResult = ReturnType<typeof useUpdateC
export type UpdateCoreViewGroupMutationResult = Apollo.MutationResult<UpdateCoreViewGroupMutation>;
export type UpdateCoreViewGroupMutationOptions = Apollo.BaseMutationOptions<UpdateCoreViewGroupMutation, UpdateCoreViewGroupMutationVariables>;
export const UpdateCoreViewSortDocument = gql`
mutation UpdateCoreViewSort($id: String!, $input: UpdateViewSortInput!) {
updateCoreViewSort(id: $id, input: $input) {
mutation UpdateCoreViewSort($input: UpdateViewSortInput!) {
updateCoreViewSort(input: $input) {
...ViewSortFragment
}
}
@@ -16045,7 +16061,6 @@ export type UpdateCoreViewSortMutationFn = Apollo.MutationFunction<UpdateCoreVie
* @example
* const [updateCoreViewSortMutation, { data, loading, error }] = useUpdateCoreViewSortMutation({
* variables: {
* id: // value for 'id'
* input: // value for 'input'
* },
* });
@@ -44,6 +44,7 @@ export const useMetadataErrorHandler = () => {
frontComponent: t`front component`,
navigationMenuItem: t`navigation menu item`,
webhook: t`webhook`,
viewSort: t`view sort`,
} as const satisfies Record<AllMetadataName, string>;
const handleMetadataError = (
@@ -6,5 +6,8 @@ export const VIEW_SORT_FRAGMENT = gql`
fieldMetadataId
direction
viewId
createdAt
deletedAt
updatedAt
}
`;
@@ -1,7 +1,7 @@
import { gql } from '@apollo/client';
export const DELETE_CORE_VIEW_SORT = gql`
mutation DeleteCoreViewSort($id: String!) {
deleteCoreViewSort(id: $id)
mutation DeleteCoreViewSort($input: DeleteViewSortInput!) {
deleteCoreViewSort(input: $input)
}
`;
@@ -1,7 +1,7 @@
import { gql } from '@apollo/client';
export const DESTROY_CORE_VIEW_SORT = gql`
mutation DestroyCoreViewSort($id: String!) {
destroyCoreViewSort(id: $id)
mutation DestroyCoreViewSort($input: DestroyViewSortInput!) {
destroyCoreViewSort(input: $input)
}
`;
@@ -3,8 +3,8 @@ import { gql } from '@apollo/client';
export const UPDATE_CORE_VIEW_SORT = gql`
${VIEW_SORT_FRAGMENT}
mutation UpdateCoreViewSort($id: String!, $input: UpdateViewSortInput!) {
updateCoreViewSort(id: $id, input: $input) {
mutation UpdateCoreViewSort($input: UpdateViewSortInput!) {
updateCoreViewSort(input: $input) {
...ViewSortFragment
}
}
@@ -16,6 +16,7 @@ import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jes
import { mockedCoreViews } from '~/testing/mock-data/generated/metadata/views/mock-views-data';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { useApplyCurrentViewSortsToCurrentRecordSorts } from '@/views/hooks/useApplyCurrentViewSortsToCurrentRecordSorts';
import { type ViewSort } from '@/views/types/ViewSort';
const mockObjectMetadataItemNameSingular = 'company';
@@ -38,7 +39,8 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => {
throw new Error('Missing mock field metadata item with type TEXT');
}
const mockViewSort: CoreViewSortEssential = {
const mockViewSort: ViewSort = {
__typename: 'ViewSort',
id: 'sort-1',
fieldMetadataId: mockFieldMetadataItem.id,
direction: ViewSortDirection.ASC,
@@ -103,6 +105,7 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => {
expect(result.current.currentRecordSorts).toEqual([
{
__typename: 'ViewSort',
id: mockViewSort.id,
fieldMetadataId: mockViewSort.fieldMetadataId,
direction: mockViewSort.direction,
@@ -1,107 +1,276 @@
import { useCallback } from 'react';
import { CREATE_CORE_VIEW_SORT } from '@/views/graphql/mutations/createCoreViewSort';
import { DESTROY_CORE_VIEW_SORT } from '@/views/graphql/mutations/destroyCoreViewSort';
import { UPDATE_CORE_VIEW_SORT } from '@/views/graphql/mutations/updateCoreViewSort';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useTriggerViewSortOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewSortOptimisticEffect';
import { type CoreViewSortEssential } from '@/views/types/CoreViewSortEssential';
import { type GraphQLView } from '@/views/types/GraphQLView';
import { useApolloClient } from '@apollo/client';
import { ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type CoreViewSort } from '~/generated-metadata/graphql';
import {
type CreateCoreViewSortMutationVariables,
type DeleteCoreViewSortMutationVariables,
type DestroyCoreViewSortMutationVariables,
type UpdateCoreViewSortMutationVariables,
useCreateCoreViewSortMutation,
useDeleteCoreViewSortMutation,
useDestroyCoreViewSortMutation,
useUpdateCoreViewSortMutation,
} from '~/generated-metadata/graphql';
export const usePerformViewSortAPIPersist = () => {
const apolloClient = useApolloClient();
const { triggerViewSortOptimisticEffect } =
useTriggerViewSortOptimisticEffect();
const [createCoreViewSortMutation] = useCreateCoreViewSortMutation();
const [updateCoreViewSortMutation] = useUpdateCoreViewSortMutation();
const [deleteCoreViewSortMutation] = useDeleteCoreViewSortMutation();
const [destroyCoreViewSortMutation] = useDestroyCoreViewSortMutation();
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const performViewSortAPICreate = useCallback(
(
viewSortsToCreate: CoreViewSortEssential[],
view: Pick<GraphQLView, 'id'>,
) => {
if (!viewSortsToCreate.length) return;
return Promise.all(
viewSortsToCreate.map((viewSort) =>
apolloClient.mutate({
mutation: CREATE_CORE_VIEW_SORT,
variables: {
input: {
id: viewSort.id,
fieldMetadataId: viewSort.fieldMetadataId,
viewId: view.id,
direction: viewSort.direction,
} satisfies Partial<CoreViewSort>,
},
update: (_cache, { data }) => {
const createdViewSort = data?.createCoreViewSort;
if (!isDefined(createdViewSort)) return;
async (
createCoreViewSortInputs: CreateCoreViewSortMutationVariables[],
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof createCoreViewSortMutation>>[]
>
> => {
if (createCoreViewSortInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
triggerViewSortOptimisticEffect({
createdViewSorts: [createdViewSort],
});
},
}),
),
);
try {
const results = await Promise.all(
createCoreViewSortInputs.map((variables) =>
createCoreViewSortMutation({
variables,
update: (_cache, { data }) => {
const createdViewSort = data?.createCoreViewSort;
if (!isDefined(createdViewSort)) {
return;
}
triggerViewSortOptimisticEffect({
createdViewSorts: [createdViewSort],
});
},
}),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'viewSort',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[apolloClient, triggerViewSortOptimisticEffect],
[
triggerViewSortOptimisticEffect,
createCoreViewSortMutation,
handleMetadataError,
enqueueErrorSnackBar,
],
);
const performViewSortAPIUpdate = useCallback(
(viewSortsToUpdate: CoreViewSortEssential[]) => {
if (!viewSortsToUpdate.length) return;
return Promise.all(
viewSortsToUpdate.map((viewSort) =>
apolloClient.mutate({
mutation: UPDATE_CORE_VIEW_SORT,
variables: {
id: viewSort.id,
input: {
direction: viewSort.direction,
} satisfies Partial<CoreViewSort>,
},
update: (_cache, { data }) => {
const updatedViewSort = data?.updateCoreViewSort;
if (!isDefined(updatedViewSort)) return;
async (
updateCoreViewSortInputs: UpdateCoreViewSortMutationVariables[],
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof updateCoreViewSortMutation>>[]
>
> => {
if (updateCoreViewSortInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
triggerViewSortOptimisticEffect({
updatedViewSorts: [updatedViewSort],
});
},
}),
),
);
try {
const results = await Promise.all(
updateCoreViewSortInputs.map((variables) =>
updateCoreViewSortMutation({
variables,
update: (_cache, { data }) => {
const updatedViewSort = data?.updateCoreViewSort;
if (!isDefined(updatedViewSort)) {
return;
}
triggerViewSortOptimisticEffect({
updatedViewSorts: [updatedViewSort],
});
},
}),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'viewSort',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred` });
}
return {
status: 'failed',
error,
};
}
},
[apolloClient, triggerViewSortOptimisticEffect],
[
triggerViewSortOptimisticEffect,
updateCoreViewSortMutation,
handleMetadataError,
enqueueErrorSnackBar,
],
);
const performViewSortAPIDelete = useCallback(
(viewSortsToDelete: Pick<CoreViewSortEssential, 'id' | 'viewId'>[]) => {
if (!viewSortsToDelete.length) return;
return Promise.all(
viewSortsToDelete.map((viewSort) =>
apolloClient.mutate({
mutation: DESTROY_CORE_VIEW_SORT,
variables: {
id: viewSort.id,
},
update: (_cache) => {
triggerViewSortOptimisticEffect({
deletedViewSorts: [viewSort],
});
},
}),
),
);
async (
deleteCoreViewSortInputs: DeleteCoreViewSortMutationVariables[],
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof deleteCoreViewSortMutation>>[]
>
> => {
if (deleteCoreViewSortInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
deleteCoreViewSortInputs.map((variables) =>
deleteCoreViewSortMutation({
variables,
update: (_cache, { data }) => {
const deletedViewSort = data?.deleteCoreViewSort;
if (!isDefined(deletedViewSort)) {
return;
}
triggerViewSortOptimisticEffect({
deletedViewSorts: [
{
id: variables.input.id,
},
],
});
},
}),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'viewSort',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred` });
}
return {
status: 'failed',
error,
};
}
},
[apolloClient, triggerViewSortOptimisticEffect],
[
triggerViewSortOptimisticEffect,
deleteCoreViewSortMutation,
handleMetadataError,
enqueueErrorSnackBar,
],
);
const performViewSortAPIDestroy = useCallback(
async (
destroyCoreViewSortInputs: DestroyCoreViewSortMutationVariables[],
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof destroyCoreViewSortMutation>>[]
>
> => {
if (destroyCoreViewSortInputs.length === 0) {
return {
status: 'successful',
response: [],
};
}
try {
const results = await Promise.all(
destroyCoreViewSortInputs.map((variables) =>
destroyCoreViewSortMutation({
variables,
}),
),
);
return {
status: 'successful',
response: results,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'viewSort',
operationType: CrudOperationType.DESTROY,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred` });
}
return {
status: 'failed',
error,
};
}
},
[destroyCoreViewSortMutation, handleMetadataError, enqueueErrorSnackBar],
);
return {
performViewSortAPICreate,
performViewSortAPIUpdate,
performViewSortAPIDelete,
performViewSortAPIDestroy,
};
};
@@ -106,7 +106,14 @@ export const useApplyCoreViewsForObjectMetadataId = () => {
);
}
if (!isDeeplyEqual(coreView.viewSorts, existingView.viewSorts)) {
if (
!isDeeplyEqual(
coreView.viewSorts.map((viewSort) =>
removePropertiesFromRecord(viewSort, ['createdAt', 'updatedAt']),
),
existingView.viewSorts,
)
) {
const view = convertCoreViewToView(coreView);
jotaiStore.set(
currentRecordSortsComponentState.atomFamily({
@@ -20,7 +20,7 @@ export const useAreViewSortsDifferentFromRecordSorts = () => {
return false;
}
const viewSortsFromCurrentRecordSorts = currentRecordSorts.map(
(recordSort) => mapRecordSortToViewSort(recordSort, currentView.id),
(recordSort) => mapRecordSortToViewSort(recordSort),
);
const viewSortsToCreate = getViewSortsToCreate(
@@ -197,7 +197,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
});
const viewSortsToCreate = currentRecordSorts
.map((recordSort) => mapRecordSortToViewSort(recordSort, newViewId))
.map((recordSort) => mapRecordSortToViewSort(recordSort))
.map((viewSort) => ({
...viewSort,
id: v4(),
@@ -230,7 +230,20 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
return undefined;
}
await performViewSortAPICreate(viewSortsToCreate, { id: newViewId });
const createViewSortInputs = viewSortsToCreate.map((viewSort) => ({
input: {
id: viewSort.id,
fieldMetadataId: viewSort.fieldMetadataId,
viewId: newViewId,
direction: viewSort.direction,
},
}));
const sortResult = await performViewSortAPICreate(createViewSortInputs);
if (sortResult.status === 'failed') {
return undefined;
}
}
await refreshCoreViewsByObjectMetadataId(objectMetadataItem.id);
@@ -36,9 +36,7 @@ export const useSaveRecordSortsToViewSorts = () => {
const currentRecordSorts = store.get(currentRecordSortsCallbackState);
const newViewSorts = currentRecordSorts.map((recordSort) =>
mapRecordSortToViewSort(recordSort, currentView.id),
);
const newViewSorts = currentRecordSorts.map(mapRecordSortToViewSort);
const viewSortsToCreate = getViewSortsToCreate(
currentViewSorts,
@@ -55,13 +53,48 @@ export const useSaveRecordSortsToViewSorts = () => {
newViewSorts,
);
await performViewSortAPICreate(viewSortsToCreate, currentView);
await performViewSortAPIUpdate(viewSortsToUpdate);
await performViewSortAPIDelete(viewSortsToDelete);
const createViewSortInputs = viewSortsToCreate.map((viewSort) => ({
input: {
id: viewSort.id,
fieldMetadataId: viewSort.fieldMetadataId,
viewId: currentView.id,
direction: viewSort.direction,
},
}));
const updateViewSortInputs = viewSortsToUpdate.map((viewSort) => ({
input: {
id: viewSort.id,
update: {
direction: viewSort.direction,
},
},
}));
const deleteViewSortInputs = viewSortsToDelete.map((viewSort) => ({
input: {
id: viewSort.id,
},
}));
const createResult = await performViewSortAPICreate(createViewSortInputs);
if (createResult.status === 'failed') {
return;
}
const updateResult = await performViewSortAPIUpdate(updateViewSortInputs);
if (updateResult.status === 'failed') {
return;
}
const deleteResult = await performViewSortAPIDelete(deleteViewSortInputs);
if (deleteResult.status === 'failed') {
return;
}
}, [
store,
canPersistChanges,
currentView,
store,
currentRecordSortsCallbackState,
performViewSortAPICreate,
performViewSortAPIUpdate,
@@ -1,11 +1,11 @@
import { coreViewsState } from '@/views/states/coreViewState';
import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations';
import { useApolloClient } from '@apollo/client';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { type CoreViewSort } from '~/generated-metadata/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { useStore } from 'jotai';
export const useTriggerViewSortOptimisticEffect = () => {
const store = useStore();
@@ -21,7 +21,7 @@ export const useTriggerViewSortOptimisticEffect = () => {
}: {
createdViewSorts?: Omit<CoreViewSort, 'workspaceId'>[];
updatedViewSorts?: Omit<CoreViewSort, 'workspaceId'>[];
deletedViewSorts?: Pick<CoreViewSort, 'id' | 'viewId'>[];
deletedViewSorts?: Pick<CoreViewSort, 'id'>[];
}) => {
const coreViews = store.get(coreViewsState.atom);
let newCoreViews = [...coreViews];
@@ -93,40 +93,45 @@ export const useTriggerViewSortOptimisticEffect = () => {
}
});
deletedViewSorts.forEach(
(deletedViewSort: Pick<CoreViewSort, 'id' | 'viewId'>) => {
cache.modify<CoreViewWithRelations>({
id: cache.identify({
__typename: 'CoreView',
id: deletedViewSort.viewId,
}),
fields: {
viewSorts: (existingViewSorts, { readField }) =>
existingViewSorts.filter(
(viewSort) =>
readField('id', viewSort) !== deletedViewSort.id,
),
},
});
const toBeModifiedCoreView = newCoreViews.find(
(coreView) => coreView.id === deletedViewSort.viewId,
);
deletedViewSorts.forEach((deletedViewSort: Pick<CoreViewSort, 'id'>) => {
const viewId = coreViews.find((coreView) =>
coreView.viewSorts.some(
(viewSort) => viewSort.id === deletedViewSort.id,
),
)?.id;
if (isDefined(toBeModifiedCoreView)) {
newCoreViews = [
...newCoreViews.filter(
(coreView) => coreView.id !== deletedViewSort.viewId,
if (!viewId) {
return;
}
cache.modify<CoreViewWithRelations>({
id: cache.identify({
__typename: 'CoreView',
id: viewId,
}),
fields: {
viewSorts: (existingViewSorts, { readField }) =>
existingViewSorts.filter(
(viewSort) => readField('id', viewSort) !== deletedViewSort.id,
),
{
...toBeModifiedCoreView,
viewSorts: toBeModifiedCoreView.viewSorts.filter(
(viewSort) => viewSort.id !== deletedViewSort.id,
),
},
];
}
},
);
},
});
const toBeModifiedCoreView = newCoreViews.find(
(coreView) => coreView.id === viewId,
);
if (isDefined(toBeModifiedCoreView)) {
newCoreViews = [
...newCoreViews.filter((coreView) => coreView.id !== viewId),
{
...toBeModifiedCoreView,
viewSorts: toBeModifiedCoreView.viewSorts.filter(
(viewSort) => viewSort.id !== deletedViewSort.id,
),
},
];
}
});
if (!isDeeplyEqual(coreViews, newCoreViews)) {
store.set(coreViewsState.atom, newCoreViews);
@@ -1,4 +1,3 @@
import { type CoreViewSortEssential } from '@/views/types/CoreViewSortEssential';
import {
type AggregateOperations,
type CoreViewField,
@@ -6,6 +5,7 @@ import {
type CoreViewFilter,
type CoreViewFilterGroup,
type CoreViewGroup,
type CoreViewSort,
type ViewCalendarLayout,
type ViewKey,
type ViewOpenRecordIn,
@@ -43,7 +43,7 @@ export type CoreViewWithRelations = {
CoreViewFilterGroup,
'workspaceId' | 'createdAt' | 'updatedAt'
>[];
viewSorts: CoreViewSortEssential[];
viewSorts: Omit<CoreViewSort, 'workspaceId' | 'createdAt' | 'updatedAt'>[];
mainGroupByFieldMetadataId?: string | null;
shouldHideEmptyGroups: boolean;
kanbanAggregateOperation?: AggregateOperations | null;
@@ -1,5 +1,4 @@
import { type AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { type CoreViewSortEssential } from '@/views/types/CoreViewSortEssential';
import { type ViewField } from '@/views/types/ViewField';
import { type ViewFilter } from '@/views/types/ViewFilter';
import { type ViewFilterGroup } from '@/views/types/ViewFilterGroup';
@@ -11,6 +10,7 @@ import {
type ViewVisibility,
type ViewCalendarLayout,
} from '~/generated-metadata/graphql';
import { type ViewSort } from '@/views/types/ViewSort';
export type GraphQLView = {
id: string;
name: string;
@@ -26,7 +26,7 @@ export type GraphQLView = {
viewFields: ViewField[];
viewFilters: ViewFilter[];
viewFilterGroups?: ViewFilterGroup[];
viewSorts: CoreViewSortEssential[];
viewSorts: ViewSort[];
viewGroups: ViewGroup[];
position: number;
icon: string;
@@ -1,5 +1,4 @@
import { type AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { type CoreViewSortEssential } from '@/views/types/CoreViewSortEssential';
import { type ViewField } from '@/views/types/ViewField';
import { type ViewFieldGroup } from '@/views/types/ViewFieldGroup';
import { type ViewFilter } from '@/views/types/ViewFilter';
@@ -12,6 +11,7 @@ import {
type ViewCalendarLayout,
type ViewVisibility,
} from '~/generated-metadata/graphql';
import { type ViewSort } from '@/views/types/ViewSort';
export type View = {
id: string;
@@ -25,7 +25,7 @@ export type View = {
viewGroups: ViewGroup[];
viewFilters: ViewFilter[];
viewFilterGroups?: ViewFilterGroup[];
viewSorts: CoreViewSortEssential[];
viewSorts: ViewSort[];
kanbanAggregateOperation: AggregateOperations | null;
kanbanAggregateOperationFieldMetadataId: string | null;
mainGroupByFieldMetadataId?: string | null;
@@ -0,0 +1,11 @@
import { type ViewSortDirection } from '~/generated-metadata/graphql';
export type ViewSort = {
__typename: 'ViewSort';
id: string;
fieldMetadataId: string;
createdAt?: string;
updatedAt?: string;
direction: ViewSortDirection;
viewId?: string;
};
@@ -1,9 +1,10 @@
import { type CoreViewSortEssential } from '@/views/types/CoreViewSortEssential';
import { ViewSortDirection } from '~/generated-metadata/graphql';
import { getViewSortsToCreate } from '@/views/utils/getViewSortsToCreate';
import { type ViewSort } from '@/views/types/ViewSort';
describe('getViewSortsToCreate', () => {
const baseSort: CoreViewSortEssential = {
const baseSort: ViewSort = {
__typename: 'ViewSort',
id: 'sort-1',
fieldMetadataId: 'field-1',
direction: ViewSortDirection.ASC,
@@ -11,14 +12,14 @@ describe('getViewSortsToCreate', () => {
};
it('should return all sorts when current sorts array is empty', () => {
const currentViewSorts: CoreViewSortEssential[] = [];
const newViewSorts: CoreViewSortEssential[] = [
const currentViewSorts: ViewSort[] = [];
const newViewSorts: ViewSort[] = [
{ ...baseSort },
{
...baseSort,
id: 'sort-2',
fieldMetadataId: 'field-2',
} satisfies CoreViewSortEssential,
} satisfies ViewSort,
];
const result = getViewSortsToCreate(currentViewSorts, newViewSorts);
@@ -27,8 +28,8 @@ describe('getViewSortsToCreate', () => {
});
it('should return empty array when new sorts array is empty', () => {
const currentViewSorts: CoreViewSortEssential[] = [baseSort];
const newViewSorts: CoreViewSortEssential[] = [];
const currentViewSorts: ViewSort[] = [baseSort];
const newViewSorts: ViewSort[] = [];
const result = getViewSortsToCreate(currentViewSorts, newViewSorts);
@@ -41,11 +42,11 @@ describe('getViewSortsToCreate', () => {
...baseSort,
id: 'sort-2',
fieldMetadataId: 'field-2',
} satisfies CoreViewSortEssential;
} satisfies ViewSort;
const currentViewSorts: CoreViewSortEssential[] = [existingSort];
const currentViewSorts: ViewSort[] = [existingSort];
const newViewSorts: CoreViewSortEssential[] = [
const newViewSorts: ViewSort[] = [
existingSort,
newSortWithDifferentFieldMetadataId,
];
@@ -56,8 +57,8 @@ describe('getViewSortsToCreate', () => {
});
it('should handle empty arrays for both inputs', () => {
const currentViewSorts: CoreViewSortEssential[] = [];
const newViewSorts: CoreViewSortEssential[] = [];
const currentViewSorts: ViewSort[] = [];
const newViewSorts: ViewSort[] = [];
const result = getViewSortsToCreate(currentViewSorts, newViewSorts);
@@ -1,9 +1,10 @@
import { type CoreViewSortEssential } from '@/views/types/CoreViewSortEssential';
import { type ViewSort } from '@/views/types/ViewSort';
import { ViewSortDirection } from '~/generated-metadata/graphql';
import { getViewSortsToDelete } from '@/views/utils/getViewSortsToDelete';
describe('getViewSortsToDelete', () => {
const baseSort: CoreViewSortEssential = {
const baseSort: ViewSort = {
__typename: 'ViewSort',
id: 'sort-1',
fieldMetadataId: 'field-1',
direction: ViewSortDirection.ASC,
@@ -11,8 +12,8 @@ describe('getViewSortsToDelete', () => {
};
it('should return empty array when current sorts array is empty', () => {
const currentViewSorts: CoreViewSortEssential[] = [];
const newViewSorts: CoreViewSortEssential[] = [baseSort];
const currentViewSorts: ViewSort[] = [];
const newViewSorts: ViewSort[] = [baseSort];
const result = getViewSortsToDelete(currentViewSorts, newViewSorts);
@@ -21,8 +22,8 @@ describe('getViewSortsToDelete', () => {
it('should return all current sorts when new sorts array is empty', () => {
const existingSort = { ...baseSort };
const currentViewSorts: CoreViewSortEssential[] = [existingSort];
const newViewSorts: CoreViewSortEssential[] = [];
const currentViewSorts: ViewSort[] = [existingSort];
const newViewSorts: ViewSort[] = [];
const result = getViewSortsToDelete(currentViewSorts, newViewSorts);
@@ -37,11 +38,8 @@ describe('getViewSortsToDelete', () => {
fieldMetadataId: 'field-2',
};
const currentViewSorts: CoreViewSortEssential[] = [
sortToDelete,
sortToKeep,
];
const newViewSorts: CoreViewSortEssential[] = [sortToKeep];
const currentViewSorts: ViewSort[] = [sortToDelete, sortToKeep];
const newViewSorts: ViewSort[] = [sortToKeep];
const result = getViewSortsToDelete(currentViewSorts, newViewSorts);
@@ -49,8 +47,8 @@ describe('getViewSortsToDelete', () => {
});
it('should handle empty arrays for both inputs', () => {
const currentViewSorts: CoreViewSortEssential[] = [];
const newViewSorts: CoreViewSortEssential[] = [];
const currentViewSorts: ViewSort[] = [];
const newViewSorts: ViewSort[] = [];
const result = getViewSortsToDelete(currentViewSorts, newViewSorts);
@@ -60,14 +58,15 @@ describe('getViewSortsToDelete', () => {
it('should not delete sorts that match in both fieldMetadataId and direction', () => {
const existingSort = { ...baseSort };
const matchingSort = {
__typename: 'ViewSort',
id: 'sort-2',
fieldMetadataId: 'field-1',
direction: ViewSortDirection.ASC,
viewId: 'view-1',
};
} as ViewSort;
const currentViewSorts: CoreViewSortEssential[] = [existingSort];
const newViewSorts: CoreViewSortEssential[] = [matchingSort];
const currentViewSorts: ViewSort[] = [existingSort];
const newViewSorts: ViewSort[] = [matchingSort];
const result = getViewSortsToDelete(currentViewSorts, newViewSorts);
@@ -1,9 +1,10 @@
import { type CoreViewSortEssential } from '@/views/types/CoreViewSortEssential';
import { type ViewSort } from '@/views/types/ViewSort';
import { ViewSortDirection } from '~/generated-metadata/graphql';
import { getViewSortsToUpdate } from '@/views/utils/getViewSortsToUpdate';
describe('getViewSortsToUpdate', () => {
const baseSort: CoreViewSortEssential = {
const baseSort: ViewSort = {
__typename: 'ViewSort',
id: 'sort-1',
fieldMetadataId: 'field-1',
direction: ViewSortDirection.ASC,
@@ -11,8 +12,8 @@ describe('getViewSortsToUpdate', () => {
};
it('should return empty array when current sorts array is empty', () => {
const currentViewSorts: CoreViewSortEssential[] = [];
const newViewSorts: CoreViewSortEssential[] = [baseSort];
const currentViewSorts: ViewSort[] = [];
const newViewSorts: ViewSort[] = [baseSort];
const result = getViewSortsToUpdate(currentViewSorts, newViewSorts);
@@ -20,8 +21,8 @@ describe('getViewSortsToUpdate', () => {
});
it('should return empty array when new sorts array is empty', () => {
const currentViewSorts: CoreViewSortEssential[] = [baseSort];
const newViewSorts: CoreViewSortEssential[] = [];
const currentViewSorts: ViewSort[] = [baseSort];
const newViewSorts: ViewSort[] = [];
const result = getViewSortsToUpdate(currentViewSorts, newViewSorts);
@@ -33,10 +34,10 @@ describe('getViewSortsToUpdate', () => {
const updatedSort = {
...baseSort,
direction: ViewSortDirection.DESC,
} satisfies CoreViewSortEssential;
} satisfies ViewSort;
const currentViewSorts: CoreViewSortEssential[] = [existingSort];
const newViewSorts: CoreViewSortEssential[] = [updatedSort];
const currentViewSorts: ViewSort[] = [existingSort];
const newViewSorts: ViewSort[] = [updatedSort];
const result = getViewSortsToUpdate(currentViewSorts, newViewSorts);
@@ -47,8 +48,8 @@ describe('getViewSortsToUpdate', () => {
const existingSort = { ...baseSort };
const sameSort = { ...baseSort };
const currentViewSorts: CoreViewSortEssential[] = [existingSort];
const newViewSorts: CoreViewSortEssential[] = [sameSort];
const currentViewSorts: ViewSort[] = [existingSort];
const newViewSorts: ViewSort[] = [sameSort];
const result = getViewSortsToUpdate(currentViewSorts, newViewSorts);
@@ -56,8 +57,8 @@ describe('getViewSortsToUpdate', () => {
});
it('should handle empty arrays for both inputs', () => {
const currentViewSorts: CoreViewSortEssential[] = [];
const newViewSorts: CoreViewSortEssential[] = [];
const currentViewSorts: ViewSort[] = [];
const newViewSorts: ViewSort[] = [];
const result = getViewSortsToUpdate(currentViewSorts, newViewSorts);
@@ -0,0 +1,17 @@
import type { CoreViewSort } from '~/generated-metadata/graphql';
import type { ViewSort } from '@/views/types/ViewSort';
export const convertCoreViewSortToViewSort = (
coreViewSort: Pick<
CoreViewSort,
'id' | 'direction' | 'fieldMetadataId' | 'viewId'
>,
): ViewSort => {
return {
__typename: 'ViewSort',
id: coreViewSort.id,
direction: coreViewSort.direction,
fieldMetadataId: coreViewSort.fieldMetadataId,
viewId: coreViewSort.viewId,
};
};
@@ -9,6 +9,7 @@ import { convertCoreViewKeyToViewKey } from '@/views/utils/convertCoreViewKeyToV
import { convertCoreViewOpenRecordInToViewOpenRecordIn } from '@/views/utils/convertCoreViewOpenRecordInToViewOpenRecordIn';
import { convertCoreViewTypeToViewType } from '@/views/utils/convertCoreViewTypeToViewType';
import { ViewVisibility } from '~/generated-metadata/graphql';
import { convertCoreViewSortToViewSort } from '@/views/utils/convertCoreViewSortToViewSort';
export const convertCoreViewToView = (
coreView: CoreViewWithRelations,
@@ -41,7 +42,7 @@ export const convertCoreViewToView = (
viewFilterGroups: coreView.viewFilterGroups?.map(
convertCoreViewFilterGroupToViewFilterGroup,
),
viewSorts: coreView.viewSorts,
viewSorts: coreView.viewSorts?.map(convertCoreViewSortToViewSort),
mainGroupByFieldMetadataId: coreView.mainGroupByFieldMetadataId ?? null,
shouldHideEmptyGroups: coreView.shouldHideEmptyGroups,
kanbanAggregateOperation: coreView.kanbanAggregateOperation ?? null,
@@ -1,15 +1,17 @@
import { type CoreViewSortEssential } from '@/views/types/CoreViewSortEssential';
import { isDefined } from 'twenty-shared/utils';
import { type CoreViewSort } from '~/generated-metadata/graphql';
import { type ViewSort } from '@/views/types/ViewSort';
import { compareStrictlyExceptForNullAndUndefined } from '~/utils/compareStrictlyExceptForNullAndUndefined';
export const getViewSortsToCreate = (
currentViewSorts: Pick<CoreViewSort, 'fieldMetadataId'>[],
newViewSorts: CoreViewSortEssential[],
currentViewSorts: ViewSort[],
newViewSorts: ViewSort[],
) => {
return newViewSorts.filter((newViewSort) => {
const correspondingViewSort = currentViewSorts.find(
(currentViewSort) =>
currentViewSort.fieldMetadataId === newViewSort.fieldMetadataId,
const correspondingViewSort = currentViewSorts.find((currentViewSort) =>
compareStrictlyExceptForNullAndUndefined(
currentViewSort.id,
newViewSort.id,
),
);
const shouldCreateBecauseViewSortIsNew = !isDefined(correspondingViewSort);
@@ -1,14 +1,22 @@
import { type CoreViewSortEssential } from '@/views/types/CoreViewSortEssential';
import { type ViewSort } from '@/views/types/ViewSort';
import { compareStrictlyExceptForNullAndUndefined } from '~/utils/compareStrictlyExceptForNullAndUndefined';
const isSameSortTarget = (sortA: ViewSort, sortB: ViewSort): boolean =>
sortA.fieldMetadataId === sortB.fieldMetadataId &&
sortA.direction === sortB.direction;
export const getViewSortsToDelete = (
currentViewSorts: CoreViewSortEssential[],
newViewSorts: Pick<CoreViewSortEssential, 'fieldMetadataId'>[],
currentViewSorts: ViewSort[],
newViewSorts: ViewSort[],
) => {
return currentViewSorts.filter(
(currentViewSort) =>
!newViewSorts.some(
(newViewSort) =>
newViewSort.fieldMetadataId === currentViewSort.fieldMetadataId,
compareStrictlyExceptForNullAndUndefined(
currentViewSort.id,
newViewSort.id,
) || isSameSortTarget(currentViewSort, newViewSort),
),
);
};
@@ -1,18 +1,18 @@
import { type CoreViewSortEssential } from '@/views/types/CoreViewSortEssential';
import { areViewSortsEqual } from '@/views/utils/areViewSortsEqual';
import { isDefined } from 'twenty-shared/utils';
import { type ViewSort } from '@/views/types/ViewSort';
import { compareStrictlyExceptForNullAndUndefined } from '~/utils/compareStrictlyExceptForNullAndUndefined';
export const getViewSortsToUpdate = (
currentViewSorts: Pick<
CoreViewSortEssential,
'fieldMetadataId' | 'direction'
>[],
newViewSorts: CoreViewSortEssential[],
currentViewSorts: ViewSort[],
newViewSorts: ViewSort[],
) => {
return newViewSorts.filter((newViewSort) => {
const correspondingViewSort = currentViewSorts.find(
(currentViewSort) =>
currentViewSort.fieldMetadataId === newViewSort.fieldMetadataId,
const correspondingViewSort = currentViewSorts.find((currentViewSort) =>
compareStrictlyExceptForNullAndUndefined(
currentViewSort.id,
newViewSort.id,
),
);
if (!isDefined(correspondingViewSort)) {
@@ -1,12 +1,11 @@
import { type RecordSort } from '@/object-record/record-sort/types/RecordSort';
import { type CoreViewSort } from '~/generated-metadata/graphql';
import { type ViewSort } from '@/views/types/ViewSort';
export const mapRecordSortToViewSort = (
recordSort: RecordSort,
viewId: string,
): Pick<CoreViewSort, 'id' | 'fieldMetadataId' | 'direction' | 'viewId'> => {
export const mapRecordSortToViewSort = (recordSort: RecordSort): ViewSort => {
return {
...recordSort,
viewId,
__typename: 'ViewSort',
id: recordSort.id,
fieldMetadataId: recordSort.fieldMetadataId,
direction: recordSort.direction,
};
};
@@ -97,6 +97,7 @@ export const fromFieldManifestToUniversalFlatFieldMetadata = ({
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortUniversalIdentifiers: [],
createdAt: now,
updatedAt: now,
};
@@ -42,6 +42,7 @@ export const fromViewManifestToUniversalFlatView = ({
viewFilterGroupUniversalIdentifiers: [],
viewGroupUniversalIdentifiers: [],
viewFieldGroupUniversalIdentifiers: [],
viewSortUniversalIdentifiers: [],
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -31,6 +31,7 @@ import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entiti
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
import { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
// This entity is used as a reference test case for type utilities in:
// Modifying relations or properties may require updating type test expectations for Typecheck to pass.
@@ -206,4 +207,7 @@ export class FieldMetadataEntity<
@OneToMany(() => ViewEntity, (view) => view.mainGroupByFieldMetadata)
mainGroupByFieldMetadataViews: Relation<ViewEntity[]>;
@OneToMany(() => ViewSortEntity, (viewSort) => viewSort.fieldMetadata)
viewSorts: Relation<ViewSortEntity[]>;
}
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should match snapshot 1`] = `
{
@@ -299,6 +299,15 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
],
"propertiesToStringify": [],
},
"viewSort": {
"propertiesToCompare": [
"direction",
"fieldMetadataUniversalIdentifier",
"viewUniversalIdentifier",
"deletedAt",
],
"propertiesToStringify": [],
},
"webhook": {
"propertiesToCompare": [
"targetUrl",
@@ -1162,6 +1162,38 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
universalProperty: undefined,
},
},
viewSort: {
direction: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
},
fieldMetadataId: {
toCompare: true,
toStringify: false,
universalProperty: 'fieldMetadataUniversalIdentifier',
},
createdAt: {
toCompare: false,
toStringify: false,
universalProperty: undefined,
},
updatedAt: {
toCompare: false,
toStringify: false,
universalProperty: undefined,
},
viewId: {
toCompare: true,
toStringify: false,
universalProperty: 'viewUniversalIdentifier',
},
deletedAt: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
},
},
viewFilterGroup: {
viewId: {
toCompare: true,
@@ -240,6 +240,16 @@ export const ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY = {
workspace: null,
application: null,
},
viewSort: {
application: null,
workspace: null,
fieldMetadata: {
foreignKey: 'fieldMetadataId',
},
view: {
foreignKey: 'viewId',
},
},
} as const satisfies ManyToOneMetadataRelationsProperties;
// satisfies with complex mapped types involving nested generics doesn't always catch missing required keys
@@ -420,6 +420,24 @@ export const ALL_MANY_TO_ONE_METADATA_RELATIONS = {
workspace: null,
application: null,
},
viewSort: {
application: null,
workspace: null,
fieldMetadata: {
foreignKey: 'fieldMetadataId',
universalForeignKey: 'fieldMetadataUniversalIdentifier',
inverseOneToManyProperty: null,
isNullable: false,
metadataName: 'fieldMetadata',
},
view: {
foreignKey: 'viewId',
universalForeignKey: 'viewUniversalIdentifier',
inverseOneToManyProperty: 'viewSorts',
isNullable: false,
metadataName: 'view',
},
},
} as const satisfies ManyToOneMetadataRelationsProperties;
// satisfies with complex mapped types involving nested generics doesn't always catch missing required keys
@@ -24,6 +24,7 @@ import { ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-g
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
export const ALL_METADATA_ENTITY_BY_METADATA_NAME = {
viewField: ViewFieldEntity,
@@ -49,4 +50,5 @@ export const ALL_METADATA_ENTITY_BY_METADATA_NAME = {
commandMenuItem: CommandMenuItemEntity,
navigationMenuItem: NavigationMenuItemEntity,
webhook: WebhookEntity,
viewSort: ViewSortEntity,
} as const satisfies Record<AllMetadataName, EntityTarget<ObjectLiteral>>;
@@ -53,6 +53,10 @@ export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = {
viewFilterGroup: {
view: true,
},
viewSort: {
fieldMetadata: true,
view: true,
},
role: {},
roleTarget: {
role: true,
@@ -44,6 +44,7 @@ export const ALL_METADATA_SERIALIZED_RELATION = {
rowLevelPermissionPredicate: {},
rowLevelPermissionPredicateGroup: {},
viewFilterGroup: {},
viewSort: {},
frontComponent: {},
webhook: {},
} as const satisfies MetadataSerializedRelationProperties;
@@ -37,6 +37,11 @@ export const ALL_ONE_TO_MANY_METADATA_RELATIONS = {
commandMenuItem: {},
navigationMenuItem: {},
fieldMetadata: {
viewSorts: {
flatEntityForeignKeyAggregator: 'viewSortIds',
metadataName: 'viewSort',
universalFlatEntityForeignKeyAggregator: 'viewSortUniversalIdentifiers',
},
fieldPermissions: null,
indexFieldMetadatas: null,
viewFields: {
@@ -202,6 +207,7 @@ export const ALL_ONE_TO_MANY_METADATA_RELATIONS = {
},
frontComponent: {},
webhook: {},
viewSort: {},
} as const satisfies OneToManyMetadataRelationsProperties;
// satisfies with complex mapped types involving nested generics doesn't always catch missing required keys
@@ -35,6 +35,8 @@ import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entiti
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
import { WorkspaceFlatViewSortMapCacheService } from 'src/engine/metadata-modules/flat-view-sort/services/workspace-flat-view-sort-map-cache.service';
@Module({
imports: [
@@ -46,6 +48,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
ViewFilterEntity,
ViewFilterGroupEntity,
ViewGroupEntity,
ViewSortEntity,
IndexMetadataEntity,
IndexFieldMetadataEntity,
FieldMetadataEntity,
@@ -71,6 +74,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
WorkspaceFlatIndexMapCacheService,
WorkspaceFlatFieldMetadataMapCacheService,
WorkspaceFlatViewGroupMapCacheService,
WorkspaceFlatViewSortMapCacheService,
WorkspaceFlatPageLayoutMapCacheService,
WorkspaceFlatPageLayoutTabMapCacheService,
WorkspaceFlatPageLayoutWidgetMapCacheService,
@@ -88,6 +92,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
WorkspaceFlatIndexMapCacheService,
WorkspaceFlatFieldMetadataMapCacheService,
WorkspaceFlatViewGroupMapCacheService,
WorkspaceFlatViewSortMapCacheService,
WorkspaceFlatPageLayoutMapCacheService,
WorkspaceFlatPageLayoutTabMapCacheService,
WorkspaceFlatPageLayoutWidgetMapCacheService,
@@ -17,7 +17,7 @@ type FieldMetadataOneToManySyncableRelations =
type Assertions = [
// FieldMetadataEntity OneToMany relations (all targets):
// - indexFieldMetadatas, fieldPermissions, viewFields, viewFilters,
// - kanbanAggregateOperationViews, calendarViews, mainGroupByFieldMetadataViews
// - kanbanAggregateOperationViews, calendarViews, mainGroupByFieldMetadataViews, viewSorts
Expect<
Equal<
FieldMetadataOneToManyRelations,
@@ -28,6 +28,7 @@ type Assertions = [
| 'kanbanAggregateOperationViews'
| 'calendarViews'
| 'mainGroupByFieldMetadataViews'
| 'viewSorts'
>
>,
@@ -40,6 +41,7 @@ type Assertions = [
| 'kanbanAggregateOperationViews'
| 'calendarViews'
| 'mainGroupByFieldMetadataViews'
| 'viewSorts'
>
>,
];
@@ -10,7 +10,7 @@ type FieldMetadataRelatedProperties =
type Assertions = [
// FieldMetadataEntity has both ManyToOne and OneToMany relations
// ManyToOne: object, workspace, application, relationTargetFieldMetadata, relationTargetObjectMetadata
// OneToMany: indexFieldMetadatas, fieldPermissions, viewFields, viewFilters, kanbanAggregateOperationViews, calendarViews, mainGroupByFieldMetadataViews
// OneToMany: indexFieldMetadatas, fieldPermissions, viewFields, viewFilters, kanbanAggregateOperationViews, calendarViews, mainGroupByFieldMetadataViews, viewSorts
Expect<
Equal<
FieldMetadataRelatedProperties,
@@ -26,6 +26,7 @@ type Assertions = [
| 'kanbanAggregateOperationViews'
| 'calendarViews'
| 'mainGroupByFieldMetadataViews'
| 'viewSorts'
>
>,
];
@@ -33,6 +33,8 @@ import { type FlatViewFilterMaps } from 'src/engine/metadata-modules/flat-view-f
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
import { type FlatViewGroupMaps } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group-maps.type';
import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type';
import { type FlatViewSortMaps } from 'src/engine/metadata-modules/flat-view-sort/types/flat-view-sort-maps.type';
import { type FlatViewSort } from 'src/engine/metadata-modules/flat-view-sort/types/flat-view-sort.type';
import { type FlatViewMaps } from 'src/engine/metadata-modules/flat-view/types/flat-view-maps.type';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type FlatWebhookMaps } from 'src/engine/metadata-modules/flat-webhook/types/flat-webhook-maps.type';
@@ -65,6 +67,7 @@ import { type UniversalFlatViewFilterGroup } from 'src/engine/workspace-manager/
import { type UniversalFlatViewFilter } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-filter.type';
import { type UniversalFlatViewGroup } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-group.type';
import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view.type';
import { type UniversalFlatViewSort } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-sort.type';
import { type UniversalFlatWebhook } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-webhook.type';
import {
type FlatCreateAgentAction,
@@ -250,6 +253,14 @@ import {
type UniversalDeleteWebhookAction,
type UniversalUpdateWebhookAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/webhook/types/workspace-migration-webhook-action.type';
import {
type FlatCreateViewSortAction,
type FlatDeleteViewSortAction,
type FlatUpdateViewSortAction,
type UniversalCreateViewSortAction,
type UniversalDeleteViewSortAction,
type UniversalUpdateViewSortAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-sort/types/workspace-migration-view-sort-action.type';
export type AllFlatEntityTypesByMetadataName = {
fieldMetadata: {
@@ -351,6 +362,22 @@ export type AllFlatEntityTypesByMetadataName = {
universalFlatEntity: UniversalFlatViewGroup;
entity: MetadataEntity<'viewGroup'>;
};
viewSort: {
flatEntityMaps: FlatViewSortMaps;
universalActions: {
create: UniversalCreateViewSortAction;
update: UniversalUpdateViewSortAction;
delete: UniversalDeleteViewSortAction;
};
flatActions: {
create: FlatCreateViewSortAction;
update: FlatUpdateViewSortAction;
delete: FlatDeleteViewSortAction;
};
flatEntity: FlatViewSort;
universalFlatEntity: UniversalFlatViewSort;
entity: MetadataEntity<'viewSort'>;
};
rowLevelPermissionPredicate: {
flatEntityMaps: FlatRowLevelPermissionPredicateMaps;
universalActions: {
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`getMetadataRelatedMetadataNames should return related metadata names for agent 1`] = `[]`;
@@ -13,6 +13,7 @@ exports[`getMetadataRelatedMetadataNames should return related metadata names fo
[
"objectMetadata",
"fieldMetadata",
"viewSort",
"viewField",
"viewFilter",
"view",
@@ -149,4 +150,11 @@ exports[`getMetadataRelatedMetadataNames should return related metadata names fo
]
`;
exports[`getMetadataRelatedMetadataNames should return related metadata names for viewSort 1`] = `
[
"fieldMetadata",
"view",
]
`;
exports[`getMetadataRelatedMetadataNames should return related metadata names for webhook 1`] = `[]`;
@@ -8,6 +8,7 @@ exports[`sortMetadataNamesChildrenFirst should return metadata names sorted with
"viewFilter",
"commandMenuItem",
"pageLayoutWidget",
"viewSort",
"index",
"pageLayout",
"roleTarget",
@@ -53,6 +53,8 @@ export const getFlatFieldMetadataMock = <T extends FieldMetadataType>(
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortIds: [],
viewSortUniversalIdentifiers: [],
universalSettings: null,
...overrides,
};
@@ -73,6 +73,8 @@ export const getRelationTargetFlatFieldMetadataMock = ({
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortIds: [],
viewSortUniversalIdentifiers: [],
universalSettings: settings,
};
};
@@ -147,6 +147,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"updatedAt": Any<String>,
"viewFieldUniversalIdentifiers": [],
"viewFilterUniversalIdentifiers": [],
"viewSortUniversalIdentifiers": [],
},
{
"applicationUniversalIdentifier": Any<String>,
@@ -182,6 +183,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"updatedAt": Any<String>,
"viewFieldUniversalIdentifiers": [],
"viewFilterUniversalIdentifiers": [],
"viewSortUniversalIdentifiers": [],
},
{
"applicationUniversalIdentifier": Any<String>,
@@ -215,6 +217,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"updatedAt": Any<String>,
"viewFieldUniversalIdentifiers": [],
"viewFilterUniversalIdentifiers": [],
"viewSortUniversalIdentifiers": [],
},
{
"applicationUniversalIdentifier": Any<String>,
@@ -250,6 +253,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"updatedAt": Any<String>,
"viewFieldUniversalIdentifiers": [],
"viewFilterUniversalIdentifiers": [],
"viewSortUniversalIdentifiers": [],
},
],
"indexMetadatas": [
@@ -20,6 +20,7 @@ import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-
import { createIdToUniversalIdentifierMap } from 'src/engine/workspace-cache/utils/create-id-to-universal-identifier-map.util';
import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id';
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
@Injectable()
@WorkspaceCache('flatFieldMetadataMaps')
@@ -39,6 +40,8 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
private readonly viewFilterRepository: Repository<ViewFilterEntity>,
@InjectRepository(ViewGroupEntity)
private readonly viewGroupRepository: Repository<ViewGroupEntity>,
@InjectRepository(ViewSortEntity)
private readonly viewSortRepository: Repository<ViewSortEntity>,
@InjectRepository(ViewEntity)
private readonly viewRepository: Repository<ViewEntity>,
) {
@@ -54,6 +57,7 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
applications,
viewFields,
viewFilters,
viewSorts,
views,
] = await Promise.all([
this.fieldMetadataRepository.find({
@@ -80,6 +84,11 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
select: ['id', 'universalIdentifier', 'fieldMetadataId'],
withDeleted: true,
}),
this.viewSortRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier', 'fieldMetadataId'],
withDeleted: true,
}),
this.viewRepository.find({
where: { workspaceId },
select: [
@@ -99,6 +108,7 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
calendarViewsByFieldId,
kanbanViewsByFieldId,
mainGroupByFieldMetadataViewsByFieldId,
viewSortsByFieldId,
] = (
[
{
@@ -121,6 +131,10 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
entities: views,
foreignKey: 'mainGroupByFieldMetadataId',
},
{
entities: viewSorts,
foreignKey: 'fieldMetadataId',
},
] as const
).map(regroupEntitiesByRelatedEntityId);
@@ -147,6 +161,7 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
mainGroupByFieldMetadataViewsByFieldId.get(
fieldMetadataEntity.id,
) || [],
viewSorts: viewSortsByFieldId.get(fieldMetadataEntity.id) || [],
},
fieldMetadataIdToUniversalIdentifierMap,
objectMetadataIdToUniversalIdentifierMap,
@@ -127,6 +127,11 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = ({
fieldMetadataEntity.mainGroupByFieldMetadataViews?.map(
({ universalIdentifier }) => universalIdentifier,
) ?? [],
viewSortIds: fieldMetadataEntity.viewSorts?.map(({ id }) => id) ?? [],
viewSortUniversalIdentifiers:
fieldMetadataEntity.viewSorts?.map(
({ universalIdentifier }) => universalIdentifier,
) ?? [],
universalSettings: settingsWithUniversalIdentifiers,
};
};
@@ -57,5 +57,6 @@ export const getDefaultFlatFieldMetadata = ({
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: settings ?? null,
viewSortUniversalIdentifiers: [],
} as const satisfies UniversalFlatFieldMetadata;
};
@@ -0,0 +1,5 @@
import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
export const FLAT_VIEW_SORT_EDITABLE_PROPERTIES = [
'direction',
] as const satisfies MetadataEntityPropertyName<'viewSort'>[];
@@ -0,0 +1,84 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service';
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
import { FlatViewSortMaps } from 'src/engine/metadata-modules/flat-view-sort/types/flat-view-sort-maps.type';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { fromViewSortEntityToFlatViewSort } from 'src/engine/metadata-modules/flat-view-sort/utils/from-view-sort-entity-to-flat-view-sort.util';
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { createIdToUniversalIdentifierMap } from 'src/engine/workspace-cache/utils/create-id-to-universal-identifier-map.util';
@Injectable()
@WorkspaceCache('flatViewSortMaps')
export class WorkspaceFlatViewSortMapCacheService extends WorkspaceCacheProvider<FlatViewSortMaps> {
constructor(
@InjectRepository(ViewSortEntity)
private readonly viewSortRepository: Repository<ViewSortEntity>,
@InjectRepository(ViewEntity)
private readonly viewRepository: Repository<ViewEntity>,
@InjectRepository(ApplicationEntity)
private readonly applicationRepository: Repository<ApplicationEntity>,
@InjectRepository(FieldMetadataEntity)
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
) {
super();
}
async computeForCache(workspaceId: string): Promise<FlatViewSortMaps> {
const [existingViewSorts, applications, views, fieldMetadatas] =
await Promise.all([
this.viewSortRepository.find({
where: { workspaceId },
withDeleted: true,
}),
this.applicationRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier'],
withDeleted: true,
}),
this.viewRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier'],
withDeleted: true,
}),
this.fieldMetadataRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier'],
withDeleted: true,
}),
]);
const applicationIdToUniversalIdentifierMap =
createIdToUniversalIdentifierMap(applications);
const viewIdToUniversalIdentifierMap =
createIdToUniversalIdentifierMap(views);
const fieldMetadataIdToUniversalIdentifierMap =
createIdToUniversalIdentifierMap(fieldMetadatas);
const flatViewSortMaps = createEmptyFlatEntityMaps();
for (const viewSort of existingViewSorts) {
const flatViewSort = fromViewSortEntityToFlatViewSort({
entity: viewSort,
applicationIdToUniversalIdentifierMap,
viewIdToUniversalIdentifierMap,
fieldMetadataIdToUniversalIdentifierMap,
});
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
flatEntity: flatViewSort,
flatEntityMapsToMutate: flatViewSortMaps,
});
}
return flatViewSortMaps;
}
}
@@ -0,0 +1,4 @@
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatViewSort } from 'src/engine/metadata-modules/flat-view-sort/types/flat-view-sort.type';
export type FlatViewSortMaps = FlatEntityMaps<FlatViewSort>;
@@ -0,0 +1,4 @@
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { type ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
export type FlatViewSort = FlatEntityFrom<ViewSortEntity>;
@@ -0,0 +1,58 @@
import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type CreateViewSortInput } from 'src/engine/metadata-modules/view-sort/dtos/inputs/create-view-sort.input';
import { ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { type UniversalFlatViewSort } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-sort.type';
import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util';
export const fromCreateViewSortInputToFlatViewSortToCreate = ({
createViewSortInput: rawCreateViewSortInput,
flatApplication,
flatFieldMetadataMaps,
flatViewMaps,
}: {
createViewSortInput: CreateViewSortInput;
flatApplication: FlatApplication;
} & Pick<
AllFlatEntityMaps,
'flatViewMaps' | 'flatFieldMetadataMaps'
>): UniversalFlatViewSort & {
id: string;
} => {
const { viewId, fieldMetadataId, ...createViewSortInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawCreateViewSortInput,
['fieldMetadataId', 'id', 'viewId'],
);
const createdAt = new Date().toISOString();
const viewSortId = createViewSortInput.id ?? v4();
const { fieldMetadataUniversalIdentifier, viewUniversalIdentifier } =
resolveEntityRelationUniversalIdentifiers({
metadataName: 'viewSort',
foreignKeyValues: {
fieldMetadataId,
viewId,
},
flatEntityMaps: {
flatFieldMetadataMaps,
flatViewMaps,
},
});
return {
id: viewSortId,
fieldMetadataUniversalIdentifier,
viewUniversalIdentifier,
createdAt,
updatedAt: createdAt,
deletedAt: null,
universalIdentifier: createViewSortInput.universalIdentifier ?? v4(),
direction: createViewSortInput.direction ?? ViewSortDirection.ASC,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
};
};
@@ -0,0 +1,44 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type FlatViewSortMaps } from 'src/engine/metadata-modules/flat-view-sort/types/flat-view-sort-maps.type';
import { type DeleteViewSortInput } from 'src/engine/metadata-modules/view-sort/dtos/inputs/delete-view-sort.input';
import {
ViewSortException,
ViewSortExceptionCode,
} from 'src/engine/metadata-modules/view-sort/exceptions/view-sort.exception';
import { type UniversalFlatViewSort } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-sort.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
export const fromDeleteViewSortInputToFlatViewSortOrThrow = ({
deleteViewSortInput: rawDeleteViewSortInput,
flatViewSortMaps,
}: {
deleteViewSortInput: DeleteViewSortInput;
flatViewSortMaps: FlatViewSortMaps;
}): UniversalFlatViewSort => {
const { id: viewSortId } = extractAndSanitizeObjectStringFields(
rawDeleteViewSortInput,
['id'],
);
const existingFlatViewSortToDelete = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: viewSortId,
flatEntityMaps: flatViewSortMaps,
});
if (!isDefined(existingFlatViewSortToDelete)) {
throw new ViewSortException(
t`View sort not found`,
ViewSortExceptionCode.VIEW_SORT_NOT_FOUND,
);
}
return {
...existingFlatViewSortToDelete,
deletedAt: new Date().toISOString(),
};
};
@@ -0,0 +1,41 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type FlatViewSortMaps } from 'src/engine/metadata-modules/flat-view-sort/types/flat-view-sort-maps.type';
import { type DestroyViewSortInput } from 'src/engine/metadata-modules/view-sort/dtos/inputs/destroy-view-sort.input';
import {
ViewSortException,
ViewSortExceptionCode,
} from 'src/engine/metadata-modules/view-sort/exceptions/view-sort.exception';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type UniversalFlatViewSort } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-sort.type';
export const fromDestroyViewSortInputToFlatViewSortOrThrow = ({
destroyViewSortInput,
flatViewSortMaps,
}: {
destroyViewSortInput: DestroyViewSortInput;
flatViewSortMaps: FlatViewSortMaps;
}): UniversalFlatViewSort => {
const { id: viewSortId } = extractAndSanitizeObjectStringFields(
destroyViewSortInput,
['id'],
);
const existingFlatViewSortToDestroy = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: viewSortId,
flatEntityMaps: flatViewSortMaps,
});
if (!isDefined(existingFlatViewSortToDestroy)) {
throw new ViewSortException(
t`View sort not found`,
ViewSortExceptionCode.VIEW_SORT_NOT_FOUND,
);
}
return existingFlatViewSortToDestroy;
};
@@ -0,0 +1,54 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { FLAT_VIEW_SORT_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-sort/constants/flat-view-sort-editable-properties.constant';
import { type UpdateViewSortInput } from 'src/engine/metadata-modules/view-sort/dtos/inputs/update-view-sort.input';
import {
ViewSortException,
ViewSortExceptionCode,
} from 'src/engine/metadata-modules/view-sort/exceptions/view-sort.exception';
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
import { type UniversalFlatViewSort } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-sort.type';
import { type FlatViewSortMaps } from 'src/engine/metadata-modules/flat-view-sort/types/flat-view-sort-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
export const fromUpdateViewSortInputToFlatViewSortToUpdateOrThrow = ({
updateViewSortInput: rawUpdateViewSortInput,
flatViewSortMaps,
}: {
updateViewSortInput: UpdateViewSortInput;
flatViewSortMaps: FlatViewSortMaps;
}): UniversalFlatViewSort => {
const { id: viewSortToUpdateId } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawUpdateViewSortInput,
['id'],
);
const existingFlatViewSortToUpdate = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: viewSortToUpdateId,
flatEntityMaps: flatViewSortMaps,
});
if (!isDefined(existingFlatViewSortToUpdate)) {
throw new ViewSortException(
t`View sort not found`,
ViewSortExceptionCode.VIEW_SORT_NOT_FOUND,
);
}
const updatedEditableSortProperties = extractAndSanitizeObjectStringFields(
rawUpdateViewSortInput.update,
FLAT_VIEW_SORT_EDITABLE_PROPERTIES,
);
return mergeUpdateInExistingRecord({
existing: existingFlatViewSortToUpdate,
properties: FLAT_VIEW_SORT_EDITABLE_PROPERTIES,
update: updatedEditableSortProperties,
});
};
@@ -0,0 +1,63 @@
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
import type { FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type FlatViewSort } from 'src/engine/metadata-modules/flat-view-sort/types/flat-view-sort.type';
export const fromViewSortEntityToFlatViewSort = ({
entity: viewSortEntity,
applicationIdToUniversalIdentifierMap,
viewIdToUniversalIdentifierMap,
fieldMetadataIdToUniversalIdentifierMap,
}: FromEntityToFlatEntityArgs<'viewSort'>): FlatViewSort => {
const viewSortEntityWithoutRelations = removePropertiesFromRecord(
viewSortEntity,
getMetadataEntityRelationProperties('viewSort'),
);
const applicationUniversalIdentifier =
applicationIdToUniversalIdentifierMap.get(viewSortEntity.applicationId);
if (!isDefined(applicationUniversalIdentifier)) {
throw new FlatEntityMapsException(
`Application with id ${viewSortEntity.applicationId} not found for viewSort ${viewSortEntity.id}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
const viewUniversalIdentifier = viewIdToUniversalIdentifierMap.get(
viewSortEntity.viewId,
);
if (!isDefined(viewUniversalIdentifier)) {
throw new FlatEntityMapsException(
`View with id ${viewSortEntity.viewId} not found for viewSort ${viewSortEntity.id}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
const fieldMetadataUniversalIdentifier =
fieldMetadataIdToUniversalIdentifierMap.get(viewSortEntity.fieldMetadataId);
if (!isDefined(fieldMetadataUniversalIdentifier)) {
throw new FlatEntityMapsException(
`Field metadata with id ${viewSortEntity.fieldMetadataId} not found for viewSort ${viewSortEntity.id}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
return {
...viewSortEntityWithoutRelations,
createdAt: viewSortEntity.createdAt.toISOString(),
updatedAt: viewSortEntity.updatedAt.toISOString(),
deletedAt: viewSortEntity.deletedAt?.toISOString() ?? null,
universalIdentifier: viewSortEntityWithoutRelations.universalIdentifier,
applicationUniversalIdentifier,
viewUniversalIdentifier,
fieldMetadataUniversalIdentifier,
};
};
@@ -11,6 +11,7 @@ import { ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-g
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
@Module({
imports: [
@@ -23,6 +24,7 @@ import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entit
ViewFieldGroupEntity,
ViewFilterEntity,
ViewGroupEntity,
ViewSortEntity,
ViewFilterGroupEntity,
]),
],
@@ -16,6 +16,7 @@ import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities
import { ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
import { createIdToUniversalIdentifierMap } from 'src/engine/workspace-cache/utils/create-id-to-universal-identifier-map.util';
@@ -42,6 +43,8 @@ export class WorkspaceFlatViewMapCacheService extends WorkspaceCacheProvider<Fla
private readonly viewGroupRepository: Repository<ViewGroupEntity>,
@InjectRepository(ViewFilterGroupEntity)
private readonly viewFilterGroupRepository: Repository<ViewFilterGroupEntity>,
@InjectRepository(ViewSortEntity)
private readonly viewSortRepository: Repository<ViewSortEntity>,
@InjectRepository(ViewFieldGroupEntity)
private readonly viewFieldGroupRepository: Repository<ViewFieldGroupEntity>,
) {
@@ -58,6 +61,7 @@ export class WorkspaceFlatViewMapCacheService extends WorkspaceCacheProvider<Fla
viewFilters,
viewGroups,
viewFilterGroups,
viewSorts,
viewFieldGroups,
] = await Promise.all([
this.viewRepository.find({
@@ -99,6 +103,11 @@ export class WorkspaceFlatViewMapCacheService extends WorkspaceCacheProvider<Fla
select: ['id', 'universalIdentifier', 'viewId'],
withDeleted: true,
}),
this.viewSortRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier', 'viewId'],
withDeleted: true,
}),
this.viewFieldGroupRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier', 'viewId'],
@@ -111,6 +120,7 @@ export class WorkspaceFlatViewMapCacheService extends WorkspaceCacheProvider<Fla
viewFiltersByViewId,
viewGroupsByViewId,
viewFilterGroupsByViewId,
viewSortsByViewId,
viewFieldGroupsByViewId,
] = (
[
@@ -130,6 +140,10 @@ export class WorkspaceFlatViewMapCacheService extends WorkspaceCacheProvider<Fla
entities: viewFilterGroups,
foreignKey: 'viewId',
},
{
entities: viewSorts,
foreignKey: 'viewId',
},
{
entities: viewFieldGroups,
foreignKey: 'viewId',
@@ -154,6 +168,7 @@ export class WorkspaceFlatViewMapCacheService extends WorkspaceCacheProvider<Fla
viewFilters: viewFiltersByViewId.get(viewEntity.id) || [],
viewGroups: viewGroupsByViewId.get(viewEntity.id) || [],
viewFilterGroups: viewFilterGroupsByViewId.get(viewEntity.id) || [],
viewSorts: viewSortsByViewId.get(viewEntity.id) || [],
viewFieldGroups: viewFieldGroupsByViewId.get(viewEntity.id) || [],
},
applicationIdToUniversalIdentifierMap,
@@ -1,7 +1,4 @@
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
export type FlatView = FlatEntityFrom<
// TODO remove once viewSorts has been migrated to v2
Omit<ViewEntity, 'viewSorts'>
>;
export type FlatView = FlatEntityFrom<ViewEntity>;
@@ -91,6 +91,7 @@ export const fromCreateViewInputToFlatViewToCreate = ({
viewGroupUniversalIdentifiers: [],
viewFieldGroupUniversalIdentifiers: [],
viewFilterGroupUniversalIdentifiers: [],
viewSortUniversalIdentifiers: [],
applicationUniversalIdentifier: flatApplication.universalIdentifier,
};
@@ -121,5 +121,10 @@ export const fromViewEntityToFlatView = ({
viewEntity.viewFilterGroups?.map(
({ universalIdentifier }) => universalIdentifier,
) ?? [],
viewSortIds: viewEntity.viewSorts?.map(({ id }) => id) ?? [],
viewSortUniversalIdentifiers:
viewEntity.viewSorts?.map(
({ universalIdentifier }) => universalIdentifier,
) ?? [],
};
};
@@ -38,6 +38,7 @@ const PARTIAL_ID_FIELD = {
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortUniversalIdentifiers: [],
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.UUID>;
const PARTIAL_CREATED_AT_FIELD = {
@@ -65,6 +66,7 @@ const PARTIAL_CREATED_AT_FIELD = {
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortUniversalIdentifiers: [],
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.DATE_TIME>;
const PARTIAL_UPDATED_AT_FIELD = {
@@ -92,6 +94,7 @@ const PARTIAL_UPDATED_AT_FIELD = {
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortUniversalIdentifiers: [],
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.DATE_TIME>;
const PARTIAL_DELETED_AT_FIELD = {
@@ -119,6 +122,7 @@ const PARTIAL_DELETED_AT_FIELD = {
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortUniversalIdentifiers: [],
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.DATE_TIME>;
const PARTIAL_CREATED_BY_FIELD = {
@@ -146,6 +150,7 @@ const PARTIAL_CREATED_BY_FIELD = {
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortUniversalIdentifiers: [],
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.ACTOR>;
const PARTIAL_UPDATED_BY_FIELD = {
@@ -173,6 +178,7 @@ const PARTIAL_UPDATED_BY_FIELD = {
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortUniversalIdentifiers: [],
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.ACTOR>;
const PARTIAL_POSITION_FIELD = {
@@ -200,6 +206,7 @@ const PARTIAL_POSITION_FIELD = {
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortUniversalIdentifiers: [],
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.POSITION>;
const PARTIAL_SEARCH_VECTOR_FIELD = {
@@ -229,6 +236,7 @@ const PARTIAL_SEARCH_VECTOR_FIELD = {
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortUniversalIdentifiers: [],
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.TS_VECTOR>;
export const PARTIAL_SYSTEM_FLAT_FIELD_METADATAS = {
@@ -633,6 +633,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
viewFilterUniversalIdentifiers: [],
viewGroupUniversalIdentifiers: [],
viewFilterGroupUniversalIdentifiers: [],
viewSortUniversalIdentifiers: [],
applicationUniversalIdentifier: flatApplication.universalIdentifier,
};
}
@@ -675,6 +676,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
viewFilterUniversalIdentifiers: [],
viewGroupUniversalIdentifiers: [],
viewFilterGroupUniversalIdentifiers: [],
viewSortUniversalIdentifiers: [],
applicationUniversalIdentifier: flatApplication.universalIdentifier,
};
}
@@ -152,6 +152,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
viewSortUniversalIdentifiers: [],
};
const searchVectorUniversalSettings: UniversalFlatFieldMetadata<FieldMetadataType.TS_VECTOR>['universalSettings'] =
@@ -21,17 +21,17 @@ import { CreateViewSortPermissionGuard } from 'src/engine/metadata-modules/view-
import { DeleteViewSortPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-sort-permission.guard';
import { UpdateViewSortPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/update-view-sort-permission.guard';
import { CreateViewSortInput } from 'src/engine/metadata-modules/view-sort/dtos/inputs/create-view-sort.input';
import { UpdateViewSortInput } from 'src/engine/metadata-modules/view-sort/dtos/inputs/update-view-sort.input';
import { type ViewSortDTO } from 'src/engine/metadata-modules/view-sort/dtos/view-sort.dto';
import {
generateViewSortExceptionMessage,
generateViewSortUserFriendlyExceptionMessage,
ViewSortException,
ViewSortExceptionCode,
ViewSortExceptionMessageKey,
generateViewSortExceptionMessage,
generateViewSortUserFriendlyExceptionMessage,
} from 'src/engine/metadata-modules/view-sort/exceptions/view-sort.exception';
import { ViewSortRestApiExceptionFilter } from 'src/engine/metadata-modules/view-sort/filters/view-sort-rest-api-exception.filter';
import { ViewSortService } from 'src/engine/metadata-modules/view-sort/services/view-sort.service';
import { ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction';
@Controller('rest/metadata/viewSorts')
@UseGuards(WorkspaceAuthGuard)
@@ -82,11 +82,11 @@ export class ViewSortController {
@UseGuards(CreateViewSortPermissionGuard)
async create(
@Body() input: CreateViewSortInput,
@AuthWorkspace() workspace: WorkspaceEntity,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewSortDTO> {
return this.viewSortService.create({
...input,
workspaceId: workspace.id,
return await this.viewSortService.createOne({
createViewSortInput: input,
workspaceId,
});
}
@@ -94,25 +94,25 @@ export class ViewSortController {
@UseGuards(UpdateViewSortPermissionGuard)
async update(
@Param('id') id: string,
@Body() input: UpdateViewSortInput,
@AuthWorkspace() workspace: WorkspaceEntity,
@Body() input: { direction?: ViewSortDirection },
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewSortDTO> {
const updatedViewSort = await this.viewSortService.update(
id,
workspace.id,
input,
);
return updatedViewSort;
return await this.viewSortService.updateOne({
updateViewSortInput: { id, update: input },
workspaceId,
});
}
@Delete(':id')
@UseGuards(DeleteViewSortPermissionGuard)
async delete(
@Param('id') id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<{ success: boolean }> {
const deletedViewSort = await this.viewSortService.delete(id, workspace.id);
const deletedViewSort = await this.viewSortService.deleteOne({
deleteViewSortInput: { id },
workspaceId,
});
return { success: isDefined(deletedViewSort) };
}
@@ -1,22 +1,36 @@
import { Field, InputType } from '@nestjs/graphql';
import { Field, HideField, InputType } from '@nestjs/graphql';
import { IsEnum, IsOptional, IsUUID } from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction';
@InputType()
export class CreateViewSortInput {
@IsOptional()
@IsUUID()
@Field(() => UUIDScalarType, { nullable: true })
id?: string;
@IsUUID()
@Field(() => UUIDScalarType, { nullable: false })
fieldMetadataId: string;
@IsOptional()
@IsEnum(ViewSortDirection)
@Field(() => ViewSortDirection, {
nullable: true,
defaultValue: ViewSortDirection.ASC,
})
direction?: ViewSortDirection;
@IsUUID()
@Field(() => UUIDScalarType, { nullable: false })
viewId: string;
@HideField()
universalIdentifier?: string;
@HideField()
applicationId?: string;
}
@@ -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 DeleteViewSortInput {
@IDField(() => UUIDScalarType, {
description: 'The id of the view sort to delete.',
})
@IsUUID()
id: string;
}
@@ -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 DestroyViewSortInput {
@IDField(() => UUIDScalarType, {
description: 'The id of the view sort to destroy.',
})
@IsUUID()
id: string;
}
@@ -1,6 +1,38 @@
import { InputType, PartialType } from '@nestjs/graphql';
import { Field, InputType } from '@nestjs/graphql';
import { CreateViewSortInput } from './create-view-sort.input';
import {
IsEnum,
IsNotEmpty,
IsOptional,
IsUUID,
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction';
@InputType()
export class UpdateViewSortInput extends PartialType(CreateViewSortInput) {}
class UpdateViewSortInputUpdates {
@IsOptional()
@IsEnum(ViewSortDirection)
@Field(() => ViewSortDirection, { nullable: true })
direction?: ViewSortDirection;
}
@InputType()
export class UpdateViewSortInput {
@IsUUID()
@IsNotEmpty()
@Field(() => UUIDScalarType, {
description: 'The id of the view sort to update',
})
id: string;
@Type(() => UpdateViewSortInputUpdates)
@ValidateNested()
@Field(() => UpdateViewSortInputUpdates, {
description: 'The view sort to update',
})
update: UpdateViewSortInputUpdates;
}
@@ -1,7 +1,7 @@
import { UseFilters, UseGuards } from '@nestjs/common';
import { Args, Mutation, Query } from '@nestjs/graphql';
import { isDefined } from 'twenty-shared/utils';
import { isDefined } from 'class-validator';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
@@ -17,6 +17,8 @@ import { UpdateViewSortInput } from 'src/engine/metadata-modules/view-sort/dtos/
import { ViewSortDTO } from 'src/engine/metadata-modules/view-sort/dtos/view-sort.dto';
import { ViewSortService } from 'src/engine/metadata-modules/view-sort/services/view-sort.service';
import { ViewGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/view/utils/view-graphql-api-exception.filter';
import { DeleteViewSortInput } from 'src/engine/metadata-modules/view-sort/dtos/inputs/delete-view-sort.input';
import { DestroyViewSortInput } from 'src/engine/metadata-modules/view-sort/dtos/inputs/destroy-view-sort.input';
@MetadataResolver(() => ViewSortDTO)
@UseFilters(ViewGraphqlApiExceptionFilter)
@@ -42,40 +44,45 @@ export class ViewSortResolver {
@UseGuards(NoPermissionGuard)
async getCoreViewSort(
@Args('id', { type: () => String }) id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewSortDTO | null> {
return this.viewSortService.findById(id, workspace.id);
return this.viewSortService.findById(id, workspaceId);
}
@Mutation(() => ViewSortDTO)
@UseGuards(CreateViewSortPermissionGuard)
async createCoreViewSort(
@Args('input') input: CreateViewSortInput,
@AuthWorkspace() workspace: WorkspaceEntity,
@Args('input') createViewSortInput: CreateViewSortInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewSortDTO> {
return this.viewSortService.create({
...input,
workspaceId: workspace.id,
return this.viewSortService.createOne({
createViewSortInput,
workspaceId,
});
}
@Mutation(() => ViewSortDTO)
@UseGuards(UpdateViewSortPermissionGuard)
async updateCoreViewSort(
@Args('id', { type: () => String }) id: string,
@Args('input') input: UpdateViewSortInput,
@AuthWorkspace() workspace: WorkspaceEntity,
@Args('input') updateViewSortInput: UpdateViewSortInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<ViewSortDTO> {
return this.viewSortService.update(id, workspace.id, input);
return this.viewSortService.updateOne({
updateViewSortInput,
workspaceId,
});
}
@Mutation(() => Boolean)
@UseGuards(DeleteViewSortPermissionGuard)
async deleteCoreViewSort(
@Args('id', { type: () => String }) id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
@Args('input') deleteViewSortInput: DeleteViewSortInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<boolean> {
const deletedViewSort = await this.viewSortService.delete(id, workspace.id);
const deletedViewSort = await this.viewSortService.deleteOne({
deleteViewSortInput,
workspaceId,
});
return isDefined(deletedViewSort);
}
@@ -83,9 +90,14 @@ export class ViewSortResolver {
@Mutation(() => Boolean)
@UseGuards(DestroyViewSortPermissionGuard)
async destroyCoreViewSort(
@Args('id', { type: () => String }) id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
@Args('input') destroyViewSortInput: DestroyViewSortInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<boolean> {
return this.viewSortService.destroy(id, workspace.id);
const destroyedViewSort = await this.viewSortService.destroyOne({
destroyViewSortInput,
workspaceId,
});
return isDefined(destroyedViewSort);
}
}
@@ -1,353 +0,0 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { type Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
import { ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction';
import {
ViewSortException,
ViewSortExceptionCode,
ViewSortExceptionMessageKey,
generateViewSortExceptionMessage,
generateViewSortUserFriendlyExceptionMessage,
} from 'src/engine/metadata-modules/view-sort/exceptions/view-sort.exception';
import { ViewSortService } from 'src/engine/metadata-modules/view-sort/services/view-sort.service';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
describe('ViewSortService', () => {
let viewSortService: ViewSortService;
let viewSortRepository: Repository<ViewSortEntity>;
let applicationService: ApplicationService;
const mockViewSort = {
id: 'view-sort-id',
fieldMetadataId: 'field-id',
viewId: 'view-id',
workspaceId: 'workspace-id',
direction: ViewSortDirection.ASC,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
} as ViewSortEntity;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ViewSortService,
{
provide: getRepositoryToken(ViewSortEntity),
useValue: {
find: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
save: jest.fn(),
softDelete: jest.fn(),
delete: jest.fn(),
},
},
{
provide: WorkspaceCacheStorageService,
useValue: {
flushGraphQLOperation: jest.fn(),
},
},
{
provide: ApplicationService,
useValue: {
findWorkspaceTwentyStandardAndCustomApplicationOrThrow: jest.fn(),
},
},
],
}).compile();
viewSortService = module.get<ViewSortService>(ViewSortService);
viewSortRepository = module.get<Repository<ViewSortEntity>>(
getRepositoryToken(ViewSortEntity),
);
applicationService = module.get<ApplicationService>(ApplicationService);
});
it('should be defined', () => {
expect(viewSortService).toBeDefined();
});
describe('findByWorkspaceId', () => {
it('should return view sorts for a workspace', async () => {
const workspaceId = 'workspace-id';
const expectedViewSorts = [mockViewSort];
jest
.spyOn(viewSortRepository, 'find')
.mockResolvedValue(expectedViewSorts);
const result = await viewSortService.findByWorkspaceId(workspaceId);
expect(viewSortRepository.find).toHaveBeenCalledWith({
where: {
workspaceId,
deletedAt: expect.anything(),
},
relations: ['workspace', 'view'],
});
expect(result).toEqual(expectedViewSorts);
});
});
describe('findByViewId', () => {
it('should return view sorts for a view', async () => {
const workspaceId = 'workspace-id';
const viewId = 'view-id';
const expectedViewSorts = [mockViewSort];
jest
.spyOn(viewSortRepository, 'find')
.mockResolvedValue(expectedViewSorts);
const result = await viewSortService.findByViewId(workspaceId, viewId);
expect(viewSortRepository.find).toHaveBeenCalledWith({
where: {
workspaceId,
viewId,
deletedAt: expect.anything(),
},
relations: ['workspace', 'view'],
});
expect(result).toEqual(expectedViewSorts);
});
});
describe('findById', () => {
it('should return a view sort by id', async () => {
const id = 'view-sort-id';
const workspaceId = 'workspace-id';
jest.spyOn(viewSortRepository, 'findOne').mockResolvedValue(mockViewSort);
const result = await viewSortService.findById(id, workspaceId);
expect(viewSortRepository.findOne).toHaveBeenCalledWith({
where: {
id,
workspaceId,
deletedAt: expect.anything(),
},
relations: ['workspace', 'view'],
});
expect(result).toEqual(mockViewSort);
});
it('should return null when view sort is not found', async () => {
const id = 'non-existent-id';
const workspaceId = 'workspace-id';
jest.spyOn(viewSortRepository, 'findOne').mockResolvedValue(null);
const result = await viewSortService.findById(id, workspaceId);
expect(result).toBeNull();
});
});
describe('create', () => {
const validViewSortData = {
fieldMetadataId: 'field-id',
viewId: 'view-id',
workspaceId: 'workspace-id',
direction: ViewSortDirection.ASC,
};
it('should create a view sort successfully', async () => {
const mockApplicationId = 'application-id';
jest
.spyOn(
applicationService,
'findWorkspaceTwentyStandardAndCustomApplicationOrThrow',
)
.mockResolvedValue({
workspaceCustomFlatApplication: { id: mockApplicationId },
} as any);
jest.spyOn(viewSortRepository, 'create').mockReturnValue(mockViewSort);
jest.spyOn(viewSortRepository, 'save').mockResolvedValue(mockViewSort);
const result = await viewSortService.create(validViewSortData);
expect(viewSortRepository.create).toHaveBeenCalledWith({
...validViewSortData,
universalIdentifier: expect.any(String),
applicationId: mockApplicationId,
});
expect(viewSortRepository.save).toHaveBeenCalledWith(mockViewSort);
expect(result).toEqual(mockViewSort);
});
it('should throw exception when workspaceId is missing', async () => {
const invalidData = { ...validViewSortData, workspaceId: undefined };
await expect(viewSortService.create(invalidData)).rejects.toThrow(
new ViewSortException(
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.WORKSPACE_ID_REQUIRED,
),
ViewSortExceptionCode.INVALID_VIEW_SORT_DATA,
{
userFriendlyMessage: generateViewSortUserFriendlyExceptionMessage(
ViewSortExceptionMessageKey.WORKSPACE_ID_REQUIRED,
),
},
),
);
});
it('should throw exception when viewId is missing', async () => {
const invalidData = { ...validViewSortData, viewId: undefined };
jest
.spyOn(
applicationService,
'findWorkspaceTwentyStandardAndCustomApplicationOrThrow',
)
.mockResolvedValue({
workspaceCustomFlatApplication: { id: 'application-id' },
} as any);
await expect(viewSortService.create(invalidData)).rejects.toThrow(
new ViewSortException(
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.VIEW_ID_REQUIRED,
),
ViewSortExceptionCode.INVALID_VIEW_SORT_DATA,
{
userFriendlyMessage: generateViewSortUserFriendlyExceptionMessage(
ViewSortExceptionMessageKey.VIEW_ID_REQUIRED,
),
},
),
);
});
it('should throw exception when fieldMetadataId is missing', async () => {
const invalidData = { ...validViewSortData, fieldMetadataId: undefined };
jest
.spyOn(
applicationService,
'findWorkspaceTwentyStandardAndCustomApplicationOrThrow',
)
.mockResolvedValue({
workspaceCustomFlatApplication: { id: 'application-id' },
} as any);
await expect(viewSortService.create(invalidData)).rejects.toThrow(
new ViewSortException(
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.FIELD_METADATA_ID_REQUIRED,
),
ViewSortExceptionCode.INVALID_VIEW_SORT_DATA,
{
userFriendlyMessage: generateViewSortUserFriendlyExceptionMessage(
ViewSortExceptionMessageKey.FIELD_METADATA_ID_REQUIRED,
),
},
),
);
});
});
describe('update', () => {
it('should update a view sort successfully', async () => {
const id = 'view-sort-id';
const workspaceId = 'workspace-id';
const updateData = { direction: ViewSortDirection.DESC };
const updatedViewSort = { ...mockViewSort, ...updateData };
jest.spyOn(viewSortService, 'findById').mockResolvedValue(mockViewSort);
jest.spyOn(viewSortRepository, 'save').mockResolvedValue(updatedViewSort);
const result = await viewSortService.update(id, workspaceId, updateData);
expect(viewSortService.findById).toHaveBeenCalledWith(id, workspaceId);
expect(viewSortRepository.save).toHaveBeenCalledWith({
id,
...updateData,
});
expect(result).toEqual({ ...mockViewSort, ...updatedViewSort });
});
it('should throw exception when view sort is not found', async () => {
const id = 'non-existent-id';
const workspaceId = 'workspace-id';
const updateData = { direction: ViewSortDirection.DESC };
jest.spyOn(viewSortService, 'findById').mockResolvedValue(null);
await expect(
viewSortService.update(id, workspaceId, updateData),
).rejects.toThrow(
new ViewSortException(
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.VIEW_SORT_NOT_FOUND,
id,
),
ViewSortExceptionCode.VIEW_SORT_NOT_FOUND,
),
);
});
});
describe('delete', () => {
it('should delete a view sort successfully', async () => {
const id = 'view-sort-id';
const workspaceId = 'workspace-id';
jest.spyOn(viewSortService, 'findById').mockResolvedValue(mockViewSort);
jest.spyOn(viewSortRepository, 'softDelete').mockResolvedValue({} as any);
const result = await viewSortService.delete(id, workspaceId);
expect(viewSortService.findById).toHaveBeenCalledWith(id, workspaceId);
expect(viewSortRepository.softDelete).toHaveBeenCalledWith(id);
expect(result).toEqual(mockViewSort);
});
it('should throw exception when view sort is not found', async () => {
const id = 'non-existent-id';
const workspaceId = 'workspace-id';
jest.spyOn(viewSortService, 'findById').mockResolvedValue(null);
await expect(viewSortService.delete(id, workspaceId)).rejects.toThrow(
new ViewSortException(
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.VIEW_SORT_NOT_FOUND,
id,
),
ViewSortExceptionCode.VIEW_SORT_NOT_FOUND,
),
);
});
});
describe('destroy', () => {
it('should destroy a view sort successfully', async () => {
const id = 'view-sort-id';
const workspaceId = 'workspace-id';
jest.spyOn(viewSortRepository, 'findOne').mockResolvedValue(mockViewSort);
jest.spyOn(viewSortRepository, 'delete').mockResolvedValue({} as any);
const result = await viewSortService.destroy(id, workspaceId);
expect(viewSortRepository.findOne).toHaveBeenCalledWith({
where: { id, workspaceId },
relations: ['workspace', 'view'],
withDeleted: true,
});
expect(viewSortRepository.delete).toHaveBeenCalledWith(id);
expect(result).toEqual(true);
});
});
});
@@ -1,32 +1,305 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { IsNull, Repository } from 'typeorm';
import { v4 } from 'uuid';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { fromCreateViewSortInputToFlatViewSortToCreate } from 'src/engine/metadata-modules/flat-view-sort/utils/from-create-view-sort-input-to-flat-view-sort-to-create.util';
import { fromDeleteViewSortInputToFlatViewSortOrThrow } from 'src/engine/metadata-modules/flat-view-sort/utils/from-delete-view-sort-input-to-flat-view-sort-or-throw.util';
import { fromDestroyViewSortInputToFlatViewSortOrThrow } from 'src/engine/metadata-modules/flat-view-sort/utils/from-destroy-view-sort-input-to-flat-view-sort-or-throw.util';
import { fromUpdateViewSortInputToFlatViewSortToUpdateOrThrow } from 'src/engine/metadata-modules/flat-view-sort/utils/from-update-view-sort-input-to-flat-view-sort-to-update-or-throw.util';
import { CreateViewSortInput } from 'src/engine/metadata-modules/view-sort/dtos/inputs/create-view-sort.input';
import { DeleteViewSortInput } from 'src/engine/metadata-modules/view-sort/dtos/inputs/delete-view-sort.input';
import { DestroyViewSortInput } from 'src/engine/metadata-modules/view-sort/dtos/inputs/destroy-view-sort.input';
import { UpdateViewSortInput } from 'src/engine/metadata-modules/view-sort/dtos/inputs/update-view-sort.input';
import { ViewSortDTO } from 'src/engine/metadata-modules/view-sort/dtos/view-sort.dto';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
import {
ViewSortException,
ViewSortExceptionCode,
ViewSortExceptionMessageKey,
generateViewSortExceptionMessage,
generateViewSortUserFriendlyExceptionMessage,
} from 'src/engine/metadata-modules/view-sort/exceptions/view-sort.exception';
import { FIND_ALL_CORE_VIEWS_GRAPHQL_OPERATION } from 'src/engine/metadata-modules/view/constants/find-all-core-views-graphql-operation.constant';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { fromFlatViewSortToViewSortDto } from 'src/engine/metadata-modules/view-sort/utils/from-flat-view-sort-to-view-sort-dto.util';
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
// TODO migrate to v2
@Injectable()
export class ViewSortService {
constructor(
@InjectRepository(ViewSortEntity)
private readonly viewSortRepository: Repository<ViewSortEntity>,
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly applicationService: ApplicationService,
) {}
async createOne({
createViewSortInput,
workspaceId,
}: {
createViewSortInput: CreateViewSortInput;
workspaceId: string;
}): Promise<ViewSortDTO> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const { flatFieldMetadataMaps, flatViewMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatFieldMetadataMaps', 'flatViewMaps'],
},
);
const flatViewSortToCreate = fromCreateViewSortInputToFlatViewSortToCreate({
createViewSortInput,
flatApplication: workspaceCustomFlatApplication,
flatFieldMetadataMaps,
flatViewMaps,
});
const buildAndRunResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
viewSort: {
flatEntityToCreate: [flatViewSortToCreate],
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
workspaceId,
isSystemBuild: false,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
},
);
if (buildAndRunResult.status === 'fail') {
throw new WorkspaceMigrationBuilderException(
buildAndRunResult,
'Multiple validation errors occurred while creating view sort',
);
}
const { flatViewSortMaps: recomputedExistingFlatViewSortMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewSortMaps'],
},
);
return fromFlatViewSortToViewSortDto(
findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatViewSortToCreate.id,
flatEntityMaps: recomputedExistingFlatViewSortMaps,
}),
);
}
async updateOne({
updateViewSortInput,
workspaceId,
}: {
workspaceId: string;
updateViewSortInput: UpdateViewSortInput;
}): Promise<ViewSortDTO> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const { flatViewSortMaps: existingFlatViewSortMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewSortMaps'],
},
);
const optimisticallyUpdatedFlatViewSort =
fromUpdateViewSortInputToFlatViewSortToUpdateOrThrow({
flatViewSortMaps: existingFlatViewSortMaps,
updateViewSortInput,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
viewSort: {
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [optimisticallyUpdatedFlatViewSort],
},
},
workspaceId,
isSystemBuild: false,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
},
);
if (validateAndBuildResult.status === 'fail') {
throw new WorkspaceMigrationBuilderException(
validateAndBuildResult,
'Multiple validation errors occurred while updating view sort',
);
}
const { flatViewSortMaps: recomputedExistingFlatViewSortMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewSortMaps'],
},
);
return fromFlatViewSortToViewSortDto(
findFlatEntityByUniversalIdentifierOrThrow({
universalIdentifier:
optimisticallyUpdatedFlatViewSort.universalIdentifier,
flatEntityMaps: recomputedExistingFlatViewSortMaps,
}),
);
}
async deleteOne({
deleteViewSortInput,
workspaceId,
}: {
deleteViewSortInput: DeleteViewSortInput;
workspaceId: string;
}): Promise<ViewSortDTO> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const { flatViewSortMaps: existingFlatViewSortMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewSortMaps'],
},
);
const optimisticallyUpdatedFlatViewSortWithDeletedAt =
fromDeleteViewSortInputToFlatViewSortOrThrow({
flatViewSortMaps: existingFlatViewSortMaps,
deleteViewSortInput,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
viewSort: {
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [
optimisticallyUpdatedFlatViewSortWithDeletedAt,
],
},
},
workspaceId,
isSystemBuild: false,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
},
);
if (validateAndBuildResult.status === 'fail') {
throw new WorkspaceMigrationBuilderException(
validateAndBuildResult,
'Multiple validation errors occurred while deleting view sort',
);
}
const { flatViewSortMaps: recomputedExistingFlatViewSortMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewSortMaps'],
},
);
return fromFlatViewSortToViewSortDto(
findFlatEntityByUniversalIdentifierOrThrow({
universalIdentifier:
optimisticallyUpdatedFlatViewSortWithDeletedAt.universalIdentifier,
flatEntityMaps: recomputedExistingFlatViewSortMaps,
}),
);
}
async destroyOne({
destroyViewSortInput,
workspaceId,
}: {
destroyViewSortInput: DestroyViewSortInput;
workspaceId: string;
}): Promise<ViewSortDTO> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const { flatViewSortMaps: existingFlatViewSortMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewSortMaps'],
},
);
const existingViewSortToDelete =
fromDestroyViewSortInputToFlatViewSortOrThrow({
destroyViewSortInput,
flatViewSortMaps: existingFlatViewSortMaps,
});
const existingFlatViewSort = findFlatEntityByUniversalIdentifierOrThrow({
universalIdentifier: existingViewSortToDelete.universalIdentifier,
flatEntityMaps: existingFlatViewSortMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
viewSort: {
flatEntityToCreate: [],
flatEntityToDelete: [existingViewSortToDelete],
flatEntityToUpdate: [],
},
},
workspaceId,
isSystemBuild: false,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
},
);
if (validateAndBuildResult.status === 'fail') {
throw new WorkspaceMigrationBuilderException(
validateAndBuildResult,
'Multiple validation errors occurred while destroying view sort',
);
}
return fromFlatViewSortToViewSortDto(existingFlatViewSort);
}
async findByWorkspaceId(workspaceId: string): Promise<ViewSortEntity[]> {
return this.viewSortRepository.find({
where: {
@@ -66,148 +339,4 @@ export class ViewSortService {
return viewSort || null;
}
async create(viewSortData: Partial<ViewSortEntity>): Promise<ViewSortEntity> {
if (!isDefined(viewSortData.workspaceId)) {
throw new ViewSortException(
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.WORKSPACE_ID_REQUIRED,
),
ViewSortExceptionCode.INVALID_VIEW_SORT_DATA,
{
userFriendlyMessage: generateViewSortUserFriendlyExceptionMessage(
ViewSortExceptionMessageKey.WORKSPACE_ID_REQUIRED,
),
},
);
}
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId: viewSortData.workspaceId,
},
);
if (!isDefined(viewSortData.viewId)) {
throw new ViewSortException(
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.VIEW_ID_REQUIRED,
),
ViewSortExceptionCode.INVALID_VIEW_SORT_DATA,
{
userFriendlyMessage: generateViewSortUserFriendlyExceptionMessage(
ViewSortExceptionMessageKey.VIEW_ID_REQUIRED,
),
},
);
}
if (!isDefined(viewSortData.fieldMetadataId)) {
throw new ViewSortException(
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.FIELD_METADATA_ID_REQUIRED,
),
ViewSortExceptionCode.INVALID_VIEW_SORT_DATA,
{
userFriendlyMessage: generateViewSortUserFriendlyExceptionMessage(
ViewSortExceptionMessageKey.FIELD_METADATA_ID_REQUIRED,
),
},
);
}
const viewSort = this.viewSortRepository.create({
...viewSortData,
universalIdentifier: v4(),
applicationId: workspaceCustomFlatApplication.id,
});
const savedViewSort = await this.viewSortRepository.save(viewSort);
await this.flushGraphQLCache(viewSortData.workspaceId);
return savedViewSort;
}
async update(
id: string,
workspaceId: string,
updateData: Partial<ViewSortEntity>,
): Promise<ViewSortEntity> {
const existingViewSort = await this.findById(id, workspaceId);
if (!isDefined(existingViewSort)) {
throw new ViewSortException(
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.VIEW_SORT_NOT_FOUND,
id,
),
ViewSortExceptionCode.VIEW_SORT_NOT_FOUND,
);
}
const updatedViewSort = await this.viewSortRepository.save({
id,
...updateData,
});
await this.flushGraphQLCache(workspaceId);
return { ...existingViewSort, ...updatedViewSort };
}
async delete(id: string, workspaceId: string): Promise<ViewSortEntity> {
const viewSort = await this.findById(id, workspaceId);
if (!isDefined(viewSort)) {
throw new ViewSortException(
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.VIEW_SORT_NOT_FOUND,
id,
),
ViewSortExceptionCode.VIEW_SORT_NOT_FOUND,
);
}
await this.viewSortRepository.softDelete(id);
await this.flushGraphQLCache(workspaceId);
return viewSort;
}
async destroy(id: string, workspaceId: string): Promise<boolean> {
const viewSort = await this.viewSortRepository.findOne({
where: {
id,
workspaceId,
},
relations: ['workspace', 'view'],
withDeleted: true,
});
if (!isDefined(viewSort)) {
throw new ViewSortException(
generateViewSortExceptionMessage(
ViewSortExceptionMessageKey.VIEW_SORT_NOT_FOUND,
id,
),
ViewSortExceptionCode.VIEW_SORT_NOT_FOUND,
);
}
await this.viewSortRepository.delete(id);
await this.flushGraphQLCache(workspaceId);
return true;
}
private async flushGraphQLCache(workspaceId: string): Promise<void> {
await this.workspaceCacheStorageService.flushGraphQLOperation({
operationName: FIND_ALL_CORE_VIEWS_GRAPHQL_OPERATION,
workspaceId,
});
}
}
@@ -0,0 +1,15 @@
import { type FlatViewSort } from 'src/engine/metadata-modules/flat-view-sort/types/flat-view-sort.type';
import { type ViewSortDTO } from 'src/engine/metadata-modules/view-sort/dtos/view-sort.dto';
export const fromFlatViewSortToViewSortDto = (
flatViewSort: FlatViewSort,
): ViewSortDTO => {
const { createdAt, updatedAt, deletedAt, ...rest } = flatViewSort;
return {
...rest,
createdAt: new Date(createdAt),
updatedAt: new Date(updatedAt),
deletedAt: deletedAt ? new Date(deletedAt) : null,
};
};
@@ -10,14 +10,18 @@ import { ViewSortResolver } from 'src/engine/metadata-modules/view-sort/resolver
import { ViewSortService } from 'src/engine/metadata-modules/view-sort/services/view-sort.service';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
@Module({
imports: [
TypeOrmModule.forFeature([ViewSortEntity, ViewEntity]),
PermissionsModule,
WorkspaceCacheStorageModule,
ViewPermissionsModule,
ApplicationModule,
PermissionsModule,
WorkspaceMigrationModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
ViewPermissionsModule,
],
controllers: [ViewSortController],
providers: [ViewSortService, ViewSortResolver],
@@ -175,6 +175,8 @@ describe('WorkspaceEntityManager', () => {
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortIds: [],
viewSortUniversalIdentifiers: [],
universalSettings: null,
};
@@ -105,6 +105,8 @@ describe('WorkspaceRepository', () => {
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortIds: [],
viewSortUniversalIdentifiers: [],
universalSettings: null,
};
@@ -21,6 +21,7 @@ export const WORKSPACE_CACHE_KEYS_V2 = {
flatViewGroupMaps: 'flat-maps:view-group',
flatViewFilterMaps: 'flat-maps:view-filter',
flatViewFilterGroupMaps: 'flat-maps:view-filter-group',
flatViewSortMaps: 'flat-maps:view-sort',
flatLogicFunctionMaps: 'flat-maps:logic-function',
featureFlagsMap: 'feature-flag:feature-flags-map',
rolesPermissions: 'metadata:permissions:roles-permissions',
@@ -110,6 +110,8 @@ export const createStandardFieldFlatMetadata = <
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortIds: [],
viewSortUniversalIdentifiers: [],
universalSettings: settings ?? null,
};
};
@@ -130,6 +130,8 @@ export const createStandardRelationFieldFlatMetadata = <
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortIds: [],
viewSortUniversalIdentifiers: [],
universalSettings: settings,
};
};
@@ -152,6 +152,8 @@ export const createStandardViewFlatMetadata = <
viewGroupUniversalIdentifiers: [],
viewFilterGroupIds: [],
viewFilterGroupUniversalIdentifiers: [],
viewSortIds: [],
viewSortUniversalIdentifiers: [],
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -166,6 +166,7 @@ export const enrichCreateWorkspaceMigrationActionsWithIds = ({
case 'commandMenuItem':
case 'navigationMenuItem':
case 'frontComponent':
case 'viewSort':
case 'webhook': {
if (!isDefined(idByUniversalIdentifier)) {
return action;
@@ -36,6 +36,7 @@ import { WorkspaceMigrationViewFilterActionsBuilderService } from 'src/engine/wo
import { WorkspaceMigrationViewGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-group/workspace-migration-view-group-actions-builder.service';
import { WorkspaceMigrationViewActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view/workspace-migration-view-actions-builder.service';
import { WorkspaceMigrationWebhookActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/webhook/workspace-migration-webhook-actions-builder.service';
import { WorkspaceMigrationViewSortActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-sort/workspace-migration-view-sort-actions.builder.service';
@Injectable()
export class WorkspaceMigrationBuildOrchestratorService {
@@ -48,6 +49,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
private readonly workspaceMigrationViewFilterGroupActionsBuilderService: WorkspaceMigrationViewFilterGroupActionsBuilderService,
private readonly workspaceMigrationViewGroupActionsBuilderService: WorkspaceMigrationViewGroupActionsBuilderService,
private readonly workspaceMigrationViewFieldGroupActionsBuilderService: WorkspaceMigrationViewFieldGroupActionsBuilderService,
private readonly workspaceMigrationViewSortActionsBuilderService: WorkspaceMigrationViewSortActionsBuilderService,
private readonly workspaceMigrationLogicFunctionActionsBuilderService: WorkspaceMigrationLogicFunctionActionsBuilderService,
private readonly workspaceMigrationRoleTargetActionsBuilderService: WorkspaceMigrationRoleTargetActionsBuilderService,
private readonly workspaceMigrationFieldActionsBuilderService: WorkspaceMigrationFieldActionsBuilderService,
@@ -134,6 +136,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
flatViewFilterGroupMaps,
flatViewGroupMaps,
flatViewFieldGroupMaps,
flatViewSortMaps,
flatRowLevelPermissionPredicateMaps,
flatRowLevelPermissionPredicateGroupMaps,
flatRoleMaps,
@@ -355,6 +358,28 @@ export class WorkspaceMigrationBuildOrchestratorService {
}
}
if (isDefined(flatViewSortMaps)) {
const { from: fromFlatViewSortMaps, to: toFlatViewSortMaps } =
flatViewSortMaps;
const viewSortResult =
await this.workspaceMigrationViewSortActionsBuilderService.validateAndBuild(
{
additionalCacheDataMaps,
from: fromFlatViewSortMaps,
to: toFlatViewSortMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: optimisticAllFlatEntityMaps,
workspaceId,
},
);
if (viewSortResult.status === 'fail') {
orchestratorFailureReport.viewSort.push(...viewSortResult.errors);
} else {
orchestratorActionsReport.viewSort = viewSortResult.actions;
}
}
if (isDefined(flatRowLevelPermissionPredicateGroupMaps)) {
const {
from: fromFlatRowLevelPermissionPredicateGroupMaps,
@@ -763,6 +788,9 @@ export class WorkspaceMigrationBuildOrchestratorService {
...aggregatedOrchestratorActionsReport.viewGroup.delete,
...aggregatedOrchestratorActionsReport.viewGroup.create,
...aggregatedOrchestratorActionsReport.viewGroup.update,
...aggregatedOrchestratorActionsReport.viewSort.create,
...aggregatedOrchestratorActionsReport.viewSort.update,
...aggregatedOrchestratorActionsReport.viewSort.delete,
///
// Logic functions
@@ -5,6 +5,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should matc
"agent": [],
"commandMenuItem": [],
"fieldMetadata": [
"viewSortUniversalIdentifiers",
"viewFieldUniversalIdentifiers",
"viewFilterUniversalIdentifiers",
"kanbanAggregateOperationViewUniversalIdentifiers",
@@ -56,6 +57,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should matc
"viewFilterUniversalIdentifiers",
],
"viewGroup": [],
"viewSort": [],
"webhook": [],
}
`;
@@ -18,6 +18,7 @@ export const ALL_JSONB_PROPERTIES_WITH_SERIALIZED_RELATION_BY_METADATA_NAME = {
viewFieldGroup: {},
viewFilter: {},
viewFilterGroup: {},
viewSort: {},
index: {},
role: {},
roleTarget: {},
@@ -0,0 +1,7 @@
import { type ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
export type UniversalFlatViewSort = UniversalFlatEntityFrom<
ViewSortEntity,
'viewSort'
>;
@@ -1,8 +1,4 @@
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
// TODO remove once viewSorts has been migrated to v2
export type UniversalFlatView = UniversalFlatEntityFrom<
Omit<ViewEntity, 'viewSorts'>,
'view'
>;
export type UniversalFlatView = UniversalFlatEntityFrom<ViewEntity, 'view'>;
@@ -45,6 +45,8 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a crea
"viewFieldUniversalIdentifiers": [],
"viewFilterIds": [],
"viewFilterUniversalIdentifiers": [],
"viewSortIds": [],
"viewSortUniversalIdentifiers": [],
"workspaceId": "workspace-id-1",
},
},
@@ -106,6 +108,8 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a dele
"viewFieldUniversalIdentifiers": [],
"viewFilterIds": [],
"viewFilterUniversalIdentifiers": [],
"viewSortIds": [],
"viewSortUniversalIdentifiers": [],
"workspaceId": "workspace-id-1",
},
},
@@ -181,6 +185,8 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create
"viewFieldUniversalIdentifiers": [],
"viewFilterIds": [],
"viewFilterUniversalIdentifiers": [],
"viewSortIds": [],
"viewSortUniversalIdentifiers": [],
"workspaceId": "workspace-id-1",
},
},
@@ -228,6 +234,8 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create
"viewFieldUniversalIdentifiers": [],
"viewFilterIds": [],
"viewFilterUniversalIdentifiers": [],
"viewSortIds": [],
"viewSortUniversalIdentifiers": [],
"workspaceId": "workspace-id-1",
},
},
@@ -0,0 +1,24 @@
import { type BaseFlatCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-create-workspace-migration-action.type';
import { type BaseFlatUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-update-workspace-migration-action.type';
import { type BaseFlatDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-delete-workspace-migration-action.type';
import { type BaseUniversalCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-create-workspace-migration-action.type';
import { type BaseUniversalUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-update-workspace-migration-action.type';
import { type BaseUniversalDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-delete-workspace-migration-action.type';
export type FlatCreateViewSortAction =
BaseFlatCreateWorkspaceMigrationAction<'viewSort'>;
export type FlatUpdateViewSortAction =
BaseFlatUpdateWorkspaceMigrationAction<'viewSort'>;
export type FlatDeleteViewSortAction =
BaseFlatDeleteWorkspaceMigrationAction<'viewSort'>;
export type UniversalCreateViewSortAction =
BaseUniversalCreateWorkspaceMigrationAction<'viewSort'>;
export type UniversalUpdateViewSortAction =
BaseUniversalUpdateWorkspaceMigrationAction<'viewSort'>;
export type UniversalDeleteViewSortAction =
BaseUniversalDeleteWorkspaceMigrationAction<'viewSort'>;
@@ -0,0 +1,108 @@
import { Injectable } from '@nestjs/common';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { WorkspaceEntityMigrationBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service';
import { UniversalUpdateViewSortAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-sort/types/workspace-migration-view-sort-action.type';
import { FlatViewSortValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-sort-validator.service';
import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
import { UniversalFlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-result.type';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
@Injectable()
export class WorkspaceMigrationViewSortActionsBuilderService extends WorkspaceEntityMigrationBuilderService<
typeof ALL_METADATA_NAME.viewSort
> {
constructor(
private readonly flatViewSortValidatorService: FlatViewSortValidatorService,
) {
super(ALL_METADATA_NAME.viewSort);
}
protected validateFlatEntityCreation(
args: UniversalFlatEntityValidationArgs<typeof ALL_METADATA_NAME.viewSort>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.viewSort,
'create'
> {
const validationResult =
this.flatViewSortValidatorService.validateFlatViewSortCreation(args);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate: flatViewSortToValidate } = args;
return {
status: 'success',
action: {
type: 'create',
metadataName: 'viewSort',
flatEntity: flatViewSortToValidate,
},
};
}
protected validateFlatEntityDeletion(
args: UniversalFlatEntityValidationArgs<typeof ALL_METADATA_NAME.viewSort>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.viewSort,
'delete'
> {
const validationResult =
this.flatViewSortValidatorService.validateFlatViewSortDeletion(args);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate: flatViewSortToValidate } = args;
return {
status: 'success',
action: {
type: 'delete',
metadataName: 'viewSort',
universalIdentifier: flatViewSortToValidate.universalIdentifier,
},
};
}
protected validateFlatEntityUpdate(
args: FlatEntityUpdateValidationArgs<typeof ALL_METADATA_NAME.viewSort>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.viewSort,
'update'
> {
const validationResult =
this.flatViewSortValidatorService.validateFlatViewSortUpdate(args);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { universalIdentifier, flatEntityUpdate } = args;
const updateViewSortAction: UniversalUpdateViewSortAction = {
type: 'update',
metadataName: 'viewSort',
universalIdentifier,
update: flatEntityUpdate,
};
return {
status: 'success',
action: updateViewSortAction,
};
}
}
@@ -0,0 +1,229 @@
import { Injectable } from '@nestjs/common';
import { msg, t } from '@lingui/core/macro';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { ViewSortExceptionCode } from 'src/engine/metadata-modules/view-sort/exceptions/view-sort.exception';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
import { ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction';
import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util';
@Injectable()
export class FlatViewSortValidatorService {
constructor() {}
validateFlatViewSortCreation({
flatEntityToValidate: flatViewSortToValidate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatViewSortMaps: optimisticFlatViewSortMaps,
flatViewMaps,
flatFieldMetadataMaps,
},
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.viewSort
>): FailedFlatEntityValidation<'viewSort', 'create'> {
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier: flatViewSortToValidate.universalIdentifier,
viewUniversalIdentifier: flatViewSortToValidate.viewUniversalIdentifier,
},
metadataName: 'viewSort',
type: 'create',
});
const existingFlatViewSort = findFlatEntityByUniversalIdentifier({
universalIdentifier: flatViewSortToValidate.universalIdentifier,
flatEntityMaps: optimisticFlatViewSortMaps,
});
if (isDefined(existingFlatViewSort)) {
const flatViewSortId = flatViewSortToValidate.universalIdentifier;
validationResult.errors.push({
code: ViewSortExceptionCode.INVALID_VIEW_SORT_DATA,
message: t`View sort with id ${flatViewSortId} already exists`,
userFriendlyMessage: msg`View sort already exists`,
});
}
const flatFieldMetadata = findFlatEntityByUniversalIdentifier({
universalIdentifier:
flatViewSortToValidate.fieldMetadataUniversalIdentifier,
flatEntityMaps: flatFieldMetadataMaps,
});
if (!isDefined(flatFieldMetadata)) {
validationResult.errors.push({
code: ViewSortExceptionCode.INVALID_VIEW_SORT_DATA,
message: t`Field metadata not found`,
userFriendlyMessage: msg`Field metadata not found`,
});
}
const flatView = findFlatEntityByUniversalIdentifier({
universalIdentifier: flatViewSortToValidate.viewUniversalIdentifier,
flatEntityMaps: flatViewMaps,
});
if (!isDefined(flatView)) {
validationResult.errors.push({
code: ViewSortExceptionCode.VIEW_NOT_FOUND,
message: t`View not found`,
userFriendlyMessage: msg`View not found`,
});
return validationResult;
}
const otherFlatViewSorts =
findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow({
universalIdentifiers: flatView.viewSortUniversalIdentifiers,
flatEntityMaps: optimisticFlatViewSortMaps,
});
const equivalentExistingFlatViewSortExists = otherFlatViewSorts.some(
(flatViewSort) =>
flatViewSort.universalIdentifier ===
flatViewSortToValidate.universalIdentifier &&
flatViewSort.fieldMetadataUniversalIdentifier ===
flatViewSortToValidate.fieldMetadataUniversalIdentifier,
);
if (equivalentExistingFlatViewSortExists) {
validationResult.errors.push({
code: ViewSortExceptionCode.INVALID_VIEW_SORT_DATA,
message: t`View sort with same fieldMetadataId and viewId already exists`,
userFriendlyMessage: msg`View sort already exists`,
});
}
if (
!isDefined(flatViewSortToValidate.direction) ||
!Object.values(ViewSortDirection).includes(
flatViewSortToValidate.direction,
)
) {
validationResult.errors.push({
code: ViewSortExceptionCode.INVALID_VIEW_SORT_DATA,
message: t`View sort with invalid direction`,
userFriendlyMessage: msg`View sort with invalid direction, should be ASC or DESC`,
});
}
return validationResult;
}
public validateFlatViewSortUpdate({
universalIdentifier,
flatEntityUpdate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatViewSortMaps: optimisticFlatViewSortMaps,
flatViewMaps,
},
}: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.viewSort
>): FailedFlatEntityValidation<'viewSort', 'update'> {
const existingFlatViewSort = findFlatEntityByUniversalIdentifier({
universalIdentifier,
flatEntityMaps: optimisticFlatViewSortMaps,
});
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier,
},
metadataName: 'viewSort',
type: 'update',
});
if (!isDefined(existingFlatViewSort)) {
validationResult.errors.push({
code: ViewSortExceptionCode.VIEW_SORT_NOT_FOUND,
message: t`View sort to update not found`,
userFriendlyMessage: msg`View sort to update not found`,
});
return validationResult;
}
const updatedFlatViewSort = {
...existingFlatViewSort,
...flatEntityUpdate,
};
if (
!isDefined(updatedFlatViewSort?.direction) ||
!(
updatedFlatViewSort.direction === ViewSortDirection.DESC ||
updatedFlatViewSort.direction === ViewSortDirection.ASC
)
) {
validationResult.errors.push({
code: ViewSortExceptionCode.INVALID_VIEW_SORT_DATA,
message: t`Correct direction is required`,
userFriendlyMessage: msg`Correct direction is required`,
});
}
validationResult.flatEntityMinimalInformation = {
...validationResult.flatEntityMinimalInformation,
viewUniversalIdentifier: updatedFlatViewSort.viewUniversalIdentifier,
};
const flatView = findFlatEntityByUniversalIdentifier({
universalIdentifier: updatedFlatViewSort.viewUniversalIdentifier,
flatEntityMaps: flatViewMaps,
});
if (!isDefined(flatView)) {
validationResult.errors.push({
code: ViewSortExceptionCode.VIEW_NOT_FOUND,
message: t`View sort to update parent view not found`,
userFriendlyMessage: msg`View sort to update parent view not found`,
});
return validationResult;
}
return validationResult;
}
public validateFlatViewSortDeletion({
flatEntityToValidate: { universalIdentifier },
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatViewSortMaps: optimisticFlatViewSortMaps,
},
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.viewSort
>): FailedFlatEntityValidation<'viewSort', 'delete'> {
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier,
},
metadataName: 'viewSort',
type: 'delete',
});
const existingFlatViewSort = findFlatEntityByUniversalIdentifier({
universalIdentifier,
flatEntityMaps: optimisticFlatViewSortMaps,
});
if (!isDefined(existingFlatViewSort)) {
validationResult.errors.push({
code: ViewSortExceptionCode.VIEW_SORT_NOT_FOUND,
message: t`View sort to delete not found`,
userFriendlyMessage: msg`View sort to delete not found`,
});
return validationResult;
}
return validationResult;
}
}
@@ -26,6 +26,7 @@ import { FlatViewFilterValidatorService } from 'src/engine/workspace-manager/wor
import { FlatViewGroupValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-group-validator.service';
import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-validator.service';
import { FlatWebhookValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-webhook-validator.service';
import { FlatViewSortValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-sort-validator.service';
@Module({
imports: [FeatureFlagModule],
@@ -36,6 +37,7 @@ import { FlatWebhookValidatorService } from 'src/engine/workspace-manager/worksp
FlatViewFilterGroupValidatorService,
FlatViewGroupValidatorService,
FlatViewFieldGroupValidatorService,
FlatViewSortValidatorService,
FlatIndexValidatorService,
FlatFieldMetadataValidatorService,
FlatObjectMetadataValidatorService,
@@ -63,6 +65,7 @@ import { FlatWebhookValidatorService } from 'src/engine/workspace-manager/worksp
FlatViewFilterGroupValidatorService,
FlatViewGroupValidatorService,
FlatViewFieldGroupValidatorService,
FlatViewSortValidatorService,
FlatIndexValidatorService,
FlatFieldMetadataValidatorService,
FlatObjectMetadataValidatorService,
@@ -26,6 +26,7 @@ import { WorkspaceMigrationViewFilterActionsBuilderService } from 'src/engine/wo
import { WorkspaceMigrationViewGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-group/workspace-migration-view-group-actions-builder.service';
import { WorkspaceMigrationViewActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view/workspace-migration-view-actions-builder.service';
import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/workspace-migration-builder-validators.module';
import { WorkspaceMigrationViewSortActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-sort/workspace-migration-view-sort-actions.builder.service';
@Module({
imports: [FeatureFlagModule, WorkspaceMigrationBuilderValidatorsModule],
@@ -38,6 +39,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationViewFilterGroupActionsBuilderService,
WorkspaceMigrationViewGroupActionsBuilderService,
WorkspaceMigrationViewFieldGroupActionsBuilderService,
WorkspaceMigrationViewSortActionsBuilderService,
WorkspaceMigrationIndexActionsBuilderService,
WorkspaceMigrationLogicFunctionActionsBuilderService,
WorkspaceMigrationFieldActionsBuilderService,
@@ -64,6 +66,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationViewFilterGroupActionsBuilderService,
WorkspaceMigrationViewGroupActionsBuilderService,
WorkspaceMigrationViewFieldGroupActionsBuilderService,
WorkspaceMigrationViewSortActionsBuilderService,
WorkspaceMigrationFieldActionsBuilderService,
WorkspaceMigrationLogicFunctionActionsBuilderService,
WorkspaceMigrationRoleActionsBuilderService,
@@ -133,6 +133,7 @@ export const fromUniversalFlatFieldMetadataToFlatFieldMetadata = ({
calendarViewIds: [],
mainGroupByFieldMetadataViewIds: [],
kanbanAggregateOperationViewIds: [],
viewSortIds: [],
...emptyUniversalForeignKeyAggregators,
};
};
@@ -0,0 +1,65 @@
import { Injectable } from '@nestjs/common';
import { v4 } from 'uuid';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import {
FlatCreateViewSortAction,
UniversalCreateViewSortAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-sort/types/workspace-migration-view-sort-action.type';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util';
@Injectable()
export class CreateViewSortActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create',
'viewSort',
) {
override async transpileUniversalActionToFlatAction({
action,
allFlatEntityMaps,
flatApplication,
workspaceId,
}: WorkspaceMigrationActionRunnerArgs<UniversalCreateViewSortAction>): Promise<FlatCreateViewSortAction> {
const { fieldMetadataId, viewId } =
resolveUniversalRelationIdentifiersToIds({
flatEntityMaps: allFlatEntityMaps,
metadataName: action.metadataName,
universalForeignKeyValues: action.flatEntity,
});
return {
...action,
flatEntity: {
...action.flatEntity,
fieldMetadataId,
viewId,
id: action.id ?? v4(),
applicationId: flatApplication.id,
workspaceId,
},
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatCreateViewSortAction>,
): Promise<void> {
const { flatAction, queryRunner } = context;
const { flatEntity } = flatAction;
await this.insertFlatEntitiesInRepository({
queryRunner,
flatEntities: [flatEntity],
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatCreateViewSortAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,49 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
import {
FlatDeleteViewSortAction,
UniversalDeleteViewSortAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-sort/types/workspace-migration-view-sort-action.type';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
export class DeleteViewSortActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete',
'viewSort',
) {
constructor() {
super();
}
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalDeleteViewSortAction>,
): Promise<FlatDeleteViewSortAction> {
return this.transpileUniversalDeleteActionToFlatDeleteAction(context);
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatDeleteViewSortAction>,
): Promise<void> {
const { flatAction, queryRunner, workspaceId } = context;
const viewSortRepository =
queryRunner.manager.getRepository<ViewSortEntity>(ViewSortEntity);
await viewSortRepository.delete({
id: flatAction.entityId,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatDeleteViewSortAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,67 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
import {
FlatUpdateViewSortAction,
UniversalUpdateViewSortAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/view-sort/types/workspace-migration-view-sort-action.type';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-update-relation-identifiers-to-ids.util';
@Injectable()
export class UpdateViewSortActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'update',
'viewSort',
) {
constructor() {
super();
}
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalUpdateViewSortAction>,
): Promise<FlatUpdateViewSortAction> {
const { action, allFlatEntityMaps } = context;
const flatViewSort = findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: allFlatEntityMaps.flatViewSortMaps,
universalIdentifier: action.universalIdentifier,
});
const update = resolveUniversalUpdateRelationIdentifiersToIds({
metadataName: 'viewSort',
universalUpdate: action.update,
allFlatEntityMaps,
});
return {
type: 'update',
metadataName: 'viewSort',
entityId: flatViewSort.id,
update,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatUpdateViewSortAction>,
): Promise<void> {
const { flatAction, queryRunner, workspaceId } = context;
const { entityId, update } = flatAction;
const viewSortRepository =
queryRunner.manager.getRepository<ViewSortEntity>(ViewSortEntity);
await viewSortRepository.update({ id: entityId, workspaceId }, update);
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatUpdateViewSortAction>,
): Promise<void> {
return;
}
}
@@ -62,6 +62,7 @@ export class CreateViewActionHandlerService extends WorkspaceMigrationRunnerActi
viewGroupIds: [],
viewFilterIds: [],
viewFilterGroupIds: [],
viewSortIds: [],
...emptyUniversalForeignKeyAggregators,
},
};
@@ -72,6 +72,9 @@ import { UpdateViewGroupActionHandlerService } from 'src/engine/workspace-manage
import { CreateViewActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view/services/create-view-action-handler.service';
import { DeleteViewActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view/services/delete-view-action-handler.service';
import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view/services/update-view-action-handler.service';
import { CreateViewSortActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-sort/services/create-view-sort-action-handler.service';
import { UpdateViewSortActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-sort/services/update-view-sort-action-handler.service';
import { DeleteViewSortActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view-sort/services/delete-view-sort-action-handler.service';
@Module({
imports: [
@@ -107,6 +110,10 @@ import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/wor
UpdateViewFilterGroupActionHandlerService,
DeleteViewFilterGroupActionHandlerService,
CreateViewSortActionHandlerService,
UpdateViewSortActionHandlerService,
DeleteViewSortActionHandlerService,
CreateViewGroupActionHandlerService,
UpdateViewGroupActionHandlerService,
DeleteViewGroupActionHandlerService,

Some files were not shown because too many files have changed in this diff Show More