Merge pull request #1135 from Hexastack/1134-issue---api-make-sure-hookentityprepostupdate-is-used-across-the-board

fix(api): resolve predefined update hooks bug
This commit is contained in:
Med Marrouchi 2025-06-18 17:00:31 +01:00 committed by GitHub
commit c5abe12971
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 110 additions and 76 deletions

View File

@ -228,4 +228,33 @@ describe('NlpEntityRepository', () => {
expect(nlpEntity).toEqualPayload(result);
});
});
describe('postUpdate', () => {
it('should update an NlpEntity and trigger a postUpdate event', async () => {
jest.spyOn(nlpService, 'handleEntityPostUpdate');
jest.spyOn(llmNluHelper, 'updateEntity');
nlpEntityRepository.eventEmitter.once(
'hook:nlpEntity:postUpdate',
async (...[query, updated]) => {
await nlpService.handleEntityPostUpdate(query, updated);
expect(llmNluHelper.updateEntity).toHaveBeenCalledWith(updated);
},
);
const updatedNlpEntity = await nlpEntityRepository.updateOne(
{
name: 'test2',
},
{ value: 'test3' },
);
expect(nlpService.handleEntityPostUpdate).toHaveBeenCalledTimes(1);
expect(llmNluHelper.updateEntity).toHaveBeenCalledTimes(1);
const result = await nlpEntityRepository.findOne(updatedNlpEntity.id);
expect(result).toEqualPayload(updatedNlpEntity);
});
});
});

View File

@ -8,7 +8,7 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Document, Model, Query } from 'mongoose';
import { Model } from 'mongoose';
import { BaseRepository } from '@/utils/generics/base-repository';
@ -30,28 +30,4 @@ export class NlpEntityRepository extends BaseRepository<
constructor(@InjectModel(NlpEntity.name) readonly model: Model<NlpEntity>) {
super(model, NlpEntity, NLP_ENTITY_POPULATE, NlpEntityFull);
}
/**
* Post-update hook that triggers after an NLP entity is updated.
* Emits an event to notify other parts of the system about the update.
* Bypasses built-in entities.
*
* @param query - The query used to find and update the entity.
* @param updated - The updated NLP entity document.
*/
async postUpdate(
_query: Query<
Document<NlpEntity, any, any>,
Document<NlpEntity, any, any>,
unknown,
NlpEntity,
'findOneAndUpdate'
>,
updated: NlpEntity,
): Promise<void> {
if (!updated?.builtin) {
// Bypass builtin entities (probably fixtures)
this.eventEmitter.emit('hook:nlpEntity:update', updated);
}
}
}

View File

@ -250,4 +250,34 @@ describe('NlpValueRepository', () => {
expect(nlpValue).toEqualPayload(result);
});
});
describe('postUpdate', () => {
it('should update an NlpValue and trigger a postUpdate event', async () => {
jest.spyOn(nlpService, 'handleValuePostUpdate');
jest.spyOn(llmNluHelper, 'updateValue');
nlpValueRepository.eventEmitter.once(
'hook:nlpValue:postUpdate',
async (...[query, updated]) => {
await nlpService.handleValuePostUpdate(query, updated);
expect(llmNluHelper.updateValue).toHaveBeenCalledWith(updated);
},
);
const updatedNlpValue = await nlpValueRepository.updateOne(
{
value: 'test',
},
{ value: 'test2' },
);
expect(nlpService.handleValuePostUpdate).toHaveBeenCalledTimes(1);
expect(llmNluHelper.updateValue).toHaveBeenCalledTimes(1);
const result = await nlpValueRepository.findOne(updatedNlpValue.id);
expect(result).toEqualPayload(updatedNlpValue);
});
});
});

View File

