diff --git a/Common/Models/ApiKey.ts b/Common/Models/ApiKey.ts index 9a91eedef0..d049a9502e 100644 --- a/Common/Models/ApiKey.ts +++ b/Common/Models/ApiKey.ts @@ -42,6 +42,7 @@ export default class ApiKey extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'projectId', type: TableColumnType.Entity, + modelType: Project }) @ManyToOne( (_type: string) => { @@ -119,6 +120,7 @@ export default class ApiKey extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'createdByUserId', type: TableColumnType.Entity, + modelType: User }) @ManyToOne( (_type: string) => { diff --git a/Common/Models/ApiKeyPermission.ts b/Common/Models/ApiKeyPermission.ts index a67cf86097..567d120d8f 100644 --- a/Common/Models/ApiKeyPermission.ts +++ b/Common/Models/ApiKeyPermission.ts @@ -74,6 +74,7 @@ export default class APIKeyPermission extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'apiKeyId', type: TableColumnType.Entity, + modelType: ApiKey }) @ManyToOne( (_type: string) => { @@ -105,6 +106,7 @@ export default class APIKeyPermission extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'projectId', type: TableColumnType.Entity, + modelType: Project }) @ManyToOne( (_type: string) => { @@ -176,6 +178,7 @@ export default class APIKeyPermission extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'createdByUserId', type: TableColumnType.Entity, + modelType: User }) @ManyToOne( (_type: string) => { @@ -292,7 +295,7 @@ export default class APIKeyPermission extends BaseModel { Permission.CanEditProjectApiKey, ], }) - @TableColumn({ required: false, type: TableColumnType.Array }) + @TableColumn({ required: false, type: TableColumnType.EntityArray, modelType: Label }) @ManyToMany(() => { return Label; }) diff --git a/Common/Models/BaseModel.ts b/Common/Models/BaseModel.ts index 3de84a1e9e..42b54aefe1 100644 --- a/Common/Models/BaseModel.ts +++ b/Common/Models/BaseModel.ts @@ -269,6 +269,11 @@ export default class BaseModel extends BaseEntity { return Boolean(getTableColumn(this, columnName).isDefaultValueColumn); } + public isEntityColumn(columnName: string): boolean { + const tableColumnType: TableColumnMetadata = getTableColumn(this, columnName); + return Boolean(tableColumnType.type === TableColumnType.Entity || tableColumnType.type === TableColumnType.EntityArray); + } + public toJSON(): JSONObject { const json: JSONObject = this.toJSONObject(); return JSONFunctions.serialize(json); diff --git a/Common/Models/EmailVerificationToken.ts b/Common/Models/EmailVerificationToken.ts index 0277fc0622..bfbda29a00 100755 --- a/Common/Models/EmailVerificationToken.ts +++ b/Common/Models/EmailVerificationToken.ts @@ -34,6 +34,7 @@ export default class EmailVerificationToken extends BaseModel { manyToOneRelationColumn: 'userId', required: true, type: TableColumnType.Entity, + modelType: User }) @ManyToOne( (_type: string) => { diff --git a/Common/Models/Label.ts b/Common/Models/Label.ts index ff7a886204..a995a01adc 100644 --- a/Common/Models/Label.ts +++ b/Common/Models/Label.ts @@ -48,6 +48,7 @@ export default class Label extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'projectId', type: TableColumnType.Entity, + modelType: Project }) @ManyToOne( (_type: string) => { @@ -146,6 +147,7 @@ export default class Label extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'createdByUserId', type: TableColumnType.Entity, + modelType: Project }) @ManyToOne( (_type: string) => { diff --git a/Common/Models/Probe.ts b/Common/Models/Probe.ts index 5f9c70e6da..481166ed96 100755 --- a/Common/Models/Probe.ts +++ b/Common/Models/Probe.ts @@ -143,7 +143,7 @@ export default class Probe extends BaseModel { read: [Permission.ProjectMember, Permission.Public], update: [], }) - @TableColumn({ type: TableColumnType.Entity, required: false }) + @TableColumn({ type: TableColumnType.Entity, required: false, modelType: Project }) @ManyToOne( (_type: string) => { return Project; @@ -177,7 +177,7 @@ export default class Probe extends BaseModel { read: [], update: [], }) - @TableColumn({ type: TableColumnType.Entity }) + @TableColumn({ type: TableColumnType.Entity, modelType: User }) @ManyToOne( (_type: string) => { return User; diff --git a/Common/Models/Project.ts b/Common/Models/Project.ts index e03a86a846..f8b7f7d008 100644 --- a/Common/Models/Project.ts +++ b/Common/Models/Project.ts @@ -119,6 +119,7 @@ export default class Model extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'createdByUserId', type: TableColumnType.Entity, + modelType: User }) @ManyToOne( (_type: string) => { diff --git a/Common/Models/ProjectAPIKey.ts b/Common/Models/ProjectAPIKey.ts index 16f7d0aa09..5f4cb0dd5d 100644 --- a/Common/Models/ProjectAPIKey.ts +++ b/Common/Models/ProjectAPIKey.ts @@ -12,7 +12,7 @@ import TableColumnType from '../Types/Database/TableColumnType'; name: 'ProjectAPIKey', }) export default class ProjectAPIKey extends BaseModel { - @TableColumn({ type: TableColumnType.Entity }) + @TableColumn({ type: TableColumnType.Entity, modelType: Project }) @ManyToOne( (_type: string) => { return Project; @@ -36,7 +36,7 @@ export default class ProjectAPIKey extends BaseModel { }) public projectId?: ObjectID; - @TableColumn({ type: TableColumnType.Entity }) + @TableColumn({ type: TableColumnType.Entity, modelType: User }) @ManyToOne( (_type: string) => { return User; @@ -60,7 +60,7 @@ export default class ProjectAPIKey extends BaseModel { }) public deletedByUserId?: ObjectID; - @TableColumn({ type: TableColumnType.Entity }) + @TableColumn({ type: TableColumnType.Entity, modelType: User }) @ManyToOne( (_type: string) => { return User; diff --git a/Common/Models/Team.ts b/Common/Models/Team.ts index 7b45a2fa82..075675e8ca 100644 --- a/Common/Models/Team.ts +++ b/Common/Models/Team.ts @@ -60,6 +60,7 @@ export default class Team extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'projectId', type: TableColumnType.Entity, + modelType: Project }) @ManyToOne( (_type: string) => { @@ -159,6 +160,7 @@ export default class Team extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'createdByUserId', type: TableColumnType.Entity, + modelType: Project }) @ManyToOne( (_type: string) => { diff --git a/Common/Models/TeamMember.ts b/Common/Models/TeamMember.ts index e3fe7ed83d..4a28a27900 100644 --- a/Common/Models/TeamMember.ts +++ b/Common/Models/TeamMember.ts @@ -63,6 +63,7 @@ export default class TeamMember extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'teamId', type: TableColumnType.Entity, + modelType: Team }) @ManyToOne( (_type: string) => { @@ -116,6 +117,7 @@ export default class TeamMember extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'projectId', type: TableColumnType.Entity, + modelType: Project }) @ManyToOne( (_type: string) => { @@ -169,6 +171,7 @@ export default class TeamMember extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'userId', type: TableColumnType.Entity, + modelType: User }) @ManyToOne( (_type: string) => { @@ -213,6 +216,7 @@ export default class TeamMember extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'createdByUserId', type: TableColumnType.Entity, + modelType: User }) @ManyToOne( (_type: string) => { diff --git a/Common/Models/TeamPermission.ts b/Common/Models/TeamPermission.ts index a397d76fe1..8a2e01d10d 100644 --- a/Common/Models/TeamPermission.ts +++ b/Common/Models/TeamPermission.ts @@ -71,6 +71,7 @@ export default class TeamPermission extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'teamId', type: TableColumnType.Entity, + modelType: Team }) @ManyToOne( (_type: string) => { @@ -102,6 +103,7 @@ export default class TeamPermission extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'projectId', type: TableColumnType.Entity, + modelType: Project }) @ManyToOne( (_type: string) => { @@ -173,6 +175,7 @@ export default class TeamPermission extends BaseModel { @TableColumn({ manyToOneRelationColumn: 'createdByUserId', type: TableColumnType.Entity, + modelType: User }) @ManyToOne( (_type: string) => { diff --git a/Common/Types/Database/TableColumn.ts b/Common/Types/Database/TableColumn.ts index eb50d4510a..88e1ccde62 100644 --- a/Common/Types/Database/TableColumn.ts +++ b/Common/Types/Database/TableColumn.ts @@ -17,6 +17,7 @@ export interface TableColumnMetadata { encrypted?: boolean; manyToOneRelationColumn?: string; type: TableColumnType; + modelType?: { new (): BaseModel } } export default (props: TableColumnMetadata): ReflectionMetadataType => { diff --git a/Common/Types/Database/TableColumnType.ts b/Common/Types/Database/TableColumnType.ts index c77980fb22..f1a87845d3 100644 --- a/Common/Types/Database/TableColumnType.ts +++ b/Common/Types/Database/TableColumnType.ts @@ -24,6 +24,7 @@ enum ColumnType { Number, BigNumber, Entity, + EntityArray } export default ColumnType; diff --git a/CommonServer/Services/DatabaseService.ts b/CommonServer/Services/DatabaseService.ts index ec0adeed03..ce3a2c962e 100644 --- a/CommonServer/Services/DatabaseService.ts +++ b/CommonServer/Services/DatabaseService.ts @@ -46,6 +46,9 @@ import QueryHelper from '../Types/Database/QueryHelper'; import { getUniqueColumnsBy } from 'Common/Types/Database/UniqueColumnBy'; import Search from 'Common/Types/Database/Search'; import Typeof from 'Common/Types/Typeof'; +import TableColumns from 'Common/Types/Database/Columns'; +import TableColumnType from 'Common/Types/Database/TableColumnType'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; enum DatabaseRequestType { Create = 'create', @@ -302,6 +305,45 @@ class DatabaseService { return createBy; } + private serializeCreate(data: TBaseModel | QueryDeepPartialEntity): TBaseModel | QueryDeepPartialEntity { + const columns: TableColumns = this.model.getTableColumns(); + + for (const columnName of columns.columns) { + if (this.model.isEntityColumn(columnName)) { + const tableColumnMetadata = this.model.getTableColumnMetadata(columnName); + + if (data && tableColumnMetadata.modelType && (data as any)[columnName] && tableColumnMetadata.type === TableColumnType.Entity && (typeof (data as any)[columnName] === "string" || (data as any)[columnName] instanceof ObjectID)) { + (data as any)[columnName] = new tableColumnMetadata.modelType(); + (data as any)[columnName]._id = (data as any)[columnName].toString(); + } + + if (data + && Array.isArray((data as any)[columnName]) + && (data as any)[columnName].length > 0 + && tableColumnMetadata.modelType + && (data as any)[columnName] + && tableColumnMetadata.type === TableColumnType.EntityArray + ) { + + const itemsArray: Array = []; + for (const item of (data as any)[columnName]) { + if (typeof (data as any)[columnName] === "string" || (data as any)[columnName] instanceof ObjectID) { + const basemodelItem = new tableColumnMetadata.modelType(); + basemodelItem._id = (data as any)[columnName].toString(); + itemsArray.push(basemodelItem); + } else { + itemsArray.push(item); + } + } + (data as any)[columnName] = itemsArray; + } + + } + } + + return data; + } + public async create(createBy: CreateBy): Promise { let _createdBy: CreateBy = await this._onBeforeCreate( createBy @@ -329,6 +371,10 @@ class DatabaseService { // check uniqueColumns by: createBy = await this.checkUniqueColumnBy(createBy); + // serialize. + + createBy.data = (await this.serializeCreate(createBy.data) as TBaseModel); + try { createBy.data = await this.getRepository().save(createBy.data); await this.onCreateSuccess(createBy); @@ -1027,11 +1073,12 @@ class DatabaseService { beforeUpdateBy = this.asUpdateByByPermissions(beforeUpdateBy); + debugger; const numberOfDocsAffected: number = ( await this.getRepository().update( - beforeUpdateBy.query as any, - beforeUpdateBy.data + this.serializeQuery(beforeUpdateBy.query as any) as any, + this.serializeCreate(beforeUpdateBy.data) as QueryDeepPartialEntity ) ).affected || 0; diff --git a/CommonUI/src/Components/Dropdown/Dropdown.tsx b/CommonUI/src/Components/Dropdown/Dropdown.tsx index be57c97e9b..fdabb64dc9 100644 --- a/CommonUI/src/Components/Dropdown/Dropdown.tsx +++ b/CommonUI/src/Components/Dropdown/Dropdown.tsx @@ -6,7 +6,7 @@ import React, { useState, } from 'react'; -type DropdownValue = string | number; +export type DropdownValue = string | number; export interface DropdownOption { value: DropdownValue; @@ -19,7 +19,7 @@ export interface ComponentProps { onClick?: undefined | (() => void); placeholder?: undefined | string; className?: undefined | string; - onChange?: undefined | ((value: DropdownValue) => void); + onChange?: undefined | ((value: DropdownValue | Array) => void); value?: DropdownOption | undefined; onFocus?: (() => void) | undefined; onBlur?: (() => void) | undefined; @@ -29,8 +29,8 @@ export interface ComponentProps { const Dropdown: FunctionComponent = ( props: ComponentProps ): ReactElement => { - const [value, setValue] = useState(null); - const [selectedValue, setSelectedValue] = useState(null); + const [value, setValue] = useState | null>(null); + const [selectedValue, setSelectedValue] = useState | null>(null); useEffect(() => { if (props.initialValue) { @@ -57,17 +57,36 @@ const Dropdown: FunctionComponent = ( useEffect(() => { const selectedValues: Array = props.options.filter( (item: DropdownOption) => { - return item.value === value?.value; + if (Array.isArray(value)) { + return value.map((v) => { + return v.value + }).includes(item.value); + } else { + return item.value === value?.value; + } + } ); let selectedValue: DropdownOption | null = null; - if (selectedValues.length > 0) { + if (selectedValues.length > 0 && !props.isMultiSelect) { selectedValue = selectedValues[0] || null; } - setSelectedValue(selectedValue); + setSelectedValue(props.isMultiSelect ? selectedValues : selectedValue); + if (value) { + if (Array.isArray(value)) { + props.onChange && + props.onChange((value as Array).map((i: DropdownOption) => { + return i.value; + })); + } else { + props.onChange && + props.onChange((value as DropdownOption).value); + } + } + }, [value]); @@ -92,10 +111,15 @@ const Dropdown: FunctionComponent = ( options={props.options as any} onChange={(option: any | null) => { if (option) { - const value: DropdownOption = (option as DropdownOption) - setValue(value); - props.onChange && - props.onChange((option as DropdownOption).value); + if (props.isMultiSelect) { + const value: Array = (option as Array) + setValue(value); + + } else { + const value: DropdownOption = (option as DropdownOption) + setValue(value); + + } } }} /> diff --git a/CommonUI/src/Components/Forms/BasicForm.tsx b/CommonUI/src/Components/Forms/BasicForm.tsx index 24ac3ef93a..e2a4a61916 100644 --- a/CommonUI/src/Components/Forms/BasicForm.tsx +++ b/CommonUI/src/Components/Forms/BasicForm.tsx @@ -22,7 +22,7 @@ import Alert, { AlertType } from '../Alerts/Alert'; import ColorPicker from './Fields/ColorPicker'; import Color from 'Common/Types/Color'; import TextArea from './Fields/TextArea'; -import Dropdown from '../Dropdown/Dropdown'; +import Dropdown, { DropdownValue } from '../Dropdown/Dropdown'; import OneUptimeDate from 'Common/Types/Date'; export const DefaultValidateFunction: Function = ( @@ -155,7 +155,7 @@ const BasicForm: Function = ( return ( ) => { await form.setFieldValue( fieldName,