feat: implement skills system for AI agents (#16865)
## Summary This PR introduces a Skills system for AI agents, inspired by the [Agent Skills specification](https://agentskills.io/specification). ## Changes ### Backend - **SkillEntity**: New database entity with migration for storing skills - **V2 Sync Mechanism**: Implemented FlatSkill, builders, validators, and action handlers following the v2 flat entity pattern - **Standard Skills**: Pre-defined skills (workflow-building, data-manipulation, dashboard-building, metadata-building, research, code-interpreter, xlsx, pdf, docx, pptx) - **GraphQL API**: CRUD operations for skills with proper guards and permissions - **Workspace Cache**: Integrated skills into the workspace cache system ### Frontend - **Skills Table**: Searchable table in AI settings showing all skills - **Skill Form**: Create/edit page with Label (primary), Description, and Content (markdown editor) - **API Name**: Following existing patterns, name is derived from label with advanced settings toggle for custom API names - **Standard vs Custom**: Standard skills are read-only, custom skills can be edited/deleted ## Key Design Decisions - Skills are stored in the database (Salesforce-like approach) rather than files - Name is derived from Label by default (isLabelSyncedWithName pattern) - Skills reference functions/files via @ mentions in markdown content rather than explicit relations - Standard skills are synced from code, custom skills are created via UI ## Screenshots Skills table and form UI follow existing settings patterns. ## Testing - [x] Lint passes - [x] Typecheck passes - [ ] CI tests
This commit is contained in:
+37
@@ -0,0 +1,37 @@
|
||||
export const STANDARD_SKILL = {
|
||||
'workflow-building': {
|
||||
universalIdentifier: '20202020-6155-838a-b64e-44a791fbdc13',
|
||||
},
|
||||
'data-manipulation': {
|
||||
universalIdentifier: '20202020-e225-f5c7-3d56-45feaa36f2e6',
|
||||
},
|
||||
'dashboard-building': {
|
||||
universalIdentifier: '20202020-398f-0d7a-82db-4f43bc7e7044',
|
||||
},
|
||||
'metadata-building': {
|
||||
universalIdentifier: '20202020-c66a-5fed-4a74-46e0b42a6332',
|
||||
},
|
||||
research: {
|
||||
universalIdentifier: '20202020-db75-4fca-6813-4c7db0f964a0',
|
||||
},
|
||||
'code-interpreter': {
|
||||
universalIdentifier: '20202020-5eb9-e775-cf4e-4f22be7be362',
|
||||
},
|
||||
xlsx: {
|
||||
universalIdentifier: '20202020-2c7f-5b77-dfa4-494b84752ab7',
|
||||
},
|
||||
pdf: {
|
||||
universalIdentifier: '20202020-c3d1-e0c9-2f93-45648b8bbd26',
|
||||
},
|
||||
docx: {
|
||||
universalIdentifier: '20202020-6f15-2432-0537-4e23a2efd1cb',
|
||||
},
|
||||
pptx: {
|
||||
universalIdentifier: '20202020-c81b-baf8-5255-4c34bd0eac9b',
|
||||
},
|
||||
} as const satisfies Record<
|
||||
string,
|
||||
{
|
||||
universalIdentifier: string;
|
||||
}
|
||||
>;
|
||||
+1
@@ -10,4 +10,5 @@ export const TWENTY_STANDARD_ALL_METADATA_NAME = [
|
||||
'view',
|
||||
'role',
|
||||
'agent',
|
||||
'skill',
|
||||
] as const satisfies AllMetadataName[];
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import type { STANDARD_SKILL } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-skill.constant';
|
||||
|
||||
export type AllStandardSkillName = keyof typeof STANDARD_SKILL;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { type FlatSkill } from 'src/engine/metadata-modules/flat-skill/types/flat-skill.type';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { type CreateStandardSkillArgs } from 'src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-skill-flat-metadata.util';
|
||||
import { STANDARD_FLAT_SKILL_METADATA_BUILDERS_BY_SKILL_NAME } from 'src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-flat-skill-metadata.util';
|
||||
|
||||
export const buildStandardFlatSkillMetadataMaps = (
|
||||
args: Omit<CreateStandardSkillArgs, 'context'>,
|
||||
): FlatEntityMaps<FlatSkill> => {
|
||||
const allSkillMetadatas: FlatSkill[] = Object.values(
|
||||
STANDARD_FLAT_SKILL_METADATA_BUILDERS_BY_SKILL_NAME,
|
||||
).map((builder) => builder(args));
|
||||
|
||||
let flatSkillMetadataMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const skillMetadata of allSkillMetadatas) {
|
||||
flatSkillMetadataMaps = addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: skillMetadata,
|
||||
flatEntityMaps: flatSkillMetadataMaps,
|
||||
});
|
||||
}
|
||||
|
||||
return flatSkillMetadataMaps;
|
||||
};
|
||||
+1044
File diff suppressed because it is too large
Load Diff
+46
@@ -0,0 +1,46 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type FlatSkill } from 'src/engine/metadata-modules/flat-skill/types/flat-skill.type';
|
||||
import { STANDARD_SKILL } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-skill.constant';
|
||||
import { type AllStandardSkillName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-skill-name.type';
|
||||
import { type StandardBuilderArgs } from 'src/engine/workspace-manager/twenty-standard-application/types/metadata-standard-buillder-args.type';
|
||||
|
||||
export type CreateStandardSkillContext = {
|
||||
skillName: AllStandardSkillName;
|
||||
name: string;
|
||||
label: string;
|
||||
icon: string | null;
|
||||
description: string | null;
|
||||
content: string;
|
||||
isCustom: boolean;
|
||||
};
|
||||
|
||||
export type CreateStandardSkillArgs = StandardBuilderArgs<'skill'> & {
|
||||
context: CreateStandardSkillContext;
|
||||
};
|
||||
|
||||
export const createStandardSkillFlatMetadata = ({
|
||||
context: { skillName, name, label, icon, description, content, isCustom },
|
||||
workspaceId,
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}: CreateStandardSkillArgs): FlatSkill => {
|
||||
const universalIdentifier = STANDARD_SKILL[skillName].universalIdentifier;
|
||||
|
||||
return {
|
||||
id: v4(),
|
||||
universalIdentifier,
|
||||
standardId: universalIdentifier,
|
||||
name,
|
||||
label,
|
||||
icon,
|
||||
description,
|
||||
content,
|
||||
isCustom,
|
||||
isActive: true,
|
||||
workspaceId,
|
||||
applicationId: twentyStandardApplicationId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
};
|
||||
+10
@@ -1,6 +1,7 @@
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { type TwentyStandardAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/types/twenty-standard-all-flat-entity-maps.type';
|
||||
import { buildStandardFlatAgentMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/agent-metadata/build-standard-flat-agent-metadata-maps.util';
|
||||
import { buildStandardFlatSkillMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/build-standard-flat-skill-metadata-maps.util';
|
||||
import { buildStandardFlatFieldMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/build-standard-flat-field-metadata-maps.util';
|
||||
import { getStandardObjectMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-object-metadata-related-entity-ids.util';
|
||||
import { buildStandardFlatIndexMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/index/build-standard-flat-index-metadata-maps.util';
|
||||
@@ -122,6 +123,14 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({
|
||||
},
|
||||
});
|
||||
|
||||
const flatSkillMaps = buildStandardFlatSkillMetadataMaps({
|
||||
now,
|
||||
workspaceId,
|
||||
twentyStandardApplicationId,
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
dependencyFlatEntityMaps: undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
flatViewFieldMaps,
|
||||
flatViewFilterMaps,
|
||||
@@ -132,5 +141,6 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({
|
||||
flatObjectMetadataMaps,
|
||||
flatRoleMaps,
|
||||
flatAgentMaps,
|
||||
flatSkillMaps,
|
||||
};
|
||||
};
|
||||
|
||||
+1
@@ -18,6 +18,7 @@ export const fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseE
|
||||
return {
|
||||
summary: {
|
||||
invalidAgent: 0,
|
||||
invalidSkill: 0,
|
||||
invalidViewFilter: 0,
|
||||
invalidViewFilterGroup: 0,
|
||||
invalidObjectMetadata: 0,
|
||||
|
||||
+39
@@ -29,6 +29,7 @@ import { WorkspaceMigrationV2RouteTriggerActionsBuilderService } from 'src/engin
|
||||
import { WorkspaceMigrationV2RowLevelPermissionPredicateGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate-group/workspace-migration-v2-row-level-permission-predicate-group-actions-builder.service';
|
||||
import { WorkspaceMigrationV2RowLevelPermissionPredicateActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate/workspace-migration-v2-row-level-permission-predicate-actions-builder.service';
|
||||
import { WorkspaceMigrationV2ServerlessFunctionActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service';
|
||||
import { WorkspaceMigrationV2SkillActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/skill/workspace-migration-v2-skill-actions-builder.service';
|
||||
import { WorkspaceMigrationV2ViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service';
|
||||
import { WorkspaceMigrationV2ViewFilterGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter-group/workspace-migration-v2-view-filter-group-actions-builder.service';
|
||||
import { WorkspaceMigrationV2ViewFilterActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter/workspace-migration-v2-view-filter-actions-builder.service';
|
||||
@@ -53,6 +54,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
private readonly workspaceMigrationV2FieldActionsBuilderService: WorkspaceMigrationV2FieldActionsBuilderService,
|
||||
private readonly workspaceMigrationV2RoleActionsBuilderService: WorkspaceMigrationV2RoleActionsBuilderService,
|
||||
private readonly workspaceMigrationV2AgentActionsBuilderService: WorkspaceMigrationV2AgentActionsBuilderService,
|
||||
private readonly workspaceMigrationV2SkillActionsBuilderService: WorkspaceMigrationV2SkillActionsBuilderService,
|
||||
private readonly workspaceMigrationV2PageLayoutActionsBuilderService: WorkspaceMigrationV2PageLayoutActionsBuilderService,
|
||||
private readonly workspaceMigrationV2PageLayoutWidgetActionsBuilderService: WorkspaceMigrationV2PageLayoutWidgetActionsBuilderService,
|
||||
private readonly workspaceMigrationV2PageLayoutTabActionsBuilderService: WorkspaceMigrationV2PageLayoutTabActionsBuilderService,
|
||||
@@ -153,6 +155,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
flatRoleMaps,
|
||||
flatRoleTargetMaps,
|
||||
flatAgentMaps,
|
||||
flatSkillMaps,
|
||||
flatPageLayoutMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
flatPageLayoutTabMaps,
|
||||
@@ -781,6 +784,36 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefined(flatSkillMaps)) {
|
||||
const { from: fromFlatSkillMaps, to: toFlatSkillMaps } = flatSkillMaps;
|
||||
|
||||
const skillResult =
|
||||
await this.workspaceMigrationV2SkillActionsBuilderService.validateAndBuild(
|
||||
{
|
||||
additionalCacheDataMaps,
|
||||
from: fromFlatSkillMaps,
|
||||
to: toFlatSkillMaps,
|
||||
buildOptions,
|
||||
dependencyOptimisticFlatEntityMaps: undefined,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
this.mergeFlatEntityMapsAndRelatedFlatEntityMapsInAllFlatEntityMapsThroughMutation(
|
||||
{
|
||||
allFlatEntityMaps: optimisticAllFlatEntityMaps,
|
||||
flatEntityMapsAndRelatedFlatEntityMaps:
|
||||
skillResult.optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
|
||||
if (skillResult.status === 'fail') {
|
||||
orchestratorFailureReport.skill.push(...skillResult.errors);
|
||||
} else {
|
||||
orchestratorActionsReport.skill = skillResult.actions;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefined(flatPageLayoutMaps)) {
|
||||
const { from: fromFlatPageLayoutMaps, to: toFlatPageLayoutMaps } =
|
||||
flatPageLayoutMaps;
|
||||
@@ -988,6 +1021,12 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
...aggregatedOrchestratorActionsReport.agent.updated,
|
||||
///
|
||||
|
||||
// Skills
|
||||
...aggregatedOrchestratorActionsReport.skill.deleted,
|
||||
...aggregatedOrchestratorActionsReport.skill.created,
|
||||
...aggregatedOrchestratorActionsReport.skill.updated,
|
||||
///
|
||||
|
||||
// Page layouts
|
||||
...aggregatedOrchestratorActionsReport.pageLayout.deleted,
|
||||
...aggregatedOrchestratorActionsReport.pageLayout.created,
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { type FlatSkill } from 'src/engine/metadata-modules/flat-skill/types/flat-skill.type';
|
||||
import { type FlatEntityPropertiesUpdates } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-updates.type';
|
||||
|
||||
export type UpdateSkillAction = {
|
||||
type: 'update_skill';
|
||||
flatEntityId: string;
|
||||
flatEntityUpdates: FlatEntityPropertiesUpdates<'skill'>;
|
||||
};
|
||||
|
||||
export type CreateSkillAction = {
|
||||
type: 'create_skill';
|
||||
flatEntity: FlatSkill;
|
||||
};
|
||||
|
||||
export type DeleteSkillAction = {
|
||||
type: 'delete_skill';
|
||||
flatEntityId: string;
|
||||
};
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
|
||||
import { UpdateSkillAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/skill/types/workspace-migration-v2-skill-action.type';
|
||||
import { WorkspaceEntityMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
|
||||
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type';
|
||||
import { FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
|
||||
import { FlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-result.type';
|
||||
import { FlatSkillValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-skill-validator.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMigrationV2SkillActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
|
||||
typeof ALL_METADATA_NAME.skill
|
||||
> {
|
||||
constructor(
|
||||
private readonly flatSkillValidatorService: FlatSkillValidatorService,
|
||||
) {
|
||||
super(ALL_METADATA_NAME.skill);
|
||||
}
|
||||
|
||||
protected validateFlatEntityCreation(
|
||||
args: FlatEntityValidationArgs<typeof ALL_METADATA_NAME.skill>,
|
||||
): FlatEntityValidationReturnType<typeof ALL_METADATA_NAME.skill, 'created'> {
|
||||
const validationResult =
|
||||
this.flatSkillValidatorService.validateFlatSkillCreation(args);
|
||||
|
||||
if (validationResult.errors.length > 0) {
|
||||
return {
|
||||
status: 'fail',
|
||||
...validationResult,
|
||||
};
|
||||
}
|
||||
|
||||
const { flatEntityToValidate: flatSkillToValidate } = args;
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
action: {
|
||||
type: 'create_skill',
|
||||
flatEntity: flatSkillToValidate,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected validateFlatEntityDeletion(
|
||||
args: FlatEntityValidationArgs<typeof ALL_METADATA_NAME.skill>,
|
||||
): FlatEntityValidationReturnType<typeof ALL_METADATA_NAME.skill, 'deleted'> {
|
||||
const validationResult =
|
||||
this.flatSkillValidatorService.validateFlatSkillDeletion(args);
|
||||
|
||||
if (validationResult.errors.length > 0) {
|
||||
return {
|
||||
status: 'fail',
|
||||
...validationResult,
|
||||
};
|
||||
}
|
||||
|
||||
const { flatEntityToValidate: flatSkillToValidate } = args;
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
action: {
|
||||
type: 'delete_skill',
|
||||
flatEntityId: flatSkillToValidate.id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected validateFlatEntityUpdate(
|
||||
args: FlatEntityUpdateValidationArgs<typeof ALL_METADATA_NAME.skill>,
|
||||
): FlatEntityValidationReturnType<typeof ALL_METADATA_NAME.skill, 'updated'> {
|
||||
const validationResult =
|
||||
this.flatSkillValidatorService.validateFlatSkillUpdate(args);
|
||||
|
||||
if (validationResult.errors.length > 0) {
|
||||
return {
|
||||
status: 'fail',
|
||||
...validationResult,
|
||||
};
|
||||
}
|
||||
|
||||
const { flatEntityId, flatEntityUpdates } = args;
|
||||
|
||||
const updateSkillAction: UpdateSkillAction = {
|
||||
type: 'update_skill',
|
||||
flatEntityId,
|
||||
flatEntityUpdates,
|
||||
};
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
action: updateSkillAction,
|
||||
};
|
||||
}
|
||||
}
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { type ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { type FlatSkill } from 'src/engine/metadata-modules/flat-skill/types/flat-skill.type';
|
||||
import { SkillExceptionCode } from 'src/engine/metadata-modules/skill/skill.exception';
|
||||
import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
|
||||
import { findFlatEntityPropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/utils/find-flat-entity-property-update.util';
|
||||
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
|
||||
import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type';
|
||||
import { type FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
|
||||
import { validateSkillNameUniqueness } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-skill-name-uniqueness.util';
|
||||
import {
|
||||
validateSkillContentIsDefined,
|
||||
validateSkillLabelIsDefined,
|
||||
validateSkillRequiredProperties,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-skill-required-properties.util';
|
||||
import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-flat-entity-properties-updates-to-partial-flat-entity';
|
||||
|
||||
@Injectable()
|
||||
export class FlatSkillValidatorService {
|
||||
public validateFlatSkillCreation({
|
||||
flatEntityToValidate: flatSkill,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
|
||||
flatSkillMaps: optimisticFlatSkillMaps,
|
||||
},
|
||||
}: FlatEntityValidationArgs<
|
||||
typeof ALL_METADATA_NAME.skill
|
||||
>): FailedFlatEntityValidation<FlatSkill> {
|
||||
const validationResult: FailedFlatEntityValidation<FlatSkill> = {
|
||||
type: 'create_skill',
|
||||
errors: [],
|
||||
flatEntityMinimalInformation: {
|
||||
id: flatSkill.id,
|
||||
name: flatSkill.name,
|
||||
},
|
||||
};
|
||||
|
||||
const existingSkills = Object.values(optimisticFlatSkillMaps.byId).filter(
|
||||
isDefined,
|
||||
);
|
||||
|
||||
validationResult.errors.push(
|
||||
...validateSkillRequiredProperties({ flatSkill }),
|
||||
);
|
||||
|
||||
validationResult.errors.push(
|
||||
...validateSkillNameUniqueness({
|
||||
name: flatSkill.name,
|
||||
existingFlatSkills: existingSkills,
|
||||
}),
|
||||
);
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
public validateFlatSkillDeletion({
|
||||
flatEntityToValidate,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
|
||||
flatSkillMaps: optimisticFlatSkillMaps,
|
||||
},
|
||||
buildOptions,
|
||||
}: FlatEntityValidationArgs<
|
||||
typeof ALL_METADATA_NAME.skill
|
||||
>): FailedFlatEntityValidation<FlatSkill> {
|
||||
const validationResult: FailedFlatEntityValidation<FlatSkill> = {
|
||||
type: 'delete_skill',
|
||||
errors: [],
|
||||
flatEntityMinimalInformation: {
|
||||
id: flatEntityToValidate.id,
|
||||
name: flatEntityToValidate.name,
|
||||
},
|
||||
};
|
||||
|
||||
const existingSkill = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: flatEntityToValidate.id,
|
||||
flatEntityMaps: optimisticFlatSkillMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(existingSkill)) {
|
||||
validationResult.errors.push({
|
||||
code: SkillExceptionCode.SKILL_NOT_FOUND,
|
||||
message: t`Skill not found`,
|
||||
userFriendlyMessage: msg`Skill not found`,
|
||||
});
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
if (!buildOptions.isSystemBuild && isStandardMetadata(existingSkill)) {
|
||||
validationResult.errors.push({
|
||||
code: SkillExceptionCode.SKILL_IS_STANDARD,
|
||||
message: t`Cannot delete standard skill`,
|
||||
userFriendlyMessage: msg`Cannot delete standard skill`,
|
||||
});
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
public validateFlatSkillUpdate({
|
||||
flatEntityId,
|
||||
flatEntityUpdates,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
|
||||
flatSkillMaps: optimisticFlatSkillMaps,
|
||||
},
|
||||
buildOptions,
|
||||
}: FlatEntityUpdateValidationArgs<
|
||||
typeof ALL_METADATA_NAME.skill
|
||||
>): FailedFlatEntityValidation<FlatSkill> {
|
||||
const validationResult: FailedFlatEntityValidation<FlatSkill> = {
|
||||
type: 'update_skill',
|
||||
errors: [],
|
||||
flatEntityMinimalInformation: {
|
||||
id: flatEntityId,
|
||||
},
|
||||
};
|
||||
|
||||
const fromFlatSkill = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId,
|
||||
flatEntityMaps: optimisticFlatSkillMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(fromFlatSkill)) {
|
||||
validationResult.errors.push({
|
||||
code: SkillExceptionCode.SKILL_NOT_FOUND,
|
||||
message: t`Skill not found`,
|
||||
userFriendlyMessage: msg`Skill not found`,
|
||||
});
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
// Standard skills can only have isActive toggled, not other properties
|
||||
const isActiveUpdate = findFlatEntityPropertyUpdate({
|
||||
flatEntityUpdates,
|
||||
property: 'isActive',
|
||||
});
|
||||
|
||||
const hasNonIsActiveUpdates = flatEntityUpdates.some(
|
||||
(update) => update.property !== 'isActive',
|
||||
);
|
||||
|
||||
if (
|
||||
!buildOptions.isSystemBuild &&
|
||||
isStandardMetadata(fromFlatSkill) &&
|
||||
hasNonIsActiveUpdates
|
||||
) {
|
||||
validationResult.errors.push({
|
||||
code: SkillExceptionCode.SKILL_IS_STANDARD,
|
||||
message: t`Cannot update standard skill properties (only activation/deactivation allowed)`,
|
||||
userFriendlyMessage: msg`Cannot update standard skill properties (only activation/deactivation allowed)`,
|
||||
});
|
||||
}
|
||||
|
||||
// If only isActive is being updated on a standard skill, allow it
|
||||
if (
|
||||
isStandardMetadata(fromFlatSkill) &&
|
||||
isDefined(isActiveUpdate) &&
|
||||
!hasNonIsActiveUpdates
|
||||
) {
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
const optimisticFlatSkill: FlatSkill = {
|
||||
...fromFlatSkill,
|
||||
...fromFlatEntityPropertiesUpdatesToPartialFlatEntity({
|
||||
updates: flatEntityUpdates,
|
||||
}),
|
||||
};
|
||||
|
||||
const labelUpdate = findFlatEntityPropertyUpdate({
|
||||
flatEntityUpdates,
|
||||
property: 'label',
|
||||
});
|
||||
|
||||
if (isDefined(labelUpdate)) {
|
||||
validationResult.errors.push(
|
||||
...validateSkillLabelIsDefined({ flatSkill: optimisticFlatSkill }),
|
||||
);
|
||||
}
|
||||
|
||||
const contentUpdate = findFlatEntityPropertyUpdate({
|
||||
flatEntityUpdates,
|
||||
property: 'content',
|
||||
});
|
||||
|
||||
if (isDefined(contentUpdate)) {
|
||||
validationResult.errors.push(
|
||||
...validateSkillContentIsDefined({ flatSkill: optimisticFlatSkill }),
|
||||
);
|
||||
}
|
||||
|
||||
const nameUpdate = findFlatEntityPropertyUpdate({
|
||||
flatEntityUpdates,
|
||||
property: 'name',
|
||||
});
|
||||
|
||||
if (isDefined(nameUpdate)) {
|
||||
const existingSkills = Object.values(optimisticFlatSkillMaps.byId)
|
||||
.filter(isDefined)
|
||||
.filter((skill) => skill.id !== flatEntityId);
|
||||
|
||||
validationResult.errors.push(
|
||||
...validateSkillNameUniqueness({
|
||||
name: nameUpdate.to,
|
||||
existingFlatSkills: existingSkills,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
|
||||
import { SkillExceptionCode } from 'src/engine/metadata-modules/skill/skill.exception';
|
||||
import { type FlatSkill } from 'src/engine/metadata-modules/flat-skill/types/flat-skill.type';
|
||||
import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
|
||||
|
||||
export const validateSkillNameUniqueness = ({
|
||||
name,
|
||||
existingFlatSkills,
|
||||
}: {
|
||||
name: string;
|
||||
existingFlatSkills: FlatSkill[];
|
||||
}): FlatEntityValidationError<SkillExceptionCode>[] => {
|
||||
const errors: FlatEntityValidationError<SkillExceptionCode>[] = [];
|
||||
|
||||
if (existingFlatSkills.some((skill) => skill.name === name)) {
|
||||
errors.push({
|
||||
code: SkillExceptionCode.SKILL_ALREADY_EXISTS,
|
||||
message: t`Skill with name "${name}" already exists`,
|
||||
userFriendlyMessage: msg`A skill with this name already exists`,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { type FlatSkill } from 'src/engine/metadata-modules/flat-skill/types/flat-skill.type';
|
||||
import { SkillExceptionCode } from 'src/engine/metadata-modules/skill/skill.exception';
|
||||
import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
|
||||
|
||||
export const validateSkillLabelIsDefined = ({
|
||||
flatSkill,
|
||||
}: {
|
||||
flatSkill: FlatSkill;
|
||||
}): FlatEntityValidationError<SkillExceptionCode>[] => {
|
||||
if (isNonEmptyString(flatSkill.label)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
code: SkillExceptionCode.INVALID_SKILL_INPUT,
|
||||
message: t`Label cannot be empty`,
|
||||
userFriendlyMessage: msg`Label cannot be empty`,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const validateSkillContentIsDefined = ({
|
||||
flatSkill,
|
||||
}: {
|
||||
flatSkill: FlatSkill;
|
||||
}): FlatEntityValidationError<SkillExceptionCode>[] => {
|
||||
if (isNonEmptyString(flatSkill.content)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
code: SkillExceptionCode.INVALID_SKILL_INPUT,
|
||||
message: t`Content cannot be empty`,
|
||||
userFriendlyMessage: msg`Content cannot be empty`,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const validateSkillRequiredProperties = ({
|
||||
flatSkill,
|
||||
}: {
|
||||
flatSkill: FlatSkill;
|
||||
}): FlatEntityValidationError<SkillExceptionCode>[] => [
|
||||
...validateSkillLabelIsDefined({ flatSkill }),
|
||||
...validateSkillContentIsDefined({ flatSkill }),
|
||||
];
|
||||
+3
@@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
|
||||
import { FlatAgentValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-agent-validator.service';
|
||||
import { FlatSkillValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-skill-validator.service';
|
||||
import { FlatCronTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service';
|
||||
import { FlatDatabaseEventTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service';
|
||||
import { FlatFieldMetadataValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service';
|
||||
@@ -42,6 +43,7 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace
|
||||
FlatRoleValidatorService,
|
||||
FlatRoleTargetValidatorService,
|
||||
FlatAgentValidatorService,
|
||||
FlatSkillValidatorService,
|
||||
FlatPageLayoutValidatorService,
|
||||
FlatPageLayoutWidgetValidatorService,
|
||||
FlatPageLayoutTabValidatorService,
|
||||
@@ -65,6 +67,7 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace
|
||||
FlatRoleValidatorService,
|
||||
FlatRoleTargetValidatorService,
|
||||
FlatAgentValidatorService,
|
||||
FlatSkillValidatorService,
|
||||
FlatPageLayoutValidatorService,
|
||||
FlatPageLayoutWidgetValidatorService,
|
||||
FlatPageLayoutTabValidatorService,
|
||||
|
||||
+3
@@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
|
||||
import { WorkspaceMigrationV2AgentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/workspace-migration-v2-agent-actions-builder.service';
|
||||
import { WorkspaceMigrationV2SkillActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/skill/workspace-migration-v2-skill-actions-builder.service';
|
||||
import { WorkspaceMigrationV2CronTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service';
|
||||
import { WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service';
|
||||
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/workspace-migration-v2-field-actions-builder.service';
|
||||
@@ -43,6 +44,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
|
||||
WorkspaceMigrationV2RoleActionsBuilderService,
|
||||
WorkspaceMigrationV2RoleTargetActionsBuilderService,
|
||||
WorkspaceMigrationV2AgentActionsBuilderService,
|
||||
WorkspaceMigrationV2SkillActionsBuilderService,
|
||||
WorkspaceMigrationV2PageLayoutActionsBuilderService,
|
||||
WorkspaceMigrationV2PageLayoutWidgetActionsBuilderService,
|
||||
WorkspaceMigrationV2PageLayoutTabActionsBuilderService,
|
||||
@@ -65,6 +67,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
|
||||
WorkspaceMigrationV2RoleActionsBuilderService,
|
||||
WorkspaceMigrationV2RoleTargetActionsBuilderService,
|
||||
WorkspaceMigrationV2AgentActionsBuilderService,
|
||||
WorkspaceMigrationV2SkillActionsBuilderService,
|
||||
WorkspaceMigrationV2PageLayoutActionsBuilderService,
|
||||
WorkspaceMigrationV2PageLayoutWidgetActionsBuilderService,
|
||||
WorkspaceMigrationV2PageLayoutTabActionsBuilderService,
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
|
||||
WorkspaceMigrationRunnerActionHandler,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { SkillEntity } from 'src/engine/metadata-modules/skill/entities/skill.entity';
|
||||
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { CreateSkillAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/skill/types/workspace-migration-v2-skill-action.type';
|
||||
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class CreateSkillActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'create_skill',
|
||||
) {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
optimisticallyApplyActionOnAllFlatEntityMaps({
|
||||
action,
|
||||
allFlatEntityMaps,
|
||||
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<CreateSkillAction>): Partial<AllFlatEntityMaps> {
|
||||
const { flatSkillMaps } = allFlatEntityMaps;
|
||||
const { flatEntity } = action;
|
||||
|
||||
const updatedFlatSkillMaps = addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity,
|
||||
flatEntityMaps: flatSkillMaps,
|
||||
});
|
||||
|
||||
return {
|
||||
flatSkillMaps: updatedFlatSkillMaps,
|
||||
};
|
||||
}
|
||||
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerArgs<CreateSkillAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner, workspaceId } = context;
|
||||
const { flatEntity } = action;
|
||||
|
||||
const skillRepository =
|
||||
queryRunner.manager.getRepository<SkillEntity>(SkillEntity);
|
||||
|
||||
await skillRepository.save({
|
||||
...flatEntity,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
_context: WorkspaceMigrationActionRunnerArgs<CreateSkillAction>,
|
||||
): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
|
||||
WorkspaceMigrationRunnerActionHandler,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { SkillEntity } from 'src/engine/metadata-modules/skill/entities/skill.entity';
|
||||
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
||||
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
|
||||
import { DeleteSkillAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/skill/types/workspace-migration-v2-skill-action.type';
|
||||
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteSkillActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'delete_skill',
|
||||
) {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
optimisticallyApplyActionOnAllFlatEntityMaps({
|
||||
action,
|
||||
allFlatEntityMaps,
|
||||
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<DeleteSkillAction>): Partial<AllFlatEntityMaps> {
|
||||
const { flatSkillMaps } = allFlatEntityMaps;
|
||||
const { flatEntityId } = action;
|
||||
|
||||
const updatedFlatSkillMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
|
||||
entityToDeleteId: flatEntityId,
|
||||
flatEntityMaps: flatSkillMaps,
|
||||
});
|
||||
|
||||
return {
|
||||
flatSkillMaps: updatedFlatSkillMaps,
|
||||
};
|
||||
}
|
||||
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerArgs<DeleteSkillAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner, workspaceId } = context;
|
||||
const { flatEntityId } = action;
|
||||
|
||||
const skillRepository =
|
||||
queryRunner.manager.getRepository<SkillEntity>(SkillEntity);
|
||||
|
||||
await skillRepository.delete({ id: flatEntityId, workspaceId });
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
_context: WorkspaceMigrationActionRunnerArgs<DeleteSkillAction>,
|
||||
): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
|
||||
WorkspaceMigrationRunnerActionHandler,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { SkillEntity } from 'src/engine/metadata-modules/skill/entities/skill.entity';
|
||||
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
|
||||
import { UpdateSkillAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/skill/types/workspace-migration-v2-skill-action.type';
|
||||
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||
import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-flat-entity-properties-updates-to-partial-flat-entity';
|
||||
|
||||
@Injectable()
|
||||
export class UpdateSkillActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'update_skill',
|
||||
) {
|
||||
optimisticallyApplyActionOnAllFlatEntityMaps({
|
||||
action,
|
||||
allFlatEntityMaps,
|
||||
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<UpdateSkillAction>): Partial<AllFlatEntityMaps> {
|
||||
const { flatSkillMaps } = allFlatEntityMaps;
|
||||
const { flatEntityId, flatEntityUpdates } = action;
|
||||
|
||||
const existingSkill = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId,
|
||||
flatEntityMaps: flatSkillMaps,
|
||||
});
|
||||
|
||||
const updatedSkill = {
|
||||
...existingSkill,
|
||||
...fromFlatEntityPropertiesUpdatesToPartialFlatEntity({
|
||||
updates: flatEntityUpdates,
|
||||
}),
|
||||
};
|
||||
|
||||
const updatedFlatSkillMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: updatedSkill,
|
||||
flatEntityMaps: flatSkillMaps,
|
||||
});
|
||||
|
||||
return {
|
||||
flatSkillMaps: updatedFlatSkillMaps,
|
||||
};
|
||||
}
|
||||
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerArgs<UpdateSkillAction>,
|
||||
): Promise<void> {
|
||||
const { action, queryRunner, workspaceId } = context;
|
||||
const { flatEntityId, flatEntityUpdates } = action;
|
||||
|
||||
const skillRepository =
|
||||
queryRunner.manager.getRepository<SkillEntity>(SkillEntity);
|
||||
|
||||
await skillRepository.update(
|
||||
{ id: flatEntityId, workspaceId },
|
||||
fromFlatEntityPropertiesUpdatesToPartialFlatEntity({
|
||||
updates: flatEntityUpdates,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async executeForWorkspaceSchema(
|
||||
_context: WorkspaceMigrationActionRunnerArgs<UpdateSkillAction>,
|
||||
): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
+7
@@ -4,6 +4,9 @@ import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-sc
|
||||
import { CreateAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/create-agent-action-handler.service';
|
||||
import { DeleteAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/delete-agent-action-handler.service';
|
||||
import { UpdateAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/update-agent-action-handler.service';
|
||||
import { CreateSkillActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/skill/services/create-skill-action-handler.service';
|
||||
import { DeleteSkillActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/skill/services/delete-skill-action-handler.service';
|
||||
import { UpdateSkillActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/skill/services/update-skill-action-handler.service';
|
||||
import { CreateCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/create-cron-trigger-action-handler.service';
|
||||
import { DeleteCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/delete-cron-trigger-action-handler.service';
|
||||
import { UpdateCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/update-cron-trigger-action-handler.service';
|
||||
@@ -123,6 +126,10 @@ import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/wor
|
||||
UpdateAgentActionHandlerService,
|
||||
DeleteAgentActionHandlerService,
|
||||
|
||||
CreateSkillActionHandlerService,
|
||||
UpdateSkillActionHandlerService,
|
||||
DeleteSkillActionHandlerService,
|
||||
|
||||
CreatePageLayoutActionHandlerService,
|
||||
UpdatePageLayoutActionHandlerService,
|
||||
DeletePageLayoutActionHandlerService,
|
||||
|
||||
Reference in New Issue
Block a user