Skip to content

Commit

Permalink
feat(Datapoints): add query feautre (#211)
Browse files Browse the repository at this point in the history
* test(Module/datapoints): set query marble
* test(DeviceDataChannelTimeRange): test for state
* feat(mcs-lite-connect): default with emptyFunction #173
* fix(State): add some default value
* fix(datapoints): zip with response
  • Loading branch information
evenchange4 authored Mar 11, 2017
1 parent 9604712 commit 90bfb6e
Show file tree
Hide file tree
Showing 32 changed files with 811 additions and 160 deletions.
20 changes: 12 additions & 8 deletions packages/mcs-lite-connect/src/connectSocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import createEagerFactory from 'recompose/createEagerFactory';
import createHelper from 'recompose/createHelper';
import { w3cwebsocket as W3CWebSocket } from 'websocket';

const emptyFunction = () => {
console.warn('[mcs-lite-connect] W3CWebSocket is not ready.');
};

/**
* connectSocket
* urlMapper => props => string
Expand All @@ -18,7 +22,7 @@ const connectSocket = (urlMapper, onMessage, sendPropsName) => (BaseComponent) =
const factory = createEagerFactory(BaseComponent);

return class ConnectMCS extends React.Component {
state = { exposeSenderFunction: null };
state = { exposeSenderFunction: emptyFunction };
componentWillMount = () => this.createWebSocket();
componentWillReceiveProps = () => this.createWebSocket();
componentWillUnmount = () => this.close();
Expand All @@ -29,9 +33,9 @@ const connectSocket = (urlMapper, onMessage, sendPropsName) => (BaseComponent) =

if (!this.viewer) {
this.viewer = new W3CWebSocket(`${URL}/viewer`);
this.viewer.onopen = data => console.info('viewer onopen', data);
this.viewer.onerror = error => console.info('viewer onerror', error);
this.viewer.onclose = data => console.info('viewer onclose', data);
// this.viewer.onopen = data => console.info('viewer onopen', data);
// this.viewer.onerror = error => console.info('viewer onerror', error);
// this.viewer.onclose = data => console.info('viewer onclose', data);
this.viewer.onmessage = (payload) => {
const data = JSON.parse(payload.data);
onMessage(this.props)(data); // Remind: Handle receieve messages.
Expand All @@ -42,16 +46,16 @@ const connectSocket = (urlMapper, onMessage, sendPropsName) => (BaseComponent) =
this.sender = new W3CWebSocket(`${URL}`);
this.sender.onopen = () =>
this.setState({ exposeSenderFunction: this.sender.send.bind(this.sender) });
this.sender.onerror = error => console.info('sender onerror', error);
this.sender.onclose = data => console.info('sender onclose', data);
this.sender.onmessage = e => console.info('sender onmessage', e.data);
// this.sender.onerror = error => console.info('sender onerror', error);
// this.sender.onclose = data => console.info('sender onclose', data);
// this.sender.onmessage = e => console.info('sender onmessage', e.data);
}
}

close = () => {
this.viewer.close();
this.sender.close();
this.setState({ exposeSenderFunction: null });
this.setState({ exposeSenderFunction: emptyFunction });
}

render() {
Expand Down
9 changes: 8 additions & 1 deletion packages/mcs-lite-mobile-web/mcs-lite-mobile-web.pot
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2017-03-08T07:56:47.307Z\n"
"POT-Creation-Date: 2017-03-11T09:28:50.361Z\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"MIME-Version: 1.0\n"
Expand Down Expand Up @@ -124,6 +124,13 @@ msgstr ""
msgid "密碼"
msgstr ""

#: ./messages.json
#. [DeviceDataChannelDetail.noData] - No Data avaliable for history
#. defaultMessage is:
#. 尚無資料
msgid "尚無資料"
msgstr ""

#: ./messages.json
#. [Account.account] - Title
#. defaultMessage is:
Expand Down
5 changes: 5 additions & 0 deletions packages/mcs-lite-mobile-web/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
"description": "reset for history query",
"defaultMessage": "回復預設值"
},
{
"id": "DeviceDataChannelDetail.noData",
"description": "No Data avaliable for history",
"defaultMessage": "尚無資料"
},
{
"id": "DeviceDataChannelTimeRange.searchTimeRange",
"description": "title",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,66 +1,62 @@
import React, { PropTypes } from 'react';
import Helmet from 'react-helmet';
import {
MobileHeader, DataChannelCard, DataChannelAdapter, P, DataPointAreaChart,
} from 'mcs-lite-ui';
import { Link } from 'react-router';
import IconArrowLeft from 'mcs-lite-icon/lib/IconArrowLeft';
import IconCalendar from 'mcs-lite-icon/lib/IconCalendar';
import IconRefresh from 'mcs-lite-icon/lib/IconRefresh';
import { Link } from 'react-router';
import {
MobileHeader, DataChannelCard, DataChannelAdapter, P, DataPointAreaChart,
} from 'mcs-lite-ui';
import {
CardContainer, StyledSamll, HistoryHeader, ResetWrapper, HistoryContainer,
ChartWrapper,
} from './styled-components';
import updatePathname from '../../utils/updatePathname';
import dataChannelTypeMapper from '../../utils/dataChannelTypeMapper';
import datetimeFormat from '../../utils/datetimeFormat';
import areaChartTypeMapper from '../../utils/areaChartTypeMapper';

class DeviceDataChannelDetail extends React.Component {
static propTypes = {
device: PropTypes.object,
datapoints: PropTypes.array,
deviceId: PropTypes.string.isRequired,
dataChannelId: PropTypes.string.isRequired,
datachannel: PropTypes.object,
data: PropTypes.array.isRequired,
getMessages: PropTypes.func.isRequired,
fetchDeviceDetail: PropTypes.func.isRequired,
fetchDatapoints: PropTypes.func.isRequired,
setQuery: PropTypes.func.isRequired,
sendMessage: PropTypes.func,
setDatapoint: PropTypes.func.isRequired,
}
static defaultProps = {
device: {
user: {},
prototype: {},
},
datapoints: [],
componentWillMount = () => {
const { deviceId, dataChannelId, fetchDeviceDetail, fetchDatapoints } = this.props;
fetchDeviceDetail(deviceId);
fetchDatapoints(deviceId, dataChannelId);
};
onResetClick = () => {
const { dataChannelId, setQuery } = this.props;
setQuery(dataChannelId, {});
}
componentWillMount = () => this.fetch();
onResetClick = console.log; // eslint-disable-line
eventHandler = (e) => {
const { id, values, type } = e;
const { deviceId, sendMessage, setDatapoint } = this.props;
// TODO: refactor these codes.
const datapoint = { datachannelId: e.id, values: e.values };
switch (e.type) {
const datapoint = { datachannelId: id, values };
switch (type) {
case 'submit':
// Remind: MUST upload the datapoint via WebSocket.
this.props.sendMessage(JSON.stringify(datapoint));
sendMessage(JSON.stringify(datapoint));
break;
default:
// Remind: Just change the state.
this.props.setDatapoint(this.props.deviceId, datapoint);
setDatapoint(deviceId, datapoint);
break;
}
}
fetch = () => {
const { deviceId, dataChannelId } = this.props;

this.props.fetchDeviceDetail(deviceId);
this.props.fetchDatapoints(deviceId, dataChannelId);
}
render() {
const { device, datapoints, getMessages: t, dataChannelId } = this.props;
const { deviceId, datachannel, data, getMessages: t, dataChannelId } = this.props;
const { eventHandler, onResetClick } = this;
const data = datapoints.map(d => ({
value: parseInt(d.values.value, 10),
updatedAt: datetimeFormat(new Date(d.updatedAt)),
}));
const c = (device.datachannels || []).filter(e => e.datachannelId === dataChannelId)[0];

return (
<div>
Expand All @@ -70,15 +66,15 @@ class DeviceDataChannelDetail extends React.Component {
leftChildren={
<MobileHeader.MobileHeaderIcon
component={Link}
to={updatePathname(`/devices/${device.deviceId}`)}
to={updatePathname(`/devices/${deviceId}`)}
>
<IconArrowLeft />
</MobileHeader.MobileHeaderIcon>
}
rightChildren={
<MobileHeader.MobileHeaderIcon
component={Link}
to={updatePathname(`/devices/${device.deviceId}/dataChannels/${dataChannelId}/timeRange`)}
to={updatePathname(`/devices/${deviceId}/dataChannels/${dataChannelId}/timeRange`)}
>
<IconCalendar />
</MobileHeader.MobileHeaderIcon>
Expand All @@ -87,18 +83,18 @@ class DeviceDataChannelDetail extends React.Component {

<main>
<CardContainer>
{c && (
{datachannel && (
<DataChannelCard
key={c.datachannelId}
title={c.datachannelName}
subtitle={datetimeFormat(new Date(c.createdAt))}
key={datachannel.datachannelId}
title={datachannel.datachannelName}
subtitle={datetimeFormat(new Date(datachannel.createdAt))}
>
<DataChannelAdapter
dataChannelProps={{
id: c.datachannelId,
type: dataChannelTypeMapper(c.channelType.name, c.type),
values: c.datapoints.values || {},
format: c.format,
id: datachannel.datachannelId,
type: dataChannelTypeMapper(datachannel.channelType.name, datachannel.type),
values: datachannel.datapoints.values || {},
format: datachannel.format,
}}
eventHandler={eventHandler}
/>
Expand All @@ -121,12 +117,12 @@ class DeviceDataChannelDetail extends React.Component {
</HistoryHeader>

<ChartWrapper>
{c && (
{data.length > 0 ? (
<DataPointAreaChart
data={data}
type={['Switch'].includes(c.channelType.name) ? 'step' : 'linear'}
type={areaChartTypeMapper(datachannel.channelType.name)}
/>
)}
) : t('noData')}
</ChartWrapper>
</HistoryContainer>
</main>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import DeviceDataChannelDetail from '../DeviceDataChannelDetail';

it('should renders <DeviceDataChannelDetail> correctly', () => {
it('should renders <DeviceDataChannelDetail> correctly without data', () => {
const fetchDeviceMock = jest.fn();
const fetchDatapointsMock = jest.fn();
const wrapper = shallow(
Expand All @@ -19,14 +19,100 @@ it('should renders <DeviceDataChannelDetail> correctly', () => {
deviceDescription: 'deviceDescription',
deviceKey: 'deviceKey',
}}
datapoints={[]}
data={[]}
setQuery={() => {}}
isLoading={false}
fetchDeviceDetail={fetchDeviceMock}
fetchDatapoints={fetchDatapointsMock}
sendMessage={() => {}}
setDatapoint={() => {}}
/>,
);

expect(toJson(wrapper)).toMatchSnapshot();
expect(fetchDeviceMock).toHaveBeenCalledWith('deviceId');
expect(fetchDatapointsMock).toHaveBeenCalledWith('deviceId', 'dataChannelId');
});

it('should handle onResetClick correctly', () => {
const fetchDatapointsMock = jest.fn();
const setQueryMock = jest.fn();
const wrapper = shallow(
<DeviceDataChannelDetail
getMessages={R.identity}
deviceId="deviceId"
dataChannelId="dataChannelId"
device={{
deviceId: 'deviceId',
deviceName: 'deviceName',
createUserId: 'createUserId',
deviceDescription: 'deviceDescription',
deviceKey: 'deviceKey',
}}
data={[]}
setQuery={setQueryMock}
isLoading={false}
fetchDeviceDetail={() => {}}
fetchDatapoints={fetchDatapointsMock}
sendMessage={() => {}}
setDatapoint={() => {}}
/>,
);

// Before onResetClick
expect(setQueryMock).not.toHaveBeenCalled();
// After onResetClick
wrapper.instance().onResetClick();
expect(setQueryMock).toHaveBeenCalledWith('dataChannelId', {});
expect(fetchDatapointsMock).toHaveBeenCalledWith('deviceId', 'dataChannelId');
});

it('should handle eventHandler correctly', () => {
const sendMessageMock = jest.fn();
const setDatapointMock = jest.fn();
const wrapper = shallow(
<DeviceDataChannelDetail
getMessages={R.identity}
deviceId="deviceId"
dataChannelId="dataChannelId"
device={{
deviceId: 'deviceId',
deviceName: 'deviceName',
createUserId: 'createUserId',
deviceDescription: 'deviceDescription',
deviceKey: 'deviceKey',
}}
data={[]}
setQuery={() => {}}
isLoading={false}
fetchDeviceDetail={() => {}}
fetchDatapoints={() => {}}
sendMessage={sendMessageMock}
setDatapoint={setDatapointMock}
/>,
);

// Before eventHandler with submit type
expect(setDatapointMock).not.toHaveBeenCalled();
// After eventHandler with submit type
wrapper.instance().eventHandler({
type: 'submit',
id: 'id',
values: { value: 1 },
});
expect(sendMessageMock).toHaveBeenCalledWith('{"datachannelId":"id","values":{"value":1}}');


// Before eventHandler with other type
expect(setDatapointMock).not.toHaveBeenCalled();
// After eventHandler with other type
wrapper.instance().eventHandler({
type: 'change',
id: 'id',
values: { value: 1 },
});
expect(setDatapointMock).toHaveBeenCalledWith(
'deviceId',
{ datachannelId: 'id', values: { value: 1 }},
);
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
exports[`test should renders <DeviceDataChannelDetail> correctly 1`] = `
exports[`test should renders <DeviceDataChannelDetail> correctly without data 1`] = `
<div>
<HelmetWrapper
title="dataChannelDetail" />
Expand Down Expand Up @@ -44,7 +44,9 @@ exports[`test should renders <DeviceDataChannelDetail> correctly 1`] = `
</styled.a>
</div>
</styled.div>
<styled.div />
<styled.div>
noData
</styled.div>
</styled.div>
</main>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ Object {
"fetchDatapoints": [Function],
"fetchDeviceDetail": [Function],
"setDatapoint": [Function],
"setQuery": [Function],
}
`;

exports[`test should return props correctly with mapStateToProps 1`] = `
Object {
"data": Array [],
"dataChannelId": "dataChannelId",
"datapoints": Object {},
"device": Object {
"deviceName": "deviceName",
},
"datachannel": undefined,
"deviceId": "deviceId",
"deviceKey": "",
"isLoading": false,
"query": Object {},
}
`;
Loading

0 comments on commit 90bfb6e

Please sign in to comment.