Introducing view field group syncable entity (#17867)
## Context Introduces a new viewFieldGroup entity that allows grouping view fields into sections (e.g. "General", "Additional", "Other") within a view. The page layout fields widget needs a way to organize fields into sections. Today, views have no concept of field grouping. This PR introduces the viewFieldGroup entity which sits between a view and its viewFields, enabling section-based organization. <img width="401" height="724" alt="Layout - V2 (customize visibility)" src="https://github.com/user-attachments/assets/6376e2ab-44db-42bf-9d2c-758f56f6b548" /> --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
+8
-8
@@ -29,7 +29,7 @@ import {
|
||||
ViewFieldExceptionMessageKey,
|
||||
} from 'src/engine/metadata-modules/view-field/exceptions/view-field.exception';
|
||||
import { ViewFieldRestApiExceptionFilter } from 'src/engine/metadata-modules/view-field/filters/view-field-rest-api-exception.filter';
|
||||
import { ViewFieldV2Service } from 'src/engine/metadata-modules/view-field/services/view-field-v2.service';
|
||||
import { ViewFieldService } from 'src/engine/metadata-modules/view-field/services/view-field.service';
|
||||
import { CreateViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-field-permission.guard';
|
||||
import { DeleteViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-field-permission.guard';
|
||||
import { UpdateViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/update-view-field-permission.guard';
|
||||
@@ -38,7 +38,7 @@ import { UpdateViewFieldPermissionGuard } from 'src/engine/metadata-modules/view
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(ViewFieldRestApiExceptionFilter)
|
||||
export class ViewFieldController {
|
||||
constructor(private readonly viewFieldV2Service: ViewFieldV2Service) {}
|
||||
constructor(private readonly viewFieldService: ViewFieldService) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(NoPermissionGuard)
|
||||
@@ -47,10 +47,10 @@ export class ViewFieldController {
|
||||
@Query('viewId') viewId?: string,
|
||||
): Promise<ViewFieldEntity[]> {
|
||||
if (viewId) {
|
||||
return this.viewFieldV2Service.findByViewId(workspace.id, viewId);
|
||||
return this.viewFieldService.findByViewId(workspace.id, viewId);
|
||||
}
|
||||
|
||||
return this.viewFieldV2Service.findByWorkspaceId(workspace.id);
|
||||
return this.viewFieldService.findByWorkspaceId(workspace.id);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@@ -59,7 +59,7 @@ export class ViewFieldController {
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<ViewFieldEntity> {
|
||||
const viewField = await this.viewFieldV2Service.findById(id, workspace.id);
|
||||
const viewField = await this.viewFieldService.findById(id, workspace.id);
|
||||
|
||||
if (!isDefined(viewField)) {
|
||||
throw new ViewFieldException(
|
||||
@@ -86,7 +86,7 @@ export class ViewFieldController {
|
||||
@Body() input: UpdateViewFieldInput['update'],
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<ViewFieldDTO> {
|
||||
return await this.viewFieldV2Service.updateOne({
|
||||
return await this.viewFieldService.updateOne({
|
||||
updateViewFieldInput: { id, update: input },
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
@@ -98,7 +98,7 @@ export class ViewFieldController {
|
||||
@Body() input: CreateViewFieldInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<ViewFieldDTO> {
|
||||
return await this.viewFieldV2Service.createOne({
|
||||
return await this.viewFieldService.createOne({
|
||||
createViewFieldInput: input,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
@@ -110,7 +110,7 @@ export class ViewFieldController {
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<{ success: boolean }> {
|
||||
const deletedViewField = await this.viewFieldV2Service.deleteOne({
|
||||
const deletedViewField = await this.viewFieldService.deleteOne({
|
||||
deleteViewFieldInput: { id },
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
+5
@@ -46,6 +46,11 @@ export class CreateViewFieldInput {
|
||||
@Field(() => AggregateOperations, { nullable: true })
|
||||
aggregateOperation?: AggregateOperations;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
viewFieldGroupId?: string;
|
||||
|
||||
@HideField()
|
||||
universalIdentifier?: string;
|
||||
|
||||
|
||||
+5
@@ -35,6 +35,11 @@ class UpdateViewFieldInputUpdates {
|
||||
@IsEnum(AggregateOperations)
|
||||
@Field(() => AggregateOperations, { nullable: true })
|
||||
aggregateOperation?: AggregateOperations;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
viewFieldGroupId?: string | null;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
|
||||
+15
@@ -13,6 +13,7 @@ import {
|
||||
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
|
||||
@@ -64,6 +65,9 @@ export class ViewFieldEntity
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
viewId: string;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
viewFieldGroupId: string | null;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@@ -78,4 +82,15 @@ export class ViewFieldEntity
|
||||
})
|
||||
@JoinColumn({ name: 'viewId' })
|
||||
view: Relation<ViewEntity>;
|
||||
|
||||
@ManyToOne(
|
||||
() => ViewFieldGroupEntity,
|
||||
(viewFieldGroup) => viewFieldGroup.viewFields,
|
||||
{
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: 'viewFieldGroupId' })
|
||||
viewFieldGroup: Relation<ViewFieldGroupEntity> | null;
|
||||
}
|
||||
|
||||
+10
-10
@@ -1,9 +1,9 @@
|
||||
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Query } from '@nestjs/graphql';
|
||||
|
||||
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input';
|
||||
@@ -12,7 +12,7 @@ import { DestroyViewFieldInput } from 'src/engine/metadata-modules/view-field/dt
|
||||
import { UpdateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/update-view-field.input';
|
||||
import { ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
|
||||
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
import { ViewFieldV2Service } from 'src/engine/metadata-modules/view-field/services/view-field-v2.service';
|
||||
import { ViewFieldService } from 'src/engine/metadata-modules/view-field/services/view-field.service';
|
||||
import { CreateViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-field-permission.guard';
|
||||
import { DeleteViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-field-permission.guard';
|
||||
import { DestroyViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/destroy-view-field-permission.guard';
|
||||
@@ -23,7 +23,7 @@ import { ViewGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/view/
|
||||
@UseFilters(ViewGraphqlApiExceptionFilter)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
export class ViewFieldResolver {
|
||||
constructor(private readonly viewFieldV2Service: ViewFieldV2Service) {}
|
||||
constructor(private readonly viewFieldService: ViewFieldService) {}
|
||||
|
||||
@Query(() => [ViewFieldDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
@@ -31,7 +31,7 @@ export class ViewFieldResolver {
|
||||
@Args('viewId', { type: () => String }) viewId: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<ViewFieldEntity[]> {
|
||||
return this.viewFieldV2Service.findByViewId(workspace.id, viewId);
|
||||
return this.viewFieldService.findByViewId(workspace.id, viewId);
|
||||
}
|
||||
|
||||
@Query(() => ViewFieldDTO, { nullable: true })
|
||||
@@ -40,7 +40,7 @@ export class ViewFieldResolver {
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<ViewFieldEntity | null> {
|
||||
return this.viewFieldV2Service.findById(id, workspace.id);
|
||||
return this.viewFieldService.findById(id, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => ViewFieldDTO)
|
||||
@@ -49,7 +49,7 @@ export class ViewFieldResolver {
|
||||
@Args('input') updateViewFieldInput: UpdateViewFieldInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<ViewFieldDTO> {
|
||||
return await this.viewFieldV2Service.updateOne({
|
||||
return await this.viewFieldService.updateOne({
|
||||
updateViewFieldInput,
|
||||
workspaceId,
|
||||
});
|
||||
@@ -61,7 +61,7 @@ export class ViewFieldResolver {
|
||||
@Args('input') createViewFieldInput: CreateViewFieldInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<ViewFieldDTO> {
|
||||
return await this.viewFieldV2Service.createOne({
|
||||
return await this.viewFieldService.createOne({
|
||||
createViewFieldInput,
|
||||
workspaceId,
|
||||
});
|
||||
@@ -74,7 +74,7 @@ export class ViewFieldResolver {
|
||||
createViewFieldInputs: CreateViewFieldInput[],
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<ViewFieldDTO[]> {
|
||||
return await this.viewFieldV2Service.createMany({
|
||||
return await this.viewFieldService.createMany({
|
||||
createViewFieldInputs,
|
||||
workspaceId,
|
||||
});
|
||||
@@ -86,7 +86,7 @@ export class ViewFieldResolver {
|
||||
@Args('input') deleteViewFieldInput: DeleteViewFieldInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<ViewFieldDTO> {
|
||||
return await this.viewFieldV2Service.deleteOne({
|
||||
return await this.viewFieldService.deleteOne({
|
||||
deleteViewFieldInput,
|
||||
workspaceId,
|
||||
});
|
||||
@@ -98,7 +98,7 @@ export class ViewFieldResolver {
|
||||
@Args('input') destroyViewFieldInput: DestroyViewFieldInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<ViewFieldDTO> {
|
||||
return await this.viewFieldV2Service.destroyOne({
|
||||
return await this.viewFieldService.destroyOne({
|
||||
destroyViewFieldInput,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
+14
-5
@@ -27,7 +27,7 @@ import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@Injectable()
|
||||
export class ViewFieldV2Service {
|
||||
export class ViewFieldService {
|
||||
constructor(
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
@@ -76,11 +76,15 @@ export class ViewFieldV2Service {
|
||||
},
|
||||
);
|
||||
|
||||
const { flatFieldMetadataMaps, flatViewMaps } =
|
||||
const { flatFieldMetadataMaps, flatViewMaps, flatViewFieldGroupMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatFieldMetadataMaps', 'flatViewMaps'],
|
||||
flatMapsKeys: [
|
||||
'flatFieldMetadataMaps',
|
||||
'flatViewMaps',
|
||||
'flatViewFieldGroupMaps',
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -91,6 +95,7 @@ export class ViewFieldV2Service {
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
flatFieldMetadataMaps,
|
||||
flatViewMaps,
|
||||
flatViewFieldGroupMaps,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -146,17 +151,21 @@ export class ViewFieldV2Service {
|
||||
},
|
||||
);
|
||||
|
||||
const { flatViewFieldMaps: existingFlatViewFieldMaps } =
|
||||
const {
|
||||
flatViewFieldMaps: existingFlatViewFieldMaps,
|
||||
flatViewFieldGroupMaps,
|
||||
} =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatViewFieldMaps'],
|
||||
flatMapsKeys: ['flatViewFieldMaps', 'flatViewFieldGroupMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const optimisticallyUpdatedFlatView =
|
||||
fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow({
|
||||
flatViewFieldMaps: existingFlatViewFieldMaps,
|
||||
flatViewFieldGroupMaps,
|
||||
updateViewFieldInput,
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
||||
import { ViewFieldController } from 'src/engine/metadata-modules/view-field/controllers/view-field.controller';
|
||||
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
import { ViewFieldResolver } from 'src/engine/metadata-modules/view-field/resolvers/view-field.resolver';
|
||||
import { ViewFieldV2Service } from 'src/engine/metadata-modules/view-field/services/view-field-v2.service';
|
||||
import { ViewFieldService } from 'src/engine/metadata-modules/view-field/services/view-field.service';
|
||||
import { ViewPermissionsModule } from 'src/engine/metadata-modules/view-permissions/view-permissions.module';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
@@ -24,7 +24,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
ViewPermissionsModule,
|
||||
],
|
||||
controllers: [ViewFieldController],
|
||||
providers: [ViewFieldResolver, ViewFieldV2Service],
|
||||
exports: [ViewFieldV2Service],
|
||||
providers: [ViewFieldResolver, ViewFieldService],
|
||||
exports: [ViewFieldService],
|
||||
})
|
||||
export class ViewFieldModule {}
|
||||
|
||||
Reference in New Issue
Block a user