diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 30a58b60258..a54ef7f1965 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1918,9 +1918,6 @@ export type Mutation = { deleteOneRole: Scalars['String']; deleteOneRouteTrigger: RouteTrigger; deleteOneServerlessFunction: ServerlessFunction; - deletePageLayout: PageLayout; - deletePageLayoutTab: Scalars['Boolean']; - deletePageLayoutWidget: PageLayoutWidget; deletePublicDomain: Scalars['Boolean']; deleteRowLevelPermissionPredicate: RowLevelPermissionPredicate; deleteRowLevelPermissionPredicateGroup: RowLevelPermissionPredicateGroup; @@ -1968,9 +1965,6 @@ export type Mutation = { renewToken: AuthTokens; resendEmailVerificationToken: ResendEmailVerificationTokenOutput; resendWorkspaceInvitation: SendInvitationsOutput; - restorePageLayout: PageLayout; - restorePageLayoutTab: PageLayoutTab; - restorePageLayoutWidget: PageLayoutWidget; retryJobs: RetryJobsResponse; revokeApiKey?: Maybe; runEvaluationInput: AgentTurn; @@ -2397,21 +2391,6 @@ export type MutationDeleteOneServerlessFunctionArgs = { }; -export type MutationDeletePageLayoutArgs = { - id: Scalars['String']; -}; - - -export type MutationDeletePageLayoutTabArgs = { - id: Scalars['String']; -}; - - -export type MutationDeletePageLayoutWidgetArgs = { - id: Scalars['String']; -}; - - export type MutationDeletePublicDomainArgs = { domain: Scalars['String']; }; @@ -2631,21 +2610,6 @@ export type MutationResendWorkspaceInvitationArgs = { }; -export type MutationRestorePageLayoutArgs = { - id: Scalars['String']; -}; - - -export type MutationRestorePageLayoutTabArgs = { - id: Scalars['String']; -}; - - -export type MutationRestorePageLayoutWidgetArgs = { - id: Scalars['String']; -}; - - export type MutationRetryJobsArgs = { jobIds: Array; queueName: Scalars['String']; diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index fd232ca54ad..dd9dba31747 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -1897,9 +1897,6 @@ export type Mutation = { deleteOneRole: Scalars['String']; deleteOneRouteTrigger: RouteTrigger; deleteOneServerlessFunction: ServerlessFunction; - deletePageLayout: PageLayout; - deletePageLayoutTab: Scalars['Boolean']; - deletePageLayoutWidget: PageLayoutWidget; deletePublicDomain: Scalars['Boolean']; deleteRowLevelPermissionPredicate: RowLevelPermissionPredicate; deleteRowLevelPermissionPredicateGroup: RowLevelPermissionPredicateGroup; @@ -1945,9 +1942,6 @@ export type Mutation = { renewToken: AuthTokens; resendEmailVerificationToken: ResendEmailVerificationTokenOutput; resendWorkspaceInvitation: SendInvitationsOutput; - restorePageLayout: PageLayout; - restorePageLayoutTab: PageLayoutTab; - restorePageLayoutWidget: PageLayoutWidget; retryJobs: RetryJobsResponse; revokeApiKey?: Maybe; runWorkflowVersion: RunWorkflowVersionOutput; @@ -2352,21 +2346,6 @@ export type MutationDeleteOneServerlessFunctionArgs = { }; -export type MutationDeletePageLayoutArgs = { - id: Scalars['String']; -}; - - -export type MutationDeletePageLayoutTabArgs = { - id: Scalars['String']; -}; - - -export type MutationDeletePageLayoutWidgetArgs = { - id: Scalars['String']; -}; - - export type MutationDeletePublicDomainArgs = { domain: Scalars['String']; }; @@ -2576,21 +2555,6 @@ export type MutationResendWorkspaceInvitationArgs = { }; -export type MutationRestorePageLayoutArgs = { - id: Scalars['String']; -}; - - -export type MutationRestorePageLayoutTabArgs = { - id: Scalars['String']; -}; - - -export type MutationRestorePageLayoutWidgetArgs = { - id: Scalars['String']; -}; - - export type MutationRetryJobsArgs = { jobIds: Array; queueName: Scalars['String']; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DashboardActionsConfig.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DashboardActionsConfig.tsx index 7738da3a637..05280b0808f 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DashboardActionsConfig.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DashboardActionsConfig.tsx @@ -104,6 +104,9 @@ export const DASHBOARD_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({ SingleRecordActionKeys.RESTORE, MultipleRecordsActionKeys.DELETE, MultipleRecordsActionKeys.DESTROY, + MultipleRecordsActionKeys.RESTORE, + NoSelectionRecordActionKeys.SEE_DELETED_RECORDS, + NoSelectionRecordActionKeys.HIDE_DELETED_RECORDS, SingleRecordActionKeys.EXPORT_FROM_RECORD_SHOW, SingleRecordActionKeys.NAVIGATE_TO_PREVIOUS_RECORD, SingleRecordActionKeys.NAVIGATE_TO_NEXT_RECORD, @@ -160,26 +163,38 @@ export const DASHBOARD_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({ position: 14, label: msg`Restore dashboard`, }, - [NoSelectionRecordActionKeys.GO_TO_WORKFLOWS]: { + [MultipleRecordsActionKeys.RESTORE]: { position: 15, + label: msg`Restore dashboards`, + }, + [NoSelectionRecordActionKeys.SEE_DELETED_RECORDS]: { + position: 22, + label: msg`See deleted dashboards`, + }, + [NoSelectionRecordActionKeys.HIDE_DELETED_RECORDS]: { + position: 23, + label: msg`Hide deleted dashboards`, + }, + [NoSelectionRecordActionKeys.GO_TO_WORKFLOWS]: { + position: 24, }, [NoSelectionRecordActionKeys.GO_TO_PEOPLE]: { - position: 16, + position: 25, }, [NoSelectionRecordActionKeys.GO_TO_COMPANIES]: { - position: 17, + position: 26, }, [NoSelectionRecordActionKeys.GO_TO_OPPORTUNITIES]: { - position: 18, + position: 27, }, [NoSelectionRecordActionKeys.GO_TO_SETTINGS]: { - position: 19, + position: 28, }, [NoSelectionRecordActionKeys.GO_TO_TASKS]: { - position: 20, + position: 29, }, [NoSelectionRecordActionKeys.GO_TO_NOTES]: { - position: 21, + position: 30, }, }, }); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-delete-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-delete-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util.ts deleted file mode 100644 index 597c0272f17..00000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-delete-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { t } from '@lingui/core/macro'; -import { - extractAndSanitizeObjectStringFields, - isDefined, -} from 'twenty-shared/utils'; - -import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type'; -import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type'; -import { - PageLayoutTabException, - PageLayoutTabExceptionCode, -} from 'src/engine/metadata-modules/page-layout-tab/exceptions/page-layout-tab.exception'; - -export type DeletePageLayoutTabInput = { - id: string; -}; - -export const fromDeletePageLayoutTabInputToFlatPageLayoutTabOrThrow = ({ - deletePageLayoutTabInput: rawDeletePageLayoutTabInput, - flatPageLayoutTabMaps, -}: { - deletePageLayoutTabInput: DeletePageLayoutTabInput; - flatPageLayoutTabMaps: FlatPageLayoutTabMaps; -}): FlatPageLayoutTab => { - const { id: pageLayoutTabId } = extractAndSanitizeObjectStringFields( - rawDeletePageLayoutTabInput, - ['id'], - ); - - const existingFlatPageLayoutTabToDelete = - flatPageLayoutTabMaps.byId[pageLayoutTabId]; - - if (!isDefined(existingFlatPageLayoutTabToDelete)) { - throw new PageLayoutTabException( - t`Page layout tab to delete not found`, - PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND, - ); - } - - return { - ...existingFlatPageLayoutTabToDelete, - deletedAt: new Date().toISOString(), - }; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-restore-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-restore-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util.ts deleted file mode 100644 index ec985dd0640..00000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-tab/utils/from-restore-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { t } from '@lingui/core/macro'; -import { - extractAndSanitizeObjectStringFields, - isDefined, -} from 'twenty-shared/utils'; - -import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type'; -import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type'; -import { - PageLayoutTabException, - PageLayoutTabExceptionCode, -} from 'src/engine/metadata-modules/page-layout-tab/exceptions/page-layout-tab.exception'; - -export type RestorePageLayoutTabInput = { - id: string; -}; - -export const fromRestorePageLayoutTabInputToFlatPageLayoutTabOrThrow = ({ - restorePageLayoutTabInput, - flatPageLayoutTabMaps, -}: { - restorePageLayoutTabInput: RestorePageLayoutTabInput; - flatPageLayoutTabMaps: FlatPageLayoutTabMaps; -}): FlatPageLayoutTab => { - const { id: pageLayoutTabId } = extractAndSanitizeObjectStringFields( - restorePageLayoutTabInput, - ['id'], - ); - - const existingFlatPageLayoutTabToRestore = - flatPageLayoutTabMaps.byId[pageLayoutTabId]; - - if (!isDefined(existingFlatPageLayoutTabToRestore)) { - throw new PageLayoutTabException( - t`Page layout tab to restore not found`, - PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND, - ); - } - - if (!isDefined(existingFlatPageLayoutTabToRestore.deletedAt)) { - throw new PageLayoutTabException( - t`Page layout tab is not deleted and cannot be restored`, - PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA, - ); - } - - return { - ...existingFlatPageLayoutTabToRestore, - deletedAt: null, - }; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-delete-page-layout-widget-input-to-flat-page-layout-widget-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-delete-page-layout-widget-input-to-flat-page-layout-widget-or-throw.util.ts deleted file mode 100644 index 4ffc9d5fc82..00000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-delete-page-layout-widget-input-to-flat-page-layout-widget-or-throw.util.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { t } from '@lingui/core/macro'; -import { - extractAndSanitizeObjectStringFields, - isDefined, -} from 'twenty-shared/utils'; - -import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type'; -import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; -import { type DeletePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/delete-page-layout-widget.input'; -import { - PageLayoutWidgetException, - PageLayoutWidgetExceptionCode, -} from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception'; - -export const fromDeletePageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow = ({ - deletePageLayoutWidgetInput: rawDeletePageLayoutWidgetInput, - flatPageLayoutWidgetMaps, -}: { - deletePageLayoutWidgetInput: DeletePageLayoutWidgetInput; - flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps; -}): FlatPageLayoutWidget => { - const { id: pageLayoutWidgetId } = extractAndSanitizeObjectStringFields( - rawDeletePageLayoutWidgetInput, - ['id'], - ); - - const existingFlatPageLayoutWidgetToDelete = - flatPageLayoutWidgetMaps.byId[pageLayoutWidgetId]; - - if (!isDefined(existingFlatPageLayoutWidgetToDelete)) { - throw new PageLayoutWidgetException( - t`Page layout widget to delete not found`, - PageLayoutWidgetExceptionCode.PAGE_LAYOUT_WIDGET_NOT_FOUND, - ); - } - - return { - ...existingFlatPageLayoutWidgetToDelete, - deletedAt: new Date().toISOString(), - }; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-restore-page-layout-widget-input-to-flat-page-layout-widget-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-restore-page-layout-widget-input-to-flat-page-layout-widget-or-throw.util.ts deleted file mode 100644 index 8d0cb5cfe50..00000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-restore-page-layout-widget-input-to-flat-page-layout-widget-or-throw.util.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { t } from '@lingui/core/macro'; -import { - extractAndSanitizeObjectStringFields, - isDefined, -} from 'twenty-shared/utils'; - -import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type'; -import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; -import { - PageLayoutWidgetException, - PageLayoutWidgetExceptionCode, -} from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception'; - -export type RestorePageLayoutWidgetInput = { - id: string; -}; - -export const fromRestorePageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow = ({ - restorePageLayoutWidgetInput, - flatPageLayoutWidgetMaps, -}: { - restorePageLayoutWidgetInput: RestorePageLayoutWidgetInput; - flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps; -}): FlatPageLayoutWidget => { - const { id: pageLayoutWidgetId } = extractAndSanitizeObjectStringFields( - restorePageLayoutWidgetInput, - ['id'], - ); - - const existingFlatPageLayoutWidgetToRestore = - flatPageLayoutWidgetMaps.byId[pageLayoutWidgetId]; - - if (!isDefined(existingFlatPageLayoutWidgetToRestore)) { - throw new PageLayoutWidgetException( - t`Page layout widget to restore not found`, - PageLayoutWidgetExceptionCode.PAGE_LAYOUT_WIDGET_NOT_FOUND, - ); - } - - if (!isDefined(existingFlatPageLayoutWidgetToRestore.deletedAt)) { - throw new PageLayoutWidgetException( - t`Page layout widget is not deleted and cannot be restored`, - PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA, - ); - } - - return { - ...existingFlatPageLayoutWidgetToRestore, - deletedAt: null, - }; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-delete-page-layout-input-to-flat-page-layout-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-delete-page-layout-input-to-flat-page-layout-or-throw.util.ts deleted file mode 100644 index db0449a1391..00000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-delete-page-layout-input-to-flat-page-layout-or-throw.util.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { t } from '@lingui/core/macro'; -import { - extractAndSanitizeObjectStringFields, - isDefined, -} from 'twenty-shared/utils'; - -import { type FlatPageLayoutMaps } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout-maps.type'; -import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type'; -import { - PageLayoutException, - PageLayoutExceptionCode, -} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout.exception'; - -export type DeletePageLayoutInput = { - id: string; -}; - -export const fromDeletePageLayoutInputToFlatPageLayoutOrThrow = ({ - deletePageLayoutInput: rawDeletePageLayoutInput, - flatPageLayoutMaps, -}: { - deletePageLayoutInput: DeletePageLayoutInput; - flatPageLayoutMaps: FlatPageLayoutMaps; -}): FlatPageLayout => { - const { id: pageLayoutId } = extractAndSanitizeObjectStringFields( - rawDeletePageLayoutInput, - ['id'], - ); - - const existingFlatPageLayoutToDelete = flatPageLayoutMaps.byId[pageLayoutId]; - - if (!isDefined(existingFlatPageLayoutToDelete)) { - throw new PageLayoutException( - t`Page layout to delete not found`, - PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND, - ); - } - - return { - ...existingFlatPageLayoutToDelete, - deletedAt: new Date().toISOString(), - }; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-restore-page-layout-input-to-flat-page-layout-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-restore-page-layout-input-to-flat-page-layout-or-throw.util.ts deleted file mode 100644 index 6939c4aa49a..00000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout/utils/from-restore-page-layout-input-to-flat-page-layout-or-throw.util.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { t } from '@lingui/core/macro'; -import { - extractAndSanitizeObjectStringFields, - isDefined, -} from 'twenty-shared/utils'; - -import { type FlatPageLayoutMaps } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout-maps.type'; -import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type'; -import { - PageLayoutException, - PageLayoutExceptionCode, -} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout.exception'; - -export type RestorePageLayoutInput = { - id: string; -}; - -export const fromRestorePageLayoutInputToFlatPageLayoutOrThrow = ({ - restorePageLayoutInput, - flatPageLayoutMaps, -}: { - restorePageLayoutInput: RestorePageLayoutInput; - flatPageLayoutMaps: FlatPageLayoutMaps; -}): FlatPageLayout => { - const { id: pageLayoutId } = extractAndSanitizeObjectStringFields( - restorePageLayoutInput, - ['id'], - ); - - const existingFlatPageLayoutToRestore = flatPageLayoutMaps.byId[pageLayoutId]; - - if (!isDefined(existingFlatPageLayoutToRestore)) { - throw new PageLayoutException( - t`Page layout to restore not found`, - PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND, - ); - } - - if (!isDefined(existingFlatPageLayoutToRestore.deletedAt)) { - throw new PageLayoutException( - t`Page layout is not deleted and cannot be restored`, - PageLayoutExceptionCode.INVALID_PAGE_LAYOUT_DATA, - ); - } - - return { - ...existingFlatPageLayoutToRestore, - deletedAt: null, - }; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/controllers/page-layout-tab.controller.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/controllers/page-layout-tab.controller.ts index 40ce11346e5..fed651fd405 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/controllers/page-layout-tab.controller.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/controllers/page-layout-tab.controller.ts @@ -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 { - 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 { - 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 { - 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 { - return this.pageLayoutTabService.delete(id, workspace.id); + ): Promise { + return this.pageLayoutTabService.destroy({ + id, + workspaceId: workspace.id, + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/resolvers/page-layout-tab.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/resolvers/page-layout-tab.resolver.ts index 7c29694d4af..161081008ff 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/resolvers/page-layout-tab.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/resolvers/page-layout-tab.resolver.ts @@ -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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - return this.pageLayoutTabService.restore(id, workspace.id); + return this.pageLayoutTabService.destroy({ + id, + workspaceId: workspace.id, + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/services/page-layout-tab.service.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/services/page-layout-tab.service.ts index b0a80a5613d..63123aa9384 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/services/page-layout-tab.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-tab/services/page-layout-tab.service.ts @@ -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 { + async findByPageLayoutId({ + workspaceId, + pageLayoutId, + }: { + workspaceId: string; + pageLayoutId: string; + }): Promise { const { flatPageLayoutTabMaps, flatPageLayoutWidgetMaps } = await this.getPageLayoutTabFlatEntityMaps(workspaceId); @@ -63,10 +64,13 @@ export class PageLayoutTabService { ); } - async findByIdOrThrow( - id: string, - workspaceId: string, - ): Promise { + async findByIdOrThrow({ + id, + workspaceId, + }: { + id: string; + workspaceId: string; + }): Promise { const { flatPageLayoutTabMaps, flatPageLayoutWidgetMaps } = await this.getPageLayoutTabFlatEntityMaps(workspaceId); @@ -102,10 +106,13 @@ export class PageLayoutTabService { ); } - async create( - createPageLayoutTabInput: CreatePageLayoutTabInput, - workspaceId: string, - ): Promise> { + async create({ + createPageLayoutTabInput, + workspaceId, + }: { + createPageLayoutTabInput: CreatePageLayoutTabInput; + workspaceId: string; + }): Promise> { 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> { + async update({ + id, + workspaceId, + updateData, + }: { + id: string; + workspaceId: string; + updateData: UpdatePageLayoutTabInput; + }): Promise> { const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } = await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { @@ -248,69 +259,13 @@ export class PageLayoutTabService { return fromFlatPageLayoutTabToPageLayoutTabDto(updatedTab); } - async delete( - id: string, - workspaceId: string, - ): Promise> { - 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 { + async destroy({ + id, + workspaceId, + }: { + id: string; + workspaceId: string; + }): Promise { const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } = await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { @@ -355,66 +310,4 @@ export class PageLayoutTabService { return true; } - - async restore( - id: string, - workspaceId: string, - ): Promise> { - 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); - } } diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/controllers/page-layout-widget.controller.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/controllers/page-layout-widget.controller.ts index f0bc3faa07a..288634ba758 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/controllers/page-layout-widget.controller.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/controllers/page-layout-widget.controller.ts @@ -54,10 +54,10 @@ export class PageLayoutWidgetController { ); } - return this.pageLayoutWidgetService.findByPageLayoutTabId( - workspace.id, + return this.pageLayoutWidgetService.findByPageLayoutTabId({ + workspaceId: workspace.id, pageLayoutTabId, - ); + }); } @Get(':id') @@ -66,7 +66,10 @@ export class PageLayoutWidgetController { @Param('id') id: string, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - return this.pageLayoutWidgetService.findByIdOrThrow(id, workspace.id); + return this.pageLayoutWidgetService.findByIdOrThrow({ + id, + workspaceId: workspace.id, + }); } @Post() @@ -75,7 +78,10 @@ export class PageLayoutWidgetController { @Body() input: CreatePageLayoutWidgetInput, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - return this.pageLayoutWidgetService.create(input, workspace.id); + return this.pageLayoutWidgetService.create({ + input, + workspaceId: workspace.id, + }); } @Patch(':id') @@ -85,7 +91,11 @@ export class PageLayoutWidgetController { @Body() input: UpdatePageLayoutWidgetInput, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - return this.pageLayoutWidgetService.update(id, workspace.id, input); + return this.pageLayoutWidgetService.update({ + id, + workspaceId: workspace.id, + updateData: input, + }); } @Delete(':id') @@ -93,7 +103,10 @@ export class PageLayoutWidgetController { async delete( @Param('id') id: string, @AuthWorkspace() workspace: WorkspaceEntity, - ): Promise { - return this.pageLayoutWidgetService.delete(id, workspace.id); + ): Promise { + return this.pageLayoutWidgetService.destroy({ + id, + workspaceId: workspace.id, + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/resolvers/page-layout-widget.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/resolvers/page-layout-widget.resolver.ts index 54d50a3df96..acbed6f8567 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/resolvers/page-layout-widget.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/resolvers/page-layout-widget.resolver.ts @@ -45,10 +45,10 @@ export class PageLayoutWidgetResolver { @AuthWorkspace() workspace: WorkspaceEntity, @Args('pageLayoutTabId', { type: () => String }) pageLayoutTabId: string, ): Promise { - return this.pageLayoutWidgetService.findByPageLayoutTabId( - workspace.id, + return this.pageLayoutWidgetService.findByPageLayoutTabId({ + workspaceId: workspace.id, pageLayoutTabId, - ); + }); } @Query(() => PageLayoutWidgetDTO) @@ -57,7 +57,10 @@ export class PageLayoutWidgetResolver { @Args('id', { type: () => String }) id: string, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - return this.pageLayoutWidgetService.findByIdOrThrow(id, workspace.id); + return this.pageLayoutWidgetService.findByIdOrThrow({ + id, + workspaceId: workspace.id, + }); } @Mutation(() => PageLayoutWidgetDTO) @@ -66,7 +69,10 @@ export class PageLayoutWidgetResolver { @Args('input') input: CreatePageLayoutWidgetInput, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - return this.pageLayoutWidgetService.create(input, workspace.id); + return this.pageLayoutWidgetService.create({ + input, + workspaceId: workspace.id, + }); } @Mutation(() => PageLayoutWidgetDTO) @@ -76,16 +82,11 @@ export class PageLayoutWidgetResolver { @Args('input') input: UpdatePageLayoutWidgetInput, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - return this.pageLayoutWidgetService.update(id, workspace.id, input); - } - - @Mutation(() => PageLayoutWidgetDTO) - @UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS)) - async deletePageLayoutWidget( - @Args('id', { type: () => String }) id: string, - @AuthWorkspace() workspace: WorkspaceEntity, - ): Promise { - return this.pageLayoutWidgetService.delete(id, workspace.id); + return this.pageLayoutWidgetService.update({ + id, + workspaceId: workspace.id, + updateData: input, + }); } @Mutation(() => Boolean) @@ -94,16 +95,10 @@ export class PageLayoutWidgetResolver { @Args('id', { type: () => String }) id: string, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - return this.pageLayoutWidgetService.destroy(id, workspace.id); - } - - @Mutation(() => PageLayoutWidgetDTO) - @UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS)) - async restorePageLayoutWidget( - @Args('id', { type: () => String }) id: string, - @AuthWorkspace() workspace: WorkspaceEntity, - ): Promise { - return this.pageLayoutWidgetService.restore(id, workspace.id); + return this.pageLayoutWidgetService.destroy({ + id, + workspaceId: workspace.id, + }); } @ResolveField(() => WidgetConfiguration, { nullable: true }) diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/services/page-layout-widget.service.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/services/page-layout-widget.service.ts index 5569136b48c..5caddbc5a99 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/services/page-layout-widget.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout-widget/services/page-layout-widget.service.ts @@ -8,9 +8,7 @@ import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-m import { FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type'; import { FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; import { fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-create-page-layout-widget-input-to-flat-page-layout-widget-to-create.util'; -import { fromDeletePageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-delete-page-layout-widget-input-to-flat-page-layout-widget-or-throw.util'; import { fromDestroyPageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-destroy-page-layout-widget-input-to-flat-page-layout-widget-or-throw.util'; -import { fromRestorePageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-restore-page-layout-widget-input-to-flat-page-layout-widget-or-throw.util'; import { fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThrow, type UpdatePageLayoutWidgetInputWithId, @@ -87,10 +85,13 @@ export class PageLayoutWidgetService { } } - async findByPageLayoutTabId( - workspaceId: string, - pageLayoutTabId: string, - ): Promise { + async findByPageLayoutTabId({ + workspaceId, + pageLayoutTabId, + }: { + workspaceId: string; + pageLayoutTabId: string; + }): Promise { const flatPageLayoutWidgetMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId); @@ -109,10 +110,13 @@ export class PageLayoutWidgetService { .map(fromFlatPageLayoutWidgetToPageLayoutWidgetDto); } - async findByIdOrThrow( - id: string, - workspaceId: string, - ): Promise { + async findByIdOrThrow({ + id, + workspaceId, + }: { + id: string; + workspaceId: string; + }): Promise { const flatPageLayoutWidgetMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId); @@ -131,10 +135,13 @@ export class PageLayoutWidgetService { return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(flatWidget); } - async create( - createPageLayoutWidgetInput: CreatePageLayoutWidgetInput, - workspaceId: string, - ): Promise { + async create({ + input, + workspaceId, + }: { + input: CreatePageLayoutWidgetInput; + workspaceId: string; + }): Promise { const { workspaceCustomFlatApplication } = await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( { workspaceId }, @@ -142,7 +149,7 @@ export class PageLayoutWidgetService { const flatPageLayoutWidgetToCreate = fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate({ - createPageLayoutWidgetInput, + createPageLayoutWidgetInput: input, workspaceId, workspaceCustomApplicationId: workspaceCustomFlatApplication.id, }); @@ -174,11 +181,15 @@ export class PageLayoutWidgetService { return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(createdWidget); } - async update( - id: string, - workspaceId: string, - updateData: UpdatePageLayoutWidgetInput, - ): Promise { + async update({ + id, + workspaceId, + updateData, + }: { + id: string; + workspaceId: string; + updateData: UpdatePageLayoutWidgetInput; + }): Promise { const existingFlatPageLayoutWidgetMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId); @@ -252,44 +263,13 @@ export class PageLayoutWidgetService { return existingWidget; } - async delete(id: string, workspaceId: string): Promise { - const existingFlatPageLayoutWidgetMaps = - await this.getFlatPageLayoutWidgetMaps(workspaceId); - - const flatPageLayoutWidgetToDelete = - fromDeletePageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow({ - deletePageLayoutWidgetInput: { id }, - flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps, - }); - - await this.validateAndRunWidgetMigration({ - workspaceId, - operations: { - flatEntityToCreate: [], - flatEntityToUpdate: [flatPageLayoutWidgetToDelete], - flatEntityToDelete: [], - }, - errorMessage: - 'Multiple validation errors occurred while deleting page layout widget', - }); - - const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId); - - 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 { + async destroy({ + id, + workspaceId, + }: { + id: string; + workspaceId: string; + }): Promise { const existingFlatPageLayoutWidgetMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId); @@ -318,41 +298,4 @@ export class PageLayoutWidgetService { return true; } - - async restore(id: string, workspaceId: string): Promise { - const existingFlatPageLayoutWidgetMaps = - await this.getFlatPageLayoutWidgetMaps(workspaceId); - - const flatPageLayoutWidgetToRestore = - fromRestorePageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow({ - restorePageLayoutWidgetInput: { id }, - flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps, - }); - - await this.validateAndRunWidgetMigration({ - workspaceId, - operations: { - flatEntityToCreate: [], - flatEntityToUpdate: [flatPageLayoutWidgetToRestore], - flatEntityToDelete: [], - }, - errorMessage: - 'Multiple validation errors occurred while restoring page layout widget', - }); - - const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId); - - const restoredWidget = findFlatEntityByIdInFlatEntityMapsOrThrow({ - flatEntityId: id, - flatEntityMaps: recomputedMaps, - }); - - await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByWidgetId({ - widgetId: id, - workspaceId, - updatedAt: new Date(restoredWidget.updatedAt), - }); - - return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(restoredWidget); - } } diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/controllers/page-layout.controller.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/controllers/page-layout.controller.ts index c43697f37de..f2e08e461d4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout/controllers/page-layout.controller.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/controllers/page-layout.controller.ts @@ -38,10 +38,10 @@ export class PageLayoutController { @Query('objectMetadataId') objectMetadataId?: string, ): Promise { if (isDefined(objectMetadataId)) { - return this.pageLayoutService.findByObjectMetadataId( - workspace.id, + return this.pageLayoutService.findByObjectMetadataId({ + workspaceId: workspace.id, objectMetadataId, - ); + }); } return this.pageLayoutService.findByWorkspaceId(workspace.id); @@ -53,7 +53,10 @@ export class PageLayoutController { @Param('id') id: string, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - return this.pageLayoutService.findByIdOrThrow(id, workspace.id); + return this.pageLayoutService.findByIdOrThrow({ + id, + workspaceId: workspace.id, + }); } @Post() @@ -62,7 +65,10 @@ export class PageLayoutController { @Body() input: CreatePageLayoutInput, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - return this.pageLayoutService.create(input, workspace.id); + return this.pageLayoutService.create({ + createPageLayoutInput: input, + workspaceId: workspace.id, + }); } @Patch(':id') @@ -72,26 +78,24 @@ export class PageLayoutController { @Body() input: UpdatePageLayoutInput, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - const updatedPageLayout = await this.pageLayoutService.update( + const updatedPageLayout = await this.pageLayoutService.update({ id, - workspace.id, - input, - ); + workspaceId: workspace.id, + updateData: input, + }); return updatedPageLayout; } @Delete(':id') @UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS)) - async delete( + async destroy( @Param('id') id: string, @AuthWorkspace() workspace: WorkspaceEntity, - ): Promise { - const deletedPageLayout = await this.pageLayoutService.delete( + ): Promise { + return this.pageLayoutService.destroy({ id, - workspace.id, - ); - - return deletedPageLayout; + workspaceId: workspace.id, + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/page-layout.module.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/page-layout.module.ts index 01ea2ba1cd1..333adaa38d8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout/page-layout.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/page-layout.module.ts @@ -9,8 +9,6 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata 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'; @@ -38,8 +36,6 @@ import { DashboardSyncModule } from 'src/modules/dashboard-sync/dashboard-sync.m FlatPageLayoutTabModule, FlatPageLayoutWidgetModule, ApplicationModule, - PageLayoutTabModule, - PageLayoutWidgetModule, DashboardSyncModule, ], controllers: [PageLayoutController], diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/resolvers/page-layout.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/resolvers/page-layout.resolver.ts index a9ea8c073e2..b6e3bcaa033 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout/resolvers/page-layout.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/resolvers/page-layout.resolver.ts @@ -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 { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -43,10 +42,10 @@ export class PageLayoutResolver { objectMetadataId?: string, ): Promise { if (objectMetadataId) { - return this.pageLayoutService.findByObjectMetadataId( - workspace.id, + return this.pageLayoutService.findByObjectMetadataId({ + workspaceId: workspace.id, objectMetadataId, - ); + }); } return this.pageLayoutService.findByWorkspaceId(workspace.id); @@ -58,7 +57,10 @@ export class PageLayoutResolver { @Args('id', { type: () => String }) id: string, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - return this.pageLayoutService.findByIdOrThrow(id, workspace.id); + return this.pageLayoutService.findByIdOrThrow({ + id, + workspaceId: workspace.id, + }); } @Mutation(() => PageLayoutDTO) @@ -67,7 +69,10 @@ export class PageLayoutResolver { @Args('input') input: CreatePageLayoutInput, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - return this.pageLayoutService.create(input, workspace.id); + return this.pageLayoutService.create({ + createPageLayoutInput: input, + workspaceId: workspace.id, + }); } @Mutation(() => PageLayoutDTO) @@ -77,21 +82,11 @@ export class PageLayoutResolver { @Args('input') input: UpdatePageLayoutInput, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - return this.pageLayoutService.update(id, workspace.id, input); - } - - @Mutation(() => PageLayoutDTO) - @UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS)) - async deletePageLayout( - @Args('id', { type: () => String }) id: string, - @AuthWorkspace() workspace: WorkspaceEntity, - ): Promise { - const deletedPageLayout = await this.pageLayoutService.delete( + return this.pageLayoutService.update({ id, - workspace.id, - ); - - return deletedPageLayout; + workspaceId: workspace.id, + updateData: input, + }); } @Mutation(() => Boolean) @@ -100,21 +95,10 @@ export class PageLayoutResolver { @Args('id', { type: () => String }) id: string, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - const deletedPageLayout = await this.pageLayoutService.destroy( + return this.pageLayoutService.destroy({ id, - workspace.id, - ); - - return isDefined(deletedPageLayout); - } - - @Mutation(() => PageLayoutDTO) - @UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS)) - async restorePageLayout( - @Args('id', { type: () => String }) id: string, - @AuthWorkspace() workspace: WorkspaceEntity, - ): Promise { - return this.pageLayoutService.restore(id, workspace.id); + workspaceId: workspace.id, + }); } @Mutation(() => PageLayoutDTO) diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout.service.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout.service.ts index b9265023866..413b1a06ae7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { isNonEmptyString } from '@sniptt/guards'; import { isDefined } from 'twenty-shared/utils'; @@ -10,9 +10,7 @@ import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-pag import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type'; import { type FlatPageLayoutMaps } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout-maps.type'; import { fromCreatePageLayoutInputToFlatPageLayoutToCreate } from 'src/engine/metadata-modules/flat-page-layout/utils/from-create-page-layout-input-to-flat-page-layout-to-create.util'; -import { fromDeletePageLayoutInputToFlatPageLayoutOrThrow } from 'src/engine/metadata-modules/flat-page-layout/utils/from-delete-page-layout-input-to-flat-page-layout-or-throw.util'; import { fromDestroyPageLayoutInputToFlatPageLayoutOrThrow } from 'src/engine/metadata-modules/flat-page-layout/utils/from-destroy-page-layout-input-to-flat-page-layout-or-throw.util'; -import { fromRestorePageLayoutInputToFlatPageLayoutOrThrow } from 'src/engine/metadata-modules/flat-page-layout/utils/from-restore-page-layout-input-to-flat-page-layout-or-throw.util'; import { fromUpdatePageLayoutInputToFlatPageLayoutToUpdateOrThrow, type UpdatePageLayoutInputWithId, @@ -38,8 +36,6 @@ import { DashboardSyncService } from 'src/modules/dashboard-sync/services/dashbo @Injectable() export class PageLayoutService { - private readonly logger = new Logger(PageLayoutService.name); - constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, @@ -70,10 +66,13 @@ export class PageLayoutService { ); } - async findByObjectMetadataId( - workspaceId: string, - objectMetadataId: string, - ): Promise { + async findByObjectMetadataId({ + workspaceId, + objectMetadataId, + }: { + workspaceId: string; + objectMetadataId: string; + }): Promise { const { flatPageLayoutMaps, flatPageLayoutTabMaps, @@ -99,10 +98,13 @@ export class PageLayoutService { ); } - async findByIdOrThrow( - id: string, - workspaceId: string, - ): Promise { + async findByIdOrThrow({ + id, + workspaceId, + }: { + id: string; + workspaceId: string; + }): Promise { const { flatPageLayoutMaps, flatPageLayoutTabMaps, @@ -111,7 +113,10 @@ export class PageLayoutService { const flatLayout = flatPageLayoutMaps.byId[id]; - if (!isDefined(flatLayout) || isDefined(flatLayout.deletedAt)) { + const isLayoutNotFound = + !isDefined(flatLayout) || isDefined(flatLayout.deletedAt); + + if (isLayoutNotFound) { throw new PageLayoutException( generatePageLayoutExceptionMessage( PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND, @@ -147,10 +152,13 @@ export class PageLayoutService { ); } - async create( - createPageLayoutInput: CreatePageLayoutInput, - workspaceId: string, - ): Promise> { + async create({ + createPageLayoutInput, + workspaceId, + }: { + createPageLayoutInput: CreatePageLayoutInput; + workspaceId: string; + }): Promise> { if (!isNonEmptyString(createPageLayoutInput.name)) { throw new PageLayoutException( generatePageLayoutExceptionMessage( @@ -210,11 +218,15 @@ export class PageLayoutService { ); } - async update( - id: string, - workspaceId: string, - updateData: UpdatePageLayoutInput, - ): Promise> { + async update({ + id, + workspaceId, + updateData, + }: { + id: string; + workspaceId: string; + updateData: UpdatePageLayoutInput; + }): Promise> { const { flatPageLayoutMaps: existingFlatPageLayoutMaps } = await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { @@ -280,74 +292,15 @@ export class PageLayoutService { return fromFlatPageLayoutToPageLayoutDto(updatedLayout); } - async delete( - id: string, - workspaceId: string, - ): Promise> { - const { flatPageLayoutMaps: existingFlatPageLayoutMaps } = - await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatPageLayoutMaps'], - }, - ); - - const flatPageLayoutToDelete = - fromDeletePageLayoutInputToFlatPageLayoutOrThrow({ - deletePageLayoutInput: { id }, - flatPageLayoutMaps: existingFlatPageLayoutMaps, - }); - - const validateAndBuildResult = - await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( - { - allFlatEntityOperationByMetadataName: { - pageLayout: { - flatEntityToCreate: [], - flatEntityToDelete: [], - flatEntityToUpdate: [flatPageLayoutToDelete], - }, - }, - workspaceId, - isSystemBuild: false, - }, - ); - - if (isDefined(validateAndBuildResult)) { - throw new WorkspaceMigrationBuilderException( - validateAndBuildResult, - 'Multiple validation errors occurred while deleting page layout', - ); - } - - const { flatPageLayoutMaps: recomputedFlatPageLayoutMaps } = - await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatPageLayoutMaps'], - }, - ); - - 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( - id: string, - workspaceId: string, - ): Promise> { + async destroy({ + id, + workspaceId, + isLinkedDashboardAlreadyDestroyed = false, + }: { + id: string; + workspaceId: string; + isLinkedDashboardAlreadyDestroyed?: boolean; + }): Promise { const { flatPageLayoutMaps: existingFlatPageLayoutMaps } = await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { @@ -384,106 +337,48 @@ export class PageLayoutService { ); } - if (flatPageLayoutToDestroy.type === PageLayoutType.DASHBOARD) { - await this.destroyAssociatedDashboards(id, workspaceId); + if ( + flatPageLayoutToDestroy.type === PageLayoutType.DASHBOARD && + !isLinkedDashboardAlreadyDestroyed + ) { + await this.destroyAssociatedDashboards({ + pageLayoutId: id, + workspaceId, + }); } - return fromFlatPageLayoutToPageLayoutDto(flatPageLayoutToDestroy); + return true; } - private async destroyAssociatedDashboards( - pageLayoutId: string, - workspaceId: string, - ): Promise { + private async destroyAssociatedDashboards({ + pageLayoutId, + workspaceId, + }: { + pageLayoutId: string; + workspaceId: string; + }): Promise { const authContext = buildSystemAuthContext(workspaceId); - try { - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const dashboardRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'dashboard', - { shouldBypassPermissionChecks: true }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext( + authContext, + async () => { + const dashboardRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'dashboard', + { shouldBypassPermissionChecks: true }, + ); - const dashboards = await dashboardRepository.find({ - where: { - pageLayoutId, - }, - }); - - for (const dashboard of dashboards) { - await dashboardRepository.delete(dashboard.id); - } - }, - ); - } catch (error) { - this.logger.error( - `Failed to destroy associated dashboards for page layout ${pageLayoutId}: ${error}`, - ); - } - } - - async restore( - id: string, - workspaceId: string, - ): Promise> { - const { flatPageLayoutMaps: existingFlatPageLayoutMaps } = - await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatPageLayoutMaps'], - }, - ); - - const flatPageLayoutToRestore = - fromRestorePageLayoutInputToFlatPageLayoutOrThrow({ - restorePageLayoutInput: { id }, - flatPageLayoutMaps: existingFlatPageLayoutMaps, - }); - - const validateAndBuildResult = - await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( - { - allFlatEntityOperationByMetadataName: { - pageLayout: { - flatEntityToCreate: [], - flatEntityToDelete: [], - flatEntityToUpdate: [flatPageLayoutToRestore], - }, + const dashboards = await dashboardRepository.find({ + where: { + pageLayoutId, }, - workspaceId, - isSystemBuild: false, - }, - ); + }); - if (isDefined(validateAndBuildResult)) { - throw new WorkspaceMigrationBuilderException( - validateAndBuildResult, - 'Multiple validation errors occurred while restoring page layout', - ); - } - - const { flatPageLayoutMaps: recomputedFlatPageLayoutMaps } = - await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatPageLayoutMaps'], - }, - ); - - const restoredLayout = findFlatEntityByIdInFlatEntityMapsOrThrow({ - flatEntityId: id, - flatEntityMaps: recomputedFlatPageLayoutMaps, - }); - - await this.dashboardSyncService.restoreLinkedDashboardsByPageLayoutId({ - pageLayoutId: id, - workspaceId, - }); - - return fromFlatPageLayoutToPageLayoutDto(restoredLayout); + for (const dashboard of dashboards) { + await dashboardRepository.delete(dashboard.id); + } + }, + ); } } diff --git a/packages/twenty-server/src/modules/dashboard-sync/services/dashboard-sync.service.ts b/packages/twenty-server/src/modules/dashboard-sync/services/dashboard-sync.service.ts index 1fb986b3942..aae3cb9d091 100644 --- a/packages/twenty-server/src/modules/dashboard-sync/services/dashboard-sync.service.ts +++ b/packages/twenty-server/src/modules/dashboard-sync/services/dashboard-sync.service.ts @@ -79,89 +79,6 @@ export class DashboardSyncService { } } - async softDeleteLinkedDashboardsByPageLayoutId({ - pageLayoutId, - workspaceId, - deletedAt, - }: { - pageLayoutId: string; - workspaceId: string; - deletedAt: Date; - }): Promise { - 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 { - 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, diff --git a/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-create-many.pre-query.hook.ts b/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-create-many.pre-query.hook.ts new file mode 100644 index 00000000000..0d2dedd13d8 --- /dev/null +++ b/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-create-many.pre-query.hook.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@nestjs/common'; + +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 CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +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 { DashboardToPageLayoutSyncService } from 'src/modules/dashboard/services/dashboard-to-page-layout-sync.service'; +import { type DashboardWorkspaceEntity } from 'src/modules/dashboard/standard-objects/dashboard.workspace-entity'; + +@Injectable() +@WorkspaceQueryHook(`dashboard.createMany`) +export class DashboardCreateManyPreQueryHook + implements WorkspacePreQueryHookInstance +{ + constructor( + private readonly dashboardToPageLayoutSyncService: DashboardToPageLayoutSyncService, + ) {} + + async execute( + authContext: AuthContext, + _objectName: string, + payload: CreateManyResolverArgs, + ): Promise> { + const workspace = authContext.workspace; + + assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError); + + for (const data of payload.data) { + if (isDefined(data.pageLayoutId)) { + continue; + } + + const pageLayoutId = + await this.dashboardToPageLayoutSyncService.createPageLayoutForDashboard( + { + workspaceId: workspace.id, + }, + ); + + data.pageLayoutId = pageLayoutId; + } + + return payload; + } +} diff --git a/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-create-one.pre-query.hook.ts b/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-create-one.pre-query.hook.ts index 4daa08ac036..368542c8edd 100644 --- a/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-create-one.pre-query.hook.ts +++ b/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-create-one.pre-query.hook.ts @@ -8,9 +8,7 @@ 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 { 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 { DashboardToPageLayoutSyncService } from 'src/modules/dashboard/services/dashboard-to-page-layout-sync.service'; import { type DashboardWorkspaceEntity } from 'src/modules/dashboard/standard-objects/dashboard.workspace-entity'; @Injectable() @@ -19,8 +17,7 @@ export class DashboardCreateOnePreQueryHook implements WorkspacePreQueryHookInstance { constructor( - private readonly pageLayoutService: PageLayoutService, - private readonly pageLayoutTabService: PageLayoutTabService, + private readonly dashboardToPageLayoutSyncService: DashboardToPageLayoutSyncService, ) {} async execute( @@ -36,24 +33,12 @@ export class DashboardCreateOnePreQueryHook return payload; } - const pageLayout = await this.pageLayoutService.create( - { - type: PageLayoutType.DASHBOARD, - objectMetadataId: null, - name: 'Dashboard Layout', - }, - workspace.id, - ); + const pageLayoutId = + await this.dashboardToPageLayoutSyncService.createPageLayoutForDashboard({ + workspaceId: workspace.id, + }); - await this.pageLayoutTabService.create( - { - title: 'Tab 1', - pageLayoutId: pageLayout.id, - }, - workspace.id, - ); - - payload.data.pageLayoutId = pageLayout.id; + payload.data.pageLayoutId = pageLayoutId; return payload; } diff --git a/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-destroy-many.pre-query.hook.ts b/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-destroy-many.pre-query.hook.ts new file mode 100644 index 00000000000..d020c4a5a35 --- /dev/null +++ b/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-destroy-many.pre-query.hook.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@nestjs/common'; + +import { assertIsDefinedOrThrow } 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 DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception'; +import { DashboardToPageLayoutSyncService } from 'src/modules/dashboard/services/dashboard-to-page-layout-sync.service'; + +@Injectable() +@WorkspaceQueryHook(`dashboard.destroyMany`) +export class DashboardDestroyManyPreQueryHook + implements WorkspacePreQueryHookInstance +{ + constructor( + private readonly dashboardToPageLayoutSyncService: DashboardToPageLayoutSyncService, + ) {} + + async execute( + authContext: AuthContext, + _objectName: string, + payload: DestroyManyResolverArgs<{ id: { in: string[] } }>, + ): Promise> { + const workspace = authContext.workspace; + + assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError); + + await this.dashboardToPageLayoutSyncService.destroyPageLayoutsForDashboards( + { + dashboardIds: payload.filter.id.in, + workspaceId: workspace.id, + }, + ); + + return payload; + } +} diff --git a/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-destroy-one.pre-query.hook.ts b/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-destroy-one.pre-query.hook.ts new file mode 100644 index 00000000000..efefd003ad3 --- /dev/null +++ b/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-destroy-one.pre-query.hook.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@nestjs/common'; + +import { assertIsDefinedOrThrow } 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 DestroyOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception'; +import { DashboardToPageLayoutSyncService } from 'src/modules/dashboard/services/dashboard-to-page-layout-sync.service'; + +@Injectable() +@WorkspaceQueryHook(`dashboard.destroyOne`) +export class DashboardDestroyOnePreQueryHook + implements WorkspacePreQueryHookInstance +{ + constructor( + private readonly dashboardToPageLayoutSyncService: DashboardToPageLayoutSyncService, + ) {} + + async execute( + authContext: AuthContext, + _objectName: string, + payload: DestroyOneResolverArgs, + ): Promise { + const workspace = authContext.workspace; + + assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError); + + await this.dashboardToPageLayoutSyncService.destroyPageLayoutsForDashboards( + { + dashboardIds: [payload.id], + workspaceId: workspace.id, + }, + ); + + return payload; + } +} diff --git a/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-query-hook.module.ts b/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-query-hook.module.ts index f19970cdf50..a3a1cf61c99 100644 --- a/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-query-hook.module.ts +++ b/packages/twenty-server/src/modules/dashboard/query-hooks/dashboard-query-hook.module.ts @@ -2,10 +2,21 @@ import { Module } from '@nestjs/common'; import { PageLayoutTabModule } from 'src/engine/metadata-modules/page-layout-tab/page-layout-tab.module'; import { PageLayoutModule } from 'src/engine/metadata-modules/page-layout/page-layout.module'; +import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module'; +import { DashboardCreateManyPreQueryHook } from 'src/modules/dashboard/query-hooks/dashboard-create-many.pre-query.hook'; import { DashboardCreateOnePreQueryHook } from 'src/modules/dashboard/query-hooks/dashboard-create-one.pre-query.hook'; +import { DashboardDestroyManyPreQueryHook } from 'src/modules/dashboard/query-hooks/dashboard-destroy-many.pre-query.hook'; +import { DashboardDestroyOnePreQueryHook } from 'src/modules/dashboard/query-hooks/dashboard-destroy-one.pre-query.hook'; +import { DashboardToPageLayoutSyncService } from 'src/modules/dashboard/services/dashboard-to-page-layout-sync.service'; @Module({ - imports: [PageLayoutModule, PageLayoutTabModule], - providers: [DashboardCreateOnePreQueryHook], + imports: [PageLayoutModule, PageLayoutTabModule, TwentyORMModule], + providers: [ + DashboardToPageLayoutSyncService, + DashboardCreateOnePreQueryHook, + DashboardCreateManyPreQueryHook, + DashboardDestroyOnePreQueryHook, + DashboardDestroyManyPreQueryHook, + ], }) export class DashboardQueryHookModule {} diff --git a/packages/twenty-server/src/modules/dashboard/services/dashboard-to-page-layout-sync.service.ts b/packages/twenty-server/src/modules/dashboard/services/dashboard-to-page-layout-sync.service.ts new file mode 100644 index 00000000000..3dd4b5ff6f5 --- /dev/null +++ b/packages/twenty-server/src/modules/dashboard/services/dashboard-to-page-layout-sync.service.ts @@ -0,0 +1,86 @@ +import { Injectable } from '@nestjs/common'; + +import { isDefined } from 'twenty-shared/utils'; +import { In } from 'typeorm'; + +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 { 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'; +import type { DashboardWorkspaceEntity } from 'src/modules/dashboard/standard-objects/dashboard.workspace-entity'; + +@Injectable() +export class DashboardToPageLayoutSyncService { + constructor( + private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, + private readonly pageLayoutService: PageLayoutService, + private readonly pageLayoutTabService: PageLayoutTabService, + ) {} + + public async createPageLayoutForDashboard({ + workspaceId, + }: { + workspaceId: string; + }): Promise { + const pageLayout = await this.pageLayoutService.create({ + createPageLayoutInput: { + type: PageLayoutType.DASHBOARD, + objectMetadataId: null, + name: 'Dashboard Layout', + }, + workspaceId, + }); + + await this.pageLayoutTabService.create({ + createPageLayoutTabInput: { + title: 'Tab 1', + pageLayoutId: pageLayout.id, + }, + workspaceId, + }); + + return pageLayout.id; + } + + public async destroyPageLayoutsForDashboards({ + dashboardIds, + workspaceId, + }: { + dashboardIds: string[]; + workspaceId: string; + }): Promise { + const authContext = buildSystemAuthContext(workspaceId); + + await this.globalWorkspaceOrmManager.executeInWorkspaceContext( + authContext, + async () => { + const dashboardRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'dashboard', + { shouldBypassPermissionChecks: true }, + ); + + const dashboards = await dashboardRepository.find({ + where: { + id: In(dashboardIds), + }, + withDeleted: true, + }); + + for (const dashboard of dashboards) { + if (!isDefined(dashboard.pageLayoutId)) { + continue; + } + + await this.pageLayoutService.destroy({ + id: dashboard.pageLayoutId, + workspaceId, + isLinkedDashboardAlreadyDestroyed: true, + }); + } + }, + ); + } +} diff --git a/packages/twenty-server/src/modules/dashboard/tools/add-dashboard-widget.tool.ts b/packages/twenty-server/src/modules/dashboard/tools/add-dashboard-widget.tool.ts index 47e393cc89c..4e936254a50 100644 --- a/packages/twenty-server/src/modules/dashboard/tools/add-dashboard-widget.tool.ts +++ b/packages/twenty-server/src/modules/dashboard/tools/add-dashboard-widget.tool.ts @@ -52,10 +52,10 @@ See create_complete_dashboard for configuration examples.`, configuration?: AllPageLayoutWidgetConfiguration; }) => { try { - const widget = await deps.pageLayoutWidgetService.create( - parameters as CreatePageLayoutWidgetInput, - context.workspaceId, - ); + const widget = await deps.pageLayoutWidgetService.create({ + input: parameters as CreatePageLayoutWidgetInput, + workspaceId: context.workspaceId, + }); return { success: true, diff --git a/packages/twenty-server/src/modules/dashboard/tools/create-complete-dashboard.tool.ts b/packages/twenty-server/src/modules/dashboard/tools/create-complete-dashboard.tool.ts index f9315cb3ad3..8641c61db2c 100644 --- a/packages/twenty-server/src/modules/dashboard/tools/create-complete-dashboard.tool.ts +++ b/packages/twenty-server/src/modules/dashboard/tools/create-complete-dashboard.tool.ts @@ -101,28 +101,35 @@ AGGREGATION OPERATIONS: COUNT, SUM, AVG, MIN, MAX, COUNT_EMPTY, COUNT_NOT_EMPTY` const tabTitle = parameters.tabTitle ?? 'Main'; const widgets = parameters.widgets ?? []; - const pageLayout = await deps.pageLayoutService.create( - { name: parameters.title, type: PageLayoutType.DASHBOARD }, - context.workspaceId, - ); + const pageLayout = await deps.pageLayoutService.create({ + createPageLayoutInput: { + name: parameters.title, + type: PageLayoutType.DASHBOARD, + }, + workspaceId: context.workspaceId, + }); - const pageLayoutTab = await deps.pageLayoutTabService.create( - { title: tabTitle, pageLayoutId: pageLayout.id, position: 0 }, - context.workspaceId, - ); + const pageLayoutTab = await deps.pageLayoutTabService.create({ + createPageLayoutTabInput: { + title: tabTitle, + pageLayoutId: pageLayout.id, + position: 0, + }, + workspaceId: context.workspaceId, + }); const createdWidgets = []; const widgetErrors = []; for (const widget of widgets) { try { - const createdWidget = await deps.pageLayoutWidgetService.create( - { + const createdWidget = await deps.pageLayoutWidgetService.create({ + input: { ...widget, pageLayoutTabId: pageLayoutTab.id, } as CreatePageLayoutWidgetInput, - context.workspaceId, - ); + workspaceId: context.workspaceId, + }); createdWidgets.push({ id: createdWidget.id, diff --git a/packages/twenty-server/src/modules/dashboard/tools/delete-dashboard-widget.tool.ts b/packages/twenty-server/src/modules/dashboard/tools/delete-dashboard-widget.tool.ts index b6c0ae1ee88..04eeccd026c 100644 --- a/packages/twenty-server/src/modules/dashboard/tools/delete-dashboard-widget.tool.ts +++ b/packages/twenty-server/src/modules/dashboard/tools/delete-dashboard-widget.tool.ts @@ -18,15 +18,15 @@ export const createDeleteDashboardWidgetTool = ( inputSchema: deleteDashboardWidgetSchema, execute: async (parameters: { widgetId: string }) => { try { - const widget = await deps.pageLayoutWidgetService.findByIdOrThrow( - parameters.widgetId, - context.workspaceId, - ); + const widget = await deps.pageLayoutWidgetService.findByIdOrThrow({ + id: parameters.widgetId, + workspaceId: context.workspaceId, + }); - await deps.pageLayoutWidgetService.destroy( - parameters.widgetId, - context.workspaceId, - ); + await deps.pageLayoutWidgetService.destroy({ + id: parameters.widgetId, + workspaceId: context.workspaceId, + }); return { success: true, diff --git a/packages/twenty-server/src/modules/dashboard/tools/get-dashboard.tool.ts b/packages/twenty-server/src/modules/dashboard/tools/get-dashboard.tool.ts index d1853fc2c70..6d79a706782 100644 --- a/packages/twenty-server/src/modules/dashboard/tools/get-dashboard.tool.ts +++ b/packages/twenty-server/src/modules/dashboard/tools/get-dashboard.tool.ts @@ -55,10 +55,10 @@ export const createGetDashboardTool = ( }; } - const pageLayout = await deps.pageLayoutService.findByIdOrThrow( - dashboard.pageLayoutId, - context.workspaceId, - ); + const pageLayout = await deps.pageLayoutService.findByIdOrThrow({ + id: dashboard.pageLayoutId, + workspaceId: context.workspaceId, + }); const tabs = pageLayout.tabs?.map((tab) => ({ diff --git a/packages/twenty-server/src/modules/dashboard/tools/update-dashboard-widget.tool.ts b/packages/twenty-server/src/modules/dashboard/tools/update-dashboard-widget.tool.ts index f8b4df6e42b..57cf2d04353 100644 --- a/packages/twenty-server/src/modules/dashboard/tools/update-dashboard-widget.tool.ts +++ b/packages/twenty-server/src/modules/dashboard/tools/update-dashboard-widget.tool.ts @@ -57,11 +57,11 @@ Only provide fields you want to change - others remain unchanged.`, Object.entries(updates).filter(([, value]) => isDefined(value)), ); - const widget = await deps.pageLayoutWidgetService.update( - widgetId, - context.workspaceId, + const widget = await deps.pageLayoutWidgetService.update({ + id: widgetId, + workspaceId: context.workspaceId, updateData, - ); + }); return { success: true, diff --git a/packages/twenty-server/test/integration/metadata/suites/dashboard/successful-dashboard-duplication.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/dashboard/successful-dashboard-duplication.integration-spec.ts index 68b14eeed30..718b6c556cc 100644 --- a/packages/twenty-server/test/integration/metadata/suites/dashboard/successful-dashboard-duplication.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/dashboard/successful-dashboard-duplication.integration-spec.ts @@ -6,19 +6,16 @@ import { } from 'test/integration/metadata/suites/dashboard/utils/dashboard-graphql.util'; import { duplicateOneDashboard } from 'test/integration/metadata/suites/dashboard/utils/duplicate-one-dashboard.util'; import { createOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/create-one-page-layout-tab.util'; -import { destroyOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/destroy-one-page-layout-tab.util'; import { createOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/create-one-page-layout-widget.util'; -import { destroyOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/destroy-one-page-layout-widget.util'; import { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util'; -import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util'; import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; import { type EachTestingContext, eachTestingContextFilter, } from 'twenty-shared/testing'; -import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; 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 = { id: string; @@ -57,10 +54,8 @@ const SUCCESSFUL_TEST_CASES: EachTestingContext[] = [ describe('Dashboard duplication should succeed', () => { let testPageLayoutId: string; let testPageLayoutTabId: string; - let testPageLayoutWidgetId: string; let testDashboardId: string; let duplicatedDashboardId: string; - let duplicatedPageLayoutId: string; let currentTestContextId: string; const cleanup = async () => { @@ -69,30 +64,6 @@ describe('Dashboard duplication should succeed', () => { duplicatedDashboardId = ''; } - if (isNonEmptyString(duplicatedPageLayoutId)) { - await destroyOnePageLayout({ - expectToFail: false, - input: { id: duplicatedPageLayoutId }, - }); - duplicatedPageLayoutId = ''; - } - - if (isNonEmptyString(testPageLayoutWidgetId)) { - await destroyOnePageLayoutWidget({ - expectToFail: false, - input: { id: testPageLayoutWidgetId }, - }); - testPageLayoutWidgetId = ''; - } - - if (isNonEmptyString(testPageLayoutTabId)) { - await destroyOnePageLayoutTab({ - expectToFail: false, - input: { id: testPageLayoutTabId }, - }); - testPageLayoutTabId = ''; - } - if (isNonEmptyString(testDashboardId)) { await destroyDashboardWithGraphQL(testDashboardId); testDashboardId = ''; @@ -102,23 +73,13 @@ describe('Dashboard duplication should succeed', () => { await destroyDashboardWithGraphQL(currentTestContextId); currentTestContextId = ''; } - - if (isNonEmptyString(testPageLayoutId)) { - await destroyOnePageLayout({ - expectToFail: false, - input: { id: testPageLayoutId }, - }); - testPageLayoutId = ''; - } }; beforeEach(async () => { testPageLayoutId = ''; testPageLayoutTabId = ''; - testPageLayoutWidgetId = ''; testDashboardId = ''; duplicatedDashboardId = ''; - duplicatedPageLayoutId = ''; currentTestContextId = ''; }); @@ -153,7 +114,7 @@ describe('Dashboard duplication should succeed', () => { testPageLayoutTabId = tabData.createPageLayoutTab.id; if (withWidgets) { - const { data: widgetData } = await createOnePageLayoutWidget({ + await createOnePageLayoutWidget({ expectToFail: false, input: { title: 'Test Widget', @@ -168,8 +129,6 @@ describe('Dashboard duplication should succeed', () => { configuration: TEST_IFRAME_CONFIG, }, }); - - testPageLayoutWidgetId = widgetData.createPageLayoutWidget.id; } } @@ -187,7 +146,6 @@ describe('Dashboard duplication should succeed', () => { }); duplicatedDashboardId = data.duplicateDashboard.id; - duplicatedPageLayoutId = data.duplicateDashboard.pageLayoutId ?? ''; expect(data.duplicateDashboard).toMatchSnapshot( extractRecordIdsAndDatesAsExpectAny({ ...data.duplicateDashboard }), diff --git a/packages/twenty-server/test/integration/metadata/suites/dashboard/successful-dashboard-page-layout-auto-creation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/dashboard/successful-dashboard-page-layout-auto-creation.integration-spec.ts new file mode 100644 index 00000000000..3cf7a45f1db --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/dashboard/successful-dashboard-page-layout-auto-creation.integration-spec.ts @@ -0,0 +1,168 @@ +import { isNonEmptyString } from '@sniptt/guards'; +import { + createManyDashboardsWithGraphQL, + createTestDashboardWithGraphQL, + destroyDashboardWithGraphQL, + destroyManyDashboardsWithGraphQL, +} from 'test/integration/metadata/suites/dashboard/utils/dashboard-graphql.util'; +import { findPageLayoutTabs } from 'test/integration/metadata/suites/page-layout-tab/utils/find-page-layout-tabs.util'; +import { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util'; +import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util'; +import { findOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/find-one-page-layout.util'; + +import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; + +describe('Dashboard page layout auto-creation should succeed', () => { + describe('createOne dashboard without pageLayoutId', () => { + let createdDashboardId: string; + + beforeEach(() => { + createdDashboardId = ''; + }); + + afterEach(async () => { + if (isNonEmptyString(createdDashboardId)) { + await destroyDashboardWithGraphQL(createdDashboardId); + } + }); + + it('should auto-create page layout and tab for dashboard without pageLayoutId', async () => { + const title = 'Test Dashboard'; + const dashboard = await createTestDashboardWithGraphQL({ title }); + + createdDashboardId = dashboard.id; + + expect(dashboard.id).toBeDefined(); + expect(dashboard.title).toBe(title); + expect(dashboard.pageLayoutId).toBeDefined(); + expect(isNonEmptyString(dashboard.pageLayoutId)).toBe(true); + + const { data: pageLayoutData } = await findOnePageLayout({ + expectToFail: false, + input: { id: dashboard.pageLayoutId! }, + }); + + expect(pageLayoutData.getPageLayout).toBeDefined(); + expect(pageLayoutData.getPageLayout?.type).toBe(PageLayoutType.DASHBOARD); + + const { data: tabsData } = await findPageLayoutTabs({ + expectToFail: false, + input: { pageLayoutId: dashboard.pageLayoutId! }, + }); + + expect(tabsData.getPageLayoutTabs).toBeDefined(); + expect(tabsData.getPageLayoutTabs.length).toBeGreaterThanOrEqual(1); + expect(tabsData.getPageLayoutTabs[0].title).toBe('Tab 1'); + }); + }); + + describe('createMany dashboards without pageLayoutId', () => { + let createdDashboardIds: string[] = []; + + beforeEach(() => { + createdDashboardIds = []; + }); + + afterEach(async () => { + if (createdDashboardIds.length > 0) { + await destroyManyDashboardsWithGraphQL({ + id: { in: createdDashboardIds }, + }); + } + }); + + it('should auto-create separate page layouts for each dashboard in createMany', async () => { + const dashboardsData = [ + { title: 'Dashboard 1' }, + { title: 'Dashboard 2' }, + { title: 'Dashboard 3' }, + ]; + + const dashboards = await createManyDashboardsWithGraphQL(dashboardsData); + + createdDashboardIds = dashboards.map((d) => d.id); + + expect(dashboards.length).toBe(dashboardsData.length); + + const pageLayoutIds = new Set( + dashboards.map((d) => d.pageLayoutId).filter(isNonEmptyString), + ); + + expect(pageLayoutIds.size).toBe(dashboardsData.length); + + for (const dashboard of dashboards) { + expect(dashboard.pageLayoutId).toBeDefined(); + expect(isNonEmptyString(dashboard.pageLayoutId)).toBe(true); + + const { data: pageLayoutData } = await findOnePageLayout({ + expectToFail: false, + input: { id: dashboard.pageLayoutId! }, + }); + + expect(pageLayoutData.getPageLayout).toBeDefined(); + expect(pageLayoutData.getPageLayout?.type).toBe( + PageLayoutType.DASHBOARD, + ); + + const { data: tabsData } = await findPageLayoutTabs({ + expectToFail: false, + input: { pageLayoutId: dashboard.pageLayoutId! }, + }); + + expect(tabsData.getPageLayoutTabs).toBeDefined(); + expect(tabsData.getPageLayoutTabs.length).toBeGreaterThanOrEqual(1); + } + }); + + it('should use provided pageLayoutId for some dashboards and auto-create for others', async () => { + const { data: pageLayoutData } = await createOnePageLayout({ + input: { + name: 'Pre-existing page layout', + type: PageLayoutType.DASHBOARD, + }, + expectToFail: false, + }); + + const existingPageLayoutId = pageLayoutData.createPageLayout.id; + + expect(existingPageLayoutId).toBeDefined(); + + const dashboardsData = [ + { + title: 'Dashboard with provided layout', + pageLayoutId: existingPageLayoutId, + }, + { title: 'Dashboard without layout 1' }, + { title: 'Dashboard without layout 2' }, + ]; + + const dashboards = await createManyDashboardsWithGraphQL(dashboardsData); + + createdDashboardIds = dashboards.map((d) => d.id); + + expect(dashboards.length).toBe(3); + + const dashboardWithProvidedLayout = dashboards.find( + (d) => d.title === 'Dashboard with provided layout', + ); + const dashboardsWithAutoCreatedLayout = dashboards.filter( + (d) => d.title !== 'Dashboard with provided layout', + ); + + expect(dashboardWithProvidedLayout?.pageLayoutId).toBe( + existingPageLayoutId, + ); + + for (const dashboard of dashboardsWithAutoCreatedLayout) { + expect(dashboard.pageLayoutId).toBeDefined(); + expect(isNonEmptyString(dashboard.pageLayoutId)).toBe(true); + expect(dashboard.pageLayoutId).not.toBe(existingPageLayoutId); + } + + await destroyOnePageLayout({ + input: { id: existingPageLayoutId }, + expectToFail: false, + }); + }); + }); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/dashboard/successful-dashboard-to-page-layout-sync.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/dashboard/successful-dashboard-to-page-layout-sync.integration-spec.ts new file mode 100644 index 00000000000..cfa426417f1 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/dashboard/successful-dashboard-to-page-layout-sync.integration-spec.ts @@ -0,0 +1,118 @@ +import { isNonEmptyString } from '@sniptt/guards'; +import { + createManyDashboardsWithGraphQL, + createTestDashboardWithGraphQL, + destroyDashboardWithGraphQL, + destroyManyDashboardsWithGraphQL, +} from 'test/integration/metadata/suites/dashboard/utils/dashboard-graphql.util'; +import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util'; +import { findOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/find-one-page-layout.util'; + +describe('Dashboard to Page Layout sync should succeed', () => { + describe('destroyOne dashboard', () => { + let pageLayoutId: string; + + beforeEach(() => { + pageLayoutId = ''; + }); + + afterEach(async () => { + if (isNonEmptyString(pageLayoutId)) { + await destroyOnePageLayout({ + expectToFail: false, + input: { id: pageLayoutId }, + }); + pageLayoutId = ''; + } + }); + + it('should hard delete linked page layout when dashboard is destroyed', async () => { + const dashboard = await createTestDashboardWithGraphQL({ + title: 'Dashboard for Destroy Test', + }); + + pageLayoutId = dashboard.pageLayoutId ?? ''; + + expect(isNonEmptyString(pageLayoutId)).toBe(true); + + const { data: beforeData } = await findOnePageLayout({ + expectToFail: false, + input: { id: pageLayoutId }, + }); + + expect(beforeData.getPageLayout).toBeDefined(); + + await destroyDashboardWithGraphQL(dashboard.id); + + const { errors } = await findOnePageLayout({ + expectToFail: true, + input: { id: pageLayoutId }, + }); + + expect(errors).toBeDefined(); + + pageLayoutId = ''; + }); + }); + + describe('destroyMany dashboards', () => { + let pageLayoutIds: string[] = []; + + beforeEach(() => { + pageLayoutIds = []; + }); + + afterEach(async () => { + for (const pageLayoutId of pageLayoutIds) { + if (isNonEmptyString(pageLayoutId)) { + await destroyOnePageLayout({ + expectToFail: false, + input: { id: pageLayoutId }, + }); + } + } + pageLayoutIds = []; + }); + + it('should hard delete all linked page layouts when multiple dashboards are destroyed', async () => { + const dashboardsData = [ + { title: 'Dashboard 1 for Destroy Many Test' }, + { title: 'Dashboard 2 for Destroy Many Test' }, + ]; + + const dashboards = await createManyDashboardsWithGraphQL(dashboardsData); + + const dashboardIds = dashboards.map((d) => d.id); + + pageLayoutIds = dashboards + .map((d) => d.pageLayoutId) + .filter(isNonEmptyString); + + expect(pageLayoutIds.length).toBe(dashboardsData.length); + + for (const pageLayoutId of pageLayoutIds) { + const { data } = await findOnePageLayout({ + expectToFail: false, + input: { id: pageLayoutId }, + }); + + expect(data.getPageLayout).toBeDefined(); + } + + await destroyManyDashboardsWithGraphQL({ + id: { in: dashboardIds }, + }); + + for (const pageLayoutId of pageLayoutIds) { + const { errors } = await findOnePageLayout({ + expectToFail: true, + input: { id: pageLayoutId }, + }); + + expect(errors).toBeDefined(); + } + + pageLayoutIds = []; + }); + }); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/dashboard/dashboard-updated-at-sync.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/dashboard/successful-dashboard-updated-at-sync.integration-spec.ts similarity index 65% rename from packages/twenty-server/test/integration/metadata/suites/dashboard/dashboard-updated-at-sync.integration-spec.ts rename to packages/twenty-server/test/integration/metadata/suites/dashboard/successful-dashboard-updated-at-sync.integration-spec.ts index 238c22e1f30..70639d699c7 100644 --- a/packages/twenty-server/test/integration/metadata/suites/dashboard/dashboard-updated-at-sync.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/dashboard/successful-dashboard-updated-at-sync.integration-spec.ts @@ -5,19 +5,13 @@ import { 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'; @@ -83,22 +77,7 @@ const createTestContext = async (): Promise => { }; const cleanupTestContext = async (context: TestContext): Promise => { - 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 ( @@ -124,43 +103,6 @@ const assertDashboardUpdatedAtIncreased = async ( expect(isIncreased).toBe(true); }; -const assertDashboardSoftDeleted = async ( - dashboardId: string, - operation: () => Promise, -): Promise => { - 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, -): Promise => { - 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; @@ -212,29 +154,6 @@ describe('Dashboard updatedAt should sync when linked page layout entities chang }); }); }); - - 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', () => { @@ -278,49 +197,6 @@ describe('Dashboard updatedAt should sync when linked page layout entities chang }); }); }); - - 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', () => { @@ -345,29 +221,6 @@ describe('Dashboard updatedAt should sync when linked page layout entities chang }); }); }); - - 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', () => { diff --git a/packages/twenty-server/test/integration/metadata/suites/dashboard/utils/dashboard-graphql.util.ts b/packages/twenty-server/test/integration/metadata/suites/dashboard/utils/dashboard-graphql.util.ts index 19ef3406558..1cc1f43c58d 100644 --- a/packages/twenty-server/test/integration/metadata/suites/dashboard/utils/dashboard-graphql.util.ts +++ b/packages/twenty-server/test/integration/metadata/suites/dashboard/utils/dashboard-graphql.util.ts @@ -10,10 +10,34 @@ interface CreateDashboardResponse extends Record { createDashboard: DashboardWorkspaceEntity; } +interface CreateManyDashboardsResponse extends Record { + createDashboards: DashboardWorkspaceEntity[]; +} + interface FindDashboardResponse extends Record { dashboard: DashboardWorkspaceEntity | null; } +interface DeleteDashboardResponse extends Record { + deleteDashboard: DashboardWorkspaceEntity; +} + +interface DeleteManyDashboardsResponse extends Record { + deleteDashboards: DashboardWorkspaceEntity[]; +} + +interface RestoreDashboardResponse extends Record { + restoreDashboard: DashboardWorkspaceEntity; +} + +interface RestoreManyDashboardsResponse extends Record { + restoreDashboards: DashboardWorkspaceEntity[]; +} + +interface DestroyManyDashboardsResponse extends Record { + destroyDashboards: DashboardWorkspaceEntity[]; +} + export const createTestDashboardWithGraphQL = async (data: { id?: string; title: string; @@ -87,20 +111,259 @@ export const destroyDashboardWithGraphQL = async ( ): Promise => { const operation = { query: gql` - mutation DestroyDashboard($filter: DashboardFilterInput!) { - destroyDashboard(filter: $filter) { + mutation DestroyDashboard($dashboardId: UUID!) { + destroyDashboard(id: $dashboardId) { id } } `, variables: { - filter: { id: { eq: dashboardId } }, + dashboardId, }, }; await makeGraphqlAPIRequest(operation); }; +export const createManyDashboardsWithGraphQL = async ( + data: Array<{ + id?: string; + title: string; + position?: number; + pageLayoutId?: string; + }>, +): Promise => { + const operation = { + query: gql` + mutation CreateDashboards($data: [DashboardCreateInput!]!) { + createDashboards(data: $data) { + ${DASHBOARD_GQL_FIELDS} + } + } + `, + variables: { + data: data.map((item, index) => ({ + id: item.id, + title: item.title, + position: item.position ?? index, + pageLayoutId: item.pageLayoutId, + })), + }, + }; + + const response = (await makeGraphqlAPIRequest( + operation, + )) as GraphQLResponse; + + if (response.body.errors) { + throw new Error( + `Failed to create dashboards: ${JSON.stringify(response.body.errors)}`, + ); + } + + if (!response.body.data) { + throw new Error('No data returned from createManyDashboardsWithGraphQL'); + } + + return response.body.data.createDashboards; +}; + +export const deleteDashboardWithGraphQL = async ( + dashboardId: string, +): Promise => { + const operation = { + query: gql` + mutation DeleteDashboard($dashboardId: UUID!) { + deleteDashboard(id: $dashboardId) { + ${DASHBOARD_GQL_FIELDS} + } + } + `, + variables: { + dashboardId, + }, + }; + + const response = (await makeGraphqlAPIRequest( + operation, + )) as GraphQLResponse; + + if (response.body.errors) { + throw new Error( + `Failed to delete dashboard: ${JSON.stringify(response.body.errors)}`, + ); + } + + if (!response.body.data) { + throw new Error('No data returned from deleteDashboardWithGraphQL'); + } + + return response.body.data.deleteDashboard; +}; + +export const deleteManyDashboardsWithGraphQL = async (filter: { + id: { in: string[] }; +}): Promise => { + const operation = { + query: gql` + mutation DeleteDashboards($filter: DashboardFilterInput!) { + deleteDashboards(filter: $filter) { + ${DASHBOARD_GQL_FIELDS} + } + } + `, + variables: { + filter, + }, + }; + + const response = (await makeGraphqlAPIRequest( + operation, + )) as GraphQLResponse; + + if (response.body.errors) { + throw new Error( + `Failed to delete dashboards: ${JSON.stringify(response.body.errors)}`, + ); + } + + if (!response.body.data) { + throw new Error('No data returned from deleteManyDashboardsWithGraphQL'); + } + + return response.body.data.deleteDashboards; +}; + +export const restoreDashboardWithGraphQL = async ( + dashboardId: string, +): Promise => { + const operation = { + query: gql` + mutation RestoreDashboard($dashboardId: UUID!) { + restoreDashboard(id: $dashboardId) { + ${DASHBOARD_GQL_FIELDS} + } + } + `, + variables: { + dashboardId, + }, + }; + + const response = (await makeGraphqlAPIRequest( + operation, + )) as GraphQLResponse; + + if (response.body.errors) { + throw new Error( + `Failed to restore dashboard: ${JSON.stringify(response.body.errors)}`, + ); + } + + if (!response.body.data) { + throw new Error('No data returned from restoreDashboardWithGraphQL'); + } + + return response.body.data.restoreDashboard; +}; + +export const restoreManyDashboardsWithGraphQL = async (filter: { + id: { in: string[] }; +}): Promise => { + const operation = { + query: gql` + mutation RestoreDashboards($filter: DashboardFilterInput!) { + restoreDashboards(filter: $filter) { + ${DASHBOARD_GQL_FIELDS} + } + } + `, + variables: { + filter, + }, + }; + + const response = (await makeGraphqlAPIRequest( + operation, + )) as GraphQLResponse; + + if (response.body.errors) { + throw new Error( + `Failed to restore dashboards: ${JSON.stringify(response.body.errors)}`, + ); + } + + if (!response.body.data) { + throw new Error('No data returned from restoreManyDashboardsWithGraphQL'); + } + + return response.body.data.restoreDashboards; +}; + +export const destroyManyDashboardsWithGraphQL = async (filter: { + id: { in: string[] }; +}): Promise => { + const operation = { + query: gql` + mutation DestroyDashboards($filter: DashboardFilterInput!) { + destroyDashboards(filter: $filter) { + ${DASHBOARD_GQL_FIELDS} + } + } + `, + variables: { + filter, + }, + }; + + const response = (await makeGraphqlAPIRequest( + operation, + )) as GraphQLResponse; + + if (response.body.errors) { + throw new Error( + `Failed to destroy dashboards: ${JSON.stringify(response.body.errors)}`, + ); + } + + if (!response.body.data) { + throw new Error('No data returned from destroyManyDashboardsWithGraphQL'); + } + + return response.body.data.destroyDashboards; +}; + +export const findDeletedDashboardWithGraphQL = async ( + dashboardId: string, +): Promise => { + const operation = { + query: gql` + query FindDeletedDashboard($filter: DashboardFilterInput!) { + dashboard(filter: $filter) { + ${DASHBOARD_GQL_FIELDS} + deletedAt + } + } + `, + variables: { + filter: { + id: { eq: dashboardId }, + deletedAt: { is: 'NOT_NULL' }, + }, + }, + }; + + const response = (await makeGraphqlAPIRequest( + operation, + )) as GraphQLResponse; + + if (response.body.errors) { + return null; + } + + return response.body.data?.dashboard ?? null; +}; + const TEST_SCHEMA_NAME = 'workspace_1wgvd1injqtife6y4rvfbu3h5'; export const cleanupDashboardRecords = async (): Promise => { diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/__snapshots__/failing-page-layout-tab-deletion.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/__snapshots__/failing-page-layout-tab-deletion.integration-spec.ts.snap deleted file mode 100644 index b842360db54..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/__snapshots__/failing-page-layout-tab-deletion.integration-spec.ts.snap +++ /dev/null @@ -1,12 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Page layout tab deletion should fail when deleting a non-existent page layout tab 1`] = ` -{ - "extensions": { - "code": "NOT_FOUND", - "userFriendlyMessage": "An error occurred.", - }, - "message": "Page layout tab to delete not found", - "name": "NotFoundError", -} -`; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/__snapshots__/successful-page-layout-tab-deletion.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/__snapshots__/successful-page-layout-tab-deletion.integration-spec.ts.snap deleted file mode 100644 index a1ce3169a52..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/__snapshots__/successful-page-layout-tab-deletion.integration-spec.ts.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Page layout tab deletion should succeed should soft delete and restore a page layout tab 1`] = ` -{ - "createdAt": Any, - "deletedAt": null, - "id": Any, - "pageLayoutId": Any, - "position": 0, - "title": "Tab To Delete", - "updatedAt": Any, -} -`; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/failing-page-layout-tab-deletion.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/failing-page-layout-tab-deletion.integration-spec.ts deleted file mode 100644 index a6915b5e7ea..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/failing-page-layout-tab-deletion.integration-spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { faker } from '@faker-js/faker'; -import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; -import { deleteOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/delete-one-page-layout-tab.util'; - -describe('Page layout tab deletion should fail', () => { - it('when deleting a non-existent page layout tab', async () => { - const { errors } = await deleteOnePageLayoutTab({ - expectToFail: true, - input: { id: faker.string.uuid() }, - }); - - expectOneNotInternalServerErrorSnapshot({ errors }); - }); -}); diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/successful-page-layout-tab-deletion.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/successful-page-layout-tab-deletion.integration-spec.ts index c18fc70aa19..08a3263e754 100644 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/successful-page-layout-tab-deletion.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/successful-page-layout-tab-deletion.integration-spec.ts @@ -1,36 +1,7 @@ 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 { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util'; import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util'; -import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; -import { - type EachTestingContext, - eachTestingContextFilter, -} from 'twenty-shared/testing'; - -type TestContext = { - title: string; - operation: 'soft-delete-restore' | 'hard-delete'; -}; - -const SUCCESSFUL_TEST_CASES: EachTestingContext[] = [ - { - title: 'soft delete and restore a page layout tab', - context: { - title: 'Tab To Delete', - operation: 'soft-delete-restore', - }, - }, - { - title: 'hard delete a page layout tab', - context: { - title: 'Tab To Destroy', - operation: 'hard-delete', - }, - }, -]; describe('Page layout tab deletion should succeed', () => { let testPageLayoutId: string; @@ -51,50 +22,22 @@ describe('Page layout tab deletion should succeed', () => { }); }); - it.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))( - 'should $title', - async ({ context: { title, operation } }) => { - const { data: createData } = await createOnePageLayoutTab({ - expectToFail: false, - input: { - title, - pageLayoutId: testPageLayoutId, - }, - }); + it('should hard delete a page layout tab', async () => { + const { data: createData } = await createOnePageLayoutTab({ + expectToFail: false, + input: { + title: 'Tab To Destroy', + pageLayoutId: testPageLayoutId, + }, + }); - const tabId = createData.createPageLayoutTab.id; + const tabId = createData.createPageLayoutTab.id; - if (operation === 'soft-delete-restore') { - const { data: deleteData } = await deleteOnePageLayoutTab({ - expectToFail: false, - input: { id: tabId }, - }); + const { data: destroyData } = await destroyOnePageLayoutTab({ + expectToFail: false, + input: { id: tabId }, + }); - expect(deleteData.deletePageLayoutTab).toBe(true); - - const { data: restoreData } = await restoreOnePageLayoutTab({ - expectToFail: false, - input: { id: tabId }, - }); - - expect(restoreData.restorePageLayoutTab).toMatchSnapshot( - extractRecordIdsAndDatesAsExpectAny({ - ...restoreData.restorePageLayoutTab, - }), - ); - - await destroyOnePageLayoutTab({ - expectToFail: false, - input: { id: tabId }, - }); - } else { - const { data: destroyData } = await destroyOnePageLayoutTab({ - expectToFail: false, - input: { id: tabId }, - }); - - expect(destroyData.destroyPageLayoutTab).toBe(true); - } - }, - ); + expect(destroyData.destroyPageLayoutTab).toBe(true); + }); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/utils/delete-one-page-layout-tab-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/utils/delete-one-page-layout-tab-query-factory.util.ts deleted file mode 100644 index 15ea71b91c3..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/utils/delete-one-page-layout-tab-query-factory.util.ts +++ /dev/null @@ -1,19 +0,0 @@ -import gql from 'graphql-tag'; -import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; - -export type DeleteOnePageLayoutTabFactoryInput = { - id: string; -}; - -export const deleteOnePageLayoutTabQueryFactory = ({ - input, -}: PerformMetadataQueryParams) => ({ - query: gql` - mutation DeletePageLayoutTab($id: String!) { - deletePageLayoutTab(id: $id) - } - `, - variables: { - id: input.id, - }, -}); diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/utils/delete-one-page-layout-tab.util.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/utils/delete-one-page-layout-tab.util.ts deleted file mode 100644 index f48411bf17d..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/utils/delete-one-page-layout-tab.util.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { - type DeleteOnePageLayoutTabFactoryInput, - deleteOnePageLayoutTabQueryFactory, -} from 'test/integration/metadata/suites/page-layout-tab/utils/delete-one-page-layout-tab-query-factory.util'; -import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; -import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; -import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; -import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; -import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; - -export const deleteOnePageLayoutTab = async ({ - input, - expectToFail = false, - token, -}: PerformMetadataQueryParams): CommonResponseBody<{ - deletePageLayoutTab: boolean; -}> => { - const graphqlOperation = deleteOnePageLayoutTabQueryFactory({ - input, - }); - - const response = await makeMetadataAPIRequest(graphqlOperation, token); - - if (expectToFail === true) { - warnIfNoErrorButExpectedToFail({ - response, - errorMessage: 'Page layout tab deletion should have failed but did not', - }); - } - - if (expectToFail === false) { - warnIfErrorButNotExpectedToFail({ - response, - errorMessage: 'Page layout tab deletion has failed but should not', - }); - } - - return { data: response.body.data, errors: response.body.errors }; -}; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/utils/restore-one-page-layout-tab-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/utils/restore-one-page-layout-tab-query-factory.util.ts deleted file mode 100644 index e9646cd70d8..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/utils/restore-one-page-layout-tab-query-factory.util.ts +++ /dev/null @@ -1,32 +0,0 @@ -import gql from 'graphql-tag'; -import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; - -export type RestoreOnePageLayoutTabFactoryInput = { - id: string; -}; - -const DEFAULT_PAGE_LAYOUT_TAB_GQL_FIELDS = ` - id - title - position - pageLayoutId - createdAt - updatedAt - deletedAt -`; - -export const restoreOnePageLayoutTabQueryFactory = ({ - input, - gqlFields = DEFAULT_PAGE_LAYOUT_TAB_GQL_FIELDS, -}: PerformMetadataQueryParams) => ({ - query: gql` - mutation RestorePageLayoutTab($id: String!) { - restorePageLayoutTab(id: $id) { - ${gqlFields} - } - } - `, - variables: { - id: input.id, - }, -}); diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/utils/restore-one-page-layout-tab.util.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/utils/restore-one-page-layout-tab.util.ts deleted file mode 100644 index 42d8c4b53a5..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-tab/utils/restore-one-page-layout-tab.util.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - type RestoreOnePageLayoutTabFactoryInput, - restoreOnePageLayoutTabQueryFactory, -} from 'test/integration/metadata/suites/page-layout-tab/utils/restore-one-page-layout-tab-query-factory.util'; -import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; -import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; -import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; -import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; -import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; - -import { type PageLayoutTabDTO } from 'src/engine/metadata-modules/page-layout-tab/dtos/page-layout-tab.dto'; - -export const restoreOnePageLayoutTab = async ({ - input, - gqlFields, - expectToFail = false, - token, -}: PerformMetadataQueryParams): CommonResponseBody<{ - restorePageLayoutTab: PageLayoutTabDTO; -}> => { - const graphqlOperation = restoreOnePageLayoutTabQueryFactory({ - input, - gqlFields, - }); - - const response = await makeMetadataAPIRequest(graphqlOperation, token); - - if (expectToFail === true) { - warnIfNoErrorButExpectedToFail({ - response, - errorMessage: 'Page layout tab restore should have failed but did not', - }); - } - - if (expectToFail === false) { - warnIfErrorButNotExpectedToFail({ - response, - errorMessage: 'Page layout tab restore has failed but should not', - }); - } - - return { data: response.body.data, errors: response.body.errors }; -}; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/__snapshots__/failing-page-layout-widget-deletion.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/__snapshots__/failing-page-layout-widget-deletion.integration-spec.ts.snap deleted file mode 100644 index 0eb1eaf3c36..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/__snapshots__/failing-page-layout-widget-deletion.integration-spec.ts.snap +++ /dev/null @@ -1,12 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Page layout widget deletion should fail when deleting a non-existent page layout widget 1`] = ` -{ - "extensions": { - "code": "NOT_FOUND", - "userFriendlyMessage": "An error occurred.", - }, - "message": "Page layout widget to delete not found", - "name": "NotFoundError", -} -`; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/__snapshots__/successful-page-layout-widget-deletion.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/__snapshots__/successful-page-layout-widget-deletion.integration-spec.ts.snap deleted file mode 100644 index 73eb821b888..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/__snapshots__/successful-page-layout-widget-deletion.integration-spec.ts.snap +++ /dev/null @@ -1,47 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Page layout widget deletion should succeed should soft delete and restore a page layout widget 1`] = ` -{ - "configuration": { - "configurationType": "IFRAME", - "url": null, - }, - "createdAt": Any, - "deletedAt": Any, - "gridPosition": { - "column": 0, - "columnSpan": 1, - "row": 0, - "rowSpan": 1, - }, - "id": Any, - "objectMetadataId": null, - "pageLayoutTabId": Any, - "title": "Widget To Delete", - "type": "IFRAME", - "updatedAt": Any, -} -`; - -exports[`Page layout widget deletion should succeed should soft delete and restore a page layout widget 2`] = ` -{ - "configuration": { - "configurationType": "IFRAME", - "url": null, - }, - "createdAt": Any, - "deletedAt": null, - "gridPosition": { - "column": 0, - "columnSpan": 1, - "row": 0, - "rowSpan": 1, - }, - "id": Any, - "objectMetadataId": null, - "pageLayoutTabId": Any, - "title": "Widget To Delete", - "type": "IFRAME", - "updatedAt": Any, -} -`; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/failing-page-layout-widget-deletion.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/failing-page-layout-widget-deletion.integration-spec.ts deleted file mode 100644 index 08468623c8b..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/failing-page-layout-widget-deletion.integration-spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { faker } from '@faker-js/faker'; -import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; -import { deleteOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/delete-one-page-layout-widget.util'; - -describe('Page layout widget deletion should fail', () => { - it('when deleting a non-existent page layout widget', async () => { - const { errors } = await deleteOnePageLayoutWidget({ - expectToFail: true, - input: { id: faker.string.uuid() }, - }); - - expectOneNotInternalServerErrorSnapshot({ errors }); - }); -}); diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/successful-page-layout-widget-deletion.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/successful-page-layout-widget-deletion.integration-spec.ts index 8b3bbbe63b2..5ebd37b6815 100644 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/successful-page-layout-widget-deletion.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/successful-page-layout-widget-deletion.integration-spec.ts @@ -1,42 +1,13 @@ import { createOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/create-one-page-layout-tab.util'; import { destroyOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/destroy-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 { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util'; import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util'; -import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; -import { - type EachTestingContext, - eachTestingContextFilter, -} from 'twenty-shared/testing'; import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type'; import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum'; -type TestContext = { - title: string; - operation: 'soft-delete-restore' | 'hard-delete'; -}; - -const SUCCESSFUL_TEST_CASES: EachTestingContext[] = [ - { - title: 'soft delete and restore a page layout widget', - context: { - title: 'Widget To Delete', - operation: 'soft-delete-restore', - }, - }, - { - title: 'hard delete a page layout widget', - context: { - title: 'Widget To Destroy', - operation: 'hard-delete', - }, - }, -]; - describe('Page layout widget deletion should succeed', () => { let testPageLayoutId: string; let testPageLayoutTabId: string; @@ -71,64 +42,32 @@ describe('Page layout widget deletion should succeed', () => { }); }); - it.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))( - 'should $title', - async ({ context: { title, operation } }) => { - const { data: createData } = await createOnePageLayoutWidget({ - expectToFail: false, - input: { - title, - pageLayoutTabId: testPageLayoutTabId, - type: WidgetType.IFRAME, - configuration: { - configurationType: WidgetConfigurationType.IFRAME, - }, - gridPosition: { - row: 0, - column: 0, - rowSpan: 1, - columnSpan: 1, - }, + it('should hard delete a page layout widget', async () => { + const { data: createData } = await createOnePageLayoutWidget({ + expectToFail: false, + input: { + title: 'Widget To Destroy', + pageLayoutTabId: testPageLayoutTabId, + type: WidgetType.IFRAME, + configuration: { + configurationType: WidgetConfigurationType.IFRAME, }, - }); + gridPosition: { + row: 0, + column: 0, + rowSpan: 1, + columnSpan: 1, + }, + }, + }); - const widgetId = createData.createPageLayoutWidget.id; + const widgetId = createData.createPageLayoutWidget.id; - if (operation === 'soft-delete-restore') { - const { data: deleteData } = await deleteOnePageLayoutWidget({ - expectToFail: false, - input: { id: widgetId }, - }); + const { data: destroyData } = await destroyOnePageLayoutWidget({ + expectToFail: false, + input: { id: widgetId }, + }); - expect(deleteData.deletePageLayoutWidget).toMatchSnapshot( - extractRecordIdsAndDatesAsExpectAny({ - ...deleteData.deletePageLayoutWidget, - }), - ); - - const { data: restoreData } = await restoreOnePageLayoutWidget({ - expectToFail: false, - input: { id: widgetId }, - }); - - expect(restoreData.restorePageLayoutWidget).toMatchSnapshot( - extractRecordIdsAndDatesAsExpectAny({ - ...restoreData.restorePageLayoutWidget, - }), - ); - - await destroyOnePageLayoutWidget({ - expectToFail: false, - input: { id: widgetId }, - }); - } else { - const { data: destroyData } = await destroyOnePageLayoutWidget({ - expectToFail: false, - input: { id: widgetId }, - }); - - expect(destroyData.destroyPageLayoutWidget).toBe(true); - } - }, - ); + expect(destroyData.destroyPageLayoutWidget).toBe(true); + }); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/utils/delete-one-page-layout-widget-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/utils/delete-one-page-layout-widget-query-factory.util.ts deleted file mode 100644 index 191dd69b243..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/utils/delete-one-page-layout-widget-query-factory.util.ts +++ /dev/null @@ -1,43 +0,0 @@ -import gql from 'graphql-tag'; -import { WIDGET_CONFIGURATION_GQL_FIELDS } from 'test/integration/metadata/suites/page-layout-widget/constants/widget-configuration-gql-fields.constant'; -import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; - -export type DeleteOnePageLayoutWidgetFactoryInput = { - id: string; -}; - -const DEFAULT_PAGE_LAYOUT_WIDGET_GQL_FIELDS = ` - id - title - type - pageLayoutTabId - objectMetadataId - gridPosition { - row - column - rowSpan - columnSpan - } - configuration { - ${WIDGET_CONFIGURATION_GQL_FIELDS} - } - createdAt - updatedAt - deletedAt -`; - -export const deleteOnePageLayoutWidgetQueryFactory = ({ - input, - gqlFields = DEFAULT_PAGE_LAYOUT_WIDGET_GQL_FIELDS, -}: PerformMetadataQueryParams) => ({ - query: gql` - mutation DeletePageLayoutWidget($id: String!) { - deletePageLayoutWidget(id: $id) { - ${gqlFields} - } - } - `, - variables: { - id: input.id, - }, -}); diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/utils/delete-one-page-layout-widget.util.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/utils/delete-one-page-layout-widget.util.ts deleted file mode 100644 index 7fec0558761..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/utils/delete-one-page-layout-widget.util.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - type DeleteOnePageLayoutWidgetFactoryInput, - deleteOnePageLayoutWidgetQueryFactory, -} from 'test/integration/metadata/suites/page-layout-widget/utils/delete-one-page-layout-widget-query-factory.util'; -import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; -import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; -import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; -import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; -import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; - -import { type PageLayoutWidgetDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/page-layout-widget.dto'; - -export const deleteOnePageLayoutWidget = async ({ - input, - gqlFields, - expectToFail = false, - token, -}: PerformMetadataQueryParams): CommonResponseBody<{ - deletePageLayoutWidget: PageLayoutWidgetDTO; -}> => { - const graphqlOperation = deleteOnePageLayoutWidgetQueryFactory({ - input, - gqlFields, - }); - - const response = await makeMetadataAPIRequest(graphqlOperation, token); - - if (expectToFail === true) { - warnIfNoErrorButExpectedToFail({ - response, - errorMessage: - 'Page layout widget deletion should have failed but did not', - }); - } - - if (expectToFail === false) { - warnIfErrorButNotExpectedToFail({ - response, - errorMessage: 'Page layout widget deletion has failed but should not', - }); - } - - return { data: response.body.data, errors: response.body.errors }; -}; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/utils/restore-one-page-layout-widget-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/utils/restore-one-page-layout-widget-query-factory.util.ts deleted file mode 100644 index cb55cd53796..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/utils/restore-one-page-layout-widget-query-factory.util.ts +++ /dev/null @@ -1,43 +0,0 @@ -import gql from 'graphql-tag'; -import { WIDGET_CONFIGURATION_GQL_FIELDS } from 'test/integration/metadata/suites/page-layout-widget/constants/widget-configuration-gql-fields.constant'; -import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; - -export type RestoreOnePageLayoutWidgetFactoryInput = { - id: string; -}; - -const DEFAULT_PAGE_LAYOUT_WIDGET_GQL_FIELDS = ` - id - title - type - pageLayoutTabId - objectMetadataId - gridPosition { - row - column - rowSpan - columnSpan - } - configuration { - ${WIDGET_CONFIGURATION_GQL_FIELDS} - } - createdAt - updatedAt - deletedAt -`; - -export const restoreOnePageLayoutWidgetQueryFactory = ({ - input, - gqlFields = DEFAULT_PAGE_LAYOUT_WIDGET_GQL_FIELDS, -}: PerformMetadataQueryParams) => ({ - query: gql` - mutation RestorePageLayoutWidget($id: String!) { - restorePageLayoutWidget(id: $id) { - ${gqlFields} - } - } - `, - variables: { - id: input.id, - }, -}); diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/utils/restore-one-page-layout-widget.util.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/utils/restore-one-page-layout-widget.util.ts deleted file mode 100644 index 15298cb42c4..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout-widget/utils/restore-one-page-layout-widget.util.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - type RestoreOnePageLayoutWidgetFactoryInput, - restoreOnePageLayoutWidgetQueryFactory, -} from 'test/integration/metadata/suites/page-layout-widget/utils/restore-one-page-layout-widget-query-factory.util'; -import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; -import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; -import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; -import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; -import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; - -import { type PageLayoutWidgetDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/page-layout-widget.dto'; - -export const restoreOnePageLayoutWidget = async ({ - input, - gqlFields, - expectToFail = false, - token, -}: PerformMetadataQueryParams): CommonResponseBody<{ - restorePageLayoutWidget: PageLayoutWidgetDTO; -}> => { - const graphqlOperation = restoreOnePageLayoutWidgetQueryFactory({ - input, - gqlFields, - }); - - const response = await makeMetadataAPIRequest(graphqlOperation, token); - - if (expectToFail === true) { - warnIfNoErrorButExpectedToFail({ - response, - errorMessage: 'Page layout widget restore should have failed but did not', - }); - } - - if (expectToFail === false) { - warnIfErrorButNotExpectedToFail({ - response, - errorMessage: 'Page layout widget restore has failed but should not', - }); - } - - return { data: response.body.data, errors: response.body.errors }; -}; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout/__snapshots__/failing-page-layout-deletion.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/page-layout/__snapshots__/failing-page-layout-deletion.integration-spec.ts.snap index e38f9fc46ef..9040e9d08ac 100644 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout/__snapshots__/failing-page-layout-deletion.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/page-layout/__snapshots__/failing-page-layout-deletion.integration-spec.ts.snap @@ -1,12 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Page layout deletion should fail when deleting a non-existent page layout 1`] = ` +exports[`Page layout deletion should fail when destroying a non-existent page layout 1`] = ` { "extensions": { "code": "NOT_FOUND", "userFriendlyMessage": "An error occurred.", }, - "message": "Page layout to delete not found", + "message": "Page layout to destroy not found", "name": "NotFoundError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout/__snapshots__/successful-page-layout-deletion.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/page-layout/__snapshots__/successful-page-layout-deletion.integration-spec.ts.snap deleted file mode 100644 index 57df910bdf8..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout/__snapshots__/successful-page-layout-deletion.integration-spec.ts.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Page layout deletion should succeed should soft delete and restore a page layout 1`] = ` -{ - "createdAt": Any, - "deletedAt": Any, - "id": Any, - "name": "Page Layout To Delete", - "objectMetadataId": null, - "type": "RECORD_PAGE", - "updatedAt": Any, -} -`; - -exports[`Page layout deletion should succeed should soft delete and restore a page layout 2`] = ` -{ - "createdAt": Any, - "deletedAt": null, - "id": Any, - "name": "Page Layout To Delete", - "objectMetadataId": null, - "type": "RECORD_PAGE", - "updatedAt": Any, -} -`; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout/failing-page-layout-deletion.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout/failing-page-layout-deletion.integration-spec.ts index 210ab3d9afe..e1262bd8c3b 100644 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout/failing-page-layout-deletion.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/page-layout/failing-page-layout-deletion.integration-spec.ts @@ -1,10 +1,10 @@ import { faker } from '@faker-js/faker'; import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.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'; describe('Page layout deletion should fail', () => { - it('when deleting a non-existent page layout', async () => { - const { errors } = await deleteOnePageLayout({ + it('when destroying a non-existent page layout', async () => { + const { errors } = await destroyOnePageLayout({ expectToFail: true, input: { id: faker.string.uuid() }, }); diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout/successful-page-layout-deletion.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout/successful-page-layout-deletion.integration-spec.ts index af41bc79e08..d6dbe95bfbf 100644 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout/successful-page-layout-deletion.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/page-layout/successful-page-layout-deletion.integration-spec.ts @@ -1,81 +1,20 @@ 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 { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; -import { - type EachTestingContext, - eachTestingContextFilter, -} from 'twenty-shared/testing'; - -type TestContext = { - name: string; - operation: 'soft-delete-restore' | 'hard-delete'; -}; - -const SUCCESSFUL_TEST_CASES: EachTestingContext[] = [ - { - title: 'soft delete and restore a page layout', - context: { - name: 'Page Layout To Delete', - operation: 'soft-delete-restore', - }, - }, - { - title: 'hard delete a page layout', - context: { - name: 'Page Layout To Destroy', - operation: 'hard-delete', - }, - }, -]; describe('Page layout deletion should succeed', () => { - it.each(eachTestingContextFilter(SUCCESSFUL_TEST_CASES))( - 'should $title', - async ({ context: { name, operation } }) => { - const { data: createData } = await createOnePageLayout({ - expectToFail: false, - input: { name }, - }); + it('should hard delete a page layout', async () => { + const { data: createData } = await createOnePageLayout({ + expectToFail: false, + input: { name: 'Page Layout To Destroy' }, + }); - const pageLayoutId = createData.createPageLayout.id; + const pageLayoutId = createData.createPageLayout.id; - if (operation === 'soft-delete-restore') { - const { data: deleteData } = await deleteOnePageLayout({ - expectToFail: false, - input: { id: pageLayoutId }, - }); + const { data: destroyData } = await destroyOnePageLayout({ + expectToFail: false, + input: { id: pageLayoutId }, + }); - expect(deleteData.deletePageLayout).toMatchSnapshot( - extractRecordIdsAndDatesAsExpectAny({ - ...deleteData.deletePageLayout, - }), - ); - - const { data: restoreData } = await restoreOnePageLayout({ - expectToFail: false, - input: { id: pageLayoutId }, - }); - - expect(restoreData.restorePageLayout).toMatchSnapshot( - extractRecordIdsAndDatesAsExpectAny({ - ...restoreData.restorePageLayout, - }), - ); - - await destroyOnePageLayout({ - expectToFail: false, - input: { id: pageLayoutId }, - }); - } else { - const { data: destroyData } = await destroyOnePageLayout({ - expectToFail: false, - input: { id: pageLayoutId }, - }); - - expect(destroyData.destroyPageLayout).toBe(true); - } - }, - ); + expect(destroyData.destroyPageLayout).toBe(true); + }); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout/utils/delete-one-page-layout-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout/utils/delete-one-page-layout-query-factory.util.ts deleted file mode 100644 index 8685d4bbbcf..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout/utils/delete-one-page-layout-query-factory.util.ts +++ /dev/null @@ -1,32 +0,0 @@ -import gql from 'graphql-tag'; -import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; - -export type DeleteOnePageLayoutFactoryInput = { - id: string; -}; - -const DEFAULT_PAGE_LAYOUT_GQL_FIELDS = ` - id - name - type - objectMetadataId - createdAt - updatedAt - deletedAt -`; - -export const deleteOnePageLayoutQueryFactory = ({ - input, - gqlFields = DEFAULT_PAGE_LAYOUT_GQL_FIELDS, -}: PerformMetadataQueryParams) => ({ - query: gql` - mutation DeletePageLayout($id: String!) { - deletePageLayout(id: $id) { - ${gqlFields} - } - } - `, - variables: { - id: input.id, - }, -}); diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout/utils/delete-one-page-layout.util.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout/utils/delete-one-page-layout.util.ts deleted file mode 100644 index 7ad818249c9..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout/utils/delete-one-page-layout.util.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - type DeleteOnePageLayoutFactoryInput, - deleteOnePageLayoutQueryFactory, -} from 'test/integration/metadata/suites/page-layout/utils/delete-one-page-layout-query-factory.util'; -import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; -import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; -import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; -import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; -import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; - -import { type PageLayoutDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout.dto'; - -export const deleteOnePageLayout = async ({ - input, - gqlFields, - expectToFail = false, - token, -}: PerformMetadataQueryParams): CommonResponseBody<{ - deletePageLayout: PageLayoutDTO; -}> => { - const graphqlOperation = deleteOnePageLayoutQueryFactory({ - input, - gqlFields, - }); - - const response = await makeMetadataAPIRequest(graphqlOperation, token); - - if (expectToFail === true) { - warnIfNoErrorButExpectedToFail({ - response, - errorMessage: 'Page layout deletion should have failed but did not', - }); - } - - if (expectToFail === false) { - warnIfErrorButNotExpectedToFail({ - response, - errorMessage: 'Page layout deletion has failed but should not', - }); - } - - return { data: response.body.data, errors: response.body.errors }; -}; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout/utils/restore-one-page-layout-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout/utils/restore-one-page-layout-query-factory.util.ts deleted file mode 100644 index 97e989e1fed..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout/utils/restore-one-page-layout-query-factory.util.ts +++ /dev/null @@ -1,32 +0,0 @@ -import gql from 'graphql-tag'; -import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; - -export type RestoreOnePageLayoutFactoryInput = { - id: string; -}; - -const DEFAULT_PAGE_LAYOUT_GQL_FIELDS = ` - id - name - type - objectMetadataId - createdAt - updatedAt - deletedAt -`; - -export const restoreOnePageLayoutQueryFactory = ({ - input, - gqlFields = DEFAULT_PAGE_LAYOUT_GQL_FIELDS, -}: PerformMetadataQueryParams) => ({ - query: gql` - mutation RestorePageLayout($id: String!) { - restorePageLayout(id: $id) { - ${gqlFields} - } - } - `, - variables: { - id: input.id, - }, -}); diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout/utils/restore-one-page-layout.util.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout/utils/restore-one-page-layout.util.ts deleted file mode 100644 index c961e8c78fe..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/page-layout/utils/restore-one-page-layout.util.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - type RestoreOnePageLayoutFactoryInput, - restoreOnePageLayoutQueryFactory, -} from 'test/integration/metadata/suites/page-layout/utils/restore-one-page-layout-query-factory.util'; -import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; -import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; -import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; -import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; -import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; - -import { type PageLayoutDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout.dto'; - -export const restoreOnePageLayout = async ({ - input, - gqlFields, - expectToFail = false, - token, -}: PerformMetadataQueryParams): CommonResponseBody<{ - restorePageLayout: PageLayoutDTO; -}> => { - const graphqlOperation = restoreOnePageLayoutQueryFactory({ - input, - gqlFields, - }); - - const response = await makeMetadataAPIRequest(graphqlOperation, token); - - if (expectToFail === true) { - warnIfNoErrorButExpectedToFail({ - response, - errorMessage: 'Page layout restore should have failed but did not', - }); - } - - if (expectToFail === false) { - warnIfErrorButNotExpectedToFail({ - response, - errorMessage: 'Page layout restore has failed but should not', - }); - } - - return { data: response.body.data, errors: response.body.errors }; -};