@ -9,14 +9,7 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { plainToInstance } from 'class-transformer';
import {
Document,
Model,
PipelineStage,
Query,
SortOrder,
Types,
} from 'mongoose';
import { Model, PipelineStage, SortOrder, Types } from 'mongoose';
import { BaseRepository } from '@/utils/generics/base-repository';
import { PageQueryDto } from '@/utils/pagination/pagination-query.dto';
@ -45,28 +38,6 @@ export class NlpValueRepository extends BaseRepository<
super(model, NlpValue, NLP_VALUE_POPULATE, NlpValueFull);
}
/**
* Emits an event after an NLP value is updated, bypassing built-in values.
*
* @param query - The query that was used to update the NLP value.
* @param updated - The updated NLP value document.
*/
async postUpdate(
_query: Query<
Document<NlpValue, any, any>,
Document<NlpValue, any, any>,
unknown,
NlpValue,
'findOneAndUpdate'
>,
updated: NlpValue,
): Promise<void> {
if (!updated?.builtin) {
// Bypass builtin entities (probably fixtures)
this.eventEmitter.emit('hook:nlpValue:update', updated);
}
}
private getSortDirection(sortOrder: SortOrder) {
return typeof sortOrder === 'number'
? sortOrder

View File

@ -8,6 +8,7 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { Document, Query } from 'mongoose';
import { HelperService } from '@/helper/helper.service';
import { HelperType, NLU } from '@/helper/types';
@ -96,15 +97,28 @@ export class NlpService {
*
* @param entity - The NLP entity to be updated.
*/
@OnEvent('hook:nlpEntity:update')
async handleEntityUpdate(entity: NlpEntity) {
// Synchonize new entity with NLP provider
try {
const helper = await this.helperService.getDefaultNluHelper();
await helper.updateEntity(entity);
this.logger.debug('Updated entity successfully synced!', entity);
} catch (err) {
this.logger.error('Unable to sync updated entity', err);
@OnEvent('hook:nlpEntity:postUpdate')
async handleEntityPostUpdate(
_query: Query<
Document<NlpEntity>,
Document<NlpEntity>,
unknown,
NlpEntity,
'findOneAndUpdate'
>,
updated: NlpEntity,
) {
if (!updated?.builtin) {
// Synchonize new entity with NLP provider
try {
const helper = await this.helperService.getDefaultHelper(
HelperType.NLU,
);
await helper.updateEntity(updated);
this.logger.debug('Updated entity successfully synced!', updated);
} catch (err) {
this.logger.error('Unable to sync updated entity', err);
}
}
}
@ -180,15 +194,28 @@ export class NlpService {
*
* @param value - The NLP value to be updated.
*/
@OnEvent('hook:nlpValue:update')
async handleValueUpdate(value: NlpValue) {
// Synchonize new value with NLP provider
try {
const helper = await this.helperService.getDefaultNluHelper();
await helper.updateValue(value);
this.logger.debug('Updated value successfully synced!', value);
} catch (err) {
this.logger.error('Unable to sync updated value', err);
@OnEvent('hook:nlpValue:postUpdate')
async handleValuePostUpdate(
_query: Query<
Document<NlpValue, any, any>,
Document<NlpValue, any, any>,
unknown,
NlpValue,
'findOneAndUpdate'
>,
updated: NlpValue,
) {
if (!updated?.builtin) {
// Synchonize new value with NLP provider
try {
const helper = await this.helperService.getDefaultHelper(
HelperType.NLU,
);
await helper.updateValue(updated);
this.logger.debug('Updated value successfully synced!', updated);
} catch (err) {
this.logger.error('Unable to sync updated value', err);
}
}
}

View File

@ -132,10 +132,10 @@ declare module '@nestjs/event-emitter' {
menu: TDefinition<Menu>;
language: TDefinition<Language>;
translation: TDefinition<Translation>;
nlpEntity: TDefinition<NlpEntity, { update: NlpEntity }>;
nlpEntity: TDefinition<NlpEntity>;
nlpSampleEntity: TDefinition<NlpSampleEntity>;
nlpSample: TDefinition<NlpSample>;
nlpValue: TDefinition<NlpValue, { update: NlpValue }>;
nlpValue: TDefinition<NlpValue>;
setting: TDefinition<Setting>;
invitation: TDefinition<Invitation>;
model: TDefinition<Model>;
@ -189,7 +189,8 @@ declare module '@nestjs/event-emitter' {
type TPostUpdateValidate<T> = FilterQuery<T>;
type TPostUpdate<T> = THydratedDocument<T>;
// TODO this type will be optimized soon in a separated PR
type TPostUpdate<T> = T & any;
type TPostDelete = DeleteResult;