Move open-record-in to object metadata and member preference (#23614)
Replaces the per-view "Open in" setting with a two-level model, following up on #23422 / #23424 and superseding the closed #23446 and #23457: - `objectMetadata.openRecordIn`: `SIDE_PANEL` | `RECORD_PAGE` | `USER_CHOICE` (default `USER_CHOICE`) - `workspaceMember.openRecordIn`: `SIDE_PANEL` | `RECORD_PAGE` (default `SIDE_PANEL`), editable in Settings > Experience The rule: records open where the member prefers, unless the object pins them, and never in a panel there is no room for (mobile always resolves to the record page). ## Why Having the setting on views, objects and members at once was heavy, and view-level resolution was fragile: a chip rendered outside a view (notes, front components, kanban cards pointing at another object) had no view to read from, which is the class of bug behind #23422. Resolution is now context-free: it needs only the object, the current member and the viewport, so chips behave identically everywhere by construction. ## Changes **Object level** - New `openRecordIn` enum column on `objectMetadata`, editable through `updateOneObject` and surfaced in Settings > Data model > Object > Layout ("Open records in": Member preference / Side Panel / Record Page) - Standard definitions pin `workflow`, `workflowVersion`, `dashboard` and `messageCampaign` to the record page (matching the previously hardcoded list) and `calendarEvent` to the side panel (it has no curated record page); everything else, including `workflowRun`, follows the member preference - Apps can set it in `defineObject()` via the object manifest **Member level** - New `openRecordIn` standard field on `workspaceMember`, persisted through the existing settings path (same as `colorScheme`) and exposed in Settings > Experience **View level (deprecated)** - `view.openRecordIn` is no longer read or written by the frontend; the "Open in" entry is gone from the view options dropdown - The column, DTO field and inputs are kept for one release for API compatibility: the output field carries a `deprecationReason`, the inputs keep accepting the value with a `Deprecated:` description (NestJS silently drops input fields that have a `deprecationReason`, which would have been a breaking change) **Upgrade (2.27)** - Fast instance command adds the `objectMetadata.openRecordIn` column defaulting to `USER_CHOICE` - Workspace command adds the `workspaceMember.openRecordIn` field - Workspace command seeds the object column from the standard definitions (any non-`USER_CHOICE` value), then lifts deliberate per-view record page choices onto objects the definitions don't pin **Debt removed** - `canOpenObjectInSidePanel` hardcoded object list and its test - `ObjectOptionsDropdownLayoutOpenInContent` and the `layoutOpenIn` dropdown wiring - `DefaultViewOpenRecordIn` - Context-store/view-based resolution in `useResolveOpenRecordIn` (now reads object metadata + member + viewport) - Front components no longer guess from the current view: an explicit side-panel call honours a pinned object and the viewport, nothing else ## Verification - Ran the three upgrade commands against a live database: column created, the pinned standard objects seeded per workspace (record page pins plus calendarEvent to side panel), member field backfilled to `SIDE_PANEL`; seed rerun is a no-op - Seed command verified on a simulated pre-upgrade workspace (index view set to record page on company): pins the standard objects plus company, idempotent on rerun - Both packages typecheck and lint clean; affected unit suites and the application sync, view creation and metadata cache integration specs pass --------- Co-authored-by: Thomas des Francs <tdesfrancs@gmail.com>
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { type FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
|
||||
|
||||
@RegisteredInstanceCommand('2.27.0', 1785504900000)
|
||||
export class AddOpenRecordInToObjectMetadataFastInstanceCommand
|
||||
implements FastInstanceCommand
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "core"."objectMetadata_openrecordin_enum" AS ENUM('SIDE_PANEL', 'RECORD_PAGE', 'USER_CHOICE')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."objectMetadata" ADD "openRecordIn" "core"."objectMetadata_openrecordin_enum" NOT NULL DEFAULT 'USER_CHOICE'`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."objectMetadata" DROP COLUMN "openRecordIn"',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'DROP TYPE "core"."objectMetadata_openrecordin_enum"',
|
||||
);
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -1,18 +1,26 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
|
||||
import { AddWorkspaceMemberOpenRecordInCommand } from 'src/database/commands/upgrade-version-command/2-27/2-27-workspace-command-1785505000000-add-workspace-member-open-record-in.command';
|
||||
import { SeedObjectOpenRecordInCommand } from 'src/database/commands/upgrade-version-command/2-27/2-27-workspace-command-1785505100000-seed-object-open-record-in.command';
|
||||
import { BackfillMissingStandardSkillsCommand } from 'src/database/commands/upgrade-version-command/2-27/2-27-workspace-command-1785499350000-backfill-standard-skills.command';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ApplicationModule,
|
||||
WorkspaceCacheModule,
|
||||
WorkspaceIteratorModule,
|
||||
WorkspaceMigrationModule,
|
||||
WorkspaceMigrationRunnerModule,
|
||||
WorkspaceIteratorModule,
|
||||
],
|
||||
providers: [
|
||||
AddWorkspaceMemberOpenRecordInCommand,
|
||||
SeedObjectOpenRecordInCommand,
|
||||
BackfillMissingStandardSkillsCommand,
|
||||
],
|
||||
providers: [BackfillMissingStandardSkillsCommand],
|
||||
})
|
||||
export class V2_27_UpgradeVersionCommandModule {}
|
||||
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
import { Command } from 'nest-commander';
|
||||
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ProvisionedWorkspaceCommandRunner } from 'src/database/commands/command-runners/provisioned-workspace.command-runner';
|
||||
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
|
||||
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
|
||||
import { getStandardFlatEntitiesToCreateOrThrow } from 'src/database/commands/upgrade-version-command/2-10/utils/get-standard-flat-entities-to-create-or-throw.util';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
const WORKSPACE_MEMBER_OPEN_RECORD_IN_FIELD_UNIVERSAL_IDENTIFIERS = [
|
||||
STANDARD_OBJECTS.workspaceMember.fields.openRecordIn.universalIdentifier,
|
||||
];
|
||||
|
||||
@RegisteredWorkspaceCommand('2.27.0', 1785505000000)
|
||||
@Command({
|
||||
name: 'upgrade:2-27:add-workspace-member-open-record-in',
|
||||
description:
|
||||
'Create the workspace member openRecordIn preference field in existing workspaces',
|
||||
})
|
||||
export class AddWorkspaceMemberOpenRecordInCommand extends ProvisionedWorkspaceCommandRunner {
|
||||
constructor(
|
||||
protected readonly workspaceIteratorService: WorkspaceIteratorService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
) {
|
||||
super(workspaceIteratorService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
const { flatObjectMetadataMaps, flatFieldMetadataMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
]);
|
||||
|
||||
const existingWorkspaceMemberObjectMetadata =
|
||||
flatObjectMetadataMaps.byUniversalIdentifier[
|
||||
STANDARD_OBJECTS.workspaceMember.universalIdentifier
|
||||
];
|
||||
|
||||
if (!isDefined(existingWorkspaceMemberObjectMetadata)) {
|
||||
this.logger.log(
|
||||
`workspaceMember object metadata does not exist for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Cheap idempotency check before building the whole standard application.
|
||||
if (
|
||||
WORKSPACE_MEMBER_OPEN_RECORD_IN_FIELD_UNIVERSAL_IDENTIFIERS.every(
|
||||
(universalIdentifier) =>
|
||||
isDefined(
|
||||
flatFieldMetadataMaps.byUniversalIdentifier[universalIdentifier],
|
||||
),
|
||||
)
|
||||
) {
|
||||
this.logger.log(
|
||||
`workspaceMember openRecordIn already exists for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { twentyStandardFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const { allFlatEntityMaps: standardAllFlatEntityMaps } =
|
||||
computeTwentyStandardApplicationAllFlatEntityMaps({
|
||||
now: new Date().toISOString(),
|
||||
workspaceId,
|
||||
twentyStandardApplicationId: twentyStandardFlatApplication.id,
|
||||
});
|
||||
|
||||
const fieldsToCreate =
|
||||
getStandardFlatEntitiesToCreateOrThrow<FlatFieldMetadata>({
|
||||
standardFlatEntityMaps: standardAllFlatEntityMaps.flatFieldMetadataMaps,
|
||||
existingFlatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifiers:
|
||||
WORKSPACE_MEMBER_OPEN_RECORD_IN_FIELD_UNIVERSAL_IDENTIFIERS,
|
||||
});
|
||||
|
||||
if (fieldsToCreate.length === 0) {
|
||||
this.logger.log(
|
||||
`workspaceMember openRecordIn already exists for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Creating the workspaceMember openRecordIn field for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
if (isDryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
workspaceId,
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
fieldMetadata: {
|
||||
flatEntityToCreate: fieldsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create the workspaceMember openRecordIn field:\n${JSON.stringify(result, null, 2)}`,
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Failed to create the workspaceMember openRecordIn field for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Created the workspaceMember openRecordIn field for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
import { Command } from 'nest-commander';
|
||||
|
||||
import {
|
||||
ObjectOpenRecordIn,
|
||||
ViewKey,
|
||||
ViewOpenRecordIn,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ProvisionedWorkspaceCommandRunner } from 'src/database/commands/command-runners/provisioned-workspace.command-runner';
|
||||
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
|
||||
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@RegisteredWorkspaceCommand('2.27.0', 1785505100000)
|
||||
@Command({
|
||||
name: 'upgrade:2-27:seed-object-open-record-in',
|
||||
description:
|
||||
'Seed objectMetadata.openRecordIn from the standard definitions and from deliberate per-view record page choices',
|
||||
})
|
||||
export class SeedObjectOpenRecordInCommand extends ProvisionedWorkspaceCommandRunner {
|
||||
// Workspace-invariant, so the standard application is only built once per run.
|
||||
private standardOpenRecordInByUniversalIdentifier?: Record<
|
||||
string,
|
||||
ObjectOpenRecordIn
|
||||
>;
|
||||
|
||||
constructor(
|
||||
protected readonly workspaceIteratorService: WorkspaceIteratorService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
) {
|
||||
super(workspaceIteratorService);
|
||||
}
|
||||
|
||||
private async getStandardOpenRecordInByUniversalIdentifier(
|
||||
workspaceId: string,
|
||||
): Promise<Record<string, ObjectOpenRecordIn>> {
|
||||
if (isDefined(this.standardOpenRecordInByUniversalIdentifier)) {
|
||||
return this.standardOpenRecordInByUniversalIdentifier;
|
||||
}
|
||||
|
||||
const { twentyStandardFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const { allFlatEntityMaps: standardAllFlatEntityMaps } =
|
||||
computeTwentyStandardApplicationAllFlatEntityMaps({
|
||||
now: new Date().toISOString(),
|
||||
workspaceId,
|
||||
twentyStandardApplicationId: twentyStandardFlatApplication.id,
|
||||
});
|
||||
|
||||
this.standardOpenRecordInByUniversalIdentifier = Object.fromEntries(
|
||||
Object.values(
|
||||
standardAllFlatEntityMaps.flatObjectMetadataMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(standardObjectMetadata) =>
|
||||
standardObjectMetadata.openRecordIn !==
|
||||
ObjectOpenRecordIn.USER_CHOICE,
|
||||
)
|
||||
.map((standardObjectMetadata) => [
|
||||
standardObjectMetadata.universalIdentifier,
|
||||
standardObjectMetadata.openRecordIn,
|
||||
]),
|
||||
);
|
||||
|
||||
return this.standardOpenRecordInByUniversalIdentifier;
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
const { flatObjectMetadataMaps, flatViewMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatViewMaps',
|
||||
]);
|
||||
|
||||
const targetOpenRecordInByUniversalIdentifier: Record<
|
||||
string,
|
||||
ObjectOpenRecordIn
|
||||
> = {
|
||||
...(await this.getStandardOpenRecordInByUniversalIdentifier(workspaceId)),
|
||||
};
|
||||
|
||||
// A deliberate per-view record page choice is lifted to the object, unless
|
||||
// the standard definitions already pin that object.
|
||||
for (const flatView of Object.values(flatViewMaps.byUniversalIdentifier)) {
|
||||
if (
|
||||
isDefined(flatView) &&
|
||||
flatView.key === ViewKey.INDEX &&
|
||||
flatView.openRecordIn === ViewOpenRecordIn.RECORD_PAGE &&
|
||||
!isDefined(
|
||||
targetOpenRecordInByUniversalIdentifier[
|
||||
flatView.objectMetadataUniversalIdentifier
|
||||
],
|
||||
)
|
||||
) {
|
||||
targetOpenRecordInByUniversalIdentifier[
|
||||
flatView.objectMetadataUniversalIdentifier
|
||||
] = ObjectOpenRecordIn.RECORD_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const objectMetadatasToUpdate = Object.values(
|
||||
flatObjectMetadataMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.flatMap((flatObjectMetadata) => {
|
||||
const targetOpenRecordIn =
|
||||
targetOpenRecordInByUniversalIdentifier[
|
||||
flatObjectMetadata.universalIdentifier
|
||||
];
|
||||
|
||||
if (
|
||||
!isDefined(targetOpenRecordIn) ||
|
||||
flatObjectMetadata.openRecordIn === targetOpenRecordIn
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
...flatObjectMetadata,
|
||||
openRecordIn: targetOpenRecordIn,
|
||||
updatedAt: now,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
if (objectMetadatasToUpdate.length === 0) {
|
||||
this.logger.log(
|
||||
`Object openRecordIn already seeded for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Workspace ${workspaceId}: seeding openRecordIn on ${objectMetadatasToUpdate.length} object(s)`,
|
||||
);
|
||||
|
||||
if (isDryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { twentyStandardFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
workspaceId,
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
objectMetadata: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: objectMetadatasToUpdate,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to seed object openRecordIn:\n${JSON.stringify(result, null, 2)}`,
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Failed to seed object openRecordIn for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(`Seeded object openRecordIn for workspace ${workspaceId}`);
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const ADD_OBJECT_METADATA_OPEN_RECORD_IN_UPGRADE_COMMAND_NAME =
|
||||
'2.27.0_AddOpenRecordInToObjectMetadataFastInstanceCommand_1785504900000';
|
||||
+2
@@ -131,6 +131,7 @@ import { AddPageLayoutCascadeDeleteIndexesFastInstanceCommand } from './2-25/2-2
|
||||
import { AddChannelWebhookSubscriptionExternalIdIndexesFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-25/2-25-instance-command-fast-1785173910915-add-channel-webhook-subscription-external-id-indexes';
|
||||
import { AddIsHiddenToAgentMessageFastInstanceCommand } from './2-25/2-25-instance-command-fast-1785230296000-add-is-hidden-to-agent-message';
|
||||
import { AddConnectedAccountHandleProviderIndexFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-26/2-26-instance-command-fast-1785420705255-add-connected-account-handle-provider-index';
|
||||
import { AddOpenRecordInToObjectMetadataFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-27/2-27-instance-command-fast-1785504900000-add-open-record-in-to-object-metadata';
|
||||
|
||||
export const INSTANCE_COMMANDS = [
|
||||
AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand,
|
||||
@@ -264,4 +265,5 @@ export const INSTANCE_COMMANDS = [
|
||||
AddChannelWebhookSubscriptionExternalIdIndexesFastInstanceCommand,
|
||||
AddIsHiddenToAgentMessageFastInstanceCommand,
|
||||
AddConnectedAccountHandleProviderIndexFastInstanceCommand,
|
||||
AddOpenRecordInToObjectMetadataFastInstanceCommand,
|
||||
];
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { FieldMetadataType, ObjectOpenRecordIn } from 'twenty-shared/types';
|
||||
|
||||
import { type WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
@@ -166,6 +166,7 @@ export const mockPersonFlatObjectMetadata = (
|
||||
overrides: null,
|
||||
isUIEditable: true,
|
||||
isUICreatable: true,
|
||||
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
|
||||
applicationUniversalIdentifier: 'test-application-id',
|
||||
fieldUniversalIdentifiers: mockFieldMetadatas.map(
|
||||
(field) => field.universalIdentifier,
|
||||
|
||||
+2
@@ -1,4 +1,5 @@
|
||||
import { type ObjectManifest } from 'twenty-shared/application';
|
||||
import { ObjectOpenRecordIn } from 'twenty-shared/types';
|
||||
|
||||
import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type';
|
||||
|
||||
@@ -19,6 +20,7 @@ export const fromObjectManifestToUniversalFlatObjectMetadata = ({
|
||||
labelSingular: objectManifest.labelSingular,
|
||||
labelPlural: objectManifest.labelPlural,
|
||||
color: null,
|
||||
openRecordIn: objectManifest.openRecordIn ?? ObjectOpenRecordIn.USER_CHOICE,
|
||||
description: objectManifest.description ?? null,
|
||||
icon: objectManifest.icon ?? null,
|
||||
overrides: null,
|
||||
|
||||
+6
-1
@@ -1,4 +1,8 @@
|
||||
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
ObjectOpenRecordIn,
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
computeUpdatedFieldsFromDiff,
|
||||
@@ -39,6 +43,7 @@ const mockObjectMetadata: FlatObjectMetadata = {
|
||||
overrides: null,
|
||||
isUIEditable: true,
|
||||
isUICreatable: true,
|
||||
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
|
||||
labelIdentifierFieldMetadataId: null,
|
||||
imageIdentifierFieldMetadataId: null,
|
||||
duplicateCriteria: null,
|
||||
|
||||
+2
@@ -1,3 +1,4 @@
|
||||
import { OpenRecordIn } from 'twenty-shared/types';
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
@@ -302,6 +303,7 @@ describe('UserWorkspaceService', () => {
|
||||
lastName: user.lastName,
|
||||
},
|
||||
colorScheme: 'System',
|
||||
openRecordIn: OpenRecordIn.SIDE_PANEL,
|
||||
userId: user.id,
|
||||
userEmail: user.email,
|
||||
locale: 'en',
|
||||
|
||||
+2
-1
@@ -2,7 +2,7 @@ import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import { FileFolder, OpenRecordIn } from 'twenty-shared/types';
|
||||
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
import { IsNull, Not, type QueryRunner, type Repository } from 'typeorm';
|
||||
|
||||
@@ -188,6 +188,7 @@ export class UserWorkspaceService {
|
||||
lastName: user.lastName,
|
||||
},
|
||||
colorScheme: 'System',
|
||||
openRecordIn: OpenRecordIn.SIDE_PANEL,
|
||||
userId: user.id,
|
||||
userEmail: user.email,
|
||||
avatarUrl: userWorkspace.defaultAvatarUrl ?? null,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
import { Field, Int, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { OpenRecordIn } from 'twenty-shared/types';
|
||||
import { Max, Min } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
@@ -9,6 +11,8 @@ import {
|
||||
WorkspaceMemberTimeFormatEnum,
|
||||
} from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
registerEnumType(OpenRecordIn, { name: 'OpenRecordIn' });
|
||||
|
||||
@ObjectType('FullName')
|
||||
export class FullNameDTO {
|
||||
@Field({ nullable: false })
|
||||
@@ -32,6 +36,9 @@ export class WorkspaceMemberDTO {
|
||||
@Field({ nullable: false })
|
||||
colorScheme: string;
|
||||
|
||||
@Field(() => OpenRecordIn, { nullable: false })
|
||||
openRecordIn: OpenRecordIn;
|
||||
|
||||
@Field({ nullable: true })
|
||||
avatarUrl: string;
|
||||
|
||||
|
||||
+3
-1
@@ -16,7 +16,7 @@ import {
|
||||
type WorkspaceMemberTimeFormatEnum,
|
||||
type WorkspaceMemberWorkspaceEntity,
|
||||
} from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import { FileFolder, type OpenRecordIn } from 'twenty-shared/types';
|
||||
|
||||
export type ToWorkspaceMemberDtoArgs = {
|
||||
workspaceMemberEntity: WorkspaceMemberWorkspaceEntity;
|
||||
@@ -69,6 +69,7 @@ export class WorkspaceMemberTranspiler {
|
||||
name,
|
||||
userEmail,
|
||||
colorScheme,
|
||||
openRecordIn,
|
||||
locale,
|
||||
timeFormat,
|
||||
timeZone,
|
||||
@@ -98,6 +99,7 @@ export class WorkspaceMemberTranspiler {
|
||||
avatarUrl,
|
||||
userWorkspaceId: userWorkspace.id,
|
||||
colorScheme,
|
||||
openRecordIn: openRecordIn as OpenRecordIn,
|
||||
dateFormat: dateFormat as WorkspaceMemberDateFormatEnum,
|
||||
locale,
|
||||
timeFormat: timeFormat as WorkspaceMemberTimeFormatEnum,
|
||||
|
||||
+1
@@ -166,6 +166,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
|
||||
},
|
||||
"objectMetadata": {
|
||||
"propertiesToCompare": [
|
||||
"openRecordIn",
|
||||
"color",
|
||||
"description",
|
||||
"icon",
|
||||
|
||||
+1
@@ -28,6 +28,7 @@ exports[`registry-derived override property maps derives the overridable propert
|
||||
"logicFunction": [],
|
||||
"navigationMenuItem": [],
|
||||
"objectMetadata": [
|
||||
"openRecordIn",
|
||||
"color",
|
||||
"description",
|
||||
"icon",
|
||||
|
||||
+6
@@ -170,6 +170,12 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
openRecordIn: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
isOverridable: true,
|
||||
},
|
||||
color: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
|
||||
+1
@@ -41,6 +41,7 @@ type Assertions = [
|
||||
keyof FlatEntityUpdate<'objectMetadata'>,
|
||||
| 'icon'
|
||||
| 'color'
|
||||
| 'openRecordIn'
|
||||
| 'description'
|
||||
| 'isActive'
|
||||
| 'overrides'
|
||||
|
||||
+2
@@ -1,3 +1,4 @@
|
||||
import { ObjectOpenRecordIn } from 'twenty-shared/types';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application';
|
||||
|
||||
@@ -37,6 +38,7 @@ export const getFlatObjectMetadataMock = (
|
||||
isSystem: false,
|
||||
isUIEditable: true,
|
||||
isUICreatable: true,
|
||||
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
|
||||
labelIdentifierFieldMetadataId,
|
||||
labelPlural: 'default flat object metadata label plural',
|
||||
labelSingular: 'default flat object metadata label singular',
|
||||
|
||||
+2
@@ -3,6 +3,7 @@ import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/fla
|
||||
export const FLAT_OBJECT_METADATA_EDITABLE_PROPERTIES = {
|
||||
custom: [
|
||||
'color',
|
||||
'openRecordIn',
|
||||
'description',
|
||||
'icon',
|
||||
'isActive',
|
||||
@@ -17,6 +18,7 @@ export const FLAT_OBJECT_METADATA_EDITABLE_PROPERTIES = {
|
||||
],
|
||||
standard: [
|
||||
'color',
|
||||
'openRecordIn',
|
||||
'description',
|
||||
'icon',
|
||||
'isActive',
|
||||
|
||||
+2
@@ -1,4 +1,5 @@
|
||||
import { getFieldUniversalIdentifier } from 'twenty-shared/application';
|
||||
import { ObjectOpenRecordIn } from 'twenty-shared/types';
|
||||
import {
|
||||
capitalize,
|
||||
isDefined,
|
||||
@@ -60,6 +61,7 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
|
||||
updatedAt: createdAt,
|
||||
duplicateCriteria: null,
|
||||
color: createObjectInput.color ?? null,
|
||||
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
|
||||
description: createObjectInput.description ?? null,
|
||||
icon: createObjectInput.icon ?? null,
|
||||
isActive: true,
|
||||
|
||||
+2
@@ -19,6 +19,7 @@ export const fromFlatObjectMetadataToObjectMetadataDto = (
|
||||
isLabelSyncedWithName,
|
||||
isRemote,
|
||||
isSearchable,
|
||||
openRecordIn,
|
||||
isSystem,
|
||||
isUIEditable,
|
||||
isUICreatable,
|
||||
@@ -39,6 +40,7 @@ export const fromFlatObjectMetadataToObjectMetadataDto = (
|
||||
isLabelSyncedWithName,
|
||||
isRemote,
|
||||
isSearchable,
|
||||
openRecordIn,
|
||||
isSystem,
|
||||
isUIEditable,
|
||||
isUICreatable,
|
||||
|
||||
+13
-1
@@ -1,4 +1,11 @@
|
||||
import { Field, HideField, ObjectType } from '@nestjs/graphql';
|
||||
import {
|
||||
Field,
|
||||
HideField,
|
||||
ObjectType,
|
||||
registerEnumType,
|
||||
} from '@nestjs/graphql';
|
||||
|
||||
import { ObjectOpenRecordIn } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
Authorize,
|
||||
@@ -14,6 +21,8 @@ import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dto
|
||||
import { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
|
||||
import { type ObjectMetadataOverrides } from 'src/engine/metadata-modules/object-metadata/types/object-metadata-overrides.type';
|
||||
|
||||
registerEnumType(ObjectOpenRecordIn, { name: 'ObjectOpenRecordIn' });
|
||||
|
||||
@ObjectType('Object')
|
||||
@Authorize({
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
@@ -87,6 +96,9 @@ export class ObjectMetadataDTO {
|
||||
@FilterableField()
|
||||
isSearchable: boolean;
|
||||
|
||||
@Field(() => ObjectOpenRecordIn)
|
||||
openRecordIn: ObjectOpenRecordIn;
|
||||
|
||||
@HideField()
|
||||
workspaceId: string;
|
||||
|
||||
|
||||
+7
@@ -1,8 +1,10 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { Type } from 'class-transformer';
|
||||
import { ObjectOpenRecordIn } from 'twenty-shared/types';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
@@ -81,6 +83,11 @@ export class UpdateObjectPayload {
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
isSearchable?: boolean;
|
||||
|
||||
@IsEnum(ObjectOpenRecordIn)
|
||||
@IsOptional()
|
||||
@Field(() => ObjectOpenRecordIn, { nullable: true })
|
||||
openRecordIn?: ObjectOpenRecordIn;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
|
||||
+13
@@ -9,7 +9,10 @@ import {
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { ObjectOpenRecordIn } from 'twenty-shared/types';
|
||||
|
||||
import { ADD_METADATA_OVERRIDES_COLUMN_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-19/add-metadata-overrides-column-upgrade-command-name.constant';
|
||||
import { ADD_OBJECT_METADATA_OPEN_RECORD_IN_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-27/add-object-metadata-open-record-in-upgrade-command-name.constant';
|
||||
import { DROP_METADATA_STANDARD_OVERRIDES_COLUMN_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-20/drop-metadata-standard-overrides-column-upgrade-command-name.constant';
|
||||
import { type WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
|
||||
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
|
||||
@@ -66,6 +69,16 @@ export class ObjectMetadataEntity
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
color: string | null;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName: ADD_OBJECT_METADATA_OPEN_RECORD_IN_UPGRADE_COMMAND_NAME,
|
||||
})
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: Object.values(ObjectOpenRecordIn),
|
||||
default: ObjectOpenRecordIn.USER_CHOICE,
|
||||
})
|
||||
openRecordIn: ObjectOpenRecordIn;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName: ADD_METADATA_OVERRIDES_COLUMN_UPGRADE_COMMAND_NAME,
|
||||
})
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ export const fromObjectMetadataEntityToObjectMetadataDto = (
|
||||
isUICreatable: entity.isUICreatable,
|
||||
isUIReadOnly: !entity.isUIEditable,
|
||||
isSearchable: entity.isSearchable,
|
||||
openRecordIn: entity.openRecordIn,
|
||||
isLabelSyncedWithName: entity.isLabelSyncedWithName,
|
||||
workspaceId: entity.workspaceId,
|
||||
labelIdentifierFieldMetadataId:
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const VIEW_OPEN_RECORD_IN_DEPRECATION =
|
||||
'Superseded by objectMetadata.openRecordIn and the workspace member preference; kept one release for API compatibility, no longer read by the frontend.';
|
||||
+2
@@ -25,6 +25,7 @@ import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/
|
||||
import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator';
|
||||
import { KANBAN_COLUMN_MAX_WIDTH } from 'src/engine/metadata-modules/view/constants/kanban-column-max-width.constant';
|
||||
import { KANBAN_COLUMN_MIN_WIDTH } from 'src/engine/metadata-modules/view/constants/kanban-column-min-width.constant';
|
||||
import { VIEW_OPEN_RECORD_IN_DEPRECATION } from 'src/engine/metadata-modules/view/constants/view-open-record-in-deprecation.constant';
|
||||
|
||||
@InputType()
|
||||
export class CreateViewInput {
|
||||
@@ -82,6 +83,7 @@ export class CreateViewInput {
|
||||
@IsEnum(ViewOpenRecordIn)
|
||||
@Field(() => ViewOpenRecordIn, {
|
||||
nullable: true,
|
||||
description: `Deprecated: ${VIEW_OPEN_RECORD_IN_DEPRECATION}`,
|
||||
defaultValue: ViewOpenRecordIn.SIDE_PANEL,
|
||||
})
|
||||
openRecordIn?: ViewOpenRecordIn;
|
||||
|
||||
+2
@@ -23,6 +23,7 @@ import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/
|
||||
import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator';
|
||||
import { KANBAN_COLUMN_MAX_WIDTH } from 'src/engine/metadata-modules/view/constants/kanban-column-max-width.constant';
|
||||
import { KANBAN_COLUMN_MIN_WIDTH } from 'src/engine/metadata-modules/view/constants/kanban-column-min-width.constant';
|
||||
import { VIEW_OPEN_RECORD_IN_DEPRECATION } from 'src/engine/metadata-modules/view/constants/view-open-record-in-deprecation.constant';
|
||||
|
||||
// TODO: this should be refactored like for view-field.input.ts
|
||||
// This is a temporary fix as we were extending the CreateViewInput class which was adding default values for the non filled fields
|
||||
@@ -62,6 +63,7 @@ export class UpdateViewInput {
|
||||
@IsEnum(ViewOpenRecordIn)
|
||||
@Field(() => ViewOpenRecordIn, {
|
||||
nullable: true,
|
||||
description: `Deprecated: ${VIEW_OPEN_RECORD_IN_DEPRECATION}`,
|
||||
})
|
||||
openRecordIn?: ViewOpenRecordIn;
|
||||
|
||||
|
||||
+5
-1
@@ -19,6 +19,7 @@ import {
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { KANBAN_COLUMN_MAX_WIDTH } from 'src/engine/metadata-modules/view/constants/kanban-column-max-width.constant';
|
||||
import { KANBAN_COLUMN_MIN_WIDTH } from 'src/engine/metadata-modules/view/constants/kanban-column-min-width.constant';
|
||||
import { VIEW_OPEN_RECORD_IN_DEPRECATION } from 'src/engine/metadata-modules/view/constants/view-open-record-in-deprecation.constant';
|
||||
|
||||
@InputType()
|
||||
export class UpsertViewWidgetViewSettingsInput {
|
||||
@@ -43,7 +44,10 @@ export class UpsertViewWidgetViewSettingsInput {
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ViewOpenRecordIn)
|
||||
@Field(() => ViewOpenRecordIn, { nullable: true })
|
||||
@Field(() => ViewOpenRecordIn, {
|
||||
nullable: true,
|
||||
description: `Deprecated: ${VIEW_OPEN_RECORD_IN_DEPRECATION}`,
|
||||
})
|
||||
openRecordIn?: ViewOpenRecordIn;
|
||||
|
||||
@IsOptional()
|
||||
|
||||
@@ -22,6 +22,7 @@ import { ViewFilterGroupDTO } from 'src/engine/metadata-modules/view-filter-grou
|
||||
import { ViewFilterDTO } from 'src/engine/metadata-modules/view-filter/dtos/view-filter.dto';
|
||||
import { ViewGroupDTO } from 'src/engine/metadata-modules/view-group/dtos/view-group.dto';
|
||||
import { ViewSortDTO } from 'src/engine/metadata-modules/view-sort/dtos/view-sort.dto';
|
||||
import { VIEW_OPEN_RECORD_IN_DEPRECATION } from 'src/engine/metadata-modules/view/constants/view-open-record-in-deprecation.constant';
|
||||
|
||||
registerEnumType(ViewOpenRecordIn, { name: 'ViewOpenRecordIn' });
|
||||
registerEnumType(ViewType, { name: 'ViewType' });
|
||||
@@ -61,6 +62,7 @@ export class ViewDTO {
|
||||
@Field(() => ViewOpenRecordIn, {
|
||||
nullable: false,
|
||||
defaultValue: ViewOpenRecordIn.SIDE_PANEL,
|
||||
deprecationReason: VIEW_OPEN_RECORD_IN_DEPRECATION,
|
||||
})
|
||||
openRecordIn: ViewOpenRecordIn;
|
||||
|
||||
|
||||
@@ -121,6 +121,7 @@ export class ViewEntity
|
||||
@Column({ nullable: false, default: false, type: 'boolean' })
|
||||
isCustom: boolean;
|
||||
|
||||
// Deprecated: superseded by objectMetadata.openRecordIn and the member preference.
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: Object.values(ViewOpenRecordIn),
|
||||
|
||||
+2
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
type FieldMetadataType,
|
||||
type ObjectsPermissions,
|
||||
ObjectOpenRecordIn,
|
||||
} from 'twenty-shared/types';
|
||||
import { EntityManager } from 'typeorm';
|
||||
import { EntityPersistExecutor } from 'typeorm/persistence/EntityPersistExecutor';
|
||||
@@ -124,6 +125,7 @@ describe('WorkspaceEntityManager', () => {
|
||||
isLabelSyncedWithName: false,
|
||||
isUIEditable: true,
|
||||
isUICreatable: true,
|
||||
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
|
||||
duplicateCriteria: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { FieldMetadataType, ObjectOpenRecordIn } from 'twenty-shared/types';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
@@ -39,6 +39,7 @@ describe('getColumnNameToFieldMetadataIdMap', () => {
|
||||
overrides: null,
|
||||
isUIEditable: true,
|
||||
isUICreatable: true,
|
||||
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
|
||||
labelIdentifierFieldMetadataId: null,
|
||||
imageIdentifierFieldMetadataId: null,
|
||||
duplicateCriteria: null,
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { FieldMetadataType, ObjectOpenRecordIn } from 'twenty-shared/types';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
@@ -39,6 +39,7 @@ describe('getFieldMetadataIdToColumnNamesMap', () => {
|
||||
overrides: null,
|
||||
isUIEditable: true,
|
||||
isUICreatable: true,
|
||||
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
|
||||
labelIdentifierFieldMetadataId: null,
|
||||
imageIdentifierFieldMetadataId: null,
|
||||
duplicateCriteria: null,
|
||||
|
||||
+6
-1
@@ -1,4 +1,8 @@
|
||||
import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
ObjectOpenRecordIn,
|
||||
type ObjectRecord,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
@@ -39,6 +43,7 @@ describe('isRecordMatchingRLSRowLevelPermissionPredicate', () => {
|
||||
overrides: null,
|
||||
isUIEditable: true,
|
||||
isUICreatable: true,
|
||||
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
|
||||
labelIdentifierFieldMetadataId: null,
|
||||
imageIdentifierFieldMetadataId: null,
|
||||
duplicateCriteria: null,
|
||||
|
||||
+27
-24
@@ -2956,22 +2956,22 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
|
||||
"workspaceMember": {
|
||||
"fields": {
|
||||
"accountOwnerForCompanies": {
|
||||
"id": "00000000-0000-0000-0000-000000000898",
|
||||
"id": "00000000-0000-0000-0000-000000000899",
|
||||
},
|
||||
"assignedTasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000896",
|
||||
"id": "00000000-0000-0000-0000-000000000897",
|
||||
},
|
||||
"avatarUrl": {
|
||||
"id": "00000000-0000-0000-0000-000000000892",
|
||||
"id": "00000000-0000-0000-0000-000000000893",
|
||||
},
|
||||
"blocklist": {
|
||||
"id": "00000000-0000-0000-0000-000000000900",
|
||||
},
|
||||
"calendarEventParticipants": {
|
||||
"id": "00000000-0000-0000-0000-000000000901",
|
||||
},
|
||||
"calendarEventParticipants": {
|
||||
"id": "00000000-0000-0000-0000-000000000902",
|
||||
},
|
||||
"calendarStartDay": {
|
||||
"id": "00000000-0000-0000-0000-000000000906",
|
||||
"id": "00000000-0000-0000-0000-000000000907",
|
||||
},
|
||||
"colorScheme": {
|
||||
"id": "00000000-0000-0000-0000-000000000890",
|
||||
@@ -2983,7 +2983,7 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
|
||||
"id": "00000000-0000-0000-0000-000000000885",
|
||||
},
|
||||
"dateFormat": {
|
||||
"id": "00000000-0000-0000-0000-000000000904",
|
||||
"id": "00000000-0000-0000-0000-000000000905",
|
||||
},
|
||||
"deletedAt": {
|
||||
"id": "00000000-0000-0000-0000-000000000884",
|
||||
@@ -2992,22 +2992,25 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
|
||||
"id": "00000000-0000-0000-0000-000000000881",
|
||||
},
|
||||
"jobTitle": {
|
||||
"id": "00000000-0000-0000-0000-000000000894",
|
||||
"id": "00000000-0000-0000-0000-000000000895",
|
||||
},
|
||||
"locale": {
|
||||
"id": "00000000-0000-0000-0000-000000000891",
|
||||
"id": "00000000-0000-0000-0000-000000000892",
|
||||
},
|
||||
"messageParticipants": {
|
||||
"id": "00000000-0000-0000-0000-000000000899",
|
||||
"id": "00000000-0000-0000-0000-000000000900",
|
||||
},
|
||||
"name": {
|
||||
"id": "00000000-0000-0000-0000-000000000889",
|
||||
},
|
||||
"numberFormat": {
|
||||
"id": "00000000-0000-0000-0000-000000000907",
|
||||
"id": "00000000-0000-0000-0000-000000000908",
|
||||
},
|
||||
"openRecordIn": {
|
||||
"id": "00000000-0000-0000-0000-000000000891",
|
||||
},
|
||||
"ownedOpportunities": {
|
||||
"id": "00000000-0000-0000-0000-000000000897",
|
||||
"id": "00000000-0000-0000-0000-000000000898",
|
||||
},
|
||||
"position": {
|
||||
"id": "00000000-0000-0000-0000-000000000887",
|
||||
@@ -3016,13 +3019,13 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
|
||||
"id": "00000000-0000-0000-0000-000000000888",
|
||||
},
|
||||
"timeFormat": {
|
||||
"id": "00000000-0000-0000-0000-000000000905",
|
||||
"id": "00000000-0000-0000-0000-000000000906",
|
||||
},
|
||||
"timeZone": {
|
||||
"id": "00000000-0000-0000-0000-000000000903",
|
||||
"id": "00000000-0000-0000-0000-000000000904",
|
||||
},
|
||||
"timelineActivities": {
|
||||
"id": "00000000-0000-0000-0000-000000000902",
|
||||
"id": "00000000-0000-0000-0000-000000000903",
|
||||
},
|
||||
"updatedAt": {
|
||||
"id": "00000000-0000-0000-0000-000000000883",
|
||||
@@ -3031,29 +3034,29 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
|
||||
"id": "00000000-0000-0000-0000-000000000886",
|
||||
},
|
||||
"userEmail": {
|
||||
"id": "00000000-0000-0000-0000-000000000893",
|
||||
"id": "00000000-0000-0000-0000-000000000894",
|
||||
},
|
||||
"userId": {
|
||||
"id": "00000000-0000-0000-0000-000000000895",
|
||||
"id": "00000000-0000-0000-0000-000000000896",
|
||||
},
|
||||
},
|
||||
"id": "00000000-0000-0000-0000-000000000913",
|
||||
"id": "00000000-0000-0000-0000-000000000914",
|
||||
"views": {
|
||||
"allWorkspaceMembers": {
|
||||
"id": "00000000-0000-0000-0000-000000000912",
|
||||
"id": "00000000-0000-0000-0000-000000000913",
|
||||
"viewFieldGroups": {},
|
||||
"viewFields": {
|
||||
"assignedTasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000911",
|
||||
"id": "00000000-0000-0000-0000-000000000912",
|
||||
},
|
||||
"createdAt": {
|
||||
"id": "00000000-0000-0000-0000-000000000909",
|
||||
"id": "00000000-0000-0000-0000-000000000910",
|
||||
},
|
||||
"name": {
|
||||
"id": "00000000-0000-0000-0000-000000000908",
|
||||
"id": "00000000-0000-0000-0000-000000000909",
|
||||
},
|
||||
"ownedOpportunities": {
|
||||
"id": "00000000-0000-0000-0000-000000000910",
|
||||
"id": "00000000-0000-0000-0000-000000000911",
|
||||
},
|
||||
},
|
||||
"viewGroups": {},
|
||||
|
||||
+22
@@ -5,6 +5,7 @@ import {
|
||||
FieldMetadataType,
|
||||
NumberDataType,
|
||||
RelationType,
|
||||
OpenRecordIn,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
@@ -163,6 +164,27 @@ export const buildWorkspaceMemberStandardFlatFieldMetadatas = ({
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}),
|
||||
openRecordIn: createStandardFieldFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
context: {
|
||||
fieldName: 'openRecordIn',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: i18nLabel(msg`Open Records In`),
|
||||
description: i18nLabel(
|
||||
msg`Where records open for objects that follow the member's preference`,
|
||||
),
|
||||
icon: 'IconLayoutSidebarRight',
|
||||
isSystem: true,
|
||||
isNullable: false,
|
||||
isUIEditable: false,
|
||||
defaultValue: `'${OpenRecordIn.SIDE_PANEL}'`,
|
||||
},
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
dependencyFlatEntityMaps,
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}),
|
||||
locale: createStandardFieldFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
|
||||
+6
@@ -1,5 +1,6 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import { ObjectOpenRecordIn } from 'twenty-shared/types';
|
||||
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type';
|
||||
@@ -144,6 +145,7 @@ export const STANDARD_FLAT_OBJECT_METADATA_BUILDERS_BY_OBJECT_NAME = {
|
||||
context: {
|
||||
universalIdentifier: STANDARD_OBJECTS.calendarEvent.universalIdentifier,
|
||||
nameSingular: 'calendarEvent',
|
||||
openRecordIn: ObjectOpenRecordIn.SIDE_PANEL,
|
||||
namePlural: 'calendarEvents',
|
||||
labelSingular: i18nLabel(msg`Calendar event`),
|
||||
labelPlural: i18nLabel(msg`Calendar events`),
|
||||
@@ -232,6 +234,7 @@ export const STANDARD_FLAT_OBJECT_METADATA_BUILDERS_BY_OBJECT_NAME = {
|
||||
context: {
|
||||
universalIdentifier: STANDARD_OBJECTS.dashboard.universalIdentifier,
|
||||
nameSingular: 'dashboard',
|
||||
openRecordIn: ObjectOpenRecordIn.RECORD_PAGE,
|
||||
namePlural: 'dashboards',
|
||||
labelSingular: i18nLabel(msg`Dashboard`),
|
||||
labelPlural: i18nLabel(msg`Dashboards`),
|
||||
@@ -263,6 +266,7 @@ export const STANDARD_FLAT_OBJECT_METADATA_BUILDERS_BY_OBJECT_NAME = {
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.messageCampaign.universalIdentifier,
|
||||
nameSingular: 'messageCampaign',
|
||||
openRecordIn: ObjectOpenRecordIn.RECORD_PAGE,
|
||||
namePlural: 'messageCampaigns',
|
||||
labelSingular: i18nLabel(msg`Campaign`),
|
||||
labelPlural: i18nLabel(msg`Campaigns`),
|
||||
@@ -713,6 +717,7 @@ export const STANDARD_FLAT_OBJECT_METADATA_BUILDERS_BY_OBJECT_NAME = {
|
||||
context: {
|
||||
universalIdentifier: STANDARD_OBJECTS.workflow.universalIdentifier,
|
||||
nameSingular: 'workflow',
|
||||
openRecordIn: ObjectOpenRecordIn.RECORD_PAGE,
|
||||
namePlural: 'workflows',
|
||||
labelSingular: i18nLabel(msg`Workflow`),
|
||||
labelPlural: i18nLabel(msg`Workflows`),
|
||||
@@ -803,6 +808,7 @@ export const STANDARD_FLAT_OBJECT_METADATA_BUILDERS_BY_OBJECT_NAME = {
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.workflowVersion.universalIdentifier,
|
||||
nameSingular: 'workflowVersion',
|
||||
openRecordIn: ObjectOpenRecordIn.RECORD_PAGE,
|
||||
namePlural: 'workflowVersions',
|
||||
labelSingular: i18nLabel(msg`Workflow Version`),
|
||||
labelPlural: i18nLabel(msg`Workflow Versions`),
|
||||
|
||||
+4
@@ -1,4 +1,5 @@
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import { ObjectOpenRecordIn } from 'twenty-shared/types';
|
||||
import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application';
|
||||
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
@@ -19,6 +20,7 @@ export type CreateStandardObjectContext<O extends AllStandardObjectName> = {
|
||||
isAuditLogged?: boolean;
|
||||
isUIEditable?: boolean;
|
||||
isUICreatable?: boolean;
|
||||
openRecordIn?: ObjectOpenRecordIn;
|
||||
shortcut?: string | null;
|
||||
duplicateCriteria?: string[][] | null;
|
||||
labelIdentifierFieldMetadataName: AllStandardObjectFieldName<O>;
|
||||
@@ -48,6 +50,7 @@ export const createStandardObjectFlatMetadata = <
|
||||
isAuditLogged = true,
|
||||
isUIEditable = true,
|
||||
isUICreatable = true,
|
||||
openRecordIn = ObjectOpenRecordIn.USER_CHOICE,
|
||||
shortcut = null,
|
||||
duplicateCriteria = null,
|
||||
labelIdentifierFieldMetadataName,
|
||||
@@ -90,6 +93,7 @@ export const createStandardObjectFlatMetadata = <
|
||||
isAuditLogged,
|
||||
isUIEditable,
|
||||
isUICreatable,
|
||||
openRecordIn,
|
||||
isLabelSyncedWithName: false,
|
||||
overrides: null,
|
||||
duplicateCriteria,
|
||||
|
||||
+1
@@ -28,6 +28,7 @@ type Assertions = [
|
||||
keyof UniversalFlatEntityUpdate<'objectMetadata'>,
|
||||
| 'icon'
|
||||
| 'color'
|
||||
| 'openRecordIn'
|
||||
| 'description'
|
||||
| 'isActive'
|
||||
| 'overrides'
|
||||
|
||||
+1
@@ -68,6 +68,7 @@ exports[`deleteFlatEntityForeignKeyAggregators should strip raw workspace-scoped
|
||||
"namePlural": "defaultflatObjectMetadataNamePlural",
|
||||
"nameSingular": "defaultflatObjectMetadataNameSingular",
|
||||
"objectPermissionUniversalIdentifiers": [],
|
||||
"openRecordIn": "USER_CHOICE",
|
||||
"overrides": null,
|
||||
"searchFieldMetadataUniversalIdentifiers": [],
|
||||
"shortcut": "shortcut",
|
||||
|
||||
+1
@@ -55,6 +55,7 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
position: number;
|
||||
name: FullNameMetadata;
|
||||
colorScheme: string;
|
||||
openRecordIn: string;
|
||||
locale: keyof typeof APP_LOCALES;
|
||||
avatarUrl: string | null;
|
||||
userEmail: string | null;
|
||||
|
||||
Reference in New Issue
Block a user