UNPKG

node-red-contrib-datacake-helpers

Version:

Node-RED nodes to interact with the Datacake GraphQL API for KPIs and device stats. Includes other helper nodes for device management.

302 lines (272 loc) 13.1 kB
<script type="text/javascript"> RED.nodes.registerType('datacakegraphql-product-stats', { category: 'Datacake', color: '#3FADB5', defaults: { name: { value: "" }, datacakeConfig: { value: "", type: "datacakegraphql-config", required: true }, productId: { value: "", required: true }, selectedFields: { value: [] }, availableProducts: { value: [] }, availableFields: { value: [] }, searchTags: { value: [] }, searchTagsAnyAll: { value: "any" }, availableTags: { value: [] } }, credentials: {}, inputs: 1, outputs: 1, icon: "icons/datacake.svg", label: function() { return this.name || "Datacake Product Stats"; }, paletteLabel: "Product Stats", oneditprepare: function() { const node = this; const productSelect = $('#node-input-productId'); const fieldsContainer = $('#selected-fields-container'); const tagsContainer = $('#selected-tags-container'); const anyAllSelect = $('#node-input-searchTagsAnyAll'); // Set selected tags matching logic if (node.searchTagsAnyAll) { anyAllSelect.val(node.searchTagsAnyAll); } // Function to populate the product select dropdown function populateProducts(products) { productSelect.empty(); productSelect.append('<option value="">Select a product</option>'); if (products && products.data && products.data.allProducts) { node.availableProducts = products.data.allProducts; products.data.allProducts.forEach(product => { productSelect.append(`<option value="${product.id}">${product.name}</option>`); }); // Set previously selected product if (node.productId) { productSelect.val(node.productId); // Populate fields for the selected product populateFields(node.productId); } } } // Function to populate available tags function populateTags(tagsData) { tagsContainer.empty(); if (tagsData && tagsData.data && tagsData.data.workspace && tagsData.data.workspace.allTags) { node.availableTags = tagsData.data.workspace.allTags; if (node.availableTags.length === 0) { tagsContainer.append('<p>No tags available in this workspace.</p>'); return; } // Create checkboxes for each tag let tagsHTML = '<div class="form-row tags-list">'; node.availableTags.forEach(tag => { const isSelected = node.searchTags && node.searchTags.includes(tag); tagsHTML += ` <div class="form-row tag-row"> <label> <input type="checkbox" class="tag-checkbox" data-tag="${tag}" ${isSelected ? 'checked' : ''}> ${tag} </label> </div> `; }); tagsHTML += '</div>'; tagsContainer.append(tagsHTML); } else { tagsContainer.append('<p>No tags found or error fetching tags.</p>'); } } // Find product by ID function findProductById(productId) { if (!node.availableProducts) return null; return node.availableProducts.find(p => p.id === productId); } // Function to populate fields for the selected product function populateFields(productId) { const product = findProductById(productId); if (!product || !product.measurementFields) return; node.availableFields = product.measurementFields; // Clear current fields display fieldsContainer.empty(); if (product.measurementFields.length === 0) { fieldsContainer.append('<p>No measurement fields available for this product.</p>'); return; } // Create checkboxes for each field let fieldsHTML = '<div class="form-row fields-list">'; product.measurementFields.forEach(field => { const isSelected = node.selectedFields && node.selectedFields.some(f => f.id === field.id); fieldsHTML += ` <div class="form-row field-row"> <label> <input type="checkbox" class="field-checkbox" data-field-id="${field.id}" data-field-name="${field.fieldName}" data-field-type="${field.fieldType}" ${isSelected ? 'checked' : ''}> ${field.fieldName} (${field.fieldType}) </label> </div> `; }); fieldsHTML += '</div>'; fieldsContainer.append(fieldsHTML); } // Handle config node selection change $('#node-input-datacakeConfig').on('change', function() { const configNodeId = $(this).val(); if (configNodeId) { $('#product-loading').show(); $('#tags-loading').show(); // Fetch products from the selected config $.getJSON('datacakegraphql/products', { configId: configNodeId }) .done(function(products) { populateProducts(products); $('#product-loading').hide(); }) .fail(function(err) { console.error('Error fetching products:', err); RED.notify(`Error fetching products: ${err.responseJSON ? err.responseJSON.error : err.statusText}`, "error"); $('#product-loading').hide(); }); // Fetch tags from the selected config $.getJSON('datacakegraphql/tags', { configId: configNodeId }) .done(function(tags) { populateTags(tags); $('#tags-loading').hide(); }) .fail(function(err) { console.error('Error fetching tags:', err); RED.notify(`Error fetching tags: ${err.responseJSON ? err.responseJSON.error : err.statusText}`, "error"); $('#tags-loading').hide(); }); } else { productSelect.empty(); productSelect.append('<option value="">Select a config first</option>'); fieldsContainer.empty(); tagsContainer.empty(); } }); // Handle product selection change productSelect.on('change', function() { const productId = $(this).val(); if (productId) { populateFields(productId); } else { fieldsContainer.empty(); } }); // Trigger config node change if already selected if (this.datacakeConfig) { $('#node-input-datacakeConfig').trigger('change'); } }, oneditsave: function() { this.productId = $('#node-input-productId').val(); this.searchTagsAnyAll = $('#node-input-searchTagsAnyAll').val(); // Get selected fields const selectedFields = []; $('.field-checkbox:checked').each(function() { const $checkbox = $(this); selectedFields.push({ id: $checkbox.data('field-id'), fieldName: $checkbox.data('field-name'), fieldType: $checkbox.data('field-type') }); }); this.selectedFields = selectedFields; // Get selected tags const selectedTags = []; $('.tag-checkbox:checked').each(function() { const $checkbox = $(this); selectedTags.push($checkbox.data('tag')); }); this.searchTags = selectedTags; } }); </script> <script type="text/html" data-template-name="datacakegraphql-product-stats"> <div class="form-row"> <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <input type="text" id="node-input-name" placeholder="Name"> </div> <div class="form-row"> <label for="node-input-datacakeConfig"><i class="fa fa-cog"></i> Config</label> <input type="text" id="node-input-datacakeConfig"> </div> <div class="form-row"> <label for="node-input-productId"><i class="fa fa-cubes"></i> Product</label> <select type="text" id="node-input-productId"> <option value="">Select a config first</option> </select> <span id="product-loading" style="margin-left: 10px; display: none;"> <i class="fa fa-spinner fa-spin"></i> Loading products... </span> </div> <div class="form-row"> <label><i class="fa fa-list"></i> Fields</label> <div id="selected-fields-container" class="form-tips form-tips-auto-width"> <p>Select a product to view available fields</p> </div> </div> <div class="form-row"> <label for="node-input-searchTagsAnyAll"><i class="fa fa-filter"></i> Tags Match</label> <select id="node-input-searchTagsAnyAll"> <option value="any">Match ANY tag</option> <option value="all">Match ALL tags</option> </select> </div> <div class="form-row"> <label><i class="fa fa-tags"></i> Filter Tags</label> <div id="selected-tags-container" class="form-tips form-tips-auto-width"> <p>Select a config to view available tags</p> </div> <span id="tags-loading" style="margin-left: 10px; display: none;"> <i class="fa fa-spinner fa-spin"></i> Loading tags... </span> </div> </script> <script type="text/html" data-help-name="datacakegraphql-product-stats"> <p>Node that calculates statistics for selected fields across all devices in a Datacake product.</p> <h3>Inputs</h3> <dl class="message-properties"> <dt>payload <span class="property-type">any</span></dt> <dd>Any input message will trigger a query to fetch the latest device data and calculate statistics.</dd> </dl> <h3>Outputs</h3> <dl class="message-properties"> <dt>payload <span class="property-type">object</span></dt> <dd>An object containing statistics for each selected field.</dd> </dl> <h3>Details</h3> <p>This node calculates min, max, and average values for selected fields across all devices in a Datacake product.</p> <p>You can filter devices by tags to include only devices that have specific tags.</p> <p>For each field, the node provides:</p> <ul> <li>Minimum value and the device with that value</li> <li>Maximum value and the device with that value</li> <li>Average value across all devices</li> <li>Count of devices with valid values for the field</li> </ul> <p>Example output:</p> <pre>{ "SOIL_MOISTURE": { "min": 23, "max": 51, "avg": 37, "count": 2, "minDevice": { "id": "c9617e90-296d-47a8-900e-27cb68da59e9", "name": "SFM Satinksplas" }, "maxDevice": { "id": "1456574d-b4c9-47fc-bcc5-03cb763c6b33", "name": "SFM GKC" } } }</pre> </script>