[DASHBOARDS] Allow dashboards to be restored (#17042)
This PR introduces a few changes: - Add three actions: see deleted dashboards, destroy dashboard and restore dashboard - Remove the soft delete and restore on all the page layout entities - Cascade the destruction of a dashboard to a page layout Video QA: https://github.com/user-attachments/assets/ab993b11-dd9c-4e88-880c-92691a521cc2
This commit is contained in:
+21
-8
@@ -52,10 +52,10 @@ export class PageLayoutTabController {
|
||||
);
|
||||
}
|
||||
|
||||
return this.pageLayoutTabService.findByPageLayoutId(
|
||||
workspace.id,
|
||||
return this.pageLayoutTabService.findByPageLayoutId({
|
||||
workspaceId: workspace.id,
|
||||
pageLayoutId,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@@ -64,7 +64,10 @@ export class PageLayoutTabController {
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO | null> {
|
||||
return this.pageLayoutTabService.findByIdOrThrow(id, workspace.id);
|
||||
return this.pageLayoutTabService.findByIdOrThrow({
|
||||
id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@Post()
|
||||
@@ -73,7 +76,10 @@ export class PageLayoutTabController {
|
||||
@Body() input: CreatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.create(input, workspace.id);
|
||||
return this.pageLayoutTabService.create({
|
||||
createPageLayoutTabInput: input,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@@ -83,7 +89,11 @@ export class PageLayoutTabController {
|
||||
@Body() input: UpdatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.update(id, workspace.id, input);
|
||||
return this.pageLayoutTabService.update({
|
||||
id,
|
||||
workspaceId: workspace.id,
|
||||
updateData: input,
|
||||
});
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@@ -91,7 +101,10 @@ export class PageLayoutTabController {
|
||||
async delete(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.delete(id, workspace.id);
|
||||
): Promise<boolean> {
|
||||
return this.pageLayoutTabService.destroy({
|
||||
id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+19
-30
@@ -7,7 +7,6 @@ import {
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -36,10 +35,10 @@ export class PageLayoutTabResolver {
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args('pageLayoutId', { type: () => String }) pageLayoutId: string,
|
||||
): Promise<PageLayoutTabDTO[]> {
|
||||
return this.pageLayoutTabService.findByPageLayoutId(
|
||||
workspace.id,
|
||||
return this.pageLayoutTabService.findByPageLayoutId({
|
||||
workspaceId: workspace.id,
|
||||
pageLayoutId,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => PageLayoutTabDTO)
|
||||
@@ -48,7 +47,10 @@ export class PageLayoutTabResolver {
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.findByIdOrThrow(id, workspace.id);
|
||||
return this.pageLayoutTabService.findByIdOrThrow({
|
||||
id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutTabDTO)
|
||||
@@ -57,7 +59,10 @@ export class PageLayoutTabResolver {
|
||||
@Args('input') input: CreatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.create(input, workspace.id);
|
||||
return this.pageLayoutTabService.create({
|
||||
createPageLayoutTabInput: input,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutTabDTO)
|
||||
@@ -67,21 +72,11 @@ export class PageLayoutTabResolver {
|
||||
@Args('input') input: UpdatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.update(id, workspace.id, input);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async deletePageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<boolean> {
|
||||
const deletedPageLayoutTab = await this.pageLayoutTabService.delete(
|
||||
return this.pageLayoutTabService.update({
|
||||
id,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
return isDefined(deletedPageLayoutTab);
|
||||
workspaceId: workspace.id,
|
||||
updateData: input,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@@ -90,15 +85,9 @@ export class PageLayoutTabResolver {
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<boolean> {
|
||||
return this.pageLayoutTabService.destroy(id, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutTabDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async restorePageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.restore(id, workspace.id);
|
||||
return this.pageLayoutTabService.destroy({
|
||||
id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+37
-144
@@ -7,9 +7,7 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadat
|
||||
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';
|
||||
import { fromCreatePageLayoutTabInputToFlatPageLayoutTabToCreate } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/from-create-page-layout-tab-input-to-flat-page-layout-tab-to-create.util';
|
||||
import { fromDeletePageLayoutTabInputToFlatPageLayoutTabOrThrow } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/from-delete-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util';
|
||||
import { fromDestroyPageLayoutTabInputToFlatPageLayoutTabOrThrow } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/from-destroy-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util';
|
||||
import { fromRestorePageLayoutTabInputToFlatPageLayoutTabOrThrow } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/from-restore-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util';
|
||||
import {
|
||||
fromUpdatePageLayoutTabInputToFlatPageLayoutTabToUpdateOrThrow,
|
||||
type UpdatePageLayoutTabInputWithId,
|
||||
@@ -40,10 +38,13 @@ export class PageLayoutTabService {
|
||||
private readonly dashboardSyncService: DashboardSyncService,
|
||||
) {}
|
||||
|
||||
async findByPageLayoutId(
|
||||
workspaceId: string,
|
||||
pageLayoutId: string,
|
||||
): Promise<PageLayoutTabDTO[]> {
|
||||
async findByPageLayoutId({
|
||||
workspaceId,
|
||||
pageLayoutId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
pageLayoutId: string;
|
||||
}): Promise<PageLayoutTabDTO[]> {
|
||||
const { flatPageLayoutTabMaps, flatPageLayoutWidgetMaps } =
|
||||
await this.getPageLayoutTabFlatEntityMaps(workspaceId);
|
||||
|
||||
@@ -63,10 +64,13 @@ export class PageLayoutTabService {
|
||||
);
|
||||
}
|
||||
|
||||
async findByIdOrThrow(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
async findByIdOrThrow({
|
||||
id,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}): Promise<PageLayoutTabDTO> {
|
||||
const { flatPageLayoutTabMaps, flatPageLayoutWidgetMaps } =
|
||||
await this.getPageLayoutTabFlatEntityMaps(workspaceId);
|
||||
|
||||
@@ -102,10 +106,13 @@ export class PageLayoutTabService {
|
||||
);
|
||||
}
|
||||
|
||||
async create(
|
||||
createPageLayoutTabInput: CreatePageLayoutTabInput,
|
||||
workspaceId: string,
|
||||
): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
async create({
|
||||
createPageLayoutTabInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
createPageLayoutTabInput: CreatePageLayoutTabInput;
|
||||
workspaceId: string;
|
||||
}): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
if (!isDefined(createPageLayoutTabInput.title)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
@@ -180,11 +187,15 @@ export class PageLayoutTabService {
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(createdTab);
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
updateData: UpdatePageLayoutTabInput,
|
||||
): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
async update({
|
||||
id,
|
||||
workspaceId,
|
||||
updateData,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
updateData: UpdatePageLayoutTabInput;
|
||||
}): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
@@ -248,69 +259,13 @@ export class PageLayoutTabService {
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(updatedTab);
|
||||
}
|
||||
|
||||
async delete(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatPageLayoutTabToDelete =
|
||||
fromDeletePageLayoutTabInputToFlatPageLayoutTabOrThrow({
|
||||
deletePageLayoutTabInput: { id },
|
||||
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [flatPageLayoutTabToDelete],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while deleting page layout tab',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatPageLayoutTabMaps: recomputedFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
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> {
|
||||
async destroy({
|
||||
id,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}): Promise<boolean> {
|
||||
const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
@@ -355,66 +310,4 @@ export class PageLayoutTabService {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async restore(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatPageLayoutTabToRestore =
|
||||
fromRestorePageLayoutTabInputToFlatPageLayoutTabOrThrow({
|
||||
restorePageLayoutTabInput: { id },
|
||||
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [flatPageLayoutTabToRestore],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while restoring page layout tab',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatPageLayoutTabMaps: recomputedFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const restoredTab = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByTabId({
|
||||
tabId: id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(restoredTab.updatedAt),
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(restoredTab);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user