Baptiste Devessier
2026-02-20 14:18:00 +01:00
committed by GitHub
parent b107020ad3
commit 00209f7e2c
136 changed files with 4875 additions and 835 deletions
@@ -8,6 +8,7 @@ import { WorkspaceFlatPageLayoutWidgetMapCacheService } from 'src/engine/metadat
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
@Module({
imports: [
@@ -17,6 +18,7 @@ import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-
PageLayoutTabEntity,
ObjectMetadataEntity,
FieldMetadataEntity,
ViewEntity,
]),
],
providers: [
@@ -17,6 +17,7 @@ import { validateGraphFlatPageLayoutWidgetForUpdate } from 'src/engine/metadata-
import { validateIframeFlatPageLayoutWidgetForCreation } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-iframe-flat-page-layout-widget-for-creation.util';
import { validateIframeFlatPageLayoutWidgetForUpdate } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-iframe-flat-page-layout-widget-for-update.util';
import { validateSimpleRecordPageWidgetForCreation } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-simple-record-page-widget-for-creation.util';
import { validateSimpleRecordPageWidgetForUpdate } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-simple-record-page-widget-for-update.util';
import { validateStandaloneRichTextFlatPageLayoutWidgetForCreation } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-standalone-rich-text-flat-page-layout-widget-for-creation.util';
import { validateStandaloneRichTextFlatPageLayoutWidgetForUpdate } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-standalone-rich-text-flat-page-layout-widget-for-update.util';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
@@ -110,55 +111,35 @@ export class FlatPageLayoutWidgetTypeValidatorService {
STANDALONE_RICH_TEXT:
validateStandaloneRichTextFlatPageLayoutWidgetForUpdate,
FRONT_COMPONENT: validateFrontComponentFlatPageLayoutWidgetForUpdate,
TIMELINE: rejectWidgetType(
WidgetType.TIMELINE,
'Widget type TIMELINE is not supported yet.',
msg`Widget type TIMELINE is not supported yet.`,
TIMELINE: validateSimpleRecordPageWidgetForUpdate(
WidgetConfigurationType.TIMELINE,
),
TASKS: rejectWidgetType(
WidgetType.TASKS,
'Widget type TASKS is not supported yet.',
msg`Widget type TASKS is not supported yet.`,
TASKS: validateSimpleRecordPageWidgetForUpdate(
WidgetConfigurationType.TASKS,
),
NOTES: rejectWidgetType(
WidgetType.NOTES,
'Widget type NOTES is not supported yet.',
msg`Widget type NOTES is not supported yet.`,
NOTES: validateSimpleRecordPageWidgetForUpdate(
WidgetConfigurationType.NOTES,
),
FILES: rejectWidgetType(
WidgetType.FILES,
'Widget type FILES is not supported yet.',
msg`Widget type FILES is not supported yet.`,
FILES: validateSimpleRecordPageWidgetForUpdate(
WidgetConfigurationType.FILES,
),
EMAILS: rejectWidgetType(
WidgetType.EMAILS,
'Widget type EMAILS is not supported yet.',
msg`Widget type EMAILS is not supported yet.`,
EMAILS: validateSimpleRecordPageWidgetForUpdate(
WidgetConfigurationType.EMAILS,
),
CALENDAR: rejectWidgetType(
WidgetType.CALENDAR,
'Widget type CALENDAR is not supported yet.',
msg`Widget type CALENDAR is not supported yet.`,
CALENDAR: validateSimpleRecordPageWidgetForUpdate(
WidgetConfigurationType.CALENDAR,
),
FIELD_RICH_TEXT: rejectWidgetType(
WidgetType.FIELD_RICH_TEXT,
'Widget type FIELD_RICH_TEXT is not supported yet.',
msg`Widget type FIELD_RICH_TEXT is not supported yet.`,
FIELD_RICH_TEXT: validateSimpleRecordPageWidgetForUpdate(
WidgetConfigurationType.FIELD_RICH_TEXT,
),
WORKFLOW: rejectWidgetType(
WidgetType.WORKFLOW,
'Widget type WORKFLOW is not supported yet.',
msg`Widget type WORKFLOW is not supported yet.`,
WORKFLOW: validateSimpleRecordPageWidgetForUpdate(
WidgetConfigurationType.WORKFLOW,
),
WORKFLOW_VERSION: rejectWidgetType(
WidgetType.WORKFLOW_VERSION,
'Widget type WORKFLOW_VERSION is not supported yet.',
msg`Widget type WORKFLOW_VERSION is not supported yet.`,
WORKFLOW_VERSION: validateSimpleRecordPageWidgetForUpdate(
WidgetConfigurationType.WORKFLOW_VERSION,
),
WORKFLOW_RUN: rejectWidgetType(
WidgetType.WORKFLOW_RUN,
'Widget type WORKFLOW_RUN is not supported yet.',
msg`Widget type WORKFLOW_RUN is not supported yet.`,
WORKFLOW_RUN: validateSimpleRecordPageWidgetForUpdate(
WidgetConfigurationType.WORKFLOW_RUN,
),
};
@@ -13,6 +13,7 @@ import { fromPageLayoutWidgetEntityToFlatPageLayoutWidget } from 'src/engine/met
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
import { createIdToUniversalIdentifierMap } from 'src/engine/workspace-cache/utils/create-id-to-universal-identifier-map.util';
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
@@ -31,6 +32,8 @@ export class WorkspaceFlatPageLayoutWidgetMapCacheService extends WorkspaceCache
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
@InjectRepository(FieldMetadataEntity)
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
@InjectRepository(ViewEntity)
private readonly viewRepository: Repository<ViewEntity>,
) {
super();
}
@@ -44,6 +47,7 @@ export class WorkspaceFlatPageLayoutWidgetMapCacheService extends WorkspaceCache
pageLayoutTabs,
objectMetadatas,
fieldMetadatas,
views,
] = await Promise.all([
this.pageLayoutWidgetRepository.find({
where: { workspaceId },
@@ -69,6 +73,11 @@ export class WorkspaceFlatPageLayoutWidgetMapCacheService extends WorkspaceCache
select: ['id', 'universalIdentifier'],
withDeleted: true,
}),
this.viewRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier'],
withDeleted: true,
}),
]);
const applicationIdToUniversalIdentifierMap =
@@ -80,6 +89,9 @@ export class WorkspaceFlatPageLayoutWidgetMapCacheService extends WorkspaceCache
const fieldMetadataUniversalIdentifierById = Object.fromEntries(
createIdToUniversalIdentifierMap(fieldMetadatas),
);
const viewUniversalIdentifierById = Object.fromEntries(
createIdToUniversalIdentifierMap(views),
);
const flatPageLayoutWidgetMaps = createEmptyFlatEntityMaps();
@@ -91,6 +103,7 @@ export class WorkspaceFlatPageLayoutWidgetMapCacheService extends WorkspaceCache
pageLayoutTabIdToUniversalIdentifierMap,
objectMetadataIdToUniversalIdentifierMap,
fieldMetadataUniversalIdentifierById,
viewUniversalIdentifierById,
});
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
@@ -20,6 +20,7 @@ export type FromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreateArgs =
| 'flatObjectMetadataMaps'
| 'flatFieldMetadataMaps'
| 'flatViewFieldGroupMaps'
| 'flatViewMaps'
>;
export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({
@@ -30,6 +31,7 @@ export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({
flatObjectMetadataMaps,
flatFieldMetadataMaps,
flatViewFieldGroupMaps,
flatViewMaps,
}: FromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreateArgs): FlatPageLayoutWidget => {
const { pageLayoutTabId, ...createPageLayoutWidgetInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -82,6 +84,7 @@ export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({
flatFieldMetadataMaps.universalIdentifierById,
viewFieldGroupUniversalIdentifierById:
flatViewFieldGroupMaps.universalIdentifierById,
viewUniversalIdentifierById: flatViewMaps.universalIdentifierById,
shouldThrowOnMissingIdentifier: true,
}),
};
@@ -74,11 +74,13 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
configuration,
fieldMetadataUniversalIdentifierById,
viewFieldGroupUniversalIdentifierById = {},
viewUniversalIdentifierById = {},
shouldThrowOnMissingIdentifier = false,
}: {
configuration: PageLayoutWidgetConfiguration;
fieldMetadataUniversalIdentifierById: Partial<Record<string, string>>;
viewFieldGroupUniversalIdentifierById?: Partial<Record<string, string>>;
viewUniversalIdentifierById?: Partial<Record<string, string>>;
shouldThrowOnMissingIdentifier?: boolean;
}): UniversalPageLayoutWidgetConfiguration => {
switch (configuration.configurationType) {
@@ -269,10 +271,30 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
}
case WidgetConfigurationType.FIELDS: {
const { newFieldDefaultConfiguration, ...rest } = configuration;
const { viewId, newFieldDefaultConfiguration, ...rest } = configuration;
let viewUniversalIdentifier: string | null = null;
if (isDefined(viewId)) {
viewUniversalIdentifier = viewUniversalIdentifierById[viewId] ?? null;
if (
!isDefined(viewUniversalIdentifier) &&
shouldThrowOnMissingIdentifier
) {
throw new FlatEntityMapsException(
`View universal identifier not found for id: ${viewId}`,
FlatEntityMapsExceptionCode.RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND,
);
}
}
if (!isDefined(newFieldDefaultConfiguration)) {
return configuration;
return {
...rest,
newFieldDefaultConfiguration,
viewId: viewUniversalIdentifier,
};
}
let viewFieldGroupUniversalIdentifier: string | null = null;
@@ -296,6 +318,7 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
return {
...rest,
viewId: viewUniversalIdentifier,
newFieldDefaultConfiguration: {
isVisible: newFieldDefaultConfiguration.isVisible,
viewFieldGroupId: viewFieldGroupUniversalIdentifier,
@@ -13,6 +13,7 @@ type FromPageLayoutWidgetEntityToFlatPageLayoutWidgetArgs =
FromEntityToFlatEntityArgs<'pageLayoutWidget'> & {
fieldMetadataUniversalIdentifierById: Partial<Record<string, string>>;
viewFieldGroupUniversalIdentifierById?: Partial<Record<string, string>>;
viewUniversalIdentifierById?: Partial<Record<string, string>>;
};
export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ({
@@ -22,6 +23,7 @@ export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ({
objectMetadataIdToUniversalIdentifierMap,
fieldMetadataUniversalIdentifierById,
viewFieldGroupUniversalIdentifierById,
viewUniversalIdentifierById,
}: FromPageLayoutWidgetEntityToFlatPageLayoutWidgetArgs): FlatPageLayoutWidget => {
const pageLayoutWidgetEntityWithoutRelations = removePropertiesFromRecord(
pageLayoutWidgetEntity,
@@ -73,6 +75,7 @@ export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ({
configuration: pageLayoutWidgetEntityWithoutRelations.configuration,
fieldMetadataUniversalIdentifierById,
viewFieldGroupUniversalIdentifierById,
viewUniversalIdentifierById,
});
return {
@@ -31,6 +31,7 @@ export const fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThro
flatObjectMetadataMaps,
flatFieldMetadataMaps,
flatViewFieldGroupMaps,
flatViewMaps,
}: {
updatePageLayoutWidgetInput: UpdatePageLayoutWidgetInputWithId;
flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps;
@@ -39,6 +40,7 @@ export const fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThro
| 'flatObjectMetadataMaps'
| 'flatFieldMetadataMaps'
| 'flatViewFieldGroupMaps'
| 'flatViewMaps'
>): FlatPageLayoutWidget => {
const { id: pageLayoutWidgetToUpdateId } =
extractAndSanitizeObjectStringFields(rawUpdatePageLayoutWidgetInput, [
@@ -102,6 +104,7 @@ export const fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThro
flatFieldMetadataMaps.universalIdentifierById,
viewFieldGroupUniversalIdentifierById:
flatViewFieldGroupMaps.universalIdentifierById,
viewUniversalIdentifierById: flatViewMaps.universalIdentifierById,
});
}
@@ -0,0 +1,39 @@
import { msg, t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { type ValidateFlatPageLayoutWidgetTypeSpecificitiesForUpdateArgs } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
import { type WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
export const validateSimpleRecordPageWidgetForUpdate =
(expectedConfigurationType: WidgetConfigurationType) =>
(
args: ValidateFlatPageLayoutWidgetTypeSpecificitiesForUpdateArgs,
): FlatPageLayoutWidgetValidationError[] => {
const { flatEntityToValidate } = args;
const { universalConfiguration, title: widgetTitle } = flatEntityToValidate;
if (!isDefined(universalConfiguration)) {
return [];
}
const errors: FlatPageLayoutWidgetValidationError[] = [];
if (
universalConfiguration.configurationType !== expectedConfigurationType
) {
const expectedString = expectedConfigurationType.toString();
const actualString =
universalConfiguration.configurationType?.toString() ?? 'undefined';
errors.push({
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
message: t`Invalid configuration type for widget "${widgetTitle}". Expected ${expectedString}, got ${actualString}`,
userFriendlyMessage: msg`Invalid configuration type for widget`,
value: universalConfiguration.configurationType,
});
}
return errors;
};