Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rkessler/semantic units #259

Merged
merged 5 commits into from
Apr 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ ClientBin/
*.publishsettings
orleans.codegen.cs

# Including strong name files can present a security risk
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk

Expand Down Expand Up @@ -317,7 +317,7 @@ __pycache__/
# OpenCover UI analysis results
OpenCover/

# Azure Stream Analytics local run output
# Azure Stream Analytics local run output
ASALocalRun/

# MSBuild Binary and Structured Log
Expand All @@ -326,7 +326,7 @@ ASALocalRun/
# NVidia Nsight GPU debugger configuration file
*.nvuser

# MFractors (Xamarin productivity tool) working folder
# MFractors (Xamarin productivity tool) working folder
.mfractor/
node_modules/
dist/
Expand All @@ -338,7 +338,7 @@ dist/
coverage/

# ts complied scripts
scripts/composeLocalizationKeys.js
scripts/*.js

# test results
jest-test-results.trx
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"clean": "IF EXIST .\\dist RMDIR /Q /S .\\dist",
"clean:linux": "rm --recursive -f ./dist",
"localization": "tsc ./scripts/composeLocalizationKeys.ts --skipLibCheck && node ./scripts/composeLocalizationKeys.js",
"import-semantic-units": "tsc ./scripts/importSemanticUnitTypes.ts --skipLibCheck && node ./scripts/importSemanticUnitTypes.js",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me confirm if I've understand it correctly. This 'import-semantic-units' is not included in any npm build script, because the output json is already included in the source code.
This script is for the future in case we need re-generate the json.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly. I suspect the they'll do updates over time -- like adding localization.

"docker": "docker pull electronuserland/electron-builder && docker run --rm -ti --mount source=$(pwd),target=/project,type=bind electronuserland/electron-builder:latest",
"electron": "electron .",
"electron:compile": "tsc ./public/electron.ts --skipLibCheck --lib es2015 --inlineSourceMap",
Expand Down
48 changes: 48 additions & 0 deletions scripts/importSemanticUnitTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/***********************************************************
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License
**********************************************************/
import * as fs from 'fs';

export interface StringMap<T> {
[key: string]: T;
}

export interface SemanticUnit {
displayName: string | StringMap<string>;
abbreviation: string;
}

export const generateSemanticUnitDigest = () => {
const rawSemanticUnitsFileLocation = './src/app/shared/units/semanticUnitsListRaw.json';
const rawSemanticUnitsFileContents = fs.readFileSync(rawSemanticUnitsFileLocation, 'utf-8');
const rawSemanticUnitsFileObject = JSON.parse(rawSemanticUnitsFileContents);

const semanticUnits: StringMap<SemanticUnit> = {};

const minDtmiLength = 4;
const extensionTypeIndex = 2;
const extensionType = 'unit';
const unitNameIndex = 3;

// tslint:disable-next-line: no-any
rawSemanticUnitsFileObject['@graph'].forEach((entry: any) => {
const dtmi = entry['@id'].split(':');
if (dtmi.length >= minDtmiLength && dtmi[extensionTypeIndex].toLowerCase() === extensionType) {
const unitName = dtmi[unitNameIndex].split(';')[0];
const displayName = entry.displayName;
const abbreviation = entry.abbreviation || entry.symbol;

semanticUnits[unitName] = {
abbreviation,
displayName
};
}
});

const semanticUnitsFileLocation = './src/app/shared/units/semanticUnitsList.json';
const semanticUnitsFileContents = JSON.stringify(semanticUnits);
fs.writeFileSync(semanticUnitsFileLocation, semanticUnitsFileContents);
};

generateSemanticUnitDigest();
2 changes: 1 addition & 1 deletion src/app/api/models/modelDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ interface ContentBase {
description?: string | object;
displayName?: string | object;
displayUnit?: string;
unit?: any; // tslint:disable-line:no-any
unit?: string;
}

