Create record page layout after custom object creation (#17923)
## Context Creating a new object should now also create its record page layout, tabs and widgets, including fields widget with its associated views/view fields. Custom objects record page layout fields widgets don't have section per default Note: I had to enable some widget creation through the custom API but we should now implement proper validation (which should be minimal since there is usually only the configuration type in the configuration (except for FIELDS widget which contains a viewId) Next step: Create view field should also create a viewField for the FIELDS_WIDGET view (we should also add in the FIELDS widget configuration a newFieldDefaults which will contain default visibility and position to apply to the new view field) The feature is still gated behind an env variable (this was necessary for workspace creation, not so much here in this case but I prefer to keep the same path for consistence)
This commit is contained in:
@@ -29,3 +29,4 @@ AUTH_MICROSOFT_APIS_CALLBACK_URL=http://localhost:3000/auth/microsoft-apis/get-a
|
||||
|
||||
CLICKHOUSE_URL=http://default:clickhousePassword@localhost:8123/twenty
|
||||
IS_WORKSPACE_CREATION_V2_ENABLED=true
|
||||
SHOULD_SEED_STANDARD_RECORD_PAGE_LAYOUTS=true
|
||||
|
||||
+25
-50
@@ -9,14 +9,17 @@ import {
|
||||
} from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-type-validator.type';
|
||||
import { type FlatPageLayoutWidgetValidationError } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-validation-error.type';
|
||||
import { rejectWidgetType } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/reject-widget-type.util';
|
||||
import { validateFieldsFlatPageLayoutWidgetForCreation } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-fields-flat-page-layout-widget-for-creation.util';
|
||||
import { validateFrontComponentFlatPageLayoutWidgetForCreation } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-front-component-flat-page-layout-widget-for-creation.util';
|
||||
import { validateFrontComponentFlatPageLayoutWidgetForUpdate } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-front-component-flat-page-layout-widget-for-update.util';
|
||||
import { validateGraphFlatPageLayoutWidgetForCreation } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-graph-flat-page-layout-widget-for-creation.util';
|
||||
import { validateGraphFlatPageLayoutWidgetForUpdate } from 'src/engine/metadata-modules/flat-page-layout-widget/validators/utils/validate-graph-flat-page-layout-widget-for-update.util';
|
||||
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 { 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';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutWidgetExceptionCode } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
import { UniversalFlatEntityUpdate } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-update.type';
|
||||
@@ -52,64 +55,40 @@ export class FlatPageLayoutWidgetTypeValidatorService {
|
||||
'Widget type FIELD is not supported yet.',
|
||||
msg`Widget type FIELD is not supported yet.`,
|
||||
),
|
||||
FIELDS: rejectWidgetType(
|
||||
WidgetType.FIELDS,
|
||||
'Widget type FIELDS is not supported yet.',
|
||||
msg`Widget type FIELDS is not supported yet.`,
|
||||
),
|
||||
FIELDS: validateFieldsFlatPageLayoutWidgetForCreation,
|
||||
GRAPH: validateGraphFlatPageLayoutWidgetForCreation,
|
||||
STANDALONE_RICH_TEXT:
|
||||
validateStandaloneRichTextFlatPageLayoutWidgetForCreation,
|
||||
FRONT_COMPONENT: validateFrontComponentFlatPageLayoutWidgetForCreation,
|
||||
TIMELINE: rejectWidgetType(
|
||||
WidgetType.TIMELINE,
|
||||
'Widget type TIMELINE is not supported yet.',
|
||||
msg`Widget type TIMELINE is not supported yet.`,
|
||||
TIMELINE: validateSimpleRecordPageWidgetForCreation(
|
||||
WidgetConfigurationType.TIMELINE,
|
||||
),
|
||||
TASKS: rejectWidgetType(
|
||||
WidgetType.TASKS,
|
||||
'Widget type TASKS is not supported yet.',
|
||||
msg`Widget type TASKS is not supported yet.`,
|
||||
TASKS: validateSimpleRecordPageWidgetForCreation(
|
||||
WidgetConfigurationType.TASKS,
|
||||
),
|
||||
NOTES: rejectWidgetType(
|
||||
WidgetType.NOTES,
|
||||
'Widget type NOTES is not supported yet.',
|
||||
msg`Widget type NOTES is not supported yet.`,
|
||||
NOTES: validateSimpleRecordPageWidgetForCreation(
|
||||
WidgetConfigurationType.NOTES,
|
||||
),
|
||||
FILES: rejectWidgetType(
|
||||
WidgetType.FILES,
|
||||
'Widget type FILES is not supported yet.',
|
||||
msg`Widget type FILES is not supported yet.`,
|
||||
FILES: validateSimpleRecordPageWidgetForCreation(
|
||||
WidgetConfigurationType.FILES,
|
||||
),
|
||||
EMAILS: rejectWidgetType(
|
||||
WidgetType.EMAILS,
|
||||
'Widget type EMAILS is not supported yet.',
|
||||
msg`Widget type EMAILS is not supported yet.`,
|
||||
EMAILS: validateSimpleRecordPageWidgetForCreation(
|
||||
WidgetConfigurationType.EMAILS,
|
||||
),
|
||||
CALENDAR: rejectWidgetType(
|
||||
WidgetType.CALENDAR,
|
||||
'Widget type CALENDAR is not supported yet.',
|
||||
msg`Widget type CALENDAR is not supported yet.`,
|
||||
CALENDAR: validateSimpleRecordPageWidgetForCreation(
|
||||
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: validateSimpleRecordPageWidgetForCreation(
|
||||
WidgetConfigurationType.FIELD_RICH_TEXT,
|
||||
),
|
||||
WORKFLOW: rejectWidgetType(
|
||||
WidgetType.WORKFLOW,
|
||||
'Widget type WORKFLOW is not supported yet.',
|
||||
msg`Widget type WORKFLOW is not supported yet.`,
|
||||
WORKFLOW: validateSimpleRecordPageWidgetForCreation(
|
||||
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: validateSimpleRecordPageWidgetForCreation(
|
||||
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: validateSimpleRecordPageWidgetForCreation(
|
||||
WidgetConfigurationType.WORKFLOW_RUN,
|
||||
),
|
||||
};
|
||||
|
||||
@@ -126,11 +105,7 @@ export class FlatPageLayoutWidgetTypeValidatorService {
|
||||
'Widget type FIELD is not supported yet.',
|
||||
msg`Widget type FIELD is not supported yet.`,
|
||||
),
|
||||
FIELDS: rejectWidgetType(
|
||||
WidgetType.FIELDS,
|
||||
'Widget type FIELDS is not supported yet.',
|
||||
msg`Widget type FIELDS is not supported yet.`,
|
||||
),
|
||||
FIELDS: () => [],
|
||||
GRAPH: validateGraphFlatPageLayoutWidgetForUpdate,
|
||||
STANDALONE_RICH_TEXT:
|
||||
validateStandaloneRichTextFlatPageLayoutWidgetForUpdate,
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { validate as uuidValidate } from 'uuid';
|
||||
|
||||
import { type ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs } 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 { 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 validateFieldsFlatPageLayoutWidgetForCreation = (
|
||||
args: ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const { flatEntityToValidate } = args;
|
||||
const { universalConfiguration, title: widgetTitle } = flatEntityToValidate;
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(universalConfiguration)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Configuration is required for fields widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`Configuration is required for fields widget`,
|
||||
});
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (
|
||||
universalConfiguration.configurationType !== WidgetConfigurationType.FIELDS
|
||||
) {
|
||||
const actualString =
|
||||
universalConfiguration.configurationType?.toString() ?? 'undefined';
|
||||
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Invalid configuration type for fields widget "${widgetTitle}". Expected FIELDS, got ${actualString}`,
|
||||
userFriendlyMessage: msg`Invalid configuration type for fields widget`,
|
||||
value: universalConfiguration.configurationType,
|
||||
});
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
const viewId = (
|
||||
universalConfiguration as { configurationType: string; viewId?: unknown }
|
||||
).viewId;
|
||||
|
||||
if (isDefined(viewId) && viewId !== null) {
|
||||
if (typeof viewId !== 'string' || !uuidValidate(viewId)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Invalid viewId for fields widget "${widgetTitle}". Expected a valid UUID`,
|
||||
userFriendlyMessage: msg`Invalid viewId for fields widget`,
|
||||
value: viewId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs } 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 validateSimpleRecordPageWidgetForCreation =
|
||||
(expectedConfigurationType: WidgetConfigurationType) =>
|
||||
(
|
||||
args: ValidateFlatPageLayoutWidgetTypeSpecificitiesForCreationArgs,
|
||||
): FlatPageLayoutWidgetValidationError[] => {
|
||||
const { flatEntityToValidate } = args;
|
||||
const { universalConfiguration, title: widgetTitle } = flatEntityToValidate;
|
||||
const errors: FlatPageLayoutWidgetValidationError[] = [];
|
||||
|
||||
if (!isDefined(universalConfiguration)) {
|
||||
errors.push({
|
||||
code: PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
message: t`Configuration is required for widget "${widgetTitle}"`,
|
||||
userFriendlyMessage: msg`Configuration is required for widget`,
|
||||
});
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
+260
-2
@@ -10,6 +10,7 @@ import { ApplicationService } from 'src/engine/core-modules/application/services
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
|
||||
@@ -21,6 +22,9 @@ import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-meta
|
||||
import { fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util';
|
||||
import { fromDeleteObjectInputToFlatFieldMetadatasToDelete } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util';
|
||||
import { fromUpdateObjectInputToFlatObjectMetadataAndRelatedFlatEntities } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-update-object-input-to-flat-object-metadata-and-related-flat-entities.util';
|
||||
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
|
||||
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/metadata-modules/flat-view-field/constants/default-view-field-size.constant';
|
||||
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
||||
import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input';
|
||||
@@ -30,6 +34,8 @@ import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
|
||||
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';
|
||||
@@ -37,6 +43,10 @@ import { ViewVisibility } from 'src/engine/metadata-modules/view/enums/view-visi
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import {
|
||||
TAB_PROPS,
|
||||
WIDGET_PROPS,
|
||||
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
|
||||
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
|
||||
@@ -56,6 +66,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
) {
|
||||
super(objectMetadataRepository);
|
||||
}
|
||||
@@ -415,6 +426,48 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
viewUniversalIdentifier: flatDefaultViewToCreate.universalIdentifier,
|
||||
});
|
||||
|
||||
let flatRecordPageFieldsViewToCreate:
|
||||
| (UniversalFlatView & { id: string })
|
||||
| null = null;
|
||||
let flatRecordPageFieldsViewFieldsToCreate: UniversalFlatViewField[] = [];
|
||||
let flatDefaultRecordPageLayoutsToCreate: {
|
||||
pageLayouts: FlatPageLayout[];
|
||||
pageLayoutTabs: FlatPageLayoutTab[];
|
||||
pageLayoutWidgets: FlatPageLayoutWidget[];
|
||||
} = {
|
||||
pageLayouts: [],
|
||||
pageLayoutTabs: [],
|
||||
pageLayoutWidgets: [],
|
||||
};
|
||||
|
||||
if (
|
||||
this.twentyConfigService.get('SHOULD_SEED_STANDARD_RECORD_PAGE_LAYOUTS')
|
||||
) {
|
||||
flatRecordPageFieldsViewToCreate =
|
||||
this.computeFlatRecordPageFieldsViewToCreate({
|
||||
objectMetadata: flatObjectMetadataToCreate,
|
||||
flatApplication: resolvedOwnerFlatApplication,
|
||||
});
|
||||
|
||||
flatRecordPageFieldsViewFieldsToCreate =
|
||||
await this.computeFlatViewFieldsToCreate({
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
objectFlatFieldMetadatas: flatFieldMetadataToCreateOnObject,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
flatObjectMetadataToCreate.labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
viewUniversalIdentifier:
|
||||
flatRecordPageFieldsViewToCreate.universalIdentifier,
|
||||
});
|
||||
|
||||
flatDefaultRecordPageLayoutsToCreate =
|
||||
this.computeFlatDefaultRecordPageLayoutToCreate({
|
||||
objectMetadata: flatObjectMetadataToCreate,
|
||||
flatApplication: resolvedOwnerFlatApplication,
|
||||
recordPageFieldsView: flatRecordPageFieldsViewToCreate,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
const isNavigationMenuItemEnabled =
|
||||
existingFeatureFlagsMap[FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_ENABLED] ??
|
||||
false;
|
||||
@@ -439,12 +492,20 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
view: {
|
||||
flatEntityToCreate: [flatDefaultViewToCreate],
|
||||
flatEntityToCreate: [
|
||||
flatDefaultViewToCreate,
|
||||
...(isDefined(flatRecordPageFieldsViewToCreate)
|
||||
? [flatRecordPageFieldsViewToCreate]
|
||||
: []),
|
||||
],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: flatDefaultViewFieldsToCreate,
|
||||
flatEntityToCreate: [
|
||||
...flatDefaultViewFieldsToCreate,
|
||||
...flatRecordPageFieldsViewFieldsToCreate,
|
||||
],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
@@ -461,6 +522,27 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayout: {
|
||||
flatEntityToCreate: [
|
||||
...flatDefaultRecordPageLayoutsToCreate.pageLayouts,
|
||||
],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: [
|
||||
...flatDefaultRecordPageLayoutsToCreate.pageLayoutTabs,
|
||||
],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: [
|
||||
...flatDefaultRecordPageLayoutsToCreate.pageLayoutWidgets,
|
||||
],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
...(isDefined(flatNavigationMenuItemToCreate)
|
||||
? {
|
||||
navigationMenuItem: {
|
||||
@@ -557,6 +639,182 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
};
|
||||
}
|
||||
|
||||
private computeFlatRecordPageFieldsViewToCreate({
|
||||
objectMetadata,
|
||||
flatApplication,
|
||||
}: {
|
||||
flatApplication: FlatApplication;
|
||||
objectMetadata: UniversalFlatObjectMetadata & { id: string };
|
||||
}): UniversalFlatView & { id: string } {
|
||||
const createdAt = new Date().toISOString();
|
||||
|
||||
return {
|
||||
id: v4(),
|
||||
objectMetadataUniversalIdentifier: objectMetadata.universalIdentifier,
|
||||
name: `${objectMetadata.labelSingular} Record Page Fields`,
|
||||
key: null,
|
||||
icon: 'IconList',
|
||||
type: ViewType.FIELDS_WIDGET,
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
deletedAt: null,
|
||||
isCustom: true,
|
||||
anyFieldFilterValue: null,
|
||||
calendarFieldMetadataUniversalIdentifier: null,
|
||||
calendarLayout: null,
|
||||
isCompact: false,
|
||||
shouldHideEmptyGroups: false,
|
||||
kanbanAggregateOperation: null,
|
||||
kanbanAggregateOperationFieldMetadataUniversalIdentifier: null,
|
||||
mainGroupByFieldMetadataUniversalIdentifier: null,
|
||||
openRecordIn: ViewOpenRecordIn.SIDE_PANEL,
|
||||
position: 0,
|
||||
universalIdentifier: v4(),
|
||||
visibility: ViewVisibility.WORKSPACE,
|
||||
createdByUserWorkspaceId: null,
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
viewFieldGroupUniversalIdentifiers: [],
|
||||
viewFilterUniversalIdentifiers: [],
|
||||
viewGroupUniversalIdentifiers: [],
|
||||
viewFilterGroupUniversalIdentifiers: [],
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
};
|
||||
}
|
||||
|
||||
private computeFlatDefaultRecordPageLayoutToCreate({
|
||||
objectMetadata,
|
||||
flatApplication,
|
||||
recordPageFieldsView,
|
||||
workspaceId,
|
||||
}: {
|
||||
flatApplication: FlatApplication;
|
||||
objectMetadata: UniversalFlatObjectMetadata & { id: string };
|
||||
recordPageFieldsView: UniversalFlatView & { id: string };
|
||||
workspaceId: string;
|
||||
}): {
|
||||
pageLayouts: FlatPageLayout[];
|
||||
pageLayoutTabs: FlatPageLayoutTab[];
|
||||
pageLayoutWidgets: FlatPageLayoutWidget[];
|
||||
} {
|
||||
const now = new Date().toISOString();
|
||||
const pageLayoutId = v4();
|
||||
const pageLayoutUniversalIdentifier = v4();
|
||||
|
||||
const tabDefinitions = [
|
||||
{ key: 'home' as const, widgetKey: 'fields' as const },
|
||||
{ key: 'timeline' as const, widgetKey: 'timeline' as const },
|
||||
{ key: 'tasks' as const, widgetKey: 'tasks' as const },
|
||||
{ key: 'notes' as const, widgetKey: 'notes' as const },
|
||||
{ key: 'files' as const, widgetKey: 'files' as const },
|
||||
{ key: 'emails' as const, widgetKey: 'emails' as const },
|
||||
{ key: 'calendar' as const, widgetKey: 'calendar' as const },
|
||||
];
|
||||
|
||||
const pageLayoutTabs: FlatPageLayoutTab[] = [];
|
||||
const pageLayoutWidgets: FlatPageLayoutWidget[] = [];
|
||||
|
||||
for (const { key, widgetKey } of tabDefinitions) {
|
||||
const tabProps = TAB_PROPS[key];
|
||||
const widgetProps = WIDGET_PROPS[widgetKey];
|
||||
const tabId = v4();
|
||||
const tabUniversalIdentifier = v4();
|
||||
const widgetId = v4();
|
||||
const widgetUniversalIdentifier = v4();
|
||||
|
||||
pageLayoutTabs.push({
|
||||
id: tabId,
|
||||
universalIdentifier: tabUniversalIdentifier,
|
||||
applicationId: flatApplication.id,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
workspaceId,
|
||||
title: tabProps.title,
|
||||
position: tabProps.position,
|
||||
pageLayoutId,
|
||||
pageLayoutUniversalIdentifier,
|
||||
widgetIds: [widgetId],
|
||||
widgetUniversalIdentifiers: [widgetUniversalIdentifier],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
icon: tabProps.icon,
|
||||
layoutMode: tabProps.layoutMode,
|
||||
});
|
||||
|
||||
const isFieldsWidget = widgetKey === 'fields';
|
||||
|
||||
const configuration = isFieldsWidget
|
||||
? {
|
||||
configurationType: WidgetConfigurationType.FIELDS,
|
||||
viewId: recordPageFieldsView.id,
|
||||
}
|
||||
: {
|
||||
configurationType:
|
||||
WidgetConfigurationType[
|
||||
widgetKey.toUpperCase() as keyof typeof WidgetConfigurationType
|
||||
],
|
||||
};
|
||||
|
||||
const universalConfiguration = isFieldsWidget
|
||||
? {
|
||||
configurationType: WidgetConfigurationType.FIELDS,
|
||||
viewId: recordPageFieldsView.universalIdentifier,
|
||||
}
|
||||
: {
|
||||
configurationType:
|
||||
WidgetConfigurationType[
|
||||
widgetKey.toUpperCase() as keyof typeof WidgetConfigurationType
|
||||
],
|
||||
};
|
||||
|
||||
pageLayoutWidgets.push({
|
||||
id: widgetId,
|
||||
universalIdentifier: widgetUniversalIdentifier,
|
||||
applicationId: flatApplication.id,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
workspaceId,
|
||||
pageLayoutTabId: tabId,
|
||||
pageLayoutTabUniversalIdentifier: tabUniversalIdentifier,
|
||||
title: widgetProps.title,
|
||||
type: widgetProps.type,
|
||||
gridPosition: widgetProps.gridPosition,
|
||||
position: widgetProps.position,
|
||||
// @ts-expect-error - configurationType is validated but TS can't match to discriminated union
|
||||
configuration,
|
||||
// @ts-expect-error - configurationType is validated but TS can't match to discriminated union
|
||||
universalConfiguration,
|
||||
objectMetadataId: objectMetadata.id,
|
||||
objectMetadataUniversalIdentifier: objectMetadata.universalIdentifier,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
conditionalDisplay: null,
|
||||
});
|
||||
}
|
||||
|
||||
const pageLayout: FlatPageLayout = {
|
||||
id: pageLayoutId,
|
||||
universalIdentifier: pageLayoutUniversalIdentifier,
|
||||
applicationId: flatApplication.id,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
workspaceId,
|
||||
name: `Default ${objectMetadata.labelSingular} Layout`,
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: objectMetadata.id,
|
||||
objectMetadataUniversalIdentifier: objectMetadata.universalIdentifier,
|
||||
tabIds: pageLayoutTabs.map((tab) => tab.id),
|
||||
tabUniversalIdentifiers: pageLayoutTabs.map(
|
||||
(tab) => tab.universalIdentifier,
|
||||
),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
defaultTabToFocusOnMobileAndSidePanelId: null,
|
||||
defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier: null,
|
||||
};
|
||||
|
||||
return { pageLayouts: [pageLayout], pageLayoutTabs, pageLayoutWidgets };
|
||||
}
|
||||
|
||||
private async computeFlatViewFieldsToCreate({
|
||||
objectFlatFieldMetadatas,
|
||||
viewUniversalIdentifier,
|
||||
|
||||
+4811
-68
File diff suppressed because it is too large
Load Diff
+20
-3
@@ -43,13 +43,30 @@ exports[`Object metadata update should fail when labelIdentifier is not a TEXT o
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_VIEW_DATA",
|
||||
"message": "View field position cannot be lower than label identifier view field position",
|
||||
"userFriendlyMessage": "View field position cannot be lower than label identifier view field position",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"fieldMetadataUniversalIdentifier": Any<String>,
|
||||
"universalIdentifier": Any<String>,
|
||||
"viewUniversalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "viewField",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 1 objectMetadata, 1 viewField",
|
||||
"message": "Validation failed for 1 objectMetadata, 2 viewFields",
|
||||
"summary": {
|
||||
"objectMetadata": 1,
|
||||
"totalErrors": 2,
|
||||
"viewField": 1,
|
||||
"totalErrors": 3,
|
||||
"viewField": 2,
|
||||
},
|
||||
"userFriendlyMessage": "Metadata validation failed",
|
||||
},
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ describe('View side effect on object creation', () => {
|
||||
});
|
||||
|
||||
expect(createdViews).toBeDefined();
|
||||
expect(createdViews.length).toBe(1);
|
||||
expect(createdViews.length).toBe(2);
|
||||
const [firstView] = createdViews;
|
||||
|
||||
expect(firstView).toMatchObject<Partial<FlatView>>({
|
||||
|
||||
Reference in New Issue
Block a user