Skip to content

Commit

Permalink
Added the ability to use relative time with the get-history node
Browse files Browse the repository at this point in the history
  • Loading branch information
zachowj committed Jan 17, 2019
1 parent eaaadc3 commit 3154f79
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 46 deletions.
111 changes: 95 additions & 16 deletions nodes/get-history/get-history.html
Expand Up @@ -8,7 +8,12 @@
startdate: { value: "" },
enddate: { value: "" },
entityid: { value: "" },
entityidtype: { value: "" }
entityidtype: { value: "" },
useRelativeTime: { value: false },
relativeTime: { value: "" },
output_type: { value: "array" },
output_location_type: { value: "msg" },
output_location: { value: "payload" }
},
inputs: 1,
outputs: 1,
Expand All @@ -18,7 +23,9 @@
if (this.name) {
return this.name;
}
if (this.startdate) {
if (this.useRelativeTime && this.relativeTime) {
return this.relativeTime;
} else if (this.startdate) {
const startdate = new Date(this.startdate);
return `${startdate.toLocaleDateString()} ${startdate.toLocaleTimeString()}`;
}
Expand Down Expand Up @@ -68,7 +75,7 @@
});

const validSelection =
this.availableEntities.indexOf(this.entity_id) > -1;
this.availableEntities.indexOf(NODE.entityid) > -1;
if (validSelection) {
$entityIdField.removeClass("input-error");
} else {
Expand All @@ -88,6 +95,34 @@
}
});
}

$("#node-input-useRelativeTime").on("change", function() {
if (this.checked) {
$(".relative_row").show();
$(".date_row").hide();
} else {
$(".relative_row").hide();
$(".date_row").show();
}
});

if (this.output_location === undefined) {
$("#node-input-output_location").val("payload");
}
if (this.output_type === undefined) {
$("#node-input-output_type").val("array");
}

$("#node-input-output_location").typedInput({
types: ["msg", "flow", "global"],
typeField: "#node-input-output_location_type"
});

