Vue.js and bigger objects - performance - node.js

i'm working on a Vue 3 application where data comes from a weather station (NodeJS backend). A sample of a parameter from a "module" (module is my object which holds data from different sensors, but all from one location):
{
data: "temperatur",
factor: "10",
​​​ icon: null,
​​ key: "TemperaturOutside01",
​​ label: "Air Temperature",
​​​parameter: "Temperature",
​​​ type: "rw",
​​​ unit: "°C",
​​​value: 18.1
}
The "key" is my uniqe identifier in the whole object. It can also be that there are some childrens to the entry like:
{
...
children: [
{...}
]
}
and those children can also have childrens and so on. I'm working with a recurisve function which search for the specific parameter in the object an returns the node when found.
The recursive function:
function findByParameter(tree, parameter) {
for (let node of tree) {
if (node.parameter === parameter) {
return node;
}
if (node.children) {
let desiredNode = findByParameter(node.children, parameter);
if (desiredNode) {
return desiredNode;
}
}
}
return false;
}
All of that is working, and i'm getting the values in my Vue template like that:
<InputNumber id="temp01" ref="temp01" v-model="returnParameter(theObj, 'parameter', Temperature').value" :suffix="' ' + returnParameter(theObj, 'parameter', 'Temperature').unit" />
and the result for my example would be
18.1 °C
(the returnParameter() is just a method in Vue, returning the node from the findByParameter function)
This is a real time application with Socket.io for data exchange and value updates 1x/sec. if needed.
But now for the question: if i have a few hundred sensors and most of them change every second the values, would that be a problem for the client performance? The for ... of loop should be one of the fastest ways i heard here but maybe some of you have other ways...
Thank you!

Related

Unable to add event listener to webNavigation.onCompleted

