mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
fix: nlp sample inconsistency when language is deleted
This commit is contained in:
parent
82dd888f2f
commit
6652629995
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { MongooseModule } from '@nestjs/mongoose';
|
import { MongooseModule } from '@nestjs/mongoose';
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
|
|
||||||
@ -63,6 +64,7 @@ describe('LanguageController', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
LoggerService,
|
LoggerService,
|
||||||
|
EventEmitter2,
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
languageService = module.get<LanguageService>(LanguageService);
|
languageService = module.get<LanguageService>(LanguageService);
|
||||||
|
@ -8,16 +8,46 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { InjectModel } from '@nestjs/mongoose';
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
import { Model } from 'mongoose';
|
import { Document, Model, Query, TFilterQuery } from 'mongoose';
|
||||||
|
|
||||||
import { BaseRepository } from '@/utils/generics/base-repository';
|
import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository';
|
||||||
|
|
||||||
import { Language } from '../schemas/language.schema';
|
import { Language } from '../schemas/language.schema';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LanguageRepository extends BaseRepository<Language> {
|
export class LanguageRepository extends BaseRepository<Language> {
|
||||||
constructor(@InjectModel(Language.name) readonly model: Model<Language>) {
|
constructor(
|
||||||
|
@InjectModel(Language.name) readonly model: Model<Language>,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
) {
|
||||||
super(model, Language);
|
super(model, Language);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-delete hook that triggers before an language is deleted.
|
||||||
|
*
|
||||||
|
* @param query The query used to delete the language.
|
||||||
|
* @param criteria The filter criteria used to find the language for deletion.
|
||||||
|
*/
|
||||||
|
async preDelete(
|
||||||
|
_query: Query<
|
||||||
|
DeleteResult,
|
||||||
|
Document<Language, any, any>,
|
||||||
|
unknown,
|
||||||
|
Language,
|
||||||
|
'deleteOne' | 'deleteMany'
|
||||||
|
>,
|
||||||
|
criteria: TFilterQuery<Language>,
|
||||||
|
): Promise<void> {
|
||||||
|
if (criteria._id) {
|
||||||
|
const language = await this.findOne(
|
||||||
|
typeof criteria === 'string' ? { _id: criteria } : criteria,
|
||||||
|
);
|
||||||
|
this.eventEmitter.emit('hook:language:delete', language);
|
||||||
|
} else {
|
||||||
|
throw new Error('Attempted to delete language using unknown criteria');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import { Prop, Schema, SchemaFactory, ModelDefinition } from '@nestjs/mongoose';
|
|||||||
import { THydratedDocument } from 'mongoose';
|
import { THydratedDocument } from 'mongoose';
|
||||||
|
|
||||||
import { BaseSchema } from '@/utils/generics/base-schema';
|
import { BaseSchema } from '@/utils/generics/base-schema';
|
||||||
|
import { LifecycleHookManager } from '@/utils/generics/lifecycle-hook-manager';
|
||||||
|
|
||||||
@Schema({ timestamps: true })
|
@Schema({ timestamps: true })
|
||||||
export class Language extends BaseSchema {
|
export class Language extends BaseSchema {
|
||||||
@ -41,10 +42,10 @@ export class Language extends BaseSchema {
|
|||||||
isRTL?: boolean;
|
isRTL?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LanguageModel: ModelDefinition = {
|
export const LanguageModel: ModelDefinition = LifecycleHookManager.attach({
|
||||||
name: Language.name,
|
name: Language.name,
|
||||||
schema: SchemaFactory.createForClass(Language),
|
schema: SchemaFactory.createForClass(Language),
|
||||||
};
|
});
|
||||||
|
|
||||||
export type LanguageDocument = THydratedDocument<Language>;
|
export type LanguageDocument = THydratedDocument<Language>;
|
||||||
|
|
||||||
|
@ -49,15 +49,15 @@ export class NlpSampleStub extends BaseSchema {
|
|||||||
@Prop({
|
@Prop({
|
||||||
type: MongooseSchema.Types.ObjectId,
|
type: MongooseSchema.Types.ObjectId,
|
||||||
ref: 'Language',
|
ref: 'Language',
|
||||||
required: true,
|
required: false,
|
||||||
})
|
})
|
||||||
language: unknown;
|
language: unknown | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Schema({ timestamps: true })
|
@Schema({ timestamps: true })
|
||||||
export class NlpSample extends NlpSampleStub {
|
export class NlpSample extends NlpSampleStub {
|
||||||
@Transform(({ obj }) => obj.language.toString())
|
@Transform(({ obj }) => obj.language.toString())
|
||||||
language: string;
|
language: string | null;
|
||||||
|
|
||||||
@Exclude()
|
@Exclude()
|
||||||
entities?: never;
|
entities?: never;
|
||||||
@ -66,7 +66,7 @@ export class NlpSample extends NlpSampleStub {
|
|||||||
@Schema({ timestamps: true })
|
@Schema({ timestamps: true })
|
||||||
export class NlpSampleFull extends NlpSampleStub {
|
export class NlpSampleFull extends NlpSampleStub {
|
||||||
@Type(() => Language)
|
@Type(() => Language)
|
||||||
language: Language;
|
language: Language | null;
|
||||||
|
|
||||||
@Type(() => NlpSampleEntity)
|
@Type(() => NlpSampleEntity)
|
||||||
entities: NlpSampleEntity[];
|
entities: NlpSampleEntity[];
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CommonExample,
|
CommonExample,
|
||||||
@ -16,6 +17,7 @@ import {
|
|||||||
ExampleEntity,
|
ExampleEntity,
|
||||||
LookupTable,
|
LookupTable,
|
||||||
} from '@/extensions/helpers/nlp/default/types';
|
} from '@/extensions/helpers/nlp/default/types';
|
||||||
|
import { Language } from '@/i18n/schemas/language.schema';
|
||||||
import { LanguageService } from '@/i18n/services/language.service';
|
import { LanguageService } from '@/i18n/services/language.service';
|
||||||
import { BaseService } from '@/utils/generics/base-service';
|
import { BaseService } from '@/utils/generics/base-service';
|
||||||
|
|
||||||
@ -140,4 +142,21 @@ export class NlpSampleService extends BaseService<
|
|||||||
entity_synonyms,
|
entity_synonyms,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a language gets deleted, we need to set related samples to null
|
||||||
|
*
|
||||||
|
* @param language The language that has been deleted.
|
||||||
|
*/
|
||||||
|
@OnEvent('hook:language:delete')
|
||||||
|
async handleLanguageDelete(language: Language) {
|
||||||
|
await this.updateMany(
|
||||||
|
{
|
||||||
|
language: language.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import AddIcon from "@mui/icons-material/Add";
|
|||||||
import { Button, Grid, Paper } from "@mui/material";
|
import { Button, Grid, Paper } from "@mui/material";
|
||||||
import { GridColDef } from "@mui/x-data-grid";
|
import { GridColDef } from "@mui/x-data-grid";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useQueryClient } from "react-query";
|
||||||
|
|
||||||
import { DeleteDialog } from "@/app-components/dialogs/DeleteDialog";
|
import { DeleteDialog } from "@/app-components/dialogs/DeleteDialog";
|
||||||
import { FilterTextfield } from "@/app-components/inputs/FilterTextfield";
|
import { FilterTextfield } from "@/app-components/inputs/FilterTextfield";
|
||||||
@ -21,6 +22,7 @@ import {
|
|||||||
} from "@/app-components/tables/columns/getColumns";
|
} from "@/app-components/tables/columns/getColumns";
|
||||||
import { renderHeader } from "@/app-components/tables/columns/renderHeader";
|
import { renderHeader } from "@/app-components/tables/columns/renderHeader";
|
||||||
import { DataGrid } from "@/app-components/tables/DataGrid";
|
import { DataGrid } from "@/app-components/tables/DataGrid";
|
||||||
|
import { isSameEntity } from "@/hooks/crud/helpers";
|
||||||
import { useDelete } from "@/hooks/crud/useDelete";
|
import { useDelete } from "@/hooks/crud/useDelete";
|
||||||
import { useFind } from "@/hooks/crud/useFind";
|
import { useFind } from "@/hooks/crud/useFind";
|
||||||
import { useUpdate } from "@/hooks/crud/useUpdate";
|
import { useUpdate } from "@/hooks/crud/useUpdate";
|
||||||
@ -42,6 +44,7 @@ export const Languages = () => {
|
|||||||
const addDialogCtl = useDialog<ILanguage>(false);
|
const addDialogCtl = useDialog<ILanguage>(false);
|
||||||
const editDialogCtl = useDialog<ILanguage>(false);
|
const editDialogCtl = useDialog<ILanguage>(false);
|
||||||
const deleteDialogCtl = useDialog<string>(false);
|
const deleteDialogCtl = useDialog<string>(false);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const hasPermission = useHasPermission();
|
const hasPermission = useHasPermission();
|
||||||
const { onSearch, searchPayload } = useSearch<ILanguage>({
|
const { onSearch, searchPayload } = useSearch<ILanguage>({
|
||||||
$or: ["title", "code"],
|
$or: ["title", "code"],
|
||||||
@ -66,6 +69,13 @@ export const Languages = () => {
|
|||||||
toast.error(t("message.internal_server_error"));
|
toast.error(t("message.internal_server_error"));
|
||||||
},
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
queryClient.removeQueries({
|
||||||
|
predicate: ({ queryKey }) => {
|
||||||
|
const [_qType, qEntity] = queryKey;
|
||||||
|
|
||||||
|
return isSameEntity(qEntity, EntityType.NLP_SAMPLE);
|
||||||
|
},
|
||||||
|
});
|
||||||
deleteDialogCtl.closeDialog();
|
deleteDialogCtl.closeDialog();
|
||||||
toast.success(t("message.item_delete_success"));
|
toast.success(t("message.item_delete_success"));
|
||||||
},
|
},
|
||||||
|
@ -104,7 +104,6 @@ export default function NlpSample() {
|
|||||||
{
|
{
|
||||||
label: ActionColumnLabel.Edit,
|
label: ActionColumnLabel.Edit,
|
||||||
action: ({ entities, language, ...rest }) => {
|
action: ({ entities, language, ...rest }) => {
|
||||||
const lang = getLanguageFromCache(language) as ILanguage;
|
|
||||||
const data: INlpDatasetSample = {
|
const data: INlpDatasetSample = {
|
||||||
...rest,
|
...rest,
|
||||||
entities: entities?.map((e) => {
|
entities: entities?.map((e) => {
|
||||||
@ -119,7 +118,9 @@ export default function NlpSample() {
|
|||||||
entity: getNlpEntityFromCache(entity)?.name || "",
|
entity: getNlpEntityFromCache(entity)?.name || "",
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
language: lang.code,
|
language: language
|
||||||
|
? (getLanguageFromCache(language) as ILanguage).code
|
||||||
|
: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
editDialogCtl.openDialog(data);
|
editDialogCtl.openDialog(data);
|
||||||
@ -186,9 +187,7 @@ export default function NlpSample() {
|
|||||||
maxWidth: 90,
|
maxWidth: 90,
|
||||||
field: "language",
|
field: "language",
|
||||||
renderCell: ({ row }) => {
|
renderCell: ({ row }) => {
|
||||||
const language = getLanguageFromCache(row.language);
|
return row.language ? getLanguageFromCache(row.language)?.title : "";
|
||||||
|
|
||||||
return language?.title;
|
|
||||||
},
|
},
|
||||||
headerName: t("label.language"),
|
headerName: t("label.language"),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
|
@ -24,7 +24,7 @@ export interface INlpSampleAttributes {
|
|||||||
trained?: boolean;
|
trained?: boolean;
|
||||||
type?: NlpSampleType;
|
type?: NlpSampleType;
|
||||||
entities: string[];
|
entities: string[];
|
||||||
language: string;
|
language: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INlpSampleStub
|
export interface INlpSampleStub
|
||||||
@ -33,12 +33,12 @@ export interface INlpSampleStub
|
|||||||
|
|
||||||
export interface INlpSample extends INlpSampleStub, IFormat<Format.BASIC> {
|
export interface INlpSample extends INlpSampleStub, IFormat<Format.BASIC> {
|
||||||
entities: string[];
|
entities: string[];
|
||||||
language: string;
|
language: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INlpSampleFull extends INlpSampleStub, IFormat<Format.FULL> {
|
export interface INlpSampleFull extends INlpSampleStub, IFormat<Format.FULL> {
|
||||||
entities: INlpSampleEntity[];
|
entities: INlpSampleEntity[];
|
||||||
language: ILanguage;
|
language: ILanguage | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dataset Trainer
|
// Dataset Trainer
|
||||||
|
Loading…
Reference in New Issue
Block a user