export interface Schema {
Expand Down
7 changes: 7 additions & 0 deletions src/app/api/models/stringMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/***********************************************************
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License
**********************************************************/
export interface StringMap<T> {
[key: string]: T;
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ describe('components/devices/deviceEventsPerInterface', () => {
const errorBoundary = deviceEventsPerInterfaceComponent.find(ErrorBoundary);
expect(errorBoundary.children().at(1).props().children.props.children).toEqual('humid (Temperature)');
expect(errorBoundary.children().at(2).props().children.props.children).toEqual('double'); // tslint:disable-line:no-magic-numbers
expect(errorBoundary.children().at(3).props().children.props.children).toEqual('--'); // tslint:disable-line:no-magic-numbers
expect(errorBoundary.children().at(4).props().children.props.children[0]).toEqual(JSON.stringify(events[0].body, undefined, 2)); // tslint:disable-line:no-magic-numbers
expect(errorBoundary.children().at(4).props().children.props.children[1].props['aria-label']).toEqual(ResourceKeys.deviceEvents.columns.validation.value.label); // tslint:disable-line:no-magic-numbers
});
Expand All @@ -130,7 +129,6 @@ describe('components/devices/deviceEventsPerInterface', () => {
const errorBoundary = deviceEventsPerInterfaceComponent.find(ErrorBoundary);
expect(errorBoundary.children().at(1).props().children.props.children).toEqual('humid (Temperature)');
expect(errorBoundary.children().at(2).props().children.props.children).toEqual('double'); // tslint:disable-line:no-magic-numbers
expect(errorBoundary.children().at(3).props().children.props.children).toEqual('--'); // tslint:disable-line:no-magic-numbers
expect(errorBoundary.children().at(4).props().children.props.children[0]).toEqual(JSON.stringify(events[0].body, undefined, 2)); // tslint:disable-line:no-magic-numbers
expect(errorBoundary.children().at(4).props().children.props.children[1].props.className).toEqual('value-validation-error'); // tslint:disable-line:no-magic-numbers
});
Expand All @@ -152,13 +150,11 @@ describe('components/devices/deviceEventsPerInterface', () => {
let errorBoundary = deviceEventsPerInterfaceComponent.find(ErrorBoundary).first();
expect(errorBoundary.children().at(1).props().children.props.children).toEqual('humid (Temperature)');
expect(errorBoundary.children().at(2).props().children.props.children).toEqual('double'); // tslint:disable-line:no-magic-numbers
expect(errorBoundary.children().at(3).props().children.props.children).toEqual('--'); // tslint:disable-line:no-magic-numbers
expect(errorBoundary.children().at(4).props().children.props.children[0]).toEqual(JSON.stringify({humid: 0}, undefined, 2)); // tslint:disable-line:no-magic-numbers

errorBoundary = deviceEventsPerInterfaceComponent.find(ErrorBoundary).at(1);
expect(errorBoundary.children().at(1).props().children.props.children).toEqual('--');
expect(errorBoundary.children().at(2).props().children.props.children).toEqual('--'); // tslint:disable-line:no-magic-numbers
expect(errorBoundary.children().at(3).props().children.props.children).toEqual('--'); // tslint:disable-line:no-magic-numbers
expect(errorBoundary.children().at(4).props().children.props.children[0]).toEqual(JSON.stringify({'humid-foo': 'test'}, undefined, 2)); // tslint:disable-line:no-magic-numbers
expect(errorBoundary.children().at(4).props().children.props.children[1].props.className).toEqual('value-validation-error'); // tslint:disable-line:no-magic-numbers
});
Expand All @@ -178,7 +174,6 @@ describe('components/devices/deviceEventsPerInterface', () => {
const errorBoundary = deviceEventsPerInterfaceComponent.find(ErrorBoundary);
expect(errorBoundary.children().at(1).props().children.props.children).toEqual('--');
expect(errorBoundary.children().at(2).props().children.props.children).toEqual('--'); // tslint:disable-line:no-magic-numbers
expect(errorBoundary.children().at(3).props().children.props.children).toEqual('--'); // tslint:disable-line:no-magic-numbers
expect(errorBoundary.children().at(4).props().children.props.children).toEqual(JSON.stringify(events[0].body, undefined, 2)); // tslint:disable-line:no-magic-numbers
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import LabelWithTooltip from '../../../../shared/components/labelWithTooltip';
import { MILLISECONDS_IN_MINUTE } from '../../../../constants/shared';
import { appConfig, HostMode } from '../../../../../appConfig/appConfig';
import { DigitalTwinHeaderContainer } from '../digitalTwin/digitalTwinHeaderView';
import { SemanticUnit } from '../../../../shared/units/components/semanticUnit';
import { ROUTE_PARAMS } from '../../../../constants/routes';
import '../../../../css/_deviceEvents.scss';

Expand Down Expand Up @@ -441,12 +442,10 @@ export default class DeviceEventsPerInterfaceComponent extends React.Component<D
}

private readonly renderEventUnit = (context: LocalizationContextInterface, telemetryModelDefinition?: TelemetryContent) => {
const displayUnit = telemetryModelDefinition ? getLocalizedData(telemetryModelDefinition.displayUnit) : '';
return(
<div className="col-sm2">
<Label aria-label={context.t(ResourceKeys.deviceEvents.columns.unit)}>
{telemetryModelDefinition ?
telemetryModelDefinition.unit || displayUnit || '--' : '--'}
<SemanticUnit unitHost={telemetryModelDefinition}/>
</Label>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('getDeviceCommandPairs', () => {
const telemetrySchemas = [
{
parsedSchema: {
description: 'Temperature / Current temperature on the device ( Unit: Units/Temperature/fahrenheit )',
description: 'Temperature / Current temperature on the device',
required: null,
title: 'temp',
type: 'number'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { RenderSimplyTypeValue } from '../shared/simpleReportedSection';
import { PropertyContent } from '../../../../api/models/modelDefinition';
import { ParsedJsonSchema } from '../../../../api/models/interfaceJsonParserOutput';
import { SMALL_COLUMN_WIDTH, EXTRA_LARGE_COLUMN_WIDTH } from '../../../../constants/columnWidth';
import { SemanticUnit } from '../../../../shared/units/components/semanticUnit';

export interface DevicePropertiesDataProps {
twinAndSchema: TwinWithSchema[];
Expand Down Expand Up @@ -110,8 +111,10 @@ export default class DevicePropertiesPerInterface

private readonly renderPropertyUnit = (context: LocalizationContextInterface, item: TwinWithSchema) => {
const ariaLabel = context.t(ResourceKeys.deviceProperties.columns.unit);
const unit = item.propertyModelDefinition.unit;
return <Label aria-label={ariaLabel}>{unit ? unit : '--'}</Label>;
return (
<Label aria-label={ariaLabel}>
<SemanticUnit unitHost={item.propertyModelDefinition} />
</Label>);
}

private readonly renderPropertyReportedValue = (context: LocalizationContextInterface, item: TwinWithSchema) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,6 @@ describe('components/devices/deviceSettingsPerInterfacePerSetting', () => {
const schemaLabel = wrapper.find(Label).at(1);
expect(schemaLabel.props().children).toEqual(schema);

const unitLabel = wrapper.find(Label).at(2); // tslint:disable-line:no-magic-numbers
expect(unitLabel.props().children).toEqual('--');

const valueLabel = wrapper.find(Label).at(3); // tslint:disable-line:no-magic-numbers
expect(valueLabel.props().children).toEqual('true');
});
Expand Down Expand Up @@ -116,9 +113,6 @@ describe('components/devices/deviceSettingsPerInterfacePerSetting', () => {
const schemaLabel = wrapper.find(Label).at(1);
expect(schemaLabel.props().children).toEqual(schema);

const unitLabel = wrapper.find(Label).at(2); // tslint:disable-line:no-magic-numbers
expect(unitLabel.props().children).toEqual('--');

const complexValueButton = wrapper.find(ActionButton).first();
expect(complexValueButton.props().className).toEqual('column-value-button');
complexValueButton.props().onClick(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { RenderSimplyTypeValue } from '../shared/simpleReportedSection';
import { PatchDigitalTwinActionParameters } from '../../actions';
import ErrorBoundary from '../../../errorBoundary';
import { getLocalizedData } from '../../../../api/dataTransforms/modelDefinitionTransform';
import { MetadataSection } from './selectors';
import { SemanticUnit } from '../../../../shared/units/components/semanticUnit';
import '../../../../css/_deviceSettings.scss';
import { JsonPatchOperation } from '../../../../api/parameters/deviceParameters';

Expand Down Expand Up @@ -109,8 +109,13 @@ export default class DeviceSettingsPerInterfacePerSetting

private readonly renderPropertyUnit = (context: LocalizationContextInterface) => {
const ariaLabel = context.t(ResourceKeys.deviceProperties.columns.unit);
const unit = this.props.settingModelDefinition.unit;
return <div className="col-sm2"><Label aria-label={ariaLabel}>{unit ? unit : '--'}</Label></div>;

return (
<div className="col-sm2">
<Label aria-label={ariaLabel}>
<SemanticUnit unitHost={this.props.settingModelDefinition} />
</Label>
</div>);
}

private readonly renderReportedValueAndMetadata = (context: LocalizationContextInterface) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SemanticUnit matches snapshot when unit host is undefined 1`] = `
<span>
--
</span>
`;

exports[`SemanticUnit matches snapshot when unit is listed 1`] = `
<span
title="displayName"
>
displayName
(
abbr
)
</span>
`;

exports[`SemanticUnit matches snapshot when unit is undefined 1`] = `
<span>
--
</span>
`;

exports[`SemanticUnit matches snapshot when unit is unlisted 1`] = `
<span>
fizzbin
</span>
`;
27 changes: 27 additions & 0 deletions src/app/shared/units/components/semanticUnit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/***********************************************************
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License
**********************************************************/
import * as React from 'react';
import { getSemanticUnit } from '../semanticUnits';
import { getLocalizedData } from '../../../api/dataTransforms/modelDefinitionTransform';

export interface SemanticUnitProps {
unitHost?: {unit?: string};
}

export const SemanticUnit: React.FC<SemanticUnitProps> = props => {
const { unitHost } = props;
const emptyUnit = '--';
if (!unitHost || !unitHost.unit) {
return <span>{emptyUnit}</span>;
}

const semanticUnit = getSemanticUnit(unitHost.unit);
if (!semanticUnit) {
return <span>{unitHost.unit}</span>;
} else {
const title = getLocalizedData(semanticUnit.displayName);
return <span title={title}>{title} ({semanticUnit.abbreviation})</span>;
}
};
35 changes: 35 additions & 0 deletions src/app/shared/units/components/semanticUnits.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/***********************************************************
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License
**********************************************************/
import * as React from 'react';
import { shallow } from 'enzyme';
import { SemanticUnit } from './semanticUnit';
import * as SemanticUnits from '../semanticUnits';

describe('SemanticUnit', () => {
it('matches snapshot when unit host is undefined', () => {
expect(shallow(<SemanticUnit unitHost={undefined}/>)).toMatchSnapshot();

});

it('matches snapshot when unit is undefined', () => {
expect(shallow(<SemanticUnit unitHost={{}}/>)).toMatchSnapshot();

});

it('matches snapshot when unit is unlisted', () => {
expect(shallow(<SemanticUnit unitHost={{unit: 'fizzbin'}}/>)).toMatchSnapshot();

});

it('matches snapshot when unit is listed', () => {
const spy = jest.spyOn(SemanticUnits, 'getSemanticUnit');
spy.mockReturnValue({
abbreviation: 'abbr',
displayName: 'displayName'
});

expect(shallow(<SemanticUnit unitHost={{unit: 'fizzbin'}}/>)).toMatchSnapshot();
});
});
27 changes: 27 additions & 0 deletions src/app/shared/units/semanticUnits.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/***********************************************************
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License
**********************************************************/
import { getSemanticUnit, semanticUnits } from './semanticUnits';

describe('getSemanticUnit', () => {
it('returns undefined when unit name is undefined', () => {
expect(getSemanticUnit(undefined)).toEqual(undefined);
});

it('returns undefined when unit name is unlisted', () => {
expect(getSemanticUnit('fizzbin')).toEqual(undefined);
});

it('returns listed semantic unit with expected value', () => {
semanticUnits.fizzbin = {
abbreviation: 'abbr',
displayName: 'displayName'
};

expect(getSemanticUnit('fizzbin')).toEqual({
abbreviation: 'abbr',
displayName: 'displayName'
});
});
});
15 changes: 15 additions & 0 deletions src/app/shared/units/semanticUnits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/***********************************************************
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License
**********************************************************/
import { StringMap } from '../../api/models/stringMap';

export interface SemanticUnit {
displayName: string | StringMap<string>;
abbreviation: string;
}

export const semanticUnits = require('./semanticUnitsList.json'); // tslint:disable-line: no-var-requires
export const getSemanticUnit = (unitName: string | undefined): SemanticUnit | undefined => {
return unitName && semanticUnits[unitName];
};
Loading