{ "custom": {}, "params": {}, "props": { "defaultSize": { "height": 600, "width": 750 } }, "root": { "children": [ { "meta": { "name": "TitleLabel" }, "position": { "height": 35, "width": "100%" }, "props": { "style": { "backgroundColor": "#1A4A5E", "borderColor": "#000", "borderStyle": "solid", "borderWidth": "1px", "color": "#FFF", "fontSize": "20px", "fontWeight": "bold", "paddingLeft": 5 }, "text": "Heatmap Options" }, "type": "ia.display.label" }, { "meta": { "name": "ShiftLabel" }, "position": { "height": 25, "width": "33%", "y": 40 }, "props": { "style": { "backgroundColor": "#F0F0F0", "borderStyle": "solid", "fontWeight": "bold", "textAlign": "center" }, "text": "Filter By Time \u0026 Shifts" }, "type": "ia.display.label" }, { "meta": { "name": "TimeStartLabel" }, "position": { "height": 30, "width": 75, "y": 65 }, "props": { "style": { "backgroundColor": "#F0F0F0", "borderLeftStyle": "solid", "borderRightStyle": "solid", "fontWeight": "bold", "textAlign": "right" }, "text": "Start:" }, "type": "ia.display.label" }, { "events": { "component": { "onActionPerformed": { "config": { "script": "\tstartDate \u003d self.props\n\tendDate \u003d self.getSibling(\"TimeEndInput\").props\n\thours \u003d system.date.hoursBetween(startDate.value, endDate.value)\n\tif hours \u003e 30*24:\n\t\t# Cap endDate to within 30 days of startDate:\n\t\tendDate.value \u003d system.date.addDays(startDate.value, 30)" }, "scope": "G", "type": "script" } } }, "meta": { "name": "TimeStartInput" }, "position": { "height": 30, "width": "calc(33% - 75px)", "x": 75, "y": 65 }, "props": { "format": "YYYY-MM-DD HH:mm:ss", "formattedValue": "2021-02-13 00:00:00", "formattedValues": { "date": "2021-02-13", "datetime": "2021-02-13 00:00:00", "time": "00:00:00" }, "value": { "$": [ "ts", 192, 1613256769474 ], "$ts": 1613192400000 } }, "type": "ia.input.date-time-input" }, { "meta": { "name": "TimeEndLabel" }, "position": { "height": 30, "width": 75, "y": 95 }, "props": { "style": { "backgroundColor": "#F0F0F0", "borderLeftStyle": "solid", "borderRightStyle": "solid", "fontWeight": "bold", "textAlign": "right" }, "text": "End:" }, "type": "ia.display.label" }, { "events": { "component": { "onActionPerformed": { "config": { "script": "\tstartDate \u003d self.getSibling(\"TimeStartInput\").props\n\tendDate \u003d self.props\n\thours \u003d system.date.hoursBetween(startDate.value, endDate.value)\n\tif hours \u003e 30*24:\n\t\t# Cap startDate to within 30 days of endDate:\n\t\tstartDate.value \u003d system.date.addDays(endDate.value, -30)" }, "scope": "G", "type": "script" } } }, "meta": { "name": "TimeEndInput" }, "position": { "height": 30, "width": "calc(33% - 75px)", "x": 75, "y": 95 }, "props": { "format": "YYYY-MM-DD HH:mm:ss", "formattedValue": "2021-02-14 00:00:00", "formattedValues": { "date": "2021-02-14", "datetime": "2021-02-14 00:00:00", "time": "00:00:00" }, "value": { "$": [ "ts", 192, 1613256769474 ], "$ts": 1613278800000 } }, "type": "ia.input.date-time-input" }, { "meta": { "name": "AlarmTypeTable" }, "position": { "height": "calc(100% - 145px)", "width": "33%", "x": 251.25, "y": 90 }, "propConfig": { "props.cells.style.backgroundColor": { "binding": { "config": { "path": "../AlarmTypeAll.props.selected" }, "transforms": [ { "fallback": null, "inputType": "scalar", "mappings": [ { "input": true, "output": "#888" } ], "outputType": "scalar", "type": "map" } ], "type": "property" } }, "props.data": { "binding": { "config": { "queryPath": "Alarms/HeatmapUniqueAlarms" }, "type": "query" } }, "props.selection.enableRowSelection": { "binding": { "config": { "expression": "!{../AlarmTypeAll.props.selected}" }, "type": "expr" } } }, "props": { "enableHeader": false, "pager": { "bottom": false, "initialOption": 5 }, "selection": { "mode": "multiple interval" } }, "type": "ia.display.table" }, { "meta": { "name": "AlarmTypeLabel" }, "position": { "height": 25, "width": "33%", "x": 251.25, "y": 40 }, "props": { "style": { "backgroundColor": "#F0F0F0", "borderStyle": "solid", "fontWeight": "bold", "textAlign": "center" }, "text": "Filter By Alarm Type" }, "type": "ia.display.label" }, { "meta": { "name": "AlarmTypeAll" }, "position": { "height": 25, "width": "33%", "x": 251.25, "y": 65 }, "props": { "selected": true, "style": { "backgroundColor": "#F0F0F0", "borderBottomStyle": "solid", "borderLeftStyle": "solid", "borderRightStyle": "solid" }, "text": "Select All" }, "type": "ia.input.checkbox" }, { "meta": { "name": "ShiftAll" }, "position": { "height": 25, "width": "33%", "y": 125 }, "props": { "selected": true, "style": { "backgroundColor": "#F0F0F0", "borderStyle": "solid" }, "text": "No Shift Filter (24/7)" }, "type": "ia.input.checkbox" }, { "meta": { "name": "ShiftTable" }, "position": { "height": "calc(100% - 205px)", "width": "33%", "y": 150 }, "propConfig": { "props.cells.style.backgroundColor": { "binding": { "config": { "path": "../ShiftAll.props.selected" }, "transforms": [ { "fallback": null, "inputType": "scalar", "mappings": [ { "input": true, "output": "#888" } ], "outputType": "scalar", "type": "map" } ], "type": "property" } }, "props.data": { "binding": { "config": { "fallbackDelay": 2.5, "mode": "direct", "tagPath": "[default]Gateway/Shifts" }, "transforms": [ { "code": "\tdata \u003d [[value.getValueAt(r, \"Name\")] for r in range(value.getRowCount()) if value.getValueAt(r, \"Enabled\")]\n\tdata \u003d [r for r in data if r[0] !\u003d \"\"]\n\treturn system.dataset.toDataSet([\"description\"], data)", "type": "script" } ], "type": "tag" } }, "props.selection.enableRowSelection": { "binding": { "config": { "expression": "!{../ShiftAll.props.selected}" }, "type": "expr" } } }, "props": { "enableHeader": false, "pager": { "bottom": false, "initialOption": 5 }, "selection": { "mode": "multiple interval" } }, "type": "ia.display.table" }, { "meta": { "name": "DeviceTypeLabel" }, "position": { "height": 25, "width": "33%", "x": 502.5, "y": 40 }, "props": { "style": { "backgroundColor": "#F0F0F0", "borderStyle": "solid", "fontWeight": "bold", "textAlign": "center" }, "text": "Filter By Device Type" }, "type": "ia.display.label" }, { "meta": { "name": "DeviceTypeAll" }, "position": { "height": 25, "width": "33%", "x": 502.5, "y": 65 }, "props": { "selected": true, "style": { "backgroundColor": "#F0F0F0", "borderBottomStyle": "solid", "borderLeftStyle": "solid", "borderRightStyle": "solid" }, "text": "Select All" }, "type": "ia.input.checkbox" }, { "meta": { "name": "DeviceTypeTable" }, "position": { "height": "calc(100% - 145px)", "width": "33%", "x": 502.5, "y": 90 }, "propConfig": { "props.cells.style.backgroundColor": { "binding": { "config": { "path": "../DeviceTypeAll.props.selected" }, "transforms": [ { "fallback": null, "inputType": "scalar", "mappings": [ { "input": true, "output": "#888" } ], "outputType": "scalar", "type": "map" } ], "type": "property" } }, "props.selection.enableRowSelection": { "binding": { "config": { "expression": "!{../DeviceTypeAll.props.selected}" }, "type": "expr" } } }, "props": { "columns": [ { "align": "center", "boolean": "checkbox", "dateFormat": "MM/DD/YYYY", "editable": false, "field": "", "footer": { "align": "center", "justify": "left", "style": { "classes": "" }, "title": "" }, "header": { "align": "center", "justify": "left", "style": { "classes": "" }, "title": "" }, "justify": "auto", "number": "value", "numberFormat": "0,0.##", "progressBar": { "bar": { "color": "", "style": { "classes": "" } }, "max": 100, "min": 0, "track": { "color": "", "style": { "classes": "" } }, "value": { "enabled": true, "format": "0,0.##", "justify": "center", "style": { "classes": "" } } }, "render": "auto", "resizable": true, "sort": "none", "sortable": true, "strictWidth": false, "style": { "classes": "" }, "toggleSwitch": { "color": { "selected": "", "unselected": "" } }, "viewParams": {}, "viewPath": "", "visible": true, "width": "" } ], "data": { "$": [ "ds", 192, 1597260961733 ], "$columns": [ { "data": [ "Beacons", "EIPs", "Encoders", "Estops", "Limit Switches", "MCPs", "Photo Eyes", "Push Buttons", "Scanners", "VFDs", "Solenoids", "Conveyors" ], "name": "description", "type": "String" }, { "data": [ "beacon", "eip", "encoder", "estop", "limitswitch", "mcp", "photoeye", "pushbutton", "scanner", "vfd", "solenoid", "conveyor" ], "name": "type", "type": "String" } ] }, "enableHeader": false, "pager": { "bottom": false, "initialOption": 5 }, "selection": { "mode": "multiple interval" } }, "type": "ia.display.table" }, { "events": { "component": { "onActionPerformed": { "config": { "script": "\timport re\n\timport time\n\t\n\t# Gather Shifts:\n\tshifts \u003d []\n\tshiftsQuery \u003d []\n\tshiftsText \u003d \"None\"\n\tif not self.getSibling(\"ShiftAll\").props.selected:\n\t\t# Retrieve shift table:\n\t\tshiftTable \u003d utils.datasetToJSON(system.tag.readBlocking([\"[default]Gateway/Shifts\"])[0].value)\n\t\tshiftTable \u003d {row[\"Name\"]: row for row in shiftTable}\n\t\t# Grab selection:\n\t\tselection \u003d self.getSibling(\"ShiftTable\").props.selection.data\n\t\tshifts \u003d [dat[\"description\"] for dat in selection]\n\t\tshiftsText \u003d \", \".join(shifts)\n\t\t# Generate shift query:\n\t\tfor shift in selection:\n\t\t\tif shift[\"description\"] in shiftTable:\n\t\t\t\tshift \u003d shiftTable[shift[\"description\"]]\n\t\t\t\tline \u003d []\n\t\t\t\t# Add day filter:\n\t\t\t\tdays \u003d [str(i+1) for i, day in enumerate([\"isSunday\", \"isMonday\", \"isTuesday\", \"isWednesday\", \"isThursday\", \"isFriday\", \"isSaturday\"]) if shift[day]]\n\t\t\t\t# If no days, ignore this shift:\n\t\t\t\tif len(days) \u003c\u003d 0:\n\t\t\t\t\tshiftsQuery.append(\"false\")\n\t\t\t\t\tcontinue\n\t\t\t\t# If not all days are present, then add day filter:\n\t\t\t\tif len(days) \u003c 7:\n\t\t\t\t\tline.append(\"DAYOFWEEK(eventtime) IN (\"+\",\".join(days)+\")\")\n\t\t\t\t# If time range is incompatible (starttime \u003e\u003d endtime), then remove shift:\n\t\t\t\t# TODO: Handle time ranges that go across midnight\n\t\t\t\tif shift[\"StartHour\"]*60+shift[\"StartMinute\"] \u003e\u003d shift[\"EndHour\"]*60+shift[\"EndMinute\"]:\n\t\t\t\t\tshiftsQuery.append(\"false\")\n\t\t\t\t\tcontinue\n\t\t\t\t# Add time range:\n\t\t\t\tline.append(\"TIME(eventtime) BETWEEN \\\"{:02d}:{:02d}:00\\\" AND \\\"{:02d}:{:02d}:00\\\"\".format(shift[\"StartHour\"], shift[\"StartMinute\"], shift[\"EndHour\"], shift[\"EndMinute\"]))\n\t\t\t\t# If nothing was added to line, then treat shift as 24/7, which then we can ignore all shifts:\n\t\t\t\tif len(line) \u003c\u003d 0:\n\t\t\t\t\tshiftsQuery \u003d []\n\t\t\t\t\tbreak\n\t\t\t\t# Add shift to shifts:\n\t\t\t\tshiftsQuery.append(\" AND \".join(line))\n\t# Gather Alarms:\n\talarms \u003d []\n\talarmsText \u003d \"All\"\n\tif not self.getSibling(\"AlarmTypeAll\").props.selected:\n\t\talarms \u003d self.getSibling(\"AlarmTypeTable\").props.selection.data\n\t\talarms \u003d [dat[\"description\"] for dat in alarms]\n\t\talarmsText \u003d \", \".join(alarms)\n\tif len(alarms) \u003d\u003d 0:\n\t\talarms \u003d self.getSibling(\"AlarmTypeTable\").props.data\n\t\talarms \u003d [alarms.getValueAt(r, \"description\") for r in range(alarms.getRowCount())]\n\t# Gather Devices:\n\tdevices \u003d []\n\tdeviceTypes \u003d []\n\tdevicesText \u003d \"All\"\n\tif self.getSibling(\"DeviceTypeAll\").props.selected:\n\t\tdataset \u003d self.getSibling(\"DeviceTypeTable\").props.data\n\t\tdevices \u003d [dataset.getValueAt(row, \"description\") for row in range(dataset.getRowCount())]\n\t\tdeviceTypes \u003d [dataset.getValueAt(row, \"type\") for row in range(dataset.getRowCount())]\n\telse:\n\t\tdataset \u003d self.getSibling(\"DeviceTypeTable\").props.data\n\t\tdevices \u003d [dat[\"description\"] for dat in self.getSibling(\"DeviceTypeTable\").props.selection.data]\n\t\tdeviceTypes \u003d [dataset.getValueAt(row, \"type\") for row in range(dataset.getRowCount()) if dataset.getValueAt(row, \"description\") in devices]\n\t\tdevicesText \u003d \", \".join(devices)\n\tdevices \u003d [dat.replace(\" \", \"\") for dat in devices]\n\tif len(devices) \u003d\u003d 0:\n\t\tdevicesText \u003d \"None\"\n\t\n\t# Run report:\n\t# Sanitize:\n\talarmQuery \u003d (\u0027\"\u0027+alarm.replace(\"\\\\\", \"\\\\\\\\\").replace(\u0027\"\u0027, \u0027\\\\\"\u0027)+\u0027\"\u0027 for alarm in alarms)\n\tshiftsQuery \u003d [\"(\"+shift+\")\" for shift in shiftsQuery]\n\tparams \u003d {\n\t\t\"startDate\": self.getSibling(\"TimeStartInput\").props.formattedValue,\n\t\t\"endDate\": self.getSibling(\"TimeEndInput\").props.formattedValue,\n\t\t\"alarms\": \",\".join(alarmQuery),\n\t\t\"shifts\": \" \" if len(shiftsQuery) \u003d\u003d 0 else \"AND (\"+\" OR \".join(shiftsQuery)+\")\",\n\t\t\"devices\": \u0027\"\"\u0027 if len(deviceTypes) \u003d\u003d 0 else \",\".join([\u0027\"\u0027+device+\u0027\"\u0027 for device in deviceTypes])\n\t}\n\t#system.perspective.print(params)\n\tdata \u003d system.db.runNamedQuery(\"Alarms/Heatmap\", params)\n\t\n\t# Apply and activate heatmap:\n\tself.session.custom.heatmapSettings \u003d {\n\t\t\"enabled\": True,\n\t\t\"time\": {\n\t\t\t\"start\": params[\"startDate\"],\n\t\t\t\"end\": params[\"endDate\"]\n\t\t},\n\t\t\"shifts\": shifts,\n\t\t\"alarms\": alarms,\n\t\t\"devices\": devices,\n\t\t\"shiftsText\": shiftsText,\n\t\t\"alarmsText\": alarmsText,\n\t\t\"devicesText\": devicesText,\n\t\t\"data\": data\n\t}\n\t\n\t# Close popup:\n\tif self.session.custom.isMobile:\n\t\tsystem.perspective.navigate(page\u003d\"/\")\n\telse:\n\t\tsystem.perspective.closePopup(\"\")\n\t\t# Force client to graphics screens only:\n\t\tif self.page.props.primaryView !\u003d \"Windows/GraphicsWrapper\":\n\t\t\tsystem.perspective.navigate(page\u003d\"/\")" }, "scope": "G", "type": "script" } } }, "meta": { "name": "RunButton" }, "position": { "height": 50, "width": "100%", "y": "calc(100% - 50px)" }, "propConfig": { "props.enabled": { "binding": { "config": { "expression": " {../TimeStartInput.props.value} !\u003d null\r\n\u0026\u0026{../TimeEndInput.props.value} !\u003d null\r\n\u0026\u0026({../ShiftAll.props.selected}||{../ShiftTable.props.selection.selectedRow}!\u003dnull)\r\n\u0026\u0026({../AlarmTypeAll.props.selected}||{../AlarmTypeTable.props.selection.selectedRow}!\u003dnull)" }, "type": "expr" } } }, "props": { "style": { "classes": "Buttons/Grey" }, "text": "Run heatmap" }, "type": "ia.input.button" } ], "events": { "system": { "onStartup": { "config": { "script": "\tnow \u003d system.date.now()\n\tstart \u003d system.date.midnight(now)\n\tend \u003d system.date.addDays(start, 1)\n\tself.getChild(\"TimeStartInput\").props.value \u003d start\n\tself.getChild(\"TimeEndInput\").props.value \u003d end" }, "scope": "G", "type": "script" } } }, "meta": { "name": "root" }, "props": { "style": { "classes": "window", "minWidth": "750px" } }, "type": "ia.container.coord" } }