Files
twenty/packages/twenty-front/scripts/mock-data/generate-views.ts
T
Weiko cfb9772179 feat(server): convert view to overridable entity (#21436)
## Context

Every entity created as a side effect of object creation must support
the overridable pattern (`isActive` + `overrides` + override routing)
before we can re-own side effects to their true application. Starting
with View.

viewField, viewFieldGroup, pageLayoutTab and pageLayoutWidget already
extend `OverridableEntity`. This PR brings `view` to the same pattern.

## What this does

- `ViewEntity` now extends `OverridableEntity<ViewOverrides>` (adds
`isActive` boolean + `overrides` jsonb). All editable view properties
are overridable; the 3 fieldMetadata foreign keys are converted to/from
universal identifiers like viewField's `viewFieldGroupId`.
- **Update**: mutations on a view not owned by the caller (e.g. standard
views like "All Companies") are written into `overrides` instead of
mutating the row. Reads merge overrides in
the DTO.
- **Delete/destroy**: views not owned by the caller are deactivated
(`isActive = false`) instead of deleted.
~~- **INDEX invariant**: `key = INDEX` views can only be created via
object-creation side effect. The API now rejects creating, deleting or
destroying INDEX views (object-deletion cascade is unaffected). This was
not really needed for this migration but was flagged during
implementation.~~
- **Front**: views with `isActive = false` are filtered out of the views
selector.
- Fast instance command adds the two columns
(`2-12-instance-command-fast-...-view-overridable-entity.ts`).

## Notes

- Custom (caller-owned) views behave exactly as before: direct updates,
soft delete.
- View-group side effects (kanban groups) are computed on the
override-merged view so overridden `mainGroupByFieldMetadataId` works.
2026-06-11 16:00:52 +00:00

121 lines
2.3 KiB
TypeScript

/* oxlint-disable no-console, lingui/no-unlocalized-strings */
import { graphqlRequest, writeGeneratedFile } from './utils.js';
const FIND_ALL_VIEWS_QUERY = `
query FindAllViews {
getViews {
id
name
objectMetadataId
type
key
icon
position
isCompact
openRecordIn
kanbanAggregateOperation
kanbanAggregateOperationFieldMetadataId
mainGroupByFieldMetadataId
shouldHideEmptyGroups
anyFieldFilterValue
calendarFieldMetadataId
calendarLayout
visibility
createdByUserWorkspaceId
isActive
viewFields {
id
fieldMetadataId
viewId
isVisible
position
size
aggregateOperation
createdAt
updatedAt
deletedAt
}
viewFieldGroups {
id
name
position
isVisible
viewId
createdAt
updatedAt
deletedAt
viewFields {
id
fieldMetadataId
viewId
isVisible
position
size
aggregateOperation
createdAt
updatedAt
deletedAt
}
}
viewFilters {
id
fieldMetadataId
operand
value
viewFilterGroupId
positionInViewFilterGroup
subFieldName
viewId
createdAt
updatedAt
deletedAt
}
viewFilterGroups {
id
parentViewFilterGroupId
logicalOperator
positionInViewFilterGroup
viewId
}
viewSorts {
id
fieldMetadataId
direction
viewId
}
viewGroups {
id
isVisible
fieldValue
position
viewId
createdAt
updatedAt
deletedAt
}
}
}
`;
export const generateViews = async (token: string) => {
console.log('Fetching views from /metadata ...');
const data = (await graphqlRequest(
'/metadata',
FIND_ALL_VIEWS_QUERY,
token,
)) as {
getViews: Record<string, unknown>[];
};
console.log(` Got ${data.getViews.length} views.`);
writeGeneratedFile(
'metadata/views/mock-views-data.ts',
'mockedViews',
'ViewWithRelations[]',
"import { type ViewWithRelations } from '@/views/types/ViewWithRelations';",
data.getViews,
);
};