otsdaq_utilities  v2_05_02_indev
SystemStatus.js
1 
3 var _allAppsArray;// leave undefined to indicate first time in getAppsArray()
4 var _allContextNames = {}; //use map for unique keys
5 var _allClassNames = {}; //use map for unique keys
6 var _allHostNames = {}; //use map for unique keys
7 var _arrayOnDisplayTable = new Array(); // has the array values currently displayed on the table
8 
9 var _updateAppsTimeout = 0;
10 
11 var _displayingFilters = false; //set default here
12 
13 var _statusDivElement, _filtersDivElement, _toggleFiltersLinkElement;
14 
15 var _MARGIN = 5;
16 var _OFFSET_Y = 80;
17 
18 //functions:
19  // init()
20  // paint()
21  // toggleFilters()
22  // ========= server calls ====================
23  // getContextNames()
24  // getAppsArray()
25  // updateAppsArray()
26  // ========== Display functions ==============
27  // displayTable(appsArray)
28  // ========= filtering functions =============
29  // createFilterList()
30  // localRenderFilterList()
31  // collapsibleList()
32  // selectAll()
33  // applyFilterItemListeners()
34  // filter()
35  // getFilteredArray(className)
36  // isEquivalent(a, b)
37  // setIntersection(list1, list2)
38 
39 var windowTooltip = "To verify Status Monitoring is enabled, check the Gateway Supervisor parameter that " +
40  "controls it. To check app status, set this field to YES in your Context Group Configuration Tree: \n\n" +
41  "<b>XDAQApplicationTable --> \nGatewaySupervisor (record in XDAQApplicationTable) --> \nLinkToSuperivorTable --> \nEnableApplicationStatusMonitoring</b>" +
42  "\n\n" +
43  "Remember, to restart ots after a Context group configuration change.";
46 
47 //=====================================================================================
48 //init called once body has loaded
49 function init()
50 {
51  Debug.log("App status init");
52 
53 
54  DesktopContent.setWindowTooltip(windowTooltip);
55 
56 
57  _statusDivElement = document.getElementById("appStatusDiv");
58  _filtersDivElement = document.getElementById("filtersDiv");
59  _toggleFiltersLinkElement = document.getElementById("toggleFiltersLink");
60 
61  collapsibleList();
62 
63  window.onresize = paint;
64  paint();
65 
66  // Use promises to make code execute in an intutive manner
67  // 1. Get context names into an array...then
68  // 2. Get app names into an array ....then
69  // 3. display the table of array names and call filtering functions
70  // 4. repeatedly update the _allAppsArray values
71  getContextNames().
72  then(getAppsArray).
73  then(function(result){
74  // display table of apps
75  displayTable(result);
76 
77  // populate filterDiv
78  createFilterList();
79  });
80 
81 } // end of init()
82 
83 //=====================================================================================
84 //paint sets size of divs, called on window resize
85 function paint()
86 {
87  var w = window.innerWidth;
88  var h = window.innerHeight;
89 
90  Debug.log("paint to " + w + " - " + h);
91 
92  if(_displayingFilters)
93  {
94  _filtersDivElement.style.display = "block";
95  _toggleFiltersLinkElement.innerHTML = "Hide Filters";
96  }
97  else
98  {
99  _filtersDivElement.style.display = "none";
100  _toggleFiltersLinkElement.innerHTML = "Show Filters";
101  }
102 
103  h -= _MARGIN*2 + _OFFSET_Y;
104 
105  w = (w*.2)|0;
106  if(w < 200) w = 200;
107  if(h < 200) h = 200;
108 
109  _filtersDivElement.style.width = w + "px";
110  _filtersDivElement.style.height = h + "px";
111 
112  w = _filtersDivElement.scrollWidth;
113  Debug.log("Resize filters " + _filtersDivElement.scrollWidth);
114 
115  var filterEls = document.getElementsByClassName("filterList");
116  var filterBtns = document.getElementsByClassName("collapsible");
117  for(var i=0;i<filterEls.length;++i)
118  {
119  filterEls[i].style.width = (w-30) + "px";
120  filterBtns[i].style.width = w + "px";
121  }
122 
123 
124 } //end paint()
125 
126 //=====================================================================================
127 function toggleFilters()
128 {
129  Debug.log("toggleFilters()");
130  _displayingFilters = !_displayingFilters;
131  paint();
132 } //end toggleFilters()
133 
134 //=====================================================================================
135 // The function below gets the available context names from the server
136 function getContextNames()
137 {
138  return new Promise(function(resolve, reject)
139  {
140  //get context
141  DesktopContent.XMLHttpRequest("Request?RequestType=getContextMemberNames", "",
142  function (req)
143  {
144  var memberNames = req.responseXML.getElementsByTagName("ContextMember");
145 
146  _allContextNames = {}; //reset and treat as count
147 
148  for(var i=0;i<memberNames.length;++i)
149  if(_allContextNames[memberNames[i].getAttribute("value")])
150  ++_allContextNames[memberNames[i].getAttribute("value")];
151  else
152  _allContextNames[memberNames[i].getAttribute("value")] = 1;
153 
154  console.log("_allContextNames",Object.keys(_allContextNames).length,_allContextNames);
155 
156  if(Object.keys(_allContextNames).length == 0)
157  {
158  Debug.log("Empty context member list found!",Debug.HIGH_PRIORITY);
159  reject("Empty context member list found!");
160  }
161 
162  resolve(_allContextNames);
163 
164  }); //end request handler
165 
166  }); // end of Promise
167 
168 } // end of getContextNames()
169 
170 //=====================================================================================
171 // This function makes a call to the server and returns an array of objects
172 // each object contains the details of an application such as the id, name, status etc.
173 function getAppsArray()
174 {
175  return new Promise(function(resolve, reject)
176  {
177  DesktopContent.XMLHttpRequest("Request?RequestType=getAppStatus", "",
178  function (req,param,err)
179  {
180 
181  if(err)
182  {
183  Debug.log("Error received updating status: " + err);
184 
185  //try again in a few seconds
186  // update the _allAppsArray variable with repeated calls to server
187  if(_updateAppsTimeout) window.clearTimeout(_updateAppsTimeout);
188  _updateAppsTimeout = window.setTimeout(updateAppsArray, 5000 /*ms*/);
189 
190  return;
191  }
192  var appNames, appUrls, appIds, appStatus, appTime,
193  appStale, appClasses, appProgress, appDetail, appContexts;
194 
195  appNames = req.responseXML.getElementsByTagName("name");
196  appIds = req.responseXML.getElementsByTagName("id");
197  appStatus = req.responseXML.getElementsByTagName("status");
198  appTime = req.responseXML.getElementsByTagName("time");
199  appStale = req.responseXML.getElementsByTagName("stale");
200  appProgress = req.responseXML.getElementsByTagName("progress");
201  appDetail = req.responseXML.getElementsByTagName("detail");
202  appClasses = req.responseXML.getElementsByTagName("class");
203  appUrls = req.responseXML.getElementsByTagName("url");
204  appContexts = req.responseXML.getElementsByTagName("context");
205 
206  if(_allAppsArray === undefined && appTime.length > 1)
207  {
208  //first time, check for app status monitoring enabled
209  // time of 0, indicates app status not updating
210 
211  var all0 = true;
212  for(var i=1;i<appTime.length;++i)
213  {
214  //Wed Oct 14 05:20:48 1970 CDT
215  var appTimeSplit = appTime[i].getAttribute("value").split(' ');
216  if(appTime[i].getAttribute("value") != "0" &&
217  (appTimeSplit.length > 2 &&
218  (appTimeSplit[appTimeSplit.length-2]|0) != 1970
219  &&
220  (appTimeSplit[appTimeSplit.length-2]|0) < 4000
221  ) //i.e. real if year is not 0 or -1
222  )
223  {
224  all0 = false;
225  break;
226  }
227  }
228 
229  if(all0)
230  {
231  Debug.log("It appears that active application status monitoring is currently OFF! " +
232  "\n\n\n" + windowTooltip,
233  Debug.HIGH_PRIORITY);
234  }
235  }
236 
237  _allAppsArray = new Array();
238  _allClassNames = {}; //reset and treat as count
239  _allHostNames = {}; //reset and treat as count
240 
241  for(var i=0;i<appNames.length;i++)
242  {
243  _allAppsArray.push({
244  "name" : appNames[i].getAttribute("value"),
245  "id" : appIds[i].getAttribute("value"),
246  "status" : appStatus[i].getAttribute("value"),
247  "time" : appTime[i].getAttribute("value"),
248  "stale" : appStale[i].getAttribute("value"),
249  "progress" : appProgress[i].getAttribute("value"),
250  "detail" : appDetail[i].getAttribute("value"),
251  "class" : appClasses[i].getAttribute("value"),
252  "url" : appUrls[i].getAttribute("value"),
253  "context" : appContexts[i].getAttribute("value")
254  });
255 
256  var appTimeSplit = _allAppsArray[_allAppsArray.length-1].time.split(' ');
257  if(!(appTimeSplit.length > 2 &&
258  (appTimeSplit[appTimeSplit.length-2]|0) != 1970
259  &&
260  (appTimeSplit[appTimeSplit.length-2]|0) < 4000
261  )) //i.e. real if year is not 0 or -1
262  _allAppsArray[_allAppsArray.length-1].progress = 0;
263 
264  // populate the array of classes
265 
266  if(_allClassNames[appClasses[i].getAttribute("value")])
267  ++_allClassNames[appClasses[i].getAttribute("value")];
268  else
269  _allClassNames[appClasses[i].getAttribute("value")] = 1;
270 
271  // populate the array of hostnames
272  var hostname = appUrls[i].getAttribute("value");
273  if(hostname && hostname.length)
274  {
275  if(hostname.lastIndexOf(':') >= 0) //remove port
276  hostname = hostname.substr(0,hostname.lastIndexOf(':'));
277  if(hostname.lastIndexOf('/') >= 0) //remove http://
278  hostname = hostname.substr(hostname.lastIndexOf('/')+1);
279 
280  if(_allHostNames[hostname])
281  ++_allHostNames[hostname];
282  else
283  _allHostNames[hostname] = 1;
284  }
285 
286  } //end app parameter extration loop
287 
288 
289  if(_allAppsArray.length == 0)
290  {
291  Debug.log("Empty apps array!",Debug.HIGH_PRIORITY);
292  reject("Empty Empty apps array!");
293  }
294 
295  //return _allAppsArray;
296  resolve(_allAppsArray);
297 
298  // update the _allAppsArray variable with repeated calls to server
299  if(_updateAppsTimeout) window.clearTimeout(_updateAppsTimeout);
300  _updateAppsTimeout = window.setTimeout(updateAppsArray, 1000 /*ms*/);
301 
302  },
303  0,0, //reqParam, progressHandler
304  true /*callHandlerOnErr*/,
305  true /*doNotShowLoadingOverlay*/);// end of request handler
306  });// end of Promise
307 
308 }// end of getAppsArray()
309 
310 //=====================================================================================
311 // this function updates the _allAppsArray by making repeated requests to the server
312 // at specific time intervals. The function is called by setTimeout()
313 // because setInterval() can get unwieldy.
314 function updateAppsArray()
315 {
316  getAppsArray();
317  _arrayOnDisplayTable = setIntersection(_allAppsArray, _arrayOnDisplayTable);
318  displayTable(_arrayOnDisplayTable);
319 }; // end of updateAppsArray()
320 
321 //=====================================================================================
322 // this function displays a table with the app array passed into it
323 function displayTable(appsArray)
324 {
325  // clear the appStatusDiv
326  var statusDivElement = document.getElementById("appStatusDiv");
327  statusDivElement.innerHTML = "";
328 
329  //Create a last update timestamp
330  if(appsArray && appsArray.length)
331  document.getElementById(
332  "lastUpdateTimeDiv").innerHTML =
333  "Showing " + appsArray.length +
334  "/" + _allAppsArray.length + " Apps " +
335  "(Last update: " + appsArray[0].time + ")";
336 
337  //Create a HTML Table element.
338  var table = document.createElement("TABLE");
339  table.border = "0";
340 
341  //Get the count of columns.
342  var columnNames = ["App Name", "Status", "Progress", "Detail", "Last Update", "App Type", "App URL", "App ID", "Parent Context Name"];
343  var columnKeys = ["name", "status", "progress", "detail", "stale", "class", "url", "id", "context" ];
344  var columnCount = columnNames.length;
345 
346  //Add the header row.
347  var row = table.insertRow(-1);
348  for (var i = 0; i < columnCount; i++)
349  {
350  var headerCell = document.createElement("TH");
351  headerCell.innerHTML = columnNames[i];
352  row.appendChild(headerCell);
353  }
354 
355  //Add the data rows.
356  for (var i = 0; i < appsArray.length; i++)
357  {
358  row = table.insertRow(-1);
359  for (var j = 0; j < columnKeys.length; ++j)
360  {
361  var cell = row.insertCell(-1);
362 
363  //add mouseover tooltip
364  cell.title = appsArray[i].name + "'s " +
365  columnNames[j];
366 
367  if(columnKeys[j] == "stale")
368  {
369  var staleString = "";
370  var staleSeconds = appsArray[i][columnKeys[j]] | 0;
371  if(appsArray[i].time == "0")
372  staleString = "No status";
373  else if(staleSeconds < 10)
374  staleString = "Seconds ago";
375  else if(staleSeconds < 60)
376  staleString = "One minute ago";
377  else if(staleSeconds < 40*60)
378  staleString = (((staleSeconds/60)|0)+1) + " minutes ago";
379  else if(staleSeconds < 75*60)
380  staleString = "One hour ago";
381  else if (staleSeconds < 60*60*2)
382  staleString = (((staleSeconds/60/60)|0)+1) + " hours ago";
383  else if (staleSeconds < 60*60*48)
384  staleString = (((staleSeconds/60/60)|0)+1) + " days ago";
385 
386  cell.innerHTML = staleString;
387  }
388  else if(columnKeys[j] == "progress")
389  {
390  var progressNum = appsArray[i][columnKeys[j]] | 0;
391  if(progressNum > 100)
392  progressNum = 99; //attempting to figure out max (or variable steps)
393 
394  if(progressNum == 100)
395  cell.innerHTML = "Done";
396  else
397  {
398  //scale progress bar to width of cell (66px)
399 
400  var progressPX = ((66*progressNum/100)|0);
401  if(progressPX > 0 && progressPX < 3) progressPX = 3; //show something non-zero
402 
403  cell.innerHTML = "&nbsp;" + progressNum + " %<div class='progressBar' style='width:" +
404  progressPX + "px;'></div>";
405 
406  }
407  }
408  else if (columnKeys[j] == "status")
409  {
410  var statusString = appsArray[i][columnKeys[j]];
411 
412  try
413  {
414  statusString = statusString.split(":::")[0];
415  }
416  catch(e)
417  {
418  str = "Unknown";
419  Debug.log("What happened? " + e);
420  }
421 
422 
423  switch(statusString)
424  {
425  case "Initial":
426  cell.style.background = "radial-gradient(circle at 50% 120%, rgb(119, 208, 255), rgb(119, 208, 255) 10%, rgb(7, 105, 191) 80%, rgb(6, 39, 69) 100%)";
427  break;
428  case "Halted":
429  cell.style.background = "radial-gradient(circle at 50% 120%, rgb(255, 207, 105), rgb(245, 218, 179) 10%, rgb(234, 131, 3) 80%, rgb(121, 68, 0) 100%)";
430  break;
431  case "Configured":
432  case "Paused":
433  cell.style.background = "radial-gradient(circle at 50% 120%, rgb(80, 236, 199), rgb(179, 204, 197) 10%, rgb(5, 148, 122) 80%, rgb(6, 39, 69) 100%)";
434  break;
435  case "Running":
436  cell.style.background = "radial-gradient(circle at 50% 120%, rgb(0, 255, 67), rgb(142, 255, 172) 10%, rgb(5, 148, 42) 80%, rgb(6, 39, 69) 100%)";
437  break;
438  case "Failed":
439  case "Error":
440  case "Soft-Error":
441  cell.style.background = "radial-gradient(circle at 50% 120%, rgb(255, 124, 124), rgb(255, 159, 159) 10%, rgb(218, 0, 0) 80%, rgb(144, 1, 1) 100%)";
442 
443  cell.style.cursor = "pointer";
444  cell.id = "cell-" + i + "-" + j;
445  cell.onclick =
446  function()
447  {
448  Debug.log("Cell " + this.id);
449 
450  var i = this.id.split('-');
451  var j = i[2]|0;
452  var i = i[1]|0;
453  Debug.log(
454  appsArray[i][columnKeys[j]],
455  Debug.HIGH_PRIORITY);
456  };
457  break;
458  default:
459  } // end of switch
460 
461  cell.innerHTML = statusString;
462  }
463  else if (columnKeys[j] == "detail")
464  {
465  cell.innerHTML = decodeURIComponent(appsArray[i][columnKeys[j]]);
466  }
467  else
468  cell.innerHTML = appsArray[i][columnKeys[j]];
469 
470  if (columnKeys[j] == "status")
471  {
472  cell.style.textAlign = "center";
473  cell.className = "statusCell";
474 
475  }// end of status style handling
476  else if(columnKeys[j] == "progress" || columnKeys[j] == "id")
477  cell.style.textAlign = "center";
478  }
479  }// done with adding data rows
480 
481 
482  // add table to appStatusDiv
483  statusDivElement.appendChild(table);
484 
485  // keep record of current array on display. This variable is later used to redisplay table after user does filtering
486  _arrayOnDisplayTable = appsArray;
487 
488  return 1;
489 
490 }// end of displayTable()
491 
492 //=====================================================================================
493 // this function creates list elements and checkboxes to
494 // be displayed in the filterDiv
495 function createFilterList()
496 {
497  Debug.log("createFilterList()");
498 
499  localRenderFilterList(
500  _allContextNames,
501  document.getElementById('contextUl'),
502  "ContextName");
503  localRenderFilterList(
504  _allClassNames,
505  document.getElementById('classUl'),
506  "ClassName");
507  localRenderFilterList(
508  _allHostNames,
509  document.getElementById('hostUl'),
510  "HostName");
511 
512  // if user clicks on list item instead, tick the checkbox and call filter function
513  applyFilterItemListeners();
514 
515  return;
516 
517  //========================
518  function localRenderFilterList(elemObject, ulelem, cbName)
519  {
520  //create select all at top
521  {
522  var li = document.createElement('li'); // create a list element
523  var cb_input = document.createElement('input'); // create a checkbox
524  cb_input.setAttribute("type", "checkbox");
525  cb_input.setAttribute("class", cbName);
526  cb_input.checked = true; // default checkboxes to false
527  cb_input.setAttribute("value", "selectAll");
528 
529  //stop normal checkbox behavior by re-inverting it
530  cb_input.onclick = function(e) {console.log("cb"); this.checked = !this.checked;}
531 
532  li.setAttribute('class','item');
533  li.appendChild(cb_input);
534  var textnode;
535  textnode = document.createTextNode(" " + "Select All" + " ");
536 
537  li.appendChild(textnode);
538  ulelem.appendChild(li);
539  } //end create select all
540 
541  //add all keys in elements object
542  for (var key in elemObject)
543  {
544  var li = document.createElement('li'); // create a list element
545  var cb_input = document.createElement('input'); // create a checkbox
546  cb_input.setAttribute("type", "checkbox");
547  cb_input.setAttribute("class", cbName);
548  cb_input.checked = true; // default checkboxes to false
549  cb_input.setAttribute("value", key);
550 
551  //stop normal checkbox behavior by re-inverting it
552  cb_input.onclick = function(e) {console.log("cb"); this.checked = !this.checked;}
553 
554 
555  li.setAttribute('class','item');
556  li.appendChild(cb_input);
557 
558  var textnode;
559  //add space before and after for 'margin'
560  if (cbName == "className")
561  textnode = document.createTextNode(" " + key.slice(5) + " ");// remove "ots::" in display text
562  else
563  textnode = document.createTextNode(" " + key + " ");
564 
565  li.appendChild(textnode);
566  ulelem.appendChild(li);
567 
568  } // list element loop
569 
570  }// end of localRenderFilterList()
571 
572 }// end of createFilterList()
573 
574 //=====================================================================================
575 // this function does the setup for the collapsible menu in the filterDiv
576 function collapsibleList()
577 {
578 
579  var collapsible = document.getElementsByClassName("collapsible");
580 
581  Debug.log(collapsible.length + " collapsible lists found.");
582 
583  for (var i = 0; i < collapsible.length; i++)
584  {
585  collapsible[i].addEventListener("click",
586  function(e)
587  {
588  Debug.log("click handler " + this.nextElementSibling.id);
589 
590  this.firstElementChild.style.visibility = "hidden"; // make help tooltip hidden
591 
592  this.classList.toggle("active");
593  var content = this.nextElementSibling;
594  if (content.style.display === "block")
595  content.style.display = "none";
596  else
597  content.style.display = "block";
598 
599  paint();
600  }); //end click handler
601 
602  }
603 }// end of collapsibleList()
604 
605 //=====================================================================================
606 function applyFilterItemListeners()
607 {
608  Debug.log("applyFilterItemListeners()");
609 
610  var listElements = document.getElementsByTagName("li");
611 
612  for (let i = 0; i < listElements.length; i++)
613  {
614 
615  //========================
616  listElements[i].onmouseup = function(e) { e.stopPropagation(); }
617  listElements[i].onmousedown = function(e) { e.stopPropagation(); }
618  listElements[i].onclick =
619  function(e)
620  {
621  var val = this.firstElementChild.value;
622  var type = this.firstElementChild.className;
623 
624 
625  Debug.log("Clicked list item " + val + " type" + type);
626 
627  //toggle checkbox
628  this.firstElementChild.checked = !this.firstElementChild.checked;
629 
630  // tick the checkbox and call filter function
631  var listChildren = document.getElementsByClassName(type);
632  console.log("listChildren",listChildren);
633 
634  if(val == "selectAll")
635  {
636  for(var j = 0; j < listChildren.length; j++)
637  listChildren[j].checked = this.firstElementChild.checked;
638  }
639 
640  filter();
641 
642  }; //end list item click handler
643 
644  } //end list item loop
645 
646 }// end of applyFilterItemListeners()
647 
648 //=====================================================================================
649 function filter()
650 {
651  var filteredClass = getFilteredArray("ClassName","class"); // filter by class
652  var filteredContext = getFilteredArray("ContextName","context"); // filter by context
653  var filteredHost = getFilteredArray("HostName","host"); // filter by host
654 
655  // if filterByClass and filterByContext return empty arrays, display the full table
656  if (filteredClass.length == 0 && filteredContext.length == 0 &&
657  filteredHost.length == 0)
658  {
659  displayTable(_allAppsArray);
660  return;
661  }
662 
663  var found;
664 
665  var result = [];
666 
667  // loop through each app and keep if found in each filter
668  for (var i = 0; i < _allAppsArray.length; i++)
669  {
671  found = false;
672  for (var j = 0; j < filteredClass.length; j++)
673  if(_allAppsArray[i].name == filteredClass[j].name)
674  {
675  found = true;
676  break;
677  }
678  if(!found)
679  continue;
680 
682  found = false;
683  for (var j = 0; j < filteredContext.length; j++)
684  if(_allAppsArray[i].name == filteredContext[j].name)
685  {
686  found = true;
687  break;
688  }
689  if(!found)
690  continue;
691 
693  found = false;
694  for (var j = 0; j < filteredHost.length; j++)
695  if(_allAppsArray[i].name == filteredHost[j].name)
696  {
697  found = true;
698  break;
699  }
700  if(!found)
701  continue;
702 
703  result.push(_allAppsArray[i]);
704  } //end all apps loop
705 
706  // display the table
707  displayTable(result);
708 
709 } // end of filter()
710 
711 //=====================================================================================
712 function getFilteredArray(filterName, type)
713 {
714  var filterObjects = document.getElementsByClassName(filterName);
715  var checkedItems = new Array();
716 
717  // loop through elements and find those that are checked
718  for(var i = 0; i < filterObjects.length; i++)
719  {
720  if (filterObjects[i].checked)
721  {
722  var val = filterObjects[i].getAttribute("value");
723  checkedItems.push(val);
724  }
725  }
726 
727  // loop through _allAppsArray and get apps that match checked values
728  var filtered = _allAppsArray.filter(
729  function(app)
730  {
731 
732  // returns apps that have class/context values in checkedItems array
733 
734  if(type == "host")
735  {
736  var hostname = app.url;
737  if(hostname.lastIndexOf(':') >= 0) //remove port
738  hostname = hostname.substr(0,hostname.lastIndexOf(':'));
739  if(hostname.lastIndexOf('/') >= 0) //remove http://
740  hostname = hostname.substr(hostname.lastIndexOf('/')+1);
741 
742  return checkedItems.includes(hostname);
743  } //end hostname handling
744 
745  // return array if value is in checkedItems
746  return checkedItems.includes(app[type]);
747  }); //end filter handler
748 
749  return filtered;
750 } // end of getFilteredArray()
751 
752 //=====================================================================================
753 // generic function that can be used to get union/intersection of two arrays of objects
754 function setIntersection(list1, list2)
755 {
756 
757  result = [];
758  for (let i = 0; i < list1.length; i++)
759  {
760 
761  for(let j = 0; j < list2.length; j++)
762  {
763  if (list1[i].id == list2[j].id)
764  {
765  result.push(list1[i]);
766  break;
767  }
768  }// inner for loop
769 
770  }// outer for loop
771  return result;
772 } // end of setIntersection()
773 
774 
775 
776 
777 
778 
779 
780 
781 
782 
783 
784 
785 
786 
787 
788 
789 
790 
791 
792