Fix Tasks/Notes created with null position (#9068)

Fixes https://github.com/twentyhq/twenty/issues/8810
Fixes https://github.com/twentyhq/twenty/issues/5268
Fixes https://github.com/twentyhq/twenty/issues/8971

- Fixing Task/Note creation not sending position during creation
- Adding a command to backfill position being null, using existing
backfill command.
- Removed unused backfill job.
- Updated workspace entities to set position non-nullable and set a
default value to make it non-required on the API
- Updated position factory to set a default position for all objects
having a POSITION field instead of only company/people
- Moved the try/catch in each resolver factory calling
GraphqlQueryRunnerException handler, makes more sense to call it in the
actual graphql-query-runner and removing some duplicate codes
- Adding validations for input in QueryRunnerArgs factories
- Allow sync-metadata to override and sync defaultValues for certain
field types (that can't be updated by users)
- Removing health-check from sync-metadata command during force mode to
improve performances
This commit is contained in:
Weiko
2024-12-16 14:45:54 +01:00
committed by GitHub
parent 2ceb1c87b3
commit 5a27491bb2
55 changed files with 556 additions and 513 deletions

View File

@ -31,7 +31,7 @@ export const useOpenCreateActivityDrawer = ({
const setHotkeyScope = useSetHotkeyScope(); const setHotkeyScope = useSetHotkeyScope();
const { createOneRecord: createOneActivity } = useCreateOneRecord< const { createOneRecord: createOneActivity } = useCreateOneRecord<
Task | Note (Task | Note) & { position: 'first' | 'last' }
>({ >({
objectNameSingular: activityObjectNameSingular, objectNameSingular: activityObjectNameSingular,
}); });
@ -74,6 +74,7 @@ export const useOpenCreateActivityDrawer = ({
const activity = await createOneActivity({ const activity = await createOneActivity({
assigneeId: customAssignee?.id, assigneeId: customAssignee?.id,
position: 'last',
}); });
if (targetableObjects.length > 0) { if (targetableObjects.length > 0) {

View File

@ -1,10 +1,10 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { DatabaseCommandModule } from 'src/database/commands/database-command.module';
import { WorkspaceHealthCommandModule } from 'src/engine/workspace-manager/workspace-health/commands/workspace-health-command.module';
import { WorkspaceCleanerModule } from 'src/engine/workspace-manager/workspace-cleaner/workspace-cleaner.module';
import { AppModule } from 'src/app.module'; import { AppModule } from 'src/app.module';
import { WorkspaceMigrationRunnerCommandsModule } from 'src/engine/workspace-manager/workspace-migration-runner/commands/workspace-sync-metadata-commands.module'; import { DatabaseCommandModule } from 'src/database/commands/database-command.module';
import { WorkspaceCleanerModule } from 'src/engine/workspace-manager/workspace-cleaner/workspace-cleaner.module';
import { WorkspaceHealthCommandModule } from 'src/engine/workspace-manager/workspace-health/commands/workspace-health-command.module';
import { WorkspaceMigrationRunnerCommandsModule } from 'src/engine/workspace-manager/workspace-migration-runner/commands/workspace-migration-runner-commands.module';
import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module'; import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module';
@Module({ @Module({

View File

@ -10,6 +10,7 @@ import { ConfirmationQuestion } from 'src/database/commands/questions/confirmati
import { UpgradeTo0_32CommandModule } from 'src/database/commands/upgrade-version/0-32/0-32-upgrade-version.module'; import { UpgradeTo0_32CommandModule } from 'src/database/commands/upgrade-version/0-32/0-32-upgrade-version.module';
import { UpgradeTo0_33CommandModule } from 'src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module'; import { UpgradeTo0_33CommandModule } from 'src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module';
import { UpgradeTo0_34CommandModule } from 'src/database/commands/upgrade-version/0-34/0-34-upgrade-version.module'; import { UpgradeTo0_34CommandModule } from 'src/database/commands/upgrade-version/0-34/0-34-upgrade-version.module';
import { UpgradeTo0_40CommandModule } from 'src/database/commands/upgrade-version/0-40/0-40-upgrade-version.module';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
@ -52,6 +53,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
UpgradeTo0_33CommandModule, UpgradeTo0_33CommandModule,
UpgradeTo0_34CommandModule, UpgradeTo0_34CommandModule,
FeatureFlagModule, FeatureFlagModule,
UpgradeTo0_40CommandModule,
], ],
providers: [ providers: [
DataSeedWorkspaceCommand, DataSeedWorkspaceCommand,

View File

@ -0,0 +1,36 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
import { BaseCommandOptions } from 'src/database/commands/base.command';
import { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@Command({
name: 'migrate-0.40:backfill-record-position',
description: 'Backfill record position',
})
export class RecordPositionBackfillCommand extends ActiveWorkspacesCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
private readonly recordPositionBackfillService: RecordPositionBackfillService,
) {
super(workspaceRepository);
}
async executeActiveWorkspacesCommand(
_passedParam: string[],
options: BaseCommandOptions,
workspaceIds: string[],
): Promise<void> {
for (const workspaceId of workspaceIds) {
await this.recordPositionBackfillService.backfill(
workspaceId,
options.dryRun ?? false,
);
}
}
}

View File

@ -0,0 +1,46 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
import { BaseCommandOptions } from 'src/database/commands/base.command';
import { RecordPositionBackfillCommand } from 'src/database/commands/upgrade-version/0-40/0-40-record-position-backfill.command';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
@Command({
name: 'upgrade-0.40',
description: 'Upgrade to 0.40',
})
export class UpgradeTo0_40Command extends ActiveWorkspacesCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
private readonly recordPositionBackfillCommand: RecordPositionBackfillCommand,
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
) {
super(workspaceRepository);
}
async executeActiveWorkspacesCommand(
passedParam: string[],
options: BaseCommandOptions,
workspaceIds: string[],
): Promise<void> {
await this.recordPositionBackfillCommand.executeActiveWorkspacesCommand(
passedParam,
options,
workspaceIds,
);
await this.syncWorkspaceMetadataCommand.executeActiveWorkspacesCommand(
passedParam,
{
...options,
force: true,
},
workspaceIds,
);
}
}

View File

@ -0,0 +1,18 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { RecordPositionBackfillCommand } from 'src/database/commands/upgrade-version/0-40/0-40-record-position-backfill.command';
import { UpgradeTo0_40Command } from 'src/database/commands/upgrade-version/0-40/0-40-upgrade-version.command';
import { RecordPositionBackfillModule } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module';
@Module({
imports: [
TypeOrmModule.forFeature([Workspace], 'core'),
WorkspaceSyncMetadataCommandsModule,
RecordPositionBackfillModule,
],
providers: [UpgradeTo0_40Command, RecordPositionBackfillCommand],
})
export class UpgradeTo0_40CommandModule {}

View File

@ -18,6 +18,7 @@ import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service'; import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory'; import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory';
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory'; import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service'; import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@ -56,77 +57,81 @@ export abstract class GraphqlQueryBaseResolverService<
args: Input, args: Input,
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,
operationName: WorkspaceResolverBuilderMethodNames, operationName: WorkspaceResolverBuilderMethodNames,
): Promise<Response> { ): Promise<Response | undefined> {
const { authContext, objectMetadataItemWithFieldMaps } = options; try {
const { authContext, objectMetadataItemWithFieldMaps } = options;
await this.validate(args, options); await this.validate(args, options);
const hookedArgs = const hookedArgs =
await this.workspaceQueryHookService.executePreQueryHooks( await this.workspaceQueryHookService.executePreQueryHooks(
authContext,
objectMetadataItemWithFieldMaps.nameSingular,
operationName,
args,
);
const computedArgs = (await this.queryRunnerArgsFactory.create(
hookedArgs,
options,
ResolverArgsType[capitalize(operationName)],
)) as Input;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
);
const repository = dataSource.getRepository(
objectMetadataItemWithFieldMaps.nameSingular,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataItemWithFieldMaps.fieldsByName,
options.objectMetadataMaps,
);
const selectedFields = graphqlFields(options.info);
const graphqlQuerySelectedFieldsResult =
graphqlQueryParser.parseSelectedFields(
objectMetadataItemWithFieldMaps,
selectedFields,
);
const graphqlQueryResolverExecutionArgs = {
args: computedArgs,
options,
dataSource,
repository,
graphqlQueryParser,
graphqlQuerySelectedFieldsResult,
};
const results = await this.resolve(graphqlQueryResolverExecutionArgs);
const resultWithGetters = await this.queryResultGettersFactory.create(
results,
objectMetadataItemWithFieldMaps,
authContext.workspace.id,
options.objectMetadataMaps,
);
const resultWithGettersArray = Array.isArray(resultWithGetters)
? resultWithGetters
: [resultWithGetters];
await this.workspaceQueryHookService.executePostQueryHooks(
authContext, authContext,
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
operationName, operationName,
args, resultWithGettersArray,
); );
const computedArgs = (await this.queryRunnerArgsFactory.create( return resultWithGetters;
hookedArgs, } catch (error) {
options, workspaceQueryRunnerGraphqlApiExceptionHandler(error, options);
ResolverArgsType[capitalize(operationName)], }
)) as Input;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
);
const repository = dataSource.getRepository(
objectMetadataItemWithFieldMaps.nameSingular,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataItemWithFieldMaps.fieldsByName,
options.objectMetadataMaps,
);
const selectedFields = graphqlFields(options.info);
const graphqlQuerySelectedFieldsResult =
graphqlQueryParser.parseSelectedFields(
objectMetadataItemWithFieldMaps,
selectedFields,
);
const graphqlQueryResolverExecutionArgs = {
args: computedArgs,
options,
dataSource,
repository,
graphqlQueryParser,
graphqlQuerySelectedFieldsResult,
};
const results = await this.resolve(graphqlQueryResolverExecutionArgs);
const resultWithGetters = await this.queryResultGettersFactory.create(
results,
objectMetadataItemWithFieldMaps,
authContext.workspace.id,
options.objectMetadataMaps,
);
const resultWithGettersArray = Array.isArray(resultWithGetters)
? resultWithGetters
: [resultWithGetters];
await this.workspaceQueryHookService.executePostQueryHooks(
authContext,
objectMetadataItemWithFieldMaps.nameSingular,
operationName,
resultWithGettersArray,
);
return resultWithGetters;
} }
protected abstract resolve( protected abstract resolve(

View File

@ -83,8 +83,6 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
}); });
} }
async;
async validate( async validate(
args: CreateOneResolverArgs<Partial<ObjectRecord>>, args: CreateOneResolverArgs<Partial<ObjectRecord>>,
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,

View File

@ -45,7 +45,7 @@ export class RecordPositionQueryFactory {
objectMetadata: { isCustom: boolean; nameSingular: string }, objectMetadata: { isCustom: boolean; nameSingular: string },
dataSourceSchema: string, dataSourceSchema: string,
): [RecordPositionQuery, RecordPositionQueryParams] { ): [RecordPositionQuery, RecordPositionQueryParams] {
const name = computeTableName( const tableName = computeTableName(
objectMetadata.nameSingular, objectMetadata.nameSingular,
objectMetadata.isCustom, objectMetadata.isCustom,
); );
@ -54,17 +54,17 @@ export class RecordPositionQueryFactory {
case RecordPositionQueryType.FIND_BY_POSITION: case RecordPositionQueryType.FIND_BY_POSITION:
return this.buildFindByPositionQuery( return this.buildFindByPositionQuery(
recordPositionQueryArgs satisfies FindByPositionQueryArgs, recordPositionQueryArgs satisfies FindByPositionQueryArgs,
name, tableName,
dataSourceSchema, dataSourceSchema,
); );
case RecordPositionQueryType.FIND_MIN_POSITION: case RecordPositionQueryType.FIND_MIN_POSITION:
return this.buildFindMinPositionQuery(name, dataSourceSchema); return this.buildFindMinPositionQuery(tableName, dataSourceSchema);
case RecordPositionQueryType.FIND_MAX_POSITION: case RecordPositionQueryType.FIND_MAX_POSITION:
return this.buildFindMaxPositionQuery(name, dataSourceSchema); return this.buildFindMaxPositionQuery(tableName, dataSourceSchema);
case RecordPositionQueryType.UPDATE_POSITION: case RecordPositionQueryType.UPDATE_POSITION:
return this.buildUpdatePositionQuery( return this.buildUpdatePositionQuery(
recordPositionQueryArgs satisfies UpdatePositionQueryArgs, recordPositionQueryArgs satisfies UpdatePositionQueryArgs,
name, tableName,
dataSourceSchema, dataSourceSchema,
); );
default: default:

View File

@ -1,56 +0,0 @@
import { Command, CommandRunner, Option } from 'nest-commander';
import {
RecordPositionBackfillJob,
RecordPositionBackfillJobData,
} from 'src/engine/api/graphql/workspace-query-runner/jobs/record-position-backfill.job';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
export type RecordPositionBackfillCommandOptions = {
workspaceId: string;
dryRun?: boolean;
};
@Command({
name: 'migrate-0.20:backfill-record-position',
description: 'Backfill record position',
})
export class RecordPositionBackfillCommand extends CommandRunner {
constructor(
@InjectMessageQueue(MessageQueue.recordPositionBackfillQueue)
private readonly messageQueueService: MessageQueueService,
) {
super();
}
@Option({
flags: '-w, --workspace-id [workspace_id]',
description: 'workspace id',
required: true,
})
parseWorkspaceId(value: string): string {
return value;
}
@Option({
flags: '-d, --dry-run [dry run]',
description: 'Dry run: Log backfill actions.',
required: false,
})
dryRun(value: string): boolean {
return Boolean(value);
}
async run(
_passedParam: string[],
options: RecordPositionBackfillCommandOptions,
): Promise<void> {
this.messageQueueService.add<RecordPositionBackfillJobData>(
RecordPositionBackfillJob.name,
{ workspaceId: options.workspaceId, dryRun: options.dryRun ?? false },
{ retryLimit: 3 },
);
}
}

View File

@ -18,6 +18,23 @@ describe('QueryRunnerArgsFactory', () => {
objectMetadataItemWithFieldMaps: { objectMetadataItemWithFieldMaps: {
isCustom: true, isCustom: true,
nameSingular: 'testNumber', nameSingular: 'testNumber',
fields: [
{
type: FieldMetadataType.POSITION,
isCustom: true,
nameSingular: 'position',
},
{
type: FieldMetadataType.NUMBER,
isCustom: true,
nameSingular: 'testNumber',
},
{
type: FieldMetadataType.TEXT,
isCustom: true,
nameSingular: 'otherField',
},
],
fieldsByName: { fieldsByName: {
position: { position: {
type: FieldMetadataType.POSITION, type: FieldMetadataType.POSITION,

View File

@ -17,7 +17,6 @@ import {
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { hasPositionField } from 'src/engine/metadata-modules/object-metadata/utils/has-position-field.util';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map'; import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
import { RecordPositionFactory } from './record-position.factory'; import { RecordPositionFactory } from './record-position.factory';
@ -39,9 +38,10 @@ export class QueryRunnerArgsFactory {
const fieldMetadataMapByNameByName = const fieldMetadataMapByNameByName =
options.objectMetadataItemWithFieldMaps.fieldsByName; options.objectMetadataItemWithFieldMaps.fieldsByName;
const shouldBackfillPosition = hasPositionField( const shouldBackfillPosition =
options.objectMetadataItemWithFieldMaps, options.objectMetadataItemWithFieldMaps.fields.some(
); (field) => field.type === FieldMetadataType.POSITION,
);
switch (resolverArgsType) { switch (resolverArgsType) {
case ResolverArgsType.CreateOne: case ResolverArgsType.CreateOne:

View File

@ -2,12 +2,12 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'class-validator'; import { isDefined } from 'class-validator';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { import {
RecordPositionQueryArgs, RecordPositionQueryArgs,
RecordPositionQueryFactory, RecordPositionQueryFactory,
RecordPositionQueryType, RecordPositionQueryType,
} from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory'; } from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
@Injectable() @Injectable()
export class RecordPositionFactory { export class RecordPositionFactory {

View File

@ -1,24 +0,0 @@
import { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
export type RecordPositionBackfillJobData = {
workspaceId: string;
dryRun: boolean;
};
@Processor(MessageQueue.recordPositionBackfillQueue)
export class RecordPositionBackfillJob {
constructor(
private readonly recordPositionBackfillService: RecordPositionBackfillService,
) {}
@Process(RecordPositionBackfillJob.name)
async handle(data: RecordPositionBackfillJobData): Promise<void> {
await this.recordPositionBackfillService.backfill(
data.workspaceId,
data.dryRun,
);
}
}

View File

@ -1,16 +0,0 @@
import { Module } from '@nestjs/common';
import { RecordPositionBackfillJob } from 'src/engine/api/graphql/workspace-query-runner/jobs/record-position-backfill.job';
import { RecordPositionBackfillModule } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
@Module({
imports: [
WorkspaceDataSourceModule,
DataSourceModule,
RecordPositionBackfillModule,
],
providers: [RecordPositionBackfillJob],
})
export class WorkspaceQueryRunnerJobModule {}

View File

@ -1,15 +1,17 @@
import { TestingModule, Test } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { RecordPositionQueryFactory } from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory'; import { RecordPositionQueryFactory } from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory'; import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory';
import { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service'; import { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
describe('RecordPositionBackfillService', () => { describe('RecordPositionBackfillService', () => {
let recordPositionQueryFactory; let recordPositionQueryFactory;
let recordPositionFactory; let recordPositionFactory;
let objectMetadataService; let objectMetadataRepository;
let workspaceDataSourceService; let workspaceDataSourceService;
let service: RecordPositionBackfillService; let service: RecordPositionBackfillService;
@ -27,8 +29,8 @@ describe('RecordPositionBackfillService', () => {
]), ]),
}; };
objectMetadataService = { objectMetadataRepository = {
findManyWithinWorkspace: jest.fn().mockReturnValue([]), find: jest.fn().mockReturnValue([]),
}; };
workspaceDataSourceService = { workspaceDataSourceService = {
@ -51,8 +53,8 @@ describe('RecordPositionBackfillService', () => {
useValue: workspaceDataSourceService, useValue: workspaceDataSourceService,
}, },
{ {
provide: ObjectMetadataService, provide: getRepositoryToken(ObjectMetadataEntity, 'metadata'),
useValue: objectMetadataService, useValue: objectMetadataRepository,
}, },
], ],
}).compile(); }).compile();
@ -76,23 +78,23 @@ describe('RecordPositionBackfillService', () => {
}); });
it('when objectMetadata without position, should do nothing', async () => { it('when objectMetadata without position, should do nothing', async () => {
objectMetadataService.findManyWithinWorkspace.mockReturnValue([ objectMetadataRepository.find.mockReturnValue([]);
{
id: '1',
nameSingular: 'name',
fields: [],
},
]);
await service.backfill('workspaceId', false); await service.backfill('workspaceId', false);
expect(workspaceDataSourceService.executeRawQuery).not.toHaveBeenCalled(); expect(workspaceDataSourceService.executeRawQuery).not.toHaveBeenCalled();
}); });
it('when objectMetadata but all record with position, should create and run query once', async () => { it('when objectMetadata but all record with position, should create and run query once', async () => {
objectMetadataService.findManyWithinWorkspace.mockReturnValue([ objectMetadataRepository.find.mockReturnValue([
{ {
id: '1', id: '1',
nameSingular: 'company', nameSingular: 'company',
fields: [], fields: [
{
type: FieldMetadataType.POSITION,
isCustom: true,
nameSingular: 'position',
},
],
}, },
]); ]);
await service.backfill('workspaceId', false); await service.backfill('workspaceId', false);
@ -100,11 +102,17 @@ describe('RecordPositionBackfillService', () => {
}); });
it('when record without position, should create and run query twice', async () => { it('when record without position, should create and run query twice', async () => {
objectMetadataService.findManyWithinWorkspace.mockReturnValue([ objectMetadataRepository.find.mockReturnValue([
{ {
id: '1', id: '1',
nameSingular: 'company', nameSingular: 'company',
fields: [], fields: [
{
type: FieldMetadataType.POSITION,
isCustom: true,
nameSingular: 'position',
},
],
}, },
]); ]);
workspaceDataSourceService.executeRawQuery.mockResolvedValueOnce([ workspaceDataSourceService.executeRawQuery.mockResolvedValueOnce([
@ -119,11 +127,17 @@ describe('RecordPositionBackfillService', () => {
}); });
it('when dryRun is true, should not update position', async () => { it('when dryRun is true, should not update position', async () => {
objectMetadataService.findManyWithinWorkspace.mockReturnValue([ objectMetadataRepository.find.mockReturnValue([
{ {
id: '1', id: '1',
nameSingular: 'company', nameSingular: 'company',
fields: [], fields: [
{
type: FieldMetadataType.POSITION,
isCustom: true,
nameSingular: 'position',
},
],
}, },
]); ]);
workspaceDataSourceService.executeRawQuery.mockResolvedValueOnce([ workspaceDataSourceService.executeRawQuery.mockResolvedValueOnce([

View File

@ -1,13 +1,17 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { RecordPositionQueryFactory } from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory'; import { RecordPositionQueryFactory } from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory'; import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory';
import { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service'; import { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
@Module({ @Module({
imports: [WorkspaceDataSourceModule, ObjectMetadataModule], imports: [
WorkspaceDataSourceModule,
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
],
providers: [ providers: [
RecordPositionFactory, RecordPositionFactory,
RecordPositionQueryFactory, RecordPositionQueryFactory,

View File

@ -1,21 +1,24 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'class-validator'; import { isDefined } from 'class-validator';
import { Repository } from 'typeorm';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { import {
RecordPositionQueryFactory, RecordPositionQueryFactory,
RecordPositionQueryType, RecordPositionQueryType,
} from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory'; } from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory'; import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { hasPositionField } from 'src/engine/metadata-modules/object-metadata/utils/has-position-field.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
@Injectable() @Injectable()
export class RecordPositionBackfillService { export class RecordPositionBackfillService {
private readonly logger = new Logger(RecordPositionBackfillService.name); private readonly logger = new Logger(RecordPositionBackfillService.name);
constructor( constructor(
private readonly objectMetadataService: ObjectMetadataService, @InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
private readonly recordPositionFactory: RecordPositionFactory, private readonly recordPositionFactory: RecordPositionFactory,
private readonly recordPositionQueryFactory: RecordPositionQueryFactory, private readonly recordPositionQueryFactory: RecordPositionQueryFactory,
private readonly workspaceDataSourceService: WorkspaceDataSourceService, private readonly workspaceDataSourceService: WorkspaceDataSourceService,
@ -29,15 +32,20 @@ export class RecordPositionBackfillService {
const dataSourceSchema = const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId); this.workspaceDataSourceService.getSchemaName(workspaceId);
const objectMetadataEntities = const objectMetadataCollection = await this.objectMetadataRepository.find({
await this.objectMetadataService.findManyWithinWorkspace(workspaceId, { where: {
where: { isSystem: false }, workspaceId,
}); fields: {
name: 'position',
type: FieldMetadataType.POSITION,
},
},
relations: {
fields: true,
},
});
const objectMetadataWithPosition = for (const objectMetadata of objectMetadataCollection) {
objectMetadataEntities.filter(hasPositionField);
for (const objectMetadata of objectMetadataWithPosition) {
const [recordsWithoutPositionQuery, recordsWithoutPositionQueryParams] = const [recordsWithoutPositionQuery, recordsWithoutPositionQueryParams] =
this.recordPositionQueryFactory.create( this.recordPositionQueryFactory.create(
{ {

View File

@ -1,6 +1,6 @@
import { QueryFailedError } from 'typeorm'; import { QueryFailedError } from 'typeorm';
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { import {
GraphqlQueryRunnerException, GraphqlQueryRunnerException,
@ -20,7 +20,7 @@ import {
export const workspaceQueryRunnerGraphqlApiExceptionHandler = ( export const workspaceQueryRunnerGraphqlApiExceptionHandler = (
error: Error, error: Error,
context: WorkspaceSchemaBuilderContext, context: WorkspaceQueryRunnerOptions,
) => { ) => {
if (error instanceof QueryFailedError) { if (error instanceof QueryFailedError) {
if ( if (
@ -96,6 +96,7 @@ export const workspaceQueryRunnerGraphqlApiExceptionHandler = (
case GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR: case GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR:
case GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT: case GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT:
case GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND: case GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND:
case GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
throw new UserInputError(error.message); throw new UserInputError(error.message);
case GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND: case GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND:
throw new NotFoundError(error.message); throw new NotFoundError(error.message);

View File

@ -2,7 +2,6 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceQueryBuilderModule } from 'src/engine/api/graphql/workspace-query-builder/workspace-query-builder.module'; import { WorkspaceQueryBuilderModule } from 'src/engine/api/graphql/workspace-query-builder/workspace-query-builder.module';
import { RecordPositionBackfillCommand } from 'src/engine/api/graphql/workspace-query-runner/commands/0-20-record-position-backfill.command';
import { workspaceQueryRunnerFactories } from 'src/engine/api/graphql/workspace-query-runner/factories'; import { workspaceQueryRunnerFactories } from 'src/engine/api/graphql/workspace-query-runner/factories';
import { TelemetryListener } from 'src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener'; import { TelemetryListener } from 'src/engine/api/graphql/workspace-query-runner/listeners/telemetry.listener';
import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module'; import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module';
@ -35,7 +34,6 @@ import { EntityEventsToDbListener } from './listeners/entity-events-to-db.listen
...workspaceQueryRunnerFactories, ...workspaceQueryRunnerFactories,
EntityEventsToDbListener, EntityEventsToDbListener,
TelemetryListener, TelemetryListener,
RecordPositionBackfillCommand,
], ],
exports: [...workspaceQueryRunnerFactories], exports: [...workspaceQueryRunnerFactories],
}) })

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service'; import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class CreateManyResolverFactory export class CreateManyResolverFactory
@ -27,23 +26,19 @@ export class CreateManyResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, _context, info) => { return async (_source, args, _context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphqlQueryRunnerService.execute( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
CreateManyResolverFactory.methodName, CreateManyResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, context);
}
}; };
} }
} }

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryCreateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service'; import { GraphqlQueryCreateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class CreateOneResolverFactory export class CreateOneResolverFactory
@ -27,23 +26,19 @@ export class CreateOneResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, _context, info) => { return async (_source, args, _context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphqlQueryRunnerService.execute( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
CreateOneResolverFactory.methodName, CreateOneResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
}
}; };
} }
} }

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryDeleteManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service'; import { GraphqlQueryDeleteManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class DeleteManyResolverFactory export class DeleteManyResolverFactory
@ -27,23 +26,19 @@ export class DeleteManyResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, context, info) => { return async (_source, args, context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphqlQueryRunnerService.execute( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
DeleteManyResolverFactory.methodName, DeleteManyResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
}
}; };
} }
} }

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryDeleteOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service'; import { GraphqlQueryDeleteOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class DeleteOneResolverFactory export class DeleteOneResolverFactory
@ -27,23 +26,19 @@ export class DeleteOneResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, context, info) => { return async (_source, args, context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphqlQueryRunnerService.execute( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
DeleteOneResolverFactory.methodName, DeleteOneResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
}
}; };
} }
} }

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryDestroyManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service'; import { GraphqlQueryDestroyManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class DestroyManyResolverFactory export class DestroyManyResolverFactory
@ -27,23 +26,19 @@ export class DestroyManyResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, context, info) => { return async (_source, args, context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphqlQueryRunnerService.execute( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
DestroyManyResolverFactory.methodName, DestroyManyResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
}
}; };
} }
} }

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service'; import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class DestroyOneResolverFactory export class DestroyOneResolverFactory
@ -27,23 +26,19 @@ export class DestroyOneResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, context, info) => { return async (_source, args, context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphQLQueryRunnerService.execute( return await this.graphQLQueryRunnerService.execute(
args, args,
options, options,
DestroyOneResolverFactory.methodName, DestroyOneResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
}
}; };
} }
} }

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service'; import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class FindDuplicatesResolverFactory export class FindDuplicatesResolverFactory
@ -27,23 +26,19 @@ export class FindDuplicatesResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, context, info) => { return async (_source, args, context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphqlQueryRunnerService.execute( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
FindDuplicatesResolverFactory.methodName, FindDuplicatesResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
}
}; };
} }
} }

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class FindManyResolverFactory export class FindManyResolverFactory
@ -27,23 +26,19 @@ export class FindManyResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, _context, info) => { return async (_source, args, _context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphqlQueryRunnerService.execute( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
FindManyResolverFactory.methodName, FindManyResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
}
}; };
} }
} }

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service'; import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class FindOneResolverFactory export class FindOneResolverFactory
@ -27,23 +26,19 @@ export class FindOneResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, _context, info) => { return async (_source, args, _context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphqlQueryRunnerService.execute( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
FindOneResolverFactory.methodName, FindOneResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
}
}; };
} }
} }

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryRestoreManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service'; import { GraphqlQueryRestoreManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class RestoreManyResolverFactory export class RestoreManyResolverFactory
@ -27,23 +26,19 @@ export class RestoreManyResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, context, info) => { return async (_source, args, context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphqlQueryRunnerService.execute( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
RestoreManyResolverFactory.methodName, RestoreManyResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
}
}; };
} }
} }

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryRestoreOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service'; import { GraphqlQueryRestoreOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class RestoreOneResolverFactory export class RestoreOneResolverFactory
@ -27,23 +26,19 @@ export class RestoreOneResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, context, info) => { return async (_source, args, context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphqlQueryRunnerService.execute( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
RestoreOneResolverFactory.methodName, RestoreOneResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
}
}; };
} }
} }

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service'; import { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class SearchResolverFactory export class SearchResolverFactory
@ -25,23 +24,19 @@ export class SearchResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, _context, info) => { return async (_source, args, _context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphqlQueryRunnerService.execute( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
SearchResolverFactory.methodName, SearchResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
}
}; };
} }
} }

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service'; import { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class UpdateManyResolverFactory export class UpdateManyResolverFactory
@ -27,23 +26,19 @@ export class UpdateManyResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, context, info) => { return async (_source, args, context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphqlQueryRunnerService.execute( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
UpdateManyResolverFactory.methodName, UpdateManyResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
}
}; };
} }
} }

