@meshmakers/octo-meshboard 3.4.290 → 3.4.310
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.
|
@@ -2884,6 +2884,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
2884
2884
|
}]
|
|
2885
2885
|
}], ctorParameters: () => [{ type: i1.Apollo }] });
|
|
2886
2886
|
|
|
2887
|
+
const ResolveSeriesQueryDocumentDto = gql `
|
|
2888
|
+
query resolveSeriesQuery($input: ResolveSeriesQueryInput!) {
|
|
2889
|
+
streamData {
|
|
2890
|
+
resolveSeriesQuery(input: $input) {
|
|
2891
|
+
archiveRtId
|
|
2892
|
+
effectiveBucketMs
|
|
2893
|
+
points
|
|
2894
|
+
reducingFunction
|
|
2895
|
+
signal
|
|
2896
|
+
actualPoints
|
|
2897
|
+
diagnostic
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
`;
|
|
2902
|
+
class ResolveSeriesQueryDtoGQL extends i1.Query {
|
|
2903
|
+
document = ResolveSeriesQueryDocumentDto;
|
|
2904
|
+
constructor(apollo) {
|
|
2905
|
+
super(apollo);
|
|
2906
|
+
}
|
|
2907
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ResolveSeriesQueryDtoGQL, deps: [{ token: i1.Apollo }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2908
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ResolveSeriesQueryDtoGQL, providedIn: 'root' });
|
|
2909
|
+
}
|
|
2910
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ResolveSeriesQueryDtoGQL, decorators: [{
|
|
2911
|
+
type: Injectable,
|
|
2912
|
+
args: [{
|
|
2913
|
+
providedIn: 'root'
|
|
2914
|
+
}]
|
|
2915
|
+
}], ctorParameters: () => [{ type: i1.Apollo }] });
|
|
2916
|
+
|
|
2887
2917
|
const EMPTY_RESULT_BASE = Object.freeze({
|
|
2888
2918
|
queryRtId: null,
|
|
2889
2919
|
associatedCkTypeId: null,
|
|
@@ -2905,6 +2935,7 @@ class QueryExecutorService {
|
|
|
2905
2935
|
runtimeGql = inject(ExecuteRuntimeQueryDtoGQL);
|
|
2906
2936
|
streamDataGql = inject(ExecuteStreamDataQueryDtoGQL);
|
|
2907
2937
|
persistentQueriesGql = inject(GetSystemPersistentQueriesDtoGQL);
|
|
2938
|
+
resolveSeriesGql = inject(ResolveSeriesQueryDtoGQL);
|
|
2908
2939
|
/**
|
|
2909
2940
|
* Cache of resolved query families, keyed by query rtId. Filled lazily for
|
|
2910
2941
|
* legacy widget configs that pre-date the `queryFamily` field — saves a
|
|
@@ -3006,6 +3037,29 @@ class QueryExecutorService {
|
|
|
3006
3037
|
};
|
|
3007
3038
|
}));
|
|
3008
3039
|
}
|
|
3040
|
+
/**
|
|
3041
|
+
* Resolution-aware series routing (AB#4290): asks the backend which archive/rollup to
|
|
3042
|
+
* query for a base archive family, time window and target point count — so a widget can
|
|
3043
|
+
* render ~`targetPoints` points without knowing which physical archive holds the data at a
|
|
3044
|
+
* usable grain. The caller then runs {@link executeStreamData} (downsampling) against the
|
|
3045
|
+
* returned `archiveRtId` with `limit = points`. Returns null when StreamData is not enabled.
|
|
3046
|
+
*/
|
|
3047
|
+
async resolveSeriesQuery(input) {
|
|
3048
|
+
const result = await firstValueFrom(this.resolveSeriesGql.fetch({ variables: { input }, fetchPolicy: 'network-only' }));
|
|
3049
|
+
const decision = result.data?.streamData?.resolveSeriesQuery;
|
|
3050
|
+
if (!decision) {
|
|
3051
|
+
return null;
|
|
3052
|
+
}
|
|
3053
|
+
return {
|
|
3054
|
+
archiveRtId: String(decision.archiveRtId),
|
|
3055
|
+
effectiveBucketMs: Number(decision.effectiveBucketMs),
|
|
3056
|
+
points: decision.points,
|
|
3057
|
+
reducingFunction: decision.reducingFunction,
|
|
3058
|
+
signal: decision.signal,
|
|
3059
|
+
actualPoints: decision.actualPoints ?? null,
|
|
3060
|
+
diagnostic: decision.diagnostic ?? null
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3009
3063
|
buildStreamDataArg(args) {
|
|
3010
3064
|
// Skip the entire `arg` field when the caller has nothing to override —
|
|
3011
3065
|
// the persisted query then runs with its intrinsic from/to/limit and the
|
|
@@ -4847,6 +4901,36 @@ function matchesAttributePath(cellPath, configField) {
|
|
|
4847
4901
|
const cellBase = stripAggregationFunctionSuffix(cellPath);
|
|
4848
4902
|
return toCanonicalAttributeKey(cellBase) === toCanonicalAttributeKey(configField);
|
|
4849
4903
|
}
|
|
4904
|
+
/**
|
|
4905
|
+
* Finds the single cell in a row that a config field refers to, preferring an
|
|
4906
|
+
* exact `sanitizeFieldName` match over the loose canonical fallback of
|
|
4907
|
+
* {@link matchesAttributePath}.
|
|
4908
|
+
*
|
|
4909
|
+
* This disambiguation matters for grouping-aggregation results, where a group-by
|
|
4910
|
+
* cell (`state`) coexists with an aggregation cell over the same attribute
|
|
4911
|
+
* (`state_count`). A bare category config (`state`) loose-matches BOTH via the
|
|
4912
|
+
* canonical fallback; iterating cells and assigning on every match let the later
|
|
4913
|
+
* `state_count` cell overwrite the category with the raw count, so the pie/bar
|
|
4914
|
+
* legend showed the count instead of the enum label (AB#4293). Resolving each
|
|
4915
|
+
* field to its best cell — exact wins — keeps the group-by and aggregation cells
|
|
4916
|
+
* on their intended fields while still honouring the loose fallback for legacy
|
|
4917
|
+
* configs whose only matching cell is the wire-form aggregation column.
|
|
4918
|
+
*/
|
|
4919
|
+
function findCellForField(cells, configField) {
|
|
4920
|
+
if (!configField)
|
|
4921
|
+
return undefined;
|
|
4922
|
+
let looseMatch;
|
|
4923
|
+
for (const cell of cells) {
|
|
4924
|
+
if (!cell?.attributePath)
|
|
4925
|
+
continue;
|
|
4926
|
+
if (sanitizeFieldName(cell.attributePath) === configField)
|
|
4927
|
+
return cell; // exact wins
|
|
4928
|
+
if (looseMatch === undefined && matchesAttributePath(cell.attributePath, configField)) {
|
|
4929
|
+
looseMatch = cell;
|
|
4930
|
+
}
|
|
4931
|
+
}
|
|
4932
|
+
return looseMatch;
|
|
4933
|
+
}
|
|
4850
4934
|
/**
|
|
4851
4935
|
* Parses a value to a number.
|
|
4852
4936
|
* Returns 0 for NaN or non-numeric values.
|
|
@@ -4876,12 +4960,9 @@ function extractAggregationValue(queryResult, valueField) {
|
|
|
4876
4960
|
const cells = firstRow.cells?.items ?? [];
|
|
4877
4961
|
// Find the value field if specified
|
|
4878
4962
|
if (valueField) {
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
if (matchesAttributePath(cell.attributePath, valueField)) {
|
|
4883
|
-
return parseNumericValue(cell.value);
|
|
4884
|
-
}
|
|
4963
|
+
const cell = findCellForField(cells, valueField);
|
|
4964
|
+
if (cell) {
|
|
4965
|
+
return parseNumericValue(cell.value);
|
|
4885
4966
|
}
|
|
4886
4967
|
}
|
|
4887
4968
|
// Fallback: return first cell value
|
|
@@ -4908,20 +4989,10 @@ function extractGroupedAggregationValue(queryResult, categoryField, categoryValu
|
|
|
4908
4989
|
if (!row || !SUPPORTED_ROW_TYPES$1.includes(row.__typename ?? ''))
|
|
4909
4990
|
continue;
|
|
4910
4991
|
const cells = row.cells?.items ?? [];
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
continue;
|
|
4916
|
-
if (matchesAttributePath(cell.attributePath, categoryField) && String(cell.value) === categoryValue) {
|
|
4917
|
-
categoryMatch = true;
|
|
4918
|
-
}
|
|
4919
|
-
if (matchesAttributePath(cell.attributePath, valueField)) {
|
|
4920
|
-
value = parseNumericValue(cell.value);
|
|
4921
|
-
}
|
|
4922
|
-
}
|
|
4923
|
-
if (categoryMatch) {
|
|
4924
|
-
return value;
|
|
4992
|
+
const categoryCell = findCellForField(cells, categoryField);
|
|
4993
|
+
if (categoryCell && String(categoryCell.value) === categoryValue) {
|
|
4994
|
+
const valueCell = findCellForField(cells, valueField);
|
|
4995
|
+
return valueCell ? parseNumericValue(valueCell.value) : 0;
|
|
4925
4996
|
}
|
|
4926
4997
|
}
|
|
4927
4998
|
return 0;
|
|
@@ -4945,19 +5016,13 @@ function processStaticSeriesData(rows, categoryField, seriesConfigs) {
|
|
|
4945
5016
|
if (!SUPPORTED_ROW_TYPES$1.includes(row.__typename ?? ''))
|
|
4946
5017
|
continue;
|
|
4947
5018
|
const cells = row.cells?.items ?? [];
|
|
4948
|
-
|
|
5019
|
+
const categoryCell = findCellForField(cells, categoryField);
|
|
5020
|
+
const categoryValue = categoryCell ? String(categoryCell.value ?? '') : '';
|
|
4949
5021
|
const rowValues = new Map();
|
|
4950
|
-
for (const
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
categoryValue = String(cell.value ?? '');
|
|
4955
|
-
}
|
|
4956
|
-
// Check if this cell is one of our series fields
|
|
4957
|
-
for (const seriesConfig of seriesConfigs) {
|
|
4958
|
-
if (matchesAttributePath(cell.attributePath, seriesConfig.field)) {
|
|
4959
|
-
rowValues.set(seriesConfig.field, parseNumericValue(cell.value));
|
|
4960
|
-
}
|
|
5022
|
+
for (const seriesConfig of seriesConfigs) {
|
|
5023
|
+
const seriesCell = findCellForField(cells, seriesConfig.field);
|
|
5024
|
+
if (seriesCell) {
|
|
5025
|
+
rowValues.set(seriesConfig.field, parseNumericValue(seriesCell.value));
|
|
4961
5026
|
}
|
|
4962
5027
|
}
|
|
4963
5028
|
if (categoryValue !== '') {
|
|
@@ -4995,22 +5060,12 @@ function processDynamicSeriesData(rows, categoryField, seriesGroupField, valueFi
|
|
|
4995
5060
|
if (!SUPPORTED_ROW_TYPES$1.includes(row.__typename ?? ''))
|
|
4996
5061
|
continue;
|
|
4997
5062
|
const cells = row.cells?.items ?? [];
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
if (matchesAttributePath(cell.attributePath, categoryField)) {
|
|
5005
|
-
categoryValue = String(cell.value ?? '');
|
|
5006
|
-
}
|
|
5007
|
-
else if (matchesAttributePath(cell.attributePath, seriesGroupField)) {
|
|
5008
|
-
seriesGroupValue = String(cell.value ?? '');
|
|
5009
|
-
}
|
|
5010
|
-
else if (matchesAttributePath(cell.attributePath, valueField)) {
|
|
5011
|
-
numericValue = parseNumericValue(cell.value);
|
|
5012
|
-
}
|
|
5013
|
-
}
|
|
5063
|
+
const categoryCell = findCellForField(cells, categoryField);
|
|
5064
|
+
const seriesGroupCell = findCellForField(cells, seriesGroupField);
|
|
5065
|
+
const valueCell = findCellForField(cells, valueField);
|
|
5066
|
+
const categoryValue = categoryCell ? String(categoryCell.value ?? '') : '';
|
|
5067
|
+
const seriesGroupValue = seriesGroupCell ? String(seriesGroupCell.value ?? '') : '';
|
|
5068
|
+
const numericValue = valueCell ? parseNumericValue(valueCell.value) : 0;
|
|
5014
5069
|
if (categoryValue && seriesGroupValue) {
|
|
5015
5070
|
allCategories.add(categoryValue);
|
|
5016
5071
|
allSeriesGroups.add(seriesGroupValue);
|
|
@@ -5048,18 +5103,10 @@ function processPieChartData(rows, categoryField, valueField) {
|
|
|
5048
5103
|
if (!SUPPORTED_ROW_TYPES$1.includes(row.__typename ?? ''))
|
|
5049
5104
|
continue;
|
|
5050
5105
|
const cells = row.cells?.items ?? [];
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
continue;
|
|
5056
|
-
if (matchesAttributePath(cell.attributePath, categoryField)) {
|
|
5057
|
-
categoryValue = String(cell.value ?? '');
|
|
5058
|
-
}
|
|
5059
|
-
else if (matchesAttributePath(cell.attributePath, valueField)) {
|
|
5060
|
-
numericValue = parseNumericValue(cell.value);
|
|
5061
|
-
}
|
|
5062
|
-
}
|
|
5106
|
+
const categoryCell = findCellForField(cells, categoryField);
|
|
5107
|
+
const valueCell = findCellForField(cells, valueField);
|
|
5108
|
+
const categoryValue = categoryCell ? String(categoryCell.value ?? '') : '';
|
|
5109
|
+
const numericValue = valueCell ? parseNumericValue(valueCell.value) : 0;
|
|
5063
5110
|
if (categoryValue) {
|
|
5064
5111
|
result.push({
|
|
5065
5112
|
category: categoryValue,
|
|
@@ -12001,16 +12048,16 @@ class PieChartWidgetComponent {
|
|
|
12001
12048
|
const chartData = result.rows
|
|
12002
12049
|
.filter(row => PieChartWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''))
|
|
12003
12050
|
.map(row => {
|
|
12004
|
-
|
|
12051
|
+
// Resolve each field to its best cell (exact match wins over the loose
|
|
12052
|
+
// aggregation-suffix fallback) so a `state` category is not overwritten
|
|
12053
|
+
// by the `state_count` aggregation cell — AB#4293.
|
|
12054
|
+
const categoryCell = findCellForField(row.cells, this.config.categoryField);
|
|
12055
|
+
const valueCell = findCellForField(row.cells, this.config.valueField);
|
|
12056
|
+
const category = categoryCell ? String(categoryCell.value ?? '') : '';
|
|
12005
12057
|
let value = 0;
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
}
|
|
12010
|
-
if (matchesAttributePath(cell.attributePath, this.config.valueField)) {
|
|
12011
|
-
const numValue = typeof cell.value === 'number' ? cell.value : parseFloat(String(cell.value));
|
|
12012
|
-
value = isNaN(numValue) ? 0 : numValue;
|
|
12013
|
-
}
|
|
12058
|
+
if (valueCell) {
|
|
12059
|
+
const numValue = typeof valueCell.value === 'number' ? valueCell.value : parseFloat(String(valueCell.value));
|
|
12060
|
+
value = isNaN(numValue) ? 0 : numValue;
|
|
12014
12061
|
}
|
|
12015
12062
|
return { category, value };
|
|
12016
12063
|
})
|
|
@@ -13063,18 +13110,17 @@ class BarChartWidgetComponent {
|
|
|
13063
13110
|
seriesMap.set(seriesConfig.field, []);
|
|
13064
13111
|
}
|
|
13065
13112
|
for (const row of filteredRows) {
|
|
13066
|
-
|
|
13113
|
+
// Exact match wins over the loose aggregation-suffix fallback so a group-by
|
|
13114
|
+
// category is not overwritten by an aggregation cell over the same attribute
|
|
13115
|
+
// (e.g. `state` vs `state_count`) — AB#4293.
|
|
13116
|
+
const categoryCell = findCellForField(row.cells, this.config.categoryField);
|
|
13117
|
+
const categoryValue = categoryCell ? this.formatCategoryValue(categoryCell.value) : '';
|
|
13067
13118
|
const rowValues = new Map();
|
|
13068
|
-
for (const
|
|
13069
|
-
|
|
13070
|
-
|
|
13071
|
-
|
|
13072
|
-
|
|
13073
|
-
for (const seriesConfig of (this.config.series ?? [])) {
|
|
13074
|
-
if (matchesAttributePath(cell.attributePath, seriesConfig.field)) {
|
|
13075
|
-
const numValue = typeof cell.value === 'number' ? cell.value : parseFloat(String(cell.value));
|
|
13076
|
-
rowValues.set(seriesConfig.field, isNaN(numValue) ? 0 : numValue);
|
|
13077
|
-
}
|
|
13119
|
+
for (const seriesConfig of (this.config.series ?? [])) {
|
|
13120
|
+
const seriesCell = findCellForField(row.cells, seriesConfig.field);
|
|
13121
|
+
if (seriesCell) {
|
|
13122
|
+
const numValue = typeof seriesCell.value === 'number' ? seriesCell.value : parseFloat(String(seriesCell.value));
|
|
13123
|
+
rowValues.set(seriesConfig.field, isNaN(numValue) ? 0 : numValue);
|
|
13078
13124
|
}
|
|
13079
13125
|
}
|
|
13080
13126
|
if (categoryValue !== '') {
|
|
@@ -13122,22 +13168,18 @@ class BarChartWidgetComponent {
|
|
|
13122
13168
|
const allCategories = new Set();
|
|
13123
13169
|
const allSeriesGroups = new Set();
|
|
13124
13170
|
for (const row of filteredRows) {
|
|
13125
|
-
|
|
13126
|
-
|
|
13171
|
+
// Exact match wins over the loose aggregation-suffix fallback (AB#4293).
|
|
13172
|
+
const categoryCell = findCellForField(row.cells, categoryField);
|
|
13173
|
+
const seriesGroupCell = findCellForField(row.cells, seriesGroupField);
|
|
13174
|
+
const valueCell = findCellForField(row.cells, valueField);
|
|
13175
|
+
const categoryValue = categoryCell ? this.formatCategoryValue(categoryCell.value) : '';
|
|
13176
|
+
const seriesGroupValue = seriesGroupCell ? String(seriesGroupCell.value ?? '') : '';
|
|
13127
13177
|
let numericValue = 0;
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
seriesGroupValue = String(cell.value ?? '');
|
|
13134
|
-
}
|
|
13135
|
-
else if (matchesAttributePath(cell.attributePath, valueField)) {
|
|
13136
|
-
const val = cell.value;
|
|
13137
|
-
numericValue = typeof val === 'number' ? val : parseFloat(String(val));
|
|
13138
|
-
if (isNaN(numericValue))
|
|
13139
|
-
numericValue = 0;
|
|
13140
|
-
}
|
|
13178
|
+
if (valueCell) {
|
|
13179
|
+
const val = valueCell.value;
|
|
13180
|
+
numericValue = typeof val === 'number' ? val : parseFloat(String(val));
|
|
13181
|
+
if (isNaN(numericValue))
|
|
13182
|
+
numericValue = 0;
|
|
13141
13183
|
}
|
|
13142
13184
|
if (categoryValue && seriesGroupValue) {
|
|
13143
13185
|
allCategories.add(categoryValue);
|
|
@@ -17012,10 +17054,9 @@ function extractAggregation(result, valueField) {
|
|
|
17012
17054
|
if (!firstRow)
|
|
17013
17055
|
return 0;
|
|
17014
17056
|
if (valueField) {
|
|
17015
|
-
|
|
17016
|
-
|
|
17017
|
-
|
|
17018
|
-
}
|
|
17057
|
+
const cell = findCellForField(firstRow.cells, valueField);
|
|
17058
|
+
if (cell) {
|
|
17059
|
+
return parseNumericValue(cell.value);
|
|
17019
17060
|
}
|
|
17020
17061
|
}
|
|
17021
17062
|
// Fallback: first cell value when no specific field is configured.
|
|
@@ -17027,18 +17068,11 @@ function extractGroupedAggregation(result, categoryField, categoryValue, valueFi
|
|
|
17027
17068
|
for (const row of result.rows) {
|
|
17028
17069
|
if (!SUPPORTED_ROW_TYPES.has(row.__typename ?? ''))
|
|
17029
17070
|
continue;
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
|
|
17033
|
-
|
|
17034
|
-
categoryMatch = true;
|
|
17035
|
-
}
|
|
17036
|
-
if (matchesAttributePath(cell.attributePath, valueField)) {
|
|
17037
|
-
value = parseNumericValue(cell.value);
|
|
17038
|
-
}
|
|
17071
|
+
const categoryCell = findCellForField(row.cells, categoryField);
|
|
17072
|
+
if (categoryCell && String(categoryCell.value) === categoryValue) {
|
|
17073
|
+
const valueCell = findCellForField(row.cells, valueField);
|
|
17074
|
+
return valueCell ? parseNumericValue(valueCell.value) : 0;
|
|
17039
17075
|
}
|
|
17040
|
-
if (categoryMatch)
|
|
17041
|
-
return value;
|
|
17042
17076
|
}
|
|
17043
17077
|
return 0;
|
|
17044
17078
|
}
|