$("#node-input-output_type")
.on("change", e =>
$(".output-option").toggle(e.target.value === "array")
)
.trigger("change");
},
oneditsave: function() {
this.entityid = $("#entity_id").val();
Expand All @@ -106,17 +141,6 @@
<input type="text" id="node-input-server" />
</div>


<div class="form-row">
<label for="node-input-startdate"><i class="fa fa-date"></i> Start Date</label>
<input type="text" id="node-input-startdate" autofocus="autofocus" />
</div>

<div class="form-row">
<label for="node-input-enddate"><i class="fa fa-date"></i> End Date</label>
<input type="text" id="node-input-enddate" />
</div>

<!-- Comparator Selection -->
<div class="form-row">
<label for="entity_id"><i class="fa fa-date"></i> Entity ID</label>
Expand All @@ -129,11 +153,60 @@
<input type="text" id="entity_id" style="width: 52%;"/>
</div>

<div class="form-tips">
<div class="form-row">
<input type="checkbox" id="node-input-useRelativeTime" checked style="display: inline-block; width: auto; vertical-align: top;margin-left: 105px;">&nbsp;
<label for="node-input-useRelativeTime" style="width: auto;">Use Relative Time</label>
</div>

<div class="form-row relative_row">
<label for="node-input-relativeTime"><i class="fa fa-date"></i> In the Last</label>
<input type="text" id="node-input-relativeTime" autofocus="autofocus" placeholder="20 minutes" />
</div>

<div class="form-row date_row">
<label for="node-input-startdate"><i class="fa fa-date"></i> Start Date</label>
<input type="text" id="node-input-startdate" autofocus="autofocus" />
</div>

<div class="form-row date_row">
<label for="node-input-enddate"><i class="fa fa-date"></i> End Date</label>
<input type="text" id="node-input-enddate" />
</div>

<div class="form-row form-tips date_row">
<ul>
<li>dates must be in ISO format, example: "2018-01-27T13:00:00+00:00"</li>
<li>dates must be in ISO format</li>
<li>example: "2018-01-27T13:00:00+00:00"</li>
</ul>
</div>

<div class="form-row form-tips relative_row">
<p><code>timestring</code> will parse the following keywords into time values:</p>
<ol>
<li><code>ms, milli, millisecond, milliseconds</code> - will parse to milliseconds</li>
<li><code>s, sec, secs, second, seconds</code> - will parse to seconds</li>
<li><code>m, min, mins, minute, minutes</code> - will parse to minutes</li>
<li><code>h, hr, hrs, hour, hours</code> - will parse to hours</li>
<li><code>d, day, days</code> - will parse to days</li>
<li><code>w, week, weeks</code> - will parse to weeks</li>
<li><code>mon, mth, mths, month, months</code> - will parse to months</li>
<li><code>y, yr, yrs, year, years</code> - will parse to years</li>
</ol>
</div>

<div class="form-row">
<label for="node-input-output_type">Output Type</label>
<select id="node-input-output_type">
<option value="array">Array</option>
<option value="split">Split</option>
</select>
</div>

<div class="form-row output-option" id="output_location">
<label for="node-input-output_location">Output Location</label>
<input type="hidden" id="node-input-output_location_type" />
<input type="text" id="node-input-output_location" />
</div>
</script>

<script type="text/html" data-help-name="api-get-history">
Expand All @@ -147,6 +220,9 @@ <h3>Inputs</h3>
<dt class="optional">enddate <span class="property-type">string | date</span></dt>
<dd>Date to fetch history to. Will override the nodes configuration if passed in</dd>

<dt class="optional">relativetime <span class="property-type">string</span></dt>
<dd>A timestring to be parsed into datetime string</dd>

<dt class="optional">entityid <span class="property-type">string</span></dt>
<dd>Exact entity_id to fetch history for, must be an exact match as is passed directly to the home-assistant api</dd>
</dl>
Expand All @@ -163,6 +239,9 @@ <h3>Outputs</h3>
<dt>enddate <span class="property-type">string</span></dt>
<dd>ISO date string used to fetch history</dd>

<dt>relativetime <span class="property-type">string</span></dt>
<dd>The timestring parsed into the startdate</dd>

<dt> entityid <span class="property-type">string</span></dt>
<dd>entityid string used during fetch history call</dd>

Expand Down
144 changes: 114 additions & 30 deletions nodes/get-history/get-history.js
@@ -1,4 +1,5 @@
const Joi = require('joi');
const timestring = require('timestring');
const BaseNode = require('../../lib/base-node');

module.exports = function(RED) {
Expand All @@ -10,7 +11,12 @@ module.exports = function(RED) {
startdate: {},
enddate: {},
entityid: {},
entityidtype: {}
entityidtype: {},
useRelativeTime: {},
relativeTime: {},
output_type: {},
output_location_type: {},
output_location: {}
},
input: {
startdate: {
Expand Down Expand Up @@ -45,6 +51,14 @@ module.exports = function(RED) {
entityidtype: {
messageProp: 'entityidtype',
configProp: 'entityidtype'
},
useRelativeTime: {
messageProp: 'userelativetime',
configProp: 'useRelativeTime'
},
relativeTime: {
messageProp: 'relativetime',
configProp: 'relativeTime'
}
}
};
Expand All @@ -54,11 +68,29 @@ module.exports = function(RED) {
super(nodeDefinition, RED, nodeOptions);
}

onInput({ parsedMessage, message }) {
let { startdate, enddate, entityid, entityidtype } = parsedMessage;
async onInput({ parsedMessage, message }) {
let {
startdate,
enddate,
entityid,
entityidtype,
useRelativeTime,
relativeTime
} = parsedMessage;
startdate = startdate.value;
enddate = enddate.value;
entityid = entityid.value;
relativeTime = relativeTime.value;
useRelativeTime = useRelativeTime.value;

if (
useRelativeTime ||
parsedMessage.relativeTime.source === 'message'
) {
startdate = new Date(
Date.now() - timestring(relativeTime, 'ms')
).toISOString();
}

let apiRequest =
entityidtype.value === 'includes' && entityid
Expand All @@ -82,34 +114,86 @@ module.exports = function(RED) {
text: `Requesting at: ${this.getPrettyDate()}`
});

return apiRequest
.then(res => {
message.startdate = startdate;
message.enddate = enddate || null;
message.entityid = entityid || null;
message.payload = res;
this.send(message);
this.status({
fill: 'green',
shape: 'dot',
text: `Success at: ${this.getPrettyDate()}`
});
})
.catch(err => {
this.warn(
'Error calling service, home assistant api error',
err
);
this.error(
'Error calling service, home assistant api error',
message
);
this.status({
fill: 'red',
shape: 'ring',
text: `Error at: ${this.getPrettyDate()}`
});
try {
var results = await apiRequest;
message.startdate = startdate;
message.enddate = enddate || null;
message.entityid = entityid || null;
} catch (err) {
let errorMessage =
'Error get-history, home assistant api error.';
if (this.utils.selectn('response.data.message', err))
errorMessage = `${errorMessage} Error Message: ${
err.response.data.message
}`;
this.error(errorMessage);

this.status({
fill: 'red',
shape: 'ring',
text: `Error at: ${this.getPrettyDate()}`
});

return null;
}

switch (this.nodeConfig.output_type) {
case 'split':
if (results.length === 0) {
this.status({
fill: 'red',
shape: 'dot',
text: `No Results at: ${this.getPrettyDate()}`
});
return;
}
if (entityidtype.value === 'is') {
results = results[0];
}

message.parts = {};
message.parts.id = RED.util.generateId();
delete message._msgid;
message.parts.type = 'array';
message.parts.count = results.length;

let pos = 0;
message.parts.len = 1;
for (let i = 0; i < results.length; i++) {
message.payload = results.slice(pos, pos + 1)[0];
message.parts.index = i;
pos += 1;
this.node.send(this.RED.util.cloneMessage(message));
}
break;

case 'array':
default:
const contextKey = RED.util.parseContextStore(
this.nodeConfig.output_location

This comment has been minimized.

Copy link
@sibbl

sibbl Feb 11, 2019

I'd just like to leave some late feedback here as this line broke my whole setup.

Please check this field for undefined next time. When migrating the config after an upgrade of this node, Node Red doesn't fill it with any newly set default value but it will be undefined at this point.
So my data didn't end up in msg.payload but in msg.undefined instead, even though the Node Red GUI showed "payload" in the text field of the node's config ;)

I saw that this was kept in mind in newer changes for the get-state node, but the upgrade path must be something I'd definitely expect to be tested from such a wide spread npm module ;) Thanks in advance :)

);
const locationType = this.nodeConfig.output_location_type;
if (locationType === 'flow' || locationType === 'global') {
this.node
.context()
[locationType].set(
contextKey.key,
results,
contextKey.store
);
} else {
message[contextKey.key] = results;
}

this.node.send(message);
break;
}

this.status({
fill: 'green',
shape: 'dot',
text: `Success at: ${this.getPrettyDate()}`
});
}
}

Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -64,6 +64,7 @@
"p-iteration": "1.1.7",
"selectn": "1.1.2",
"time-ago": "0.2.1",
"timestring": "5.0.1",
"ws": "6.1.2"
},
"devDependencies": {
Expand Down

0 comments on commit 3154f79

Please sign in to comment.