cfb9772179
## 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.
121 lines
2.3 KiB
TypeScript
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,
|
|
);
|
|
};
|