View File

@ -9,7 +9,6 @@ import {
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service'; import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
export class UpdateOneResolverFactory export class UpdateOneResolverFactory
@ -27,23 +26,19 @@ export class UpdateOneResolverFactory
const internalContext = context; const internalContext = context;
return async (_source, args, context, info) => { return async (_source, args, context, info) => {
try { const options: WorkspaceQueryRunnerOptions = {
const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext,
authContext: internalContext.authContext, info,
info, objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataMaps: internalContext.objectMetadataMaps, objectMetadataItemWithFieldMaps:
objectMetadataItemWithFieldMaps: internalContext.objectMetadataItemWithFieldMaps,
internalContext.objectMetadataItemWithFieldMaps, };
};
return await this.graphqlQueryRunnerService.execute( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
UpdateOneResolverFactory.methodName, UpdateOneResolverFactory.methodName,
); );
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
}
}; };
} }
} }

View File

@ -1,11 +1,16 @@
import { Logger } from '@nestjs/common/services/logger.service'; import { Logger } from '@nestjs/common/services/logger.service';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'class-validator';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
import { buildCreatedByFromWorkspaceMember } from 'src/engine/core-modules/actor/utils/build-created-by-from-workspace-member.util'; import { buildCreatedByFromWorkspaceMember } from 'src/engine/core-modules/actor/utils/build-created-by-from-workspace-member.util';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
@ -45,6 +50,13 @@ export class CreatedByCreateManyPreQueryHook
): Promise<CreateManyResolverArgs<CustomWorkspaceItem>> { ): Promise<CreateManyResolverArgs<CustomWorkspaceItem>> {
let createdBy: ActorMetadata | null = null; let createdBy: ActorMetadata | null = null;
if (!isDefined(payload.data)) {
throw new GraphqlQueryRunnerException(
'Payload data is required',
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
);
}
// TODO: Once all objects have it, we can remove this check // TODO: Once all objects have it, we can remove this check
const createdByFieldMetadata = await this.fieldMetadataRepository.findOne({ const createdByFieldMetadata = await this.fieldMetadataRepository.findOne({
where: { where: {

View File

@ -1,11 +1,16 @@
import { Logger } from '@nestjs/common/services/logger.service'; import { Logger } from '@nestjs/common/services/logger.service';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'class-validator';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
import { CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
import { buildCreatedByFromWorkspaceMember } from 'src/engine/core-modules/actor/utils/build-created-by-from-workspace-member.util'; import { buildCreatedByFromWorkspaceMember } from 'src/engine/core-modules/actor/utils/build-created-by-from-workspace-member.util';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
@ -45,6 +50,13 @@ export class CreatedByCreateOnePreQueryHook
): Promise<CreateOneResolverArgs<CustomWorkspaceItem>> { ): Promise<CreateOneResolverArgs<CustomWorkspaceItem>> {
let createdBy: ActorMetadata | null = null; let createdBy: ActorMetadata | null = null;
if (!isDefined(payload.data)) {
throw new GraphqlQueryRunnerException(
'Payload data is required',
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
);
}
// TODO: Once all objects have it, we can remove this check // TODO: Once all objects have it, we can remove this check
const createdByFieldMetadata = await this.fieldMetadataRepository.findOne({ const createdByFieldMetadata = await this.fieldMetadataRepository.findOne({
where: { where: {

View File

@ -5,7 +5,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module'; import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module';
import { DataSeedDemoWorkspaceJob } from 'src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job'; import { DataSeedDemoWorkspaceJob } from 'src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { WorkspaceQueryRunnerJobModule } from 'src/engine/api/graphql/workspace-query-runner/jobs/workspace-query-runner-job.module';
import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
import { UpdateSubscriptionQuantityJob } from 'src/engine/core-modules/billing/jobs/update-subscription-quantity.job'; import { UpdateSubscriptionQuantityJob } from 'src/engine/core-modules/billing/jobs/update-subscription-quantity.job';
@ -47,7 +46,6 @@ import { WorkflowModule } from 'src/modules/workflow/workflow.module';
CalendarEventParticipantManagerModule, CalendarEventParticipantManagerModule,
TimelineActivityModule, TimelineActivityModule,
StripeModule, StripeModule,
WorkspaceQueryRunnerJobModule,
AutoCompaniesAndContactsCreationJobModule, AutoCompaniesAndContactsCreationJobModule,
TimelineJobModule, TimelineJobModule,
WebhookJobModule, WebhookJobModule,

View File

@ -1,6 +0,0 @@
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
export const hasPositionField = (objectMetadataItem: ObjectMetadataInterface) =>
['opportunity', 'person', 'company'].includes(
objectMetadataItem.nameSingular,
) || objectMetadataItem.isCustom;

View File

@ -52,10 +52,10 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
description: 'Position', description: 'Position',
type: FieldMetadataType.POSITION, type: FieldMetadataType.POSITION,
icon: 'IconHierarchy2', icon: 'IconHierarchy2',
defaultValue: 0,
}) })
@WorkspaceIsNullable()
@WorkspaceIsSystem() @WorkspaceIsSystem()
position: number | null; position: number;
@WorkspaceField({ @WorkspaceField({
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy, standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy,

View File

@ -50,40 +50,43 @@ export class SyncWorkspaceMetadataCommand extends ActiveWorkspacesCommandRunner
`Running workspace sync for workspace: ${workspaceId} (${count} out of ${workspaceIds.length})`, `Running workspace sync for workspace: ${workspaceId} (${count} out of ${workspaceIds.length})`,
); );
count++; count++;
try {
const issues =
await this.workspaceHealthService.healthCheck(workspaceId);
// Security: abort if there are issues. if (!options.force) {
if (issues.length > 0) { try {
const issues =
await this.workspaceHealthService.healthCheck(workspaceId);
// Security: abort if there are issues.
if (issues.length > 0) {
if (!options.force) {
this.logger.error(
`Workspace contains ${issues.length} issues, aborting.`,
);
this.logger.log(
'If you want to force the migration, use --force flag',
);
this.logger.log(
'Please use `workspace:health` command to check issues and fix them before running this command.',
);
continue;
}
this.logger.warn(
`Workspace contains ${issues.length} issues, sync has been forced.`,
);
}
} catch (error) {
if (!options.force) { if (!options.force) {
this.logger.error( throw error;
`Workspace contains ${issues.length} issues, aborting.`,
);
this.logger.log(
'If you want to force the migration, use --force flag',
);
this.logger.log(
'Please use `workspace:health` command to check issues and fix them before running this command.',
);
continue;
} }
this.logger.warn( this.logger.warn(
`Workspace contains ${issues.length} issues, sync has been forced.`, `Workspace health check failed with error, but sync has been forced.`,
error,
); );
} }
} catch (error) {
if (!options.force) {
throw error;
}
this.logger.warn(
`Workspace health check failed with error, but sync has been forced.`,
error,
);
} }
try { try {

View File

@ -8,7 +8,10 @@ import {
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface'; } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
import { ComputedPartialFieldMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface'; import { ComputedPartialFieldMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import {
FieldMetadataEntity,
FieldMetadataType,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util'; import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
const commonFieldPropertiesToIgnore = [ const commonFieldPropertiesToIgnore = [
@ -24,10 +27,24 @@ const commonFieldPropertiesToIgnore = [
'asExpression', 'asExpression',
'generatedType', 'generatedType',
'defaultValue', 'defaultValue',
'isLabelSyncedWithName',
]; ];
const fieldPropertiesToStringify = ['defaultValue'] as const; const fieldPropertiesToStringify = ['defaultValue'] as const;
const shouldNotOverrideDefaultValue = (
fieldMetadata: FieldMetadataEntity | ComputedPartialFieldMetadata,
) => {
return [
FieldMetadataType.BOOLEAN,
FieldMetadataType.SELECT,
FieldMetadataType.MULTI_SELECT,
FieldMetadataType.CURRENCY,
FieldMetadataType.PHONES,
FieldMetadataType.ADDRESS,
].includes(fieldMetadata.type);
};
const shouldSkipFieldCreation = ( const shouldSkipFieldCreation = (
standardFieldMetadata: ComputedPartialFieldMetadata | undefined, standardFieldMetadata: ComputedPartialFieldMetadata | undefined,
) => { ) => {
@ -55,7 +72,17 @@ export class WorkspaceFieldComparator {
const originalFieldMetadataMap = transformMetadataForComparison( const originalFieldMetadataMap = transformMetadataForComparison(
filteredOriginalFieldCollection, filteredOriginalFieldCollection,
{ {
shouldIgnoreProperty: (property) => { shouldIgnoreProperty: (
property,
fieldMetadata: FieldMetadataEntity,
) => {
if (
property === 'defaultValue' &&
shouldNotOverrideDefaultValue(fieldMetadata)
) {
return true;
}
if (commonFieldPropertiesToIgnore.includes(property)) { if (commonFieldPropertiesToIgnore.includes(property)) {
return true; return true;
} }
@ -72,7 +99,17 @@ export class WorkspaceFieldComparator {
const standardFieldMetadataMap = transformMetadataForComparison( const standardFieldMetadataMap = transformMetadataForComparison(
standardFieldMetadataCollection, standardFieldMetadataCollection,
{ {
shouldIgnoreProperty: (property) => { shouldIgnoreProperty: (
property,
fieldMetadata: ComputedPartialFieldMetadata,
) => {
if (
property === 'defaultValue' &&
shouldNotOverrideDefaultValue(fieldMetadata)
) {
return true;
}
if (commonFieldPropertiesToIgnore.includes(property)) { if (commonFieldPropertiesToIgnore.includes(property)) {
return true; return true;
} }

View File

@ -147,9 +147,9 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
label: 'Position', label: 'Position',
description: 'Company record position', description: 'Company record position',
icon: 'IconHierarchy2', icon: 'IconHierarchy2',
defaultValue: 0,
}) })
@WorkspaceIsSystem() @WorkspaceIsSystem()
@WorkspaceIsNullable()
position: number; position: number;
@WorkspaceField({ @WorkspaceField({

View File

@ -37,6 +37,7 @@ export class FavoriteFolderWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconList', icon: 'IconList',
defaultValue: 0, defaultValue: 0,
}) })
@WorkspaceIsSystem()
position: number; position: number;
@WorkspaceField({ @WorkspaceField({

View File

@ -48,6 +48,7 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconList', icon: 'IconList',
defaultValue: 0, defaultValue: 0,
}) })
@WorkspaceIsSystem()
position: number; position: number;
// Relations // Relations

View File

@ -55,10 +55,10 @@ export class NoteWorkspaceEntity extends BaseWorkspaceEntity {
label: 'Position', label: 'Position',
description: 'Note record position', description: 'Note record position',
icon: 'IconHierarchy2', icon: 'IconHierarchy2',
defaultValue: 0,
}) })
@WorkspaceIsSystem() @WorkspaceIsSystem()
@WorkspaceIsNullable() position: number;
position: number | null;
@WorkspaceField({ @WorkspaceField({
standardId: NOTE_STANDARD_FIELD_IDS.title, standardId: NOTE_STANDARD_FIELD_IDS.title,

View File

@ -113,10 +113,10 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity {
label: 'Position', label: 'Position',
description: 'Opportunity record position', description: 'Opportunity record position',
icon: 'IconHierarchy2', icon: 'IconHierarchy2',
defaultValue: 0,
}) })
@WorkspaceIsSystem() @WorkspaceIsSystem()
@WorkspaceIsNullable() position: number;
position: number | null;
@WorkspaceField({ @WorkspaceField({
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.createdBy, standardId: OPPORTUNITY_STANDARD_FIELD_IDS.createdBy,

View File

@ -157,9 +157,9 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
label: 'Position', label: 'Position',
description: 'Person record Position', description: 'Person record Position',
icon: 'IconHierarchy2', icon: 'IconHierarchy2',
defaultValue: 0,
}) })
@WorkspaceIsSystem() @WorkspaceIsSystem()
@WorkspaceIsNullable()
position: number; position: number;
@WorkspaceField({ @WorkspaceField({

View File

@ -57,10 +57,10 @@ export class TaskWorkspaceEntity extends BaseWorkspaceEntity {
label: 'Position', label: 'Position',
description: 'Task record position', description: 'Task record position',
icon: 'IconHierarchy2', icon: 'IconHierarchy2',
defaultValue: 0,
}) })
@WorkspaceIsSystem() @WorkspaceIsSystem()
@WorkspaceIsNullable() position: number;
position: number | null;
@WorkspaceField({ @WorkspaceField({
standardId: TASK_STANDARD_FIELD_IDS.title, standardId: TASK_STANDARD_FIELD_IDS.title,

View File

@ -77,6 +77,7 @@ export class ViewFieldWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconList', icon: 'IconList',
defaultValue: 0, defaultValue: 0,
}) })
@WorkspaceIsSystem()
position: number; position: number;
@WorkspaceRelation({ @WorkspaceRelation({

View File

@ -59,6 +59,7 @@ export class ViewGroupWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconList', icon: 'IconList',
defaultValue: 0, defaultValue: 0,
}) })
@WorkspaceIsSystem()
position: number; position: number;
@WorkspaceRelation({ @WorkspaceRelation({

View File

@ -97,8 +97,9 @@ export class ViewWorkspaceEntity extends BaseWorkspaceEntity {
type: FieldMetadataType.POSITION, type: FieldMetadataType.POSITION,
label: 'Position', label: 'Position',
description: 'View position', description: 'View position',
defaultValue: 0,
}) })
@WorkspaceIsNullable() @WorkspaceIsSystem()
position: number; position: number;
@WorkspaceField({ @WorkspaceField({

View File

@ -155,10 +155,10 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
label: 'Position', label: 'Position',
description: 'Workflow run position', description: 'Workflow run position',
icon: 'IconHierarchy2', icon: 'IconHierarchy2',
defaultValue: 0,
}) })
@WorkspaceIsSystem() @WorkspaceIsSystem()
@WorkspaceIsNullable() position: number;
position: number | null;
// Relations // Relations
@WorkspaceRelation({ @WorkspaceRelation({

View File

@ -117,10 +117,10 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
label: 'Position', label: 'Position',
description: 'Workflow version position', description: 'Workflow version position',
icon: 'IconHierarchy2', icon: 'IconHierarchy2',
defaultValue: 0,
}) })
@WorkspaceIsSystem() @WorkspaceIsSystem()
@WorkspaceIsNullable() position: number;
position: number | null;
// Relations // Relations
@WorkspaceRelation({ @WorkspaceRelation({

View File

@ -1,6 +1,10 @@
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import {
ActorMetadata,
FieldActorSource,
} from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { import {
RelationMetadataType, RelationMetadataType,
@ -21,10 +25,6 @@ import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-o
import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity'; import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity';
import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import {
ActorMetadata,
FieldActorSource,
} from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
export enum WorkflowStatus { export enum WorkflowStatus {
DRAFT = 'DRAFT', DRAFT = 'DRAFT',
@ -103,10 +103,10 @@ export class WorkflowWorkspaceEntity extends BaseWorkspaceEntity {
label: 'Position', label: 'Position',
description: 'Workflow record position', description: 'Workflow record position',
icon: 'IconHierarchy2', icon: 'IconHierarchy2',
defaultValue: 0,
}) })
@WorkspaceIsSystem() @WorkspaceIsSystem()
@WorkspaceIsNullable() position: number;
position: number | null;
// Relations // Relations
@WorkspaceRelation({ @WorkspaceRelation({