@meshmakers/octo-meshboard 3.5.520 → 3.5.530
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -4350,6 +4350,29 @@ class QueryExecutorService {
|
|
|
4350
4350
|
};
|
|
4351
4351
|
}));
|
|
4352
4352
|
}
|
|
4353
|
+
/**
|
|
4354
|
+
* Column metadata of a persisted stream-data query for the widget config pickers.
|
|
4355
|
+
* Runs the query with `first: 1` and its own window first; a query that cannot run
|
|
4356
|
+
* without a window — an `Integral` column over a raw archive requires from/to
|
|
4357
|
+
* (AB#7921) — fails or returns no columns, so it is run once more with `window`
|
|
4358
|
+
* (the board's time window or a recent default). The error of the retry propagates.
|
|
4359
|
+
*/
|
|
4360
|
+
async fetchStreamDataColumns(queryRtId, window) {
|
|
4361
|
+
try {
|
|
4362
|
+
const result = await firstValueFrom(this.executeStreamData(queryRtId, { first: 1 }));
|
|
4363
|
+
if (result.columns.length > 0) {
|
|
4364
|
+
return result.columns;
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4367
|
+
catch {
|
|
4368
|
+
// Fall through to the windowed retry below.
|
|
4369
|
+
}
|
|
4370
|
+
const windowed = await firstValueFrom(this.executeStreamData(queryRtId, {
|
|
4371
|
+
first: 1,
|
|
4372
|
+
streamDataArgs: { from: window.from, to: window.to }
|
|
4373
|
+
}));
|
|
4374
|
+
return windowed.columns;
|
|
4375
|
+
}
|
|
4353
4376
|
/**
|
|
4354
4377
|
* Resolution-aware series routing (AB#4290): asks the backend which archive/rollup to
|
|
4355
4378
|
* query for a base archive family, time window and target point count — so a widget can
|
|
@@ -6422,14 +6445,20 @@ const SUPPORTED_ROW_TYPES$1 = [
|
|
|
6422
6445
|
function sanitizeFieldName(fieldName) {
|
|
6423
6446
|
return fieldName.replace(/\./g, '_');
|
|
6424
6447
|
}
|
|
6448
|
+
/**
|
|
6449
|
+
* Trailing aggregation function suffix the engine appends to a wire-form column:
|
|
6450
|
+
* `_count`, `_sum`, `_avg`, `_min`, `_max`, and `_integral` for the stream-data
|
|
6451
|
+
* Integral (energy over the query window, AB#7921).
|
|
6452
|
+
*/
|
|
6453
|
+
const AGGREGATION_FUNCTION_SUFFIX = /_(?:count|sum|avg|min|max|integral)$/i;
|
|
6425
6454
|
/**
|
|
6426
6455
|
* Strips a trailing aggregation function suffix from a path so the base path can
|
|
6427
6456
|
* be compared independently of which aggregation produced it. The engine emits
|
|
6428
|
-
* cell paths like `meterreading_count` / `amountvalue_sum`
|
|
6429
|
-
* grouped aggregation, and stream-data variants.
|
|
6457
|
+
* cell paths like `meterreading_count` / `amountvalue_sum` / `importpower_integral`
|
|
6458
|
+
* for RT aggregation, grouped aggregation, and stream-data variants.
|
|
6430
6459
|
*/
|
|
6431
6460
|
function stripAggregationFunctionSuffix(path) {
|
|
6432
|
-
return path.replace(
|
|
6461
|
+
return path.replace(AGGREGATION_FUNCTION_SUFFIX, '');
|
|
6433
6462
|
}
|
|
6434
6463
|
/**
|
|
6435
6464
|
* Canonical key form: drop dot and underscore separators and lowercase the rest.
|
|
@@ -6443,11 +6472,11 @@ function toCanonicalAttributeKey(s) {
|
|
|
6443
6472
|
}
|
|
6444
6473
|
/**
|
|
6445
6474
|
* Detects whether a path ends with the engine's aggregation function suffix
|
|
6446
|
-
* (`_count`, `_sum`, `_avg`, `_min`, `_max`). Used to decide whether the
|
|
6447
|
-
* back-compat fallback in {@link matchesAttributePath} should fire.
|
|
6475
|
+
* (`_count`, `_sum`, `_avg`, `_min`, `_max`, `_integral`). Used to decide whether the
|
|
6476
|
+
* loose back-compat fallback in {@link matchesAttributePath} should fire.
|
|
6448
6477
|
*/
|
|
6449
6478
|
function hasAggregationFunctionSuffix(path) {
|
|
6450
|
-
return
|
|
6479
|
+
return AGGREGATION_FUNCTION_SUFFIX.test(path);
|
|
6451
6480
|
}
|
|
6452
6481
|
/**
|
|
6453
6482
|
* Returns true when a cell's `attributePath` refers to the same source attribute
|
|
@@ -6701,10 +6730,10 @@ function processPieChartData(rows, categoryField, valueField) {
|
|
|
6701
6730
|
/**
|
|
6702
6731
|
* Applies an optional scale factor to a query value (KPI `valueMultiplier`).
|
|
6703
6732
|
* Only numeric values are scaled — strings (e.g. enum labels or error
|
|
6704
|
-
* placeholders) pass through unchanged, as does everything
|
|
6705
|
-
* is configured. Use case:
|
|
6706
|
-
* (
|
|
6707
|
-
*
|
|
6733
|
+
* placeholders) and `null` (no data) pass through unchanged, as does everything
|
|
6734
|
+
* when no multiplier is configured. Use case: the unit factor of an energy KPI
|
|
6735
|
+
* over a stream-data Integral column (value × hours): 1 for kW → kWh, 0.001 for
|
|
6736
|
+
* W → kWh (AB#7921).
|
|
6708
6737
|
*/
|
|
6709
6738
|
function applyValueMultiplier(value, multiplier) {
|
|
6710
6739
|
if (typeof multiplier !== 'number' || typeof value !== 'number') {
|
|
@@ -7184,8 +7213,8 @@ class KpiWidgetComponent {
|
|
|
7184
7213
|
value = this.extractGroupedAggregationValue(result);
|
|
7185
7214
|
break;
|
|
7186
7215
|
}
|
|
7187
|
-
// Optional
|
|
7188
|
-
// Applied to numeric values only; strings pass through unchanged.
|
|
7216
|
+
// Optional unit factor (e.g. 1 for an Integral over kW → kWh, 0.001 for W → kWh).
|
|
7217
|
+
// Applied to numeric values only; strings and null (no data) pass through unchanged.
|
|
7189
7218
|
value = applyValueMultiplier(value, this.config.valueMultiplier);
|
|
7190
7219
|
// Create a synthetic entity with the value
|
|
7191
7220
|
const kpiEntity = {
|
|
@@ -7254,10 +7283,12 @@ class KpiWidgetComponent {
|
|
|
7254
7283
|
/**
|
|
7255
7284
|
* Extracts a cell value, preserving strings and converting numbers.
|
|
7256
7285
|
* Returns the value as-is if it's a string, or parses it as a number.
|
|
7286
|
+
* A null cell is "no data" (e.g. an Integral over an empty window, AB#7921) and
|
|
7287
|
+
* stays null, so the KPI shows its empty state instead of a misleading 0.
|
|
7257
7288
|
*/
|
|
7258
7289
|
extractCellValue(value) {
|
|
7259
7290
|
if (value === null || value === undefined)
|
|
7260
|
-
return
|
|
7291
|
+
return null;
|
|
7261
7292
|
if (typeof value === 'number')
|
|
7262
7293
|
return value;
|
|
7263
7294
|
if (typeof value === 'string') {
|
|
@@ -8082,14 +8113,24 @@ class KpiConfigDialogComponent {
|
|
|
8082
8113
|
aggregationType: c.aggregationType ?? null
|
|
8083
8114
|
}));
|
|
8084
8115
|
}
|
|
8085
|
-
// Stream-data: execute with a tiny page just to surface columns.
|
|
8086
|
-
|
|
8087
|
-
|
|
8116
|
+
// Stream-data: execute with a tiny page just to surface columns. An Integral
|
|
8117
|
+
// column needs from/to (AB#7921), so the executor retries with the board's
|
|
8118
|
+
// window — or the last 24 hours when the board has no time filter.
|
|
8119
|
+
const columns = await this.queryExecutor.fetchStreamDataColumns(rtId, this.columnProbeWindow());
|
|
8120
|
+
return columns.map(c => ({
|
|
8088
8121
|
attributePath: c.attributePath,
|
|
8089
8122
|
attributeValueType: c.attributeValueType ?? '',
|
|
8090
8123
|
aggregationType: c.aggregationType ?? null
|
|
8091
8124
|
}));
|
|
8092
8125
|
}
|
|
8126
|
+
columnProbeWindow() {
|
|
8127
|
+
const boardWindow = this.meshBoardStateService.resolveStreamDataTimeArgs();
|
|
8128
|
+
if (boardWindow) {
|
|
8129
|
+
return boardWindow;
|
|
8130
|
+
}
|
|
8131
|
+
const to = new Date();
|
|
8132
|
+
return { from: new Date(to.getTime() - 24 * 60 * 60 * 1000), to };
|
|
8133
|
+
}
|
|
8093
8134
|
async onCategoryFieldChange(categoryField) {
|
|
8094
8135
|
this.form.queryCategoryField = categoryField;
|
|
8095
8136
|
this.form.queryCategoryValue = '';
|