Move view in metadata-modules/ and create atomic folder + module for each view entity (#14990)

# Introduction
Preparing view-filter and view-group introduction in v2 core engine
Moving view from `core-modules` to `metadata-modules`

## What happened

### Created dedicated modules for each view entity:
- ViewFieldModule
- ViewFilterModule
- ViewFilterGroupModule
- ViewGroupModule
- ViewSortModule

### Each module is now completely independent with its own:
- Controller
- Resolver
- Service
- Entity

### Created dedicated abstraction metadata module folder for:
- flat-view-field
- flat-view

### Dependencies 
- Eleminated circular dep on ViewModule to all others ones
- Granular import not importing the whole viewModule anymore everywhere


close https://github.com/twentyhq/core-team-issues/issues/1703
This commit is contained in:
Paul Rastoin
2025-10-09 15:18:15 +02:00
committed by GitHub
parent c9a1a110e1
commit 59fbe35a8c
198 changed files with 877 additions and 765 deletions
@@ -0,0 +1,56 @@
import diff from 'microdiff';
import { type FromTo } from 'twenty-shared/types';
import { FLAT_VIEW_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-view/constants/flat-view-properties-to-compare.constant';
import { type FlatViewPropertiesToCompare } from 'src/engine/metadata-modules/flat-view/types/flat-view-properties-to-compare.type';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type UpdateViewAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-action-v2.type';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
type GetWorkspaceMigrationUpdateViewActionArgs = FromTo<FlatView, 'FlatView'>;
export const compareTwoFlatView = ({
fromFlatView,
toFlatView,
}: GetWorkspaceMigrationUpdateViewActionArgs) => {
const transformMetadataForComparisonParameters = {
shouldIgnoreProperty: (property: string) =>
!FLAT_VIEW_PROPERTIES_TO_COMPARE.includes(
property as FlatViewPropertiesToCompare,
),
propertiesToStringify: [],
};
const fromCompare = transformMetadataForComparison(
fromFlatView,
transformMetadataForComparisonParameters,
);
const toCompare = transformMetadataForComparison(
toFlatView,
transformMetadataForComparisonParameters,
);
const flatViewDifferences = diff(fromCompare, toCompare);
return flatViewDifferences.flatMap<UpdateViewAction['updates'][number]>(
(difference) => {
switch (difference.type) {
case 'CHANGE': {
const { oldValue, path, value } = difference;
const property = path[0] as FlatViewPropertiesToCompare;
return {
from: oldValue,
to: value,
property,
};
}
case 'CREATE':
case 'REMOVE':
default: {
// Should never occurs, we should only provide null never undefined and so on
return [];
}
}
},
);
};
@@ -0,0 +1,48 @@
import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type CreateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/create-view.input';
import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in';
import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum';
export const fromCreateViewInputToFlatViewToCreate = ({
createViewInput: rawCreateViewInput,
workspaceId,
}: {
createViewInput: CreateViewInput;
workspaceId: string;
}): FlatView => {
const { objectMetadataId, ...createViewInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawCreateViewInput,
['id', 'name', 'objectMetadataId'],
);
const createdAt = new Date();
return {
id: createViewInput.id ?? v4(),
objectMetadataId,
workspaceId,
name: createViewInput.name,
createdAt: createdAt,
updatedAt: createdAt,
deletedAt: null,
isCustom: true,
anyFieldFilterValue: createViewInput.anyFieldFilterValue ?? null,
calendarFieldMetadataId: createViewInput.calendarFieldMetadataId ?? null,
calendarLayout: createViewInput.calendarLayout ?? null,
icon: createViewInput.icon,
isCompact: createViewInput.isCompact ?? false,
kanbanAggregateOperation: createViewInput.kanbanAggregateOperation ?? null,
kanbanAggregateOperationFieldMetadataId:
createViewInput.kanbanAggregateOperationFieldMetadataId ?? null,
key: createViewInput.key ?? null,
openRecordIn: createViewInput.openRecordIn ?? ViewOpenRecordIn.SIDE_PANEL,
position: createViewInput.position ?? 0,
type: createViewInput.type ?? ViewType.TABLE,
universalIdentifier: v4(),
viewFieldIds: [],
};
};
@@ -0,0 +1,40 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type FlatViewMaps } from 'src/engine/metadata-modules/flat-view/types/flat-view-maps.type';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type DeleteViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/delete-view.input';
import {
ViewException,
ViewExceptionCode,
} from 'src/engine/metadata-modules/view/exceptions/view.exception';
export const fromDeleteViewInputToFlatViewOrThrow = ({
deleteViewInput: rawDeleteViewInput,
flatViewMaps,
}: {
deleteViewInput: DeleteViewInput;
flatViewMaps: FlatViewMaps;
}): FlatView => {
const { id: viewId } = extractAndSanitizeObjectStringFields(
rawDeleteViewInput,
['id'],
);
const existingFlatViewToDelete = flatViewMaps.byId[viewId];
if (!isDefined(existingFlatViewToDelete)) {
throw new ViewException(
t`View to delete not found`,
ViewExceptionCode.VIEW_NOT_FOUND,
);
}
return {
...existingFlatViewToDelete,
deletedAt: new Date(),
};
};
@@ -0,0 +1,37 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type FlatViewMaps } from 'src/engine/metadata-modules/flat-view/types/flat-view-maps.type';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type DestroyViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/destroy-view.input';
import {
ViewException,
ViewExceptionCode,
} from 'src/engine/metadata-modules/view/exceptions/view.exception';
export const fromDestroyViewInputToFlatViewOrThrow = ({
destroyViewInput: rawDestroyViewInput,
flatViewMaps,
}: {
destroyViewInput: DestroyViewInput;
flatViewMaps: FlatViewMaps;
}): FlatView => {
const { id: viewId } = extractAndSanitizeObjectStringFields(
rawDestroyViewInput,
['id'],
);
const existingFlatViewToDestroy = flatViewMaps.byId[viewId];
if (!isDefined(existingFlatViewToDestroy)) {
throw new ViewException(
t`View to destroy not found`,
ViewExceptionCode.VIEW_NOT_FOUND,
);
}
return existingFlatViewToDestroy;
};
@@ -0,0 +1,38 @@
import { v4 } from 'uuid';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in';
import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum';
export const fromPartialFlatViewToFlatViewWithDefault = (
partialFlatView: Partial<FlatView>,
): FlatView => {
const createdAt = new Date();
const viewId = partialFlatView.id ?? v4();
return {
...partialFlatView,
id: viewId,
name: partialFlatView.name ?? '',
objectMetadataId: partialFlatView.objectMetadataId ?? '',
type: partialFlatView.type ?? ViewType.TABLE,
key: partialFlatView.key ?? null,
icon: partialFlatView.icon ?? '',
position: partialFlatView.position ?? 0,
isCompact: partialFlatView.isCompact ?? false,
isCustom: partialFlatView.isCustom ?? false,
openRecordIn: partialFlatView.openRecordIn ?? ViewOpenRecordIn.SIDE_PANEL,
kanbanAggregateOperation: partialFlatView.kanbanAggregateOperation ?? null,
kanbanAggregateOperationFieldMetadataId:
partialFlatView.kanbanAggregateOperationFieldMetadataId ?? null,
workspaceId: partialFlatView.workspaceId ?? '',
createdAt: createdAt,
updatedAt: createdAt,
deletedAt: null,
anyFieldFilterValue: partialFlatView.anyFieldFilterValue ?? null,
universalIdentifier: partialFlatView.universalIdentifier ?? viewId,
viewFieldIds: partialFlatView.viewFieldIds ?? [],
calendarLayout: partialFlatView.calendarLayout ?? null,
calendarFieldMetadataId: partialFlatView.calendarFieldMetadataId ?? null,
};
};
@@ -0,0 +1,50 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { FLAT_VIEW_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view/constants/flat-view-editable-properties.constant';
import { type FlatViewMaps } from 'src/engine/metadata-modules/flat-view/types/flat-view-maps.type';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type UpdateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/update-view.input';
import {
ViewException,
ViewExceptionCode,
} from 'src/engine/metadata-modules/view/exceptions/view.exception';
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
export const fromUpdateViewInputToFlatViewToUpdateOrThrow = ({
updateViewInput: rawUpdateViewInput,
flatViewMaps,
}: {
updateViewInput: UpdateViewInput;
flatViewMaps: FlatViewMaps;
}): FlatView => {
const { id: viewToUpdateId } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawUpdateViewInput,
['id'],
);
const existingFlatViewToUpdate = flatViewMaps.byId[viewToUpdateId];
if (!isDefined(existingFlatViewToUpdate)) {
throw new ViewException(
t`View to update not found`,
ViewExceptionCode.VIEW_NOT_FOUND,
);
}
const updatedEditableFieldProperties = extractAndSanitizeObjectStringFields(
rawUpdateViewInput,
FLAT_VIEW_EDITABLE_PROPERTIES,
);
return mergeUpdateInExistingRecord({
existing: existingFlatViewToUpdate,
properties: FLAT_VIEW_EDITABLE_PROPERTIES,
update: updatedEditableFieldProperties,
});
};
@@ -0,0 +1,20 @@
import { removePropertiesFromRecord } from 'twenty-shared/utils';
import { VIEW_ENTITY_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-view/constants/view-entity-relation-properties.constant';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
export const fromViewEntityToFlatView = (viewEntity: ViewEntity): FlatView => {
const viewEntityWithoutRelations = removePropertiesFromRecord(
viewEntity,
VIEW_ENTITY_RELATION_PROPERTIES,
);
return {
...viewEntityWithoutRelations,
universalIdentifier:
viewEntityWithoutRelations.universalIdentifier ??
viewEntityWithoutRelations.id,
viewFieldIds: viewEntity.viewFields.map((viewField) => viewField.id) ?? [],
};
};