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,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user