diff --git a/src/components/data-model-table/editable.tsx b/src/components/data-model-table/editable.tsx index b030b1a..d74b143 100644 --- a/src/components/data-model-table/editable.tsx +++ b/src/components/data-model-table/editable.tsx @@ -594,9 +594,27 @@ export const GenericDataTable: React.FC = ( } const parentIdFieldName = gqlAssociationProps.parentIdFieldName; const associatedIdFieldName = gqlAssociationProps.associatedIdFieldName; - const gqlListQuery = gqlAssociationProps.gqlListQuery; + let gqlListQuery: any | ((parentRecord: any) => any) = + gqlAssociationProps.gqlListQuery; const getGqlQueryVariables = gqlListQuery?.getQueryVariables; let gqlQueryVariables = undefined; + + // Getting record + let record; + try { + record = form.getFieldValue(fieldPath.pop().keyPath); + } catch (_e: any) { + // ignore + } + if (!record) { + record = parentRecord; + } + // Setting query value + const gqlDocumentQuery = + typeof gqlListQuery?.query === 'function' + ? gqlListQuery?.query(record, useSelector) + : gqlListQuery?.query; + if (getGqlQueryVariables) { gqlQueryVariables = getGqlQueryVariables(parentRecord, useSelector); } @@ -607,7 +625,7 @@ export const GenericDataTable: React.FC = ( fieldPath={fieldPath} parentIdFieldName={parentIdFieldName!} associatedIdFieldName={associatedIdFieldName!} - gqlQuery={gqlListQuery?.query} + gqlQuery={gqlDocumentQuery} parentRecord={parentRecord} associatedRecordClass={field.dtoClass!} value={form.getFieldValue(fieldPath.namePath)} diff --git a/src/components/form/array-field.tsx b/src/components/form/array-field.tsx index bdbf435..86b9cef 100644 --- a/src/components/form/array-field.tsx +++ b/src/components/form/array-field.tsx @@ -36,9 +36,27 @@ export const ArrayField: React.FC = ( const associatedIdFieldName = schema.gqlAssociationProps.associatedIdFieldName; if (disabled) { - const gqlQuery = schema.gqlAssociationProps.gqlListSelectedQuery; + let gqlQuery: any | ((parentRecord: any) => any) = + schema.gqlAssociationProps.gqlListSelectedQuery; const getGqlQueryVariables = gqlQuery?.getQueryVariables; let gqlQueryVariables = undefined; + + // Getting record + let record; + try { + record = form.getFieldValue(fieldPath.pop().keyPath); + } catch (_e: any) { + // ignore + } + if (!record) { + record = parentRecord; + } + // Setting query value + const gqlDocumentQuery = + typeof gqlQuery?.query === 'function' + ? gqlQuery?.query(record, useSelector) + : gqlQuery?.query; + if (getGqlQueryVariables) { gqlQueryVariables = getGqlQueryVariables(parentRecord, useSelector); } @@ -48,7 +66,7 @@ export const ArrayField: React.FC = ( expandedContent={ @@ -58,19 +76,28 @@ export const ArrayField: React.FC = ( ); } else { - const gqlQuery = schema.gqlAssociationProps.gqlListQuery; + const gqlQuery: any | ((parentRecord: any) => any) = + schema.gqlAssociationProps.gqlListQuery; const getGqlQueryVariables = gqlQuery?.getQueryVariables; let gqlQueryVariables = undefined; + + // Getting record + let record; + try { + record = form.getFieldValue(fieldPath.pop().keyPath); + } catch (_e: any) { + // ignore + } + if (!record) { + record = parentRecord; + } + // Setting query value + const gqlDocumentQuery = + typeof gqlQuery?.query === 'function' + ? gqlQuery?.query(record, useSelector) + : gqlQuery?.query; + if (getGqlQueryVariables) { - let record; - try { - record = form.getFieldValue(fieldPath.pop().keyPath); - } catch (_e: any) { - // ignore - } - if (!record) { - record = parentRecord; - } gqlQueryVariables = getGqlQueryVariables(record, useSelector); } return ( @@ -81,7 +108,7 @@ export const ArrayField: React.FC = ( selectable={SelectionType.MULTIPLE} parentIdFieldName={parentIdFieldName!} associatedIdFieldName={associatedIdFieldName!} - gqlQuery={gqlQuery?.query} + gqlQuery={gqlDocumentQuery} gqlQueryVariables={gqlQueryVariables} parentRecord={parentRecord} associatedRecordClass={schema.dtoClass!} diff --git a/src/components/form/nested-object-field.tsx b/src/components/form/nested-object-field.tsx index 6bf9c68..0955ea1 100644 --- a/src/components/form/nested-object-field.tsx +++ b/src/components/form/nested-object-field.tsx @@ -29,20 +29,35 @@ export const NestedObjectField: ( const parentIdFieldName = schema.gqlAssociationProps.parentIdFieldName; const associatedIdFieldName = schema.gqlAssociationProps.associatedIdFieldName; - const gqlQuery = schema.gqlAssociationProps.gqlQuery; - const gqlListQuery = schema.gqlAssociationProps.gqlListQuery; + let gqlQuery: any | ((parentRecord: any) => any) = + schema.gqlAssociationProps.gqlQuery; + let gqlListQuery: any | ((parentRecord: any) => any) = + schema.gqlAssociationProps.gqlListQuery; const getGqlQueryVariables = gqlListQuery?.getQueryVariables; let gqlQueryVariables = undefined; + + // Getting record + let record; + try { + record = form.getFieldValue(fieldPath.pop().keyPath); + } catch (_e: any) { + // ignore + } + if (!record) { + record = parentRecord; + } + + // Setting query value + const gqlDocumentQuery = + typeof gqlQuery?.query === 'function' + ? gqlQuery?.query(record, useSelector) + : gqlQuery?.query; + const gqlDocumentListQuery = + typeof gqlListQuery?.query === 'function' + ? gqlListQuery?.query(record, useSelector) + : gqlListQuery?.query; + if (getGqlQueryVariables) { - let record; - try { - record = form.getFieldValue(fieldPath.pop().keyPath); - } catch (_e: any) { - // ignore - } - if (!record) { - record = parentRecord; - } gqlQueryVariables = getGqlQueryVariables(record, useSelector); } if (disabled) { @@ -52,8 +67,8 @@ export const NestedObjectField: ( associatedRecordClass={schema.dtoClass!} parentIdFieldName={parentIdFieldName!} associatedIdFieldName={associatedIdFieldName!} - gqlQuery={gqlQuery?.query} - gqlListQuery={gqlListQuery?.query} + gqlQuery={gqlDocumentQuery} + gqlListQuery={gqlDocumentListQuery} /> ); } else { @@ -77,7 +92,7 @@ export const NestedObjectField: ( fieldPath={fieldPath} parentIdFieldName={parentIdFieldName!} associatedIdFieldName={associatedIdFieldName!} - gqlQuery={gqlListQuery?.query} + gqlQuery={gqlDocumentListQuery} gqlQueryVariables={gqlQueryVariables} parentRecord={parentRecord} associatedRecordClass={schema.dtoClass!} diff --git a/src/message/get-monitoring-report/index.tsx b/src/message/get-monitoring-report/index.tsx new file mode 100644 index 0000000..97b8976 --- /dev/null +++ b/src/message/get-monitoring-report/index.tsx @@ -0,0 +1,190 @@ +import { Form } from 'antd'; +import { GenericForm } from '../../components/form'; +import { ChargingStation } from 'src/pages/charging-stations/ChargingStation'; +import { IsArray, IsEnum, IsNotEmpty, IsNumber } from 'class-validator'; +import { plainToInstance, Type } from 'class-transformer'; +import { OCPP2_0_1 } from '@citrineos/base'; +import { GqlAssociation } from '@util/decorators/GqlAssociation'; +import { + Component, + ComponentProps, +} from '../../pages/variable-attributes/components/Component'; +import { + Variable, + VariableProps, +} from '../../pages/variable-attributes/variables/Variable'; +import { ClassCustomConstructor } from '@util/decorators/ClassCustomConstructor'; +import { NEW_IDENTIFIER } from '@util/consts'; +import { + VARIABLE_LIST_BY_COMPONENT_QUERY, + VARIABLE_LIST_QUERY, +} from '../../pages/variable-attributes/variables/queries'; +import { + COMPONENT_LIST_BY_VARIABLE_QUERY, + COMPONENT_LIST_QUERY, +} from '../../pages/variable-attributes/components/queries'; +import { triggerMessageAndHandleResponse } from '../util'; +import { MessageConfirmation } from '../MessageConfirmation'; + +export interface GetMonitoringReportProps { + station: ChargingStation; +} + +enum GetMonitoringReportRequestProps { + // customData = 'customData', // todo + componentVariable = 'componentVariable', + requestId = 'requestId', + monitoringCriteria = 'monitoringCriteria', +} + +enum ComponentVariableProps { + component = 'component', + variable = 'variable', +} + +const ComponentVariableCustomConstructor = () => { + const variable = new Variable(); + const component = new Component(); + variable[VariableProps.id] = NEW_IDENTIFIER as unknown as number; + component[ComponentProps.id] = NEW_IDENTIFIER as unknown as number; + const componentVariable = new ComponentVariable(); + componentVariable[ComponentVariableProps.component] = component; + componentVariable[ComponentVariableProps.variable] = variable; + return componentVariable; +}; + +@ClassCustomConstructor(ComponentVariableCustomConstructor) +export class ComponentVariable { + @Type(() => Component) + @IsNotEmpty() + @GqlAssociation({ + parentIdFieldName: ComponentVariableProps.component, + associatedIdFieldName: ComponentProps.id, + gqlQuery: { + query: COMPONENT_LIST_QUERY, + }, + gqlListQuery: { + query: (record: ComponentVariable) => { + if (record.variable?.id != (NEW_IDENTIFIER as unknown as number)) { + return COMPONENT_LIST_BY_VARIABLE_QUERY; + } else { + return COMPONENT_LIST_QUERY; + } + }, + getQueryVariables: (record: ComponentVariable) => { + if (record.variable?.id != (NEW_IDENTIFIER as unknown as number)) { + return { + variableId: record.variable?.id, + }; + } else { + return {}; + } + }, + }, + }) + component!: Component; + + @Type(() => Variable) + @IsNotEmpty() + @GqlAssociation({ + parentIdFieldName: ComponentVariableProps.variable, + associatedIdFieldName: VariableProps.id, + gqlQuery: { + query: VARIABLE_LIST_QUERY, + }, + gqlListQuery: { + query: (record: ComponentVariable) => { + if (record.component?.id != (NEW_IDENTIFIER as unknown as number)) { + return VARIABLE_LIST_BY_COMPONENT_QUERY; + } else { + return VARIABLE_LIST_QUERY; + } + }, + getQueryVariables: (record: ComponentVariable) => { + if (record.component?.id != (NEW_IDENTIFIER as unknown as number)) { + return { + componentId: record.component?.id, + mutability: 'ReadOnly', + }; + } else { + return {}; + } + }, + }, + }) + variable!: Variable; +} + +export class GetMonitoringReportRequest { + // todo + // @Type(() => CustomDataType) + // @ValidateNested() + // customData?: CustomDataType; + + @Type(() => ComponentVariable) + @IsNotEmpty() + @IsArray() + componentVariable!: ComponentVariable[] | null; + + @IsNotEmpty() + @IsNumber() + requestId!: number | null; + + @IsEnum(OCPP2_0_1.MonitoringCriterionEnumType) + @IsNotEmpty() + monitoringCriteria!: OCPP2_0_1.MonitoringCriterionEnumType; +} + +export const GetMonitoringReport: React.FC = ({ + station, +}) => { + const [form] = Form.useForm(); + const formProps = { + form, + }; + + const getMonitoringReportRequest = new GetMonitoringReportRequest(); + getMonitoringReportRequest[ + GetMonitoringReportRequestProps.componentVariable + ] = [ComponentVariableCustomConstructor()]; + + const handleSubmit = async (plainValues: any) => { + const classInstance = plainToInstance( + GetMonitoringReportRequest, + plainValues, + ); + + let data: any = {}; + + if ( + classInstance && + classInstance[GetMonitoringReportRequestProps.componentVariable] + ) { + data = { + requestId: classInstance[GetMonitoringReportRequestProps.requestId], + monitoringCriteria: + classInstance[GetMonitoringReportRequestProps.monitoringCriteria], + componentVariable: + classInstance[GetMonitoringReportRequestProps.componentVariable], + }; + } + + await triggerMessageAndHandleResponse({ + url: `/reporting/getMonitoringReport?identifier=${station.id}&tenantId=1`, + responseClass: MessageConfirmation, + data: data, + responseSuccessCheck: (response: MessageConfirmation) => + response && response.success, + }); + }; + + return ( + + ); +}; diff --git a/src/message/index.tsx b/src/message/index.tsx index 4260845..e46e1e0 100644 --- a/src/message/index.tsx +++ b/src/message/index.tsx @@ -47,6 +47,18 @@ import { } from './delete-station-network-profiles'; import { DeleteCertificate } from './delete-certificate'; import { ChangeAvailabilityProps } from './change-availability/model'; +import { + GetMonitoringReport, + GetMonitoringReportProps, +} from './get-monitoring-report'; +import { + SetMonitoringBase, + SetMonitoringBaseProps, +} from './set-monitoring-base'; +import { + SetVariableMonitoring, + SetVariableMonitoringProps, +} from './set-variable-monitoring'; const chargingStationActionMap: { [label: string]: React.FC; @@ -78,6 +90,11 @@ const chargingStationActionMap: { 'Update Firmware': UpdateFirmware as React.FC, 'Update Auth Password': UpdateAuthPassword as React.FC, + 'Get Monitoring Report': + GetMonitoringReport as React.FC, + 'Set Monitoring Base': SetMonitoringBase as React.FC, + 'Set Variable Monitoring': + SetVariableMonitoring as React.FC, }; export const CUSTOM_CHARGING_STATION_ACTIONS: CustomAction[] = diff --git a/src/message/set-monitoring-base/index.tsx b/src/message/set-monitoring-base/index.tsx new file mode 100644 index 0000000..5ce9da7 --- /dev/null +++ b/src/message/set-monitoring-base/index.tsx @@ -0,0 +1,70 @@ +import { Form } from 'antd'; +import { GenericForm } from '../../components/form'; +import { ChargingStation } from 'src/pages/charging-stations/ChargingStation'; +import { OCPP2_0_1 } from '@citrineos/base'; +import { IsEnum, IsNotEmpty } from 'class-validator'; +import { plainToInstance } from 'class-transformer'; +import { triggerMessageAndHandleResponse } from '../util'; +import { MessageConfirmation } from '../MessageConfirmation'; + +export interface SetMonitoringBaseProps { + station: ChargingStation; +} + +enum SetMonitoringBaseRequestProps { + monitoringBase = 'monitoringBase', +} + +export class SetMonitoringBaseRequest { + @IsEnum(OCPP2_0_1.MonitoringBaseEnumType) + @IsNotEmpty() + monitoringBase!: OCPP2_0_1.MonitoringBaseEnumType; +} + +export const SetMonitoringBase: React.FC = ({ + station, +}) => { + const [form] = Form.useForm(); + const formProps = { + form, + }; + + const setMonitoringBaseRequest = new SetMonitoringBaseRequest(); + + const handleSubmit = async (plainValues: any) => { + const classInstance = plainToInstance( + SetMonitoringBaseRequest, + plainValues, + ); + + let data: any = {}; + + if ( + classInstance && + classInstance[SetMonitoringBaseRequestProps.monitoringBase] + ) { + data = { + monitoringBase: + classInstance[SetMonitoringBaseRequestProps.monitoringBase], + }; + } + + await triggerMessageAndHandleResponse({ + url: `/monitoring/setMonitoringBase?identifier=${station.id}&tenantId=1`, + responseClass: MessageConfirmation, + data: data, + responseSuccessCheck: (response: MessageConfirmation) => + response && response.success, + }); + }; + + return ( + + ); +}; diff --git a/src/message/set-variable-monitoring/index.tsx b/src/message/set-variable-monitoring/index.tsx new file mode 100644 index 0000000..9269116 --- /dev/null +++ b/src/message/set-variable-monitoring/index.tsx @@ -0,0 +1,175 @@ +import { Form } from 'antd'; +import { GenericForm } from '../../components/form'; +import { ChargingStation } from 'src/pages/charging-stations/ChargingStation'; +import { plainToInstance, Type } from 'class-transformer'; +import { triggerMessageAndHandleResponse } from '../util'; +import { MessageConfirmation } from '../MessageConfirmation'; +import { OCPP2_0_1 } from '@citrineos/base'; +import { + Component, + ComponentProps, +} from '../../pages/variable-attributes/components/Component'; +import { + Variable, + VariableProps, +} from '../../pages/variable-attributes/variables/Variable'; +import { + ArrayMinSize, + IsArray, + IsBoolean, + IsEnum, + IsNotEmpty, + IsNumber, + ValidateNested, +} from 'class-validator'; +import { GqlAssociation } from '@util/decorators/GqlAssociation'; +import { + COMPONENT_GET_QUERY, + COMPONENT_LIST_QUERY, +} from '../../pages/variable-attributes/components/queries'; +import { + VARIABLE_GET_QUERY, + VARIABLE_LIST_QUERY, +} from '../../pages/variable-attributes/variables/queries'; +import { NEW_IDENTIFIER } from '@util/consts'; +import { ClassCustomConstructor } from '@util/decorators/ClassCustomConstructor'; + +export interface SetVariableMonitoringProps { + station: ChargingStation; +} + +export enum SetMonitoringDataProps { + id = 'id', + transaction = 'transaction', + value = 'value', + type = 'type', + severity = 'severity', + component = 'component', + variable = 'variable', +} + +enum SetVariableMonitoringRequestProps { + setMonitoringData = 'setMonitoringData', +} + +const SetMonitoringDataCustomConstructor = () => { + const variable = new Variable(); + const component = new Component(); + variable[VariableProps.id] = NEW_IDENTIFIER as unknown as number; + component[ComponentProps.id] = NEW_IDENTIFIER as unknown as number; + const setMonitoringData = new SetMonitoringData(); + setMonitoringData[SetMonitoringDataProps.component] = component; + setMonitoringData[SetMonitoringDataProps.variable] = variable; + return setMonitoringData; +}; + +@ClassCustomConstructor(SetMonitoringDataCustomConstructor) +export class SetMonitoringData { + @IsNotEmpty() + @IsNumber() + id!: number | null; + + @IsBoolean() + @IsNotEmpty() + transaction!: boolean | null; + + @IsNotEmpty() + @IsNumber() + value!: number | null; + + @IsEnum(OCPP2_0_1.MonitorEnumType) + @IsNotEmpty() + type!: OCPP2_0_1.MonitorEnumType; + + @IsNotEmpty() + @IsNumber() + severity!: number | null; + + @Type(() => Component) + @IsNotEmpty() + @GqlAssociation({ + parentIdFieldName: SetMonitoringDataProps.component, + associatedIdFieldName: ComponentProps.id, + gqlQuery: { + query: COMPONENT_GET_QUERY, + }, + gqlListQuery: { + query: COMPONENT_LIST_QUERY, + }, + }) + component!: Component; + + @Type(() => Variable) + @IsNotEmpty() + @GqlAssociation({ + parentIdFieldName: SetMonitoringDataProps.variable, + associatedIdFieldName: VariableProps.id, + gqlQuery: { + query: VARIABLE_GET_QUERY, + }, + gqlListQuery: { + query: VARIABLE_LIST_QUERY, + }, + }) + variable!: Variable; +} + +export class SetVariableMonitoringRequest { + @IsNotEmpty() + @ArrayMinSize(1) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => SetMonitoringData) + setMonitoringData!: SetMonitoringData[]; +} + +export const SetVariableMonitoring: React.FC = ({ + station, +}) => { + const [form] = Form.useForm(); + const formProps = { + form, + }; + + const setVariableMonitoringRequest = new SetVariableMonitoringRequest(); + setVariableMonitoringRequest[ + SetVariableMonitoringRequestProps.setMonitoringData + ] = [SetMonitoringDataCustomConstructor()]; + + const handleSubmit = async (plainValues: any) => { + const classInstance = plainToInstance( + SetVariableMonitoringRequest, + plainValues, + ); + + let data: any = {}; + + if ( + classInstance && + classInstance[SetVariableMonitoringRequestProps.setMonitoringData] + ) { + data = { + setMonitoringData: + classInstance[SetVariableMonitoringRequestProps.setMonitoringData], + }; + } + + await triggerMessageAndHandleResponse({ + url: `/monitoring/setVariableMonitoring?identifier=${station.id}&tenantId=1`, + responseClass: MessageConfirmation, + data: data, + responseSuccessCheck: (response: MessageConfirmation) => + response && response.success, + }); + }; + + return ( + + ); +}; diff --git a/src/pages/variable-attributes/components/queries.ts b/src/pages/variable-attributes/components/queries.ts index 1d4d474..2c81048 100644 --- a/src/pages/variable-attributes/components/queries.ts +++ b/src/pages/variable-attributes/components/queries.ts @@ -32,6 +32,51 @@ export const COMPONENT_LIST_QUERY = gql` } `; +export const COMPONENT_LIST_BY_VARIABLE_QUERY = gql` + query ComponentListByVariable( + $variableId: Int! + $offset: Int! + $limit: Int! + $order_by: [Components_order_by!] + $where: Components_bool_exp = {} + ) { + Components( + offset: $offset + limit: $limit + order_by: $order_by + where: { + _and: [ + { ComponentVariables: { variableId: { _eq: $variableId } } } + $where + ] + } + ) { + id + instance + name + evseDatabaseId + createdAt + updatedAt + Evse { + connectorId + id + } + } + Components_aggregate( + where: { + _and: [ + { ComponentVariables: { variableId: { _eq: $variableId } } } + $where + ] + } + ) { + aggregate { + count + } + } + } +`; + export const COMPONENT_GET_QUERY = gql` query GetComponentById($id: Int!) { Components_by_pk(id: $id) { diff --git a/src/util/decorators/GqlAssociation.tsx b/src/util/decorators/GqlAssociation.tsx index a93b457..46010bf 100644 --- a/src/util/decorators/GqlAssociation.tsx +++ b/src/util/decorators/GqlAssociation.tsx @@ -5,7 +5,7 @@ export interface QueryWithVariableGetter { /** * Grapql query */ - query: any; + query: any | ((record: any, useSelector: any) => any); /** * Set a method to generate the query variables using the parent record * @param record