Files
twenty/packages/twenty-apps/fixtures/rich-app/src/objects/post-card.object.ts
T
Félix Malfait b010599000 fix(server): preserve kanban/calendar fields in view manifest sync (#19946)
## Summary

The `fromViewManifestToUniversalFlatView` converter hardcoded five view
fields to `null` instead of reading them from the manifest:
- `mainGroupByFieldMetadataUniversalIdentifier`
- `kanbanAggregateOperation`
- `kanbanAggregateOperationFieldMetadataUniversalIdentifier`
- `calendarLayout`
- `calendarFieldMetadataUniversalIdentifier`

As a result **any** Kanban view in an app manifest is rejected by
`validateFlatViewCreation` with `"Kanban view must have a main group by
field"`, and any Calendar view would trip the `view.entity.ts` check
constraint requiring `calendarLayout` + `calendarFieldMetadataId` to be
non-null. Discovered while trying to install
[`twenty-crm-meeting-baas`](https://github.com/Meeting-BaaS/twenty-crm-meeting-baas)
which ships a Kanban view.

## Changes

- **Server converter**: read all five fields from the manifest (with `??
null` fallback).
- **`ViewManifest` type** (`twenty-shared`): add the five fields so SDK
users can set them type-safely.
- **Move `ViewCalendarLayout`** from `twenty-server` to `twenty-shared`
so the manifest type can reference it. Seven import sites updated; the
front-end imports via generated GraphQL types and is unaffected.
- **Unit tests**: extend
`from-view-manifest-to-universal-flat-view.util.spec.ts` with
preservation + null-default cases for both Kanban and Calendar (5 tests
total).
- **Regression coverage**: add a Kanban view
(`post-cards-by-status.view.ts`) to the `rich-app` fixture grouped by
the existing `status` SELECT field. The existing
`applications-install-delete-reinstall` e2e test now exercises the
Kanban path end-to-end — a future regression here would fail CI.

Note: `expected-manifest.ts` and the `views.length` assertion in
`manifest.tests.ts` were updated to reflect the new fixture view.

## Test plan

- [x] `nx test twenty-server --
from-view-manifest-to-universal-flat-view` → 5/5 pass
- [x] `nx typecheck twenty-shared` / `twenty-sdk` / `twenty-server` → no
new errors (one pre-existing unrelated error in
`admin-panel.module-factory.ts`)
- [x] `nx lint twenty-shared` / `twenty-sdk` → clean
- [x] Manual install of the Meeting BaaS app on a dev workspace succeeds
with the Kanban view after this fix
- [ ] CI: SDK e2e `applications-install-delete-reinstall` passes against
the new fixture view
- [ ] CI: integration test `calendar-field-deactivation-deletes-views`
still passes after the enum move

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-04-22 16:31:00 +02:00

112 lines
2.9 KiB
TypeScript

import { defineObject, FieldType } from 'twenty-sdk/define';
enum PostCardStatus {
DRAFT = 'DRAFT',
SENT = 'SENT',
DELIVERED = 'DELIVERED',
RETURNED = 'RETURNED',
LOST = 'LOST',
}
export const POST_CARD_UNIVERSAL_IDENTIFIER =
'54b589ca-eeed-4950-a176-358418b85c05';
export const CONTENT_FIELD_UNIVERSAL_IDENTIFIER =
'58a0a314-d7ea-4865-9850-7fb84e72f30b';
export const STATUS_FIELD_UNIVERSAL_IDENTIFIER =
'87b675b8-dd8c-4448-b4ca-20e5a2234a1e';
export const DELIVERED_AT_FIELD_UNIVERSAL_IDENTIFIER =
'e06abe72-5b44-4e7f-93be-afc185a3c433';
export default defineObject({
universalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post card',
labelPlural: 'Post cards',
description: 'A post card object',
icon: 'IconMail',
labelIdentifierFieldMetadataUniversalIdentifier:
CONTENT_FIELD_UNIVERSAL_IDENTIFIER,
fields: [
{
universalIdentifier: CONTENT_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
name: 'content',
},
{
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
type: FieldType.FULL_NAME,
label: 'Recipient name',
icon: 'IconUser',
name: 'recipientName',
},
{
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
type: FieldType.ADDRESS,
label: 'Recipient address',
icon: 'IconHome',
name: 'recipientAddress',
},
{
universalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.SELECT,
label: 'Status',
icon: 'IconSend',
defaultValue: `'${PostCardStatus.DRAFT}'`,
options: [
{
id: '1b008e19-1e59-4a07-b187-65a20e547c4e',
value: PostCardStatus.DRAFT,
label: 'Draft',
position: 0,
color: 'gray',
},
{
id: '452b9d40-889c-4342-9697-98319394db04',
value: PostCardStatus.SENT,
label: 'Sent',
position: 1,
color: 'orange',
},
{
id: 'c2ed0b8c-a3ed-4383-aef9-e0441267bcfe',
value: PostCardStatus.DELIVERED,
label: 'Delivered',
position: 2,
color: 'green',
},
{
id: 'c57a5e08-7ef7-49b8-87e6-32d720d22802',
value: PostCardStatus.RETURNED,
label: 'Returned',
position: 3,
color: 'orange',
},
{
// No id — exercises addMissingFieldOptionIds in the object branch
value: PostCardStatus.LOST,
label: 'Lost',
position: 4,
color: 'red',
},
],
name: 'status',
},
{
universalIdentifier: DELIVERED_AT_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.DATE_TIME,
label: 'Delivered at',
icon: 'IconCheck',
isNullable: true,
defaultValue: null,
name: 'deliveredAt',
},
],
});