I am following the azure device twin tutorial https://learn.microsoft.com/en-us/azure/iot-hub/iot-hub-csharp-node-twin-how-to-configure
I can make this work to update an existing reported property. What I am not sure about is how can I add a new reported property.
Specifically the code looks snippet looks like:
var currentTelemetryConfig = twin.properties.reported.telemetryConfig;
currentTelemetryConfig.pendingConfig =
twin.properties.desired.telemetryConfig;
currentTelemetryConfig.status = "Pending";
telemetryConfig: currentTelemetryConfig
var patch = {
telemetryConfig: currentTelemetryConfig
};
twin.properties.reported.update(patch, function(err) {
if (err) {
console.log(err);
} else {
console.log('success');
}
}
I can easily understand how this works for the existing property update (in this case the telemetryConfig) but what would it look like if the change I was trying to make was to a entirely new property?
How would it work if I decide at some point the I want a a new desired property called "favourite_colour" : "blue"?
In the azure backend I can add this but how do I dynamically build the var patch variable?
I tried this but it returned an error:
twin.properties.reported.update(twin.properties.desired, function(err) {
if (err) {
console.log('Could not report properties');
} else {
console.log('Success');
}
});
This is what my twin looks like:
"properties": {
"desired": {
"active": true,
"pws": "xyz",
"$metadata": {
"$lastUpdated": "2018-03-27T18:21:57.010036Z",
"$lastUpdatedVersion": 5,
"active": {
"$lastUpdated": "2018-03-27T18:21:57.010036Z",
"$lastUpdatedVersion": 5
},
"pws": {
"$lastUpdated": "2018-03-27T18:21:57.010036Z",
"$lastUpdatedVersion": 5
}
},
"$version": 5
},
"reported": {
"telemetryConfig": 6,
"$metadata": {
"$lastUpdated": "2018-03-27T18:56:05.2445399Z",
"telemetryConfig": {
"$lastUpdated": "2018-03-27T18:56:05.2445399Z"
}
},
"$version": 5
}
}
}
I'm guessing you want to:
Add new reported property on device side (favourite_colour)
I first recommend you to read this sample in GitHub.
Main this sample shows different way to listen for updates in twin on different level. The top most, where any update in device twin will trigger an event; or specific property (favourite_color).
I've edited the sample from the Microsoft document you provided to work with favorite_color.
'use strict';
var Client = require('azure-iot-device').Client;
var Protocol = require('azure-iot-device-mqtt').Mqtt;
var connectionString = '{iot hub connection string}';
var client = Client.fromConnectionString(connectionString, Protocol);
var initConfigChange = function(twin, patch) {
twin.properties.reported.update(patch, function(err) {
if (err) {
console.log('Could not report properties');
} else {
console.log('Reported pending config change: ' + JSON.stringify(patch));
setTimeout(function() {completeConfigChange(twin, patch);}, 30000);
}
});
}
var completeConfigChange = function(twin, patch) {
if (patch.telemetryConfig) {
// Same as Sample
} else if (patch.favourite_colour) {
var currentfavourite_colour = twin.properties.reported.favourite_colour;
currentfavourite_colour.color = currentfavourite_colour.pendingConfig.color;
currentfavourite_colour.status = "Success";
delete currentfavourite_colour.pendingConfig;
var patch = {
favourite_colour: currentfavourite_colour
};
patch.favourite_colour.pendingConfig = null;
}
twin.properties.reported.update(patch, function(err) {
if (err) {
console.error('Error reporting properties: ' + err);
} else {
console.log('Reported completed config change: ' + JSON.stringify(patch));
}
});
};
client.open(function(err) {
if (err) {
console.error('could not open IotHub client');
} else {
client.getTwin(function(err, twin) {
if (err) {
console.error('could not get twin');
} else {
console.log('retrieved device twin');
twin.properties.reported.favourite_colour = {
color: "green"
}
twin.on('properties.desired', function(desiredChange) {
console.log("received change: "+JSON.stringify(desiredChange));
if (desiredChange.telemetryConfig) {
// Same as sample
} else if (desiredChange.favourite_colour) {
var currentfavourite_colour = twin.properties.reported.favourite_colour;
currentfavourite_colour.pendingConfig = twin.properties.desired.favourite_colour;
currentfavourite_colour.status = "Pending Color";
var patch = {
favourite_colour: currentfavourite_colour
};
initConfigChange(twin, patch);
}
});
}
});
}
});
What I did was use a if else statement to check the reported property; as I mentioned there are other ways to do this, check the GitHub code I provided earlier. Once I have the matched reported property I can update the in the same way as the existing sample.
Related
I am trying to write a reporter in jasmine that pushes data to prometheus. I am able to execute the tests and report them in one go but as soon as I re-execute these tests the existing job with the published metrics just gets overwritten with new data. What I want is to attach metrics to an existing job without removing the data that already exists.
This will allow me to run the specs in parallel as well, which can be done but the prometheus data just keeps being overwritten.
masterReporter.js
const PrometheusUtil = require("../utils/prometheusUtil");
class masterReporter {
async suiteDone(result) {
const prometheusUtil = new PrometheusUtil("http://localhost:9091", process.env.BASEURL);
await prometheusUtil.setTestExecutionMetrics(
{"region": prometheusUtil.region,
"spec_name": result.fullName, "tenant": process.env.BASEURL});
}
}
module.exports = masterReporter;
prometheusUtil.js
const client = require("prom-client");
class PrometheusUtil {
constructor(baseUrl, environment) {
this.environment = environment;
this.baseUrl = baseUrl;
this.name = environment.replace("https://", "").replace(".com", "")
.replaceAll(".", "_").replaceAll("-", "_").replaceAll("/", "");
this.testExecution = new client.Gauge({
name: this.name,
help: "Gauge for test execution results storing region info.",
labelNames: ["region", "tenant", "spec_name", "failure_classification_value"],
});
this.gateway = new client.Pushgateway(baseUrl);
this.setRegion(environment);
}
async setTestExecutionMetrics(value, number) {
try {
this.setRegion(this.environment);
this.testExecution.set(value, number);
this.gateway.pushAdd({jobName: this.name}, function(err, resp, body) {
if (err) {
console.log(err);
}
if (body) {
console.log(body);
}
});
} catch (error) {
console.log(error);
}
}
setRegion(baseUrl) {
if (baseUrl.includes(".us.")) {
this.region = "US";
}
else {
console.log(`Sorry, region could not be defined for ${baseUrl}.`);
}
return this.region;
}
}
module.exports = PrometheusUtil;
const body = {
query: {
geo_shape: {
geometry: {
relation: 'within',
shape: {
type: 'polygon',
coordinates: [$polygon],
},
},
},
},
pit: {
id: "t_yxAwEPZXNyaS1wYzYtMjAxN3IxFjZxU2RBTzNyUXhTUV9XbzhHSk9IZ3cAFjhlclRmRGFLUU5TVHZKNXZReUc3SWcAAAAAAAALmpMWQkNwYmVSeGVRaHU2aDFZZExFRjZXZwEWNnFTZEFPM3JReFNRX1dvOEdKT0hndwAA",
keep_alive: "1m",
},
};
Query fails with search_phase_execution_exception at onBody
Without pit query works fine but it's needed to retrieve more than 10000 hits
Well, using PIT in NodeJS ElasticSearch's client is not clear, or at least is not well documented. You can create a PIT using the client like:
const pitRes = await elastic.openPointInTime({
index: index,
keep_alive: "1m"
});
pit_id = pitRes.body.id;
But there is no way to use that pit_id in the search method, and it's not documented properly :S
BUT, you can use the scroll API as follows:
const scrollSearch = await elastic.helpers.scrollSearch({
index: index,
body: {
"size": 10000,
"query": {
"query_string": {
"fields": [ "vm_ref", "org", "vm" ],
"query": organization + moreQuery
},
"sort": [
{ "utc_date": "desc" }
]
}
}});
And then read the results as follows:
let res = [];
try {
for await (const result of scrollSearch) {
res.push(...result.body.hits.hits);
}
} catch (e) {
console.log(e);
}
I know that's not the exact answer to your question, but I hope it helps ;)
The usage of point-in-time for pagination of search results is now documented in ElasticSearch. You can find more or less detailed explanations here: Paginate search results
I prepared an example that may give an idea about how to implement the workflow, described in the documentation:
async function searchWithPointInTime(cluster, index, chunkSize, keepAlive) {
if (!chunkSize) {
chunkSize = 5000;
}
if (!keepAlive) {
keepAlive = "1m";
}
const client = new Client({ node: cluster });
let pointInTimeId = null;
let searchAfter = null;
try {
// Open point in time
pointInTimeId = (await client.openPointInTime({ index, keep_alive: keepAlive })).body.id;
// Query next chunk of data
while (true) {
const size = remained === null ? chunkSize : Math.min(remained, chunkSize);
const response = await client.search({
// Pay attention: no index here (because it will come from the point-in-time)
body: {
size: chunkSize,
track_total_hits: false, // This will make query faster
query: {
// (1) TODO: put any filter you need here (instead of match_all)
match_all: {},
},
pit: {
id: pointInTimeId,
keep_alive: keepAlive,
},
// Sorting should be by _shard_doc or at least include _shard_doc
sort: [{ _shard_doc: "desc" }],
// The next parameter is very important - it tells Elastic to bring us next portion
...(searchAfter !== null && { search_after: [searchAfter] }),
},
});
const { hits } = response.body.hits;
if (!hits || !hits.length) {
break; // No more data
}
for (hit of hits) {
// (2) TODO: Do whatever you need with results
}
// Check if we done reading the data
if (hits.length < size) {
break; // We finished reading all data
}
// Get next value for the 'search after' position
// by extracting the _shard_doc from the sort key of the last hit
searchAfter = hits[hits.length - 1].sort[0];
}
} catch (ex) {
console.error(ex);
} finally {
// Close point in time
if (pointInTime) {
await client.closePointInTime({ body: { id: pointInTime } });
}
}
}
I'm absolutely brand new to DynamoDb and I'm trying to simply write an object from a NodeJS Lambda. Based on what I've read and researched I should probably be using DocumentClient from the aws-sdk. I also found the following question here regarding issues with DocumentClient, but it doesn't seem to address my specific issue....which I can't really find/pinpoint unfortunately. I've set up a debugger to help with SAM local development, but it appears to be only providing some of the errors.
The code's implementation is shown here.
var params = {
TableName: "March-Madness-Teams",
Item: {
"Id": {"S": randstring.generate(9)},
"School":{"S": team_name},
"Seed": {"S": seed},
"ESPN_Id": {"S": espn_id}
}
}
console.log(JSON.stringify(params))
dynamodb.put(params, (error,data) => {
if (error) {
console.log("Error ", error)
} else {
console.log("Success! ", data)
}
})
Basically I'm scrubbing a website utilizing cheerio library and cherry picking values from the DOM and saving them into the json object shown below.
{
"TableName": "March-Madness-Teams",
"Item": {
"Id": {
"S": "ED311Oi3N"
},
"School": {
"S": "BAYLOR"
},
"Seed": {
"S": "1"
},
"ESPN_Id": {
"S": "239"
}
}
}
When I attempt to push this json object to Dynamo, I get errors says
Error MultipleValidationErrors: There were 2 validation errors:
* MissingRequiredParameter: Missing required key 'TableName' in params
* MissingRequiredParameter: Missing required key 'Item' in params
The above error is all good in well....I assume it didn't like the fact that I had wrapped those to keys in strings, so I removed the quotes and sent the following
{
TableName: "March-Madness-Teams",
Item: {
"Id": {
"S": "ED311Oi3N"
},
"School": {
"S": "BAYLOR"
},
"Seed": {
"S": "1"
},
"ESPN_Id": {
"S": "239"
}
}
}
However, when I do that...I kind of get nothing.
Here is a larger code snippet.
return new Promise((resolve,reject) => {
axios.get('http://www.espn.com/mens-college-basketball/bracketology')
.then(html => {
const dynamodb = new aws.DynamoDB.DocumentClient()
let $ = cheerio.load(html.data)
$('.region').each(async function(index, element){
var preregion = $(element).children('h3,b').text()
var region = preregion.substr(0, preregion.indexOf('(') - 1)
$(element).find('a').each(async function(index2, element2){
var seed = $(element2).siblings('span.rank').text()
if (seed.length > 2){
seed = $(element2).siblings('span.rank').text().substring(0, 2)
}
var espn_id = $(element2).attr('href').split('/').slice(-2)[0]
var team_name = $(element2).text()
var params = {
TableName: "March-Madness-Teams",
Item: {
"Id": randstring.generate(9),
"School":team_name,
"Seed": seed,
"ESPN_Id": espn_id
}
}
console.log(JSON.stringify(params))
// dynamodb.put(params)
// .then(function(data) {
// console.log(`Success`, data)
// })
})
})
})
})
Can you try without the type?
Instead of
"School":{"S": team_name},
for example, use
"School": team_name,
From your code, I can see the mis promise on the dynamodb request. Try to change your lines :
dynamodb.put(params).then(function(data) {
console.log(`Success`, data)
})
to be :
dynamodb.put(params).promise().then(function(data) {
console.log(`Success`, data)
})
you can combine with await too :
await dynamodb.put(params).promise().then(function(data) {
console.log(`Success`, data)
})
exports.lambdaHandler = async (event, context) => {
const html = await axios.get('http://www.espn.com/mens-college-basketball/bracketology')
let $ = cheerio.load(html.data)
const schools = buildCompleteSchoolObject(html, $)
try {
await writeSchoolsToDynamo(schools)
return { statusCode: 200 }
} catch (error) {
return { statusCode: 400, message: error.message }
}
}
const writeSchoolsToDynamo = async (schools) => {
const promises = schools.map(async school => {
await dynamodb.put(school).promise()
})
await Promise.all(promises)
}
const buildCompleteSchoolObject = (html, $) => {
const schools = []
$('.region').each(loopThroughSubRegions(schools, $))
return schools
}
const loopThroughSubRegions = (schools, $) => {
return (index, element) => {
var preregion = $(element).children('h3,b').text()
var region = preregion.substr(0, preregion.indexOf('(') - 1)
$(element).find('a').each(populateSchoolObjects(schools, $))
}
}
const populateSchoolObjects = (schools, $) => {
return (index, element) => {
var seed = $(element).siblings('span.rank').text()
if (seed.length > 2) {
seed = $(element).siblings('span.rank').text().substring(0, 2)
}
var espn_id = $(element).attr('href').split('/').slice(-2)[0]
var team_name = $(element).text()
schools.push({
TableName: "March-Madness-Teams",
Item: {
"Id": randstring.generate(9),
"School": team_name,
"Seed": seed,
"ESPN_Id": espn_id
}
})
}
}
I know this is drastically different from what I started with but I did some more digging and kind of kind of worked to this...I'm not sure if this is the best way, but I seemed to get it to work...Let me know if something should change!
Oh I understand what you want.
Maybe you can see the code above works, but there is one concept you have to improve here about async - await and promise especially on lambda function.
I have some notes here from your code above, maybe can be your consideration to improve your lambda :
Using await for every promise in lambda is not the best approach because we know the lambda time limitation. But sometimes we can do that for other case.
Maybe you can change the dynamodb.put method to be dynamodb.batchWriteItem :
The BatchWriteItem operation puts or deletes multiple items in one or more tables.
Or If you have to use dynamodb.put instead, try to get improve the code to be like so :
const writeSchoolsToDynamo = async (schools) => {
const promises = schools.map(school => {
dynamodb.put(school).promise()
})
return Promise.all(promises)
}
Unable to get single NetSuite salesOrder, Generating below error
Getting Sales Order record
Error
[
{
'$attributes': { type: 'ERROR' },
code: 'INVALID_TRANS_TYP',
message: 'Transaction type specified is incorrect.'
}
]
{
"readResponse": {
"status": {
"$attributes": {
"isSuccess": "false"
},
"statusDetail": [
{
"$attributes": {
"type": "ERROR"
},
"code": "INVALID_TRANS_TYP",
"message": "Transaction type specified is incorrect."
}
]
}
}
}
Last Request:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="urn:platform_2014_2.webservices.netsuite.com" xmlns:platformMsgs="urn:messages_2014_2.platform.webservices.netsuite.com" xmlns:platformFaults="urn:faults_2014_2.platform.webservices.netsuite.com" xmlns:platformCore="urn:core_2014_2.platform.webservices.netsuite.com" xmlns:platformCommon="urn:common_2014_2.platform.webservices.netsuite.com" xmlns:listRel="urn:relationships_2014_2sales.transactions.webservices.netsuite.com" xmlns:tranSales="urn:sales_2014_2.transactions.webservices.netsuite.com" xmlns:actSched="urn:scheduling_2014_2.activities.webservices.netsuite.com" xmlns:setupCustom="urn:customization_2014_2.setup.webservices.netsuite.com" xmlns:listAcct="urn:accounting_2014_2.lists.webservices.netsuite.com" xmlns:tranBank="urn:bank_2014_2.transactions.webservices.netsuite.com" xmlns:tranCust="urn:customers_2014_2.transactions.webservices.netsuite.com" xmlns:tranInvt="urn:inventory_2014_2.transactions.webservices.netsuite.com" xmlns:listSupport="urn:support_2014_2.lists.webservices.netsuite.com" xmlns:tranGeneral="urn:general_2014_2.transactions.webservices.netsuite.com" xmlns:listMkt="urn:marketing_2014_2.lists.webservices.netsuite.com" xmlns:listWebsite="urn:website_2014_2.lists.webservices.netsuite.com" xmlns:fileCabinet="urn:filecabinet_2014_2.documents.webservices.netsuite.com" xmlns:listEmp="urn:employees_2014_2.lists.webservices.netsuite.com"><soap:Header><platformMsgs:passport><platformCore:email>darshan.sanandiya#techholding.co</platformCore:email><platformCore:password>techh#123#</platformCore:password><platformCore:account>5022995_SB1</platformCore:account><platformCore:role internalId="3"></platformCore:role></platformMsgs:passport></soap:Header><soap:Body><platformMsgs:get xmlns:platformMsgs="urn:messages_2014_2.platform.webservices.netsuite.com" xmlns="urn:messages_2014_2.platform.webservices.netsuite.com"><platformMsgs:baseRef type="salesOrder" xsi:type="platformCore:RecordRef" internalId="106095" externalId="106095"></platformMsgs:baseRef></platformMsgs:get></soap:Body></soap:Envelope>
All I want to fetch single order with internalId, But in return, it throws INVALID transction type error;
I am using npm soap; and netsuite sdk with nodejs
'use strict';
var denodeify = require('denodeify');
var NetSuite = require('../');
var credentials = require('../example/credentials.json');
var config = new NetSuite.Configuration(credentials);
var service = new NetSuite.Service(config);
console.log('Creating NetSuite connection');
console.log(service,"Service<<<")
service
.init()
.then(function( /*client*/ ) {
console.log('WSDL processed. Service description:');
console.log(service.config.client.describe());
var recordRef = new NetSuite.Records.RecordRef();
recordRef.internalId = "106095";
recordRef.type = 'salesOrder';
console.log('Getting Sales Order record');
return service.get(recordRef);
})
.then(function(result, raw, soapHeader) {
if (result.readResponse.status.$attributes.isSuccess !== 'true') {
console.error('Error');
console.error(result.readResponse.status.statusDetail);
}
console.log(JSON.stringify(result, null, 2));
console.log('Last Request:');
console.log(service.config.client.lastRequest);
})
.catch(function(err) {
console.error(err);
console.error('Last Request:');
console.error(service.config.client.lastRequest);
});
Above is the code I am executing to get salesOrder;
Believe the Record Type you want is SalesOrder, not salesOrder.
Reference: https://system.netsuite.com/help/helpcenter/en_US/srbrowser/Browser2019_2/schema/record/salesorder.html
Presently I have set time interval in such a way that every 1 seconds,a function is executed.The problem is that,i am displaying notification through this function.There are notification buttons in notification.When I click on the notification action button,mulitple windows are being open.I found out that it is because I have set Timer.But in my extension , timer is necessary in order to check the output of a server file everytime.Anyone please help me.Is there any other way to deal this problem
Here is my background.js
var myNotificationID = null;
var oldChromeVersion = !chrome.runtime;
setInterval(function() {
updateIcon();
}, 1000);
function onInit() {
updateIcon();
if (!oldChromeVersion) {
chrome.alarms.create('watchdog',{periodInMinutes:5,delayInMinutes: 0});
}
}
function onAlarm(alarm) {
if (alarm && alarm.name == 'watchdog') {
onWatchdog();
}
else {
updateIcon();
}
}
function onWatchdog() {
chrome.alarms.get('refresh', function(alarm) {
if (alarm) {
console.log('Refresh alarm exists. Yay.');
}
else {
updateIcon();
}
});
}
if (oldChromeVersion) {
updateIcon();
onInit();
}
else {
chrome.runtime.onInstalled.addListener(onInit);
chrome.alarms.onAlarm.addListener(onAlarm);
}
function updateIcon(){
if(//something)
//something
else{
chrome.notifications.create(
'id1',{
type: 'basic',
iconUrl: '/calpine_not_logged_in.png',
title: 'Warning : Attendance',
message: 'Please mark your Attendance !',
buttons: [{ title: 'Mark',
iconUrl: '/tick.jpg'
},{ title: 'Ignore',
iconUrl: '/cross.jpg'}],
priority: 0},
function(id) { myNotificationID = id;}
);
chrome.notifications.onButtonClicked.addListener(function(notifId, btnIdx) {
if (notifId === myNotificationID) {
if (btnIdx === 0) {
window.open("http://www.calpinemate.com/");
} else if (btnIdx === 1) {
notification.close();
}
}
});
chrome.notifications.onClosed.addListener(function() {
notification.close();
});
}
} }
});
}
}
onInit();
Here i found out that,when i remove the delayInminutes and laso the set timeineterval it opens only one tab,as i want.But both of them are necessary to do continuous checking of a server file.because whole my operation is based on the server file output.Is there any way to cope with this problem.Is there any way to set time interval for only that function?
The problem has nothing to do with what you "suspect". The problem is that you add a listener for chrome.notifications.onButtonClicked events inside the updateIcon() function. So this is what happens:
Every second you execute updateIcon().
Inside updateIcon() you set a new listener that listens for notification-buttons being clicked.
So, after 1 second there will be 1 listener, after 2 seconds there will be 2 listeners, after n seconds there will be n listeners.
When you click the button, each listener will catch the onButtonClicked event and open a new window. (So there will be so many windows as many seconds have elapsed since you loaded your extension.
How to fix this:
You need to create the listener only once (and not every second). To remove the following piece of code from inside the updateIcon() function:
chrome.notifications.onButtonClicked.addListener(function(notifId, btnIdx) {
if (notifId === myNotificationID) {
if (btnIdx === 0) {
window.open("http://www.calpinemate.com/");
} else if (btnIdx === 1) {
notification.close();
}
}
});
And place it at the end of your background-page (just before onInit();). Make sure you don't place it inside any function.