mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
Merge pull request #444 from Hexastack/fix/settings-saved-db-string
Fix : Number settings gets saved as strings in MongoDB
This commit is contained in:
commit
f83a459670
@ -7,12 +7,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import {
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
EventEmitter2,
|
|
||||||
IHookSettingsGroupLabelOperationMap,
|
|
||||||
} from '@nestjs/event-emitter';
|
|
||||||
import { InjectModel } from '@nestjs/mongoose';
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
import { Document, Model, Query, Types } from 'mongoose';
|
import {
|
||||||
|
Document,
|
||||||
|
FilterQuery,
|
||||||
|
Model,
|
||||||
|
Types,
|
||||||
|
UpdateQuery,
|
||||||
|
UpdateWithAggregationPipeline,
|
||||||
|
} from 'mongoose';
|
||||||
|
|
||||||
import { I18nService } from '@/i18n/services/i18n.service';
|
import { I18nService } from '@/i18n/services/i18n.service';
|
||||||
import { BaseRepository } from '@/utils/generics/base-repository';
|
import { BaseRepository } from '@/utils/generics/base-repository';
|
||||||
@ -30,6 +34,27 @@ export class SettingRepository extends BaseRepository<Setting> {
|
|||||||
super(eventEmitter, model, Setting);
|
super(eventEmitter, model, Setting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async preCreateValidate(
|
||||||
|
doc: Document<unknown, unknown, Setting> &
|
||||||
|
Setting & { _id: Types.ObjectId },
|
||||||
|
) {
|
||||||
|
this.validateSettingValue(doc.type, doc.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async preUpdateValidate(
|
||||||
|
criteria: FilterQuery<Setting>,
|
||||||
|
updates: UpdateWithAggregationPipeline | UpdateQuery<Setting>,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!Array.isArray(updates)) {
|
||||||
|
const payload = updates.$set;
|
||||||
|
if (typeof payload.value !== 'undefined') {
|
||||||
|
const { type } =
|
||||||
|
'type' in payload ? payload : await this.findOne(criteria);
|
||||||
|
this.validateSettingValue(type, payload.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the `Setting` document after it has been retrieved.
|
* Validates the `Setting` document after it has been retrieved.
|
||||||
*
|
*
|
||||||
@ -40,64 +65,51 @@ export class SettingRepository extends BaseRepository<Setting> {
|
|||||||
*
|
*
|
||||||
* @param setting The `Setting` document to be validated.
|
* @param setting The `Setting` document to be validated.
|
||||||
*/
|
*/
|
||||||
async postValidate(
|
private validateSettingValue(type: SettingType, value: any) {
|
||||||
setting: Document<unknown, unknown, Setting> &
|
|
||||||
Setting & { _id: Types.ObjectId },
|
|
||||||
) {
|
|
||||||
if (
|
if (
|
||||||
(setting.type === SettingType.text ||
|
(type === SettingType.text || type === SettingType.textarea) &&
|
||||||
setting.type === SettingType.textarea) &&
|
typeof value !== 'string' &&
|
||||||
typeof setting.value !== 'string' &&
|
value !== null
|
||||||
setting.value !== null
|
|
||||||
) {
|
) {
|
||||||
throw new Error('Setting Model : Value must be a string!');
|
throw new Error('Setting Model : Value must be a string!');
|
||||||
} else if (setting.type === SettingType.multiple_text) {
|
} else if (type === SettingType.multiple_text) {
|
||||||
const isStringArray =
|
if (!this.isArrayOfString(value)) {
|
||||||
Array.isArray(setting.value) &&
|
throw new Error(
|
||||||
setting.value.every((v) => {
|
'Setting Model (Multiple Text) : Value must be a string array!',
|
||||||
return typeof v === 'string';
|
);
|
||||||
});
|
|
||||||
if (!isStringArray) {
|
|
||||||
throw new Error('Setting Model : Value must be a string array!');
|
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
setting.type === SettingType.checkbox &&
|
type === SettingType.checkbox &&
|
||||||
typeof setting.value !== 'boolean' &&
|
typeof value !== 'boolean' &&
|
||||||
setting.value !== null
|
value !== null
|
||||||
) {
|
) {
|
||||||
throw new Error('Setting Model : Value must be a boolean!');
|
throw new Error('Setting Model : Value must be a boolean!');
|
||||||
} else if (
|
} else if (
|
||||||
setting.type === SettingType.number &&
|
type === SettingType.number &&
|
||||||
typeof setting.value !== 'number' &&
|
typeof value !== 'number' &&
|
||||||
setting.value !== null
|
value !== null
|
||||||
) {
|
) {
|
||||||
throw new Error('Setting Model : Value must be a number!');
|
throw new Error('Setting Model : Value must be a number!');
|
||||||
|
} else if (type === SettingType.multiple_attachment) {
|
||||||
|
if (!this.isArrayOfString(value)) {
|
||||||
|
throw new Error(
|
||||||
|
'Setting Model (Multiple Attachement): Value must be a string array!',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (type === SettingType.attachment) {
|
||||||
|
if (typeof value !== 'string' && typeof value !== null) {
|
||||||
|
throw new Error(
|
||||||
|
'Setting Model (attachement): Value must be a string or null !',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (type === SettingType.secret && typeof value !== 'string') {
|
||||||
|
throw new Error('Setting Model (secret) : Value must be a string');
|
||||||
|
} else if (type === SettingType.select && typeof value !== 'string') {
|
||||||
|
throw new Error('Setting Model (select): Value must be a string!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private isArrayOfString(value: any): boolean {
|
||||||
* Emits an event after a `Setting` has been updated.
|
return Array.isArray(value) && value.every((v) => typeof v === 'string');
|
||||||
*
|
|
||||||
* This method is used to synchronize global settings by emitting an event
|
|
||||||
* based on the `group` and `label` of the `Setting`.
|
|
||||||
*
|
|
||||||
* @param _query The Mongoose query object used to find and update the document.
|
|
||||||
* @param setting The updated `Setting` object.
|
|
||||||
*/
|
|
||||||
async postUpdate(
|
|
||||||
_query: Query<
|
|
||||||
Document<Setting, any, any>,
|
|
||||||
Document<Setting, any, any>,
|
|
||||||
unknown,
|
|
||||||
Setting,
|
|
||||||
'findOneAndUpdate'
|
|
||||||
>,
|
|
||||||
setting: Setting,
|
|
||||||
) {
|
|
||||||
const group = setting.group as keyof IHookSettingsGroupLabelOperationMap;
|
|
||||||
const label = setting.label as '*';
|
|
||||||
|
|
||||||
// Sync global settings var
|
|
||||||
this.eventEmitter.emit(`hook:${group}:${label}`, setting);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -162,6 +162,105 @@ describe('BaseRepository', () => {
|
|||||||
expect.objectContaining({ dummy: 'updated dummy text' }),
|
expect.objectContaining({ dummy: 'updated dummy text' }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should updateOne by id and trigger preUpdateValidate and postUpdateValidate methods', async () => {
|
||||||
|
const created = await dummyRepository.create({ dummy: 'initial text' });
|
||||||
|
const mockGetFilterValue = { _id: created.id };
|
||||||
|
const mockedGetFilter = jest.fn().mockReturnValue(mockGetFilterValue);
|
||||||
|
|
||||||
|
const mockGetUpdateValue = {
|
||||||
|
$set: {
|
||||||
|
value: 'updated dummy text',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockedGetUpdate = jest.fn().mockReturnValue(mockGetUpdateValue);
|
||||||
|
const mockQueryValue = {
|
||||||
|
getFilter: mockedGetFilter,
|
||||||
|
getUpdate: mockedGetUpdate,
|
||||||
|
lean: jest.fn(() => {
|
||||||
|
return {
|
||||||
|
exec: jest.fn(),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(dummyModel, 'findOneAndUpdate')
|
||||||
|
.mockReturnValue(mockQueryValue as any);
|
||||||
|
|
||||||
|
const mockUpdate = { dummy: 'updated dummy text' };
|
||||||
|
const spyPreUpdateValidate = jest
|
||||||
|
.spyOn(dummyRepository, 'preUpdateValidate')
|
||||||
|
.mockResolvedValue();
|
||||||
|
const spyPostUpdateValidate = jest
|
||||||
|
.spyOn(dummyRepository, 'postUpdateValidate')
|
||||||
|
.mockResolvedValue();
|
||||||
|
|
||||||
|
const spyExecutoneOne = jest
|
||||||
|
.spyOn(
|
||||||
|
dummyRepository as DummyRepository & {
|
||||||
|
executeOne: () => Promise<{ dummy: string }>;
|
||||||
|
},
|
||||||
|
'executeOne',
|
||||||
|
)
|
||||||
|
.mockResolvedValue({ dummy: 'updated dummy text' });
|
||||||
|
|
||||||
|
await dummyRepository.updateOne(created.id, mockUpdate);
|
||||||
|
|
||||||
|
expect(spyPreUpdateValidate).toHaveBeenCalledWith(
|
||||||
|
mockGetFilterValue,
|
||||||
|
mockGetUpdateValue,
|
||||||
|
);
|
||||||
|
expect(spyPostUpdateValidate).toHaveBeenCalledWith(
|
||||||
|
mockGetFilterValue,
|
||||||
|
mockGetUpdateValue,
|
||||||
|
);
|
||||||
|
expect(spyExecutoneOne).toHaveBeenCalledWith(mockQueryValue, Dummy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error while trying to updateOne when calling preUpdateValidate', async () => {
|
||||||
|
const created = await dummyRepository.create({ dummy: 'initial text' });
|
||||||
|
const mockGetFilterValue = { _id: created.id };
|
||||||
|
const mockedGetFilter = jest.fn().mockReturnValue(mockGetFilterValue);
|
||||||
|
|
||||||
|
const mockGetUpdateValue = {
|
||||||
|
$set: {
|
||||||
|
value: 10,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockedGetUpdate = jest.fn().mockReturnValue(mockGetUpdateValue);
|
||||||
|
const mockQueryValue = {
|
||||||
|
getFilter: mockedGetFilter,
|
||||||
|
getUpdate: mockedGetUpdate,
|
||||||
|
lean: jest.fn(() => {
|
||||||
|
return {
|
||||||
|
exec: jest.fn(),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(dummyModel, 'findOneAndUpdate')
|
||||||
|
.mockReturnValue(mockQueryValue as any);
|
||||||
|
|
||||||
|
const mockUpdate = { dummy: 10 };
|
||||||
|
const spyPreUpdateValidate = jest
|
||||||
|
.spyOn(dummyRepository, 'preUpdateValidate')
|
||||||
|
.mockImplementation(() => {
|
||||||
|
throw new Error('Mocked error while validating dummy');
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
dummyRepository.updateOne(created.id, mockUpdate),
|
||||||
|
).rejects.toThrow('Mocked error while validating dummy');
|
||||||
|
|
||||||
|
expect(spyPreUpdateValidate).toHaveBeenCalledWith(
|
||||||
|
mockGetFilterValue,
|
||||||
|
mockGetUpdateValue,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteOne', () => {
|
describe('deleteOne', () => {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
import { ClassTransformOptions, plainToClass } from 'class-transformer';
|
import { ClassTransformOptions, plainToClass } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
Document,
|
Document,
|
||||||
|
FilterQuery,
|
||||||
FlattenMaps,
|
FlattenMaps,
|
||||||
HydratedDocument,
|
HydratedDocument,
|
||||||
Model,
|
Model,
|
||||||
@ -38,18 +39,32 @@ export type DeleteResult = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export enum EHook {
|
export enum EHook {
|
||||||
|
preCreateValidate = 'preCreateValidate',
|
||||||
preCreate = 'preCreate',
|
preCreate = 'preCreate',
|
||||||
|
preUpdateValidate = 'preUpdateValidate',
|
||||||
preUpdate = 'preUpdate',
|
preUpdate = 'preUpdate',
|
||||||
preUpdateMany = 'preUpdateMany',
|
preUpdateMany = 'preUpdateMany',
|
||||||
preDelete = 'preDelete',
|
preDelete = 'preDelete',
|
||||||
preValidate = 'preValidate',
|
postCreateValidate = 'postCreateValidate',
|
||||||
postCreate = 'postCreate',
|
postCreate = 'postCreate',
|
||||||
|
postUpdateValidate = 'postUpdateValidate',
|
||||||
postUpdate = 'postUpdate',
|
postUpdate = 'postUpdate',
|
||||||
postUpdateMany = 'postUpdateMany',
|
postUpdateMany = 'postUpdateMany',
|
||||||
postDelete = 'postDelete',
|
postDelete = 'postDelete',
|
||||||
postValidate = 'postValidate',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ! ------------------------------------ Note --------------------------------------------
|
||||||
|
// Methods like `update()`, `updateOne()`, `updateMany()`, `findOneAndUpdate()`,
|
||||||
|
// `findByIdAndUpdate()`, `findOneAndReplace()`, `findOneAndDelete()`, and `findByIdAndDelete()`
|
||||||
|
// do not trigger Mongoose validation hooks by default. This is because these methods do not
|
||||||
|
// return Mongoose Documents but plain JavaScript objects (POJOs), which do not have Mongoose
|
||||||
|
// instance methods like `validate()` attached.
|
||||||
|
//
|
||||||
|
// Be cautious when using the `.lean()` function as well. It returns POJOs instead of Mongoose
|
||||||
|
// Documents, so methods and hooks like `validate()` will not be available when working with
|
||||||
|
// the returned data. If you need validation, ensure that you're working with a Mongoose Document
|
||||||
|
// or explicitly use `runValidators: true` in the options for update operations.
|
||||||
|
|
||||||
export abstract class BaseRepository<
|
export abstract class BaseRepository<
|
||||||
T extends FlattenMaps<unknown>,
|
T extends FlattenMaps<unknown>,
|
||||||
P extends string = never,
|
P extends string = never,
|
||||||
@ -86,14 +101,17 @@ export abstract class BaseRepository<
|
|||||||
|
|
||||||
hooks?.validate.pre.execute(async function () {
|
hooks?.validate.pre.execute(async function () {
|
||||||
const doc = this as HydratedDocument<T>;
|
const doc = this as HydratedDocument<T>;
|
||||||
await repository.preValidate(doc);
|
await repository.preCreateValidate(doc);
|
||||||
repository.emitter.emit(repository.getEventName(EHook.preValidate), doc);
|
repository.emitter.emit(
|
||||||
|
repository.getEventName(EHook.preCreateValidate),
|
||||||
|
doc,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
hooks?.validate.post.execute(async function (created: HydratedDocument<T>) {
|
hooks?.validate.post.execute(async function (created: HydratedDocument<T>) {
|
||||||
await repository.postValidate(created);
|
await repository.postCreateValidate(created);
|
||||||
repository.emitter.emit(
|
repository.emitter.emit(
|
||||||
repository.getEventName(EHook.postValidate),
|
repository.getEventName(EHook.postCreateValidate),
|
||||||
created,
|
created,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -457,6 +475,23 @@ export abstract class BaseRepository<
|
|||||||
new: true,
|
new: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
const filterCriteria = query.getFilter();
|
||||||
|
const queryUpdates = query.getUpdate();
|
||||||
|
|
||||||
|
await this.preUpdateValidate(filterCriteria, queryUpdates);
|
||||||
|
this.emitter.emit(
|
||||||
|
this.getEventName(EHook.preUpdateValidate),
|
||||||
|
filterCriteria,
|
||||||
|
queryUpdates,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.postUpdateValidate(filterCriteria, queryUpdates);
|
||||||
|
this.emitter.emit(
|
||||||
|
this.getEventName(EHook.postUpdateValidate),
|
||||||
|
filterCriteria,
|
||||||
|
queryUpdates,
|
||||||
|
);
|
||||||
|
|
||||||
return await this.executeOne(query, this.cls);
|
return await this.executeOne(query, this.cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,11 +514,29 @@ export abstract class BaseRepository<
|
|||||||
return await this.model.deleteMany(criteria);
|
return await this.model.deleteMany(criteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
async preValidate(_doc: HydratedDocument<T>): Promise<void> {
|
async preCreateValidate(
|
||||||
|
_doc: HydratedDocument<T>,
|
||||||
|
_filterCriteria?: FilterQuery<T>,
|
||||||
|
_updates?: UpdateWithAggregationPipeline | UpdateQuery<T>,
|
||||||
|
): Promise<void> {
|
||||||
// Nothing ...
|
// Nothing ...
|
||||||
}
|
}
|
||||||
|
|
||||||
async postValidate(_validated: HydratedDocument<T>): Promise<void> {
|
async postCreateValidate(_validated: HydratedDocument<T>): Promise<void> {
|
||||||
|
// Nothing ...
|
||||||
|
}
|
||||||
|
|
||||||
|
async preUpdateValidate(
|
||||||
|
_filterCriteria: FilterQuery<T>,
|
||||||
|
_updates: UpdateWithAggregationPipeline | UpdateQuery<T>,
|
||||||
|
): Promise<void> {
|
||||||
|
// Nothing ...
|
||||||
|
}
|
||||||
|
|
||||||
|
async postUpdateValidate(
|
||||||
|
_filterCriteria: FilterQuery<T>,
|
||||||
|
_updates: UpdateWithAggregationPipeline | UpdateQuery<T>,
|
||||||
|
): Promise<void> {
|
||||||
// Nothing ...
|
// Nothing ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
26
api/types/event-emitter.d.ts
vendored
26
api/types/event-emitter.d.ts
vendored
@ -179,11 +179,13 @@ declare module '@nestjs/event-emitter' {
|
|||||||
type EventNamespaces = keyof IHookEntityOperationMap;
|
type EventNamespaces = keyof IHookEntityOperationMap;
|
||||||
|
|
||||||
/* pre hooks */
|
/* pre hooks */
|
||||||
type TPreValidate<T> = THydratedDocument<T>;
|
type TPreCreateValidate<T> = THydratedDocument<T>;
|
||||||
|
|
||||||
type TPreCreate<T> = THydratedDocument<T>;
|
type TPreCreate<T> = THydratedDocument<T>;
|
||||||
|
|
||||||
type TPreUpdate<T> = TFilterQuery<T> & object;
|
type TPreUpdateValidate<T> = FilterQuery<T>;
|
||||||
|
|
||||||
|
type TPreUpdate<T> = TFilterQuery<T>;
|
||||||
|
|
||||||
type TPreDelete<T> = Query<
|
type TPreDelete<T> = Query<
|
||||||
DeleteResult,
|
DeleteResult,
|
||||||
@ -195,23 +197,27 @@ declare module '@nestjs/event-emitter' {
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
type TPreUnion<T> =
|
type TPreUnion<T> =
|
||||||
| TPreValidate<T>
|
| TPreCreateValidate<T>
|
||||||
| TPreCreate<T>
|
| TPreCreate<T>
|
||||||
|
| TPreUpdateValidate<T>
|
||||||
| TPreUpdate<T>
|
| TPreUpdate<T>
|
||||||
| TPreDelete<T>;
|
| TPreDelete<T>;
|
||||||
|
|
||||||
/* post hooks */
|
/* post hooks */
|
||||||
type TPostValidate<T> = THydratedDocument<T>;
|
type TPostCreateValidate<T> = THydratedDocument<T>;
|
||||||
|
|
||||||
type TPostCreate<T> = THydratedDocument<T>;
|
type TPostCreate<T> = THydratedDocument<T>;
|
||||||
|
|
||||||
|
type TPostUpdateValidate<T> = FilterQuery<T>;
|
||||||
|
|
||||||
type TPostUpdate<T> = THydratedDocument<T>;
|
type TPostUpdate<T> = THydratedDocument<T>;
|
||||||
|
|
||||||
type TPostDelete = DeleteResult;
|
type TPostDelete = DeleteResult;
|
||||||
|
|
||||||
type TPostUnion<T> =
|
type TPostUnion<T> =
|
||||||
| TPostValidate<T>
|
| TPostCreateValidate<T>
|
||||||
| TPostCreate<T>
|
| TPostCreate<T>
|
||||||
|
| TPostUpdateValidate<T>
|
||||||
| TPostUpdate<T>
|
| TPostUpdate<T>
|
||||||
| TPostDelete;
|
| TPostDelete;
|
||||||
|
|
||||||
@ -247,11 +253,14 @@ declare module '@nestjs/event-emitter' {
|
|||||||
T = IHookEntityOperationMap[E]['schema'],
|
T = IHookEntityOperationMap[E]['schema'],
|
||||||
> =
|
> =
|
||||||
| {
|
| {
|
||||||
[EHook.preValidate]: TPreValidate<T>;
|
[EHook.preCreateValidate]: TPreCreateValidate<T>;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
[EHook.preCreate]: TPreCreate<T>;
|
[EHook.preCreate]: TPreCreate<T>;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
[EHook.preUpdateValidate]: TPreUpdateValidate<T>;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
[EHook.preUpdate]: TPreUpdate<T>;
|
[EHook.preUpdate]: TPreUpdate<T>;
|
||||||
}
|
}
|
||||||
@ -259,11 +268,14 @@ declare module '@nestjs/event-emitter' {
|
|||||||
[EHook.preDelete]: TPreDelete<T>;
|
[EHook.preDelete]: TPreDelete<T>;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
[EHook.postValidate]: TPostValidate<T>;
|
[EHook.postCreateValidate]: TPostCreateValidate<T>;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
[EHook.postCreate]: TPostCreate<T>;
|
[EHook.postCreate]: TPostCreate<T>;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
[EHook.postUpdateValidate]: TPostUpdateValidate<T>;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
[EHook.postUpdate]: TPostUpdate<T>;
|
[EHook.postUpdate]: TPostUpdate<T>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -95,6 +95,7 @@ const SettingInput: React.FC<RenderSettingInputProps> = ({
|
|||||||
label={label}
|
label={label}
|
||||||
helperText={helperText}
|
helperText={helperText}
|
||||||
{...field}
|
{...field}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
disabled={isDisabled(setting)}
|
disabled={isDisabled(setting)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user