Update updatedat field on dashboards after edition (#16964)
Closes https://github.com/twentyhq/core-team-issues/issues/1896
This commit is contained in:
@@ -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 {}
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
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 { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
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,
|
||||
) {}
|
||||
|
||||
private async isPageLayoutOfTypeDashboard({
|
||||
pageLayoutId,
|
||||
workspaceId,
|
||||
}: {
|
||||
pageLayoutId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<boolean> {
|
||||
const { flatPageLayoutMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const pageLayout = flatPageLayoutMaps.byId[pageLayoutId];
|
||||
|
||||
return (
|
||||
isDefined(pageLayout) && pageLayout.type === PageLayoutType.DASHBOARD
|
||||
);
|
||||
}
|
||||
|
||||
async updateLinkedDashboardsUpdatedAtByPageLayoutId({
|
||||
pageLayoutId,
|
||||
workspaceId,
|
||||
updatedAt,
|
||||
}: {
|
||||
pageLayoutId: string;
|
||||
workspaceId: string;
|
||||
updatedAt: Date;
|
||||
}): Promise<void> {
|
||||
const isDashboard = await this.isPageLayoutOfTypeDashboard({
|
||||
pageLayoutId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!isDashboard) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 });
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to update dashboard updatedAt for page layout ${pageLayoutId}: ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async softDeleteLinkedDashboardsByPageLayoutId({
|
||||
pageLayoutId,
|
||||
workspaceId,
|
||||
deletedAt,
|
||||
}: {
|
||||
pageLayoutId: string;
|
||||
workspaceId: string;
|
||||
deletedAt: Date;
|
||||
}): Promise<void> {
|
||||
const isDashboard = await this.isPageLayoutOfTypeDashboard({
|
||||
pageLayoutId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!isDashboard) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 }, { deletedAt });
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to soft delete dashboards for page layout ${pageLayoutId}: ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async restoreLinkedDashboardsByPageLayoutId({
|
||||
pageLayoutId,
|
||||
workspaceId,
|
||||
}: {
|
||||
pageLayoutId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<void> {
|
||||
const isDashboard = await this.isPageLayoutOfTypeDashboard({
|
||||
pageLayoutId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!isDashboard) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 },
|
||||
{ deletedAt: null },
|
||||
);
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to restore dashboards for page layout ${pageLayoutId}: ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async updateLinkedDashboardsUpdatedAtByTabId({
|
||||
tabId,
|
||||
workspaceId,
|
||||
updatedAt,
|
||||
}: {
|
||||
tabId: string;
|
||||
workspaceId: string;
|
||||
updatedAt: Date;
|
||||
}): Promise<void> {
|
||||
const { flatPageLayoutTabMaps, flatPageLayoutMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps', 'flatPageLayoutMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const tab = flatPageLayoutTabMaps.byId[tabId];
|
||||
|
||||
if (!isDefined(tab)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pageLayout = flatPageLayoutMaps.byId[tab.pageLayoutId];
|
||||
|
||||
if (
|
||||
!isDefined(pageLayout) ||
|
||||
pageLayout.type !== PageLayoutType.DASHBOARD
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.updateLinkedDashboardsUpdatedAtByPageLayoutId({
|
||||
pageLayoutId: tab.pageLayoutId,
|
||||
workspaceId,
|
||||
updatedAt,
|
||||
});
|
||||
}
|
||||
|
||||
async updateLinkedDashboardsUpdatedAtByWidgetId({
|
||||
widgetId,
|
||||
workspaceId,
|
||||
updatedAt,
|
||||
}: {
|
||||
widgetId: string;
|
||||
workspaceId: string;
|
||||
updatedAt: Date;
|
||||
}): Promise<void> {
|
||||
const {
|
||||
flatPageLayoutWidgetMaps,
|
||||
flatPageLayoutTabMaps,
|
||||
flatPageLayoutMaps,
|
||||
} =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: [
|
||||
'flatPageLayoutWidgetMaps',
|
||||
'flatPageLayoutTabMaps',
|
||||
'flatPageLayoutMaps',
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const widget = flatPageLayoutWidgetMaps.byId[widgetId];
|
||||
|
||||
if (!isDefined(widget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tab = flatPageLayoutTabMaps.byId[widget.pageLayoutTabId];
|
||||
|
||||
if (!isDefined(tab)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pageLayout = flatPageLayoutMaps.byId[tab.pageLayoutId];
|
||||
|
||||
if (
|
||||
!isDefined(pageLayout) ||
|
||||
pageLayout.type !== PageLayoutType.DASHBOARD
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.updateLinkedDashboardsUpdatedAtByPageLayoutId({
|
||||
pageLayoutId: tab.pageLayoutId,
|
||||
workspaceId,
|
||||
updatedAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
+2
@@ -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';
|
||||
@@ -29,6 +30,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
|
||||
FlatPageLayoutTabModule,
|
||||
FlatPageLayoutWidgetModule,
|
||||
ApplicationModule,
|
||||
DashboardModule,
|
||||
],
|
||||
controllers: [PageLayoutTabController],
|
||||
providers: [
|
||||
|
||||
+56
-24
@@ -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';
|
||||
@@ -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(
|
||||
@@ -164,12 +166,18 @@ export class PageLayoutTabService {
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatPageLayoutTabToCreate.id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
}),
|
||||
);
|
||||
const createdTab = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatPageLayoutTabToCreate.id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByTabId({
|
||||
tabId: flatPageLayoutTabToCreate.id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(createdTab.updatedAt),
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(createdTab);
|
||||
}
|
||||
|
||||
async update(
|
||||
@@ -226,12 +234,18 @@ export class PageLayoutTabService {
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
}),
|
||||
);
|
||||
const updatedTab = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByTabId({
|
||||
tabId: id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(updatedTab.updatedAt),
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(updatedTab);
|
||||
}
|
||||
|
||||
async delete(
|
||||
@@ -282,12 +296,18 @@ export class PageLayoutTabService {
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
}),
|
||||
);
|
||||
const deletedTab = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByTabId({
|
||||
tabId: id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(deletedTab.updatedAt),
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(deletedTab);
|
||||
}
|
||||
|
||||
async destroy(id: string, workspaceId: string): Promise<boolean> {
|
||||
@@ -327,6 +347,12 @@ export class PageLayoutTabService {
|
||||
);
|
||||
}
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByTabId({
|
||||
tabId: id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -378,11 +404,17 @@ export class PageLayoutTabService {
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
}),
|
||||
);
|
||||
const restoredTab = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByTabId({
|
||||
tabId: id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(restoredTab.updatedAt),
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(restoredTab);
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -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';
|
||||
@@ -27,6 +28,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
FlatPageLayoutWidgetModule,
|
||||
ApplicationModule,
|
||||
DashboardModule,
|
||||
],
|
||||
controllers: [PageLayoutWidgetController],
|
||||
providers: [
|
||||
|
||||
+56
-24
@@ -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 { FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
|
||||
@@ -41,6 +42,7 @@ export class PageLayoutWidgetService {
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly dashboardSyncService: DashboardSyncService,
|
||||
) {}
|
||||
|
||||
private async getFlatPageLayoutWidgetMaps(
|
||||
@@ -158,12 +160,18 @@ export class PageLayoutWidgetService {
|
||||
|
||||
const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatPageLayoutWidgetToCreate.id,
|
||||
flatEntityMaps: recomputedMaps,
|
||||
}),
|
||||
);
|
||||
const createdWidget = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatPageLayoutWidgetToCreate.id,
|
||||
flatEntityMaps: recomputedMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByWidgetId({
|
||||
widgetId: flatPageLayoutWidgetToCreate.id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(createdWidget.updatedAt),
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(createdWidget);
|
||||
}
|
||||
|
||||
async update(
|
||||
@@ -211,12 +219,18 @@ export class PageLayoutWidgetService {
|
||||
|
||||
const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedMaps,
|
||||
}),
|
||||
);
|
||||
const updatedWidget = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByWidgetId({
|
||||
widgetId: id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(updatedWidget.updatedAt),
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(updatedWidget);
|
||||
}
|
||||
|
||||
private getExistingWidgetOrThrow(
|
||||
@@ -261,12 +275,18 @@ export class PageLayoutWidgetService {
|
||||
|
||||
const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedMaps,
|
||||
}),
|
||||
);
|
||||
const deletedWidget = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByWidgetId({
|
||||
widgetId: id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(deletedWidget.updatedAt),
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(deletedWidget);
|
||||
}
|
||||
|
||||
async destroy(id: string, workspaceId: string): Promise<boolean> {
|
||||
@@ -290,6 +310,12 @@ export class PageLayoutWidgetService {
|
||||
'Multiple validation errors occurred while destroying page layout widget',
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByWidgetId({
|
||||
widgetId: id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -316,11 +342,17 @@ export class PageLayoutWidgetService {
|
||||
|
||||
const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedMaps,
|
||||
}),
|
||||
);
|
||||
const restoredWidget = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByWidgetId({
|
||||
widgetId: id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(restoredWidget.updatedAt),
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(restoredWidget);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -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';
|
||||
@@ -39,6 +40,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
|
||||
ApplicationModule,
|
||||
PageLayoutTabModule,
|
||||
PageLayoutWidgetModule,
|
||||
DashboardModule,
|
||||
],
|
||||
controllers: [PageLayoutController],
|
||||
providers: [
|
||||
|
||||
+10
@@ -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';
|
||||
@@ -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({
|
||||
@@ -164,6 +166,14 @@ export class PageLayoutUpdateService {
|
||||
flatEntityMaps: recomputedFlatPageLayoutMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByPageLayoutId(
|
||||
{
|
||||
pageLayoutId: id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(flatLayout.updatedAt),
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatPageLayoutWithTabsAndWidgetsToPageLayoutDto(
|
||||
reconstructFlatPageLayoutWithTabsAndWidgets({
|
||||
layout: flatLayout,
|
||||
|
||||
+40
-17
@@ -4,6 +4,7 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
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 { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
|
||||
@@ -44,6 +45,7 @@ export class PageLayoutService {
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly dashboardSyncService: DashboardSyncService,
|
||||
) {}
|
||||
|
||||
async findByWorkspaceId(workspaceId: string): Promise<PageLayoutDTO[]> {
|
||||
@@ -262,12 +264,20 @@ export class PageLayoutService {
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatPageLayoutToPageLayoutDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutMaps,
|
||||
}),
|
||||
const updatedLayout = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByPageLayoutId(
|
||||
{
|
||||
pageLayoutId: id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(updatedLayout.updatedAt),
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatPageLayoutToPageLayoutDto(updatedLayout);
|
||||
}
|
||||
|
||||
async delete(
|
||||
@@ -318,12 +328,20 @@ export class PageLayoutService {
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatPageLayoutToPageLayoutDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutMaps,
|
||||
}),
|
||||
);
|
||||
const deletedLayout = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.softDeleteLinkedDashboardsByPageLayoutId({
|
||||
pageLayoutId: id,
|
||||
workspaceId,
|
||||
deletedAt: isDefined(deletedLayout.deletedAt)
|
||||
? new Date(deletedLayout.deletedAt)
|
||||
: new Date(),
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutToPageLayoutDto(deletedLayout);
|
||||
}
|
||||
|
||||
async destroy(
|
||||
@@ -456,11 +474,16 @@ export class PageLayoutService {
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatPageLayoutToPageLayoutDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutMaps,
|
||||
}),
|
||||
);
|
||||
const restoredLayout = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.restoreLinkedDashboardsByPageLayoutId({
|
||||
pageLayoutId: id,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutToPageLayoutDto(restoredLayout);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
|
||||
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||
import { type CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
@@ -8,8 +8,8 @@ import { type CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-res
|
||||
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import { PageLayoutTabService } from 'src/engine/metadata-modules/page-layout-tab/services/page-layout-tab.service';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import { PageLayoutService } from 'src/engine/metadata-modules/page-layout/services/page-layout.service';
|
||||
import { type DashboardWorkspaceEntity } from 'src/modules/dashboard/standard-objects/dashboard.workspace-entity';
|
||||
|
||||
@@ -32,6 +32,10 @@ export class DashboardCreateOnePreQueryHook
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
if (isDefined(payload.data.pageLayoutId)) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
const pageLayout = await this.pageLayoutService.create(
|
||||
{
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
import { TEST_IFRAME_CONFIG } from 'test/integration/constants/widget-configuration-test-data.constants';
|
||||
import {
|
||||
createTestDashboardWithGraphQL,
|
||||
destroyDashboardWithGraphQL,
|
||||
findDashboardWithGraphQL,
|
||||
} from 'test/integration/metadata/suites/dashboard/utils/dashboard-graphql.util';
|
||||
import { createOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/create-one-page-layout-tab.util';
|
||||
import { deleteOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/delete-one-page-layout-tab.util';
|
||||
import { destroyOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/destroy-one-page-layout-tab.util';
|
||||
import { restoreOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/restore-one-page-layout-tab.util';
|
||||
import { updateOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/update-one-page-layout-tab.util';
|
||||
import { createOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/create-one-page-layout-widget.util';
|
||||
import { deleteOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/delete-one-page-layout-widget.util';
|
||||
import { destroyOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/destroy-one-page-layout-widget.util';
|
||||
import { restoreOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/restore-one-page-layout-widget.util';
|
||||
import { updateOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/update-one-page-layout-widget.util';
|
||||
import { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util';
|
||||
import { deleteOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/delete-one-page-layout.util';
|
||||
import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util';
|
||||
import { restoreOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/restore-one-page-layout.util';
|
||||
import { updateOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/update-one-page-layout.util';
|
||||
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
|
||||
type TestContext = {
|
||||
pageLayoutId: string;
|
||||
tabId: string;
|
||||
widgetId: string;
|
||||
dashboardId: string;
|
||||
};
|
||||
|
||||
const createTestContext = async (): Promise<TestContext> => {
|
||||
const { data: pageLayoutData } = await createOnePageLayout({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
name: 'Page Layout for Dashboard Sync Test',
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
},
|
||||
});
|
||||
|
||||
const pageLayoutId = pageLayoutData.createPageLayout.id;
|
||||
|
||||
const { data: tabData } = await createOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
title: 'Tab for Dashboard Sync Test',
|
||||
pageLayoutId,
|
||||
},
|
||||
});
|
||||
|
||||
const tabId = tabData.createPageLayoutTab.id;
|
||||
|
||||
const { data: widgetData } = await createOnePageLayoutWidget({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
title: 'Widget for Dashboard Sync Test',
|
||||
type: WidgetType.IFRAME,
|
||||
pageLayoutTabId: tabId,
|
||||
gridPosition: {
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
configuration: TEST_IFRAME_CONFIG,
|
||||
},
|
||||
});
|
||||
|
||||
const widgetId = widgetData.createPageLayoutWidget.id;
|
||||
|
||||
const dashboard = await createTestDashboardWithGraphQL({
|
||||
title: 'Dashboard for Sync Test',
|
||||
pageLayoutId,
|
||||
});
|
||||
|
||||
return {
|
||||
pageLayoutId,
|
||||
tabId,
|
||||
widgetId,
|
||||
dashboardId: dashboard.id,
|
||||
};
|
||||
};
|
||||
|
||||
const cleanupTestContext = async (context: TestContext): Promise<void> => {
|
||||
await destroyOnePageLayoutWidget({
|
||||
expectToFail: false,
|
||||
input: { id: context.widgetId },
|
||||
});
|
||||
|
||||
await destroyOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: { id: context.tabId },
|
||||
});
|
||||
|
||||
await destroyDashboardWithGraphQL(context.dashboardId);
|
||||
|
||||
await destroyOnePageLayout({
|
||||
expectToFail: false,
|
||||
input: { id: context.pageLayoutId },
|
||||
});
|
||||
};
|
||||
|
||||
const assertDashboardUpdatedAtIncreased = async (
|
||||
dashboardId: string,
|
||||
operation: () => Promise<void>,
|
||||
): Promise<void> => {
|
||||
const dashboardBefore = await findDashboardWithGraphQL(dashboardId);
|
||||
|
||||
expect(dashboardBefore).not.toBeNull();
|
||||
|
||||
const updatedAtBefore = new Date(dashboardBefore!.updatedAt);
|
||||
|
||||
await operation();
|
||||
|
||||
const dashboardAfter = await findDashboardWithGraphQL(dashboardId);
|
||||
|
||||
expect(dashboardAfter).not.toBeNull();
|
||||
|
||||
const updatedAtAfter = new Date(dashboardAfter!.updatedAt);
|
||||
|
||||
const isIncreased = updatedAtAfter > updatedAtBefore;
|
||||
|
||||
expect(isIncreased).toBe(true);
|
||||
};
|
||||
|
||||
const assertDashboardSoftDeleted = async (
|
||||
dashboardId: string,
|
||||
operation: () => Promise<void>,
|
||||
): Promise<void> => {
|
||||
const dashboardBefore = await findDashboardWithGraphQL(dashboardId);
|
||||
|
||||
expect(dashboardBefore).not.toBeNull();
|
||||
|
||||
await operation();
|
||||
|
||||
const dashboardAfter = await findDashboardWithGraphQL(dashboardId);
|
||||
|
||||
expect(dashboardAfter).toBeNull();
|
||||
};
|
||||
|
||||
const assertDashboardRestored = async (
|
||||
dashboardId: string,
|
||||
operation: () => Promise<void>,
|
||||
): Promise<void> => {
|
||||
const dashboardBefore = await findDashboardWithGraphQL(dashboardId);
|
||||
|
||||
expect(dashboardBefore).toBeNull();
|
||||
|
||||
await operation();
|
||||
|
||||
const dashboardAfter = await findDashboardWithGraphQL(dashboardId);
|
||||
|
||||
expect(dashboardAfter).not.toBeNull();
|
||||
|
||||
const updatedAtAfter = new Date(dashboardAfter!.updatedAt);
|
||||
|
||||
const now = new Date();
|
||||
const timeDiff = now.getTime() - updatedAtAfter.getTime();
|
||||
|
||||
expect(timeDiff).toBeLessThan(5000);
|
||||
};
|
||||
|
||||
describe('Dashboard updatedAt should sync when linked page layout entities change', () => {
|
||||
describe('Widget operations', () => {
|
||||
let context: TestContext;
|
||||
let additionalWidgetId: string | undefined;
|
||||
|
||||
beforeEach(async () => {
|
||||
context = await createTestContext();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (additionalWidgetId) {
|
||||
await destroyOnePageLayoutWidget({
|
||||
expectToFail: false,
|
||||
input: { id: additionalWidgetId },
|
||||
});
|
||||
additionalWidgetId = undefined;
|
||||
}
|
||||
|
||||
await cleanupTestContext(context);
|
||||
});
|
||||
|
||||
it('should update dashboard updatedAt when widget is created', async () => {
|
||||
await assertDashboardUpdatedAtIncreased(context.dashboardId, async () => {
|
||||
const { data: widgetData } = await createOnePageLayoutWidget({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
title: 'New Widget for Dashboard Sync Test',
|
||||
type: WidgetType.IFRAME,
|
||||
pageLayoutTabId: context.tabId,
|
||||
gridPosition: {
|
||||
row: 1,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
configuration: TEST_IFRAME_CONFIG,
|
||||
},
|
||||
});
|
||||
|
||||
additionalWidgetId = widgetData.createPageLayoutWidget.id;
|
||||
});
|
||||
});
|
||||
|
||||
it('should update dashboard updatedAt when widget is updated', async () => {
|
||||
await assertDashboardUpdatedAtIncreased(context.dashboardId, async () => {
|
||||
await updateOnePageLayoutWidget({
|
||||
expectToFail: false,
|
||||
input: { id: context.widgetId, title: 'Updated Widget Title' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should update dashboard updatedAt when widget is soft deleted', async () => {
|
||||
await assertDashboardUpdatedAtIncreased(context.dashboardId, async () => {
|
||||
await deleteOnePageLayoutWidget({
|
||||
expectToFail: false,
|
||||
input: { id: context.widgetId },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should update dashboard updatedAt when widget is restored', async () => {
|
||||
await deleteOnePageLayoutWidget({
|
||||
expectToFail: false,
|
||||
input: { id: context.widgetId },
|
||||
});
|
||||
|
||||
await assertDashboardUpdatedAtIncreased(context.dashboardId, async () => {
|
||||
await restoreOnePageLayoutWidget({
|
||||
expectToFail: false,
|
||||
input: { id: context.widgetId },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tab operations', () => {
|
||||
let context: TestContext;
|
||||
let additionalTabId: string | undefined;
|
||||
|
||||
beforeEach(async () => {
|
||||
context = await createTestContext();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (additionalTabId) {
|
||||
await destroyOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: { id: additionalTabId },
|
||||
});
|
||||
additionalTabId = undefined;
|
||||
}
|
||||
await cleanupTestContext(context);
|
||||
});
|
||||
|
||||
it('should update dashboard updatedAt when tab is created', async () => {
|
||||
await assertDashboardUpdatedAtIncreased(context.dashboardId, async () => {
|
||||
const { data: tabData } = await createOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
title: 'New Tab for Dashboard Sync Test',
|
||||
pageLayoutId: context.pageLayoutId,
|
||||
},
|
||||
});
|
||||
|
||||
additionalTabId = tabData.createPageLayoutTab.id;
|
||||
});
|
||||
});
|
||||
|
||||
it('should update dashboard updatedAt when tab is updated', async () => {
|
||||
await assertDashboardUpdatedAtIncreased(context.dashboardId, async () => {
|
||||
await updateOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: { id: context.tabId, title: 'Updated Tab Title' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should update dashboard updatedAt when tab is soft deleted', async () => {
|
||||
const { data: tabData } = await createOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
title: 'Tab to Delete',
|
||||
pageLayoutId: context.pageLayoutId,
|
||||
},
|
||||
});
|
||||
|
||||
additionalTabId = tabData.createPageLayoutTab.id;
|
||||
|
||||
await assertDashboardUpdatedAtIncreased(context.dashboardId, async () => {
|
||||
await deleteOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: { id: additionalTabId! },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should update dashboard updatedAt when tab is restored', async () => {
|
||||
const { data: tabData } = await createOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
title: 'Tab to Restore',
|
||||
pageLayoutId: context.pageLayoutId,
|
||||
},
|
||||
});
|
||||
|
||||
additionalTabId = tabData.createPageLayoutTab.id;
|
||||
|
||||
await deleteOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: { id: additionalTabId },
|
||||
});
|
||||
|
||||
await assertDashboardUpdatedAtIncreased(context.dashboardId, async () => {
|
||||
await restoreOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: { id: additionalTabId! },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Page layout operations', () => {
|
||||
let context: TestContext;
|
||||
|
||||
beforeEach(async () => {
|
||||
context = await createTestContext();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanupTestContext(context);
|
||||
});
|
||||
|
||||
it('should update dashboard updatedAt when page layout is updated', async () => {
|
||||
await assertDashboardUpdatedAtIncreased(context.dashboardId, async () => {
|
||||
await updateOnePageLayout({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: context.pageLayoutId,
|
||||
name: 'Updated Page Layout Name',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should update dashboard updatedAt when page layout is soft deleted', async () => {
|
||||
await assertDashboardSoftDeleted(context.dashboardId, async () => {
|
||||
await deleteOnePageLayout({
|
||||
expectToFail: false,
|
||||
input: { id: context.pageLayoutId },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should update dashboard updatedAt when page layout is restored', async () => {
|
||||
await deleteOnePageLayout({
|
||||
expectToFail: false,
|
||||
input: { id: context.pageLayoutId },
|
||||
});
|
||||
|
||||
await assertDashboardRestored(context.dashboardId, async () => {
|
||||
await restoreOnePageLayout({
|
||||
expectToFail: false,
|
||||
input: { id: context.pageLayoutId },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Non-dashboard page layout operations should not trigger sync', () => {
|
||||
let nonDashboardPageLayoutId: string;
|
||||
let dashboardContext: TestContext;
|
||||
|
||||
beforeEach(async () => {
|
||||
dashboardContext = await createTestContext();
|
||||
|
||||
const { data: pageLayoutData } = await createOnePageLayout({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
name: 'Non-Dashboard Page Layout',
|
||||
type: PageLayoutType.RECORD_INDEX,
|
||||
},
|
||||
});
|
||||
|
||||
nonDashboardPageLayoutId = pageLayoutData.createPageLayout.id;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await destroyOnePageLayout({
|
||||
expectToFail: false,
|
||||
input: { id: nonDashboardPageLayoutId },
|
||||
});
|
||||
|
||||
await cleanupTestContext(dashboardContext);
|
||||
});
|
||||
|
||||
it('should not update existing dashboard updatedAt when non-dashboard page layout is updated', async () => {
|
||||
const dashboardBefore = await findDashboardWithGraphQL(
|
||||
dashboardContext.dashboardId,
|
||||
);
|
||||
|
||||
expect(dashboardBefore).not.toBeNull();
|
||||
|
||||
const updatedAtBefore = new Date(dashboardBefore!.updatedAt);
|
||||
|
||||
await updateOnePageLayout({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: nonDashboardPageLayoutId,
|
||||
name: 'Updated Non-Dashboard Page Layout',
|
||||
},
|
||||
});
|
||||
|
||||
const dashboardAfter = await findDashboardWithGraphQL(
|
||||
dashboardContext.dashboardId,
|
||||
);
|
||||
|
||||
expect(dashboardAfter).not.toBeNull();
|
||||
|
||||
const updatedAtAfter = new Date(dashboardAfter!.updatedAt);
|
||||
|
||||
expect(updatedAtAfter.getTime()).toBe(updatedAtBefore.getTime());
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user