Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';

import { DashboardSyncService } from 'src/engine/metadata-modules/dashboard/services/dashboard-sync.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';

@Module({
imports: [TwentyORMModule, WorkspaceManyOrAllFlatEntityMapsCacheModule],
providers: [DashboardSyncService],
exports: [DashboardSyncService],
})
export class DashboardModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Injectable, Logger } from '@nestjs/common';

import { isDefined } from 'twenty-shared/utils';

import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';

@Injectable()
export class DashboardSyncService {
private readonly logger = new Logger(DashboardSyncService.name);

constructor(
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {}

async updateLinkedDashboardsUpdatedAtByPageLayoutId({
pageLayoutId,
workspaceId,
}: {
pageLayoutId: string;
workspaceId: string;
}): Promise<void> {
const authContext = buildSystemAuthContext(workspaceId);

try {
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
authContext,
async () => {
const dashboardRepository =
await this.globalWorkspaceOrmManager.getRepository(
workspaceId,
'dashboard',
{ shouldBypassPermissionChecks: true },
);

await dashboardRepository.update(
{ pageLayoutId },
{ updatedAt: new Date() },
);
},
);
} catch (error) {
this.logger.error(
`Failed to update dashboard updatedAt for page layout ${pageLayoutId}: ${error}`,
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Error logging loses stack trace. When concatenating ${error} into the message string, the stack trace is lost. Consider passing the stack trace as a separate argument to preserve debugging information.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-server/src/engine/metadata-modules/dashboard/services/dashboard-sync.service.ts, line 46:

<comment>Error logging loses stack trace. When concatenating `${error}` into the message string, the stack trace is lost. Consider passing the stack trace as a separate argument to preserve debugging information.</comment>

<file context>
@@ -0,0 +1,110 @@
+      );
+    } catch (error) {
+      this.logger.error(
+        `Failed to update dashboard updatedAt for page layout ${pageLayoutId}: ${error}`,
+      );
+    }
</file context>
Fix with Cubic

);
}
}

async updateLinkedDashboardsUpdatedAtByTabId({
tabId,
workspaceId,
}: {
tabId: string;
workspaceId: string;
}): Promise<void> {
const { flatPageLayoutTabMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatPageLayoutTabMaps'],
},
);

const tab = flatPageLayoutTabMaps.byId[tabId];

if (!isDefined(tab)) {
return;
}

await this.updateLinkedDashboardsUpdatedAtByPageLayoutId({
pageLayoutId: tab.pageLayoutId,
workspaceId,
});
}

async updateLinkedDashboardsUpdatedAtByWidgetId({
widgetId,
workspaceId,
}: {
widgetId: string;
workspaceId: string;
}): Promise<void> {
const { flatPageLayoutWidgetMaps, flatPageLayoutTabMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatPageLayoutWidgetMaps', 'flatPageLayoutTabMaps'],
},
);

const widget = flatPageLayoutWidgetMaps.byId[widgetId];

if (!isDefined(widget)) {
return;
}

const tab = flatPageLayoutTabMaps.byId[widget.pageLayoutTabId];

if (!isDefined(tab)) {
return;
}

await this.updateLinkedDashboardsUpdatedAtByPageLayoutId({
pageLayoutId: tab.pageLayoutId,
workspaceId,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DashboardModule } from 'src/engine/metadata-modules/dashboard/dashboard.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { FlatPageLayoutTabModule } from 'src/engine/metadata-modules/flat-page-layout-tab/flat-page-layout-tab.module';
import { FlatPageLayoutWidgetModule } from 'src/engine/metadata-modules/flat-page-layout-widget/flat-page-layout-widget.module';
Expand All @@ -29,6 +30,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
FlatPageLayoutTabModule,
FlatPageLayoutWidgetModule,
ApplicationModule,
DashboardModule,
],
controllers: [PageLayoutTabController],
providers: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';

import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { DashboardSyncService } from 'src/engine/metadata-modules/dashboard/services/dashboard-sync.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 { FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
Expand Down Expand Up @@ -36,6 +37,7 @@ export class PageLayoutTabService {
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly applicationService: ApplicationService,
private readonly dashboardSyncService: DashboardSyncService,
) {}

async findByPageLayoutId(
Expand Down Expand Up @@ -156,6 +158,11 @@ export class PageLayoutTabService {
);
}

await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByTabId({
tabId: flatPageLayoutTabToCreate.id,
workspaceId,
});

const { flatPageLayoutTabMaps: recomputedFlatPageLayoutTabMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
Expand Down Expand Up @@ -218,6 +225,11 @@ export class PageLayoutTabService {
);
}

await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByTabId({
tabId: id,
workspaceId,
});

const { flatPageLayoutTabMaps: recomputedFlatPageLayoutTabMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
Expand Down Expand Up @@ -274,6 +286,11 @@ export class PageLayoutTabService {
);
}

await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByTabId({
tabId: id,
workspaceId,
});

const { flatPageLayoutTabMaps: recomputedFlatPageLayoutTabMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
Expand Down Expand Up @@ -327,6 +344,11 @@ export class PageLayoutTabService {
);
}

await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByTabId({
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we want to actually destroy the dashboard in this case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is already handled: in the page layout service

if (flatPageLayoutToDestroy.type === PageLayoutType.DASHBOARD) {
     await this.destroyAssociatedDashboards(id, workspaceId);
   }

tabId: id,
workspaceId,
});

