diff --git a/package-lock.json b/package-lock.json index c2a36894c..a4e5c54a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18258,4 +18258,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/app/api/dataTransforms/deviceSummaryTransform.spec.ts b/src/app/api/dataTransforms/deviceSummaryTransform.spec.ts index d1e77d930..37835cc21 100644 --- a/src/app/api/dataTransforms/deviceSummaryTransform.spec.ts +++ b/src/app/api/dataTransforms/deviceSummaryTransform.spec.ts @@ -17,6 +17,7 @@ describe('utils', () => { DeviceId: 'test', IotEdge: false, LastActivityTime: '2019-07-18T10:01:20.0568390Z', + ModelId: 'dtmi:com:example:Thermostat;1', Status: 'Enabled', StatusUpdatedTime: null, @@ -28,6 +29,7 @@ describe('utils', () => { deviceId: 'test', iotEdge: false, lastActivityTime: '3:01:20 AM, July 18, 2019', + modelId: 'dtmi:com:example:Thermostat;1', status: 'Enabled', statusUpdatedTime: null, }; @@ -42,6 +44,7 @@ describe('utils', () => { expect(transformedDevice.lastActivityTime.match(isLocalTime)).toBeTruthy(); expect(transformedDevice.status).toEqual(deviceSummary.status); expect(transformedDevice.statusUpdatedTime).toEqual(deviceSummary.statusUpdatedTime); + expect(transformedDevice.modelId).toEqual(deviceSummary.modelId); }); }); }); diff --git a/src/app/api/dataTransforms/deviceSummaryTransform.ts b/src/app/api/dataTransforms/deviceSummaryTransform.ts index beaa04a59..8534c68af 100644 --- a/src/app/api/dataTransforms/deviceSummaryTransform.ts +++ b/src/app/api/dataTransforms/deviceSummaryTransform.ts @@ -14,6 +14,7 @@ export const transformDevice = (device: Device): DeviceSummary => { deviceId: device.DeviceId, iotEdge: device.IotEdge, lastActivityTime: parseDateTimeString(device.LastActivityTime), + modelId: device.ModelId, status: device.Status, statusUpdatedTime: parseDateTimeString(device.StatusUpdatedTime) }; diff --git a/src/app/api/models/device.ts b/src/app/api/models/device.ts index fcbcfe450..1e3896b02 100644 --- a/src/app/api/models/device.ts +++ b/src/app/api/models/device.ts @@ -11,6 +11,7 @@ export interface Device { AuthenticationType: string; IotEdge: boolean; ConnectionState: string; + ModelId: string; } export interface DataPlaneResponse { diff --git a/src/app/api/models/deviceSummary.ts b/src/app/api/models/deviceSummary.ts index 45dfb019f..537153bd8 100644 --- a/src/app/api/models/deviceSummary.ts +++ b/src/app/api/models/deviceSummary.ts @@ -11,4 +11,5 @@ export interface DeviceSummary { authenticationType: string; connectionState: string; iotEdge: boolean; + modelId: string; } diff --git a/src/app/api/shared/utils.spec.ts b/src/app/api/shared/utils.spec.ts index 29d97fcb0..29303f680 100644 --- a/src/app/api/shared/utils.spec.ts +++ b/src/app/api/shared/utils.spec.ts @@ -5,7 +5,7 @@ import 'jest'; import * as utils from './utils'; import { ParameterType, OperationType, DeviceCapability, DeviceStatus } from '../models/deviceQuery'; -import { LIST_PLUG_AND_PLAY_DEVICES } from '../../constants/devices'; +import { LIST_IOT_DEVICES } from '../../constants/devices'; describe('utils', () => { it('builds query string', () => { @@ -16,7 +16,7 @@ describe('utils', () => { currentPageIndex: 1, deviceId: 'device1', } - )).toEqual(`${LIST_PLUG_AND_PLAY_DEVICES} WHERE STARTSWITH(devices.deviceId, 'device1')`); + )).toEqual(`${LIST_IOT_DEVICES} WHERE STARTSWITH(devices.deviceId, 'device1')`); expect(utils.buildQueryString( { @@ -25,11 +25,11 @@ describe('utils', () => { currentPageIndex: 1, deviceId: '', } - )).toEqual(LIST_PLUG_AND_PLAY_DEVICES + ' '); + )).toEqual(LIST_IOT_DEVICES + ' '); expect(utils.buildQueryString( null - )).toEqual(LIST_PLUG_AND_PLAY_DEVICES); + )).toEqual(LIST_IOT_DEVICES); expect(utils.buildQueryString( { @@ -44,7 +44,7 @@ describe('utils', () => { currentPageIndex: 1, deviceId: '', } - )).toEqual(`${LIST_PLUG_AND_PLAY_DEVICES} WHERE (${ParameterType.edge}=true)`); + )).toEqual(`${LIST_IOT_DEVICES} WHERE (${ParameterType.edge}=true)`); expect(utils.buildQueryString( { @@ -59,7 +59,7 @@ describe('utils', () => { currentPageIndex: 1, deviceId: '', } - )).toEqual(`${LIST_PLUG_AND_PLAY_DEVICES} WHERE (${ParameterType.status}='enabled')`); + )).toEqual(`${LIST_IOT_DEVICES} WHERE (${ParameterType.status}='enabled')`); }); it('converts query object to string', () => { @@ -110,15 +110,6 @@ describe('utils', () => { expect(utils.escapeValue(`o'really`)).toEqual(`'o'really'`); }); - it('coverts pnp query clauses', () => { - expect(utils.toPnPClause(utils.PnPQueryPrefix.HAS_CAPABILITY_MODEL, 'urn:contoso:com:EnvironmentalSensor:1')).toEqual( - `HAS_CAPABILITYMODEL('urn:contoso:com:EnvironmentalSensor', 1)` - ); - expect(utils.toPnPClause(utils.PnPQueryPrefix.HAS_INTERFACE, 'urn:contoso:com:EnvironmentalSensor')).toEqual( - `HAS_INTERFACE('urn:contoso:com:EnvironmentalSensor')` - ); - }); - it('gets connectionObject from hub connection string', () => { const connectionObject = utils.getConnectionInfoFromConnectionString('HostName=test.azure-devices-int.net;SharedAccessKeyName=iothubowner;SharedAccessKey=key'); expect(connectionObject.hostName = 'test.azure-devices-int.net'); diff --git a/src/app/api/shared/utils.ts b/src/app/api/shared/utils.ts index 5e921e0aa..90c75eafe 100644 --- a/src/app/api/shared/utils.ts +++ b/src/app/api/shared/utils.ts @@ -4,7 +4,7 @@ **********************************************************/ import { createHmac } from 'crypto'; import { IoTHubConnectionSettings } from '../services/devicesService'; -import { LIST_PLUG_AND_PLAY_DEVICES, SAS_EXPIRES_MINUTES } from '../../constants/devices'; +import { LIST_IOT_DEVICES, SAS_EXPIRES_MINUTES } from '../../constants/devices'; import { DeviceQuery, QueryClause, ParameterType, OperationType } from '../models/deviceQuery'; import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from '../../constants/shared'; @@ -81,8 +81,8 @@ export const getConnectionInfoFromConnectionString = (connectionString: string): export const buildQueryString = (query: DeviceQuery) => { return query ? - `${LIST_PLUG_AND_PLAY_DEVICES} ${queryToString(query)}` : - LIST_PLUG_AND_PLAY_DEVICES; + `${LIST_IOT_DEVICES} ${queryToString(query)}` : + LIST_IOT_DEVICES; }; export const queryToString = (query: DeviceQuery) => { @@ -127,17 +127,6 @@ export const escapeValue = (value: string) => { return value; }; -export const toPnPClause = (pnpFunctionName: string, value: string): string => { - const urnVersionRegEx = new RegExp(/:[0-9]+$/); - if (urnVersionRegEx.test(value)) { - const urnVersion = urnVersionRegEx.exec(value)[0].replace(':', ''); - const urnName = value.replace(urnVersionRegEx, ''); - - return `${pnpFunctionName}(${escapeValue(urnName)}, ${urnVersion})`; - } - return `${pnpFunctionName}('${value}')`; // when provided value is not urn with version, pass it in as string -}; - export const toEdgeClause = (edgeFunctionName: string, value: string): string => { return `${edgeFunctionName}=${value === 'true' ? true : false}`; }; diff --git a/src/app/constants/devices.ts b/src/app/constants/devices.ts index 75ed4fe79..96be979f0 100644 --- a/src/app/constants/devices.ts +++ b/src/app/constants/devices.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License **********************************************************/ -export const LIST_PLUG_AND_PLAY_DEVICES = ` +export const LIST_IOT_DEVICES = ` SELECT deviceId as DeviceId, status as Status, lastActivityTime as LastActivityTime, @@ -10,7 +10,8 @@ export const LIST_PLUG_AND_PLAY_DEVICES = ` authenticationType as AuthenticationType, cloudToDeviceMessageCount as CloudToDeviceMessageCount, connectionState as ConnectionState, - capabilities.iotEdge as IotEdge + capabilities.iotEdge as IotEdge, + modelId as ModelId FROM devices`; export const DEVICE_TWIN_QUERY_STRING = ' SELECT * FROM devices WHERE deviceId = {deviceId}'; diff --git a/src/app/devices/deviceList/components/__snapshots__/deviceList.spec.tsx.snap b/src/app/devices/deviceList/components/__snapshots__/deviceList.spec.tsx.snap index 0b564235a..d0b2c5f91 100644 --- a/src/app/devices/deviceList/components/__snapshots__/deviceList.spec.tsx.snap +++ b/src/app/devices/deviceList/components/__snapshots__/deviceList.spec.tsx.snap @@ -96,6 +96,15 @@ exports[`DeviceList matches snapshot 1`] = ` "minWidth": 100, "name": "deviceLists.columns.statusUpdatedTime", }, + Object { + "fieldName": "modelId", + "isMultiline": true, + "isResizable": true, + "key": "modelId", + "maxWidth": 400, + "minWidth": 100, + "name": "deviceLists.columns.isPnpDevice", + }, Object { "fieldName": "edge", "isResizable": true, diff --git a/src/app/devices/deviceList/components/deviceList.tsx b/src/app/devices/deviceList/components/deviceList.tsx index bb6bea617..8c56abc10 100644 --- a/src/app/devices/deviceList/components/deviceList.tsx +++ b/src/app/devices/deviceList/components/deviceList.tsx @@ -138,6 +138,8 @@ export const DeviceList: React.FC = () => { maxWidth: SMALL_COLUMN_WIDTH, minWidth: 100, name: t(ResourceKeys.deviceLists.columns.authenticationType)}, { fieldName: 'statusUpdatedTime', isMultiline: true, isResizable: true, key: 'statusUpdatedTime', maxWidth: MEDIUM_COLUMN_WIDTH, minWidth: 100, name: t(ResourceKeys.deviceLists.columns.statusUpdatedTime)}, + { fieldName: 'modelId', isMultiline: true, isResizable: true, key: 'modelId', + maxWidth: LARGE_COLUMN_WIDTH, minWidth: 100, name: t(ResourceKeys.deviceLists.columns.isPnpDevice)}, { fieldName: 'edge', isResizable: true, key: 'edge', minWidth: 100, name: t(ResourceKeys.deviceLists.columns.isEdgeDevice.label)}, ]; @@ -195,6 +197,14 @@ export const DeviceList: React.FC = () => { t(ResourceKeys.deviceLists.columns.isEdgeDevice.yes) : t(ResourceKeys.deviceLists.columns.isEdgeDevice.no)} /> ); + case 'modelId': + return ( + + ); default: return; } diff --git a/src/server/dataPlaneHelper.spec.ts b/src/server/dataPlaneHelper.spec.ts index f7e9af77e..25230c984 100644 --- a/src/server/dataPlaneHelper.spec.ts +++ b/src/server/dataPlaneHelper.spec.ts @@ -105,7 +105,7 @@ describe('server', () => { it('generates data plane response with no httpResponse', () => { const response = processDataPlaneResponse(null, null); - expect(response.body).toEqual(`Cannot read property 'headers' of null`); + expect(response.body).toEqual({body: {Message: `Cannot read property 'headers' of null`}}); }); it('generates data plane response using device status code', () => { diff --git a/src/server/dataPlaneHelper.ts b/src/server/dataPlaneHelper.ts index 00651ec20..329e1bed8 100644 --- a/src/server/dataPlaneHelper.ts +++ b/src/server/dataPlaneHelper.ts @@ -35,8 +35,15 @@ export const generateDataPlaneResponse = (httpRes: request.Response, body: any, export const processDataPlaneResponse = (httpRes: request.Response, body: any): {body: {body: any, headers?: any}, statusCode: number} => { // tslint:disable-line:no-any try { if (httpRes.headers && httpRes.headers[DEVICE_STATUS_HEADER]) { // handles happy failure cases when error code is returned as a header + let deviceResponseBody; + try { + deviceResponseBody = body && JSON.parse(body); + } + catch { + throw new Error('Failed to parse response from device. The response is expected to be a JSON document up to 128 KB. Learn more: https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-direct-methods#method-lifecycle.'); + } return { - body: {body: body && JSON.parse(body)}, + body: {body: deviceResponseBody}, statusCode: parseInt(httpRes.headers[DEVICE_STATUS_HEADER] as string) // tslint:disable-line:radix }; } @@ -49,7 +56,7 @@ export const processDataPlaneResponse = (httpRes: request.Response, body: any): } catch (error) { return { - body: error.message, + body: {body: {Message: error.message}}, statusCode: SERVER_ERROR }; }