remove ai-model-preferences var env and config (#20859)
Split the single AI_MODEL_PREFERENCES JSON config into 4 array configs and migrates existing workspace data.
This commit is contained in:
+118
@@ -0,0 +1,118 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource, IsNull, QueryRunner } from 'typeorm';
|
||||
|
||||
import {
|
||||
KeyValuePairEntity,
|
||||
KeyValuePairType,
|
||||
} from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
|
||||
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface';
|
||||
import { aiModelPreferencesSchema } from 'src/engine/metadata-modules/ai/ai-models/types/ai-model-preferences.schema';
|
||||
|
||||
const NEW_KEYS = [
|
||||
'AI_MODELS_DEFAULT_FAST',
|
||||
'AI_MODELS_DEFAULT_SMART',
|
||||
'AI_MODELS_DEFAULT_RECOMMENDED',
|
||||
'AI_MODELS_DEFAULT_DISABLED',
|
||||
] as const;
|
||||
|
||||
const PREFERENCE_KEY_MAP = {
|
||||
AI_MODELS_DEFAULT_FAST: 'defaultFastModels',
|
||||
AI_MODELS_DEFAULT_SMART: 'defaultSmartModels',
|
||||
AI_MODELS_DEFAULT_RECOMMENDED: 'recommendedModels',
|
||||
AI_MODELS_DEFAULT_DISABLED: 'disabledModels',
|
||||
} as const;
|
||||
|
||||
@RegisteredInstanceCommand('2.9.0', 1799000010000, { type: 'slow' })
|
||||
export class MigrateAiModelPreferencesSlowInstanceCommand
|
||||
implements SlowInstanceCommand
|
||||
{
|
||||
private readonly logger = new Logger(
|
||||
MigrateAiModelPreferencesSlowInstanceCommand.name,
|
||||
);
|
||||
|
||||
async runDataMigration(dataSource: DataSource): Promise<void> {
|
||||
const keyValuePairRepository = dataSource.getRepository(KeyValuePairEntity);
|
||||
|
||||
const existingRow = await keyValuePairRepository.findOne({
|
||||
where: {
|
||||
type: KeyValuePairType.CONFIG_VARIABLE,
|
||||
key: 'AI_MODEL_PREFERENCES',
|
||||
userId: IsNull(),
|
||||
workspaceId: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!isDefined(existingRow)) {
|
||||
this.logger.log(
|
||||
'No server-level AI_MODEL_PREFERENCES row found, skipping',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const parseResult = aiModelPreferencesSchema.safeParse(existingRow.value);
|
||||
|
||||
if (!parseResult.success) {
|
||||
this.logger.error(
|
||||
`Failed to parse server-level AI_MODEL_PREFERENCES: ${parseResult.error.message}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const prefs = parseResult.data;
|
||||
|
||||
this.logger.log('Migrating server-level AI_MODEL_PREFERENCES');
|
||||
|
||||
await dataSource.transaction(async (manager) => {
|
||||
const transactionalRepository = manager.getRepository(KeyValuePairEntity);
|
||||
|
||||
for (const newKey of NEW_KEYS) {
|
||||
const prefField = PREFERENCE_KEY_MAP[newKey];
|
||||
const value = prefs[prefField];
|
||||
|
||||
if (!isDefined(value) || value.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existingNewKeyCount = await transactionalRepository.count({
|
||||
where: {
|
||||
type: KeyValuePairType.CONFIG_VARIABLE,
|
||||
key: newKey,
|
||||
userId: IsNull(),
|
||||
workspaceId: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
if (existingNewKeyCount > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await transactionalRepository.insert({
|
||||
key: newKey,
|
||||
value: value as unknown as JSON,
|
||||
type: KeyValuePairType.CONFIG_VARIABLE,
|
||||
userId: null,
|
||||
workspaceId: null,
|
||||
});
|
||||
}
|
||||
|
||||
await transactionalRepository.delete({ id: existingRow.id });
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
'Migrated server-level AI_MODEL_PREFERENCES to 4 individual vars',
|
||||
);
|
||||
}
|
||||
|
||||
public async up(_queryRunner: QueryRunner): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
public async down(_queryRunner: QueryRunner): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
|
||||
import { MigrateAiModelPreferencesCommand } from 'src/database/commands/upgrade-version-command/2-9/2-9-workspace-command-1799000000000-migrate-ai-model-preferences.command';
|
||||
import { KeyValuePairEntity } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([KeyValuePairEntity]),
|
||||
WorkspaceIteratorModule,
|
||||
],
|
||||
providers: [MigrateAiModelPreferencesCommand],
|
||||
})
|
||||
export class V2_9_UpgradeVersionCommandModule {}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
|
||||
import { isArray, isDefined } from 'class-validator';
|
||||
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-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 {
|
||||
KeyValuePairEntity,
|
||||
KeyValuePairType,
|
||||
} from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
|
||||
import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
|
||||
import { aiModelPreferencesSchema } from 'src/engine/metadata-modules/ai/ai-models/types/ai-model-preferences.schema';
|
||||
|
||||
const NEW_KEYS = [
|
||||
'AI_MODELS_DEFAULT_FAST',
|
||||
'AI_MODELS_DEFAULT_SMART',
|
||||
'AI_MODELS_DEFAULT_RECOMMENDED',
|
||||
'AI_MODELS_DEFAULT_DISABLED',
|
||||
] as const;
|
||||
|
||||
const PREFERENCE_KEY_MAP = {
|
||||
AI_MODELS_DEFAULT_FAST: 'defaultFastModels',
|
||||
AI_MODELS_DEFAULT_SMART: 'defaultSmartModels',
|
||||
AI_MODELS_DEFAULT_RECOMMENDED: 'recommendedModels',
|
||||
AI_MODELS_DEFAULT_DISABLED: 'disabledModels',
|
||||
} as const;
|
||||
|
||||
@RegisteredWorkspaceCommand('2.9.0', 1799000000000)
|
||||
@Command({
|
||||
name: 'upgrade:2-9:migrate-ai-model-preferences',
|
||||
description:
|
||||
'Migrate AI_MODEL_PREFERENCES config var to the four individual AI_MODELS_DEFAULT_* vars, per workspace',
|
||||
})
|
||||
export class MigrateAiModelPreferencesCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
|
||||
constructor(
|
||||
protected readonly workspaceIteratorService: WorkspaceIteratorService,
|
||||
@InjectRepository(KeyValuePairEntity)
|
||||
private readonly keyValuePairRepository: Repository<KeyValuePairEntity>,
|
||||
) {
|
||||
super(workspaceIteratorService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
const existingPreferencesRow = await this.keyValuePairRepository.findOne({
|
||||
where: {
|
||||
key: 'AI_MODEL_PREFERENCES',
|
||||
type: KeyValuePairType.CONFIG_VARIABLE,
|
||||
workspaceId,
|
||||
userId: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
if (existingPreferencesRow === null) {
|
||||
this.logger.log(
|
||||
`No AI_MODEL_PREFERENCES row found for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const parseResult = aiModelPreferencesSchema.safeParse(
|
||||
existingPreferencesRow.value,
|
||||
);
|
||||
|
||||
if (!parseResult.success) {
|
||||
this.logger.error(
|
||||
`Failed to parse AI_MODEL_PREFERENCES for workspace ${workspaceId}: ${parseResult.error.message}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const prefs = parseResult.data;
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Migrating AI_MODEL_PREFERENCES for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
for (const newKey of NEW_KEYS) {
|
||||
const prefField = PREFERENCE_KEY_MAP[newKey];
|
||||
if (
|
||||
!isDefined(prefField) ||
|
||||
(isArray(prefs[prefField]) && prefs[prefField].length === 0)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const value = prefs[prefField];
|
||||
|
||||
if (isDryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would insert ${newKey} = ${JSON.stringify(value)} for workspace ${workspaceId}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const existingNewKeyRow = await this.keyValuePairRepository.findOne({
|
||||
where: {
|
||||
key: newKey,
|
||||
type: KeyValuePairType.CONFIG_VARIABLE,
|
||||
workspaceId,
|
||||
userId: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
if (existingNewKeyRow !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.keyValuePairRepository.insert({
|
||||
key: newKey,
|
||||
value: value as unknown as JSON,
|
||||
type: KeyValuePairType.CONFIG_VARIABLE,
|
||||
workspaceId,
|
||||
userId: null,
|
||||
});
|
||||
}
|
||||
|
||||
if (!isDryRun) {
|
||||
await this.keyValuePairRepository.delete({
|
||||
id: existingPreferencesRow.id,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Migrated AI_MODEL_PREFERENCES to 4 individual vars for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -55,6 +55,7 @@ import { AddChannelSyncStageIndexesFastInstanceCommand } from 'src/database/comm
|
||||
import { FinalizeRolePermissionFlagCutoverFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-7/2-7-instance-command-fast-1779600000000-finalize-role-permission-flag-cutover';
|
||||
import { AddSubFieldNameToIndexFieldMetadataFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-8/2-8-instance-command-fast-1798200000000-add-sub-field-name-to-index-field-metadata';
|
||||
import { DropFieldMetadataIsUniqueColumnFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-8/2-8-instance-command-fast-1798300000000-drop-field-metadata-is-unique-column';
|
||||
import { MigrateAiModelPreferencesSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-9/2-9-instance-command-slow-1799000010000-migrate-ai-model-preferences';
|
||||
|
||||
export const INSTANCE_COMMANDS = [
|
||||
AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand,
|
||||
@@ -112,4 +113,5 @@ export const INSTANCE_COMMANDS = [
|
||||
FinalizeRolePermissionFlagCutoverFastInstanceCommand,
|
||||
AddSubFieldNameToIndexFieldMetadataFastInstanceCommand,
|
||||
DropFieldMetadataIsUniqueColumnFastInstanceCommand,
|
||||
MigrateAiModelPreferencesSlowInstanceCommand,
|
||||
];
|
||||
|
||||
+2
@@ -11,6 +11,7 @@ import { V2_4_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-
|
||||
import { V2_5_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-5/2-5-upgrade-version-command.module';
|
||||
import { V2_7_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-7/2-7-upgrade-version-command.module';
|
||||
import { V2_8_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-8/2-8-upgrade-version-command.module';
|
||||
import { V2_9_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-9/2-9-upgrade-version-command.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -25,6 +26,7 @@ import { V2_8_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-
|
||||
V2_5_UpgradeVersionCommandModule,
|
||||
V2_7_UpgradeVersionCommandModule,
|
||||
V2_8_UpgradeVersionCommandModule,
|
||||
V2_9_UpgradeVersionCommandModule,
|
||||
],
|
||||
})
|
||||
export class WorkspaceCommandProviderModule {}
|
||||
|
||||
Reference in New Issue
Block a user