return true;
}

Expand Down Expand Up @@ -370,6 +392,11 @@ export class PageLayoutTabService {
);
}

await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByTabId({
tabId: id,
workspaceId,
});

const { flatPageLayoutTabMaps: recomputedFlatPageLayoutTabMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DashboardModule } from 'src/engine/metadata-modules/dashboard/dashboard.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { FlatPageLayoutWidgetModule } from 'src/engine/metadata-modules/flat-page-layout-widget/flat-page-layout-widget.module';
import { PageLayoutWidgetController } from 'src/engine/metadata-modules/page-layout-widget/controllers/page-layout-widget.controller';
Expand All @@ -27,6 +28,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
WorkspaceManyOrAllFlatEntityMapsCacheModule,
FlatPageLayoutWidgetModule,
ApplicationModule,
DashboardModule,
],
controllers: [PageLayoutWidgetController],
providers: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { DashboardSyncService } from 'src/engine/metadata-modules/dashboard/services/dashboard-sync.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 { FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
Expand Down Expand Up @@ -47,6 +48,7 @@ export class PageLayoutWidgetService {
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly applicationService: ApplicationService,
private readonly dashboardSyncService: DashboardSyncService,
) {}

private async getFlatPageLayoutWidgetMaps(
Expand Down Expand Up @@ -227,6 +229,11 @@ export class PageLayoutWidgetService {
'Multiple validation errors occurred while creating page layout widget',
});

await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByWidgetId({
widgetId: flatPageLayoutWidgetToCreate.id,
workspaceId,
});

const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId);

return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(
Expand Down Expand Up @@ -332,6 +339,11 @@ export class PageLayoutWidgetService {
'Multiple validation errors occurred while updating page layout widget',
});

await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByWidgetId({
widgetId: id,
workspaceId,
});

const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId);

return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(
Expand Down Expand Up @@ -402,6 +414,11 @@ export class PageLayoutWidgetService {
'Multiple validation errors occurred while deleting page layout widget',
});

await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByWidgetId({
widgetId: id,
workspaceId,
});

const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId);

return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(
Expand Down Expand Up @@ -433,6 +450,11 @@ export class PageLayoutWidgetService {
'Multiple validation errors occurred while destroying page layout widget',
});

await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByWidgetId({
widgetId: id,
workspaceId,
});

return true;
}

Expand All @@ -457,6 +479,11 @@ export class PageLayoutWidgetService {
'Multiple validation errors occurred while restoring page layout widget',
});

await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByWidgetId({
widgetId: id,
workspaceId,
});

const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId);

return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@ import { ApplicationModule } from 'src/engine/core-modules/application/applicati
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { I18nModule } from 'src/engine/core-modules/i18n/i18n.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DashboardModule } from 'src/engine/metadata-modules/dashboard/dashboard.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { FlatPageLayoutTabModule } from 'src/engine/metadata-modules/flat-page-layout-tab/flat-page-layout-tab.module';
import { FlatPageLayoutWidgetModule } from 'src/engine/metadata-modules/flat-page-layout-widget/flat-page-layout-widget.module';
import { FlatPageLayoutModule } from 'src/engine/metadata-modules/flat-page-layout/flat-page-layout.module';
import { PageLayoutTabModule } from 'src/engine/metadata-modules/page-layout-tab/page-layout-tab.module';
import { PageLayoutWidgetModule } from 'src/engine/metadata-modules/page-layout-widget/page-layout-widget.module';
import { PageLayoutController } from 'src/engine/metadata-modules/page-layout/controllers/page-layout.controller';
import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
import { PageLayoutResolver } from 'src/engine/metadata-modules/page-layout/resolvers/page-layout.resolver';
import { PageLayoutDuplicationService } from 'src/engine/metadata-modules/page-layout/services/page-layout-duplication.service';
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 { PageLayoutTabModule } from 'src/engine/metadata-modules/page-layout-tab/page-layout-tab.module';
import { PageLayoutWidgetModule } from 'src/engine/metadata-modules/page-layout-widget/page-layout-widget.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
Expand All @@ -39,6 +40,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
ApplicationModule,
PageLayoutTabModule,
PageLayoutWidgetModule,
DashboardModule,
],
controllers: [PageLayoutController],
providers: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { computeDiffBetweenObjects, isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';

import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { DashboardSyncService } from 'src/engine/metadata-modules/dashboard/services/dashboard-sync.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 { FLAT_PAGE_LAYOUT_TAB_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-tab/constants/flat-page-layout-tab-editable-properties.constant';
Expand Down Expand Up @@ -40,6 +41,7 @@ export class PageLayoutUpdateService {
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly applicationService: ApplicationService,
private readonly dashboardSyncService: DashboardSyncService,
) {}

async updatePageLayoutWithTabs({
Expand Down Expand Up @@ -144,6 +146,13 @@ export class PageLayoutUpdateService {
);
}

await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByPageLayoutId(
{
pageLayoutId: id,
workspaceId,
},
);

const {
flatPageLayoutMaps: recomputedFlatPageLayoutMaps,
flatPageLayoutTabMaps: recomputedFlatPageLayoutTabMaps,
Expand Down
Loading