Move view in metadata-modules/ and create atomic folder + module for each view entity (#14990)

# Introduction
Preparing view-filter and view-group introduction in v2 core engine
Moving view from `core-modules` to `metadata-modules`

## What happened

### Created dedicated modules for each view entity:
- ViewFieldModule
- ViewFilterModule
- ViewFilterGroupModule
- ViewGroupModule
- ViewSortModule

### Each module is now completely independent with its own:
- Controller
- Resolver
- Service
- Entity

### Created dedicated abstraction metadata module folder for:
- flat-view-field
- flat-view

### Dependencies 
- Eleminated circular dep on ViewModule to all others ones
- Granular import not importing the whole viewModule anymore everywhere


close https://github.com/twentyhq/core-team-issues/issues/1703
This commit is contained in:
Paul Rastoin
2025-10-09 15:18:15 +02:00
committed by GitHub
parent c9a1a110e1
commit 59fbe35a8c
198 changed files with 877 additions and 765 deletions
@@ -0,0 +1,93 @@
import { Field, InputType } from '@nestjs/graphql';
import {
IsBoolean,
IsEnum,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator';
import { ViewCalendarLayout } from 'src/engine/metadata-modules/view/enums/view-calendar-layout.enum';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in';
import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum';
@InputType()
export class CreateViewInput {
@IsOptional()
@IsUUID()
@Field(() => UUIDScalarType, { nullable: true })
id?: string;
@IsString()
@IsNotEmpty()
@IsValidMetadataName()
@Field({ nullable: false })
name: string;
@IsUUID()
@Field(() => UUIDScalarType, { nullable: false })
objectMetadataId: string;
@IsEnum(ViewType)
@Field(() => ViewType, { nullable: true, defaultValue: ViewType.TABLE })
type?: ViewType;
@IsOptional()
@IsEnum(ViewKey)
@Field(() => ViewKey, { nullable: true })
key?: ViewKey;
@IsString()
@Field({ nullable: false })
icon: string;
@IsOptional()
@IsNumber()
@Field({ nullable: true, defaultValue: 0 })
position?: number;
@IsOptional()
@IsBoolean()
@Field({ nullable: true, defaultValue: false })
isCompact?: boolean;
@IsOptional()
@IsEnum(ViewOpenRecordIn)
@Field(() => ViewOpenRecordIn, {
nullable: true,
defaultValue: ViewOpenRecordIn.SIDE_PANEL,
})
openRecordIn?: ViewOpenRecordIn;
@IsOptional()
@IsEnum(AggregateOperations)
@Field(() => AggregateOperations, { nullable: true })
kanbanAggregateOperation?: AggregateOperations;
@IsOptional()
@IsUUID()
@Field(() => UUIDScalarType, { nullable: true })
kanbanAggregateOperationFieldMetadataId?: string;
@IsOptional()
@IsString()
@Field({ nullable: true })
anyFieldFilterValue?: string;
@IsOptional()
@IsEnum(ViewCalendarLayout)
@Field(() => ViewCalendarLayout, { nullable: true })
calendarLayout?: ViewCalendarLayout;
@IsOptional()
@IsUUID()
@Field(() => UUIDScalarType, { nullable: true })
calendarFieldMetadataId?: 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 DeleteViewInput {
@IDField(() => UUIDScalarType, {
description: 'The id of the view 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 DestroyViewInput {
@IDField(() => UUIDScalarType, {
description: 'The id of the view to destroy.',
})
@IsUUID()
id: string;
}
@@ -0,0 +1,84 @@
import { Field, InputType } from '@nestjs/graphql';
import {
IsBoolean,
IsEnum,
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator';
import { ViewCalendarLayout } from 'src/engine/metadata-modules/view/enums/view-calendar-layout.enum';
import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in';
import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum';
// TODO: this should be refactored like for view-field.input.ts
// This is a temporary fix as we were extending the CreateViewInput class which was adding default values for the non filled fields
@InputType()
export class UpdateViewInput {
@IsUUID()
@Field(() => UUIDScalarType, { nullable: true })
id: string;
@IsOptional()
@IsNotEmpty()
@IsValidMetadataName()
@Field({ nullable: true })
name?: string;
@IsOptional()
@IsEnum(ViewType)
@Field(() => ViewType, { nullable: true })
type?: ViewType;
@IsOptional()
@IsString()
@Field({ nullable: true })
icon?: string;
@IsOptional()
@IsBoolean()
@Field({ nullable: true })
position?: number;
@IsOptional()
@IsBoolean()
@Field({ nullable: true })
isCompact?: boolean;
@IsOptional()
@IsEnum(ViewOpenRecordIn)
@Field(() => ViewOpenRecordIn, {
nullable: true,
})
openRecordIn?: ViewOpenRecordIn;
@IsOptional()
@IsEnum(AggregateOperations)
@Field(() => AggregateOperations, { nullable: true })
kanbanAggregateOperation?: AggregateOperations;
@IsOptional()
@IsUUID()
@Field(() => UUIDScalarType, { nullable: true })
kanbanAggregateOperationFieldMetadataId?: string;
@IsOptional()
@IsString()
@Field({ nullable: true })
anyFieldFilterValue?: string;
@IsOptional()
@IsEnum(ViewCalendarLayout)
@Field(() => ViewCalendarLayout, { nullable: true })
calendarLayout?: ViewCalendarLayout;
@IsOptional()
@IsUUID()
@Field(() => UUIDScalarType, { nullable: true })
calendarFieldMetadataId?: string;
}
@@ -0,0 +1,98 @@
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
import { ViewFilterGroupDTO } from 'src/engine/metadata-modules/view-filter-group/dtos/view-filter-group.dto';
import { ViewFilterDTO } from 'src/engine/metadata-modules/view-filter/dtos/view-filter.dto';
import { ViewGroupDTO } from 'src/engine/metadata-modules/view-group/dtos/view-group.dto';
import { ViewSortDTO } from 'src/engine/metadata-modules/view-sort/dtos/view-sort.dto';
import { ViewCalendarLayout } from 'src/engine/metadata-modules/view/enums/view-calendar-layout.enum';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in';
import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum';
registerEnumType(ViewOpenRecordIn, { name: 'ViewOpenRecordIn' });
registerEnumType(ViewType, { name: 'ViewType' });
registerEnumType(ViewKey, { name: 'ViewKey' });
registerEnumType(ViewCalendarLayout, { name: 'ViewCalendarLayout' });
@ObjectType('CoreView')
export class ViewDTO {
@IDField(() => UUIDScalarType)
id: string;
@Field({ nullable: false })
name: string;
@Field(() => UUIDScalarType, { nullable: false })
objectMetadataId: string;
@Field(() => ViewType, { nullable: false, defaultValue: ViewType.TABLE })
type: ViewType;
@Field(() => ViewKey, { nullable: true, defaultValue: ViewKey.INDEX })
key: ViewKey | null;
@Field({ nullable: false })
icon: string;
@Field({ nullable: false, defaultValue: 0 })
position: number;
@Field({ nullable: false, defaultValue: false })
isCompact: boolean;
@Field({ nullable: false, defaultValue: false })
isCustom: boolean;
@Field(() => ViewOpenRecordIn, {
nullable: false,
defaultValue: ViewOpenRecordIn.SIDE_PANEL,
})
openRecordIn: ViewOpenRecordIn;
@Field(() => AggregateOperations, { nullable: true })
kanbanAggregateOperation?: AggregateOperations | null;
@Field(() => UUIDScalarType, { nullable: true })
kanbanAggregateOperationFieldMetadataId?: string | null;
@Field(() => UUIDScalarType, { nullable: true })
calendarFieldMetadataId?: string | null;
@Field(() => UUIDScalarType, { nullable: false })
workspaceId: string;
@Field(() => String, { nullable: true })
anyFieldFilterValue?: string | null;
@Field(() => ViewCalendarLayout, { nullable: true })
calendarLayout: ViewCalendarLayout | null;
@Field()
createdAt: Date;
@Field()
updatedAt: Date;
@Field(() => Date, { nullable: true })
deletedAt?: Date | null;
@Field(() => [ViewFieldDTO])
viewFields?: ViewFieldDTO[];
@Field(() => [ViewFilterDTO])
viewFilters?: ViewFilterDTO[];
@Field(() => [ViewFilterGroupDTO])
viewFilterGroups?: ViewFilterGroupDTO[];
@Field(() => [ViewSortDTO])
viewSorts?: ViewSortDTO[];
@Field(() => [ViewGroupDTO])
viewGroups?: ViewGroupDTO[];
}