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:
Weiko
2026-02-11 22:02:58 +01:00
committed by GitHub
parent ed66fbd71b
commit 6aca1dd013
126 changed files with 3593 additions and 115 deletions
@@ -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,
});