diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/build-flat-page-layout-widget-common-properties.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/build-flat-page-layout-widget-common-properties.util.ts new file mode 100644 index 0000000000..ef98b292fb --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/build-flat-page-layout-widget-common-properties.util.ts @@ -0,0 +1,56 @@ +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; +import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; +import { type CreatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/create-page-layout-widget.input'; + +export const buildFlatPageLayoutWidgetCommonProperties = ({ + widgetInput, + flatPageLayoutTabMaps, + flatObjectMetadataMaps, +}: { + widgetInput: Pick< + CreatePageLayoutWidgetInput, + | 'pageLayoutTabId' + | 'title' + | 'type' + | 'objectMetadataId' + | 'gridPosition' + | 'position' + >; +} & Pick< + AllFlatEntityMaps, + 'flatPageLayoutTabMaps' | 'flatObjectMetadataMaps' +>): Pick< + FlatPageLayoutWidget, + | 'pageLayoutTabId' + | 'pageLayoutTabUniversalIdentifier' + | 'title' + | 'type' + | 'objectMetadataId' + | 'objectMetadataUniversalIdentifier' + | 'gridPosition' + | 'position' +> => { + const { + pageLayoutTabUniversalIdentifier, + objectMetadataUniversalIdentifier, + } = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'pageLayoutWidget', + foreignKeyValues: { + pageLayoutTabId: widgetInput.pageLayoutTabId, + objectMetadataId: widgetInput.objectMetadataId, + }, + flatEntityMaps: { flatPageLayoutTabMaps, flatObjectMetadataMaps }, + }); + + return { + pageLayoutTabId: widgetInput.pageLayoutTabId, + pageLayoutTabUniversalIdentifier, + title: widgetInput.title, + type: widgetInput.type, + objectMetadataId: widgetInput.objectMetadataId ?? null, + objectMetadataUniversalIdentifier, + gridPosition: widgetInput.gridPosition, + position: widgetInput.position ?? null, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-create-page-layout-widget-input-to-flat-page-layout-widget-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-create-page-layout-widget-input-to-flat-page-layout-widget-to-create.util.ts index 1d1a4fe87a..0a9bc725db 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-create-page-layout-widget-input-to-flat-page-layout-widget-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-create-page-layout-widget-input-to-flat-page-layout-widget-to-create.util.ts @@ -3,8 +3,8 @@ import { v4 } from 'uuid'; import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; -import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; +import { buildFlatPageLayoutWidgetCommonProperties } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/build-flat-page-layout-widget-common-properties.util'; import { fromPageLayoutWidgetConfigurationToUniversalConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util'; import { type CreatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/create-page-layout-widget.input'; import { validateWidgetConfigurationInput } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-configuration-input.util'; @@ -48,33 +48,27 @@ export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({ const createdAt = new Date().toISOString(); const pageLayoutWidgetId = v4(); - const { - pageLayoutTabUniversalIdentifier, - objectMetadataUniversalIdentifier, - } = resolveEntityRelationUniversalIdentifiers({ - metadataName: 'pageLayoutWidget', - foreignKeyValues: { + const commonProperties = buildFlatPageLayoutWidgetCommonProperties({ + widgetInput: { pageLayoutTabId, + title: createPageLayoutWidgetInput.title, + type: createPageLayoutWidgetInput.type, objectMetadataId: createPageLayoutWidgetInput.objectMetadataId, + gridPosition: createPageLayoutWidgetInput.gridPosition, + position: createPageLayoutWidgetInput.position, }, - flatEntityMaps: { flatPageLayoutTabMaps, flatObjectMetadataMaps }, + flatPageLayoutTabMaps, + flatObjectMetadataMaps, }); return { id: pageLayoutWidgetId, - pageLayoutTabId, - pageLayoutTabUniversalIdentifier, + ...commonProperties, workspaceId, createdAt, updatedAt: createdAt, deletedAt: null, universalIdentifier: pageLayoutWidgetId, - title: createPageLayoutWidgetInput.title, - type: createPageLayoutWidgetInput.type, - objectMetadataId: createPageLayoutWidgetInput.objectMetadataId ?? null, - objectMetadataUniversalIdentifier, - gridPosition: createPageLayoutWidgetInput.gridPosition, - position: createPageLayoutWidgetInput.position ?? null, configuration: createPageLayoutWidgetInput.configuration, applicationId: flatApplication.id, applicationUniversalIdentifier: flatApplication.universalIdentifier, diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts index 695396cf85..53cb769b05 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts @@ -10,12 +10,12 @@ import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; -import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; import { FLAT_PAGE_LAYOUT_TAB_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-tab/constants/flat-page-layout-tab-editable-properties.constant'; 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 { FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-widget/constants/flat-page-layout-widget-editable-properties.constant'; import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type'; +import { buildFlatPageLayoutWidgetCommonProperties } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/build-flat-page-layout-widget-common-properties.util'; import { fromPageLayoutWidgetConfigurationToUniversalConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util'; import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type'; import { reconstructFlatPageLayoutWithTabsAndWidgets } from 'src/engine/metadata-modules/flat-page-layout/utils/reconstruct-flat-page-layout-with-tabs-and-widgets.util'; @@ -476,32 +476,18 @@ export class PageLayoutUpdateService { }); const now = new Date(); + const widgetsToCreate: FlatPageLayoutWidget[] = entitiesToCreate.map( (widgetInput) => { const widgetId = widgetInput.id ?? v4(); - const { - pageLayoutTabUniversalIdentifier, - objectMetadataUniversalIdentifier, - } = resolveEntityRelationUniversalIdentifiers({ - metadataName: 'pageLayoutWidget', - foreignKeyValues: { - pageLayoutTabId: widgetInput.pageLayoutTabId, - objectMetadataId: widgetInput.objectMetadataId, - }, - flatEntityMaps: { flatPageLayoutTabMaps, flatObjectMetadataMaps }, - }); - return { id: widgetId, - pageLayoutTabId: widgetInput.pageLayoutTabId, - pageLayoutTabUniversalIdentifier, - title: widgetInput.title, - type: widgetInput.type, - objectMetadataId: widgetInput.objectMetadataId ?? null, - objectMetadataUniversalIdentifier, - gridPosition: widgetInput.gridPosition, - position: widgetInput.position ?? null, + ...buildFlatPageLayoutWidgetCommonProperties({ + widgetInput, + flatPageLayoutTabMaps, + flatObjectMetadataMaps, + }), configuration: widgetInput.configuration, workspaceId, createdAt: now.toISOString(), @@ -536,28 +522,13 @@ export class PageLayoutUpdateService { const updatedConfiguration = widgetInput.configuration ?? null; - const { - pageLayoutTabUniversalIdentifier, - objectMetadataUniversalIdentifier, - } = resolveEntityRelationUniversalIdentifiers({ - metadataName: 'pageLayoutWidget', - foreignKeyValues: { - pageLayoutTabId: widgetInput.pageLayoutTabId, - objectMetadataId: widgetInput.objectMetadataId, - }, - flatEntityMaps: { flatPageLayoutTabMaps, flatObjectMetadataMaps }, - }); - return { ...existingWidget, - pageLayoutTabId: widgetInput.pageLayoutTabId, - pageLayoutTabUniversalIdentifier, - title: widgetInput.title, - type: widgetInput.type, - objectMetadataId: widgetInput.objectMetadataId ?? null, - objectMetadataUniversalIdentifier, - gridPosition: widgetInput.gridPosition, - position: widgetInput.position ?? null, + ...buildFlatPageLayoutWidgetCommonProperties({ + widgetInput, + flatPageLayoutTabMaps, + flatObjectMetadataMaps, + }), configuration: updatedConfiguration, updatedAt: now.toISOString(), ...(isDefined(updatedConfiguration) && { @@ -587,28 +558,13 @@ export class PageLayoutUpdateService { const restoredConfiguration = widgetInput.configuration ?? null; - const { - pageLayoutTabUniversalIdentifier, - objectMetadataUniversalIdentifier, - } = resolveEntityRelationUniversalIdentifiers({ - metadataName: 'pageLayoutWidget', - foreignKeyValues: { - pageLayoutTabId: widgetInput.pageLayoutTabId, - objectMetadataId: widgetInput.objectMetadataId, - }, - flatEntityMaps: { flatPageLayoutTabMaps, flatObjectMetadataMaps }, - }); - return { ...existingWidget, - pageLayoutTabId: widgetInput.pageLayoutTabId, - pageLayoutTabUniversalIdentifier, - title: widgetInput.title, - type: widgetInput.type, - objectMetadataId: widgetInput.objectMetadataId ?? null, - objectMetadataUniversalIdentifier, - gridPosition: widgetInput.gridPosition, - position: widgetInput.position ?? null, + ...buildFlatPageLayoutWidgetCommonProperties({ + widgetInput, + flatPageLayoutTabMaps, + flatObjectMetadataMaps, + }), configuration: restoredConfiguration, deletedAt: null, updatedAt: now.toISOString(), diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout/__snapshots__/successful-page-layout-widget-restore-via-update.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/page-layout/__snapshots__/successful-page-layout-widget-restore-via-update.integration-spec.ts.snap new file mode 100644 index 0000000000..1fe417ac64 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/page-layout/__snapshots__/successful-page-layout-widget-restore-via-update.integration-spec.ts.snap @@ -0,0 +1,159 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`Page layout widget restore via bulk update should succeed should create, delete, and restore a widget via bulk update 1`] = ` +{ + "createdAt": Any, + "defaultTabToFocusOnMobileAndSidePanelId": null, + "deletedAt": null, + "id": Any, + "name": "Test Layout For Widget Restore", + "objectMetadataId": null, + "tabs": [ + { + "applicationId": Any, + "createdAt": Any, + "deletedAt": null, + "icon": null, + "id": Any, + "layoutMode": "GRID", + "pageLayoutId": Any, + "position": 0, + "title": "Test Tab For Widget Restore", + "updatedAt": Any, + "widgets": [ + { + "configuration": { + "aggregateFieldMetadataId": Any, + "aggregateOperation": "SUM", + "configurationType": "AGGREGATE_CHART", + "description": null, + "displayDataLabel": true, + "filter": null, + "firstDayOfTheWeek": 1, + "format": null, + "label": null, + "prefix": null, + "suffix": null, + "timezone": "UTC", + }, + "createdAt": Any, + "deletedAt": null, + "gridPosition": { + "column": 0, + "columnSpan": 1, + "row": 0, + "rowSpan": 1, + }, + "id": Any, + "objectMetadataId": Any, + "pageLayoutTabId": Any, + "title": "Restored Graph Widget", + "type": "GRAPH", + "updatedAt": Any, + }, + ], + }, + ], + "type": "RECORD_PAGE", + "updatedAt": Any, +} +`; + +exports[`Page layout widget restore via bulk update should succeed should handle mixed create, update, and restore in a single call 1`] = ` +{ + "createdAt": Any, + "defaultTabToFocusOnMobileAndSidePanelId": null, + "deletedAt": null, + "id": Any, + "name": "Test Layout For Widget Restore", + "objectMetadataId": null, + "tabs": [ + { + "applicationId": Any, + "createdAt": Any, + "deletedAt": null, + "icon": null, + "id": Any, + "layoutMode": "GRID", + "pageLayoutId": Any, + "position": 0, + "title": "Test Tab For Widget Restore", + "updatedAt": Any, + "widgets": [ + { + "configuration": { + "configurationType": "IFRAME", + "url": "https://new.example.com", + }, + "createdAt": Any, + "deletedAt": null, + "gridPosition": { + "column": 0, + "columnSpan": 1, + "row": 2, + "rowSpan": 1, + }, + "id": Any, + "objectMetadataId": null, + "pageLayoutTabId": Any, + "title": "Brand New Widget", + "type": "IFRAME", + "updatedAt": Any, + }, + { + "configuration": { + "configurationType": "IFRAME", + "url": "https://updated.example.com", + }, + "createdAt": Any, + "deletedAt": null, + "gridPosition": { + "column": 0, + "columnSpan": 2, + "row": 0, + "rowSpan": 2, + }, + "id": Any, + "objectMetadataId": null, + "pageLayoutTabId": Any, + "title": "Updated Widget Title", + "type": "IFRAME", + "updatedAt": Any, + }, + { + "configuration": { + "aggregateFieldMetadataId": Any, + "aggregateOperation": "SUM", + "configurationType": "AGGREGATE_CHART", + "description": null, + "displayDataLabel": true, + "filter": null, + "firstDayOfTheWeek": 1, + "format": null, + "label": null, + "prefix": null, + "suffix": null, + "timezone": "UTC", + }, + "createdAt": Any, + "deletedAt": null, + "gridPosition": { + "column": 0, + "columnSpan": 1, + "row": 1, + "rowSpan": 1, + }, + "id": Any, + "objectMetadataId": Any, + "pageLayoutTabId": Any, + "title": "Restored Widget", + "type": "GRAPH", + "updatedAt": Any, + }, + ], + }, + ], + "type": "RECORD_PAGE", + "updatedAt": Any, +} +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/page-layout/successful-page-layout-widget-restore-via-update.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/page-layout/successful-page-layout-widget-restore-via-update.integration-spec.ts new file mode 100644 index 0000000000..36467a0ac4 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/page-layout/successful-page-layout-widget-restore-via-update.integration-spec.ts @@ -0,0 +1,395 @@ +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 { fetchTestFieldMetadataIds } from 'test/integration/metadata/suites/page-layout-widget/utils/fetch-test-field-metadata-ids.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 { updateOnePageLayoutWithTabsAndWidgets } from 'test/integration/metadata/suites/page-layout/utils/update-one-page-layout-with-tabs-and-widgets.util'; +import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; +import { AggregateOperations } from 'twenty-shared/types'; +import { v4 } from 'uuid'; + +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'; +import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum'; + +describe('Page layout widget restore via bulk update should succeed', () => { + let testFieldMetadataIds: { + objectMetadataId: string; + fieldMetadataId1: string; + fieldMetadataId2: string; + }; + let testPageLayoutId: string; + let testTabId: string; + + beforeAll(async () => { + const allFieldMetadataIds = await fetchTestFieldMetadataIds(); + + testFieldMetadataIds = { + objectMetadataId: allFieldMetadataIds.objectMetadataId, + fieldMetadataId1: allFieldMetadataIds.fieldMetadataId1, + fieldMetadataId2: allFieldMetadataIds.fieldMetadataId2, + }; + + const { data: layoutData } = await createOnePageLayout({ + expectToFail: false, + input: { + name: 'Test Layout For Widget Restore', + type: PageLayoutType.RECORD_PAGE, + }, + }); + + testPageLayoutId = layoutData.createPageLayout.id; + + const { data: tabData } = await createOnePageLayoutTab({ + expectToFail: false, + input: { + title: 'Test Tab For Widget Restore', + pageLayoutId: testPageLayoutId, + }, + }); + + testTabId = tabData.createPageLayoutTab.id; + }); + + afterAll(async () => { + await destroyOnePageLayoutTab({ + expectToFail: false, + input: { id: testTabId }, + }); + await destroyOnePageLayout({ + expectToFail: false, + input: { id: testPageLayoutId }, + }); + }); + + it('should create, delete, and restore a widget via bulk update', async () => { + const widgetId = v4(); + + const { data: createData } = await updateOnePageLayoutWithTabsAndWidgets({ + expectToFail: false, + input: { + id: testPageLayoutId, + name: 'Test Layout For Widget Restore', + type: PageLayoutType.RECORD_PAGE, + objectMetadataId: null, + tabs: [ + { + id: testTabId, + title: 'Test Tab For Widget Restore', + position: 0, + widgets: [ + { + id: widgetId, + pageLayoutTabId: testTabId, + title: 'Graph Widget', + type: WidgetType.GRAPH, + objectMetadataId: testFieldMetadataIds.objectMetadataId, + gridPosition: { + row: 0, + column: 0, + rowSpan: 1, + columnSpan: 1, + }, + configuration: { + configurationType: WidgetConfigurationType.PIE_CHART, + aggregateFieldMetadataId: + testFieldMetadataIds.fieldMetadataId1, + aggregateOperation: AggregateOperations.COUNT, + groupByFieldMetadataId: testFieldMetadataIds.fieldMetadataId2, + }, + }, + ], + }, + ], + }, + }); + + const createdTab = createData.updatePageLayoutWithTabsAndWidgets.tabs![0]; + + expect(createdTab.widgets).toHaveLength(1); + expect(createdTab.widgets![0].id).toBe(widgetId); + + const { data: deleteData } = await updateOnePageLayoutWithTabsAndWidgets({ + expectToFail: false, + input: { + id: testPageLayoutId, + name: 'Test Layout For Widget Restore', + type: PageLayoutType.RECORD_PAGE, + objectMetadataId: null, + tabs: [ + { + id: testTabId, + title: 'Test Tab For Widget Restore', + position: 0, + widgets: [], + }, + ], + }, + }); + + const deletedTab = deleteData.updatePageLayoutWithTabsAndWidgets.tabs![0]; + + expect(deletedTab.widgets).toHaveLength(0); + + const { data: restoreData } = await updateOnePageLayoutWithTabsAndWidgets({ + expectToFail: false, + input: { + id: testPageLayoutId, + name: 'Test Layout For Widget Restore', + type: PageLayoutType.RECORD_PAGE, + objectMetadataId: null, + tabs: [ + { + id: testTabId, + title: 'Test Tab For Widget Restore', + position: 0, + widgets: [ + { + id: widgetId, + pageLayoutTabId: testTabId, + title: 'Restored Graph Widget', + type: WidgetType.GRAPH, + objectMetadataId: testFieldMetadataIds.objectMetadataId, + gridPosition: { + row: 0, + column: 0, + rowSpan: 1, + columnSpan: 1, + }, + configuration: { + configurationType: WidgetConfigurationType.AGGREGATE_CHART, + aggregateFieldMetadataId: + testFieldMetadataIds.fieldMetadataId1, + aggregateOperation: AggregateOperations.SUM, + displayDataLabel: true, + }, + }, + ], + }, + ], + }, + }); + + const restoredTab = restoreData.updatePageLayoutWithTabsAndWidgets.tabs![0]; + + expect(restoredTab.widgets).toHaveLength(1); + expect(restoredTab.widgets![0]).toMatchObject({ + id: widgetId, + title: 'Restored Graph Widget', + deletedAt: null, + }); + + expect(restoreData.updatePageLayoutWithTabsAndWidgets).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny({ + ...restoreData.updatePageLayoutWithTabsAndWidgets, + }), + ); + }); + + it('should handle mixed create, update, and restore in a single call', async () => { + const widgetToUpdateId = v4(); + const widgetToRestoreId = v4(); + + await updateOnePageLayoutWithTabsAndWidgets({ + expectToFail: false, + input: { + id: testPageLayoutId, + name: 'Test Layout For Widget Restore', + type: PageLayoutType.RECORD_PAGE, + objectMetadataId: null, + tabs: [ + { + id: testTabId, + title: 'Test Tab For Widget Restore', + position: 0, + widgets: [ + { + id: widgetToUpdateId, + pageLayoutTabId: testTabId, + title: 'Widget To Update', + type: WidgetType.IFRAME, + objectMetadataId: null, + gridPosition: { + row: 0, + column: 0, + rowSpan: 1, + columnSpan: 1, + }, + configuration: { + configurationType: WidgetConfigurationType.IFRAME, + url: 'https://example.com', + }, + }, + { + id: widgetToRestoreId, + pageLayoutTabId: testTabId, + title: 'Widget To Restore', + type: WidgetType.GRAPH, + objectMetadataId: testFieldMetadataIds.objectMetadataId, + gridPosition: { + row: 1, + column: 0, + rowSpan: 1, + columnSpan: 1, + }, + configuration: { + configurationType: WidgetConfigurationType.AGGREGATE_CHART, + aggregateFieldMetadataId: + testFieldMetadataIds.fieldMetadataId1, + aggregateOperation: AggregateOperations.COUNT, + displayDataLabel: false, + }, + }, + ], + }, + ], + }, + }); + + await updateOnePageLayoutWithTabsAndWidgets({ + expectToFail: false, + input: { + id: testPageLayoutId, + name: 'Test Layout For Widget Restore', + type: PageLayoutType.RECORD_PAGE, + objectMetadataId: null, + tabs: [ + { + id: testTabId, + title: 'Test Tab For Widget Restore', + position: 0, + widgets: [ + { + id: widgetToUpdateId, + pageLayoutTabId: testTabId, + title: 'Widget To Update', + type: WidgetType.IFRAME, + objectMetadataId: null, + gridPosition: { + row: 0, + column: 0, + rowSpan: 1, + columnSpan: 1, + }, + configuration: { + configurationType: WidgetConfigurationType.IFRAME, + url: 'https://example.com', + }, + }, + ], + }, + ], + }, + }); + + const newWidgetId = v4(); + + const { data: mixedData } = await updateOnePageLayoutWithTabsAndWidgets({ + expectToFail: false, + input: { + id: testPageLayoutId, + name: 'Test Layout For Widget Restore', + type: PageLayoutType.RECORD_PAGE, + objectMetadataId: null, + tabs: [ + { + id: testTabId, + title: 'Test Tab For Widget Restore', + position: 0, + widgets: [ + { + id: widgetToUpdateId, + pageLayoutTabId: testTabId, + title: 'Updated Widget Title', + type: WidgetType.IFRAME, + objectMetadataId: null, + gridPosition: { + row: 0, + column: 0, + rowSpan: 2, + columnSpan: 2, + }, + configuration: { + configurationType: WidgetConfigurationType.IFRAME, + url: 'https://updated.example.com', + }, + }, + { + id: widgetToRestoreId, + pageLayoutTabId: testTabId, + title: 'Restored Widget', + type: WidgetType.GRAPH, + objectMetadataId: testFieldMetadataIds.objectMetadataId, + gridPosition: { + row: 1, + column: 0, + rowSpan: 1, + columnSpan: 1, + }, + configuration: { + configurationType: WidgetConfigurationType.AGGREGATE_CHART, + aggregateFieldMetadataId: + testFieldMetadataIds.fieldMetadataId1, + aggregateOperation: AggregateOperations.SUM, + displayDataLabel: true, + }, + }, + { + id: newWidgetId, + pageLayoutTabId: testTabId, + title: 'Brand New Widget', + type: WidgetType.IFRAME, + objectMetadataId: null, + gridPosition: { + row: 2, + column: 0, + rowSpan: 1, + columnSpan: 1, + }, + configuration: { + configurationType: WidgetConfigurationType.IFRAME, + url: 'https://new.example.com', + }, + }, + ], + }, + ], + }, + }); + + const mixedTab = mixedData.updatePageLayoutWithTabsAndWidgets.tabs![0]; + + expect(mixedTab.widgets).toHaveLength(3); + + const updatedWidget = mixedTab.widgets!.find( + (w: { id: string }) => w.id === widgetToUpdateId, + ); + const restoredWidget = mixedTab.widgets!.find( + (w: { id: string }) => w.id === widgetToRestoreId, + ); + const newWidget = mixedTab.widgets!.find( + (w: { id: string }) => w.id === newWidgetId, + ); + + expect(updatedWidget).toMatchObject({ + title: 'Updated Widget Title', + deletedAt: null, + }); + + expect(restoredWidget).toMatchObject({ + title: 'Restored Widget', + deletedAt: null, + }); + + expect(newWidget).toMatchObject({ + title: 'Brand New Widget', + deletedAt: null, + }); + + expect(mixedData.updatePageLayoutWithTabsAndWidgets).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny({ + ...mixedData.updatePageLayoutWithTabsAndWidgets, + }), + ); + }); +});