[DASHBOARDS] Allow dashboards to be restored (#17042)
This PR introduces a few changes: - Add three actions: see deleted dashboards, destroy dashboard and restore dashboard - Remove the soft delete and restore on all the page layout entities - Cascade the destruction of a dashboard to a page layout Video QA: https://github.com/user-attachments/assets/ab993b11-dd9c-4e88-880c-92691a521cc2
This commit is contained in:
@@ -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<ApiKey>;
|
||||
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<Scalars['String']>;
|
||||
queueName: Scalars['String'];
|
||||
|
||||
@@ -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<ApiKey>;
|
||||
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<Scalars['String']>;
|
||||
queueName: Scalars['String'];
|
||||
|
||||
+22
-7
@@ -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,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
-44
@@ -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(),
|
||||
};
|
||||
};
|
||||
-51
@@ -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,
|
||||
};
|
||||
};
|
||||
-41
@@ -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(),
|
||||
};
|
||||
};
|
||||
-51
@@ -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,
|
||||
};
|
||||
};
|
||||
-43
@@ -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(),
|
||||
};
|
||||
};
|
||||
-50
@@ -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,
|
||||
};
|
||||
};
|
||||
+21
-8
@@ -52,10 +52,10 @@ export class PageLayoutTabController {
|
||||
);
|
||||
}
|
||||
|
||||
return this.pageLayoutTabService.findByPageLayoutId(
|
||||
workspace.id,
|
||||
return this.pageLayoutTabService.findByPageLayoutId({
|
||||
workspaceId: workspace.id,
|
||||
pageLayoutId,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@@ -64,7 +64,10 @@ export class PageLayoutTabController {
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO | null> {
|
||||
return this.pageLayoutTabService.findByIdOrThrow(id, workspace.id);
|
||||
return this.pageLayoutTabService.findByIdOrThrow({
|
||||
id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@Post()
|
||||
@@ -73,7 +76,10 @@ export class PageLayoutTabController {
|
||||
@Body() input: CreatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.create(input, workspace.id);
|
||||
return this.pageLayoutTabService.create({
|
||||
createPageLayoutTabInput: input,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@@ -83,7 +89,11 @@ export class PageLayoutTabController {
|
||||
@Body() input: UpdatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.update(id, workspace.id, input);
|
||||
return this.pageLayoutTabService.update({
|
||||
id,
|
||||
workspaceId: workspace.id,
|
||||
updateData: input,
|
||||
});
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@@ -91,7 +101,10 @@ export class PageLayoutTabController {
|
||||
async delete(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.delete(id, workspace.id);
|
||||
): Promise<boolean> {
|
||||
return this.pageLayoutTabService.destroy({
|
||||
id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+19
-30
@@ -7,7 +7,6 @@ import {
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -36,10 +35,10 @@ export class PageLayoutTabResolver {
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args('pageLayoutId', { type: () => String }) pageLayoutId: string,
|
||||
): Promise<PageLayoutTabDTO[]> {
|
||||
return this.pageLayoutTabService.findByPageLayoutId(
|
||||
workspace.id,
|
||||
return this.pageLayoutTabService.findByPageLayoutId({
|
||||
workspaceId: workspace.id,
|
||||
pageLayoutId,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => PageLayoutTabDTO)
|
||||
@@ -48,7 +47,10 @@ export class PageLayoutTabResolver {
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.findByIdOrThrow(id, workspace.id);
|
||||
return this.pageLayoutTabService.findByIdOrThrow({
|
||||
id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutTabDTO)
|
||||
@@ -57,7 +59,10 @@ export class PageLayoutTabResolver {
|
||||
@Args('input') input: CreatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.create(input, workspace.id);
|
||||
return this.pageLayoutTabService.create({
|
||||
createPageLayoutTabInput: input,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutTabDTO)
|
||||
@@ -67,21 +72,11 @@ export class PageLayoutTabResolver {
|
||||
@Args('input') input: UpdatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.update(id, workspace.id, input);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async deletePageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<boolean> {
|
||||
const deletedPageLayoutTab = await this.pageLayoutTabService.delete(
|
||||
return this.pageLayoutTabService.update({
|
||||
id,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
return isDefined(deletedPageLayoutTab);
|
||||
workspaceId: workspace.id,
|
||||
updateData: input,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@@ -90,15 +85,9 @@ export class PageLayoutTabResolver {
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<boolean> {
|
||||
return this.pageLayoutTabService.destroy(id, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutTabDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async restorePageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.restore(id, workspace.id);
|
||||
return this.pageLayoutTabService.destroy({
|
||||
id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+37
-144
@@ -7,9 +7,7 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadat
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
|
||||
import { fromCreatePageLayoutTabInputToFlatPageLayoutTabToCreate } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/from-create-page-layout-tab-input-to-flat-page-layout-tab-to-create.util';
|
||||
import { fromDeletePageLayoutTabInputToFlatPageLayoutTabOrThrow } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/from-delete-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util';
|
||||
import { fromDestroyPageLayoutTabInputToFlatPageLayoutTabOrThrow } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/from-destroy-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util';
|
||||
import { fromRestorePageLayoutTabInputToFlatPageLayoutTabOrThrow } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/from-restore-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util';
|
||||
import {
|
||||
fromUpdatePageLayoutTabInputToFlatPageLayoutTabToUpdateOrThrow,
|
||||
type UpdatePageLayoutTabInputWithId,
|
||||
@@ -40,10 +38,13 @@ export class PageLayoutTabService {
|
||||
private readonly dashboardSyncService: DashboardSyncService,
|
||||
) {}
|
||||
|
||||
async findByPageLayoutId(
|
||||
workspaceId: string,
|
||||
pageLayoutId: string,
|
||||
): Promise<PageLayoutTabDTO[]> {
|
||||
async findByPageLayoutId({
|
||||
workspaceId,
|
||||
pageLayoutId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
pageLayoutId: string;
|
||||
}): Promise<PageLayoutTabDTO[]> {
|
||||
const { flatPageLayoutTabMaps, flatPageLayoutWidgetMaps } =
|
||||
await this.getPageLayoutTabFlatEntityMaps(workspaceId);
|
||||
|
||||
@@ -63,10 +64,13 @@ export class PageLayoutTabService {
|
||||
);
|
||||
}
|
||||
|
||||
async findByIdOrThrow(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
async findByIdOrThrow({
|
||||
id,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}): Promise<PageLayoutTabDTO> {
|
||||
const { flatPageLayoutTabMaps, flatPageLayoutWidgetMaps } =
|
||||
await this.getPageLayoutTabFlatEntityMaps(workspaceId);
|
||||
|
||||
@@ -102,10 +106,13 @@ export class PageLayoutTabService {
|
||||
);
|
||||
}
|
||||
|
||||
async create(
|
||||
createPageLayoutTabInput: CreatePageLayoutTabInput,
|
||||
workspaceId: string,
|
||||
): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
async create({
|
||||
createPageLayoutTabInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
createPageLayoutTabInput: CreatePageLayoutTabInput;
|
||||
workspaceId: string;
|
||||
}): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
if (!isDefined(createPageLayoutTabInput.title)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
@@ -180,11 +187,15 @@ export class PageLayoutTabService {
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(createdTab);
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
updateData: UpdatePageLayoutTabInput,
|
||||
): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
async update({
|
||||
id,
|
||||
workspaceId,
|
||||
updateData,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
updateData: UpdatePageLayoutTabInput;
|
||||
}): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
@@ -248,69 +259,13 @@ export class PageLayoutTabService {
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(updatedTab);
|
||||
}
|
||||
|
||||
async delete(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatPageLayoutTabToDelete =
|
||||
fromDeletePageLayoutTabInputToFlatPageLayoutTabOrThrow({
|
||||
deletePageLayoutTabInput: { id },
|
||||
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [flatPageLayoutTabToDelete],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while deleting page layout tab',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatPageLayoutTabMaps: recomputedFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const deletedTab = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByTabId({
|
||||
tabId: id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(deletedTab.updatedAt),
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(deletedTab);
|
||||
}
|
||||
|
||||
async destroy(id: string, workspaceId: string): Promise<boolean> {
|
||||
async destroy({
|
||||
id,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}): Promise<boolean> {
|
||||
const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
@@ -355,66 +310,4 @@ export class PageLayoutTabService {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async restore(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatPageLayoutTabToRestore =
|
||||
fromRestorePageLayoutTabInputToFlatPageLayoutTabOrThrow({
|
||||
restorePageLayoutTabInput: { id },
|
||||
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [flatPageLayoutTabToRestore],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while restoring page layout tab',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatPageLayoutTabMaps: recomputedFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const restoredTab = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
await this.dashboardSyncService.updateLinkedDashboardsUpdatedAtByTabId({
|
||||
tabId: id,
|
||||
workspaceId,
|
||||
updatedAt: new Date(restoredTab.updatedAt),
|
||||
});
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(restoredTab);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-8
@@ -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<PageLayoutWidgetDTO | null> {
|
||||
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<PageLayoutWidgetDTO> {
|
||||
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<PageLayoutWidgetDTO> {
|
||||
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<PageLayoutWidgetDTO> {
|
||||
return this.pageLayoutWidgetService.delete(id, workspace.id);
|
||||
): Promise<boolean> {
|
||||
return this.pageLayoutWidgetService.destroy({
|
||||
id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+20
-25
@@ -45,10 +45,10 @@ export class PageLayoutWidgetResolver {
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args('pageLayoutTabId', { type: () => String }) pageLayoutTabId: string,
|
||||
): Promise<PageLayoutWidgetDTO[]> {
|
||||
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<PageLayoutWidgetDTO> {
|
||||
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<PageLayoutWidgetDTO> {
|
||||
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<PageLayoutWidgetDTO> {
|
||||
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<PageLayoutWidgetDTO> {
|
||||
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<boolean> {
|
||||
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<PageLayoutWidgetDTO> {
|
||||
return this.pageLayoutWidgetService.restore(id, workspace.id);
|
||||
return this.pageLayoutWidgetService.destroy({
|
||||
id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@ResolveField(() => WidgetConfiguration, { nullable: true })
|
||||
|
||||
+38
-95
@@ -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<PageLayoutWidgetDTO[]> {
|
||||
async findByPageLayoutTabId({
|
||||
workspaceId,
|
||||
pageLayoutTabId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
pageLayoutTabId: string;
|
||||
}): Promise<PageLayoutWidgetDTO[]> {
|
||||
const flatPageLayoutWidgetMaps =
|
||||
await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
@@ -109,10 +110,13 @@ export class PageLayoutWidgetService {
|
||||
.map(fromFlatPageLayoutWidgetToPageLayoutWidgetDto);
|
||||
}
|
||||
|
||||
async findByIdOrThrow(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
async findByIdOrThrow({
|
||||
id,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}): Promise<PageLayoutWidgetDTO> {
|
||||
const flatPageLayoutWidgetMaps =
|
||||
await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
@@ -131,10 +135,13 @@ export class PageLayoutWidgetService {
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(flatWidget);
|
||||
}
|
||||
|
||||
async create(
|
||||
createPageLayoutWidgetInput: CreatePageLayoutWidgetInput,
|
||||
workspaceId: string,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
async create({
|
||||
input,
|
||||
workspaceId,
|
||||
}: {
|
||||
input: CreatePageLayoutWidgetInput;
|
||||
workspaceId: string;
|
||||
}): Promise<PageLayoutWidgetDTO> {
|
||||
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<PageLayoutWidgetDTO> {
|
||||
async update({
|
||||
id,
|
||||
workspaceId,
|
||||
updateData,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
updateData: UpdatePageLayoutWidgetInput;
|
||||
}): Promise<PageLayoutWidgetDTO> {
|
||||
const existingFlatPageLayoutWidgetMaps =
|
||||
await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
@@ -252,44 +263,13 @@ export class PageLayoutWidgetService {
|
||||
return existingWidget;
|
||||
}
|
||||
|
||||
async delete(id: string, workspaceId: string): Promise<PageLayoutWidgetDTO> {
|
||||
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<boolean> {
|
||||
async destroy({
|
||||
id,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}): Promise<boolean> {
|
||||
const existingFlatPageLayoutWidgetMaps =
|
||||
await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
@@ -318,41 +298,4 @@ export class PageLayoutWidgetService {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async restore(id: string, workspaceId: string): Promise<PageLayoutWidgetDTO> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+20
-16
@@ -38,10 +38,10 @@ export class PageLayoutController {
|
||||
@Query('objectMetadataId') objectMetadataId?: string,
|
||||
): Promise<PageLayoutDTO[]> {
|
||||
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<PageLayoutDTO | null> {
|
||||
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<PageLayoutDTO> {
|
||||
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<PageLayoutDTO> {
|
||||
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<PageLayoutDTO> {
|
||||
const deletedPageLayout = await this.pageLayoutService.delete(
|
||||
): Promise<boolean> {
|
||||
return this.pageLayoutService.destroy({
|
||||
id,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
return deletedPageLayout;
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
|
||||
+18
-34
@@ -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<PageLayoutDTO[]> {
|
||||
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<PageLayoutDTO | null> {
|
||||
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<PageLayoutDTO> {
|
||||
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<PageLayoutDTO> {
|
||||
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<PageLayoutDTO> {
|
||||
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<boolean> {
|
||||
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<PageLayoutDTO> {
|
||||
return this.pageLayoutService.restore(id, workspace.id);
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutDTO)
|
||||
|
||||
+78
-183
@@ -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<PageLayoutDTO[]> {
|
||||
async findByObjectMetadataId({
|
||||
workspaceId,
|
||||
objectMetadataId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
objectMetadataId: string;
|
||||
}): Promise<PageLayoutDTO[]> {
|
||||
const {
|
||||
flatPageLayoutMaps,
|
||||
flatPageLayoutTabMaps,
|
||||
@@ -99,10 +98,13 @@ export class PageLayoutService {
|
||||
);
|
||||
}
|
||||
|
||||
async findByIdOrThrow(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<PageLayoutDTO> {
|
||||
async findByIdOrThrow({
|
||||
id,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}): Promise<PageLayoutDTO> {
|
||||
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<Omit<PageLayoutDTO, 'tabs'>> {
|
||||
async create({
|
||||
createPageLayoutInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
createPageLayoutInput: CreatePageLayoutInput;
|
||||
workspaceId: string;
|
||||
}): Promise<Omit<PageLayoutDTO, 'tabs'>> {
|
||||
if (!isNonEmptyString(createPageLayoutInput.name)) {
|
||||
throw new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
@@ -210,11 +218,15 @@ export class PageLayoutService {
|
||||
);
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
updateData: UpdatePageLayoutInput,
|
||||
): Promise<Omit<PageLayoutDTO, 'tabs'>> {
|
||||
async update({
|
||||
id,
|
||||
workspaceId,
|
||||
updateData,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
updateData: UpdatePageLayoutInput;
|
||||
}): Promise<Omit<PageLayoutDTO, 'tabs'>> {
|
||||
const { flatPageLayoutMaps: existingFlatPageLayoutMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
@@ -280,74 +292,15 @@ export class PageLayoutService {
|
||||
return fromFlatPageLayoutToPageLayoutDto(updatedLayout);
|
||||
}
|
||||
|
||||
async delete(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<Omit<PageLayoutDTO, 'tabs'>> {
|
||||
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<Omit<PageLayoutDTO, 'tabs'>> {
|
||||
async destroy({
|
||||
id,
|
||||
workspaceId,
|
||||
isLinkedDashboardAlreadyDestroyed = false,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
isLinkedDashboardAlreadyDestroyed?: boolean;
|
||||
}): Promise<boolean> {
|
||||
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<void> {
|
||||
private async destroyAssociatedDashboards({
|
||||
pageLayoutId,
|
||||
workspaceId,
|
||||
}: {
|
||||
pageLayoutId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
try {
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const dashboardRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
'dashboard',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
await 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<Omit<PageLayoutDTO, 'tabs'>> {
|
||||
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);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,89 +79,6 @@ export class DashboardSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
async softDeleteLinkedDashboardsByPageLayoutId({
|
||||
pageLayoutId,
|
||||
workspaceId,
|
||||
deletedAt,
|
||||
}: {
|
||||
pageLayoutId: string;
|
||||
workspaceId: string;
|
||||
deletedAt: Date;
|
||||
}): Promise<void> {
|
||||
const isDashboard = await this.isPageLayoutOfTypeDashboard({
|
||||
pageLayoutId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!isDashboard) {
|
||||
return;
|
||||
}
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
try {
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const dashboardRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
'dashboard',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
await dashboardRepository.update({ pageLayoutId }, { deletedAt });
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to soft delete dashboards for page layout ${pageLayoutId}: ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async restoreLinkedDashboardsByPageLayoutId({
|
||||
pageLayoutId,
|
||||
workspaceId,
|
||||
}: {
|
||||
pageLayoutId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<void> {
|
||||
const isDashboard = await this.isPageLayoutOfTypeDashboard({
|
||||
pageLayoutId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!isDashboard) {
|
||||
return;
|
||||
}
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
try {
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const dashboardRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
'dashboard',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
await dashboardRepository.update(
|
||||
{ pageLayoutId },
|
||||
{ deletedAt: null },
|
||||
);
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to restore dashboards for page layout ${pageLayoutId}: ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async updateLinkedDashboardsUpdatedAtByTabId({
|
||||
tabId,
|
||||
workspaceId,
|
||||
|
||||
+49
@@ -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<DashboardWorkspaceEntity>,
|
||||
): Promise<CreateManyResolverArgs<DashboardWorkspaceEntity>> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
+7
-22
@@ -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;
|
||||
}
|
||||
|
||||
+40
@@ -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<DestroyManyResolverArgs<{ id: { in: string[] } }>> {
|
||||
const workspace = authContext.workspace;
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
await this.dashboardToPageLayoutSyncService.destroyPageLayoutsForDashboards(
|
||||
{
|
||||
dashboardIds: payload.filter.id.in,
|
||||
workspaceId: workspace.id,
|
||||
},
|
||||
);
|
||||
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
+40
@@ -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<DestroyOneResolverArgs> {
|
||||
const workspace = authContext.workspace;
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
await this.dashboardToPageLayoutSyncService.destroyPageLayoutsForDashboards(
|
||||
{
|
||||
dashboardIds: [payload.id],
|
||||
workspaceId: workspace.id,
|
||||
},
|
||||
);
|
||||
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
+13
-2
@@ -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 {}
|
||||
|
||||
+86
@@ -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<string> {
|
||||
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<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const dashboardRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<DashboardWorkspaceEntity>(
|
||||
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,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
+19
-12
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => ({
|
||||
|
||||
@@ -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,
|
||||
|
||||
+2
-44
@@ -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<TestContext>[] = [
|
||||
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 }),
|
||||
|
||||
+168
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+118
@@ -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 = [];
|
||||
});
|
||||
});
|
||||
});
|
||||
-147
@@ -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<TestContext> => {
|
||||
};
|
||||
|
||||
const cleanupTestContext = async (context: TestContext): Promise<void> => {
|
||||
await destroyOnePageLayoutWidget({
|
||||
expectToFail: false,
|
||||
input: { id: context.widgetId },
|
||||
});
|
||||
|
||||
await destroyOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: { id: context.tabId },
|
||||
});
|
||||
|
||||
await destroyDashboardWithGraphQL(context.dashboardId);
|
||||
|
||||
await destroyOnePageLayout({
|
||||
expectToFail: false,
|
||||
input: { id: context.pageLayoutId },
|
||||
});
|
||||
};
|
||||
|
||||
const assertDashboardUpdatedAtIncreased = async (
|
||||
@@ -124,43 +103,6 @@ const assertDashboardUpdatedAtIncreased = async (
|
||||
expect(isIncreased).toBe(true);
|
||||
};
|
||||
|
||||
const assertDashboardSoftDeleted = async (
|
||||
dashboardId: string,
|
||||
operation: () => Promise<void>,
|
||||
): Promise<void> => {
|
||||
const dashboardBefore = await findDashboardWithGraphQL(dashboardId);
|
||||
|
||||
expect(dashboardBefore).not.toBeNull();
|
||||
|
||||
await operation();
|
||||
|
||||
const dashboardAfter = await findDashboardWithGraphQL(dashboardId);
|
||||
|
||||
expect(dashboardAfter).toBeNull();
|
||||
};
|
||||
|
||||
const assertDashboardRestored = async (
|
||||
dashboardId: string,
|
||||
operation: () => Promise<void>,
|
||||
): Promise<void> => {
|
||||
const dashboardBefore = await findDashboardWithGraphQL(dashboardId);
|
||||
|
||||
expect(dashboardBefore).toBeNull();
|
||||
|
||||
await operation();
|
||||
|
||||
const dashboardAfter = await findDashboardWithGraphQL(dashboardId);
|
||||
|
||||
expect(dashboardAfter).not.toBeNull();
|
||||
|
||||
const updatedAtAfter = new Date(dashboardAfter!.updatedAt);
|
||||
|
||||
const now = new Date();
|
||||
const timeDiff = now.getTime() - updatedAtAfter.getTime();
|
||||
|
||||
expect(timeDiff).toBeLessThan(5000);
|
||||
};
|
||||
|
||||
describe('Dashboard updatedAt should sync when linked page layout entities change', () => {
|
||||
describe('Widget operations', () => {
|
||||
let context: TestContext;
|
||||
@@ -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', () => {
|
||||
+266
-3
@@ -10,10 +10,34 @@ interface CreateDashboardResponse extends Record<string, unknown> {
|
||||
createDashboard: DashboardWorkspaceEntity;
|
||||
}
|
||||
|
||||
interface CreateManyDashboardsResponse extends Record<string, unknown> {
|
||||
createDashboards: DashboardWorkspaceEntity[];
|
||||
}
|
||||
|
||||
interface FindDashboardResponse extends Record<string, unknown> {
|
||||
dashboard: DashboardWorkspaceEntity | null;
|
||||
}
|
||||
|
||||
interface DeleteDashboardResponse extends Record<string, unknown> {
|
||||
deleteDashboard: DashboardWorkspaceEntity;
|
||||
}
|
||||
|
||||
interface DeleteManyDashboardsResponse extends Record<string, unknown> {
|
||||
deleteDashboards: DashboardWorkspaceEntity[];
|
||||
}
|
||||
|
||||
interface RestoreDashboardResponse extends Record<string, unknown> {
|
||||
restoreDashboard: DashboardWorkspaceEntity;
|
||||
}
|
||||
|
||||
interface RestoreManyDashboardsResponse extends Record<string, unknown> {
|
||||
restoreDashboards: DashboardWorkspaceEntity[];
|
||||
}
|
||||
|
||||
interface DestroyManyDashboardsResponse extends Record<string, unknown> {
|
||||
destroyDashboards: DashboardWorkspaceEntity[];
|
||||
}
|
||||
|
||||
export const createTestDashboardWithGraphQL = async (data: {
|
||||
id?: string;
|
||||
title: string;
|
||||
@@ -87,20 +111,259 @@ export const destroyDashboardWithGraphQL = async (
|
||||
): Promise<void> => {
|
||||
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<DashboardWorkspaceEntity[]> => {
|
||||
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<CreateManyDashboardsResponse>;
|
||||
|
||||
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<DashboardWorkspaceEntity> => {
|
||||
const operation = {
|
||||
query: gql`
|
||||
mutation DeleteDashboard($dashboardId: UUID!) {
|
||||
deleteDashboard(id: $dashboardId) {
|
||||
${DASHBOARD_GQL_FIELDS}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
dashboardId,
|
||||
},
|
||||
};
|
||||
|
||||
const response = (await makeGraphqlAPIRequest(
|
||||
operation,
|
||||
)) as GraphQLResponse<DeleteDashboardResponse>;
|
||||
|
||||
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<DashboardWorkspaceEntity[]> => {
|
||||
const operation = {
|
||||
query: gql`
|
||||
mutation DeleteDashboards($filter: DashboardFilterInput!) {
|
||||
deleteDashboards(filter: $filter) {
|
||||
${DASHBOARD_GQL_FIELDS}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
filter,
|
||||
},
|
||||
};
|
||||
|
||||
const response = (await makeGraphqlAPIRequest(
|
||||
operation,
|
||||
)) as GraphQLResponse<DeleteManyDashboardsResponse>;
|
||||
|
||||
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<DashboardWorkspaceEntity> => {
|
||||
const operation = {
|
||||
query: gql`
|
||||
mutation RestoreDashboard($dashboardId: UUID!) {
|
||||
restoreDashboard(id: $dashboardId) {
|
||||
${DASHBOARD_GQL_FIELDS}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
dashboardId,
|
||||
},
|
||||
};
|
||||
|
||||
const response = (await makeGraphqlAPIRequest(
|
||||
operation,
|
||||
)) as GraphQLResponse<RestoreDashboardResponse>;
|
||||
|
||||
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<DashboardWorkspaceEntity[]> => {
|
||||
const operation = {
|
||||
query: gql`
|
||||
mutation RestoreDashboards($filter: DashboardFilterInput!) {
|
||||
restoreDashboards(filter: $filter) {
|
||||
${DASHBOARD_GQL_FIELDS}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
filter,
|
||||
},
|
||||
};
|
||||
|
||||
const response = (await makeGraphqlAPIRequest(
|
||||
operation,
|
||||
)) as GraphQLResponse<RestoreManyDashboardsResponse>;
|
||||
|
||||
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<DashboardWorkspaceEntity[]> => {
|
||||
const operation = {
|
||||
query: gql`
|
||||
mutation DestroyDashboards($filter: DashboardFilterInput!) {
|
||||
destroyDashboards(filter: $filter) {
|
||||
${DASHBOARD_GQL_FIELDS}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
filter,
|
||||
},
|
||||
};
|
||||
|
||||
const response = (await makeGraphqlAPIRequest(
|
||||
operation,
|
||||
)) as GraphQLResponse<DestroyManyDashboardsResponse>;
|
||||
|
||||
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<DashboardWorkspaceEntity | null> => {
|
||||
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<FindDashboardResponse>;
|
||||
|
||||
if (response.body.errors) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.body.data?.dashboard ?? null;
|
||||
};
|
||||
|
||||
const TEST_SCHEMA_NAME = 'workspace_1wgvd1injqtife6y4rvfbu3h5';
|
||||
|
||||
export const cleanupDashboardRecords = async (): Promise<void> => {
|
||||
|
||||
-12
@@ -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",
|
||||
}
|
||||
`;
|
||||
-13
@@ -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<String>,
|
||||
"deletedAt": null,
|
||||
"id": Any<String>,
|
||||
"pageLayoutId": Any<String>,
|
||||
"position": 0,
|
||||
"title": "Tab To Delete",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
-14
@@ -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 });
|
||||
});
|
||||
});
|
||||
+15
-72
@@ -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<TestContext>[] = [
|
||||
{
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
-19
@@ -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<DeleteOnePageLayoutTabFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation DeletePageLayoutTab($id: String!) {
|
||||
deletePageLayoutTab(id: $id)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: input.id,
|
||||
},
|
||||
});
|
||||
-39
@@ -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<DeleteOnePageLayoutTabFactoryInput>): 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 };
|
||||
};
|
||||
-32
@@ -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<RestoreOnePageLayoutTabFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation RestorePageLayoutTab($id: String!) {
|
||||
restorePageLayoutTab(id: $id) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: input.id,
|
||||
},
|
||||
});
|
||||
-43
@@ -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<RestoreOnePageLayoutTabFactoryInput>): 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 };
|
||||
};
|
||||
-12
@@ -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",
|
||||
}
|
||||
`;
|
||||
-47
@@ -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<String>,
|
||||
"deletedAt": Any<String>,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Widget To Delete",
|
||||
"type": "IFRAME",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout widget deletion should succeed should soft delete and restore a page layout widget 2`] = `
|
||||
{
|
||||
"configuration": {
|
||||
"configurationType": "IFRAME",
|
||||
"url": null,
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 0,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": null,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Widget To Delete",
|
||||
"type": "IFRAME",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
-14
@@ -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 });
|
||||
});
|
||||
});
|
||||
+24
-85
@@ -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<TestContext>[] = [
|
||||
{
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
-43
@@ -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<DeleteOnePageLayoutWidgetFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation DeletePageLayoutWidget($id: String!) {
|
||||
deletePageLayoutWidget(id: $id) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: input.id,
|
||||
},
|
||||
});
|
||||
-44
@@ -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<DeleteOnePageLayoutWidgetFactoryInput>): 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 };
|
||||
};
|
||||
-43
@@ -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<RestoreOnePageLayoutWidgetFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation RestorePageLayoutWidget($id: String!) {
|
||||
restorePageLayoutWidget(id: $id) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: input.id,
|
||||
},
|
||||
});
|
||||
-43
@@ -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<RestoreOnePageLayoutWidgetFactoryInput>): 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 };
|
||||
};
|
||||
+2
-2
@@ -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",
|
||||
}
|
||||
`;
|
||||
|
||||
-25
@@ -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<String>,
|
||||
"deletedAt": Any<String>,
|
||||
"id": Any<String>,
|
||||
"name": "Page Layout To Delete",
|
||||
"objectMetadataId": null,
|
||||
"type": "RECORD_PAGE",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Page layout deletion should succeed should soft delete and restore a page layout 2`] = `
|
||||
{
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"id": Any<String>,
|
||||
"name": "Page Layout To Delete",
|
||||
"objectMetadataId": null,
|
||||
"type": "RECORD_PAGE",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
+3
-3
@@ -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() },
|
||||
});
|
||||
|
||||
+12
-73
@@ -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<TestContext>[] = [
|
||||
{
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
-32
@@ -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<DeleteOnePageLayoutFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation DeletePageLayout($id: String!) {
|
||||
deletePageLayout(id: $id) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: input.id,
|
||||
},
|
||||
});
|
||||
-43
@@ -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<DeleteOnePageLayoutFactoryInput>): 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 };
|
||||
};
|
||||
-32
@@ -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<RestoreOnePageLayoutFactoryInput>) => ({
|
||||
query: gql`
|
||||
mutation RestorePageLayout($id: String!) {
|
||||
restorePageLayout(id: $id) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: input.id,
|
||||
},
|
||||
});
|
||||
-43
@@ -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<RestoreOnePageLayoutFactoryInput>): 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 };
|
||||
};
|
||||
Reference in New Issue
Block a user