fix: allow app-manifest RECORD_TABLE widgets to reference a view by universal identifier (#23634)
## Context Fixes #23065. App-manifest dashboard `RECORD_TABLE` widgets could not reference a view by universal identifier. `RecordTableConfiguration.viewId` was typed as a plain `string`, so `FormatRecordSerializedRelationProperties` (which only renames properties branded with `SerializedRelation`) left it as `viewId` in the manifest type. As a result the manifest rejected `viewUniversalIdentifier`, and the widget could not be made portable across workspaces the way `FIELDS` widgets already are. ## Changes - `RecordTableConfiguration.viewId` is now `SerializedRelation | null` (was `string`), matching `FieldsConfiguration`. This makes the manifest type surface `viewUniversalIdentifier` instead of `viewId`. - `RecordTableConfigurationDTO.viewId` retyped to match. - Forward converter (`fromPageLayoutWidgetConfigurationToUniversalConfiguration`): the `RECORD_TABLE` case now emits the `viewUniversalIdentifier` key instead of `viewId`, since the branded property is renamed in the universal type. Now consistent with the `FIELDS` case (uses `| null`). - Reverse converter (`fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration`): the `RECORD_TABLE` case now reads `viewUniversalIdentifier` and resolves it back to a concrete `viewId`. Frontend readers need no change: `SerializedRelation` is a runtime string, so the existing `typeof === 'string'` guards and `as string` casts still hold. ## Migration None needed. The persisted `pageLayoutWidget.configuration` still stores a concrete `viewId`; `universalConfiguration` (which carries `viewUniversalIdentifier`) is computed on the fly from it and never persisted. Only the manifest/universal representation changes, so there is no stored data in the old shape to backfill. ## Verification - `nx typecheck twenty-server` and `nx typecheck twenty-front`: pass - oxlint + oxfmt on the changed files: clean - End-to-end against a server built from this branch: built a minimal app declaring a view (by `universalIdentifier`) and a `DASHBOARD` page layout with a `RECORD_TABLE` widget referencing that view via `viewUniversalIdentifier`, then installed it. The manifest carried `viewUniversalIdentifier`, and the installed `pageLayoutWidget.configuration.viewId` resolved to the concrete workspace view id. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23634?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+3
-4
@@ -281,11 +281,10 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
|
||||
case WidgetConfigurationType.RECORD_TABLE: {
|
||||
const { viewId, ...rest } = configuration;
|
||||
|
||||
let viewUniversalIdentifier: string | undefined = undefined;
|
||||
let viewUniversalIdentifier: string | null = null;
|
||||
|
||||
if (isDefined(viewId)) {
|
||||
viewUniversalIdentifier =
|
||||
viewUniversalIdentifierById[viewId] ?? undefined;
|
||||
viewUniversalIdentifier = viewUniversalIdentifierById[viewId] ?? null;
|
||||
|
||||
if (
|
||||
!isDefined(viewUniversalIdentifier) &&
|
||||
@@ -300,7 +299,7 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
|
||||
|
||||
return {
|
||||
...rest,
|
||||
viewId: viewUniversalIdentifier,
|
||||
viewUniversalIdentifier,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -8,7 +8,10 @@ import {
|
||||
IsUUID,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { type RecordTableConfiguration } from 'twenty-shared/types';
|
||||
import {
|
||||
type RecordTableConfiguration,
|
||||
type SerializedRelation,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
|
||||
@@ -22,7 +25,7 @@ export class RecordTableConfigurationDTO implements RecordTableConfiguration {
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
viewId?: string;
|
||||
viewId?: SerializedRelation | null;
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
@IsOptional()
|
||||
|
||||
+2
-3
@@ -260,10 +260,9 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.RECORD_TABLE: {
|
||||
const { viewId: viewUniversalIdentifier, ...rest } =
|
||||
universalConfiguration;
|
||||
const { viewUniversalIdentifier, ...rest } = universalConfiguration;
|
||||
|
||||
let viewId: string | undefined = undefined;
|
||||
let viewId: string | null = null;
|
||||
|
||||
if (isDefined(viewUniversalIdentifier)) {
|
||||
const flatView = findFlatEntityByUniversalIdentifier({
|
||||
|
||||
Reference in New Issue
Block a user