Using the mock function below along with the dev console:
This call will work:
chrome.webNavigation.onCompleted.addListener(processWebNavChange, filtera);
but when I actually pass in my real var filter it throws this error:
Uncaught TypeError: Could not add listener
My actual data looks like this:
{
url: [ {hostContains: ".im88rmbOwZ"} ]
}
function registerWebNavListener() {
var matchers = getUrlMatchers();
var filter = {
url: matchers
};
// test with mock data filtera that actually works
const filtera = {
url:
[
{hostContains: "example.com"},
]
}
if (matchers.length > 0) {
chrome.webNavigation.onCompleted.addListener(processWebNavChange, filtera);
}
}
async function processWebNavChange(data) {
}
Is there something wrong with my data structure that I'm actually using? I don't believe that the filter object I returned is incorrect
}
EDIT:
I added a new
const filterb = {
url: [ {hostContains: ".im88rmbOwZ"} ]
};
and it still fails with that. The single entry {hostContains: ".im88rmbOwZ"}, was the first item returned from getURLMatchers() which I used as an example of real data being returned.
The above comment on the upper-case letters was the cause of the issue. Converting everything to lowercase resolved the problem.
Although, I am not clear as to why that was a problem to begin with. (If there are any hints in the chromium source code event filter handlers, I'd appreciate it if it could be pointed out).

Expect positive number parameter passed - jest

The test is linked to this question here which I raised (& was resolved) a few days ago. My current test is:
// Helpers
function getObjectStructure(runners) {
const backStake = runners.back.stake || expect.any(Number).toBeGreaterThan(0)
const layStake = runners.lay.stake || expect.any(Number).toBeGreaterThan(0)
return {
netProfits: {
back: expect.any(Number).toBeGreaterThan(0),
lay: expect.any(Number).toBeGreaterThan(0)
},
grossProfits: {
back: (runners.back.price - 1) * backStake,
lay: layStake
},
stakes: {
back: backStake,
lay: layStake
}
}
}
// Mock
const funcB = jest.fn(pairs => {
return pairs[0]
})
// Test
test('Should call `funcB` with correct object structure', () => {
const params = JSON.parse(fs.readFileSync(paramsPath, 'utf8'))
const { arb } = params
const result = funcA(75)
expect(result).toBeInstanceOf(Object)
expect(funcB).toHaveBeenCalledWith(
Array(3910).fill(
expect.objectContaining(
getObjectStructure(arb.runners)
)
)
)
})
The object structure of arb.runners is this:
{
"back": {
"stake": 123,
"price": 1.23
},
"lay": {
"stake": 456,
"price": 4.56
}
}
There are many different tests around this function mainly dependent upon the argument that is passed into funcA. For this example, it's 75. There's a different length of array that is passed to funcB dependent upon this parameter. However, it's now also dependent on whether the runners (back and/or lay) have existing stake properties for them. I have a beforeAll in each test which manipulates the arb in the file where I hold the params. Hence, that's why the input for the runners is different every time. An outline of what I'm trying to achieve is:
Measure the array passed into funcB is of correct length
Measure the objects within the array are of the correct structure:
2.1 If there are stakes with the runners, that's fine & the test is straight forward
2.2 If not stakes are with the runners, I need to test that; netProfits, grossProfits, & stakes properties all have positive Numbers
2.2 is the one I'm struggling with. If I try with my attempt below, the test fails with the following error:
TypeError: expect.any(...).toBeGreaterThan is not a function
As with previous question, the problem is that expect.any(Number).toBeGreaterThan(0) is incorrect because expect.any(...) is not an assertion and doesn't have matcher methods. The result of expect.any(...) is just a special value that is recognized by Jest equality matchers. It cannot be used in an expression like (runners.back.price - 1) * backStake.
If the intention is to extend equality matcher with custom behaviour, this is the case for custom matcher. Since spy matchers use built-in equality matcher anyway, spy arguments need to be asserted explicitly with custom matcher.
Otherwise additional restrictions should be asserted manually. It should be:
function getObjectStructure() {
return {
netProfits: {
back: expect.any(Number),
lay: expect.any(Number)
},
grossProfits: {
back: expect.any(Number),
lay: expect.any(Number)
},
stakes: {
back: expect.any(Number),
lay: expect.any(Number)
}
}
}
and
expect(result).toBeInstanceOf(Object)
expect(funcB).toHaveBeenCalledTimes(1);
expect(funcB).toHaveBeenCalledWith(
Array(3910).fill(
expect.objectContaining(
getObjectStructure()
)
)
)
const funcBArg = funcB.mock.calls[0][0];
const nonPositiveNetProfitsBack = funcBArg
.map(({ netProfits: { back } }, i) => [i, back])
.filter(([, val] => !(val > 0))
.map(([i, val] => `${netProfits:back:${i}:${val}`);
expect(nonPositiveNetProfitsBack).toEqual([]);
const nonPositiveNetProfitsLay = ...
Where !(val > 0) is necessary to detect NaN. Without custom matcher failed assertion won't result in meaningful message but an index and nonPositiveNetProfitsBack temporary variable name can give enough feedback to spot the problem. An array can be additionally remapped to contain meaningful values like a string and occupy less space in errors.

Angular 7 HttpClient get - can you access and process the return object?

I know this is a general question but I have exhausted google and tried many approaches.Any feedback is appreciated.
The HTTPClient is Angular 5+ so it returns an object created from the response JSON data. I get a massive JSON response from an endpoint I have no control over and I want to use about 20% of the response in my app and ignore the rest.
I am really trying hard to avoid using a series of templates or export objects or whatever and trying to force this massive untyped Observable into a typed object with hundreds of fields many being Arrays. All I need for the app is just a Array of very small objects with 3 fields per object. The 3 fields are all over within the JSON response and I want to map them to my object .map only seems to work when you are using the full response object and I can't find an example where .map does custom work besides in the case where you are mapping a few fields to 1 object and I am trying to map to an Array of my small objects.
UPDATED
Basically I want this service to return an object of Type DislayData to the module that subscribes to it but I get just an Object back. This is not what I ultimately need to do but if I can prove I can map the body of the response to my needed return type I can then start to break down the response body and return an Array of the Type I really need based on my silly DisplayData object. Thanks again!
export interface DislayData {
body: any;
}
...
export class DataService {
constructor(private http: HttpClient) { }
/** GET data from the black box */
getData(): Observable<DislayData> {
return this.http.get<HttpResponse<any>>(searchUrl, { observe: 'response' })
.pipe(
map(res => {
return res.body as DislayData;
}
tap(res => console.log(//do stuff with entire respoonse also)),
catchError(err => this.handleError(err)));
}
private handleError(error: HttpErrorResponse) {
...
Do you know the structure of the answering object?
If yes, you can do something like this:
item$ = new BehaviorSubject<any>({});
item = {
foo: 'a',
bar: 'b',
iton: [1, 2, 3],
boo: {
far: 'c'
}
};
logNewItem() {
this.item$
.pipe(
map(response => {
if (response.foo
&& response.iton
&& response.iton.length >= 3
&& response.boo
&& response.boo.far) {
let newItem = {
foo: response.foo,
iton2: response.iton[2],
far: response.boo.far
};
console.log(newItem); // output: Object { foo: "a", iton2: 3, far: "c" }
}
})
)
.subscribe();
this.item$.next(this.item);
}
Basically, you can simply make sure the properties exist, call them directly and map them to a better fitting object.
I heavily recommend creating an interface for the object you're receiving and an interface or class for the object you're mapping to. In that case you can also write the code more compact like this:
[...]
map(response: MyAPIResponse => {
let newItem = new NewItem(response);
console.log(newItem); // output: Object { foo: "a", iton2: 3, far: "c" }
}
})
[...]
class NewItem {
foo: string;
iton2: string;
far: string;
constructor(apiResponse: MyAPIResponse) {
//Validate parameter first
this.foo = apiResponse.foo;
this.iton2 = apiResponse.iton[2];
this.far = apiResponse.boo.far;
and make your code a lot more readable.

Why is the vuex component isn't updated, despite the state updating?

When I load my page, I fetch a list of optboxes items.
Sources
Project's sources are online:
optboxes page ;
store (store, actions, mutations, getters).
optboxes pages
The HTTP request is well send and return adequate data:
created(){
this.getOptboxes();
},
components: {
'optbox': OptboxComponent,
},
methods: {
getOptboxes() {
optboxes.all().then((response) => {
this.setOptboxes(response.data.output);
}).catch(() = > {
this.no_optbox_message = 'there is no optbox';
logging.error(this.$t('optboxes.get.failed'))
});
}
},
vuex: {
actions: { setOptboxes: actions.setOptboxes},
getters: { optboxesList: getters.retrieveOptboxes}
}
I'm iterating over the results as follow:
<div v-for="optbox in optboxesList" class="panel panel-default">
<optbox :optbox="optbox"></optbox>
</div>
Store
const state = {
optboxes: {
/*
'akema': {
hostname: "192.168.2.23",
id: "akema",
printers: [
{
description: "bureau",
destination_port: 9100,
forward: "normal",
hostname: "1.2.3.4",
id: 0,
listening_port: 9102
}
]
}
*/
}
};
Question
If I switch to another pages and come back then the list appear. I also notice that with the Vuex extension I can commit the state and see the changes.
Why are my changes not applied automatically?
I had to change my data structure due to Change Detection Caveats.
Due to limitations of JavaScript, Vue.js cannot detect the following
changes to an Array:
When you directly set an item with the index, e.g. vm.items[0] = {};
When you modify the length of the Array, e.g. vm.items.length = 0.
Store
optboxes is now an array.
const state = {
optboxes:[]
}
Then update my mutations accordingly to edit the array.
Maybe it's an issue of reactivity?! I assume your setOptboxes mutation is not being picked up by vue:
setOptboxes(state, optboxes) {
for (var optbox of optboxes) {
state.optboxes[optbox.id] = optbox;
}
}
You can read about it here:
https://vuejs.org/guide/list.html#Caveats
https://vuejs.org/guide/reactivity.html
The docs solution is to use:
state.optboxes.$set(optbox.id, optbox);
which will trigger a view update.
Using Vue.set dit it for me. Take a look here

OpenLayers Ext.form.ComboBox store query creates memory leak

Ack. Using OpenLayers, I have an Ext.form.ComboBox that runs a query of a Ext.data.JsonStore to access a shapefile of ~1 Gb and return a table of results. Then, a user may select one of the results and the map zooms to that location.
This works excellent. BUT! As the ComboBox is used, the JVM size continues to grow- a memory leak?- and Geoserver throws many issues mostly all related to:
Caused by: java.io.IOException: Map failed
Caused by: java.lang.OutOfMemoryError: Map failed
ERROR [geotools.map] - Call MapContent dispose() to prevent memory leaks
I can run the ComboBox as many as five times within about a minute before a storm of errors overwhelm Geoserver (e.g. GetFeatureInfo doesn't return any results, the ComboBox query comes back empty, pink tiles appear, EDIT: and GWC stops making new tiles!).
Do I need to destroy () or clear the store or...? and how? Thoughts??
GeoExt 1.1, OpenLayers 2.12.rc6, Ext 3.4, Ext 4.2.1.883, jquery-1.6, Geoserver 2.5, JDK i586 v7, Tomcat 7
code:
THE QUERY: looks up what is typed in the ComboBox & populates the Store
Ext.onReady(function() {
Ext.override(Ext.form.ComboBox, {
doQuery: function(q, forceAll) {
console.log('queryTpl', this.queryTpl, q);
q = Ext.isEmpty(q) ? '' : q;
var qe = {
query: q,
forceAll: forceAll,
combo: this,
cancel: false
};
if (this.fireEvent('beforequery', qe) === false || qe.cancel) {
return false;
}
q = qe.query;
forceAll = qe.forceAll;
if (forceAll === true || (q.length >= this.minChars)) {
if (this.lastQuery !== q) {
this.lastQuery = q;
if (this.mode == 'local') {
this.selectedIndex = -1;
if (forceAll) {
this.store.clearFilter();
} else {
this.store.filter(this.displayField, q);
}
this.onLoad();
} else {
this.store.baseParams[this.queryParam] = this.queryTpl ? String.format(this.queryTpl, q) : q;
this.store.load({
params: this.getParams(q)
});
this.expand();
}
} else {
this.selectedIndex = -1;
this.onLoad();
}
}
},
});
})
THE STORE
var dsm = new Ext.data.JsonStore({
remoteFilter: true,
autoLoad: false,
autoDestroy: true, // doesn't appear to help
url: '../geoserver/workspace', // generalized, not for public access
storeId: 'myStore2',
baseParams: {
service: 'WFS',
version: '1.1.0',
request: 'GetFeature',
typeName: 'MRDS_LUT', // *shp in geoserver defined by var
srsName: 'EPSG:4326',
outputFormat: 'json',
propertyName: 'SiteName,Municipal,Commodity,Status,Longitude,Latitude'
},
root: 'features',
fields: [{
name: 'SiteName',
mapping: 'properties.SiteName'
}, {
name: 'Municipal',
mapping: 'properties.Municipal'
}, {
name: 'Commodity',
mapping: 'properties.Commodity'
}, {
name: 'Status',
mapping: 'properties.Status'
}, {
name: 'Longitude',
mapping: 'properties.Longitude'
}, {
name: 'Latitude',
mapping: 'properties.Latitude'
},{
name: 'bbox',
mapping: 'properties.bbox'
}],
sortInfo: {
field: "Municipal",
direction: "ASC"
}
});
The ComboBox
var panelSiteNameSearch = new Ext.form.ComboBox({
store: dsm,
fieldLabel: "<b>Search</b>",
queryParam: "cql_filter",
queryTpl: "SiteName Like '{0}%'",
minChars: 5,
displayField: "SiteName",
valueField: "SiteName",
typeAhead: false,
loadingText: "Searching...",
width: 230,
emptyText: "Mine/Mineral Site Search",
hideTrigger: true,
tpl: '<tpl for="."><div class="search-item"><b>Site Name:</b> {SiteName}<br><b>Commodity: </b> {Commodity}<br><b>Municipality:</b> {Municipal}<br><b>Status:</b> {Status}</div><hr/></tpl>',
itemSelector: "div.search-item",
listeners: {
"select": function (combo, record) {
mrdsprimary.setVisibility(true);
mrdssecondary.setVisibility(true);
mrdstertiary.setVisibility(true);
map.setCenter(new OpenLayers.LonLat(record.data.Longitude,record.data.Latitude),13);
},
"focus": function () {
keyboardnav.deactivate();
},
"blur": function () {
keyboardnav.activate();
}
}
});
This solution is a bit nebulous. I finally prevented the out of memory errors by making sure the combined total size of all combo box stores, plus the initial Java -Xms allotment, does not exceed the Java -Xmx allotment.
For example, setting -Xms to 500m, and knowing that store #1 accesses a database of 600m and store #2 accesses a database of 800m, gives a combined total of 1900mb in my Java environment heap. If the -Xmx is set to 2g, then no errors are thrown.
In theory. I can't directly affect the Java environment's Xms without interfering (poorly) with Tomcat/Java's native GC. Knowing there is total allotted -Xmx cap, and what counts directly toward it, encourages extremely trim data stores.
Unfortunately I am limited to a 32bit JDK i586, which I read somewhere that carries an inherent 2gb limitation for -Xmx (which seems about right). The 64bit is reportedly much higher.
Findings: a remote store 'loads' an entire data store, and submits the filtered results locally... clearing the either store with removeAll or clearFilter or setValue() or NULL and/or SYNC only works locally and does not actually trigger any remote GC or reduction in the remote Java's heaping pile of unnecessary storage.
Long-term Solutions: Create a server-side PHP script that passes a given cql or some other query or ComboBox value directly to a SQL or PostGIS database, and leave javascript out of it. But I don't know how to write one yet.

Resources