Allow users to create Fields and Field widget (#18801)
- Allow users to create a Fields widget or a Field widget; **this PR is focused on Fields widgets as Field widget can't be configured yet** - Automatically create a filled view when the user creates a draft Fields widget in edit mode https://github.com/user-attachments/assets/b2dbba52-c614-44cd-bf6c-095ce9d4ec26 --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
committed by
GitHub
parent
dc00701448
commit
e9aa6f47e5
@@ -16,6 +16,7 @@ import { PageLayoutDuplicationService } from 'src/engine/metadata-modules/page-l
|
||||
import { PageLayoutUpdateService } from 'src/engine/metadata-modules/page-layout/services/page-layout-update.service';
|
||||
import { PageLayoutService } from 'src/engine/metadata-modules/page-layout/services/page-layout.service';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { ViewModule } from 'src/engine/metadata-modules/view/view.module';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
|
||||
@@ -37,6 +38,7 @@ import { DashboardSyncModule } from 'src/modules/dashboard-sync/dashboard-sync.m
|
||||
FlatPageLayoutWidgetModule,
|
||||
ApplicationModule,
|
||||
DashboardSyncModule,
|
||||
ViewModule,
|
||||
],
|
||||
controllers: [PageLayoutController],
|
||||
providers: [
|
||||
|
||||
+121
-1
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { PageLayoutTabLayoutMode } from 'twenty-shared/types';
|
||||
import { computeDiffBetweenObjects, isDefined } from 'twenty-shared/utils';
|
||||
@@ -21,6 +21,7 @@ import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layou
|
||||
import { reconstructFlatPageLayoutWithTabsAndWidgets } from 'src/engine/metadata-modules/flat-page-layout/utils/reconstruct-flat-page-layout-with-tabs-and-widgets.util';
|
||||
import { UpdatePageLayoutTabWithWidgetsInput } from 'src/engine/metadata-modules/page-layout-tab/dtos/inputs/update-page-layout-tab-with-widgets.input';
|
||||
import { UpdatePageLayoutWidgetWithIdInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/update-page-layout-widget-with-id.input';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { UpdatePageLayoutWithTabsInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-with-tabs.input';
|
||||
import { PageLayoutDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout.dto';
|
||||
import {
|
||||
@@ -30,6 +31,7 @@ import {
|
||||
generatePageLayoutExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout.exception';
|
||||
import { fromFlatPageLayoutWithTabsAndWidgetsToPageLayoutDto } from 'src/engine/metadata-modules/page-layout/utils/from-flat-page-layout-with-tabs-and-widgets-to-page-layout-dto.util';
|
||||
import { ViewService } from 'src/engine/metadata-modules/view/services/view.service';
|
||||
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';
|
||||
import { DashboardSyncService } from 'src/modules/dashboard-sync/services/dashboard-sync.service';
|
||||
@@ -42,11 +44,14 @@ type UpdatePageLayoutWithTabsParams = {
|
||||
|
||||
@Injectable()
|
||||
export class PageLayoutUpdateService {
|
||||
private readonly logger = new Logger(PageLayoutUpdateService.name);
|
||||
|
||||
constructor(
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly dashboardSyncService: DashboardSyncService,
|
||||
private readonly viewService: ViewService,
|
||||
) {}
|
||||
|
||||
async updatePageLayoutWithTabs({
|
||||
@@ -161,6 +166,12 @@ export class PageLayoutUpdateService {
|
||||
flatViewMaps,
|
||||
});
|
||||
|
||||
const orphanedViewIds = this.collectOrphanedViewIdsFromDeletedWidgets({
|
||||
widgetsToUpdate,
|
||||
tabsToUpdate,
|
||||
flatPageLayoutWidgetMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
@@ -223,6 +234,11 @@ export class PageLayoutUpdateService {
|
||||
},
|
||||
);
|
||||
|
||||
await this.destroyOrphanedFieldsWidgetViews({
|
||||
viewIds: orphanedViewIds,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutWithTabsAndWidgetsToPageLayoutDto(
|
||||
reconstructFlatPageLayoutWithTabsAndWidgets({
|
||||
layout: flatLayout,
|
||||
@@ -617,4 +633,108 @@ export class PageLayoutUpdateService {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private collectOrphanedViewIdsFromDeletedWidgets({
|
||||
widgetsToUpdate,
|
||||
tabsToUpdate,
|
||||
flatPageLayoutWidgetMaps,
|
||||
}: {
|
||||
widgetsToUpdate: FlatPageLayoutWidget[];
|
||||
tabsToUpdate: FlatPageLayoutTab[];
|
||||
flatPageLayoutWidgetMaps: Pick<
|
||||
AllFlatEntityMaps,
|
||||
'flatPageLayoutWidgetMaps'
|
||||
>['flatPageLayoutWidgetMaps'];
|
||||
}): string[] {
|
||||
const viewIdsToDelete = new Set<string>();
|
||||
const directlyDeletedWidgetIds = new Set<string>();
|
||||
|
||||
// Collect viewIds from directly deleted FIELDS widgets
|
||||
for (const widget of widgetsToUpdate) {
|
||||
if (isDefined(widget.deletedAt)) {
|
||||
directlyDeletedWidgetIds.add(widget.id);
|
||||
const viewId = this.getViewIdFromFieldsWidget(widget);
|
||||
|
||||
if (isDefined(viewId)) {
|
||||
viewIdsToDelete.add(viewId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect viewIds from FIELDS widgets in deleted tabs
|
||||
const deletedTabIds = new Set(
|
||||
tabsToUpdate
|
||||
.filter((tab) => isDefined(tab.deletedAt))
|
||||
.map((tab) => tab.id),
|
||||
);
|
||||
|
||||
const allExistingWidgets = Object.values(
|
||||
flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
).filter(isDefined);
|
||||
|
||||
for (const widget of allExistingWidgets) {
|
||||
if (
|
||||
!isDefined(widget.deletedAt) &&
|
||||
deletedTabIds.has(widget.pageLayoutTabId)
|
||||
) {
|
||||
const viewId = this.getViewIdFromFieldsWidget(widget);
|
||||
|
||||
if (isDefined(viewId)) {
|
||||
viewIdsToDelete.add(viewId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out viewIds still referenced by surviving widgets
|
||||
for (const widget of allExistingWidgets) {
|
||||
if (
|
||||
!isDefined(widget.deletedAt) &&
|
||||
!directlyDeletedWidgetIds.has(widget.id) &&
|
||||
!deletedTabIds.has(widget.pageLayoutTabId)
|
||||
) {
|
||||
const viewId = this.getViewIdFromFieldsWidget(widget);
|
||||
|
||||
if (isDefined(viewId)) {
|
||||
viewIdsToDelete.delete(viewId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...viewIdsToDelete];
|
||||
}
|
||||
|
||||
private getViewIdFromFieldsWidget(
|
||||
widget: FlatPageLayoutWidget,
|
||||
): string | undefined {
|
||||
if (
|
||||
widget.configuration.configurationType !== WidgetConfigurationType.FIELDS
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const viewId = (widget.configuration as { viewId?: string | null }).viewId;
|
||||
|
||||
return typeof viewId === 'string' ? viewId : undefined;
|
||||
}
|
||||
|
||||
private async destroyOrphanedFieldsWidgetViews({
|
||||
viewIds,
|
||||
workspaceId,
|
||||
}: {
|
||||
viewIds: string[];
|
||||
workspaceId: string;
|
||||
}): Promise<void> {
|
||||
for (const viewId of viewIds) {
|
||||
try {
|
||||
await this.viewService.destroyOne({
|
||||
destroyViewInput: { id: viewId },
|
||||
workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`Failed to destroy view ${viewId} after Fields widget deletion: ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user