@meshmakers/octo-meshboard 3.4.160 → 3.4.180
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FieldFilterOperatorsDto, CkTypeSelectorService, RuntimeEntitySelectDataSource as RuntimeEntitySelectDataSource$2, RuntimeEntityDialogDataSource as RuntimeEntityDialogDataSource$2, DeleteStrategiesDto, AssociationModOptionsDto, CkModelService, GraphDirectionDto, AttributeSelectorService, GraphQL, GetCkTypeAvailableQueryColumnsDtoGQL, SortOrdersDto, HealthService, TENANT_ID_PROVIDER, AssetRepoService, JobManagementService } from '@meshmakers/octo-services';
|
|
1
|
+
import { FieldFilterOperatorsDto, CkTypeSelectorService, RuntimeEntitySelectDataSource as RuntimeEntitySelectDataSource$2, RuntimeEntityDialogDataSource as RuntimeEntityDialogDataSource$2, DeleteStrategiesDto, AssociationModOptionsDto, CkModelService, QueryModeDto, GraphDirectionDto, AttributeSelectorService, GraphQL, GetCkTypeAvailableQueryColumnsDtoGQL, SortOrdersDto, HealthService, TENANT_ID_PROVIDER, AssetRepoService, JobManagementService } from '@meshmakers/octo-services';
|
|
2
2
|
export { RuntimeEntityDialogDataSource, RuntimeEntitySelectDataSource, TENANT_ID_PROVIDER } from '@meshmakers/octo-services';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
4
|
import { Injectable, inject, EventEmitter, forwardRef, Output, Input, ViewChild, Component, Injector, EnvironmentInjector, ApplicationRef, signal, computed, Directive, makeEnvironmentProviders, provideAppInitializer, InjectionToken, effect } from '@angular/core';
|
|
@@ -6,7 +6,7 @@ import * as i1$1 from '@angular/forms';
|
|
|
6
6
|
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
|
|
7
7
|
import { firstValueFrom, from, map, Observable, of, catchError, interval, filter } from 'rxjs';
|
|
8
8
|
import * as i1$8 from '@meshmakers/shared-ui';
|
|
9
|
-
import { EntitySelectInputComponent, WindowStateService, ListViewComponent, FetchResultTyped, DataSourceBase, TimeRangePickerComponent, ImportStrategyDialogService,
|
|
9
|
+
import { EntitySelectInputComponent, WindowStateService, TimeRangeUtils, ListViewComponent, FetchResultTyped, DataSourceBase, TimeRangePickerComponent, ImportStrategyDialogService, HAS_UNSAVED_CHANGES, UnsavedChangesDirective } from '@meshmakers/shared-ui';
|
|
10
10
|
import * as i1 from 'apollo-angular';
|
|
11
11
|
import { gql, Apollo } from 'apollo-angular';
|
|
12
12
|
import * as i1$3 from '@angular/common';
|
|
@@ -14,6 +14,7 @@ import { CommonModule } from '@angular/common';
|
|
|
14
14
|
import { CkTypeSelectorInputComponent, AttributeValueTypeDto, PropertyValueDisplayComponent, FieldFilterEditorComponent, PropertyGridComponent, OctoGraphQlDataSource, AttributeSelectorDialogService, AttributeSortSelectorDialogService } from '@meshmakers/octo-ui';
|
|
15
15
|
import * as i1$5 from '@progress/kendo-angular-dialog';
|
|
16
16
|
import { WindowService, WindowCloseResult, WindowRef, DialogsModule, DialogRef, DialogModule, DialogService } from '@progress/kendo-angular-dialog';
|
|
17
|
+
import { switchMap, map as map$1, catchError as catchError$1 } from 'rxjs/operators';
|
|
17
18
|
import * as i2 from '@progress/kendo-angular-buttons';
|
|
18
19
|
import { ButtonsModule, ButtonModule } from '@progress/kendo-angular-buttons';
|
|
19
20
|
import * as i3 from '@progress/kendo-angular-inputs';
|
|
@@ -25,7 +26,6 @@ import { SVGIconModule } from '@progress/kendo-angular-icons';
|
|
|
25
26
|
import { arrowUpIcon, arrowDownIcon, minusIcon, arrowRightIcon, arrowLeftIcon, linkIcon, chevronDownIcon, chevronRightIcon, circleIcon, questionCircleIcon, minusCircleIcon, warningTriangleIcon, exclamationCircleIcon, xCircleIcon, checkCircleIcon, columnsIcon, sortAscIcon, filterIcon, searchIcon, chartPieIcon, infoCircleIcon, plusIcon, trashIcon, pencilIcon, chartLineIcon, gearsIcon, clipboardMarkdownIcon, copyIcon, gridIcon, heartIcon, gridLayoutIcon, chartLineMarkersIcon, chartColumnStackedIcon, chartDoughnutIcon, tableIcon, shareIcon, fileTxtIcon, checkIcon, xIcon, downloadIcon, uploadIcon, gearIcon, saveIcon, arrowRotateCwIcon, undoIcon } from '@progress/kendo-svg-icons';
|
|
26
27
|
import * as i4 from '@progress/kendo-angular-dropdowns';
|
|
27
28
|
import { DropDownsModule, DropDownListModule } from '@progress/kendo-angular-dropdowns';
|
|
28
|
-
import { map as map$1, catchError as catchError$1 } from 'rxjs/operators';
|
|
29
29
|
import { NotificationService } from '@progress/kendo-angular-notification';
|
|
30
30
|
import * as i1$6 from '@progress/kendo-angular-gauges';
|
|
31
31
|
import { CollectionChangesService, KENDO_GAUGES } from '@progress/kendo-angular-gauges';
|
|
@@ -52,6 +52,56 @@ import { BreadCrumbService } from '@meshmakers/shared-services';
|
|
|
52
52
|
|
|
53
53
|
// Re-export from octo-services (moved there for reuse by octo-ui)
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Persistent-query family classification.
|
|
57
|
+
*
|
|
58
|
+
* The query builder persists both runtime-data and stream-data queries as
|
|
59
|
+
* `systemPersistentQuery` entities. They are distinguished by the
|
|
60
|
+
* `queryCkTypeId` field, which carries a substring matching one of the
|
|
61
|
+
* known kinds below.
|
|
62
|
+
*
|
|
63
|
+
* Ordering matters: `GroupingAggregationSdQuery` contains `AggregationSdQuery`
|
|
64
|
+
* as a substring, so the grouping variant must be tested first.
|
|
65
|
+
*/
|
|
66
|
+
const STREAM_DATA_RULES = [
|
|
67
|
+
{ marker: 'DownsamplingSdQuery', kind: 'downsampling' },
|
|
68
|
+
{ marker: 'GroupingAggregationSdQuery', kind: 'groupingAggregation' },
|
|
69
|
+
{ marker: 'AggregationSdQuery', kind: 'aggregation' },
|
|
70
|
+
{ marker: 'SimpleSdQuery', kind: 'simple' }
|
|
71
|
+
];
|
|
72
|
+
const RUNTIME_RULES = [
|
|
73
|
+
{ marker: 'GroupingAggregationRtQuery', kind: 'groupingAggregation' },
|
|
74
|
+
{ marker: 'AggregationRtQuery', kind: 'aggregation' },
|
|
75
|
+
{ marker: 'SimpleRtQuery', kind: 'simple' }
|
|
76
|
+
];
|
|
77
|
+
/**
|
|
78
|
+
* Classify a persistent query by its `queryCkTypeId`.
|
|
79
|
+
* Returns `null` for unknown / legacy values; callers decide how to handle them.
|
|
80
|
+
*/
|
|
81
|
+
function classifyQuery(queryCkTypeId) {
|
|
82
|
+
if (!queryCkTypeId) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
for (const rule of STREAM_DATA_RULES) {
|
|
86
|
+
if (queryCkTypeId.includes(rule.marker)) {
|
|
87
|
+
return { family: 'streamData', kind: rule.kind };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const rule of RUNTIME_RULES) {
|
|
91
|
+
if (queryCkTypeId.includes(rule.marker)) {
|
|
92
|
+
return { family: 'runtime', kind: rule.kind };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Convenience accessor — returns just the family ('runtime' | 'streamData')
|
|
99
|
+
* or null when the query type is unrecognised.
|
|
100
|
+
*/
|
|
101
|
+
function queryFamily(queryCkTypeId) {
|
|
102
|
+
return classifyQuery(queryCkTypeId)?.family ?? null;
|
|
103
|
+
}
|
|
104
|
+
|
|
55
105
|
const GetSystemPersistentQueriesDocumentDto = gql `
|
|
56
106
|
query getSystemPersistentQueries($after: String, $first: Int, $searchFilter: SearchFilter, $fieldFilters: [FieldFilter], $sort: [Sort]) {
|
|
57
107
|
runtime {
|
|
@@ -89,14 +139,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
89
139
|
}]
|
|
90
140
|
}], ctorParameters: () => [{ type: i1.Apollo }] });
|
|
91
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Filter the result list down to queries whose family is in the accept list.
|
|
144
|
+
* Family is classified by the persistent-query entity's own CK type
|
|
145
|
+
* (`ckTypeId`, e.g. `RtSimpleSdQuery`) — NOT the target type
|
|
146
|
+
* (`queryCkTypeId`, e.g. `Basic.Energy/EnergyMeasurement`), which has nothing
|
|
147
|
+
* to do with runtime vs stream-data.
|
|
148
|
+
*
|
|
149
|
+
* `null` family (unrecognised legacy query type) is kept only when 'runtime'
|
|
150
|
+
* is among the accepted families — historical behavior treated everything as
|
|
151
|
+
* runtime-compatible.
|
|
152
|
+
*/
|
|
153
|
+
function filterByFamily(items, accept) {
|
|
154
|
+
if (accept.length === 0) {
|
|
155
|
+
return items;
|
|
156
|
+
}
|
|
157
|
+
return items.filter(item => {
|
|
158
|
+
const family = queryFamily(item.ckTypeId);
|
|
159
|
+
if (family === null) {
|
|
160
|
+
return accept.includes('runtime');
|
|
161
|
+
}
|
|
162
|
+
return accept.includes(family);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
92
165
|
/**
|
|
93
166
|
* Autocomplete data source for persistent query selection.
|
|
94
|
-
* Filters queries by search text using GraphQL.
|
|
167
|
+
* Filters queries by search text using GraphQL, then narrows by family on the client.
|
|
95
168
|
*/
|
|
96
169
|
class PersistentQueryAutocompleteDataSource {
|
|
97
170
|
gql;
|
|
98
|
-
|
|
171
|
+
acceptFamilies;
|
|
172
|
+
constructor(gql, acceptFamilies = ['runtime', 'streamData']) {
|
|
99
173
|
this.gql = gql;
|
|
174
|
+
this.acceptFamilies = acceptFamilies;
|
|
100
175
|
}
|
|
101
176
|
async onFilter(filter, take = 50) {
|
|
102
177
|
const result = await firstValueFrom(this.gql.fetch({
|
|
@@ -105,14 +180,16 @@ class PersistentQueryAutocompleteDataSource {
|
|
|
105
180
|
fieldFilters: filter ? [{ attributePath: 'name', operator: FieldFilterOperatorsDto.LikeDto, comparisonValue: filter }] : undefined
|
|
106
181
|
}
|
|
107
182
|
}));
|
|
108
|
-
const
|
|
183
|
+
const rawItems = (result.data?.runtime?.systemPersistentQuery?.items ?? [])
|
|
109
184
|
.filter((item) => item !== null)
|
|
110
185
|
.map(item => ({
|
|
111
186
|
rtId: item.rtId,
|
|
112
187
|
name: item.name ?? '',
|
|
113
188
|
description: item.description,
|
|
189
|
+
ckTypeId: item.ckTypeId,
|
|
114
190
|
queryCkTypeId: item.queryCkTypeId
|
|
115
191
|
}));
|
|
192
|
+
const items = filterByFamily(rawItems, this.acceptFamilies);
|
|
116
193
|
return {
|
|
117
194
|
totalCount: result.data?.runtime?.systemPersistentQuery?.totalCount ?? 0,
|
|
118
195
|
items
|
|
@@ -127,12 +204,14 @@ class PersistentQueryAutocompleteDataSource {
|
|
|
127
204
|
}
|
|
128
205
|
/**
|
|
129
206
|
* Dialog data source for persistent query selection grid.
|
|
130
|
-
* Provides columns and paginated data for the entity select dialog.
|
|
207
|
+
* Provides columns and paginated data for the entity select dialog, narrowed by family on the client.
|
|
131
208
|
*/
|
|
132
209
|
class PersistentQueryDialogDataSource {
|
|
133
210
|
gql;
|
|
134
|
-
|
|
211
|
+
acceptFamilies;
|
|
212
|
+
constructor(gql, acceptFamilies = ['runtime', 'streamData']) {
|
|
135
213
|
this.gql = gql;
|
|
214
|
+
this.acceptFamilies = acceptFamilies;
|
|
136
215
|
}
|
|
137
216
|
getColumns() {
|
|
138
217
|
return [
|
|
@@ -149,7 +228,7 @@ class PersistentQueryDialogDataSource {
|
|
|
149
228
|
fieldFilters: options.textSearch ? [{ attributePath: 'name', operator: FieldFilterOperatorsDto.LikeDto, comparisonValue: options.textSearch }] : undefined
|
|
150
229
|
}
|
|
151
230
|
})).pipe(map(result => {
|
|
152
|
-
const
|
|
231
|
+
const rawItems = (result.data?.runtime?.systemPersistentQuery?.items ?? [])
|
|
153
232
|
.filter((item) => item !== null)
|
|
154
233
|
.map(item => ({
|
|
155
234
|
rtId: item.rtId,
|
|
@@ -157,6 +236,7 @@ class PersistentQueryDialogDataSource {
|
|
|
157
236
|
description: item.description,
|
|
158
237
|
queryCkTypeId: item.queryCkTypeId
|
|
159
238
|
}));
|
|
239
|
+
const items = filterByFamily(rawItems, this.acceptFamilies);
|
|
160
240
|
return {
|
|
161
241
|
data: items,
|
|
162
242
|
totalCount: result.data?.runtime?.systemPersistentQuery?.totalCount ?? 0
|
|
@@ -194,6 +274,17 @@ class QuerySelectorComponent {
|
|
|
194
274
|
hint;
|
|
195
275
|
/** Whether the component is disabled */
|
|
196
276
|
disabled = false;
|
|
277
|
+
/**
|
|
278
|
+
* Which query families to show in the picker.
|
|
279
|
+
* Default: both runtime and stream-data.
|
|
280
|
+
* Set to `['runtime']` for legacy widgets that cannot consume stream-data,
|
|
281
|
+
* or `['streamData']` for stream-data-only pickers.
|
|
282
|
+
*
|
|
283
|
+
* Filtering happens client-side after a server-side fetch — small per-tenant
|
|
284
|
+
* query counts make this acceptable; a server-side filter would require a
|
|
285
|
+
* backend change.
|
|
286
|
+
*/
|
|
287
|
+
acceptFamilies = ['runtime', 'streamData'];
|
|
197
288
|
/** Emitted when a query is selected */
|
|
198
289
|
querySelected = new EventEmitter();
|
|
199
290
|
/** Emitted when queries are loaded (emits the selected query in a single-item array for compatibility) */
|
|
@@ -205,8 +296,16 @@ class QuerySelectorComponent {
|
|
|
205
296
|
onChange = () => { };
|
|
206
297
|
onTouched = () => { };
|
|
207
298
|
constructor() {
|
|
208
|
-
this.
|
|
209
|
-
|
|
299
|
+
this.rebuildDataSources();
|
|
300
|
+
}
|
|
301
|
+
ngOnChanges(changes) {
|
|
302
|
+
if (changes['acceptFamilies']) {
|
|
303
|
+
this.rebuildDataSources();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
rebuildDataSources() {
|
|
307
|
+
this.queryDataSource = new PersistentQueryAutocompleteDataSource(this.getSystemPersistentQueriesGQL, this.acceptFamilies);
|
|
308
|
+
this.queryDialogDataSource = new PersistentQueryDialogDataSource(this.getSystemPersistentQueriesGQL, this.acceptFamilies);
|
|
210
309
|
}
|
|
211
310
|
ngAfterViewInit() {
|
|
212
311
|
// Forward any value that was set via writeValue() before the ViewChild was available
|
|
@@ -250,6 +349,7 @@ class QuerySelectorComponent {
|
|
|
250
349
|
rtId: item.rtId,
|
|
251
350
|
name: item.name ?? '',
|
|
252
351
|
description: item.description,
|
|
352
|
+
ckTypeId: item.ckTypeId,
|
|
253
353
|
queryCkTypeId: item.queryCkTypeId
|
|
254
354
|
}));
|
|
255
355
|
const query = items[0] ?? null;
|
|
@@ -277,13 +377,13 @@ class QuerySelectorComponent {
|
|
|
277
377
|
this.disabled = isDisabled;
|
|
278
378
|
}
|
|
279
379
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: QuerySelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
280
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: QuerySelectorComponent, isStandalone: true, selector: "mm-query-selector", inputs: { placeholder: "placeholder", hint: "hint", disabled: "disabled" }, outputs: { querySelected: "querySelected", queriesLoaded: "queriesLoaded" }, providers: [
|
|
380
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: QuerySelectorComponent, isStandalone: true, selector: "mm-query-selector", inputs: { placeholder: "placeholder", hint: "hint", disabled: "disabled", acceptFamilies: "acceptFamilies" }, outputs: { querySelected: "querySelected", queriesLoaded: "queriesLoaded" }, providers: [
|
|
281
381
|
{
|
|
282
382
|
provide: NG_VALUE_ACCESSOR,
|
|
283
383
|
useExisting: forwardRef(() => QuerySelectorComponent),
|
|
284
384
|
multi: true
|
|
285
385
|
}
|
|
286
|
-
], viewQueries: [{ propertyName: "entitySelect", first: true, predicate: ["entitySelect"], descendants: true }], ngImport: i0, template: `
|
|
386
|
+
], viewQueries: [{ propertyName: "entitySelect", first: true, predicate: ["entitySelect"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
|
|
287
387
|
<div class="query-selector">
|
|
288
388
|
<mm-entity-select-input
|
|
289
389
|
#entitySelect
|
|
@@ -341,6 +441,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
341
441
|
type: Input
|
|
342
442
|
}], disabled: [{
|
|
343
443
|
type: Input
|
|
444
|
+
}], acceptFamilies: [{
|
|
445
|
+
type: Input
|
|
344
446
|
}], querySelected: [{
|
|
345
447
|
type: Output
|
|
346
448
|
}], queriesLoaded: [{
|
|
@@ -1401,7 +1503,7 @@ class MeshBoardPersistenceService {
|
|
|
1401
1503
|
*/
|
|
1402
1504
|
async createMeshBoard(config) {
|
|
1403
1505
|
// Encode variables, timeFilter, and entitySelectors in description field (temporary until backend adds config field)
|
|
1404
|
-
const encodedDescription = this.encodeVariablesInDescription(config.description ?? '', config.variables, config.timeFilter, config.entitySelectors);
|
|
1506
|
+
const encodedDescription = this.encodeVariablesInDescription(config.description ?? '', config.variables, config.timeFilter, config.entitySelectors, config.autoRefreshSeconds);
|
|
1405
1507
|
const dashboardInput = {
|
|
1406
1508
|
name: config.name,
|
|
1407
1509
|
description: encodedDescription,
|
|
@@ -1433,7 +1535,7 @@ class MeshBoardPersistenceService {
|
|
|
1433
1535
|
async updateMeshBoard(rtId, config, existingWidgetRtIds = []) {
|
|
1434
1536
|
const result = { createdWidgets: [] };
|
|
1435
1537
|
// Encode variables, timeFilter, and entitySelectors in description field (temporary until backend adds config field)
|
|
1436
|
-
const encodedDescription = this.encodeVariablesInDescription(config.description ?? '', config.variables, config.timeFilter, config.entitySelectors);
|
|
1538
|
+
const encodedDescription = this.encodeVariablesInDescription(config.description ?? '', config.variables, config.timeFilter, config.entitySelectors, config.autoRefreshSeconds);
|
|
1437
1539
|
const dashboardItem = {
|
|
1438
1540
|
name: config.name,
|
|
1439
1541
|
description: encodedDescription,
|
|
@@ -1495,8 +1597,9 @@ class MeshBoardPersistenceService {
|
|
|
1495
1597
|
* Converts a persisted MeshBoard to MeshBoardConfig
|
|
1496
1598
|
*/
|
|
1497
1599
|
toMeshBoardConfig(meshBoard, widgets) {
|
|
1498
|
-
// Decode variables, timeFilter, and
|
|
1499
|
-
|
|
1600
|
+
// Decode variables, timeFilter, entitySelectors, and autoRefresh from description field
|
|
1601
|
+
// (temporary until backend adds first-class config field)
|
|
1602
|
+
const { description, variables, timeFilter, entitySelectors, autoRefreshSeconds } = this.decodeVariablesFromDescription(meshBoard.description);
|
|
1500
1603
|
return {
|
|
1501
1604
|
id: meshBoard.rtId,
|
|
1502
1605
|
name: meshBoard.name,
|
|
@@ -1508,6 +1611,7 @@ class MeshBoardPersistenceService {
|
|
|
1508
1611
|
variables,
|
|
1509
1612
|
timeFilter,
|
|
1510
1613
|
entitySelectors,
|
|
1614
|
+
autoRefreshSeconds,
|
|
1511
1615
|
widgets: widgets.map(w => this.toWidgetConfig(w))
|
|
1512
1616
|
};
|
|
1513
1617
|
}
|
|
@@ -1629,12 +1733,13 @@ class MeshBoardPersistenceService {
|
|
|
1629
1733
|
* Note: Only static variables are persisted. TimeFilter variables are derived
|
|
1630
1734
|
* from the timeFilter selection when the MeshBoard is loaded.
|
|
1631
1735
|
*/
|
|
1632
|
-
encodeVariablesInDescription(description, variables, timeFilter, entitySelectors) {
|
|
1736
|
+
encodeVariablesInDescription(description, variables, timeFilter, entitySelectors, autoRefreshSeconds) {
|
|
1633
1737
|
// Filter out timeFilter and entitySelector variables (they are derived, not persisted directly)
|
|
1634
1738
|
const staticVariables = variables?.filter(v => v.source !== 'timeFilter' && v.source !== 'entitySelector');
|
|
1635
1739
|
// Check if there's anything to encode
|
|
1636
1740
|
const hasEntitySelectors = entitySelectors && entitySelectors.length > 0;
|
|
1637
|
-
|
|
1741
|
+
const hasAutoRefresh = !!autoRefreshSeconds && autoRefreshSeconds > 0;
|
|
1742
|
+
if ((!staticVariables || staticVariables.length === 0) && !timeFilter?.enabled && !hasEntitySelectors && !hasAutoRefresh) {
|
|
1638
1743
|
return description;
|
|
1639
1744
|
}
|
|
1640
1745
|
try {
|
|
@@ -1645,6 +1750,9 @@ class MeshBoardPersistenceService {
|
|
|
1645
1750
|
if (timeFilter?.enabled) {
|
|
1646
1751
|
data.timeFilter = timeFilter;
|
|
1647
1752
|
}
|
|
1753
|
+
if (hasAutoRefresh) {
|
|
1754
|
+
data.autoRefreshSeconds = autoRefreshSeconds;
|
|
1755
|
+
}
|
|
1648
1756
|
if (hasEntitySelectors) {
|
|
1649
1757
|
// Strip transient fields before persisting
|
|
1650
1758
|
data.entitySelectors = entitySelectors.map(es => ({
|
|
@@ -1691,12 +1799,13 @@ class MeshBoardPersistenceService {
|
|
|
1691
1799
|
if (Array.isArray(parsed)) {
|
|
1692
1800
|
return { description, variables: parsed };
|
|
1693
1801
|
}
|
|
1694
|
-
// New format: object with variables, timeFilter, and
|
|
1802
|
+
// New format: object with variables, timeFilter, entitySelectors, and autoRefreshSeconds
|
|
1695
1803
|
return {
|
|
1696
1804
|
description,
|
|
1697
1805
|
variables: parsed.variables ?? [],
|
|
1698
1806
|
timeFilter: parsed.timeFilter,
|
|
1699
|
-
entitySelectors: parsed.entitySelectors
|
|
1807
|
+
entitySelectors: parsed.entitySelectors,
|
|
1808
|
+
autoRefreshSeconds: typeof parsed.autoRefreshSeconds === 'number' ? parsed.autoRefreshSeconds : undefined
|
|
1700
1809
|
};
|
|
1701
1810
|
}
|
|
1702
1811
|
catch (error) {
|
|
@@ -2144,7 +2253,8 @@ class MeshBoardStateService {
|
|
|
2144
2253
|
selection: settings.timeFilter.defaultSelection ?? config.timeFilter?.selection
|
|
2145
2254
|
}
|
|
2146
2255
|
: config.timeFilter,
|
|
2147
|
-
entitySelectors: settings.entitySelectors ?? config.entitySelectors
|
|
2256
|
+
entitySelectors: settings.entitySelectors ?? config.entitySelectors,
|
|
2257
|
+
autoRefreshSeconds: settings.autoRefreshSeconds
|
|
2148
2258
|
}));
|
|
2149
2259
|
// If time filter is disabled, clear the time filter variables
|
|
2150
2260
|
if (settings.timeFilter && !settings.timeFilter.enabled) {
|
|
@@ -2165,7 +2275,8 @@ class MeshBoardStateService {
|
|
|
2165
2275
|
gap: config.gap,
|
|
2166
2276
|
variables: config.variables ?? [],
|
|
2167
2277
|
timeFilter: config.timeFilter,
|
|
2168
|
-
entitySelectors: config.entitySelectors
|
|
2278
|
+
entitySelectors: config.entitySelectors,
|
|
2279
|
+
autoRefreshSeconds: config.autoRefreshSeconds
|
|
2169
2280
|
};
|
|
2170
2281
|
}
|
|
2171
2282
|
/**
|
|
@@ -2331,6 +2442,33 @@ class MeshBoardStateService {
|
|
|
2331
2442
|
getTimeFilterConfig() {
|
|
2332
2443
|
return this._meshBoardConfig().timeFilter;
|
|
2333
2444
|
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Resolves the current time-filter selection to a concrete UTC `{from, to}`
|
|
2447
|
+
* range. Returns `null` when the filter is disabled, no selection exists,
|
|
2448
|
+
* or the selection is incomplete.
|
|
2449
|
+
*
|
|
2450
|
+
* Stream-data persistent queries consume this to bound their result set;
|
|
2451
|
+
* runtime queries ignore it.
|
|
2452
|
+
*
|
|
2453
|
+
* IANA-timezone-aware bucket boundaries are tracked separately (AB#4190);
|
|
2454
|
+
* this helper currently returns UTC boundaries derived from the picker.
|
|
2455
|
+
*/
|
|
2456
|
+
resolveCurrentTimeRange() {
|
|
2457
|
+
const config = this._meshBoardConfig().timeFilter;
|
|
2458
|
+
if (!config?.enabled || !config.selection) {
|
|
2459
|
+
return null;
|
|
2460
|
+
}
|
|
2461
|
+
const showTime = config.pickerConfig?.showTime ?? false;
|
|
2462
|
+
// The model stores customFrom/customTo as ISO strings for JSON persistence,
|
|
2463
|
+
// shared-ui's TimeRangeUtils expects Date objects — convert before delegating.
|
|
2464
|
+
const selection = config.selection;
|
|
2465
|
+
const sharedSelection = {
|
|
2466
|
+
...selection,
|
|
2467
|
+
customFrom: selection.customFrom ? new Date(selection.customFrom) : undefined,
|
|
2468
|
+
customTo: selection.customTo ? new Date(selection.customTo) : undefined
|
|
2469
|
+
};
|
|
2470
|
+
return TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime);
|
|
2471
|
+
}
|
|
2334
2472
|
/**
|
|
2335
2473
|
* Updates the time filter configuration.
|
|
2336
2474
|
*/
|
|
@@ -2587,6 +2725,279 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
2587
2725
|
}]
|
|
2588
2726
|
}], ctorParameters: () => [{ type: i1.Apollo }] });
|
|
2589
2727
|
|
|
2728
|
+
const ExecuteStreamDataQueryDocumentDto = gql `
|
|
2729
|
+
query executeStreamDataQuery($rtId: OctoObjectId!, $arg: StreamDataArguments, $first: Int, $after: String, $sortOrder: [Sort], $fieldFilter: [FieldFilter]) {
|
|
2730
|
+
streamData {
|
|
2731
|
+
streamDataQuery(rtId: $rtId) {
|
|
2732
|
+
items {
|
|
2733
|
+
queryRtId
|
|
2734
|
+
associatedCkTypeId
|
|
2735
|
+
columns {
|
|
2736
|
+
attributePath
|
|
2737
|
+
attributeValueType
|
|
2738
|
+
aggregationType
|
|
2739
|
+
}
|
|
2740
|
+
rows(
|
|
2741
|
+
arg: $arg
|
|
2742
|
+
first: $first
|
|
2743
|
+
after: $after
|
|
2744
|
+
sortOrder: $sortOrder
|
|
2745
|
+
fieldFilter: $fieldFilter
|
|
2746
|
+
) {
|
|
2747
|
+
totalCount
|
|
2748
|
+
pageInfo {
|
|
2749
|
+
hasNextPage
|
|
2750
|
+
endCursor
|
|
2751
|
+
}
|
|
2752
|
+
items {
|
|
2753
|
+
rtId
|
|
2754
|
+
ckTypeId
|
|
2755
|
+
timestamp
|
|
2756
|
+
rtWellKnownName
|
|
2757
|
+
rtCreationDateTime
|
|
2758
|
+
rtChangedDateTime
|
|
2759
|
+
cells {
|
|
2760
|
+
items {
|
|
2761
|
+
attributePath
|
|
2762
|
+
value
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
`;
|
|
2772
|
+
class ExecuteStreamDataQueryDtoGQL extends i1.Query {
|
|
2773
|
+
document = ExecuteStreamDataQueryDocumentDto;
|
|
2774
|
+
constructor(apollo) {
|
|
2775
|
+
super(apollo);
|
|
2776
|
+
}
|
|
2777
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ExecuteStreamDataQueryDtoGQL, deps: [{ token: i1.Apollo }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2778
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ExecuteStreamDataQueryDtoGQL, providedIn: 'root' });
|
|
2779
|
+
}
|
|
2780
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ExecuteStreamDataQueryDtoGQL, decorators: [{
|
|
2781
|
+
type: Injectable,
|
|
2782
|
+
args: [{
|
|
2783
|
+
providedIn: 'root'
|
|
2784
|
+
}]
|
|
2785
|
+
}], ctorParameters: () => [{ type: i1.Apollo }] });
|
|
2786
|
+
|
|
2787
|
+
const EMPTY_RESULT_BASE = Object.freeze({
|
|
2788
|
+
queryRtId: null,
|
|
2789
|
+
associatedCkTypeId: null,
|
|
2790
|
+
columns: [],
|
|
2791
|
+
rows: [],
|
|
2792
|
+
totalCount: 0,
|
|
2793
|
+
hasNextPage: false,
|
|
2794
|
+
endCursor: null
|
|
2795
|
+
});
|
|
2796
|
+
/**
|
|
2797
|
+
* Executes persistent queries by rtId and returns a unified result shape
|
|
2798
|
+
* regardless of whether the underlying query is runtime-data or stream-data.
|
|
2799
|
+
*
|
|
2800
|
+
* Widgets consume `QueryExecutionResult` and stay agnostic about which family
|
|
2801
|
+
* the saved query belongs to — switching a widget's data source between
|
|
2802
|
+
* runtime and stream-data is purely a configuration change.
|
|
2803
|
+
*/
|
|
2804
|
+
class QueryExecutorService {
|
|
2805
|
+
runtimeGql = inject(ExecuteRuntimeQueryDtoGQL);
|
|
2806
|
+
streamDataGql = inject(ExecuteStreamDataQueryDtoGQL);
|
|
2807
|
+
persistentQueriesGql = inject(GetSystemPersistentQueriesDtoGQL);
|
|
2808
|
+
/**
|
|
2809
|
+
* Cache of resolved query families, keyed by query rtId. Filled lazily for
|
|
2810
|
+
* legacy widget configs that pre-date the `queryFamily` field — saves a
|
|
2811
|
+
* round-trip on every refresh.
|
|
2812
|
+
*/
|
|
2813
|
+
familyCache = new Map();
|
|
2814
|
+
execute(family, queryRtId, options = {}) {
|
|
2815
|
+
if (family) {
|
|
2816
|
+
return family === 'streamData'
|
|
2817
|
+
? this.executeStreamData(queryRtId, options)
|
|
2818
|
+
: this.executeRuntime(queryRtId, options);
|
|
2819
|
+
}
|
|
2820
|
+
// Family unknown — look it up from the persistent-query entity once and
|
|
2821
|
+
// route accordingly. Legacy widget configs (saved before queryFamily was
|
|
2822
|
+
// persisted) hit this path; subsequent calls in the same session use the
|
|
2823
|
+
// cached family.
|
|
2824
|
+
return from(this.resolveFamily(queryRtId)).pipe(switchMap(resolved => resolved === 'streamData'
|
|
2825
|
+
? this.executeStreamData(queryRtId, options)
|
|
2826
|
+
: this.executeRuntime(queryRtId, options)));
|
|
2827
|
+
}
|
|
2828
|
+
/**
|
|
2829
|
+
* Resolves the family of a persistent query by rtId. Falls back to
|
|
2830
|
+
* `'runtime'` when the query type cannot be classified — this matches the
|
|
2831
|
+
* pre-Phase-1 behavior.
|
|
2832
|
+
*/
|
|
2833
|
+
async resolveFamily(queryRtId) {
|
|
2834
|
+
const cached = this.familyCache.get(queryRtId);
|
|
2835
|
+
if (cached)
|
|
2836
|
+
return cached;
|
|
2837
|
+
try {
|
|
2838
|
+
const result = await firstValueFrom(this.persistentQueriesGql.fetch({
|
|
2839
|
+
variables: {
|
|
2840
|
+
first: 1,
|
|
2841
|
+
fieldFilters: [{ attributePath: 'rtId', operator: FieldFilterOperatorsDto.EqualsDto, comparisonValue: queryRtId }]
|
|
2842
|
+
}
|
|
2843
|
+
}));
|
|
2844
|
+
const item = result.data?.runtime?.systemPersistentQuery?.items?.[0];
|
|
2845
|
+
const resolved = queryFamily(item?.ckTypeId ?? null) ?? 'runtime';
|
|
2846
|
+
this.familyCache.set(queryRtId, resolved);
|
|
2847
|
+
return resolved;
|
|
2848
|
+
}
|
|
2849
|
+
catch (error) {
|
|
2850
|
+
console.warn('QueryExecutorService: family lookup failed for', queryRtId, '— defaulting to runtime', error);
|
|
2851
|
+
return 'runtime';
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
executeRuntime(queryRtId, options = {}) {
|
|
2855
|
+
return this.runtimeGql.fetch({
|
|
2856
|
+
variables: {
|
|
2857
|
+
rtId: queryRtId,
|
|
2858
|
+
first: options.first ?? undefined,
|
|
2859
|
+
after: options.after ?? undefined,
|
|
2860
|
+
fieldFilter: options.fieldFilter ?? undefined
|
|
2861
|
+
},
|
|
2862
|
+
fetchPolicy: options.forceRefresh ? 'network-only' : 'cache-first'
|
|
2863
|
+
}).pipe(map$1(result => {
|
|
2864
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
2865
|
+
if (!queryItem) {
|
|
2866
|
+
return { family: 'runtime', ...EMPTY_RESULT_BASE };
|
|
2867
|
+
}
|
|
2868
|
+
return {
|
|
2869
|
+
family: 'runtime',
|
|
2870
|
+
queryRtId: queryItem.queryRtId ?? null,
|
|
2871
|
+
associatedCkTypeId: queryItem.associatedCkTypeId ?? null,
|
|
2872
|
+
columns: this.mapColumns(queryItem.columns),
|
|
2873
|
+
rows: this.mapRuntimeRows(queryItem.rows?.items),
|
|
2874
|
+
totalCount: queryItem.rows?.totalCount ?? 0,
|
|
2875
|
+
hasNextPage: queryItem.rows?.pageInfo?.hasNextPage ?? false,
|
|
2876
|
+
endCursor: queryItem.rows?.pageInfo?.endCursor ?? null
|
|
2877
|
+
};
|
|
2878
|
+
}));
|
|
2879
|
+
}
|
|
2880
|
+
executeStreamData(queryRtId, options = {}) {
|
|
2881
|
+
const arg = options.streamDataArgs ? this.buildStreamDataArg(options.streamDataArgs) : undefined;
|
|
2882
|
+
return this.streamDataGql.fetch({
|
|
2883
|
+
variables: {
|
|
2884
|
+
rtId: queryRtId,
|
|
2885
|
+
first: options.first ?? undefined,
|
|
2886
|
+
after: options.after ?? undefined,
|
|
2887
|
+
sortOrder: options.sortOrder ?? undefined,
|
|
2888
|
+
fieldFilter: options.fieldFilter ?? undefined,
|
|
2889
|
+
arg
|
|
2890
|
+
},
|
|
2891
|
+
fetchPolicy: options.forceRefresh ? 'network-only' : 'cache-first'
|
|
2892
|
+
}).pipe(map$1(result => {
|
|
2893
|
+
const queryItem = result.data?.streamData?.streamDataQuery?.items?.[0];
|
|
2894
|
+
if (!queryItem) {
|
|
2895
|
+
return { family: 'streamData', ...EMPTY_RESULT_BASE };
|
|
2896
|
+
}
|
|
2897
|
+
return {
|
|
2898
|
+
family: 'streamData',
|
|
2899
|
+
queryRtId: queryItem.queryRtId ?? null,
|
|
2900
|
+
associatedCkTypeId: queryItem.associatedCkTypeId ?? null,
|
|
2901
|
+
columns: this.mapColumns(queryItem.columns),
|
|
2902
|
+
rows: this.mapStreamDataRows(queryItem.rows?.items),
|
|
2903
|
+
totalCount: queryItem.rows?.totalCount ?? 0,
|
|
2904
|
+
hasNextPage: queryItem.rows?.pageInfo?.hasNextPage ?? false,
|
|
2905
|
+
endCursor: queryItem.rows?.pageInfo?.endCursor ?? null
|
|
2906
|
+
};
|
|
2907
|
+
}));
|
|
2908
|
+
}
|
|
2909
|
+
buildStreamDataArg(args) {
|
|
2910
|
+
// Skip the entire `arg` field when the caller has nothing to override —
|
|
2911
|
+
// the persisted query then runs with its intrinsic from/to/limit and the
|
|
2912
|
+
// GraphQL request stays minimal.
|
|
2913
|
+
const hasOverride = args.from != null || args.to != null || args.interval != null || args.limit != null || args.queryMode != null;
|
|
2914
|
+
if (!hasOverride) {
|
|
2915
|
+
return undefined;
|
|
2916
|
+
}
|
|
2917
|
+
// `queryMode` defaults to Default because the schema requires it. The
|
|
2918
|
+
// backend dispatcher ignores it (variant comes from the persisted entity's
|
|
2919
|
+
// CK subtype); see the type-level doc comment for the full story.
|
|
2920
|
+
return {
|
|
2921
|
+
from: args.from ?? undefined,
|
|
2922
|
+
to: args.to ?? undefined,
|
|
2923
|
+
interval: args.interval ?? undefined,
|
|
2924
|
+
limit: args.limit ?? undefined,
|
|
2925
|
+
queryMode: args.queryMode ?? QueryModeDto.DefaultDto
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
mapColumns(columns) {
|
|
2929
|
+
if (!columns)
|
|
2930
|
+
return [];
|
|
2931
|
+
const result = [];
|
|
2932
|
+
for (const col of columns) {
|
|
2933
|
+
if (!col?.attributePath)
|
|
2934
|
+
continue;
|
|
2935
|
+
result.push({
|
|
2936
|
+
attributePath: col.attributePath,
|
|
2937
|
+
attributeValueType: col.attributeValueType ?? null,
|
|
2938
|
+
aggregationType: col.aggregationType ?? null
|
|
2939
|
+
});
|
|
2940
|
+
}
|
|
2941
|
+
return result;
|
|
2942
|
+
}
|
|
2943
|
+
mapRuntimeRows(rows) {
|
|
2944
|
+
if (!rows)
|
|
2945
|
+
return [];
|
|
2946
|
+
const result = [];
|
|
2947
|
+
for (const row of rows) {
|
|
2948
|
+
if (!row)
|
|
2949
|
+
continue;
|
|
2950
|
+
const r = row;
|
|
2951
|
+
result.push({
|
|
2952
|
+
__typename: r.__typename,
|
|
2953
|
+
rtId: r.rtId ?? null,
|
|
2954
|
+
ckTypeId: r.ckTypeId ?? null,
|
|
2955
|
+
cells: this.mapCells(r.cells?.items)
|
|
2956
|
+
});
|
|
2957
|
+
}
|
|
2958
|
+
return result;
|
|
2959
|
+
}
|
|
2960
|
+
mapStreamDataRows(rows) {
|
|
2961
|
+
if (!rows)
|
|
2962
|
+
return [];
|
|
2963
|
+
const result = [];
|
|
2964
|
+
for (const row of rows) {
|
|
2965
|
+
if (!row)
|
|
2966
|
+
continue;
|
|
2967
|
+
const r = row;
|
|
2968
|
+
const ckTypeId = typeof r.ckTypeId === 'string' ? r.ckTypeId : (r.ckTypeId?.fullName ?? null);
|
|
2969
|
+
result.push({
|
|
2970
|
+
__typename: r.__typename ?? 'StreamDataQueryRow',
|
|
2971
|
+
rtId: r.rtId ?? null,
|
|
2972
|
+
ckTypeId,
|
|
2973
|
+
timestamp: r.timestamp ?? null,
|
|
2974
|
+
rtWellKnownName: r.rtWellKnownName ?? null,
|
|
2975
|
+
rtCreationDateTime: r.rtCreationDateTime ?? null,
|
|
2976
|
+
rtChangedDateTime: r.rtChangedDateTime ?? null,
|
|
2977
|
+
cells: this.mapCells(r.cells?.items)
|
|
2978
|
+
});
|
|
2979
|
+
}
|
|
2980
|
+
return result;
|
|
2981
|
+
}
|
|
2982
|
+
mapCells(cells) {
|
|
2983
|
+
if (!cells)
|
|
2984
|
+
return [];
|
|
2985
|
+
const result = [];
|
|
2986
|
+
for (const cell of cells) {
|
|
2987
|
+
if (!cell?.attributePath)
|
|
2988
|
+
continue;
|
|
2989
|
+
result.push({ attributePath: cell.attributePath, value: cell.value });
|
|
2990
|
+
}
|
|
2991
|
+
return result;
|
|
2992
|
+
}
|
|
2993
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: QueryExecutorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2994
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: QueryExecutorService, providedIn: 'root' });
|
|
2995
|
+
}
|
|
2996
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: QueryExecutorService, decorators: [{
|
|
2997
|
+
type: Injectable,
|
|
2998
|
+
args: [{ providedIn: 'root' }]
|
|
2999
|
+
}] });
|
|
3000
|
+
|
|
2590
3001
|
const GetAssociationTargetsDocumentDto = gql `
|
|
2591
3002
|
query getAssociationTargets($rtId: OctoObjectId!, $ckTypeId: String!, $targetCkTypeId: String!, $roleId: String!, $direction: GraphDirection!, $first: Int, $attributeNames: [String]) {
|
|
2592
3003
|
runtime {
|
|
@@ -2859,7 +3270,7 @@ class MeshBoardDataService {
|
|
|
2859
3270
|
getDashboardEntityGQL = inject(GetDashboardEntityDtoGQL);
|
|
2860
3271
|
getCkModelsWithStateGQL = inject(GetCkModelsWithStateDtoGQL);
|
|
2861
3272
|
getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
|
|
2862
|
-
|
|
3273
|
+
queryExecutor = inject(QueryExecutorService);
|
|
2863
3274
|
getAssociationTargetsGQL = inject(GetAssociationTargetsDtoGQL);
|
|
2864
3275
|
getCkTypeAttributesGQL = inject(GetCkTypeAttributesForMeshboardDtoGQL);
|
|
2865
3276
|
apollo = inject(Apollo);
|
|
@@ -3070,8 +3481,10 @@ class MeshBoardDataService {
|
|
|
3070
3481
|
async fetchRepeaterData(dataSource) {
|
|
3071
3482
|
const maxItems = dataSource.maxItems ?? 50;
|
|
3072
3483
|
if (dataSource.queryRtId) {
|
|
3073
|
-
// Query Mode: Execute persistent query
|
|
3074
|
-
|
|
3484
|
+
// Query Mode: Execute persistent query (runtime or stream-data).
|
|
3485
|
+
// `queryFamily` may be undefined for legacy configs — the executor falls
|
|
3486
|
+
// back to a one-time lookup keyed by rtId.
|
|
3487
|
+
return this.fetchRepeaterFromQuery(dataSource.queryFamily, dataSource.queryRtId, maxItems);
|
|
3075
3488
|
}
|
|
3076
3489
|
else if (dataSource.ckTypeId) {
|
|
3077
3490
|
// Entity Mode: Load entities by CK type
|
|
@@ -3081,49 +3494,33 @@ class MeshBoardDataService {
|
|
|
3081
3494
|
return [];
|
|
3082
3495
|
}
|
|
3083
3496
|
/**
|
|
3084
|
-
* Fetches repeater data from a persistent query.
|
|
3085
|
-
*
|
|
3497
|
+
* Fetches repeater data from a persistent query (runtime or stream-data).
|
|
3498
|
+
* Always sends `streamDataArgs` when a time filter is active — the runtime
|
|
3499
|
+
* path ignores the field, so this is safe regardless of the resolved family.
|
|
3086
3500
|
*/
|
|
3087
|
-
async fetchRepeaterFromQuery(queryRtId, maxItems) {
|
|
3501
|
+
async fetchRepeaterFromQuery(family, queryRtId, maxItems) {
|
|
3088
3502
|
try {
|
|
3089
|
-
const
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
}
|
|
3503
|
+
const streamDataArgs = this.buildRepeaterStreamDataArgs();
|
|
3504
|
+
const result = await firstValueFrom(this.queryExecutor.execute(family, queryRtId, {
|
|
3505
|
+
first: maxItems,
|
|
3506
|
+
streamDataArgs
|
|
3094
3507
|
}));
|
|
3095
|
-
const queryItems = result.data?.runtime?.runtimeQuery?.items ?? [];
|
|
3096
|
-
if (queryItems.length === 0) {
|
|
3097
|
-
return [];
|
|
3098
|
-
}
|
|
3099
|
-
const queryResult = queryItems[0];
|
|
3100
|
-
if (!queryResult) {
|
|
3101
|
-
return [];
|
|
3102
|
-
}
|
|
3103
|
-
const rows = queryResult.rows?.items ?? [];
|
|
3104
3508
|
const items = [];
|
|
3105
|
-
for (const row of rows) {
|
|
3106
|
-
if (!row)
|
|
3107
|
-
continue;
|
|
3108
|
-
// Extract rtId from RtSimpleQueryRow if available
|
|
3509
|
+
for (const row of result.rows) {
|
|
3109
3510
|
const rtId = row.rtId ?? `row-${items.length}`;
|
|
3110
|
-
const ckTypeId = row.ckTypeId ??
|
|
3111
|
-
// Build attributes map from cells
|
|
3511
|
+
const ckTypeId = row.ckTypeId ?? result.associatedCkTypeId ?? '';
|
|
3512
|
+
// Build attributes map from cells; expose both sanitised (`a_b`) and
|
|
3513
|
+
// original (`a.b`) keys so widget configs can address either form.
|
|
3112
3514
|
const attributes = new Map();
|
|
3113
|
-
const
|
|
3114
|
-
for (const cell of cells) {
|
|
3115
|
-
if (!cell?.attributePath)
|
|
3116
|
-
continue;
|
|
3117
|
-
// Sanitize the attribute path (replace dots with underscores)
|
|
3515
|
+
for (const cell of row.cells) {
|
|
3118
3516
|
const sanitizedPath = cell.attributePath.replace(/\./g, '_');
|
|
3119
3517
|
attributes.set(sanitizedPath, cell.value);
|
|
3120
|
-
// Also store with original path for flexibility
|
|
3121
3518
|
attributes.set(cell.attributePath, cell.value);
|
|
3122
3519
|
}
|
|
3123
3520
|
items.push({
|
|
3124
3521
|
rtId,
|
|
3125
|
-
ckTypeId,
|
|
3126
|
-
rtWellKnownName: attributes.get('rtWellKnownName'),
|
|
3522
|
+
ckTypeId: ckTypeId ?? '',
|
|
3523
|
+
rtWellKnownName: row.rtWellKnownName ?? attributes.get('rtWellKnownName'),
|
|
3127
3524
|
attributes
|
|
3128
3525
|
});
|
|
3129
3526
|
}
|
|
@@ -3134,6 +3531,13 @@ class MeshBoardDataService {
|
|
|
3134
3531
|
return [];
|
|
3135
3532
|
}
|
|
3136
3533
|
}
|
|
3534
|
+
buildRepeaterStreamDataArgs() {
|
|
3535
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
3536
|
+
if (!range) {
|
|
3537
|
+
return undefined;
|
|
3538
|
+
}
|
|
3539
|
+
return { from: range.from, to: range.to };
|
|
3540
|
+
}
|
|
3137
3541
|
/**
|
|
3138
3542
|
* Fetches repeater data from entities by CK type.
|
|
3139
3543
|
* Maps entities to RepeaterDataItem objects.
|
|
@@ -4252,9 +4656,19 @@ function processPieChartData(rows, categoryField, valueField) {
|
|
|
4252
4656
|
|
|
4253
4657
|
class KpiWidgetComponent {
|
|
4254
4658
|
dataService = inject(DashboardDataService);
|
|
4255
|
-
|
|
4659
|
+
queryExecutor = inject(QueryExecutorService);
|
|
4256
4660
|
stateService = inject(MeshBoardStateService);
|
|
4257
4661
|
variableService = inject(MeshBoardVariableService);
|
|
4662
|
+
/**
|
|
4663
|
+
* Row __typenames KPI extraction recognises.
|
|
4664
|
+
* Runtime queries discriminate; stream-data queries collapse all kinds
|
|
4665
|
+
* (simple / aggregation / grouped / downsampling) into `StreamDataQueryRow`.
|
|
4666
|
+
*/
|
|
4667
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
4668
|
+
'RtAggregationQueryRow',
|
|
4669
|
+
'RtGroupingAggregationQueryRow',
|
|
4670
|
+
'StreamDataQueryRow'
|
|
4671
|
+
]);
|
|
4258
4672
|
config;
|
|
4259
4673
|
arrowUpIcon = arrowUpIcon;
|
|
4260
4674
|
arrowDownIcon = arrowDownIcon;
|
|
@@ -4506,43 +4920,29 @@ class KpiWidgetComponent {
|
|
|
4506
4920
|
this._isLoading.set(true);
|
|
4507
4921
|
this._error.set(null);
|
|
4508
4922
|
try {
|
|
4509
|
-
// Convert widget filters to GraphQL format
|
|
4510
4923
|
const fieldFilter = this.convertFiltersToDto(this.config.filters);
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4924
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
4925
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
4926
|
+
// unconditionally because the runtime path ignores it.
|
|
4927
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
4928
|
+
const result = await firstValueFrom(this.queryExecutor.execute(dataSource.queryFamily, dataSource.queryRtId, {
|
|
4929
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
4930
|
+
streamDataArgs
|
|
4516
4931
|
}).pipe(catchError(err => {
|
|
4517
4932
|
console.error('Error loading KPI query data:', err);
|
|
4518
4933
|
throw err;
|
|
4519
4934
|
})));
|
|
4520
|
-
const queryItems = result.data?.runtime?.runtimeQuery?.items ?? [];
|
|
4521
|
-
if (queryItems.length === 0) {
|
|
4522
|
-
this._error.set('Query returned no results');
|
|
4523
|
-
this._isLoading.set(false);
|
|
4524
|
-
return;
|
|
4525
|
-
}
|
|
4526
|
-
const queryResult = queryItems[0];
|
|
4527
|
-
if (!queryResult) {
|
|
4528
|
-
this._error.set('Query returned no results');
|
|
4529
|
-
this._isLoading.set(false);
|
|
4530
|
-
return;
|
|
4531
|
-
}
|
|
4532
4935
|
let value = 0;
|
|
4533
4936
|
const queryMode = this.config.queryMode ?? 'simpleCount';
|
|
4534
4937
|
switch (queryMode) {
|
|
4535
4938
|
case 'simpleCount':
|
|
4536
|
-
|
|
4537
|
-
value = queryResult.rows?.totalCount ?? 0;
|
|
4939
|
+
value = result.totalCount;
|
|
4538
4940
|
break;
|
|
4539
4941
|
case 'aggregation':
|
|
4540
|
-
|
|
4541
|
-
value = this.extractAggregationValue(queryResult);
|
|
4942
|
+
value = this.extractAggregationValue(result);
|
|
4542
4943
|
break;
|
|
4543
4944
|
case 'groupedAggregation':
|
|
4544
|
-
|
|
4545
|
-
value = this.extractGroupedAggregationValue(queryResult);
|
|
4945
|
+
value = this.extractGroupedAggregationValue(result);
|
|
4546
4946
|
break;
|
|
4547
4947
|
}
|
|
4548
4948
|
// Create a synthetic entity with the value
|
|
@@ -4561,48 +4961,39 @@ class KpiWidgetComponent {
|
|
|
4561
4961
|
this._isLoading.set(false);
|
|
4562
4962
|
}
|
|
4563
4963
|
}
|
|
4964
|
+
buildStreamDataArgs() {
|
|
4965
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
4966
|
+
if (!range) {
|
|
4967
|
+
return undefined;
|
|
4968
|
+
}
|
|
4969
|
+
return { from: range.from, to: range.to };
|
|
4970
|
+
}
|
|
4564
4971
|
extractAggregationValue(queryResult) {
|
|
4565
|
-
const
|
|
4566
|
-
const supportedRowTypes = ['RtAggregationQueryRow', 'RtGroupingAggregationQueryRow'];
|
|
4567
|
-
// Get the first row
|
|
4568
|
-
const firstRow = rows.find(row => row && supportedRowTypes.includes(row.__typename ?? ''));
|
|
4972
|
+
const firstRow = queryResult.rows.find(row => KpiWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''));
|
|
4569
4973
|
if (!firstRow)
|
|
4570
4974
|
return 0;
|
|
4571
|
-
const queryRow = firstRow;
|
|
4572
|
-
const cells = queryRow.cells?.items ?? [];
|
|
4573
|
-
// Find the value field or use the first cell
|
|
4574
4975
|
const valueField = this.config.queryValueField;
|
|
4575
|
-
for (const cell of cells) {
|
|
4576
|
-
if (!cell?.attributePath)
|
|
4577
|
-
continue;
|
|
4976
|
+
for (const cell of firstRow.cells) {
|
|
4578
4977
|
if (valueField && matchesAttributePath(cell.attributePath, valueField)) {
|
|
4579
4978
|
return this.extractCellValue(cell.value);
|
|
4580
4979
|
}
|
|
4581
4980
|
}
|
|
4582
4981
|
// Fallback: return first cell value if no specific field configured
|
|
4583
|
-
|
|
4584
|
-
return firstCell ? this.extractCellValue(firstCell.value) : 0;
|
|
4982
|
+
return firstRow.cells.length > 0 ? this.extractCellValue(firstRow.cells[0].value) : 0;
|
|
4585
4983
|
}
|
|
4586
4984
|
extractGroupedAggregationValue(queryResult) {
|
|
4587
|
-
const rows = queryResult.rows?.items ?? [];
|
|
4588
|
-
const supportedRowTypes = ['RtGroupingAggregationQueryRow', 'RtAggregationQueryRow'];
|
|
4589
4985
|
const categoryField = this.config.queryCategoryField;
|
|
4590
4986
|
const categoryValue = this.config.queryCategoryValue;
|
|
4591
4987
|
const valueField = this.config.queryValueField;
|
|
4592
4988
|
if (!categoryField || !categoryValue || !valueField) {
|
|
4593
4989
|
return 0;
|
|
4594
4990
|
}
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
if (!row || !supportedRowTypes.includes(row.__typename ?? ''))
|
|
4991
|
+
for (const row of queryResult.rows) {
|
|
4992
|
+
if (!KpiWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''))
|
|
4598
4993
|
continue;
|
|
4599
|
-
const queryRow = row;
|
|
4600
|
-
const cells = queryRow.cells?.items ?? [];
|
|
4601
4994
|
let categoryMatch = false;
|
|
4602
4995
|
let value = 0;
|
|
4603
|
-
for (const cell of cells) {
|
|
4604
|
-
if (!cell?.attributePath)
|
|
4605
|
-
continue;
|
|
4996
|
+
for (const cell of row.cells) {
|
|
4606
4997
|
if (matchesAttributePath(cell.attributePath, categoryField) && String(cell.value) === categoryValue) {
|
|
4607
4998
|
categoryMatch = true;
|
|
4608
4999
|
}
|
|
@@ -4692,8 +5083,18 @@ class KpiConfigDialogComponent {
|
|
|
4692
5083
|
getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
|
|
4693
5084
|
ckTypeSelectorService = inject(CkTypeSelectorService);
|
|
4694
5085
|
attributeSelectorService = inject(AttributeSelectorService);
|
|
4695
|
-
executeRuntimeQueryGQL = inject(ExecuteRuntimeQueryDtoGQL);
|
|
4696
5086
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
5087
|
+
queryExecutor = inject(QueryExecutorService);
|
|
5088
|
+
/**
|
|
5089
|
+
* Row __typenames the dialog recognises when collecting distinct category
|
|
5090
|
+
* values from a query result for the grouped-aggregation category picker.
|
|
5091
|
+
*/
|
|
5092
|
+
static INTROSPECTION_ROW_TYPES = new Set([
|
|
5093
|
+
'RtSimpleQueryRow',
|
|
5094
|
+
'RtAggregationQueryRow',
|
|
5095
|
+
'RtGroupingAggregationQueryRow',
|
|
5096
|
+
'StreamDataQueryRow'
|
|
5097
|
+
]);
|
|
4697
5098
|
meshBoardStateService = inject(MeshBoardStateService);
|
|
4698
5099
|
windowRef = inject(WindowRef);
|
|
4699
5100
|
ckTypeSelectorInput;
|
|
@@ -4712,6 +5113,7 @@ class KpiConfigDialogComponent {
|
|
|
4712
5113
|
initialDataSourceType;
|
|
4713
5114
|
initialQueryRtId;
|
|
4714
5115
|
initialQueryName;
|
|
5116
|
+
initialQueryFamily;
|
|
4715
5117
|
initialQueryMode;
|
|
4716
5118
|
initialQueryValueField;
|
|
4717
5119
|
initialQueryCategoryField;
|
|
@@ -4985,6 +5387,7 @@ class KpiConfigDialogComponent {
|
|
|
4985
5387
|
return;
|
|
4986
5388
|
}
|
|
4987
5389
|
if (this.dataSourceType === 'persistentQuery' && this.selectedPersistentQuery) {
|
|
5390
|
+
const family = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
4988
5391
|
this.windowRef.close({
|
|
4989
5392
|
dataSourceType: 'persistentQuery',
|
|
4990
5393
|
ckTypeId: '',
|
|
@@ -4992,6 +5395,7 @@ class KpiConfigDialogComponent {
|
|
|
4992
5395
|
valueAttribute: '',
|
|
4993
5396
|
queryRtId: this.selectedPersistentQuery.rtId,
|
|
4994
5397
|
queryName: this.selectedPersistentQuery.name,
|
|
5398
|
+
queryFamily: family,
|
|
4995
5399
|
queryMode: this.queryMode,
|
|
4996
5400
|
queryValueField: this.form.queryValueField || undefined,
|
|
4997
5401
|
queryCategoryField: this.form.queryCategoryField || undefined,
|
|
@@ -5058,35 +5462,16 @@ class KpiConfigDialogComponent {
|
|
|
5058
5462
|
const rtId = queryRtId || this.selectedPersistentQuery?.rtId;
|
|
5059
5463
|
if (!rtId)
|
|
5060
5464
|
return;
|
|
5465
|
+
// queryFamily may be undefined when the selected query metadata is missing —
|
|
5466
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
5467
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
5061
5468
|
this.isLoadingQueryColumns = true;
|
|
5062
5469
|
try {
|
|
5063
|
-
|
|
5064
|
-
//
|
|
5065
|
-
//
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
rtId: rtId
|
|
5069
|
-
}
|
|
5070
|
-
}));
|
|
5071
|
-
const queryItems = result.data?.runtime?.runtimeQuery?.items ?? [];
|
|
5072
|
-
if (queryItems.length > 0 && queryItems[0]) {
|
|
5073
|
-
const columns = queryItems[0].columns ?? [];
|
|
5074
|
-
const filteredColumns = columns
|
|
5075
|
-
.filter((c) => c !== null);
|
|
5076
|
-
// Column AttributePath is already in the engine's wire form for aggregation /
|
|
5077
|
-
// grouping columns (e.g. `quantity_sum`, `operatingstatus`) so picker entries
|
|
5078
|
-
// double as both the visible label and the stored config value, and MIN + MAX
|
|
5079
|
-
// of the same source path show up as two distinct entries.
|
|
5080
|
-
this.queryColumns = filteredColumns.map(c => ({
|
|
5081
|
-
attributePath: c.attributePath ?? '',
|
|
5082
|
-
attributeValueType: c.attributeValueType ?? '',
|
|
5083
|
-
aggregationType: c.aggregationType ?? null
|
|
5084
|
-
}));
|
|
5085
|
-
// Category values for grouped aggregation are loaded on-demand by
|
|
5086
|
-
// loadCategoryValuesForField — only when a categoryField is actually selected.
|
|
5087
|
-
if (this.queryMode === 'groupedAggregation' && this.form.queryCategoryField) {
|
|
5088
|
-
await this.loadCategoryValuesForField(rtId, this.form.queryCategoryField);
|
|
5089
|
-
}
|
|
5470
|
+
this.queryColumns = await this.fetchColumnsForFamily(family, rtId);
|
|
5471
|
+
// Category values for grouped aggregation are loaded on-demand by
|
|
5472
|
+
// loadCategoryValuesForField — only when a categoryField is actually selected.
|
|
5473
|
+
if (this.queryColumns.length > 0 && this.queryMode === 'groupedAggregation' && this.form.queryCategoryField) {
|
|
5474
|
+
await this.loadCategoryValuesForField(rtId, this.form.queryCategoryField);
|
|
5090
5475
|
}
|
|
5091
5476
|
}
|
|
5092
5477
|
catch (error) {
|
|
@@ -5097,6 +5482,40 @@ class KpiConfigDialogComponent {
|
|
|
5097
5482
|
this.isLoadingQueryColumns = false;
|
|
5098
5483
|
}
|
|
5099
5484
|
}
|
|
5485
|
+
/**
|
|
5486
|
+
* Loads column metadata for the picker. Runtime queries use the
|
|
5487
|
+
* metadata-only resolver (no aggregation executed); stream-data queries
|
|
5488
|
+
* fall back to executing the query with `first: 1`. When `family` is
|
|
5489
|
+
* unknown (legacy configs), the executor resolves it once by rtId lookup.
|
|
5490
|
+
*/
|
|
5491
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
5492
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
5493
|
+
if (resolvedFamily === 'runtime') {
|
|
5494
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
5495
|
+
variables: { rtId }
|
|
5496
|
+
}));
|
|
5497
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
5498
|
+
if (!queryItem)
|
|
5499
|
+
return [];
|
|
5500
|
+
return (queryItem.columns ?? [])
|
|
5501
|
+
.filter((c) => c !== null)
|
|
5502
|
+
// Column AttributePath is already in the engine's wire form for aggregation /
|
|
5503
|
+
// grouping columns (e.g. `quantity_sum`, `operatingstatus`) so picker entries
|
|
5504
|
+
// double as both the visible label and the stored config value.
|
|
5505
|
+
.map(c => ({
|
|
5506
|
+
attributePath: c.attributePath ?? '',
|
|
5507
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
5508
|
+
aggregationType: c.aggregationType ?? null
|
|
5509
|
+
}));
|
|
5510
|
+
}
|
|
5511
|
+
// Stream-data: execute with a tiny page just to surface columns.
|
|
5512
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
5513
|
+
return sdResult.columns.map(c => ({
|
|
5514
|
+
attributePath: c.attributePath,
|
|
5515
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
5516
|
+
aggregationType: c.aggregationType ?? null
|
|
5517
|
+
}));
|
|
5518
|
+
}
|
|
5100
5519
|
async onCategoryFieldChange(categoryField) {
|
|
5101
5520
|
this.form.queryCategoryField = categoryField;
|
|
5102
5521
|
this.form.queryCategoryValue = '';
|
|
@@ -5108,36 +5527,23 @@ class KpiConfigDialogComponent {
|
|
|
5108
5527
|
async loadCategoryValuesForField(queryRtId, categoryField) {
|
|
5109
5528
|
this.isLoadingCategoryValues = true;
|
|
5110
5529
|
try {
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
const supportedRowTypes = ['RtSimpleQueryRow', 'RtAggregationQueryRow', 'RtGroupingAggregationQueryRow'];
|
|
5122
|
-
const values = new Set();
|
|
5123
|
-
for (const row of rows) {
|
|
5124
|
-
if (!row || !supportedRowTypes.includes(row.__typename ?? ''))
|
|
5125
|
-
continue;
|
|
5126
|
-
const queryRow = row;
|
|
5127
|
-
const cells = queryRow.cells?.items ?? [];
|
|
5128
|
-
for (const cell of cells) {
|
|
5129
|
-
if (!cell?.attributePath)
|
|
5130
|
-
continue;
|
|
5131
|
-
if (matchesAttributePath(cell.attributePath, categoryField) && cell.value !== null && cell.value !== undefined) {
|
|
5132
|
-
values.add(String(cell.value));
|
|
5133
|
-
}
|
|
5530
|
+
// family may be undefined here — the executor falls back to a lookup.
|
|
5531
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
5532
|
+
const result = await firstValueFrom(this.queryExecutor.execute(family, queryRtId, { first: 100 }));
|
|
5533
|
+
const values = new Set();
|
|
5534
|
+
for (const row of result.rows) {
|
|
5535
|
+
if (!KpiConfigDialogComponent.INTROSPECTION_ROW_TYPES.has(row.__typename ?? ''))
|
|
5536
|
+
continue;
|
|
5537
|
+
for (const cell of row.cells) {
|
|
5538
|
+
if (matchesAttributePath(cell.attributePath, categoryField) && cell.value !== null && cell.value !== undefined) {
|
|
5539
|
+
values.add(String(cell.value));
|
|
5134
5540
|
}
|
|
5135
5541
|
}
|
|
5136
|
-
this.categoryValues = Array.from(values).map(v => ({
|
|
5137
|
-
value: v,
|
|
5138
|
-
displayValue: v
|
|
5139
|
-
}));
|
|
5140
5542
|
}
|
|
5543
|
+
this.categoryValues = Array.from(values).map(v => ({
|
|
5544
|
+
value: v,
|
|
5545
|
+
displayValue: v
|
|
5546
|
+
}));
|
|
5141
5547
|
}
|
|
5142
5548
|
catch (error) {
|
|
5143
5549
|
console.error('Error loading category values:', error);
|
|
@@ -5154,7 +5560,7 @@ class KpiConfigDialogComponent {
|
|
|
5154
5560
|
this.filters = updatedFilters;
|
|
5155
5561
|
}
|
|
5156
5562
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: KpiConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5157
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: KpiConfigDialogComponent, isStandalone: true, selector: "mm-kpi-config-dialog", inputs: { initialCkTypeId: "initialCkTypeId", initialRtId: "initialRtId", initialValueAttribute: "initialValueAttribute", initialLabelAttribute: "initialLabelAttribute", initialPrefix: "initialPrefix", initialSuffix: "initialSuffix", initialTrend: "initialTrend", initialComparisonText: "initialComparisonText", initialDataSourceType: "initialDataSourceType", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryMode: "initialQueryMode", initialQueryValueField: "initialQueryValueField", initialQueryCategoryField: "initialQueryCategoryField", initialQueryCategoryValue: "initialQueryCategoryValue", initialStaticValue: "initialStaticValue", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "ckTypeSelectorInput", first: true, predicate: ["ckTypeSelector"], descendants: true }, { propertyName: "entitySelectorInput", first: true, predicate: ["entitySelector"], descendants: true }, { propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
5563
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: KpiConfigDialogComponent, isStandalone: true, selector: "mm-kpi-config-dialog", inputs: { initialCkTypeId: "initialCkTypeId", initialRtId: "initialRtId", initialValueAttribute: "initialValueAttribute", initialLabelAttribute: "initialLabelAttribute", initialPrefix: "initialPrefix", initialSuffix: "initialSuffix", initialTrend: "initialTrend", initialComparisonText: "initialComparisonText", initialDataSourceType: "initialDataSourceType", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryFamily: "initialQueryFamily", initialQueryMode: "initialQueryMode", initialQueryValueField: "initialQueryValueField", initialQueryCategoryField: "initialQueryCategoryField", initialQueryCategoryValue: "initialQueryCategoryValue", initialStaticValue: "initialStaticValue", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "ckTypeSelectorInput", first: true, predicate: ["ckTypeSelector"], descendants: true }, { propertyName: "entitySelectorInput", first: true, predicate: ["entitySelector"], descendants: true }, { propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
5158
5564
|
<div class="config-container">
|
|
5159
5565
|
|
|
5160
5566
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -5524,7 +5930,7 @@ class KpiConfigDialogComponent {
|
|
|
5524
5930
|
</button>
|
|
5525
5931
|
</div>
|
|
5526
5932
|
</div>
|
|
5527
|
-
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field.flex-1{flex:1}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.form-section h4{margin:0 0 16px;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-row{display:flex;gap:16px}.mode-toggle{display:flex;gap:8px}.mode-toggle button{flex:1}.attribute-item{display:flex;justify-content:space-between;align-items:center;gap:8px;width:100%}.attribute-path{flex:1}.attribute-type{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);background:var(--kendo-color-surface-alt, #f8f9fa);padding:2px 6px;border-radius:3px}.required{color:var(--kendo-color-error, #dc3545)}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "messages", "dialogMessages", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "initialDisplayValue", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
5933
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field.flex-1{flex:1}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.form-section h4{margin:0 0 16px;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-row{display:flex;gap:16px}.mode-toggle{display:flex;gap:8px}.mode-toggle button{flex:1}.attribute-item{display:flex;justify-content:space-between;align-items:center;gap:8px;width:100%}.attribute-path{flex:1}.attribute-type{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);background:var(--kendo-color-surface-alt, #f8f9fa);padding:2px 6px;border-radius:3px}.required{color:var(--kendo-color-error, #dc3545)}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "messages", "dialogMessages", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "initialDisplayValue", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled", "acceptFamilies"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
5528
5934
|
}
|
|
5529
5935
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: KpiConfigDialogComponent, decorators: [{
|
|
5530
5936
|
type: Component,
|
|
@@ -5941,6 +6347,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
5941
6347
|
type: Input
|
|
5942
6348
|
}], initialQueryName: [{
|
|
5943
6349
|
type: Input
|
|
6350
|
+
}], initialQueryFamily: [{
|
|
6351
|
+
type: Input
|
|
5944
6352
|
}], initialQueryMode: [{
|
|
5945
6353
|
type: Input
|
|
5946
6354
|
}], initialQueryValueField: [{
|
|
@@ -7364,7 +7772,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
7364
7772
|
*/
|
|
7365
7773
|
class TableWidgetDataSourceDirective extends OctoGraphQlDataSource {
|
|
7366
7774
|
getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
|
|
7367
|
-
|
|
7775
|
+
queryExecutor = inject(QueryExecutorService);
|
|
7368
7776
|
stateService = inject(MeshBoardStateService);
|
|
7369
7777
|
variableService = inject(MeshBoardVariableService);
|
|
7370
7778
|
_config = null;
|
|
@@ -7493,86 +7901,81 @@ class TableWidgetDataSourceDirective extends OctoGraphQlDataSource {
|
|
|
7493
7901
|
}
|
|
7494
7902
|
}
|
|
7495
7903
|
/**
|
|
7496
|
-
*
|
|
7497
|
-
*
|
|
7904
|
+
* Row __typenames the table widget knows how to flatten. Runtime queries
|
|
7905
|
+
* use three discriminated variants; stream-data queries collapse all
|
|
7906
|
+
* kinds (simple / aggregation / grouped / downsampling) into a single
|
|
7907
|
+
* `StreamDataQueryRow` type.
|
|
7908
|
+
*/
|
|
7909
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
7910
|
+
'RtSimpleQueryRow',
|
|
7911
|
+
'RtAggregationQueryRow',
|
|
7912
|
+
'RtGroupingAggregationQueryRow',
|
|
7913
|
+
'StreamDataQueryRow'
|
|
7914
|
+
]);
|
|
7915
|
+
/**
|
|
7916
|
+
* Fetches data from a persistent query (runtime or stream-data).
|
|
7917
|
+
* Family is determined from the cached `queryFamily` on the data source
|
|
7918
|
+
* (set by the config dialog when the user picks a query) and defaults to
|
|
7919
|
+
* `'runtime'` for legacy configs that predate stream-data support.
|
|
7498
7920
|
*/
|
|
7499
7921
|
fetchPersistentQueryData(dataSource, queryOptions) {
|
|
7500
|
-
// Convert widget-configured filters to GraphQL format (with variable resolution)
|
|
7501
7922
|
const fieldFilter = this.convertFiltersToDto(this._config?.filters);
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7923
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
7924
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
7925
|
+
// unconditionally because the runtime path ignores it.
|
|
7926
|
+
//
|
|
7927
|
+
// Precedence: MeshBoard time filter > query's intrinsic time bounds.
|
|
7928
|
+
// When no time filter is active, the persistent query uses its own bounds.
|
|
7929
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
7930
|
+
return this.queryExecutor.execute(dataSource.queryFamily, dataSource.queryRtId, {
|
|
7931
|
+
first: queryOptions.state.take ?? this._config?.pageSize ?? 10,
|
|
7932
|
+
after: GraphQL.offsetToCursor(queryOptions.state.skip ?? 0),
|
|
7933
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
7934
|
+
streamDataArgs
|
|
7509
7935
|
}).pipe(map$1(result => {
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
const queryResult = queryItems[0];
|
|
7515
|
-
if (!queryResult) {
|
|
7516
|
-
return new FetchResultTyped([], 0);
|
|
7517
|
-
}
|
|
7518
|
-
// Extract columns from query response and update signal
|
|
7519
|
-
// Replace dots with underscores for grid compatibility (Kendo treats dots as nested paths)
|
|
7520
|
-
const columns = (queryResult.columns ?? [])
|
|
7521
|
-
.filter((c) => c !== null)
|
|
7522
|
-
.map(c => ({
|
|
7523
|
-
attributePath: this.sanitizeFieldName(c.attributePath ?? ''),
|
|
7936
|
+
// Extract columns from query response and update signal.
|
|
7937
|
+
// Replace dots with underscores for grid compatibility (Kendo treats dots as nested paths).
|
|
7938
|
+
const columns = result.columns.map(c => ({
|
|
7939
|
+
attributePath: this.sanitizeFieldName(c.attributePath),
|
|
7524
7940
|
attributeValueType: c.attributeValueType ?? ''
|
|
7525
7941
|
}));
|
|
7526
7942
|
this._queryColumns.set(columns);
|
|
7527
|
-
// Emit event to notify component that columns have been loaded
|
|
7528
7943
|
this.queryColumnsLoaded.emit(columns);
|
|
7529
|
-
// Extract rows
|
|
7530
|
-
const rows = queryResult.rows?.items ?? [];
|
|
7531
|
-
const totalCount = queryResult.rows?.totalCount ?? 0;
|
|
7532
|
-
// Check which standard fields are in the query columns
|
|
7533
7944
|
const columnPaths = new Set(columns.map(c => c.attributePath));
|
|
7534
7945
|
const hasRtIdColumn = columnPaths.has('rtId');
|
|
7535
7946
|
const hasCkTypeIdColumn = columnPaths.has('ckTypeId');
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
.filter((row) => row !== null)
|
|
7541
|
-
.filter(row => supportedRowTypes.includes(row.__typename ?? ''))
|
|
7542
|
-
.map((row, index) => {
|
|
7543
|
-
// Both row types have ckTypeId and cells, but only RtSimpleQueryRow has rtId
|
|
7544
|
-
const queryRow = row;
|
|
7545
|
-
const record = {};
|
|
7546
|
-
// Only add rtId/ckTypeId if they're explicitly in the query columns
|
|
7547
|
-
if (hasRtIdColumn) {
|
|
7548
|
-
record['rtId'] = queryRow.rtId ?? `agg-${index}`;
|
|
7549
|
-
}
|
|
7550
|
-
if (hasCkTypeIdColumn) {
|
|
7551
|
-
record['ckTypeId'] = queryRow.ckTypeId ?? '';
|
|
7552
|
-
}
|
|
7553
|
-
// Flatten cells into the record. Each cell is stored under the COLUMN's
|
|
7554
|
-
// attributePath (which is what the Kendo grid uses as `field`) rather than
|
|
7555
|
-
// the cell's own attributePath — the two may differ now that the engine
|
|
7556
|
-
// emits cell paths in wire-form with a function suffix
|
|
7557
|
-
// (e.g. cell path `meterreading_count` for column path `meterReading`).
|
|
7558
|
-
// `matchesAttributePath` reconciles both forms.
|
|
7559
|
-
const cells = queryRow.cells?.items ?? [];
|
|
7560
|
-
for (const cell of cells) {
|
|
7561
|
-
if (!cell?.attributePath)
|
|
7562
|
-
continue;
|
|
7563
|
-
const matchingColumn = columns.find(col => matchesAttributePath(cell.attributePath, col.attributePath));
|
|
7564
|
-
if (matchingColumn) {
|
|
7565
|
-
record[matchingColumn.attributePath] = cell.value;
|
|
7566
|
-
}
|
|
7567
|
-
}
|
|
7568
|
-
return record;
|
|
7569
|
-
});
|
|
7570
|
-
return new FetchResultTyped(data, totalCount);
|
|
7947
|
+
const data = result.rows
|
|
7948
|
+
.filter(row => TableWidgetDataSourceDirective.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''))
|
|
7949
|
+
.map((row, index) => this.queryRowToRecord(row, columns, hasRtIdColumn, hasCkTypeIdColumn, index));
|
|
7950
|
+
return new FetchResultTyped(data, result.totalCount);
|
|
7571
7951
|
}), catchError$1(err => {
|
|
7572
7952
|
console.error('Error fetching query data:', err);
|
|
7573
7953
|
return of(new FetchResultTyped([], 0));
|
|
7574
7954
|
}));
|
|
7575
7955
|
}
|
|
7956
|
+
/**
|
|
7957
|
+
* Flattens a unified `QueryResultRow` into a Kendo-grid-friendly record.
|
|
7958
|
+
* Cells are stored under their matching column's `attributePath` rather than
|
|
7959
|
+
* the cell's own — the engine emits cell paths in wire-form with a function
|
|
7960
|
+
* suffix (e.g. cell path `meterreading_count` for column path `meterReading`).
|
|
7961
|
+
* `matchesAttributePath` reconciles both forms.
|
|
7962
|
+
*/
|
|
7963
|
+
queryRowToRecord(row, columns, hasRtIdColumn, hasCkTypeIdColumn, index) {
|
|
7964
|
+
const record = {};
|
|
7965
|
+
if (hasRtIdColumn) {
|
|
7966
|
+
record['rtId'] = row.rtId ?? `agg-${index}`;
|
|
7967
|
+
}
|
|
7968
|
+
if (hasCkTypeIdColumn) {
|
|
7969
|
+
record['ckTypeId'] = row.ckTypeId ?? '';
|
|
7970
|
+
}
|
|
7971
|
+
for (const cell of row.cells) {
|
|
7972
|
+
const matchingColumn = columns.find(col => matchesAttributePath(cell.attributePath, col.attributePath));
|
|
7973
|
+
if (matchingColumn) {
|
|
7974
|
+
record[matchingColumn.attributePath] = cell.value;
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
return record;
|
|
7978
|
+
}
|
|
7576
7979
|
/**
|
|
7577
7980
|
* Converts query columns to TableColumn format for display.
|
|
7578
7981
|
*/
|
|
@@ -7615,6 +8018,18 @@ class TableWidgetDataSourceDirective extends OctoGraphQlDataSource {
|
|
|
7615
8018
|
const variables = this.stateService.getVariables();
|
|
7616
8019
|
return this.variableService.convertToFieldFilterDto(filters, variables);
|
|
7617
8020
|
}
|
|
8021
|
+
/**
|
|
8022
|
+
* Builds `StreamDataExecutionArgs` from the MeshBoard's current time filter.
|
|
8023
|
+
* Returns `undefined` when no filter is active so the persistent query's
|
|
8024
|
+
* own bounds apply.
|
|
8025
|
+
*/
|
|
8026
|
+
buildStreamDataArgs() {
|
|
8027
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
8028
|
+
if (!range) {
|
|
8029
|
+
return undefined;
|
|
8030
|
+
}
|
|
8031
|
+
return { from: range.from, to: range.to };
|
|
8032
|
+
}
|
|
7618
8033
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TableWidgetDataSourceDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
7619
8034
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: TableWidgetDataSourceDirective, isStandalone: true, selector: "[mmTableWidgetDataSource]", inputs: { config: "config" }, outputs: { queryColumnsLoaded: "queryColumnsLoaded" }, providers: [
|
|
7620
8035
|
{
|
|
@@ -7850,6 +8265,7 @@ class TableConfigDialogComponent {
|
|
|
7850
8265
|
initialSortable;
|
|
7851
8266
|
initialQueryRtId;
|
|
7852
8267
|
initialQueryName;
|
|
8268
|
+
initialQueryFamily;
|
|
7853
8269
|
columnsIcon = columnsIcon;
|
|
7854
8270
|
sortIcon = sortAscIcon;
|
|
7855
8271
|
filterIcon = filterIcon;
|
|
@@ -8071,6 +8487,9 @@ class TableConfigDialogComponent {
|
|
|
8071
8487
|
operator: f.operator,
|
|
8072
8488
|
comparisonValue: f.comparisonValue
|
|
8073
8489
|
}));
|
|
8490
|
+
// Derive family from the selected query's CK type so the runtime executor
|
|
8491
|
+
// can route correctly without an extra lookup.
|
|
8492
|
+
const family = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
8074
8493
|
this.windowRef.close({
|
|
8075
8494
|
dataSourceType: 'persistentQuery',
|
|
8076
8495
|
ckTypeId: '', // Not used for persistent query
|
|
@@ -8079,6 +8498,7 @@ class TableConfigDialogComponent {
|
|
|
8079
8498
|
filters: queryFilterDtos,
|
|
8080
8499
|
queryRtId: this.selectedPersistentQuery.rtId,
|
|
8081
8500
|
queryName: this.selectedPersistentQuery.name,
|
|
8501
|
+
queryFamily: family,
|
|
8082
8502
|
pageSize: this.form.pageSize,
|
|
8083
8503
|
sortable: this.form.sortable
|
|
8084
8504
|
});
|
|
@@ -8095,7 +8515,7 @@ class TableConfigDialogComponent {
|
|
|
8095
8515
|
this.windowRef.close();
|
|
8096
8516
|
}
|
|
8097
8517
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TableConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8098
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: TableConfigDialogComponent, isStandalone: true, selector: "mm-table-config-dialog", inputs: { initialDataSourceType: "initialDataSourceType", initialCkTypeId: "initialCkTypeId", initialColumns: "initialColumns", initialSorting: "initialSorting", initialFilters: "initialFilters", initialPageSize: "initialPageSize", initialSortable: "initialSortable", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName" }, providers: [
|
|
8518
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: TableConfigDialogComponent, isStandalone: true, selector: "mm-table-config-dialog", inputs: { initialDataSourceType: "initialDataSourceType", initialCkTypeId: "initialCkTypeId", initialColumns: "initialColumns", initialSorting: "initialSorting", initialFilters: "initialFilters", initialPageSize: "initialPageSize", initialSortable: "initialSortable", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryFamily: "initialQueryFamily" }, providers: [
|
|
8099
8519
|
AttributeSelectorDialogService,
|
|
8100
8520
|
AttributeSortSelectorDialogService
|
|
8101
8521
|
], viewQueries: [{ propertyName: "ckTypeSelectorInput", first: true, predicate: ["ckTypeSelector"], descendants: true }, { propertyName: "filterEditor", first: true, predicate: ["filterEditor"], descendants: true }, { propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
@@ -8298,7 +8718,7 @@ class TableConfigDialogComponent {
|
|
|
8298
8718
|
</button>
|
|
8299
8719
|
</div>
|
|
8300
8720
|
</div>
|
|
8301
|
-
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:16px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.config-card{padding:12px 16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.card-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.card-header kendo-svgicon{color:var(--kendo-color-primary, #0d6efd)}.card-title{font-weight:600;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.card-count{color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.card-content{display:flex;align-items:center;justify-content:space-between;gap:16px}.config-summary{margin:0;font-size:.85rem;color:var(--kendo-color-on-app-surface, #212529);flex:1}.filters-card .card-content{flex-direction:column;align-items:stretch}.filter-content{width:100%}.options-card .options-content{display:flex;gap:24px;align-items:flex-end}.checkbox-field{flex-direction:row;align-items:center}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;color:var(--kendo-color-on-app-surface, #212529)}.data-source-type .radio-group{display:flex;gap:24px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400;color:var(--kendo-color-on-app-surface, #212529)}.radio-label span{color:var(--kendo-color-on-app-surface, #212529)}.query-info{flex-direction:column;align-items:stretch;gap:8px}.info-row{display:flex;gap:8px}.info-label{font-weight:600;min-width:100px;color:var(--kendo-color-subtle, #6c757d)}.info-value{flex:1;color:var(--kendo-color-on-app-surface, #212529)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: NumericTextBoxModule }, { kind: "ngmodule", type: DropDownsModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i1$4.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "messages", "dialogMessages", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
8721
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:16px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.config-card{padding:12px 16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.card-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.card-header kendo-svgicon{color:var(--kendo-color-primary, #0d6efd)}.card-title{font-weight:600;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.card-count{color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.card-content{display:flex;align-items:center;justify-content:space-between;gap:16px}.config-summary{margin:0;font-size:.85rem;color:var(--kendo-color-on-app-surface, #212529);flex:1}.filters-card .card-content{flex-direction:column;align-items:stretch}.filter-content{width:100%}.options-card .options-content{display:flex;gap:24px;align-items:flex-end}.checkbox-field{flex-direction:row;align-items:center}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;color:var(--kendo-color-on-app-surface, #212529)}.data-source-type .radio-group{display:flex;gap:24px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400;color:var(--kendo-color-on-app-surface, #212529)}.radio-label span{color:var(--kendo-color-on-app-surface, #212529)}.query-info{flex-direction:column;align-items:stretch;gap:8px}.info-row{display:flex;gap:8px}.info-label{font-weight:600;min-width:100px;color:var(--kendo-color-subtle, #6c757d)}.info-value{flex:1;color:var(--kendo-color-on-app-surface, #212529)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: NumericTextBoxModule }, { kind: "ngmodule", type: DropDownsModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i1$4.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "messages", "dialogMessages", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled", "acceptFamilies"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
8302
8722
|
}
|
|
8303
8723
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TableConfigDialogComponent, decorators: [{
|
|
8304
8724
|
type: Component,
|
|
@@ -8545,6 +8965,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
8545
8965
|
type: Input
|
|
8546
8966
|
}], initialQueryName: [{
|
|
8547
8967
|
type: Input
|
|
8968
|
+
}], initialQueryFamily: [{
|
|
8969
|
+
type: Input
|
|
8548
8970
|
}] } });
|
|
8549
8971
|
|
|
8550
8972
|
class GaugeWidgetComponent {
|
|
@@ -8563,7 +8985,12 @@ class GaugeWidgetComponent {
|
|
|
8563
8985
|
},
|
|
8564
8986
|
];
|
|
8565
8987
|
dataService = inject(DashboardDataService);
|
|
8566
|
-
|
|
8988
|
+
queryExecutor = inject(QueryExecutorService);
|
|
8989
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
8990
|
+
'RtAggregationQueryRow',
|
|
8991
|
+
'RtGroupingAggregationQueryRow',
|
|
8992
|
+
'StreamDataQueryRow'
|
|
8993
|
+
]);
|
|
8567
8994
|
stateService = inject(MeshBoardStateService);
|
|
8568
8995
|
variableService = inject(MeshBoardVariableService);
|
|
8569
8996
|
config;
|
|
@@ -8673,43 +9100,29 @@ class GaugeWidgetComponent {
|
|
|
8673
9100
|
this._isLoading.set(true);
|
|
8674
9101
|
this._error.set(null);
|
|
8675
9102
|
try {
|
|
8676
|
-
// Convert widget filters to GraphQL format
|
|
8677
9103
|
const fieldFilter = this.convertFiltersToDto(this.config.filters);
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
9104
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
9105
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
9106
|
+
// unconditionally because the runtime path ignores it.
|
|
9107
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
9108
|
+
const result = await firstValueFrom(this.queryExecutor.execute(dataSource.queryFamily, dataSource.queryRtId, {
|
|
9109
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
9110
|
+
streamDataArgs
|
|
8683
9111
|
}).pipe(catchError(err => {
|
|
8684
9112
|
console.error('Error loading Gauge query data:', err);
|
|
8685
9113
|
throw err;
|
|
8686
9114
|
})));
|
|
8687
|
-
const queryItems = result.data?.runtime?.runtimeQuery?.items ?? [];
|
|
8688
|
-
if (queryItems.length === 0) {
|
|
8689
|
-
this._error.set('Query returned no results');
|
|
8690
|
-
this._isLoading.set(false);
|
|
8691
|
-
return;
|
|
8692
|
-
}
|
|
8693
|
-
const queryResult = queryItems[0];
|
|
8694
|
-
if (!queryResult) {
|
|
8695
|
-
this._error.set('Query returned no results');
|
|
8696
|
-
this._isLoading.set(false);
|
|
8697
|
-
return;
|
|
8698
|
-
}
|
|
8699
9115
|
let value = 0;
|
|
8700
9116
|
const queryMode = this.config.queryMode ?? 'simpleCount';
|
|
8701
9117
|
switch (queryMode) {
|
|
8702
9118
|
case 'simpleCount':
|
|
8703
|
-
|
|
8704
|
-
value = queryResult.rows?.totalCount ?? 0;
|
|
9119
|
+
value = result.totalCount;
|
|
8705
9120
|
break;
|
|
8706
9121
|
case 'aggregation':
|
|
8707
|
-
|
|
8708
|
-
value = this.extractAggregationValue(queryResult);
|
|
9122
|
+
value = this.extractAggregationValue(result);
|
|
8709
9123
|
break;
|
|
8710
9124
|
case 'groupedAggregation':
|
|
8711
|
-
|
|
8712
|
-
value = this.extractGroupedAggregationValue(queryResult);
|
|
9125
|
+
value = this.extractGroupedAggregationValue(result);
|
|
8713
9126
|
break;
|
|
8714
9127
|
}
|
|
8715
9128
|
// Create a synthetic entity with the value
|
|
@@ -8728,48 +9141,38 @@ class GaugeWidgetComponent {
|
|
|
8728
9141
|
this._isLoading.set(false);
|
|
8729
9142
|
}
|
|
8730
9143
|
}
|
|
9144
|
+
buildStreamDataArgs() {
|
|
9145
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
9146
|
+
if (!range) {
|
|
9147
|
+
return undefined;
|
|
9148
|
+
}
|
|
9149
|
+
return { from: range.from, to: range.to };
|
|
9150
|
+
}
|
|
8731
9151
|
extractAggregationValue(queryResult) {
|
|
8732
|
-
const
|
|
8733
|
-
const supportedRowTypes = ['RtAggregationQueryRow', 'RtGroupingAggregationQueryRow'];
|
|
8734
|
-
// Get the first row
|
|
8735
|
-
const firstRow = rows.find(row => row && supportedRowTypes.includes(row.__typename ?? ''));
|
|
9152
|
+
const firstRow = queryResult.rows.find(row => GaugeWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''));
|
|
8736
9153
|
if (!firstRow)
|
|
8737
9154
|
return 0;
|
|
8738
|
-
const queryRow = firstRow;
|
|
8739
|
-
const cells = queryRow.cells?.items ?? [];
|
|
8740
|
-
// Find the value field or use the first numeric cell
|
|
8741
9155
|
const valueField = this.config.queryValueField;
|
|
8742
|
-
for (const cell of cells) {
|
|
8743
|
-
if (!cell?.attributePath)
|
|
8744
|
-
continue;
|
|
9156
|
+
for (const cell of firstRow.cells) {
|
|
8745
9157
|
if (valueField && matchesAttributePath(cell.attributePath, valueField)) {
|
|
8746
9158
|
return this.parseNumericValue(cell.value);
|
|
8747
9159
|
}
|
|
8748
9160
|
}
|
|
8749
|
-
|
|
8750
|
-
const firstCell = cells.find(c => c !== null);
|
|
8751
|
-
return firstCell ? this.parseNumericValue(firstCell.value) : 0;
|
|
9161
|
+
return firstRow.cells.length > 0 ? this.parseNumericValue(firstRow.cells[0].value) : 0;
|
|
8752
9162
|
}
|
|
8753
9163
|
extractGroupedAggregationValue(queryResult) {
|
|
8754
|
-
const rows = queryResult.rows?.items ?? [];
|
|
8755
|
-
const supportedRowTypes = ['RtGroupingAggregationQueryRow', 'RtAggregationQueryRow'];
|
|
8756
9164
|
const categoryField = this.config.queryCategoryField;
|
|
8757
9165
|
const categoryValue = this.config.queryCategoryValue;
|
|
8758
9166
|
const valueField = this.config.queryValueField;
|
|
8759
9167
|
if (!categoryField || !categoryValue || !valueField) {
|
|
8760
9168
|
return 0;
|
|
8761
9169
|
}
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
if (!row || !supportedRowTypes.includes(row.__typename ?? ''))
|
|
9170
|
+
for (const row of queryResult.rows) {
|
|
9171
|
+
if (!GaugeWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''))
|
|
8765
9172
|
continue;
|
|
8766
|
-
const queryRow = row;
|
|
8767
|
-
const cells = queryRow.cells?.items ?? [];
|
|
8768
9173
|
let categoryMatch = false;
|
|
8769
9174
|
let value = 0;
|
|
8770
|
-
for (const cell of cells) {
|
|
8771
|
-
if (!cell?.attributePath)
|
|
8772
|
-
continue;
|
|
9175
|
+
for (const cell of row.cells) {
|
|
8773
9176
|
if (matchesAttributePath(cell.attributePath, categoryField) && String(cell.value) === categoryValue) {
|
|
8774
9177
|
categoryMatch = true;
|
|
8775
9178
|
}
|
|
@@ -9157,8 +9560,18 @@ class GaugeConfigDialogComponent {
|
|
|
9157
9560
|
getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
|
|
9158
9561
|
ckTypeSelectorService = inject(CkTypeSelectorService);
|
|
9159
9562
|
attributeSelectorService = inject(AttributeSelectorService);
|
|
9160
|
-
executeRuntimeQueryGQL = inject(ExecuteRuntimeQueryDtoGQL);
|
|
9161
9563
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
9564
|
+
queryExecutor = inject(QueryExecutorService);
|
|
9565
|
+
/**
|
|
9566
|
+
* Row __typenames the dialog recognises when collecting distinct category
|
|
9567
|
+
* values from a query result for the grouped-aggregation category picker.
|
|
9568
|
+
*/
|
|
9569
|
+
static INTROSPECTION_ROW_TYPES = new Set([
|
|
9570
|
+
'RtSimpleQueryRow',
|
|
9571
|
+
'RtAggregationQueryRow',
|
|
9572
|
+
'RtGroupingAggregationQueryRow',
|
|
9573
|
+
'StreamDataQueryRow'
|
|
9574
|
+
]);
|
|
9162
9575
|
meshBoardStateService = inject(MeshBoardStateService);
|
|
9163
9576
|
windowRef = inject(WindowRef);
|
|
9164
9577
|
ckTypeSelectorInput;
|
|
@@ -9181,6 +9594,7 @@ class GaugeConfigDialogComponent {
|
|
|
9181
9594
|
initialDataSourceType;
|
|
9182
9595
|
initialQueryRtId;
|
|
9183
9596
|
initialQueryName;
|
|
9597
|
+
initialQueryFamily;
|
|
9184
9598
|
initialQueryMode;
|
|
9185
9599
|
initialQueryValueField;
|
|
9186
9600
|
initialQueryCategoryField;
|
|
@@ -9428,6 +9842,7 @@ class GaugeConfigDialogComponent {
|
|
|
9428
9842
|
}))
|
|
9429
9843
|
: undefined;
|
|
9430
9844
|
if (this.dataSourceType === 'persistentQuery' && this.selectedPersistentQuery) {
|
|
9845
|
+
const family = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
9431
9846
|
this.windowRef.close({
|
|
9432
9847
|
dataSourceType: 'persistentQuery',
|
|
9433
9848
|
ckTypeId: '',
|
|
@@ -9435,6 +9850,7 @@ class GaugeConfigDialogComponent {
|
|
|
9435
9850
|
valueAttribute: '',
|
|
9436
9851
|
queryRtId: this.selectedPersistentQuery.rtId,
|
|
9437
9852
|
queryName: this.selectedPersistentQuery.name,
|
|
9853
|
+
queryFamily: family,
|
|
9438
9854
|
queryMode: this.queryMode,
|
|
9439
9855
|
queryValueField: this.form.queryValueField || undefined,
|
|
9440
9856
|
queryCategoryField: this.form.queryCategoryField || undefined,
|
|
@@ -9507,31 +9923,13 @@ class GaugeConfigDialogComponent {
|
|
|
9507
9923
|
}
|
|
9508
9924
|
async loadQueryColumnsAndValues(queryRtId) {
|
|
9509
9925
|
this.isLoadingQueryColumns = true;
|
|
9926
|
+
// queryFamily may be undefined when the selected query metadata is missing —
|
|
9927
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
9928
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
9510
9929
|
try {
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
variables: {
|
|
9515
|
-
rtId: queryRtId
|
|
9516
|
-
}
|
|
9517
|
-
}));
|
|
9518
|
-
const queryItems = result.data?.runtime?.runtimeQuery?.items ?? [];
|
|
9519
|
-
if (queryItems.length > 0 && queryItems[0]) {
|
|
9520
|
-
const columns = queryItems[0].columns ?? [];
|
|
9521
|
-
const filteredColumns = columns
|
|
9522
|
-
.filter((c) => c !== null);
|
|
9523
|
-
// Engine emits column attributePath in wire form for aggregation / grouping
|
|
9524
|
-
// columns, so the picker can use it as both label and stored value verbatim.
|
|
9525
|
-
this.queryColumns = filteredColumns.map(c => ({
|
|
9526
|
-
attributePath: c.attributePath ?? '',
|
|
9527
|
-
attributeValueType: c.attributeValueType ?? '',
|
|
9528
|
-
aggregationType: c.aggregationType ?? null
|
|
9529
|
-
}));
|
|
9530
|
-
// Category values for grouped aggregation are loaded on-demand by
|
|
9531
|
-
// loadCategoryValuesForField — only when a categoryField is actually selected.
|
|
9532
|
-
if (this.queryMode === 'groupedAggregation' && this.form.queryCategoryField) {
|
|
9533
|
-
await this.loadCategoryValuesForField(queryRtId, this.form.queryCategoryField);
|
|
9534
|
-
}
|
|
9930
|
+
this.queryColumns = await this.fetchColumnsForFamily(family, queryRtId);
|
|
9931
|
+
if (this.queryColumns.length > 0 && this.queryMode === 'groupedAggregation' && this.form.queryCategoryField) {
|
|
9932
|
+
await this.loadCategoryValuesForField(queryRtId, this.form.queryCategoryField);
|
|
9535
9933
|
}
|
|
9536
9934
|
}
|
|
9537
9935
|
catch (error) {
|
|
@@ -9542,6 +9940,36 @@ class GaugeConfigDialogComponent {
|
|
|
9542
9940
|
this.isLoadingQueryColumns = false;
|
|
9543
9941
|
}
|
|
9544
9942
|
}
|
|
9943
|
+
/**
|
|
9944
|
+
* Runtime queries use the metadata-only resolver (no aggregation executed);
|
|
9945
|
+
* stream-data queries fall back to executing the query with `first: 1`.
|
|
9946
|
+
* When `family` is unknown (legacy configs), the executor resolves it once
|
|
9947
|
+
* by rtId lookup.
|
|
9948
|
+
*/
|
|
9949
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
9950
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
9951
|
+
if (resolvedFamily === 'runtime') {
|
|
9952
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
9953
|
+
variables: { rtId }
|
|
9954
|
+
}));
|
|
9955
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
9956
|
+
if (!queryItem)
|
|
9957
|
+
return [];
|
|
9958
|
+
return (queryItem.columns ?? [])
|
|
9959
|
+
.filter((c) => c !== null)
|
|
9960
|
+
.map(c => ({
|
|
9961
|
+
attributePath: c.attributePath ?? '',
|
|
9962
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
9963
|
+
aggregationType: c.aggregationType ?? null
|
|
9964
|
+
}));
|
|
9965
|
+
}
|
|
9966
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
9967
|
+
return sdResult.columns.map(c => ({
|
|
9968
|
+
attributePath: c.attributePath,
|
|
9969
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
9970
|
+
aggregationType: c.aggregationType ?? null
|
|
9971
|
+
}));
|
|
9972
|
+
}
|
|
9545
9973
|
async onCategoryFieldChange(categoryField) {
|
|
9546
9974
|
this.form.queryCategoryField = categoryField;
|
|
9547
9975
|
this.form.queryCategoryValue = '';
|
|
@@ -9552,37 +9980,24 @@ class GaugeConfigDialogComponent {
|
|
|
9552
9980
|
}
|
|
9553
9981
|
async loadCategoryValuesForField(queryRtId, categoryField) {
|
|
9554
9982
|
this.isLoadingCategoryValues = true;
|
|
9983
|
+
// family may be undefined — the executor falls back to a lookup.
|
|
9984
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
9555
9985
|
try {
|
|
9556
|
-
const result = await firstValueFrom(this.
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
9564
|
-
const queryResult = queryItems[0];
|
|
9565
|
-
const rows = queryResult.rows?.items ?? [];
|
|
9566
|
-
const supportedRowTypes = ['RtSimpleQueryRow', 'RtAggregationQueryRow', 'RtGroupingAggregationQueryRow'];
|
|
9567
|
-
const values = new Set();
|
|
9568
|
-
for (const row of rows) {
|
|
9569
|
-
if (!row || !supportedRowTypes.includes(row.__typename ?? ''))
|
|
9570
|
-
continue;
|
|
9571
|
-
const queryRow = row;
|
|
9572
|
-
const cells = queryRow.cells?.items ?? [];
|
|
9573
|
-
for (const cell of cells) {
|
|
9574
|
-
if (!cell?.attributePath)
|
|
9575
|
-
continue;
|
|
9576
|
-
if (matchesAttributePath(cell.attributePath, categoryField) && cell.value !== null && cell.value !== undefined) {
|
|
9577
|
-
values.add(String(cell.value));
|
|
9578
|
-
}
|
|
9986
|
+
const result = await firstValueFrom(this.queryExecutor.execute(family, queryRtId, { first: 100 }));
|
|
9987
|
+
const values = new Set();
|
|
9988
|
+
for (const row of result.rows) {
|
|
9989
|
+
if (!GaugeConfigDialogComponent.INTROSPECTION_ROW_TYPES.has(row.__typename ?? ''))
|
|
9990
|
+
continue;
|
|
9991
|
+
for (const cell of row.cells) {
|
|
9992
|
+
if (matchesAttributePath(cell.attributePath, categoryField) && cell.value !== null && cell.value !== undefined) {
|
|
9993
|
+
values.add(String(cell.value));
|
|
9579
9994
|
}
|
|
9580
9995
|
}
|
|
9581
|
-
this.categoryValues = Array.from(values).map(v => ({
|
|
9582
|
-
value: v,
|
|
9583
|
-
displayValue: v
|
|
9584
|
-
}));
|
|
9585
9996
|
}
|
|
9997
|
+
this.categoryValues = Array.from(values).map(v => ({
|
|
9998
|
+
value: v,
|
|
9999
|
+
displayValue: v
|
|
10000
|
+
}));
|
|
9586
10001
|
}
|
|
9587
10002
|
catch (error) {
|
|
9588
10003
|
console.error('Error loading category values:', error);
|
|
@@ -9599,7 +10014,7 @@ class GaugeConfigDialogComponent {
|
|
|
9599
10014
|
this.filters = updatedFilters;
|
|
9600
10015
|
}
|
|
9601
10016
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GaugeConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9602
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: GaugeConfigDialogComponent, isStandalone: true, selector: "mm-gauge-config-dialog", inputs: { initialCkTypeId: "initialCkTypeId", initialRtId: "initialRtId", initialGaugeType: "initialGaugeType", initialValueAttribute: "initialValueAttribute", initialLabelAttribute: "initialLabelAttribute", initialMin: "initialMin", initialMax: "initialMax", initialRanges: "initialRanges", initialShowLabel: "initialShowLabel", initialPrefix: "initialPrefix", initialSuffix: "initialSuffix", initialReverse: "initialReverse", initialDataSourceType: "initialDataSourceType", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryMode: "initialQueryMode", initialQueryValueField: "initialQueryValueField", initialQueryCategoryField: "initialQueryCategoryField", initialQueryCategoryValue: "initialQueryCategoryValue", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "ckTypeSelectorInput", first: true, predicate: ["ckTypeSelector"], descendants: true }, { propertyName: "entitySelectorInput", first: true, predicate: ["entitySelector"], descendants: true }, { propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
10017
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: GaugeConfigDialogComponent, isStandalone: true, selector: "mm-gauge-config-dialog", inputs: { initialCkTypeId: "initialCkTypeId", initialRtId: "initialRtId", initialGaugeType: "initialGaugeType", initialValueAttribute: "initialValueAttribute", initialLabelAttribute: "initialLabelAttribute", initialMin: "initialMin", initialMax: "initialMax", initialRanges: "initialRanges", initialShowLabel: "initialShowLabel", initialPrefix: "initialPrefix", initialSuffix: "initialSuffix", initialReverse: "initialReverse", initialDataSourceType: "initialDataSourceType", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryFamily: "initialQueryFamily", initialQueryMode: "initialQueryMode", initialQueryValueField: "initialQueryValueField", initialQueryCategoryField: "initialQueryCategoryField", initialQueryCategoryValue: "initialQueryCategoryValue", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "ckTypeSelectorInput", first: true, predicate: ["ckTypeSelector"], descendants: true }, { propertyName: "entitySelectorInput", first: true, predicate: ["entitySelector"], descendants: true }, { propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
9603
10018
|
<div class="config-container">
|
|
9604
10019
|
|
|
9605
10020
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -9997,7 +10412,7 @@ class GaugeConfigDialogComponent {
|
|
|
9997
10412
|
</button>
|
|
9998
10413
|
</div>
|
|
9999
10414
|
</div>
|
|
10000
|
-
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;padding:16px;position:relative;flex:1;overflow-y:auto}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field.flex-1{flex:1}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.form-section h4{margin:0 0 16px;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-row{display:flex;gap:16px}.checkbox-label{margin-left:8px;font-weight:400}.gauge-type-item{display:flex;flex-direction:column;gap:2px}.gauge-type-label{font-weight:500}.gauge-type-desc{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d)}.attribute-item{display:flex;justify-content:space-between;align-items:center;gap:8px;width:100%}.attribute-path{flex:1}.attribute-type{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);background:var(--kendo-color-surface-alt, #f8f9fa);padding:2px 6px;border-radius:3px}.range-row{display:flex;align-items:center;gap:8px;margin-bottom:8px}.range-input{width:80px}.range-separator{color:var(--kendo-color-subtle, #6c757d)}.color-picker{width:40px;height:32px;padding:2px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;cursor:pointer}.mode-toggle{display:flex;gap:8px}.mode-toggle button{flex:1}.required{color:var(--kendo-color-error, #dc3545)}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "messages", "dialogMessages", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "initialDisplayValue", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
10415
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;padding:16px;position:relative;flex:1;overflow-y:auto}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field.flex-1{flex:1}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.form-section h4{margin:0 0 16px;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-row{display:flex;gap:16px}.checkbox-label{margin-left:8px;font-weight:400}.gauge-type-item{display:flex;flex-direction:column;gap:2px}.gauge-type-label{font-weight:500}.gauge-type-desc{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d)}.attribute-item{display:flex;justify-content:space-between;align-items:center;gap:8px;width:100%}.attribute-path{flex:1}.attribute-type{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);background:var(--kendo-color-surface-alt, #f8f9fa);padding:2px 6px;border-radius:3px}.range-row{display:flex;align-items:center;gap:8px;margin-bottom:8px}.range-input{width:80px}.range-separator{color:var(--kendo-color-subtle, #6c757d)}.color-picker{width:40px;height:32px;padding:2px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;cursor:pointer}.mode-toggle{display:flex;gap:8px}.mode-toggle button{flex:1}.required{color:var(--kendo-color-error, #dc3545)}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "messages", "dialogMessages", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "initialDisplayValue", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled", "acceptFamilies"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
10001
10416
|
}
|
|
10002
10417
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GaugeConfigDialogComponent, decorators: [{
|
|
10003
10418
|
type: Component,
|
|
@@ -10450,6 +10865,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
10450
10865
|
type: Input
|
|
10451
10866
|
}], initialQueryName: [{
|
|
10452
10867
|
type: Input
|
|
10868
|
+
}], initialQueryFamily: [{
|
|
10869
|
+
type: Input
|
|
10453
10870
|
}], initialQueryMode: [{
|
|
10454
10871
|
type: Input
|
|
10455
10872
|
}], initialQueryValueField: [{
|
|
@@ -10463,7 +10880,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
10463
10880
|
}] } });
|
|
10464
10881
|
|
|
10465
10882
|
class PieChartWidgetComponent {
|
|
10466
|
-
|
|
10883
|
+
queryExecutor = inject(QueryExecutorService);
|
|
10884
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
10885
|
+
'RtSimpleQueryRow',
|
|
10886
|
+
'RtAggregationQueryRow',
|
|
10887
|
+
'RtGroupingAggregationQueryRow',
|
|
10888
|
+
'StreamDataQueryRow'
|
|
10889
|
+
]);
|
|
10467
10890
|
dataService = inject(MeshBoardDataService);
|
|
10468
10891
|
stateService = inject(MeshBoardStateService);
|
|
10469
10892
|
variableService = inject(MeshBoardVariableService);
|
|
@@ -10588,35 +11011,22 @@ class PieChartWidgetComponent {
|
|
|
10588
11011
|
* Note: isNotConfigured() check in loadData() ensures queryRtId is set.
|
|
10589
11012
|
*/
|
|
10590
11013
|
async loadPersistentQueryData(dataSource) {
|
|
10591
|
-
// Convert widget filters to GraphQL format
|
|
10592
11014
|
const fieldFilter = this.convertFiltersToDto(this.config.filters);
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
11015
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
11016
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
11017
|
+
// unconditionally because the runtime path ignores it.
|
|
11018
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
11019
|
+
const result = await firstValueFrom(this.queryExecutor.execute(dataSource.queryFamily, dataSource.queryRtId, {
|
|
11020
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
11021
|
+
streamDataArgs
|
|
10598
11022
|
}).pipe(catchError(err => {
|
|
10599
11023
|
console.error('Error loading Pie Chart data:', err);
|
|
10600
11024
|
throw err;
|
|
10601
11025
|
})));
|
|
10602
|
-
const queryItems = result.data?.runtime?.runtimeQuery?.items ?? [];
|
|
10603
|
-
if (queryItems.length === 0) {
|
|
10604
|
-
this._chartData.set([]);
|
|
10605
|
-
this._isLoading.set(false);
|
|
10606
|
-
return;
|
|
10607
|
-
}
|
|
10608
|
-
const queryResult = queryItems[0];
|
|
10609
|
-
if (!queryResult) {
|
|
10610
|
-
this._chartData.set([]);
|
|
10611
|
-
this._isLoading.set(false);
|
|
10612
|
-
return;
|
|
10613
|
-
}
|
|
10614
11026
|
// Extract columns to verify configured fields are present. Both forms (original CK
|
|
10615
11027
|
// path and engine wire-form) are accepted so saved configs survive the engine's
|
|
10616
11028
|
// switch to wire-form keys without a migration.
|
|
10617
|
-
const columnPaths =
|
|
10618
|
-
.filter((c) => c !== null)
|
|
10619
|
-
.map(c => c.attributePath ?? '');
|
|
11029
|
+
const columnPaths = result.columns.map(c => c.attributePath);
|
|
10620
11030
|
const categoryFieldPresent = columnPaths.some(p => matchesAttributePath(p, this.config.categoryField));
|
|
10621
11031
|
const valueFieldPresent = columnPaths.some(p => matchesAttributePath(p, this.config.valueField));
|
|
10622
11032
|
if (!categoryFieldPresent || !valueFieldPresent) {
|
|
@@ -10624,20 +11034,12 @@ class PieChartWidgetComponent {
|
|
|
10624
11034
|
this._isLoading.set(false);
|
|
10625
11035
|
return;
|
|
10626
11036
|
}
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
const supportedRowTypes = ['RtSimpleQueryRow', 'RtAggregationQueryRow', 'RtGroupingAggregationQueryRow'];
|
|
10630
|
-
const chartData = rows
|
|
10631
|
-
.filter((row) => row !== null)
|
|
10632
|
-
.filter(row => supportedRowTypes.includes(row.__typename ?? ''))
|
|
11037
|
+
const chartData = result.rows
|
|
11038
|
+
.filter(row => PieChartWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''))
|
|
10633
11039
|
.map(row => {
|
|
10634
|
-
const queryRow = row;
|
|
10635
|
-
const cells = queryRow.cells?.items ?? [];
|
|
10636
11040
|
let category = '';
|
|
10637
11041
|
let value = 0;
|
|
10638
|
-
for (const cell of cells) {
|
|
10639
|
-
if (!cell?.attributePath)
|
|
10640
|
-
continue;
|
|
11042
|
+
for (const cell of row.cells) {
|
|
10641
11043
|
if (matchesAttributePath(cell.attributePath, this.config.categoryField)) {
|
|
10642
11044
|
category = String(cell.value ?? '');
|
|
10643
11045
|
}
|
|
@@ -10652,6 +11054,13 @@ class PieChartWidgetComponent {
|
|
|
10652
11054
|
this._chartData.set(chartData);
|
|
10653
11055
|
this._isLoading.set(false);
|
|
10654
11056
|
}
|
|
11057
|
+
buildStreamDataArgs() {
|
|
11058
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
11059
|
+
if (!range) {
|
|
11060
|
+
return undefined;
|
|
11061
|
+
}
|
|
11062
|
+
return { from: range.from, to: range.to };
|
|
11063
|
+
}
|
|
10655
11064
|
/**
|
|
10656
11065
|
* Converts widget filter configuration to GraphQL FieldFilterDto format.
|
|
10657
11066
|
* Resolves MeshBoard variables in filter values before conversion.
|
|
@@ -10755,6 +11164,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
10755
11164
|
*/
|
|
10756
11165
|
class PieChartConfigDialogComponent {
|
|
10757
11166
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
11167
|
+
queryExecutor = inject(QueryExecutorService);
|
|
10758
11168
|
stateService = inject(MeshBoardStateService);
|
|
10759
11169
|
windowRef = inject(WindowRef);
|
|
10760
11170
|
querySelector;
|
|
@@ -10762,6 +11172,7 @@ class PieChartConfigDialogComponent {
|
|
|
10762
11172
|
initialDataSourceType;
|
|
10763
11173
|
initialQueryRtId;
|
|
10764
11174
|
initialQueryName;
|
|
11175
|
+
initialQueryFamily;
|
|
10765
11176
|
initialChartType;
|
|
10766
11177
|
initialCategoryField;
|
|
10767
11178
|
initialValueField;
|
|
@@ -10922,37 +11333,19 @@ class PieChartConfigDialogComponent {
|
|
|
10922
11333
|
}
|
|
10923
11334
|
async loadQueryColumns(queryRtId) {
|
|
10924
11335
|
this.isLoadingColumns = true;
|
|
11336
|
+
// family may be undefined when the selected query metadata is missing —
|
|
11337
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
11338
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
10925
11339
|
try {
|
|
10926
|
-
|
|
10927
|
-
//
|
|
10928
|
-
|
|
10929
|
-
|
|
10930
|
-
|
|
10931
|
-
|
|
10932
|
-
|
|
10933
|
-
|
|
10934
|
-
|
|
10935
|
-
if (queryItems.length > 0 && queryItems[0]) {
|
|
10936
|
-
const columns = queryItems[0].columns ?? [];
|
|
10937
|
-
const filteredColumns = columns
|
|
10938
|
-
.filter((c) => c !== null);
|
|
10939
|
-
// Engine emits column attributePath in wire form for aggregation / grouping
|
|
10940
|
-
// columns (e.g. `quantity_sum`, `operatingstatus`); picker uses it verbatim.
|
|
10941
|
-
this.queryColumns = filteredColumns.map(c => ({
|
|
10942
|
-
attributePath: c.attributePath ?? '',
|
|
10943
|
-
attributeValueType: c.attributeValueType ?? '',
|
|
10944
|
-
aggregationType: c.aggregationType ?? null
|
|
10945
|
-
}));
|
|
10946
|
-
// Auto-select fields if only 2 columns (typical for grouped aggregations)
|
|
10947
|
-
if (this.queryColumns.length === 2 && !this.form.categoryField && !this.form.valueField) {
|
|
10948
|
-
// Assume first column is category, second is value (typical pattern)
|
|
10949
|
-
const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
|
|
10950
|
-
const valueColumn = this.queryColumns.find(c => numericTypes.includes(c.attributeValueType));
|
|
10951
|
-
const categoryColumn = this.queryColumns.find(c => c !== valueColumn);
|
|
10952
|
-
if (valueColumn && categoryColumn) {
|
|
10953
|
-
this.form.valueField = valueColumn.attributePath;
|
|
10954
|
-
this.form.categoryField = categoryColumn.attributePath;
|
|
10955
|
-
}
|
|
11340
|
+
this.queryColumns = await this.fetchColumnsForFamily(family, queryRtId);
|
|
11341
|
+
// Auto-select fields if only 2 columns (typical for grouped aggregations)
|
|
11342
|
+
if (this.queryColumns.length === 2 && !this.form.categoryField && !this.form.valueField) {
|
|
11343
|
+
const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
|
|
11344
|
+
const valueColumn = this.queryColumns.find(c => numericTypes.includes(c.attributeValueType));
|
|
11345
|
+
const categoryColumn = this.queryColumns.find(c => c !== valueColumn);
|
|
11346
|
+
if (valueColumn && categoryColumn) {
|
|
11347
|
+
this.form.valueField = valueColumn.attributePath;
|
|
11348
|
+
this.form.categoryField = categoryColumn.attributePath;
|
|
10956
11349
|
}
|
|
10957
11350
|
}
|
|
10958
11351
|
}
|
|
@@ -10964,6 +11357,35 @@ class PieChartConfigDialogComponent {
|
|
|
10964
11357
|
this.isLoadingColumns = false;
|
|
10965
11358
|
}
|
|
10966
11359
|
}
|
|
11360
|
+
/**
|
|
11361
|
+
* Runtime queries use the metadata-only resolver (no aggregation executed);
|
|
11362
|
+
* stream-data queries fall back to executing the query with `first: 1`
|
|
11363
|
+
* because the SD path has no dedicated column-introspection endpoint today.
|
|
11364
|
+
*/
|
|
11365
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
11366
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
11367
|
+
if (resolvedFamily === 'runtime') {
|
|
11368
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
11369
|
+
variables: { rtId }
|
|
11370
|
+
}));
|
|
11371
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
11372
|
+
if (!queryItem)
|
|
11373
|
+
return [];
|
|
11374
|
+
return (queryItem.columns ?? [])
|
|
11375
|
+
.filter((c) => c !== null)
|
|
11376
|
+
.map(c => ({
|
|
11377
|
+
attributePath: c.attributePath ?? '',
|
|
11378
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
11379
|
+
aggregationType: c.aggregationType ?? null
|
|
11380
|
+
}));
|
|
11381
|
+
}
|
|
11382
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
11383
|
+
return sdResult.columns.map(c => ({
|
|
11384
|
+
attributePath: c.attributePath,
|
|
11385
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
11386
|
+
aggregationType: c.aggregationType ?? null
|
|
11387
|
+
}));
|
|
11388
|
+
}
|
|
10967
11389
|
onFiltersChange(updatedFilters) {
|
|
10968
11390
|
this.filters = updatedFilters;
|
|
10969
11391
|
}
|
|
@@ -10993,6 +11415,7 @@ class PieChartConfigDialogComponent {
|
|
|
10993
11415
|
return;
|
|
10994
11416
|
result.queryRtId = this.selectedPersistentQuery.rtId;
|
|
10995
11417
|
result.queryName = this.selectedPersistentQuery.name;
|
|
11418
|
+
result.queryFamily = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
10996
11419
|
}
|
|
10997
11420
|
else if (this.form.dataSourceType === 'constructionKitQuery') {
|
|
10998
11421
|
result.ckQueryTarget = this.form.ckQueryTarget;
|
|
@@ -11004,7 +11427,7 @@ class PieChartConfigDialogComponent {
|
|
|
11004
11427
|
this.windowRef.close();
|
|
11005
11428
|
}
|
|
11006
11429
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PieChartConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11007
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: PieChartConfigDialogComponent, isStandalone: true, selector: "mm-pie-chart-config-dialog", inputs: { initialDataSourceType: "initialDataSourceType", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialChartType: "initialChartType", initialCategoryField: "initialCategoryField", initialValueField: "initialValueField", initialShowLabels: "initialShowLabels", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialCkQueryTarget: "initialCkQueryTarget", initialCkGroupBy: "initialCkGroupBy", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
11430
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: PieChartConfigDialogComponent, isStandalone: true, selector: "mm-pie-chart-config-dialog", inputs: { initialDataSourceType: "initialDataSourceType", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryFamily: "initialQueryFamily", initialChartType: "initialChartType", initialCategoryField: "initialCategoryField", initialValueField: "initialValueField", initialShowLabels: "initialShowLabels", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialCkQueryTarget: "initialCkQueryTarget", initialCkGroupBy: "initialCkGroupBy", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
11008
11431
|
<div class="config-container">
|
|
11009
11432
|
|
|
11010
11433
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -11202,7 +11625,7 @@ class PieChartConfigDialogComponent {
|
|
|
11202
11625
|
</button>
|
|
11203
11626
|
</div>
|
|
11204
11627
|
</div>
|
|
11205
|
-
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;flex:1;overflow-y:auto;gap:20px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.radio-group{display:flex;gap:24px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
11628
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;flex:1;overflow-y:auto;gap:20px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.radio-group{display:flex;gap:24px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled", "acceptFamilies"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
11206
11629
|
}
|
|
11207
11630
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PieChartConfigDialogComponent, decorators: [{
|
|
11208
11631
|
type: Component,
|
|
@@ -11424,6 +11847,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
11424
11847
|
type: Input
|
|
11425
11848
|
}], initialQueryName: [{
|
|
11426
11849
|
type: Input
|
|
11850
|
+
}], initialQueryFamily: [{
|
|
11851
|
+
type: Input
|
|
11427
11852
|
}], initialChartType: [{
|
|
11428
11853
|
type: Input
|
|
11429
11854
|
}], initialCategoryField: [{
|
|
@@ -11453,7 +11878,13 @@ const CHART_TYPE_MAPPING = {
|
|
|
11453
11878
|
stackedBar100: { type: 'bar', stack: '100%' }
|
|
11454
11879
|
};
|
|
11455
11880
|
class BarChartWidgetComponent {
|
|
11456
|
-
|
|
11881
|
+
queryExecutor = inject(QueryExecutorService);
|
|
11882
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
11883
|
+
'RtSimpleQueryRow',
|
|
11884
|
+
'RtAggregationQueryRow',
|
|
11885
|
+
'RtGroupingAggregationQueryRow',
|
|
11886
|
+
'StreamDataQueryRow'
|
|
11887
|
+
]);
|
|
11457
11888
|
stateService = inject(MeshBoardStateService);
|
|
11458
11889
|
variableService = inject(MeshBoardVariableService);
|
|
11459
11890
|
config;
|
|
@@ -11562,38 +11993,19 @@ class BarChartWidgetComponent {
|
|
|
11562
11993
|
this._isLoading.set(true);
|
|
11563
11994
|
this._error.set(null);
|
|
11564
11995
|
try {
|
|
11565
|
-
// Convert widget filters to GraphQL format
|
|
11566
11996
|
const fieldFilter = this.convertFiltersToDto(this.config.filters);
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
11570
|
-
|
|
11571
|
-
|
|
11997
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
11998
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
11999
|
+
// unconditionally because the runtime path ignores it.
|
|
12000
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
12001
|
+
const result = await firstValueFrom(this.queryExecutor.execute(queryDataSource.queryFamily, queryDataSource.queryRtId, {
|
|
12002
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
12003
|
+
streamDataArgs
|
|
11572
12004
|
}).pipe(catchError(err => {
|
|
11573
12005
|
console.error('Error loading Bar Chart data:', err);
|
|
11574
12006
|
throw err;
|
|
11575
12007
|
})));
|
|
11576
|
-
const
|
|
11577
|
-
if (queryItems.length === 0) {
|
|
11578
|
-
this._categories.set([]);
|
|
11579
|
-
this._seriesData.set([]);
|
|
11580
|
-
this._isLoading.set(false);
|
|
11581
|
-
return;
|
|
11582
|
-
}
|
|
11583
|
-
const queryResult = queryItems[0];
|
|
11584
|
-
if (!queryResult) {
|
|
11585
|
-
this._categories.set([]);
|
|
11586
|
-
this._seriesData.set([]);
|
|
11587
|
-
this._isLoading.set(false);
|
|
11588
|
-
return;
|
|
11589
|
-
}
|
|
11590
|
-
// Extract rows
|
|
11591
|
-
const rows = queryResult.rows?.items ?? [];
|
|
11592
|
-
const supportedRowTypes = ['RtSimpleQueryRow', 'RtAggregationQueryRow', 'RtGroupingAggregationQueryRow'];
|
|
11593
|
-
const filteredRows = rows
|
|
11594
|
-
.filter((row) => row !== null)
|
|
11595
|
-
.filter(row => supportedRowTypes.includes(row.__typename ?? ''));
|
|
11596
|
-
// Process data based on mode
|
|
12008
|
+
const filteredRows = result.rows.filter(row => BarChartWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''));
|
|
11597
12009
|
if (this.isDynamicSeriesMode()) {
|
|
11598
12010
|
this.processDynamicSeriesData(filteredRows);
|
|
11599
12011
|
}
|
|
@@ -11608,6 +12020,13 @@ class BarChartWidgetComponent {
|
|
|
11608
12020
|
this._isLoading.set(false);
|
|
11609
12021
|
}
|
|
11610
12022
|
}
|
|
12023
|
+
buildStreamDataArgs() {
|
|
12024
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
12025
|
+
if (!range) {
|
|
12026
|
+
return undefined;
|
|
12027
|
+
}
|
|
12028
|
+
return { from: range.from, to: range.to };
|
|
12029
|
+
}
|
|
11611
12030
|
/**
|
|
11612
12031
|
* Processes data in Static Series Mode.
|
|
11613
12032
|
* Each series in config.series corresponds to a separate numeric field.
|
|
@@ -11620,13 +12039,9 @@ class BarChartWidgetComponent {
|
|
|
11620
12039
|
seriesMap.set(seriesConfig.field, []);
|
|
11621
12040
|
}
|
|
11622
12041
|
for (const row of filteredRows) {
|
|
11623
|
-
const queryRow = row;
|
|
11624
|
-
const cells = queryRow.cells?.items ?? [];
|
|
11625
12042
|
let categoryValue = '';
|
|
11626
12043
|
const rowValues = new Map();
|
|
11627
|
-
for (const cell of cells) {
|
|
11628
|
-
if (!cell?.attributePath)
|
|
11629
|
-
continue;
|
|
12044
|
+
for (const cell of row.cells) {
|
|
11630
12045
|
if (matchesAttributePath(cell.attributePath, this.config.categoryField)) {
|
|
11631
12046
|
categoryValue = this.formatCategoryValue(cell.value);
|
|
11632
12047
|
}
|
|
@@ -11683,14 +12098,10 @@ class BarChartWidgetComponent {
|
|
|
11683
12098
|
const allCategories = new Set();
|
|
11684
12099
|
const allSeriesGroups = new Set();
|
|
11685
12100
|
for (const row of filteredRows) {
|
|
11686
|
-
const queryRow = row;
|
|
11687
|
-
const cells = queryRow.cells?.items ?? [];
|
|
11688
12101
|
let categoryValue = '';
|
|
11689
12102
|
let seriesGroupValue = '';
|
|
11690
12103
|
let numericValue = 0;
|
|
11691
|
-
for (const cell of cells) {
|
|
11692
|
-
if (!cell?.attributePath)
|
|
11693
|
-
continue;
|
|
12104
|
+
for (const cell of row.cells) {
|
|
11694
12105
|
if (matchesAttributePath(cell.attributePath, categoryField)) {
|
|
11695
12106
|
categoryValue = this.formatCategoryValue(cell.value);
|
|
11696
12107
|
}
|
|
@@ -11941,12 +12352,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
11941
12352
|
*/
|
|
11942
12353
|
class BarChartConfigDialogComponent {
|
|
11943
12354
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
12355
|
+
queryExecutor = inject(QueryExecutorService);
|
|
11944
12356
|
stateService = inject(MeshBoardStateService);
|
|
11945
12357
|
windowRef = inject(WindowRef);
|
|
11946
12358
|
querySelector;
|
|
11947
12359
|
// Initial values for editing
|
|
11948
12360
|
initialQueryRtId;
|
|
11949
12361
|
initialQueryName;
|
|
12362
|
+
initialQueryFamily;
|
|
11950
12363
|
initialChartType;
|
|
11951
12364
|
initialCategoryField;
|
|
11952
12365
|
initialSeries;
|
|
@@ -12078,41 +12491,23 @@ class BarChartConfigDialogComponent {
|
|
|
12078
12491
|
}
|
|
12079
12492
|
async loadQueryColumns(queryRtId) {
|
|
12080
12493
|
this.isLoadingColumns = true;
|
|
12494
|
+
// family may be undefined when the selected query metadata is missing —
|
|
12495
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
12496
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
12081
12497
|
try {
|
|
12082
|
-
|
|
12083
|
-
//
|
|
12084
|
-
const
|
|
12085
|
-
|
|
12086
|
-
|
|
12498
|
+
this.queryColumns = await this.fetchColumnsForFamily(family, queryRtId);
|
|
12499
|
+
// Filter numeric and non-numeric columns
|
|
12500
|
+
const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
|
|
12501
|
+
this.numericColumns = this.queryColumns.filter(c => numericTypes.includes(c.attributeValueType));
|
|
12502
|
+
this.nonNumericColumns = this.queryColumns.filter(c => !numericTypes.includes(c.attributeValueType));
|
|
12503
|
+
// Auto-select fields if possible and not editing
|
|
12504
|
+
if (!this.initialQueryRtId && this.queryColumns.length >= 2) {
|
|
12505
|
+
const categoryColumn = this.queryColumns.find(c => !numericTypes.includes(c.attributeValueType));
|
|
12506
|
+
if (categoryColumn) {
|
|
12507
|
+
this.form.categoryField = categoryColumn.attributePath;
|
|
12087
12508
|
}
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
if (queryItems.length > 0 && queryItems[0]) {
|
|
12091
|
-
const columns = queryItems[0].columns ?? [];
|
|
12092
|
-
const filteredColumns = columns
|
|
12093
|
-
.filter((c) => c !== null);
|
|
12094
|
-
// Engine emits column attributePath in wire form for aggregation / grouping
|
|
12095
|
-
// columns (e.g. `quantity_sum`, `operatingstatus`); picker uses it verbatim.
|
|
12096
|
-
this.queryColumns = filteredColumns.map(c => ({
|
|
12097
|
-
attributePath: c.attributePath ?? '',
|
|
12098
|
-
attributeValueType: c.attributeValueType ?? '',
|
|
12099
|
-
aggregationType: c.aggregationType ?? null
|
|
12100
|
-
}));
|
|
12101
|
-
// Filter numeric and non-numeric columns
|
|
12102
|
-
const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
|
|
12103
|
-
this.numericColumns = this.queryColumns.filter(c => numericTypes.includes(c.attributeValueType));
|
|
12104
|
-
this.nonNumericColumns = this.queryColumns.filter(c => !numericTypes.includes(c.attributeValueType));
|
|
12105
|
-
// Auto-select fields if possible and not editing
|
|
12106
|
-
if (!this.initialQueryRtId && this.queryColumns.length >= 2) {
|
|
12107
|
-
// Find first non-numeric column for category
|
|
12108
|
-
const categoryColumn = this.queryColumns.find(c => !numericTypes.includes(c.attributeValueType));
|
|
12109
|
-
if (categoryColumn) {
|
|
12110
|
-
this.form.categoryField = categoryColumn.attributePath;
|
|
12111
|
-
}
|
|
12112
|
-
// Auto-select all numeric columns as series
|
|
12113
|
-
if (this.numericColumns.length > 0) {
|
|
12114
|
-
this.selectedSeriesFields = this.numericColumns.map(c => c.attributePath);
|
|
12115
|
-
}
|
|
12509
|
+
if (this.numericColumns.length > 0) {
|
|
12510
|
+
this.selectedSeriesFields = this.numericColumns.map(c => c.attributePath);
|
|
12116
12511
|
}
|
|
12117
12512
|
}
|
|
12118
12513
|
}
|
|
@@ -12126,6 +12521,35 @@ class BarChartConfigDialogComponent {
|
|
|
12126
12521
|
this.isLoadingColumns = false;
|
|
12127
12522
|
}
|
|
12128
12523
|
}
|
|
12524
|
+
/**
|
|
12525
|
+
* Runtime queries use the metadata-only resolver (no aggregation executed);
|
|
12526
|
+
* stream-data queries fall back to executing the query with `first: 1`
|
|
12527
|
+
* because the SD path has no dedicated column-introspection endpoint today.
|
|
12528
|
+
*/
|
|
12529
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
12530
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
12531
|
+
if (resolvedFamily === 'runtime') {
|
|
12532
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
12533
|
+
variables: { rtId }
|
|
12534
|
+
}));
|
|
12535
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
12536
|
+
if (!queryItem)
|
|
12537
|
+
return [];
|
|
12538
|
+
return (queryItem.columns ?? [])
|
|
12539
|
+
.filter((c) => c !== null)
|
|
12540
|
+
.map(c => ({
|
|
12541
|
+
attributePath: c.attributePath ?? '',
|
|
12542
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
12543
|
+
aggregationType: c.aggregationType ?? null
|
|
12544
|
+
}));
|
|
12545
|
+
}
|
|
12546
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
12547
|
+
return sdResult.columns.map(c => ({
|
|
12548
|
+
attributePath: c.attributePath,
|
|
12549
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
12550
|
+
aggregationType: c.aggregationType ?? null
|
|
12551
|
+
}));
|
|
12552
|
+
}
|
|
12129
12553
|
onSeriesFieldsChange(fields) {
|
|
12130
12554
|
this.selectedSeriesFields = fields;
|
|
12131
12555
|
}
|
|
@@ -12155,11 +12579,13 @@ class BarChartConfigDialogComponent {
|
|
|
12155
12579
|
comparisonValue: f.comparisonValue
|
|
12156
12580
|
}))
|
|
12157
12581
|
: undefined;
|
|
12582
|
+
const family = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
12158
12583
|
const result = {
|
|
12159
12584
|
ckTypeId: '',
|
|
12160
12585
|
rtId: '',
|
|
12161
12586
|
queryRtId: this.selectedPersistentQuery.rtId,
|
|
12162
12587
|
queryName: this.selectedPersistentQuery.name,
|
|
12588
|
+
queryFamily: family,
|
|
12163
12589
|
chartType: this.form.chartType,
|
|
12164
12590
|
categoryField: this.form.categoryField,
|
|
12165
12591
|
series,
|
|
@@ -12187,7 +12613,7 @@ class BarChartConfigDialogComponent {
|
|
|
12187
12613
|
this.windowRef.close();
|
|
12188
12614
|
}
|
|
12189
12615
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BarChartConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
12190
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: BarChartConfigDialogComponent, isStandalone: true, selector: "mm-bar-chart-config-dialog", inputs: { initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialChartType: "initialChartType", initialCategoryField: "initialCategoryField", initialSeries: "initialSeries", initialSeriesGroupField: "initialSeriesGroupField", initialValueField: "initialValueField", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialShowDataLabels: "initialShowDataLabels", initialColorThresholds: "initialColorThresholds", initialDefaultBarColor: "initialDefaultBarColor", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
12616
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: BarChartConfigDialogComponent, isStandalone: true, selector: "mm-bar-chart-config-dialog", inputs: { initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryFamily: "initialQueryFamily", initialChartType: "initialChartType", initialCategoryField: "initialCategoryField", initialSeries: "initialSeries", initialSeriesGroupField: "initialSeriesGroupField", initialValueField: "initialValueField", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialShowDataLabels: "initialShowDataLabels", initialColorThresholds: "initialColorThresholds", initialDefaultBarColor: "initialDefaultBarColor", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
12191
12617
|
<div class="config-container">
|
|
12192
12618
|
|
|
12193
12619
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -12448,7 +12874,7 @@ class BarChartConfigDialogComponent {
|
|
|
12448
12874
|
</button>
|
|
12449
12875
|
</div>
|
|
12450
12876
|
</div>
|
|
12451
|
-
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.chart-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.form-row{display:flex;gap:24px}.checkbox-field{flex-direction:row;align-items:center}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{margin-top:8px}.form-section h4{margin:0 0 4px;font-size:.95rem;font-weight:600}.threshold-row{display:flex;gap:8px;align-items:center;margin-bottom:8px}.threshold-row label{font-size:.85rem;min-width:70px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "component", type: i4.MultiSelectComponent, selector: "kendo-multiselect", inputs: ["showStickyHeader", "focusableId", "autoClose", "loading", "data", "value", "valueField", "textField", "tabindex", "tabIndex", "size", "rounded", "fillMode", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "disabled", "itemDisabled", "checkboxes", "readonly", "filterable", "virtual", "popupSettings", "listHeight", "valuePrimitive", "clearButton", "tagMapper", "allowCustom", "valueNormalizer", "inputAttributes"], outputs: ["filterChange", "valueChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "removeTag"], exportAs: ["kendoMultiSelect"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
12877
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.chart-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.form-row{display:flex;gap:24px}.checkbox-field{flex-direction:row;align-items:center}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{margin-top:8px}.form-section h4{margin:0 0 4px;font-size:.95rem;font-weight:600}.threshold-row{display:flex;gap:8px;align-items:center;margin-bottom:8px}.threshold-row label{font-size:.85rem;min-width:70px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "component", type: i4.MultiSelectComponent, selector: "kendo-multiselect", inputs: ["showStickyHeader", "focusableId", "autoClose", "loading", "data", "value", "valueField", "textField", "tabindex", "tabIndex", "size", "rounded", "fillMode", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "disabled", "itemDisabled", "checkboxes", "readonly", "filterable", "virtual", "popupSettings", "listHeight", "valuePrimitive", "clearButton", "tagMapper", "allowCustom", "valueNormalizer", "inputAttributes"], outputs: ["filterChange", "valueChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "removeTag"], exportAs: ["kendoMultiSelect"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled", "acceptFamilies"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
12452
12878
|
}
|
|
12453
12879
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BarChartConfigDialogComponent, decorators: [{
|
|
12454
12880
|
type: Component,
|
|
@@ -12731,6 +13157,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
12731
13157
|
type: Input
|
|
12732
13158
|
}], initialQueryName: [{
|
|
12733
13159
|
type: Input
|
|
13160
|
+
}], initialQueryFamily: [{
|
|
13161
|
+
type: Input
|
|
12734
13162
|
}], initialChartType: [{
|
|
12735
13163
|
type: Input
|
|
12736
13164
|
}], initialCategoryField: [{
|
|
@@ -12756,7 +13184,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
12756
13184
|
}] } });
|
|
12757
13185
|
|
|
12758
13186
|
class LineChartWidgetComponent {
|
|
12759
|
-
|
|
13187
|
+
queryExecutor = inject(QueryExecutorService);
|
|
13188
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
13189
|
+
'RtSimpleQueryRow',
|
|
13190
|
+
'RtAggregationQueryRow',
|
|
13191
|
+
'RtGroupingAggregationQueryRow',
|
|
13192
|
+
'StreamDataQueryRow'
|
|
13193
|
+
]);
|
|
12760
13194
|
stateService = inject(MeshBoardStateService);
|
|
12761
13195
|
variableService = inject(MeshBoardVariableService);
|
|
12762
13196
|
config;
|
|
@@ -12868,36 +13302,18 @@ class LineChartWidgetComponent {
|
|
|
12868
13302
|
this._error.set(null);
|
|
12869
13303
|
try {
|
|
12870
13304
|
const fieldFilter = this.convertFiltersToDto(this.config.filters);
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
13305
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
13306
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
13307
|
+
// unconditionally because the runtime path ignores it.
|
|
13308
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
13309
|
+
const result = await firstValueFrom(this.queryExecutor.execute(queryDataSource.queryFamily, queryDataSource.queryRtId, {
|
|
13310
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
13311
|
+
streamDataArgs
|
|
12876
13312
|
}).pipe(catchError(err => {
|
|
12877
13313
|
console.error('Error loading Line Chart data:', err);
|
|
12878
13314
|
throw err;
|
|
12879
13315
|
})));
|
|
12880
|
-
const
|
|
12881
|
-
if (queryItems.length === 0) {
|
|
12882
|
-
this._categories.set([]);
|
|
12883
|
-
this._seriesData.set([]);
|
|
12884
|
-
this._valueAxes.set([]);
|
|
12885
|
-
this._isLoading.set(false);
|
|
12886
|
-
return;
|
|
12887
|
-
}
|
|
12888
|
-
const queryResult = queryItems[0];
|
|
12889
|
-
if (!queryResult) {
|
|
12890
|
-
this._categories.set([]);
|
|
12891
|
-
this._seriesData.set([]);
|
|
12892
|
-
this._valueAxes.set([]);
|
|
12893
|
-
this._isLoading.set(false);
|
|
12894
|
-
return;
|
|
12895
|
-
}
|
|
12896
|
-
const rows = queryResult.rows?.items ?? [];
|
|
12897
|
-
const supportedRowTypes = ['RtSimpleQueryRow', 'RtAggregationQueryRow', 'RtGroupingAggregationQueryRow'];
|
|
12898
|
-
const filteredRows = rows
|
|
12899
|
-
.filter((row) => row !== null)
|
|
12900
|
-
.filter(row => supportedRowTypes.includes(row.__typename ?? ''));
|
|
13316
|
+
const filteredRows = result.rows.filter(row => LineChartWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''));
|
|
12901
13317
|
this.processData(filteredRows);
|
|
12902
13318
|
this._isLoading.set(false);
|
|
12903
13319
|
}
|
|
@@ -12907,6 +13323,13 @@ class LineChartWidgetComponent {
|
|
|
12907
13323
|
this._isLoading.set(false);
|
|
12908
13324
|
}
|
|
12909
13325
|
}
|
|
13326
|
+
buildStreamDataArgs() {
|
|
13327
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
13328
|
+
if (!range) {
|
|
13329
|
+
return undefined;
|
|
13330
|
+
}
|
|
13331
|
+
return { from: range.from, to: range.to };
|
|
13332
|
+
}
|
|
12910
13333
|
/**
|
|
12911
13334
|
* Processes query rows into line chart data.
|
|
12912
13335
|
* Groups by seriesGroupField, orders by categoryField (date), supports multi-axis by unitField.
|
|
@@ -12922,15 +13345,11 @@ class LineChartWidgetComponent {
|
|
|
12922
13345
|
const allSeriesGroups = new Set();
|
|
12923
13346
|
const seriesUnitMap = new Map(); // seriesGroup -> unit
|
|
12924
13347
|
for (const row of filteredRows) {
|
|
12925
|
-
const queryRow = row;
|
|
12926
|
-
const cells = queryRow.cells?.items ?? [];
|
|
12927
13348
|
let categoryValue = '';
|
|
12928
13349
|
let seriesGroupValue = '';
|
|
12929
13350
|
let numericValue = 0;
|
|
12930
13351
|
let unitValue = '';
|
|
12931
|
-
for (const cell of cells) {
|
|
12932
|
-
if (!cell?.attributePath)
|
|
12933
|
-
continue;
|
|
13352
|
+
for (const cell of row.cells) {
|
|
12934
13353
|
if (matchesAttributePath(cell.attributePath, categoryField)) {
|
|
12935
13354
|
categoryValue = String(cell.value ?? '');
|
|
12936
13355
|
}
|
|
@@ -13226,12 +13645,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
13226
13645
|
*/
|
|
13227
13646
|
class LineChartConfigDialogComponent {
|
|
13228
13647
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
13648
|
+
queryExecutor = inject(QueryExecutorService);
|
|
13229
13649
|
stateService = inject(MeshBoardStateService);
|
|
13230
13650
|
windowRef = inject(WindowRef);
|
|
13231
13651
|
querySelector;
|
|
13232
13652
|
// Initial values for editing
|
|
13233
13653
|
initialQueryRtId;
|
|
13234
13654
|
initialQueryName;
|
|
13655
|
+
initialQueryFamily;
|
|
13235
13656
|
initialChartType;
|
|
13236
13657
|
initialCategoryField;
|
|
13237
13658
|
initialSeriesGroupField;
|
|
@@ -13335,27 +13756,14 @@ class LineChartConfigDialogComponent {
|
|
|
13335
13756
|
}
|
|
13336
13757
|
async loadQueryColumns(queryRtId) {
|
|
13337
13758
|
this.isLoadingColumns = true;
|
|
13759
|
+
// family may be undefined when the selected query metadata is missing —
|
|
13760
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
13761
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
13338
13762
|
try {
|
|
13339
|
-
|
|
13340
|
-
const
|
|
13341
|
-
|
|
13342
|
-
|
|
13343
|
-
}
|
|
13344
|
-
}));
|
|
13345
|
-
const queryItems = result.data?.runtime?.runtimeQuery?.items ?? [];
|
|
13346
|
-
if (queryItems.length > 0 && queryItems[0]) {
|
|
13347
|
-
const columns = queryItems[0].columns ?? [];
|
|
13348
|
-
const filteredColumns = columns
|
|
13349
|
-
.filter((c) => c !== null);
|
|
13350
|
-
this.queryColumns = filteredColumns.map(c => ({
|
|
13351
|
-
attributePath: c.attributePath ?? '',
|
|
13352
|
-
attributeValueType: c.attributeValueType ?? '',
|
|
13353
|
-
aggregationType: c.aggregationType ?? null
|
|
13354
|
-
}));
|
|
13355
|
-
const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
|
|
13356
|
-
this.numericColumns = this.queryColumns.filter(c => numericTypes.includes(c.attributeValueType));
|
|
13357
|
-
this.nonNumericColumns = this.queryColumns.filter(c => !numericTypes.includes(c.attributeValueType));
|
|
13358
|
-
}
|
|
13763
|
+
this.queryColumns = await this.fetchColumnsForFamily(family, queryRtId);
|
|
13764
|
+
const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
|
|
13765
|
+
this.numericColumns = this.queryColumns.filter(c => numericTypes.includes(c.attributeValueType));
|
|
13766
|
+
this.nonNumericColumns = this.queryColumns.filter(c => !numericTypes.includes(c.attributeValueType));
|
|
13359
13767
|
}
|
|
13360
13768
|
catch (error) {
|
|
13361
13769
|
console.error('Error loading query columns:', error);
|
|
@@ -13367,6 +13775,35 @@ class LineChartConfigDialogComponent {
|
|
|
13367
13775
|
this.isLoadingColumns = false;
|
|
13368
13776
|
}
|
|
13369
13777
|
}
|
|
13778
|
+
/**
|
|
13779
|
+
* Runtime queries use the metadata-only resolver (no aggregation executed);
|
|
13780
|
+
* stream-data queries fall back to executing the query with `first: 1`
|
|
13781
|
+
* because the SD path has no dedicated column-introspection endpoint today.
|
|
13782
|
+
*/
|
|
13783
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
13784
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
13785
|
+
if (resolvedFamily === 'runtime') {
|
|
13786
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
13787
|
+
variables: { rtId }
|
|
13788
|
+
}));
|
|
13789
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
13790
|
+
if (!queryItem)
|
|
13791
|
+
return [];
|
|
13792
|
+
return (queryItem.columns ?? [])
|
|
13793
|
+
.filter((c) => c !== null)
|
|
13794
|
+
.map(c => ({
|
|
13795
|
+
attributePath: c.attributePath ?? '',
|
|
13796
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
13797
|
+
aggregationType: c.aggregationType ?? null
|
|
13798
|
+
}));
|
|
13799
|
+
}
|
|
13800
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
13801
|
+
return sdResult.columns.map(c => ({
|
|
13802
|
+
attributePath: c.attributePath,
|
|
13803
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
13804
|
+
aggregationType: c.aggregationType ?? null
|
|
13805
|
+
}));
|
|
13806
|
+
}
|
|
13370
13807
|
onFiltersChange(updatedFilters) {
|
|
13371
13808
|
this.filters = updatedFilters;
|
|
13372
13809
|
}
|
|
@@ -13380,11 +13817,13 @@ class LineChartConfigDialogComponent {
|
|
|
13380
13817
|
comparisonValue: f.comparisonValue
|
|
13381
13818
|
}))
|
|
13382
13819
|
: undefined;
|
|
13820
|
+
const family = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
13383
13821
|
const result = {
|
|
13384
13822
|
ckTypeId: '',
|
|
13385
13823
|
rtId: '',
|
|
13386
13824
|
queryRtId: this.selectedPersistentQuery.rtId,
|
|
13387
13825
|
queryName: this.selectedPersistentQuery.name,
|
|
13826
|
+
queryFamily: family,
|
|
13388
13827
|
chartType: this.form.chartType,
|
|
13389
13828
|
categoryField: this.form.categoryField,
|
|
13390
13829
|
seriesGroupField: this.form.seriesGroupField,
|
|
@@ -13408,7 +13847,7 @@ class LineChartConfigDialogComponent {
|
|
|
13408
13847
|
this.windowRef.close();
|
|
13409
13848
|
}
|
|
13410
13849
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LineChartConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13411
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: LineChartConfigDialogComponent, isStandalone: true, selector: "mm-line-chart-config-dialog", inputs: { initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialChartType: "initialChartType", initialCategoryField: "initialCategoryField", initialSeriesGroupField: "initialSeriesGroupField", initialValueField: "initialValueField", initialUnitField: "initialUnitField", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialShowMarkers: "initialShowMarkers", initialReferenceLines: "initialReferenceLines", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
13850
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: LineChartConfigDialogComponent, isStandalone: true, selector: "mm-line-chart-config-dialog", inputs: { initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryFamily: "initialQueryFamily", initialChartType: "initialChartType", initialCategoryField: "initialCategoryField", initialSeriesGroupField: "initialSeriesGroupField", initialValueField: "initialValueField", initialUnitField: "initialUnitField", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialShowMarkers: "initialShowMarkers", initialReferenceLines: "initialReferenceLines", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
13412
13851
|
<div class="config-container">
|
|
13413
13852
|
|
|
13414
13853
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -13611,7 +14050,7 @@ class LineChartConfigDialogComponent {
|
|
|
13611
14050
|
</button>
|
|
13612
14051
|
</div>
|
|
13613
14052
|
</div>
|
|
13614
|
-
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.chart-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.form-row{display:flex;gap:24px;align-items:center}.checkbox-field{flex-direction:row;align-items:center;margin-bottom:0}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{margin-top:8px}.form-section h4{margin:0 0 4px;font-size:.95rem;font-weight:600}.reference-line-row{display:flex;gap:8px;align-items:center;margin-bottom:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
14053
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.chart-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.form-row{display:flex;gap:24px;align-items:center}.checkbox-field{flex-direction:row;align-items:center;margin-bottom:0}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{margin-top:8px}.form-section h4{margin:0 0 4px;font-size:.95rem;font-weight:600}.reference-line-row{display:flex;gap:8px;align-items:center;margin-bottom:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled", "acceptFamilies"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
13615
14054
|
}
|
|
13616
14055
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LineChartConfigDialogComponent, decorators: [{
|
|
13617
14056
|
type: Component,
|
|
@@ -13836,6 +14275,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
13836
14275
|
type: Input
|
|
13837
14276
|
}], initialQueryName: [{
|
|
13838
14277
|
type: Input
|
|
14278
|
+
}], initialQueryFamily: [{
|
|
14279
|
+
type: Input
|
|
13839
14280
|
}], initialChartType: [{
|
|
13840
14281
|
type: Input
|
|
13841
14282
|
}], initialCategoryField: [{
|
|
@@ -13876,7 +14317,13 @@ function buildGradientRanges(min, max, colors) {
|
|
|
13876
14317
|
}));
|
|
13877
14318
|
}
|
|
13878
14319
|
class HeatmapWidgetComponent {
|
|
13879
|
-
|
|
14320
|
+
queryExecutor = inject(QueryExecutorService);
|
|
14321
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
14322
|
+
'RtSimpleQueryRow',
|
|
14323
|
+
'RtAggregationQueryRow',
|
|
14324
|
+
'RtGroupingAggregationQueryRow',
|
|
14325
|
+
'StreamDataQueryRow'
|
|
14326
|
+
]);
|
|
13880
14327
|
stateService = inject(MeshBoardStateService);
|
|
13881
14328
|
variableService = inject(MeshBoardVariableService);
|
|
13882
14329
|
config;
|
|
@@ -13996,36 +14443,18 @@ class HeatmapWidgetComponent {
|
|
|
13996
14443
|
this._error.set(null);
|
|
13997
14444
|
try {
|
|
13998
14445
|
const fieldFilter = this.convertFiltersToDto(this.config.filters);
|
|
13999
|
-
|
|
14000
|
-
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
14446
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
14447
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
14448
|
+
// unconditionally because the runtime path ignores it.
|
|
14449
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
14450
|
+
const result = await firstValueFrom(this.queryExecutor.execute(queryDataSource.queryFamily, queryDataSource.queryRtId, {
|
|
14451
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
14452
|
+
streamDataArgs
|
|
14004
14453
|
}).pipe(catchError(err => {
|
|
14005
14454
|
console.error('Error loading Heatmap data:', err);
|
|
14006
14455
|
throw err;
|
|
14007
14456
|
})));
|
|
14008
|
-
const
|
|
14009
|
-
if (queryItems.length === 0) {
|
|
14010
|
-
this._heatmapData.set([]);
|
|
14011
|
-
this._xCategories.set([]);
|
|
14012
|
-
this._yCategories.set([]);
|
|
14013
|
-
this._isLoading.set(false);
|
|
14014
|
-
return;
|
|
14015
|
-
}
|
|
14016
|
-
const queryResult = queryItems[0];
|
|
14017
|
-
if (!queryResult) {
|
|
14018
|
-
this._heatmapData.set([]);
|
|
14019
|
-
this._xCategories.set([]);
|
|
14020
|
-
this._yCategories.set([]);
|
|
14021
|
-
this._isLoading.set(false);
|
|
14022
|
-
return;
|
|
14023
|
-
}
|
|
14024
|
-
const rows = queryResult.rows?.items ?? [];
|
|
14025
|
-
const supportedRowTypes = ['RtSimpleQueryRow', 'RtAggregationQueryRow', 'RtGroupingAggregationQueryRow'];
|
|
14026
|
-
const filteredRows = rows
|
|
14027
|
-
.filter((row) => row !== null)
|
|
14028
|
-
.filter(row => supportedRowTypes.includes(row.__typename ?? ''));
|
|
14457
|
+
const filteredRows = result.rows.filter(row => HeatmapWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''));
|
|
14029
14458
|
this.processHeatmapData(filteredRows);
|
|
14030
14459
|
this._isLoading.set(false);
|
|
14031
14460
|
}
|
|
@@ -14035,6 +14464,13 @@ class HeatmapWidgetComponent {
|
|
|
14035
14464
|
this._isLoading.set(false);
|
|
14036
14465
|
}
|
|
14037
14466
|
}
|
|
14467
|
+
buildStreamDataArgs() {
|
|
14468
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
14469
|
+
if (!range) {
|
|
14470
|
+
return undefined;
|
|
14471
|
+
}
|
|
14472
|
+
return { from: range.from, to: range.to };
|
|
14473
|
+
}
|
|
14038
14474
|
/**
|
|
14039
14475
|
* Processes query rows into heatmap data.
|
|
14040
14476
|
* When dateEndField is configured, auto-detects the interval width and shows sub-hour columns.
|
|
@@ -14047,14 +14483,10 @@ class HeatmapWidgetComponent {
|
|
|
14047
14483
|
const aggregation = this.config.aggregation ?? 'count';
|
|
14048
14484
|
const parsedRows = [];
|
|
14049
14485
|
for (const row of filteredRows) {
|
|
14050
|
-
const queryRow = row;
|
|
14051
|
-
const cells = queryRow.cells?.items ?? [];
|
|
14052
14486
|
let dateFrom = null;
|
|
14053
14487
|
let dateTo = null;
|
|
14054
14488
|
let numericValue = 1; // default for count
|
|
14055
|
-
for (const cell of cells) {
|
|
14056
|
-
if (!cell?.attributePath)
|
|
14057
|
-
continue;
|
|
14489
|
+
for (const cell of row.cells) {
|
|
14058
14490
|
if (matchesAttributePath(cell.attributePath, dateField)) {
|
|
14059
14491
|
dateFrom = this.parseDate(cell.value);
|
|
14060
14492
|
}
|
|
@@ -14381,12 +14813,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
14381
14813
|
|
|
14382
14814
|
class HeatmapConfigDialogComponent {
|
|
14383
14815
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
14816
|
+
queryExecutor = inject(QueryExecutorService);
|
|
14384
14817
|
stateService = inject(MeshBoardStateService);
|
|
14385
14818
|
windowRef = inject(WindowRef);
|
|
14386
14819
|
querySelector;
|
|
14387
14820
|
// Initial values for editing
|
|
14388
14821
|
initialQueryRtId;
|
|
14389
14822
|
initialQueryName;
|
|
14823
|
+
initialQueryFamily;
|
|
14390
14824
|
initialDateField;
|
|
14391
14825
|
initialDateEndField;
|
|
14392
14826
|
initialValueField;
|
|
@@ -14508,36 +14942,22 @@ class HeatmapConfigDialogComponent {
|
|
|
14508
14942
|
}
|
|
14509
14943
|
async loadQueryColumns(queryRtId) {
|
|
14510
14944
|
this.isLoadingColumns = true;
|
|
14945
|
+
// family may be undefined when the selected query metadata is missing —
|
|
14946
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
14947
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
14511
14948
|
try {
|
|
14512
|
-
|
|
14513
|
-
const
|
|
14514
|
-
|
|
14515
|
-
|
|
14516
|
-
|
|
14517
|
-
|
|
14518
|
-
|
|
14519
|
-
if (
|
|
14520
|
-
|
|
14521
|
-
|
|
14522
|
-
|
|
14523
|
-
this.
|
|
14524
|
-
attributePath: c.attributePath ?? '',
|
|
14525
|
-
attributeValueType: c.attributeValueType ?? '',
|
|
14526
|
-
aggregationType: c.aggregationType ?? null
|
|
14527
|
-
}));
|
|
14528
|
-
const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
|
|
14529
|
-
const dateTimeTypes = ['DATE_TIME', 'DATETIME', 'DATE'];
|
|
14530
|
-
this.numericColumns = this.queryColumns.filter(c => numericTypes.includes(c.attributeValueType));
|
|
14531
|
-
this.dateTimeColumns = this.queryColumns.filter(c => dateTimeTypes.includes(c.attributeValueType));
|
|
14532
|
-
// If no explicit datetime columns found, also allow string columns
|
|
14533
|
-
// (datetime values are sometimes returned as strings)
|
|
14534
|
-
if (this.dateTimeColumns.length === 0) {
|
|
14535
|
-
this.dateTimeColumns = this.queryColumns;
|
|
14536
|
-
}
|
|
14537
|
-
// Auto-select first datetime column if not editing
|
|
14538
|
-
if (!this.initialQueryRtId && this.dateTimeColumns.length > 0) {
|
|
14539
|
-
this.form.dateField = this.dateTimeColumns[0].attributePath;
|
|
14540
|
-
}
|
|
14949
|
+
this.queryColumns = await this.fetchColumnsForFamily(family, queryRtId);
|
|
14950
|
+
const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
|
|
14951
|
+
const dateTimeTypes = ['DATE_TIME', 'DATETIME', 'DATE'];
|
|
14952
|
+
this.numericColumns = this.queryColumns.filter(c => numericTypes.includes(c.attributeValueType));
|
|
14953
|
+
this.dateTimeColumns = this.queryColumns.filter(c => dateTimeTypes.includes(c.attributeValueType));
|
|
14954
|
+
// If no explicit datetime columns found, also allow string columns
|
|
14955
|
+
// (datetime values are sometimes returned as strings)
|
|
14956
|
+
if (this.dateTimeColumns.length === 0) {
|
|
14957
|
+
this.dateTimeColumns = this.queryColumns;
|
|
14958
|
+
}
|
|
14959
|
+
if (!this.initialQueryRtId && this.dateTimeColumns.length > 0) {
|
|
14960
|
+
this.form.dateField = this.dateTimeColumns[0].attributePath;
|
|
14541
14961
|
}
|
|
14542
14962
|
}
|
|
14543
14963
|
catch (error) {
|
|
@@ -14550,6 +14970,35 @@ class HeatmapConfigDialogComponent {
|
|
|
14550
14970
|
this.isLoadingColumns = false;
|
|
14551
14971
|
}
|
|
14552
14972
|
}
|
|
14973
|
+
/**
|
|
14974
|
+
* Runtime queries use the metadata-only resolver (no aggregation executed);
|
|
14975
|
+
* stream-data queries fall back to executing the query with `first: 1`
|
|
14976
|
+
* because the SD path has no dedicated column-introspection endpoint today.
|
|
14977
|
+
*/
|
|
14978
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
14979
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
14980
|
+
if (resolvedFamily === 'runtime') {
|
|
14981
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
14982
|
+
variables: { rtId }
|
|
14983
|
+
}));
|
|
14984
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
14985
|
+
if (!queryItem)
|
|
14986
|
+
return [];
|
|
14987
|
+
return (queryItem.columns ?? [])
|
|
14988
|
+
.filter((c) => c !== null)
|
|
14989
|
+
.map(c => ({
|
|
14990
|
+
attributePath: c.attributePath ?? '',
|
|
14991
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
14992
|
+
aggregationType: c.aggregationType ?? null
|
|
14993
|
+
}));
|
|
14994
|
+
}
|
|
14995
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
14996
|
+
return sdResult.columns.map(c => ({
|
|
14997
|
+
attributePath: c.attributePath,
|
|
14998
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
14999
|
+
aggregationType: c.aggregationType ?? null
|
|
15000
|
+
}));
|
|
15001
|
+
}
|
|
14553
15002
|
onFiltersChange(updatedFilters) {
|
|
14554
15003
|
this.filters = updatedFilters;
|
|
14555
15004
|
}
|
|
@@ -14563,11 +15012,13 @@ class HeatmapConfigDialogComponent {
|
|
|
14563
15012
|
comparisonValue: f.comparisonValue
|
|
14564
15013
|
}))
|
|
14565
15014
|
: undefined;
|
|
15015
|
+
const family = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
14566
15016
|
const result = {
|
|
14567
15017
|
ckTypeId: '',
|
|
14568
15018
|
rtId: '',
|
|
14569
15019
|
queryRtId: this.selectedPersistentQuery.rtId,
|
|
14570
15020
|
queryName: this.selectedPersistentQuery.name,
|
|
15021
|
+
queryFamily: family,
|
|
14571
15022
|
dateField: this.form.dateField,
|
|
14572
15023
|
dateEndField: this.form.dateEndField || undefined,
|
|
14573
15024
|
valueField: this.form.valueField || undefined,
|
|
@@ -14586,7 +15037,7 @@ class HeatmapConfigDialogComponent {
|
|
|
14586
15037
|
this.windowRef.close();
|
|
14587
15038
|
}
|
|
14588
15039
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HeatmapConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14589
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: HeatmapConfigDialogComponent, isStandalone: true, selector: "mm-heatmap-config-dialog", inputs: { initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialDateField: "initialDateField", initialDateEndField: "initialDateEndField", initialValueField: "initialValueField", initialAggregation: "initialAggregation", initialColorScheme: "initialColorScheme", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialDecimalPlaces: "initialDecimalPlaces", initialCompactNumbers: "initialCompactNumbers", initialValueMultiplier: "initialValueMultiplier", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
15040
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: HeatmapConfigDialogComponent, isStandalone: true, selector: "mm-heatmap-config-dialog", inputs: { initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryFamily: "initialQueryFamily", initialDateField: "initialDateField", initialDateEndField: "initialDateEndField", initialValueField: "initialValueField", initialAggregation: "initialAggregation", initialColorScheme: "initialColorScheme", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialDecimalPlaces: "initialDecimalPlaces", initialCompactNumbers: "initialCompactNumbers", initialValueMultiplier: "initialValueMultiplier", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
14590
15041
|
<div class="config-container">
|
|
14591
15042
|
|
|
14592
15043
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -14795,7 +15246,7 @@ class HeatmapConfigDialogComponent {
|
|
|
14795
15246
|
</button>
|
|
14796
15247
|
</div>
|
|
14797
15248
|
</div>
|
|
14798
|
-
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.color-scheme-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.color-scheme-preview{display:flex;align-items:center;gap:6px}.color-swatch{display:inline-block;width:16px;height:16px;border-radius:3px;border:1px solid var(--kendo-color-border, #dee2e6)}.form-row{display:flex;gap:24px}.checkbox-field{flex-direction:row;align-items:center}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
15249
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.color-scheme-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.color-scheme-preview{display:flex;align-items:center;gap:6px}.color-swatch{display:inline-block;width:16px;height:16px;border-radius:3px;border:1px solid var(--kendo-color-border, #dee2e6)}.form-row{display:flex;gap:24px}.checkbox-field{flex-direction:row;align-items:center}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled", "acceptFamilies"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
14799
15250
|
}
|
|
14800
15251
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HeatmapConfigDialogComponent, decorators: [{
|
|
14801
15252
|
type: Component,
|
|
@@ -15026,6 +15477,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
15026
15477
|
type: Input
|
|
15027
15478
|
}], initialQueryName: [{
|
|
15028
15479
|
type: Input
|
|
15480
|
+
}], initialQueryFamily: [{
|
|
15481
|
+
type: Input
|
|
15029
15482
|
}], initialDateField: [{
|
|
15030
15483
|
type: Input
|
|
15031
15484
|
}], initialDateEndField: [{
|
|
@@ -16603,6 +17056,7 @@ class WidgetGroupConfigDialogComponent {
|
|
|
16603
17056
|
ckTypeSelectorService = inject(CkTypeSelectorService);
|
|
16604
17057
|
attributeSelectorService = inject(AttributeSelectorService);
|
|
16605
17058
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
17059
|
+
queryExecutor = inject(QueryExecutorService);
|
|
16606
17060
|
getCkTypeAvailableQueryColumnsGQL = inject(GetCkTypeAvailableQueryColumnsDtoGQL);
|
|
16607
17061
|
meshBoardStateService = inject(MeshBoardStateService);
|
|
16608
17062
|
windowRef = inject(WindowRef);
|
|
@@ -16611,6 +17065,7 @@ class WidgetGroupConfigDialogComponent {
|
|
|
16611
17065
|
initialDataSourceMode;
|
|
16612
17066
|
initialQueryRtId;
|
|
16613
17067
|
initialQueryName;
|
|
17068
|
+
initialQueryFamily;
|
|
16614
17069
|
initialCkTypeId;
|
|
16615
17070
|
initialFilters;
|
|
16616
17071
|
initialMaxItems;
|
|
@@ -16767,26 +17222,12 @@ class WidgetGroupConfigDialogComponent {
|
|
|
16767
17222
|
}
|
|
16768
17223
|
async loadQueryColumns(queryRtId) {
|
|
16769
17224
|
this.isLoadingColumns = true;
|
|
17225
|
+
// family may be undefined when the selected query metadata is missing —
|
|
17226
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
17227
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
16770
17228
|
try {
|
|
16771
|
-
|
|
16772
|
-
|
|
16773
|
-
variables: {
|
|
16774
|
-
rtId: queryRtId
|
|
16775
|
-
}
|
|
16776
|
-
}));
|
|
16777
|
-
const queryItems = result.data?.runtime?.runtimeQuery?.items ?? [];
|
|
16778
|
-
if (queryItems.length > 0 && queryItems[0]) {
|
|
16779
|
-
const queryResult = queryItems[0];
|
|
16780
|
-
const columns = queryResult.columns ?? [];
|
|
16781
|
-
this.availableColumns = columns
|
|
16782
|
-
.filter((c) => c !== null)
|
|
16783
|
-
.map(c => ({
|
|
16784
|
-
attributePath: c.attributePath ?? '',
|
|
16785
|
-
attributeValueType: c.attributeValueType ?? '',
|
|
16786
|
-
aggregationType: c.aggregationType ?? null
|
|
16787
|
-
}));
|
|
16788
|
-
this.filteredColumns.set(this.availableColumns);
|
|
16789
|
-
}
|
|
17229
|
+
this.availableColumns = await this.fetchColumnsForFamily(family, queryRtId);
|
|
17230
|
+
this.filteredColumns.set(this.availableColumns);
|
|
16790
17231
|
}
|
|
16791
17232
|
catch (error) {
|
|
16792
17233
|
console.error('Error loading query columns:', error);
|
|
@@ -16797,6 +17238,35 @@ class WidgetGroupConfigDialogComponent {
|
|
|
16797
17238
|
this.isLoadingColumns = false;
|
|
16798
17239
|
}
|
|
16799
17240
|
}
|
|
17241
|
+
/**
|
|
17242
|
+
* Runtime queries use the metadata-only resolver (no aggregation executed);
|
|
17243
|
+
* stream-data queries fall back to executing the query with `first: 1`
|
|
17244
|
+
* because the SD path has no dedicated column-introspection endpoint today.
|
|
17245
|
+
*/
|
|
17246
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
17247
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
17248
|
+
if (resolvedFamily === 'runtime') {
|
|
17249
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
17250
|
+
variables: { rtId }
|
|
17251
|
+
}));
|
|
17252
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
17253
|
+
if (!queryItem)
|
|
17254
|
+
return [];
|
|
17255
|
+
return (queryItem.columns ?? [])
|
|
17256
|
+
.filter((c) => c !== null)
|
|
17257
|
+
.map(c => ({
|
|
17258
|
+
attributePath: c.attributePath ?? '',
|
|
17259
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
17260
|
+
aggregationType: c.aggregationType ?? null
|
|
17261
|
+
}));
|
|
17262
|
+
}
|
|
17263
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
17264
|
+
return sdResult.columns.map(c => ({
|
|
17265
|
+
attributePath: c.attributePath,
|
|
17266
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
17267
|
+
aggregationType: c.aggregationType ?? null
|
|
17268
|
+
}));
|
|
17269
|
+
}
|
|
16800
17270
|
// ============================================================================
|
|
16801
17271
|
// CK Type Methods
|
|
16802
17272
|
// ============================================================================
|
|
@@ -16889,11 +17359,15 @@ class WidgetGroupConfigDialogComponent {
|
|
|
16889
17359
|
comparisonValue: f.comparisonValue
|
|
16890
17360
|
}))
|
|
16891
17361
|
: undefined;
|
|
17362
|
+
const family = this.dataSourceMode === 'persistentQuery' && this.selectedPersistentQuery
|
|
17363
|
+
? queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined
|
|
17364
|
+
: undefined;
|
|
16892
17365
|
const result = {
|
|
16893
17366
|
ckTypeId: this.dataSourceMode === 'ckType' ? (this.selectedCkType?.rtCkTypeId ?? '') : '',
|
|
16894
17367
|
dataSourceMode: this.dataSourceMode,
|
|
16895
17368
|
queryRtId: this.dataSourceMode === 'persistentQuery' ? this.selectedPersistentQuery?.rtId : undefined,
|
|
16896
17369
|
queryName: this.dataSourceMode === 'persistentQuery' ? this.selectedPersistentQuery?.name : undefined,
|
|
17370
|
+
queryFamily: family,
|
|
16897
17371
|
filters: this.dataSourceMode === 'ckType' ? filtersDto : undefined,
|
|
16898
17372
|
maxItems: this.form.maxItems,
|
|
16899
17373
|
childTemplate,
|
|
@@ -16931,7 +17405,7 @@ class WidgetGroupConfigDialogComponent {
|
|
|
16931
17405
|
this.windowRef.close();
|
|
16932
17406
|
}
|
|
16933
17407
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: WidgetGroupConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
16934
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: WidgetGroupConfigDialogComponent, isStandalone: true, selector: "mm-widget-group-config-dialog", inputs: { initialDataSourceMode: "initialDataSourceMode", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialCkTypeId: "initialCkTypeId", initialFilters: "initialFilters", initialMaxItems: "initialMaxItems", initialChildTemplate: "initialChildTemplate", initialLayout: "initialLayout", initialGridColumns: "initialGridColumns", initialMinChildWidth: "initialMinChildWidth", initialGap: "initialGap", initialEmptyMessage: "initialEmptyMessage" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
17408
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: WidgetGroupConfigDialogComponent, isStandalone: true, selector: "mm-widget-group-config-dialog", inputs: { initialDataSourceMode: "initialDataSourceMode", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryFamily: "initialQueryFamily", initialCkTypeId: "initialCkTypeId", initialFilters: "initialFilters", initialMaxItems: "initialMaxItems", initialChildTemplate: "initialChildTemplate", initialLayout: "initialLayout", initialGridColumns: "initialGridColumns", initialMinChildWidth: "initialMinChildWidth", initialGap: "initialGap", initialEmptyMessage: "initialEmptyMessage" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
|
|
16935
17409
|
<div class="config-container">
|
|
16936
17410
|
|
|
16937
17411
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -17251,7 +17725,7 @@ class WidgetGroupConfigDialogComponent {
|
|
|
17251
17725
|
</button>
|
|
17252
17726
|
</div>
|
|
17253
17727
|
</div>
|
|
17254
|
-
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;padding:16px;position:relative;flex:1;overflow-y:auto}.config-form.loading{pointer-events:none}.form-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.form-section h4{margin:0 0 16px;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field.flex-1{flex:1}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-row{display:flex;gap:16px}.mode-toggle{display:flex;gap:8px}.mode-toggle button{flex:1}.required{color:var(--kendo-color-error, #dc3545)}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "messages", "dialogMessages", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
17728
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;padding:16px;position:relative;flex:1;overflow-y:auto}.config-form.loading{pointer-events:none}.form-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.form-section h4{margin:0 0 16px;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field.flex-1{flex:1}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-row{display:flex;gap:16px}.mode-toggle{display:flex;gap:8px}.mode-toggle button{flex:1}.required{color:var(--kendo-color-error, #dc3545)}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "messages", "dialogMessages", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled", "acceptFamilies"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
|
|
17255
17729
|
}
|
|
17256
17730
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: WidgetGroupConfigDialogComponent, decorators: [{
|
|
17257
17731
|
type: Component,
|
|
@@ -17595,6 +18069,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
17595
18069
|
type: Input
|
|
17596
18070
|
}], initialQueryName: [{
|
|
17597
18071
|
type: Input
|
|
18072
|
+
}], initialQueryFamily: [{
|
|
18073
|
+
type: Input
|
|
17598
18074
|
}], initialCkTypeId: [{
|
|
17599
18075
|
type: Input
|
|
17600
18076
|
}], initialFilters: [{
|
|
@@ -24996,11 +25472,16 @@ function parseConfig(data) {
|
|
|
24996
25472
|
*/
|
|
24997
25473
|
function buildDataSourceFromPersisted(data, config) {
|
|
24998
25474
|
if (data.dataSourceType === 'systemQuery' || data.dataSourceType === 'persistentQuery') {
|
|
24999
|
-
|
|
25475
|
+
const persisted = {
|
|
25000
25476
|
type: 'persistentQuery',
|
|
25001
25477
|
queryRtId: data.dataSourceRtId ?? config['queryRtId'] ?? '',
|
|
25002
25478
|
queryName: config['queryName']
|
|
25003
25479
|
};
|
|
25480
|
+
const persistedFamily = config['queryFamily'];
|
|
25481
|
+
if (persistedFamily === 'runtime' || persistedFamily === 'streamData') {
|
|
25482
|
+
persisted.queryFamily = persistedFamily;
|
|
25483
|
+
}
|
|
25484
|
+
return persisted;
|
|
25004
25485
|
}
|
|
25005
25486
|
if (data.dataSourceType === 'static') {
|
|
25006
25487
|
return { type: 'static' };
|
|
@@ -25120,6 +25601,7 @@ function registerDefaultWidgets(registry) {
|
|
|
25120
25601
|
initialDataSourceType: dataSourceType,
|
|
25121
25602
|
initialQueryRtId: isPersistentQuery ? kpiWidget.dataSource.queryRtId : undefined,
|
|
25122
25603
|
initialQueryName: isPersistentQuery ? kpiWidget.dataSource.queryName : undefined,
|
|
25604
|
+
initialQueryFamily: isPersistentQuery ? kpiWidget.dataSource.queryFamily : undefined,
|
|
25123
25605
|
initialQueryMode: kpiWidget.queryMode,
|
|
25124
25606
|
initialQueryValueField: kpiWidget.queryValueField,
|
|
25125
25607
|
initialQueryCategoryField: kpiWidget.queryCategoryField,
|
|
@@ -25164,7 +25646,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25164
25646
|
const dataSource = {
|
|
25165
25647
|
type: 'persistentQuery',
|
|
25166
25648
|
queryRtId: result.queryRtId,
|
|
25167
|
-
queryName: result.queryName
|
|
25649
|
+
queryName: result.queryName,
|
|
25650
|
+
queryFamily: result.queryFamily
|
|
25168
25651
|
};
|
|
25169
25652
|
return {
|
|
25170
25653
|
...widget,
|
|
@@ -25235,7 +25718,10 @@ function registerDefaultWidgets(registry) {
|
|
|
25235
25718
|
filters: widget.filters,
|
|
25236
25719
|
...(isPersistentQuery && {
|
|
25237
25720
|
queryName: widget.dataSource.queryName,
|
|
25238
|
-
queryRtId: widget.dataSource.queryRtId
|
|
25721
|
+
queryRtId: widget.dataSource.queryRtId,
|
|
25722
|
+
...(widget.dataSource.queryFamily && {
|
|
25723
|
+
queryFamily: widget.dataSource.queryFamily
|
|
25724
|
+
})
|
|
25239
25725
|
})
|
|
25240
25726
|
}
|
|
25241
25727
|
};
|
|
@@ -25399,7 +25885,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25399
25885
|
initialPageSize: tableWidget.pageSize,
|
|
25400
25886
|
initialSortable: tableWidget.sortable,
|
|
25401
25887
|
initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
|
|
25402
|
-
initialQueryName: isPersistentQuery ? dataSource.queryName : undefined
|
|
25888
|
+
initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
|
|
25889
|
+
initialQueryFamily: isPersistentQuery ? dataSource.queryFamily : undefined
|
|
25403
25890
|
};
|
|
25404
25891
|
},
|
|
25405
25892
|
applyConfigResult: (widget, result) => {
|
|
@@ -25408,7 +25895,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25408
25895
|
const dataSource = {
|
|
25409
25896
|
type: 'persistentQuery',
|
|
25410
25897
|
queryRtId: result.queryRtId,
|
|
25411
|
-
queryName: result.queryName
|
|
25898
|
+
queryName: result.queryName,
|
|
25899
|
+
queryFamily: result.queryFamily
|
|
25412
25900
|
};
|
|
25413
25901
|
// Convert filters from DTO to widget format
|
|
25414
25902
|
const filters = result.filters?.map(f => ({
|
|
@@ -25462,11 +25950,12 @@ function registerDefaultWidgets(registry) {
|
|
|
25462
25950
|
// SOLID: Serialization for persistence
|
|
25463
25951
|
toPersistedConfig: (widget) => {
|
|
25464
25952
|
const isPersistentQuery = widget.dataSource.type === 'persistentQuery';
|
|
25953
|
+
const queryDataSource = isPersistentQuery ? widget.dataSource : null;
|
|
25465
25954
|
return {
|
|
25466
25955
|
dataSourceType: isPersistentQuery ? 'persistentQuery' : 'runtimeEntity',
|
|
25467
25956
|
dataSourceCkTypeId: widget.dataSource.type === 'runtimeEntity' ? widget.dataSource.ckTypeId : undefined,
|
|
25468
25957
|
dataSourceRtId: isPersistentQuery
|
|
25469
|
-
?
|
|
25958
|
+
? queryDataSource.queryRtId
|
|
25470
25959
|
: (widget.dataSource.type === 'runtimeEntity' ? widget.dataSource.rtId : undefined),
|
|
25471
25960
|
config: {
|
|
25472
25961
|
columns: widget.columns,
|
|
@@ -25474,9 +25963,10 @@ function registerDefaultWidgets(registry) {
|
|
|
25474
25963
|
filters: widget.filters,
|
|
25475
25964
|
pageSize: widget.pageSize,
|
|
25476
25965
|
sortable: widget.sortable,
|
|
25477
|
-
...(
|
|
25478
|
-
queryName:
|
|
25479
|
-
queryRtId:
|
|
25966
|
+
...(queryDataSource && {
|
|
25967
|
+
queryName: queryDataSource.queryName,
|
|
25968
|
+
queryRtId: queryDataSource.queryRtId,
|
|
25969
|
+
...(queryDataSource.queryFamily && { queryFamily: queryDataSource.queryFamily })
|
|
25480
25970
|
})
|
|
25481
25971
|
}
|
|
25482
25972
|
};
|
|
@@ -25521,6 +26011,7 @@ function registerDefaultWidgets(registry) {
|
|
|
25521
26011
|
initialDataSourceType: isPersistentQuery ? 'persistentQuery' : 'runtimeEntity',
|
|
25522
26012
|
initialQueryRtId: isPersistentQuery ? gaugeWidget.dataSource.queryRtId : undefined,
|
|
25523
26013
|
initialQueryName: isPersistentQuery ? gaugeWidget.dataSource.queryName : undefined,
|
|
26014
|
+
initialQueryFamily: isPersistentQuery ? gaugeWidget.dataSource.queryFamily : undefined,
|
|
25524
26015
|
initialQueryMode: gaugeWidget.queryMode,
|
|
25525
26016
|
initialQueryValueField: gaugeWidget.queryValueField,
|
|
25526
26017
|
initialQueryCategoryField: gaugeWidget.queryCategoryField,
|
|
@@ -25550,7 +26041,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25550
26041
|
const dataSource = {
|
|
25551
26042
|
type: 'persistentQuery',
|
|
25552
26043
|
queryRtId: result.queryRtId,
|
|
25553
|
-
queryName: result.queryName
|
|
26044
|
+
queryName: result.queryName,
|
|
26045
|
+
queryFamily: result.queryFamily
|
|
25554
26046
|
};
|
|
25555
26047
|
return {
|
|
25556
26048
|
...widget,
|
|
@@ -25632,7 +26124,10 @@ function registerDefaultWidgets(registry) {
|
|
|
25632
26124
|
filters: widget.filters,
|
|
25633
26125
|
...(isPersistentQuery && {
|
|
25634
26126
|
queryName: widget.dataSource.queryName,
|
|
25635
|
-
queryRtId: widget.dataSource.queryRtId
|
|
26127
|
+
queryRtId: widget.dataSource.queryRtId,
|
|
26128
|
+
...(widget.dataSource.queryFamily && {
|
|
26129
|
+
queryFamily: widget.dataSource.queryFamily
|
|
26130
|
+
})
|
|
25636
26131
|
})
|
|
25637
26132
|
}
|
|
25638
26133
|
};
|
|
@@ -25701,6 +26196,7 @@ function registerDefaultWidgets(registry) {
|
|
|
25701
26196
|
initialDataSourceType: dataSource.type,
|
|
25702
26197
|
initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
|
|
25703
26198
|
initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
|
|
26199
|
+
initialQueryFamily: isPersistentQuery ? dataSource.queryFamily : undefined,
|
|
25704
26200
|
initialCkQueryTarget: isCkQuery ? dataSource.queryTarget : undefined,
|
|
25705
26201
|
initialCkGroupBy: isCkQuery ? dataSource.groupBy : undefined,
|
|
25706
26202
|
initialChartType: pieWidget.chartType,
|
|
@@ -25733,7 +26229,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25733
26229
|
dataSource = {
|
|
25734
26230
|
type: 'persistentQuery',
|
|
25735
26231
|
queryRtId: result.queryRtId ?? '',
|
|
25736
|
-
queryName: result.queryName
|
|
26232
|
+
queryName: result.queryName,
|
|
26233
|
+
queryFamily: result.queryFamily
|
|
25737
26234
|
};
|
|
25738
26235
|
}
|
|
25739
26236
|
return {
|
|
@@ -25795,6 +26292,9 @@ function registerDefaultWidgets(registry) {
|
|
|
25795
26292
|
legendPosition: widget.legendPosition,
|
|
25796
26293
|
queryName: dataSource.queryName,
|
|
25797
26294
|
queryRtId: dataSource.queryRtId,
|
|
26295
|
+
...(dataSource.queryFamily && {
|
|
26296
|
+
queryFamily: dataSource.queryFamily
|
|
26297
|
+
}),
|
|
25798
26298
|
filters: widget.filters
|
|
25799
26299
|
}
|
|
25800
26300
|
};
|
|
@@ -25856,6 +26356,7 @@ function registerDefaultWidgets(registry) {
|
|
|
25856
26356
|
return {
|
|
25857
26357
|
initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
|
|
25858
26358
|
initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
|
|
26359
|
+
initialQueryFamily: isPersistentQuery ? dataSource.queryFamily : undefined,
|
|
25859
26360
|
initialChartType: barWidget.chartType,
|
|
25860
26361
|
initialCategoryField: barWidget.categoryField,
|
|
25861
26362
|
initialSeries: barWidget.series,
|
|
@@ -25873,7 +26374,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25873
26374
|
const dataSource = {
|
|
25874
26375
|
type: 'persistentQuery',
|
|
25875
26376
|
queryRtId: result.queryRtId,
|
|
25876
|
-
queryName: result.queryName
|
|
26377
|
+
queryName: result.queryName,
|
|
26378
|
+
queryFamily: result.queryFamily
|
|
25877
26379
|
};
|
|
25878
26380
|
// Convert filters from DTO to widget format
|
|
25879
26381
|
const filters = result.filters?.map(f => ({
|
|
@@ -25929,6 +26431,9 @@ function registerDefaultWidgets(registry) {
|
|
|
25929
26431
|
defaultBarColor: widget.defaultBarColor,
|
|
25930
26432
|
queryName: widget.dataSource.queryName,
|
|
25931
26433
|
queryRtId: widget.dataSource.queryRtId,
|
|
26434
|
+
...(widget.dataSource.queryFamily && {
|
|
26435
|
+
queryFamily: widget.dataSource.queryFamily
|
|
26436
|
+
}),
|
|
25932
26437
|
filters: widget.filters
|
|
25933
26438
|
}
|
|
25934
26439
|
}),
|
|
@@ -25973,6 +26478,7 @@ function registerDefaultWidgets(registry) {
|
|
|
25973
26478
|
return {
|
|
25974
26479
|
initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
|
|
25975
26480
|
initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
|
|
26481
|
+
initialQueryFamily: isPersistentQuery ? dataSource.queryFamily : undefined,
|
|
25976
26482
|
initialChartType: lineWidget.chartType,
|
|
25977
26483
|
initialCategoryField: lineWidget.categoryField,
|
|
25978
26484
|
initialSeriesGroupField: lineWidget.seriesGroupField,
|
|
@@ -25989,7 +26495,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25989
26495
|
const dataSource = {
|
|
25990
26496
|
type: 'persistentQuery',
|
|
25991
26497
|
queryRtId: result.queryRtId,
|
|
25992
|
-
queryName: result.queryName
|
|
26498
|
+
queryName: result.queryName,
|
|
26499
|
+
queryFamily: result.queryFamily
|
|
25993
26500
|
};
|
|
25994
26501
|
const filters = result.filters?.map(f => ({
|
|
25995
26502
|
attributePath: f.attributePath,
|
|
@@ -26040,6 +26547,9 @@ function registerDefaultWidgets(registry) {
|
|
|
26040
26547
|
referenceLines: widget.referenceLines,
|
|
26041
26548
|
queryName: widget.dataSource.queryName,
|
|
26042
26549
|
queryRtId: widget.dataSource.queryRtId,
|
|
26550
|
+
...(widget.dataSource.queryFamily && {
|
|
26551
|
+
queryFamily: widget.dataSource.queryFamily
|
|
26552
|
+
}),
|
|
26043
26553
|
filters: widget.filters
|
|
26044
26554
|
}
|
|
26045
26555
|
}),
|
|
@@ -26316,6 +26826,7 @@ function registerDefaultWidgets(registry) {
|
|
|
26316
26826
|
initialDataSourceMode: hasQuery ? 'persistentQuery' : (hasCkType ? 'ckType' : 'persistentQuery'),
|
|
26317
26827
|
initialQueryRtId: hasQuery ? dataSource.queryRtId : undefined,
|
|
26318
26828
|
initialQueryName: hasQuery ? dataSource.queryName : undefined,
|
|
26829
|
+
initialQueryFamily: hasQuery ? dataSource.queryFamily : undefined,
|
|
26319
26830
|
initialCkTypeId: hasCkType ? dataSource.ckTypeId : undefined,
|
|
26320
26831
|
initialFilters: hasCkType ? dataSource.filters : undefined,
|
|
26321
26832
|
initialMaxItems: dataSource.type === 'repeaterQuery' ? dataSource.maxItems : undefined,
|
|
@@ -26334,6 +26845,7 @@ function registerDefaultWidgets(registry) {
|
|
|
26334
26845
|
type: 'repeaterQuery',
|
|
26335
26846
|
queryRtId: result.dataSourceMode === 'persistentQuery' ? result.queryRtId : undefined,
|
|
26336
26847
|
queryName: result.dataSourceMode === 'persistentQuery' ? result.queryName : undefined,
|
|
26848
|
+
queryFamily: result.dataSourceMode === 'persistentQuery' ? result.queryFamily : undefined,
|
|
26337
26849
|
ckTypeId: useCkType ? result.ckTypeId : undefined,
|
|
26338
26850
|
filters: useCkType && result.filters ? result.filters.map(f => ({
|
|
26339
26851
|
attributePath: f.attributePath,
|
|
@@ -26384,6 +26896,7 @@ function registerDefaultWidgets(registry) {
|
|
|
26384
26896
|
dataSourceCkTypeId: !hasQuery ? dataSource.ckTypeId : undefined,
|
|
26385
26897
|
config: {
|
|
26386
26898
|
queryName: dataSource.queryName,
|
|
26899
|
+
...(dataSource.queryFamily && { queryFamily: dataSource.queryFamily }),
|
|
26387
26900
|
maxItems: dataSource.maxItems,
|
|
26388
26901
|
filters: dataSource.filters,
|
|
26389
26902
|
childTemplate: widget.childTemplate,
|
|
@@ -26399,10 +26912,14 @@ function registerDefaultWidgets(registry) {
|
|
|
26399
26912
|
fromPersistedConfig: (data, base) => {
|
|
26400
26913
|
const config = parseConfig(data);
|
|
26401
26914
|
const hasQuery = !!data.dataSourceRtId;
|
|
26915
|
+
const persistedFamily = config['queryFamily'];
|
|
26402
26916
|
const dataSource = {
|
|
26403
26917
|
type: 'repeaterQuery',
|
|
26404
26918
|
queryRtId: hasQuery ? data.dataSourceRtId ?? undefined : undefined,
|
|
26405
26919
|
queryName: hasQuery ? config['queryName'] : undefined,
|
|
26920
|
+
queryFamily: hasQuery && (persistedFamily === 'runtime' || persistedFamily === 'streamData')
|
|
26921
|
+
? persistedFamily
|
|
26922
|
+
: undefined,
|
|
26406
26923
|
ckTypeId: !hasQuery ? data.dataSourceCkTypeId ?? undefined : undefined,
|
|
26407
26924
|
filters: !hasQuery ? config['filters'] : undefined,
|
|
26408
26925
|
maxItems: config['maxItems']
|
|
@@ -26505,6 +27022,7 @@ function registerDefaultWidgets(registry) {
|
|
|
26505
27022
|
return {
|
|
26506
27023
|
initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
|
|
26507
27024
|
initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
|
|
27025
|
+
initialQueryFamily: isPersistentQuery ? dataSource.queryFamily : undefined,
|
|
26508
27026
|
initialDateField: heatmapWidget.dateField,
|
|
26509
27027
|
initialDateEndField: heatmapWidget.dateEndField,
|
|
26510
27028
|
initialValueField: heatmapWidget.valueField,
|
|
@@ -26522,7 +27040,8 @@ function registerDefaultWidgets(registry) {
|
|
|
26522
27040
|
const dataSource = {
|
|
26523
27041
|
type: 'persistentQuery',
|
|
26524
27042
|
queryRtId: result.queryRtId,
|
|
26525
|
-
queryName: result.queryName
|
|
27043
|
+
queryName: result.queryName,
|
|
27044
|
+
queryFamily: result.queryFamily
|
|
26526
27045
|
};
|
|
26527
27046
|
const filters = result.filters?.map(f => ({
|
|
26528
27047
|
attributePath: f.attributePath,
|
|
@@ -26575,6 +27094,9 @@ function registerDefaultWidgets(registry) {
|
|
|
26575
27094
|
valueMultiplier: widget.valueMultiplier,
|
|
26576
27095
|
queryName: widget.dataSource.queryName,
|
|
26577
27096
|
queryRtId: widget.dataSource.queryRtId,
|
|
27097
|
+
...(widget.dataSource.queryFamily && {
|
|
27098
|
+
queryFamily: widget.dataSource.queryFamily
|
|
27099
|
+
}),
|
|
26578
27100
|
filters: widget.filters
|
|
26579
27101
|
}
|
|
26580
27102
|
}),
|
|
@@ -27534,7 +28056,8 @@ class MeshBoardSettingsResult {
|
|
|
27534
28056
|
timeFilter;
|
|
27535
28057
|
rtWellKnownName;
|
|
27536
28058
|
entitySelectors;
|
|
27537
|
-
|
|
28059
|
+
autoRefreshSeconds;
|
|
28060
|
+
constructor(name, description, columns, rowHeight, gap, variables, timeFilter, rtWellKnownName, entitySelectors, autoRefreshSeconds) {
|
|
27538
28061
|
this.name = name;
|
|
27539
28062
|
this.description = description;
|
|
27540
28063
|
this.columns = columns;
|
|
@@ -27544,6 +28067,7 @@ class MeshBoardSettingsResult {
|
|
|
27544
28067
|
this.timeFilter = timeFilter;
|
|
27545
28068
|
this.rtWellKnownName = rtWellKnownName;
|
|
27546
28069
|
this.entitySelectors = entitySelectors;
|
|
28070
|
+
this.autoRefreshSeconds = autoRefreshSeconds;
|
|
27547
28071
|
}
|
|
27548
28072
|
}
|
|
27549
28073
|
/**
|
|
@@ -27559,6 +28083,12 @@ class MeshBoardSettingsDialogComponent {
|
|
|
27559
28083
|
columns = 6;
|
|
27560
28084
|
rowHeight = 200;
|
|
27561
28085
|
gap = 16;
|
|
28086
|
+
/**
|
|
28087
|
+
* Auto-refresh interval in seconds. `0` disables auto-refresh and is the default.
|
|
28088
|
+
* When > 0 the MeshBoard view re-polls all widgets at this interval while the
|
|
28089
|
+
* tab is visible.
|
|
28090
|
+
*/
|
|
28091
|
+
autoRefreshSeconds = 0;
|
|
27562
28092
|
variables = [];
|
|
27563
28093
|
entitySelectors = [];
|
|
27564
28094
|
entitySelectorEditing = false;
|
|
@@ -27576,7 +28106,8 @@ class MeshBoardSettingsDialogComponent {
|
|
|
27576
28106
|
return (this.name.trim().length > 0 &&
|
|
27577
28107
|
this.columns >= 1 && this.columns <= 12 &&
|
|
27578
28108
|
this.rowHeight >= 100 && this.rowHeight <= 1000 &&
|
|
27579
|
-
this.gap >= 0 && this.gap <= 100
|
|
28109
|
+
this.gap >= 0 && this.gap <= 100 &&
|
|
28110
|
+
this.autoRefreshSeconds >= 0 && this.autoRefreshSeconds <= 3600);
|
|
27580
28111
|
}
|
|
27581
28112
|
/**
|
|
27582
28113
|
* Sets the initial values for the form fields.
|
|
@@ -27588,6 +28119,7 @@ class MeshBoardSettingsDialogComponent {
|
|
|
27588
28119
|
this.columns = settings.columns;
|
|
27589
28120
|
this.rowHeight = settings.rowHeight;
|
|
27590
28121
|
this.gap = settings.gap;
|
|
28122
|
+
this.autoRefreshSeconds = settings.autoRefreshSeconds ?? 0;
|
|
27591
28123
|
this.variables = settings.variables ? [...settings.variables] : [];
|
|
27592
28124
|
this.entitySelectors = settings.entitySelectors ? settings.entitySelectors.map(es => ({ ...es })) : [];
|
|
27593
28125
|
this.timeFilterEnabled = settings.timeFilter?.enabled ?? false;
|
|
@@ -27629,7 +28161,7 @@ class MeshBoardSettingsDialogComponent {
|
|
|
27629
28161
|
enabled: this.timeFilterEnabled,
|
|
27630
28162
|
defaultSelection: this.timeFilterEnabled ? this.defaultSelection : undefined
|
|
27631
28163
|
};
|
|
27632
|
-
const result = new MeshBoardSettingsResult(this.name.trim(), this.description.trim(), this.columns, this.rowHeight, this.gap, this.variables, timeFilter, this.rtWellKnownName.trim() || undefined, this.entitySelectors.length > 0 ? this.entitySelectors : undefined);
|
|
28164
|
+
const result = new MeshBoardSettingsResult(this.name.trim(), this.description.trim(), this.columns, this.rowHeight, this.gap, this.variables, timeFilter, this.rtWellKnownName.trim() || undefined, this.entitySelectors.length > 0 ? this.entitySelectors : undefined, this.autoRefreshSeconds > 0 ? this.autoRefreshSeconds : undefined);
|
|
27633
28165
|
this.windowRef.close(result);
|
|
27634
28166
|
}
|
|
27635
28167
|
/**
|
|
@@ -27639,7 +28171,7 @@ class MeshBoardSettingsDialogComponent {
|
|
|
27639
28171
|
this.windowRef.close();
|
|
27640
28172
|
}
|
|
27641
28173
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MeshBoardSettingsDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
27642
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: MeshBoardSettingsDialogComponent, isStandalone: true, selector: "mm-meshboard-settings-dialog", ngImport: i0, template: "<div class=\"meshboard-settings-dialog\">\n <kendo-tabstrip [animate]=\"false\">\n <!-- General Tab -->\n <kendo-tabstrip-tab [title]=\"'General'\" [selected]=\"true\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <!-- Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"nameInput\" text=\"Name *\"></kendo-label>\n <kendo-textbox\n #nameInput\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Enter MeshBoard name\"\n required>\n </kendo-textbox>\n @if (name.trim().length === 0) {\n <kendo-formerror>Name is required</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Description Field -->\n <kendo-formfield>\n <kendo-label [for]=\"descriptionInput\" text=\"Description\"></kendo-label>\n <kendo-textarea\n #descriptionInput\n [(ngModel)]=\"description\"\n name=\"description\"\n placeholder=\"Enter MeshBoard description (optional)\"\n [rows]=\"3\">\n </kendo-textarea>\n </kendo-formfield>\n\n <!-- Well-Known Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"wellKnownNameInput\" text=\"Well-Known Name\"></kendo-label>\n <kendo-textbox\n #wellKnownNameInput\n [(ngModel)]=\"rtWellKnownName\"\n name=\"rtWellKnownName\"\n placeholder=\"e.g., cockpit, dashboard-main\">\n </kendo-textbox>\n <kendo-formhint>Unique identifier for routing. Use lowercase with hyphens (e.g., 'cockpit', 'sales-dashboard').</kendo-formhint>\n </kendo-formfield>\n\n <!-- Layout Settings -->\n <div class=\"section-title\">Layout Settings</div>\n\n <!-- Columns Field -->\n <kendo-formfield>\n <kendo-label [for]=\"columnsInput\" text=\"Columns *\"></kendo-label>\n <kendo-numerictextbox\n #columnsInput\n [(ngModel)]=\"columns\"\n name=\"columns\"\n [min]=\"1\"\n [max]=\"12\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Number of columns in the grid (1-12)</kendo-formhint>\n @if (columns < 1 || columns > 12) {\n <kendo-formerror>Columns must be between 1 and 12</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Row Height Field -->\n <kendo-formfield>\n <kendo-label [for]=\"rowHeightInput\" text=\"Row Height *\"></kendo-label>\n <kendo-numerictextbox\n #rowHeightInput\n [(ngModel)]=\"rowHeight\"\n name=\"rowHeight\"\n [min]=\"100\"\n [max]=\"1000\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Height of each row in pixels (100-1000)</kendo-formhint>\n @if (rowHeight < 100 || rowHeight > 1000) {\n <kendo-formerror>Row height must be between 100 and 1000</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Gap Field -->\n <kendo-formfield>\n <kendo-label [for]=\"gapInput\" text=\"Gap *\"></kendo-label>\n <kendo-numerictextbox\n #gapInput\n [(ngModel)]=\"gap\"\n name=\"gap\"\n [min]=\"0\"\n [max]=\"100\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Space between widgets in pixels (0-100)</kendo-formhint>\n @if (gap < 0 || gap > 100) {\n <kendo-formerror>Gap must be between 0 and 100</kendo-formerror>\n }\n </kendo-formfield>\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Variables Tab -->\n <kendo-tabstrip-tab [title]=\"'Variables'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-variables-editor\n [(variables)]=\"variables\">\n </mm-variables-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Time Filter Tab -->\n <kendo-tabstrip-tab [title]=\"'Time Filter'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <kendo-formfield>\n <div class=\"checkbox-wrapper\">\n <input\n type=\"checkbox\"\n kendoCheckBox\n #timeFilterCheckbox\n [(ngModel)]=\"timeFilterEnabled\"\n name=\"timeFilterEnabled\"\n id=\"timeFilterEnabled\"/>\n <kendo-label\n [for]=\"timeFilterCheckbox\"\n text=\"Enable Time Filter\"\n class=\"checkbox-label\">\n </kendo-label>\n </div>\n <kendo-formhint>\n Shows a time range picker in the toolbar. Sets $timeRangeFrom and $timeRangeTo variables.\n </kendo-formhint>\n </kendo-formfield>\n\n @if (timeFilterEnabled) {\n <kendo-formfield>\n <kendo-label text=\"Default Selection\"></kendo-label>\n <mm-time-range-picker\n [initialSelection]=\"initialDefaultSelection\"\n (selectionChange)=\"onDefaultSelectionChange($event)\">\n </mm-time-range-picker>\n <kendo-formhint>Initial time filter shown when no URL parameters are set. Note: User-selected filters override this default. Use the reset button in the toolbar to revert to the default.</kendo-formhint>\n </kendo-formfield>\n }\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Entity Selectors Tab -->\n <kendo-tabstrip-tab [title]=\"'Entity Selectors'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-entity-selector-editor\n [(entitySelectors)]=\"entitySelectors\"\n [existingVariableNames]=\"staticVariableNames\"\n (editingStateChange)=\"entitySelectorEditing = $event\">\n </mm-entity-selector-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n </kendo-tabstrip>\n\n <!-- Dialog Actions -->\n @if (!entitySelectorEditing) {\n <div class=\"dialog-actions mm-dialog-actions\">\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"save()\"\n [disabled]=\"!isValid\"\n themeColor=\"primary\">\n Save\n </button>\n </div>\n }\n</div>\n", styles: [".meshboard-settings-dialog{display:flex;flex-direction:column;height:100%;overflow:hidden}.meshboard-settings-dialog kendo-tabstrip{flex:1;min-height:0;display:flex;flex-direction:column}.meshboard-settings-dialog kendo-tabstrip ::ng-deep .k-tabstrip-content{flex:1;min-height:0;overflow:hidden}.meshboard-settings-dialog .tab-content{height:100%;overflow-y:auto;padding:1.5rem}.meshboard-settings-dialog .settings-form{display:flex;flex-direction:column;gap:1.25rem}.meshboard-settings-dialog .settings-form kendo-formfield{display:flex;flex-direction:column;gap:.5rem}.meshboard-settings-dialog .settings-form kendo-formfield kendo-label{font-weight:500;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-textbox,.meshboard-settings-dialog .settings-form kendo-formfield kendo-textarea,.meshboard-settings-dialog .settings-form kendo-formfield kendo-numerictextbox{width:100%}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formhint{font-size:.75rem;color:var(--kendo-color-subtle, #757575)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formerror{font-size:.75rem;color:var(--kendo-color-error, #f44336)}.meshboard-settings-dialog .settings-form .section-title{font-size:.875rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242);text-transform:uppercase;letter-spacing:.5px;margin-top:.5rem;padding-bottom:.5rem;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.meshboard-settings-dialog .settings-form .checkbox-wrapper{display:flex;align-items:center;gap:.5rem}.meshboard-settings-dialog .settings-form .checkbox-wrapper .checkbox-label{font-weight:400;cursor:pointer}.meshboard-settings-dialog .dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;background-color:var(--kendo-color-surface-alt, white)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "component", type: i3.TextAreaComponent, selector: "kendo-textarea", inputs: ["focusableId", "flow", "inputAttributes", "adornmentsOrientation", "rows", "cols", "maxlength", "maxResizableRows", "tabindex", "tabIndex", "resizable", "size", "rounded", "fillMode", "showPrefixSeparator", "showSuffixSeparator"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoTextArea"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "component", type: i3.FormFieldComponent, selector: "kendo-formfield", inputs: ["showHints", "orientation", "showErrors", "colSpan"] }, { kind: "component", type: i3.HintComponent, selector: "kendo-formhint", inputs: ["align"] }, { kind: "component", type: i3.ErrorComponent, selector: "kendo-formerror", inputs: ["align"] }, { kind: "ngmodule", type: CheckBoxModule }, { kind: "ngmodule", type: LabelModule }, { kind: "component", type: i4$1.LabelComponent, selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }, { kind: "ngmodule", type: FormFieldModule }, { kind: "ngmodule", type: TabStripModule }, { kind: "component", type: i5.TabStripComponent, selector: "kendo-tabstrip", inputs: ["height", "animate", "tabAlignment", "tabPosition", "keepTabContent", "closable", "scrollable", "size", "closeIcon", "closeIconClass", "closeSVGIcon", "showContentArea"], outputs: ["tabSelect", "tabClose", "tabScroll"], exportAs: ["kendoTabStrip"] }, { kind: "component", type: i5.TabStripTabComponent, selector: "kendo-tabstrip-tab", inputs: ["title", "disabled", "cssClass", "cssStyle", "selected", "closable", "closeIcon", "closeIconClass", "closeSVGIcon"], exportAs: ["kendoTabStripTab"] }, { kind: "directive", type: i5.TabContentDirective, selector: "[kendoTabContent]" }, { kind: "component", type: VariablesEditorComponent, selector: "mm-variables-editor", inputs: ["variables"], outputs: ["variablesChange"] }, { kind: "component", type: EntitySelectorEditorComponent, selector: "mm-entity-selector-editor", inputs: ["entitySelectors", "existingVariableNames"], outputs: ["entitySelectorsChange", "editingStateChange"] }, { kind: "component", type: TimeRangePickerComponent, selector: "mm-time-range-picker", inputs: ["config", "labels", "initialSelection"], outputs: ["rangeChange", "rangeChangeISO", "selectionChange"] }] });
|
|
28174
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: MeshBoardSettingsDialogComponent, isStandalone: true, selector: "mm-meshboard-settings-dialog", ngImport: i0, template: "<div class=\"meshboard-settings-dialog\">\n <kendo-tabstrip [animate]=\"false\">\n <!-- General Tab -->\n <kendo-tabstrip-tab [title]=\"'General'\" [selected]=\"true\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <!-- Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"nameInput\" text=\"Name *\"></kendo-label>\n <kendo-textbox\n #nameInput\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Enter MeshBoard name\"\n required>\n </kendo-textbox>\n @if (name.trim().length === 0) {\n <kendo-formerror>Name is required</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Description Field -->\n <kendo-formfield>\n <kendo-label [for]=\"descriptionInput\" text=\"Description\"></kendo-label>\n <kendo-textarea\n #descriptionInput\n [(ngModel)]=\"description\"\n name=\"description\"\n placeholder=\"Enter MeshBoard description (optional)\"\n [rows]=\"3\">\n </kendo-textarea>\n </kendo-formfield>\n\n <!-- Well-Known Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"wellKnownNameInput\" text=\"Well-Known Name\"></kendo-label>\n <kendo-textbox\n #wellKnownNameInput\n [(ngModel)]=\"rtWellKnownName\"\n name=\"rtWellKnownName\"\n placeholder=\"e.g., cockpit, dashboard-main\">\n </kendo-textbox>\n <kendo-formhint>Unique identifier for routing. Use lowercase with hyphens (e.g., 'cockpit', 'sales-dashboard').</kendo-formhint>\n </kendo-formfield>\n\n <!-- Layout Settings -->\n <div class=\"section-title\">Layout Settings</div>\n\n <!-- Columns Field -->\n <kendo-formfield>\n <kendo-label [for]=\"columnsInput\" text=\"Columns *\"></kendo-label>\n <kendo-numerictextbox\n #columnsInput\n [(ngModel)]=\"columns\"\n name=\"columns\"\n [min]=\"1\"\n [max]=\"12\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Number of columns in the grid (1-12)</kendo-formhint>\n @if (columns < 1 || columns > 12) {\n <kendo-formerror>Columns must be between 1 and 12</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Row Height Field -->\n <kendo-formfield>\n <kendo-label [for]=\"rowHeightInput\" text=\"Row Height *\"></kendo-label>\n <kendo-numerictextbox\n #rowHeightInput\n [(ngModel)]=\"rowHeight\"\n name=\"rowHeight\"\n [min]=\"100\"\n [max]=\"1000\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Height of each row in pixels (100-1000)</kendo-formhint>\n @if (rowHeight < 100 || rowHeight > 1000) {\n <kendo-formerror>Row height must be between 100 and 1000</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Gap Field -->\n <kendo-formfield>\n <kendo-label [for]=\"gapInput\" text=\"Gap *\"></kendo-label>\n <kendo-numerictextbox\n #gapInput\n [(ngModel)]=\"gap\"\n name=\"gap\"\n [min]=\"0\"\n [max]=\"100\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Space between widgets in pixels (0-100)</kendo-formhint>\n @if (gap < 0 || gap > 100) {\n <kendo-formerror>Gap must be between 0 and 100</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Auto-Refresh Field -->\n <kendo-formfield>\n <kendo-label [for]=\"autoRefreshInput\" text=\"Auto-refresh\"></kendo-label>\n <kendo-numerictextbox\n #autoRefreshInput\n [(ngModel)]=\"autoRefreshSeconds\"\n name=\"autoRefreshSeconds\"\n [min]=\"0\"\n [max]=\"3600\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\">\n </kendo-numerictextbox>\n <kendo-formhint>Refresh interval in seconds (0 = disabled, max 3600). Pauses while the tab is hidden.</kendo-formhint>\n @if (autoRefreshSeconds < 0 || autoRefreshSeconds > 3600) {\n <kendo-formerror>Auto-refresh must be between 0 and 3600 seconds</kendo-formerror>\n }\n </kendo-formfield>\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Variables Tab -->\n <kendo-tabstrip-tab [title]=\"'Variables'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-variables-editor\n [(variables)]=\"variables\">\n </mm-variables-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Time Filter Tab -->\n <kendo-tabstrip-tab [title]=\"'Time Filter'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <kendo-formfield>\n <div class=\"checkbox-wrapper\">\n <input\n type=\"checkbox\"\n kendoCheckBox\n #timeFilterCheckbox\n [(ngModel)]=\"timeFilterEnabled\"\n name=\"timeFilterEnabled\"\n id=\"timeFilterEnabled\"/>\n <kendo-label\n [for]=\"timeFilterCheckbox\"\n text=\"Enable Time Filter\"\n class=\"checkbox-label\">\n </kendo-label>\n </div>\n <kendo-formhint>\n Shows a time range picker in the toolbar. Sets $timeRangeFrom and $timeRangeTo variables.\n </kendo-formhint>\n </kendo-formfield>\n\n @if (timeFilterEnabled) {\n <kendo-formfield>\n <kendo-label text=\"Default Selection\"></kendo-label>\n <mm-time-range-picker\n [initialSelection]=\"initialDefaultSelection\"\n (selectionChange)=\"onDefaultSelectionChange($event)\">\n </mm-time-range-picker>\n <kendo-formhint>Initial time filter shown when no URL parameters are set. Note: User-selected filters override this default. Use the reset button in the toolbar to revert to the default.</kendo-formhint>\n </kendo-formfield>\n }\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Entity Selectors Tab -->\n <kendo-tabstrip-tab [title]=\"'Entity Selectors'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-entity-selector-editor\n [(entitySelectors)]=\"entitySelectors\"\n [existingVariableNames]=\"staticVariableNames\"\n (editingStateChange)=\"entitySelectorEditing = $event\">\n </mm-entity-selector-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n </kendo-tabstrip>\n\n <!-- Dialog Actions -->\n @if (!entitySelectorEditing) {\n <div class=\"dialog-actions mm-dialog-actions\">\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"save()\"\n [disabled]=\"!isValid\"\n themeColor=\"primary\">\n Save\n </button>\n </div>\n }\n</div>\n", styles: [".meshboard-settings-dialog{display:flex;flex-direction:column;height:100%;overflow:hidden}.meshboard-settings-dialog kendo-tabstrip{flex:1;min-height:0;display:flex;flex-direction:column}.meshboard-settings-dialog kendo-tabstrip ::ng-deep .k-tabstrip-content{flex:1;min-height:0;overflow:hidden}.meshboard-settings-dialog .tab-content{height:100%;overflow-y:auto;padding:1.5rem}.meshboard-settings-dialog .settings-form{display:flex;flex-direction:column;gap:1.25rem}.meshboard-settings-dialog .settings-form kendo-formfield{display:flex;flex-direction:column;gap:.5rem}.meshboard-settings-dialog .settings-form kendo-formfield kendo-label{font-weight:500;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-textbox,.meshboard-settings-dialog .settings-form kendo-formfield kendo-textarea,.meshboard-settings-dialog .settings-form kendo-formfield kendo-numerictextbox{width:100%}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formhint{font-size:.75rem;color:var(--kendo-color-subtle, #757575)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formerror{font-size:.75rem;color:var(--kendo-color-error, #f44336)}.meshboard-settings-dialog .settings-form .section-title{font-size:.875rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242);text-transform:uppercase;letter-spacing:.5px;margin-top:.5rem;padding-bottom:.5rem;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.meshboard-settings-dialog .settings-form .checkbox-wrapper{display:flex;align-items:center;gap:.5rem}.meshboard-settings-dialog .settings-form .checkbox-wrapper .checkbox-label{font-weight:400;cursor:pointer}.meshboard-settings-dialog .dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;background-color:var(--kendo-color-surface-alt, white)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "component", type: i3.TextAreaComponent, selector: "kendo-textarea", inputs: ["focusableId", "flow", "inputAttributes", "adornmentsOrientation", "rows", "cols", "maxlength", "maxResizableRows", "tabindex", "tabIndex", "resizable", "size", "rounded", "fillMode", "showPrefixSeparator", "showSuffixSeparator"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoTextArea"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "component", type: i3.FormFieldComponent, selector: "kendo-formfield", inputs: ["showHints", "orientation", "showErrors", "colSpan"] }, { kind: "component", type: i3.HintComponent, selector: "kendo-formhint", inputs: ["align"] }, { kind: "component", type: i3.ErrorComponent, selector: "kendo-formerror", inputs: ["align"] }, { kind: "ngmodule", type: CheckBoxModule }, { kind: "ngmodule", type: LabelModule }, { kind: "component", type: i4$1.LabelComponent, selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }, { kind: "ngmodule", type: FormFieldModule }, { kind: "ngmodule", type: TabStripModule }, { kind: "component", type: i5.TabStripComponent, selector: "kendo-tabstrip", inputs: ["height", "animate", "tabAlignment", "tabPosition", "keepTabContent", "closable", "scrollable", "size", "closeIcon", "closeIconClass", "closeSVGIcon", "showContentArea"], outputs: ["tabSelect", "tabClose", "tabScroll"], exportAs: ["kendoTabStrip"] }, { kind: "component", type: i5.TabStripTabComponent, selector: "kendo-tabstrip-tab", inputs: ["title", "disabled", "cssClass", "cssStyle", "selected", "closable", "closeIcon", "closeIconClass", "closeSVGIcon"], exportAs: ["kendoTabStripTab"] }, { kind: "directive", type: i5.TabContentDirective, selector: "[kendoTabContent]" }, { kind: "component", type: VariablesEditorComponent, selector: "mm-variables-editor", inputs: ["variables"], outputs: ["variablesChange"] }, { kind: "component", type: EntitySelectorEditorComponent, selector: "mm-entity-selector-editor", inputs: ["entitySelectors", "existingVariableNames"], outputs: ["entitySelectorsChange", "editingStateChange"] }, { kind: "component", type: TimeRangePickerComponent, selector: "mm-time-range-picker", inputs: ["config", "labels", "initialSelection"], outputs: ["rangeChange", "rangeChangeISO", "selectionChange"] }] });
|
|
27643
28175
|
}
|
|
27644
28176
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MeshBoardSettingsDialogComponent, decorators: [{
|
|
27645
28177
|
type: Component,
|
|
@@ -27655,7 +28187,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
27655
28187
|
VariablesEditorComponent,
|
|
27656
28188
|
EntitySelectorEditorComponent,
|
|
27657
28189
|
TimeRangePickerComponent
|
|
27658
|
-
], template: "<div class=\"meshboard-settings-dialog\">\n <kendo-tabstrip [animate]=\"false\">\n <!-- General Tab -->\n <kendo-tabstrip-tab [title]=\"'General'\" [selected]=\"true\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <!-- Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"nameInput\" text=\"Name *\"></kendo-label>\n <kendo-textbox\n #nameInput\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Enter MeshBoard name\"\n required>\n </kendo-textbox>\n @if (name.trim().length === 0) {\n <kendo-formerror>Name is required</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Description Field -->\n <kendo-formfield>\n <kendo-label [for]=\"descriptionInput\" text=\"Description\"></kendo-label>\n <kendo-textarea\n #descriptionInput\n [(ngModel)]=\"description\"\n name=\"description\"\n placeholder=\"Enter MeshBoard description (optional)\"\n [rows]=\"3\">\n </kendo-textarea>\n </kendo-formfield>\n\n <!-- Well-Known Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"wellKnownNameInput\" text=\"Well-Known Name\"></kendo-label>\n <kendo-textbox\n #wellKnownNameInput\n [(ngModel)]=\"rtWellKnownName\"\n name=\"rtWellKnownName\"\n placeholder=\"e.g., cockpit, dashboard-main\">\n </kendo-textbox>\n <kendo-formhint>Unique identifier for routing. Use lowercase with hyphens (e.g., 'cockpit', 'sales-dashboard').</kendo-formhint>\n </kendo-formfield>\n\n <!-- Layout Settings -->\n <div class=\"section-title\">Layout Settings</div>\n\n <!-- Columns Field -->\n <kendo-formfield>\n <kendo-label [for]=\"columnsInput\" text=\"Columns *\"></kendo-label>\n <kendo-numerictextbox\n #columnsInput\n [(ngModel)]=\"columns\"\n name=\"columns\"\n [min]=\"1\"\n [max]=\"12\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Number of columns in the grid (1-12)</kendo-formhint>\n @if (columns < 1 || columns > 12) {\n <kendo-formerror>Columns must be between 1 and 12</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Row Height Field -->\n <kendo-formfield>\n <kendo-label [for]=\"rowHeightInput\" text=\"Row Height *\"></kendo-label>\n <kendo-numerictextbox\n #rowHeightInput\n [(ngModel)]=\"rowHeight\"\n name=\"rowHeight\"\n [min]=\"100\"\n [max]=\"1000\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Height of each row in pixels (100-1000)</kendo-formhint>\n @if (rowHeight < 100 || rowHeight > 1000) {\n <kendo-formerror>Row height must be between 100 and 1000</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Gap Field -->\n <kendo-formfield>\n <kendo-label [for]=\"gapInput\" text=\"Gap *\"></kendo-label>\n <kendo-numerictextbox\n #gapInput\n [(ngModel)]=\"gap\"\n name=\"gap\"\n [min]=\"0\"\n [max]=\"100\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Space between widgets in pixels (0-100)</kendo-formhint>\n @if (gap < 0 || gap > 100) {\n <kendo-formerror>Gap must be between 0 and 100</kendo-formerror>\n }\n </kendo-formfield>\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Variables Tab -->\n <kendo-tabstrip-tab [title]=\"'Variables'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-variables-editor\n [(variables)]=\"variables\">\n </mm-variables-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Time Filter Tab -->\n <kendo-tabstrip-tab [title]=\"'Time Filter'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <kendo-formfield>\n <div class=\"checkbox-wrapper\">\n <input\n type=\"checkbox\"\n kendoCheckBox\n #timeFilterCheckbox\n [(ngModel)]=\"timeFilterEnabled\"\n name=\"timeFilterEnabled\"\n id=\"timeFilterEnabled\"/>\n <kendo-label\n [for]=\"timeFilterCheckbox\"\n text=\"Enable Time Filter\"\n class=\"checkbox-label\">\n </kendo-label>\n </div>\n <kendo-formhint>\n Shows a time range picker in the toolbar. Sets $timeRangeFrom and $timeRangeTo variables.\n </kendo-formhint>\n </kendo-formfield>\n\n @if (timeFilterEnabled) {\n <kendo-formfield>\n <kendo-label text=\"Default Selection\"></kendo-label>\n <mm-time-range-picker\n [initialSelection]=\"initialDefaultSelection\"\n (selectionChange)=\"onDefaultSelectionChange($event)\">\n </mm-time-range-picker>\n <kendo-formhint>Initial time filter shown when no URL parameters are set. Note: User-selected filters override this default. Use the reset button in the toolbar to revert to the default.</kendo-formhint>\n </kendo-formfield>\n }\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Entity Selectors Tab -->\n <kendo-tabstrip-tab [title]=\"'Entity Selectors'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-entity-selector-editor\n [(entitySelectors)]=\"entitySelectors\"\n [existingVariableNames]=\"staticVariableNames\"\n (editingStateChange)=\"entitySelectorEditing = $event\">\n </mm-entity-selector-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n </kendo-tabstrip>\n\n <!-- Dialog Actions -->\n @if (!entitySelectorEditing) {\n <div class=\"dialog-actions mm-dialog-actions\">\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"save()\"\n [disabled]=\"!isValid\"\n themeColor=\"primary\">\n Save\n </button>\n </div>\n }\n</div>\n", styles: [".meshboard-settings-dialog{display:flex;flex-direction:column;height:100%;overflow:hidden}.meshboard-settings-dialog kendo-tabstrip{flex:1;min-height:0;display:flex;flex-direction:column}.meshboard-settings-dialog kendo-tabstrip ::ng-deep .k-tabstrip-content{flex:1;min-height:0;overflow:hidden}.meshboard-settings-dialog .tab-content{height:100%;overflow-y:auto;padding:1.5rem}.meshboard-settings-dialog .settings-form{display:flex;flex-direction:column;gap:1.25rem}.meshboard-settings-dialog .settings-form kendo-formfield{display:flex;flex-direction:column;gap:.5rem}.meshboard-settings-dialog .settings-form kendo-formfield kendo-label{font-weight:500;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-textbox,.meshboard-settings-dialog .settings-form kendo-formfield kendo-textarea,.meshboard-settings-dialog .settings-form kendo-formfield kendo-numerictextbox{width:100%}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formhint{font-size:.75rem;color:var(--kendo-color-subtle, #757575)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formerror{font-size:.75rem;color:var(--kendo-color-error, #f44336)}.meshboard-settings-dialog .settings-form .section-title{font-size:.875rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242);text-transform:uppercase;letter-spacing:.5px;margin-top:.5rem;padding-bottom:.5rem;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.meshboard-settings-dialog .settings-form .checkbox-wrapper{display:flex;align-items:center;gap:.5rem}.meshboard-settings-dialog .settings-form .checkbox-wrapper .checkbox-label{font-weight:400;cursor:pointer}.meshboard-settings-dialog .dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;background-color:var(--kendo-color-surface-alt, white)}\n"] }]
|
|
28190
|
+
], template: "<div class=\"meshboard-settings-dialog\">\n <kendo-tabstrip [animate]=\"false\">\n <!-- General Tab -->\n <kendo-tabstrip-tab [title]=\"'General'\" [selected]=\"true\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <!-- Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"nameInput\" text=\"Name *\"></kendo-label>\n <kendo-textbox\n #nameInput\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Enter MeshBoard name\"\n required>\n </kendo-textbox>\n @if (name.trim().length === 0) {\n <kendo-formerror>Name is required</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Description Field -->\n <kendo-formfield>\n <kendo-label [for]=\"descriptionInput\" text=\"Description\"></kendo-label>\n <kendo-textarea\n #descriptionInput\n [(ngModel)]=\"description\"\n name=\"description\"\n placeholder=\"Enter MeshBoard description (optional)\"\n [rows]=\"3\">\n </kendo-textarea>\n </kendo-formfield>\n\n <!-- Well-Known Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"wellKnownNameInput\" text=\"Well-Known Name\"></kendo-label>\n <kendo-textbox\n #wellKnownNameInput\n [(ngModel)]=\"rtWellKnownName\"\n name=\"rtWellKnownName\"\n placeholder=\"e.g., cockpit, dashboard-main\">\n </kendo-textbox>\n <kendo-formhint>Unique identifier for routing. Use lowercase with hyphens (e.g., 'cockpit', 'sales-dashboard').</kendo-formhint>\n </kendo-formfield>\n\n <!-- Layout Settings -->\n <div class=\"section-title\">Layout Settings</div>\n\n <!-- Columns Field -->\n <kendo-formfield>\n <kendo-label [for]=\"columnsInput\" text=\"Columns *\"></kendo-label>\n <kendo-numerictextbox\n #columnsInput\n [(ngModel)]=\"columns\"\n name=\"columns\"\n [min]=\"1\"\n [max]=\"12\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Number of columns in the grid (1-12)</kendo-formhint>\n @if (columns < 1 || columns > 12) {\n <kendo-formerror>Columns must be between 1 and 12</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Row Height Field -->\n <kendo-formfield>\n <kendo-label [for]=\"rowHeightInput\" text=\"Row Height *\"></kendo-label>\n <kendo-numerictextbox\n #rowHeightInput\n [(ngModel)]=\"rowHeight\"\n name=\"rowHeight\"\n [min]=\"100\"\n [max]=\"1000\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Height of each row in pixels (100-1000)</kendo-formhint>\n @if (rowHeight < 100 || rowHeight > 1000) {\n <kendo-formerror>Row height must be between 100 and 1000</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Gap Field -->\n <kendo-formfield>\n <kendo-label [for]=\"gapInput\" text=\"Gap *\"></kendo-label>\n <kendo-numerictextbox\n #gapInput\n [(ngModel)]=\"gap\"\n name=\"gap\"\n [min]=\"0\"\n [max]=\"100\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Space between widgets in pixels (0-100)</kendo-formhint>\n @if (gap < 0 || gap > 100) {\n <kendo-formerror>Gap must be between 0 and 100</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Auto-Refresh Field -->\n <kendo-formfield>\n <kendo-label [for]=\"autoRefreshInput\" text=\"Auto-refresh\"></kendo-label>\n <kendo-numerictextbox\n #autoRefreshInput\n [(ngModel)]=\"autoRefreshSeconds\"\n name=\"autoRefreshSeconds\"\n [min]=\"0\"\n [max]=\"3600\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\">\n </kendo-numerictextbox>\n <kendo-formhint>Refresh interval in seconds (0 = disabled, max 3600). Pauses while the tab is hidden.</kendo-formhint>\n @if (autoRefreshSeconds < 0 || autoRefreshSeconds > 3600) {\n <kendo-formerror>Auto-refresh must be between 0 and 3600 seconds</kendo-formerror>\n }\n </kendo-formfield>\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Variables Tab -->\n <kendo-tabstrip-tab [title]=\"'Variables'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-variables-editor\n [(variables)]=\"variables\">\n </mm-variables-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Time Filter Tab -->\n <kendo-tabstrip-tab [title]=\"'Time Filter'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <form class=\"settings-form\">\n <kendo-formfield>\n <div class=\"checkbox-wrapper\">\n <input\n type=\"checkbox\"\n kendoCheckBox\n #timeFilterCheckbox\n [(ngModel)]=\"timeFilterEnabled\"\n name=\"timeFilterEnabled\"\n id=\"timeFilterEnabled\"/>\n <kendo-label\n [for]=\"timeFilterCheckbox\"\n text=\"Enable Time Filter\"\n class=\"checkbox-label\">\n </kendo-label>\n </div>\n <kendo-formhint>\n Shows a time range picker in the toolbar. Sets $timeRangeFrom and $timeRangeTo variables.\n </kendo-formhint>\n </kendo-formfield>\n\n @if (timeFilterEnabled) {\n <kendo-formfield>\n <kendo-label text=\"Default Selection\"></kendo-label>\n <mm-time-range-picker\n [initialSelection]=\"initialDefaultSelection\"\n (selectionChange)=\"onDefaultSelectionChange($event)\">\n </mm-time-range-picker>\n <kendo-formhint>Initial time filter shown when no URL parameters are set. Note: User-selected filters override this default. Use the reset button in the toolbar to revert to the default.</kendo-formhint>\n </kendo-formfield>\n }\n </form>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n\n <!-- Entity Selectors Tab -->\n <kendo-tabstrip-tab [title]=\"'Entity Selectors'\">\n <ng-template kendoTabContent>\n <div class=\"tab-content\">\n <mm-entity-selector-editor\n [(entitySelectors)]=\"entitySelectors\"\n [existingVariableNames]=\"staticVariableNames\"\n (editingStateChange)=\"entitySelectorEditing = $event\">\n </mm-entity-selector-editor>\n </div>\n </ng-template>\n </kendo-tabstrip-tab>\n </kendo-tabstrip>\n\n <!-- Dialog Actions -->\n @if (!entitySelectorEditing) {\n <div class=\"dialog-actions mm-dialog-actions\">\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"save()\"\n [disabled]=\"!isValid\"\n themeColor=\"primary\">\n Save\n </button>\n </div>\n }\n</div>\n", styles: [".meshboard-settings-dialog{display:flex;flex-direction:column;height:100%;overflow:hidden}.meshboard-settings-dialog kendo-tabstrip{flex:1;min-height:0;display:flex;flex-direction:column}.meshboard-settings-dialog kendo-tabstrip ::ng-deep .k-tabstrip-content{flex:1;min-height:0;overflow:hidden}.meshboard-settings-dialog .tab-content{height:100%;overflow-y:auto;padding:1.5rem}.meshboard-settings-dialog .settings-form{display:flex;flex-direction:column;gap:1.25rem}.meshboard-settings-dialog .settings-form kendo-formfield{display:flex;flex-direction:column;gap:.5rem}.meshboard-settings-dialog .settings-form kendo-formfield kendo-label{font-weight:500;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-textbox,.meshboard-settings-dialog .settings-form kendo-formfield kendo-textarea,.meshboard-settings-dialog .settings-form kendo-formfield kendo-numerictextbox{width:100%}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formhint{font-size:.75rem;color:var(--kendo-color-subtle, #757575)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formerror{font-size:.75rem;color:var(--kendo-color-error, #f44336)}.meshboard-settings-dialog .settings-form .section-title{font-size:.875rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242);text-transform:uppercase;letter-spacing:.5px;margin-top:.5rem;padding-bottom:.5rem;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.meshboard-settings-dialog .settings-form .checkbox-wrapper{display:flex;align-items:center;gap:.5rem}.meshboard-settings-dialog .settings-form .checkbox-wrapper .checkbox-label{font-weight:400;cursor:pointer}.meshboard-settings-dialog .dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;background-color:var(--kendo-color-surface-alt, white)}\n"] }]
|
|
27659
28191
|
}] });
|
|
27660
28192
|
|
|
27661
28193
|
/**
|
|
@@ -28520,6 +29052,10 @@ class MeshBoardViewComponent {
|
|
|
28520
29052
|
// Config dialog state
|
|
28521
29053
|
configDialogSubscription = null;
|
|
28522
29054
|
navigationSubscription = null;
|
|
29055
|
+
// Auto-refresh polling
|
|
29056
|
+
autoRefreshTimerId = null;
|
|
29057
|
+
autoRefreshActiveSeconds = 0;
|
|
29058
|
+
visibilityListener = () => this.evaluateAutoRefresh();
|
|
28523
29059
|
// State signals
|
|
28524
29060
|
config = this.stateService.meshBoardConfig;
|
|
28525
29061
|
isEditMode = this.editModeService.isEditMode;
|
|
@@ -28601,6 +29137,16 @@ class MeshBoardViewComponent {
|
|
|
28601
29137
|
}
|
|
28602
29138
|
}
|
|
28603
29139
|
});
|
|
29140
|
+
// Effect to re-evaluate the auto-refresh timer whenever the MeshBoard
|
|
29141
|
+
// config changes (e.g. user edits autoRefreshSeconds in settings, or a
|
|
29142
|
+
// different board is loaded). Re-reads on the same interval skip the
|
|
29143
|
+
// restart inside evaluateAutoRefresh().
|
|
29144
|
+
effect(() => {
|
|
29145
|
+
// Touch the signals we depend on so Angular re-fires the effect.
|
|
29146
|
+
const seconds = this.config().autoRefreshSeconds ?? 0;
|
|
29147
|
+
void seconds;
|
|
29148
|
+
this.evaluateAutoRefresh();
|
|
29149
|
+
});
|
|
28604
29150
|
// Update breadcrumb after each navigation (BreadCrumbService recreates items on NavigationEnd)
|
|
28605
29151
|
if (this.breadCrumbService) {
|
|
28606
29152
|
this.navigationSubscription = this.router.events
|
|
@@ -28687,6 +29233,13 @@ class MeshBoardViewComponent {
|
|
|
28687
29233
|
this.initialLoadComplete = true;
|
|
28688
29234
|
// Update breadcrumb with MeshBoard name
|
|
28689
29235
|
this.updateBreadcrumb();
|
|
29236
|
+
// Wire visibility-aware auto-refresh. The config-watching effect already
|
|
29237
|
+
// re-evaluates on signal changes; this listener handles the tab going
|
|
29238
|
+
// background → foreground without a config change.
|
|
29239
|
+
if (typeof document !== 'undefined') {
|
|
29240
|
+
document.addEventListener('visibilitychange', this.visibilityListener);
|
|
29241
|
+
}
|
|
29242
|
+
this.evaluateAutoRefresh();
|
|
28690
29243
|
this._isInitialized.set(true);
|
|
28691
29244
|
}
|
|
28692
29245
|
catch (err) {
|
|
@@ -28717,6 +29270,41 @@ class MeshBoardViewComponent {
|
|
|
28717
29270
|
ngOnDestroy() {
|
|
28718
29271
|
this.closeConfigDialog();
|
|
28719
29272
|
this.navigationSubscription?.unsubscribe();
|
|
29273
|
+
this.stopAutoRefresh();
|
|
29274
|
+
if (typeof document !== 'undefined') {
|
|
29275
|
+
document.removeEventListener('visibilitychange', this.visibilityListener);
|
|
29276
|
+
}
|
|
29277
|
+
}
|
|
29278
|
+
/**
|
|
29279
|
+
* Starts, restarts, or stops the auto-refresh timer based on the MeshBoard
|
|
29280
|
+
* config and document visibility. Called on init, whenever config changes,
|
|
29281
|
+
* and whenever the tab visibility changes.
|
|
29282
|
+
*
|
|
29283
|
+
* Pause-on-hidden saves bandwidth and avoids Apollo cache thrashing when
|
|
29284
|
+
* the user has the tab in the background.
|
|
29285
|
+
*/
|
|
29286
|
+
evaluateAutoRefresh() {
|
|
29287
|
+
const seconds = this.config().autoRefreshSeconds ?? 0;
|
|
29288
|
+
const tabVisible = typeof document === 'undefined' || !document.hidden;
|
|
29289
|
+
if (seconds <= 0 || !tabVisible) {
|
|
29290
|
+
this.stopAutoRefresh();
|
|
29291
|
+
return;
|
|
29292
|
+
}
|
|
29293
|
+
if (this.autoRefreshTimerId !== null && this.autoRefreshActiveSeconds === seconds) {
|
|
29294
|
+
return; // already running with the same interval
|
|
29295
|
+
}
|
|
29296
|
+
this.stopAutoRefresh();
|
|
29297
|
+
this.autoRefreshActiveSeconds = seconds;
|
|
29298
|
+
this.autoRefreshTimerId = setInterval(() => {
|
|
29299
|
+
void this.refresh();
|
|
29300
|
+
}, seconds * 1000);
|
|
29301
|
+
}
|
|
29302
|
+
stopAutoRefresh() {
|
|
29303
|
+
if (this.autoRefreshTimerId !== null) {
|
|
29304
|
+
clearInterval(this.autoRefreshTimerId);
|
|
29305
|
+
this.autoRefreshTimerId = null;
|
|
29306
|
+
}
|
|
29307
|
+
this.autoRefreshActiveSeconds = 0;
|
|
28720
29308
|
}
|
|
28721
29309
|
/**
|
|
28722
29310
|
* Preloads data for all widgets to improve initial rendering performance.
|
|
@@ -29633,5 +30221,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
29633
30221
|
* Generated bundle index. Do not edit.
|
|
29634
30222
|
*/
|
|
29635
30223
|
|
|
29636
|
-
export { AddWidgetDialogComponent, AiInsightsConfigDialogComponent, AiInsightsService, AiInsightsWidgetComponent, AlertBannerConfigDialogComponent, AlertBannerWidgetComponent, AlertListConfigDialogComponent, AlertListWidgetComponent, AssociationsConfigDialogComponent, BarChartConfigDialogComponent, BarChartWidgetComponent, DashboardDataService, DashboardGridService, EditModeStateService, EditWidgetDialogComponent, EntityAssociationsWidgetComponent, EntityCardConfigDialogComponent, EntityCardWidgetComponent, EntityDetailDialogComponent, EntitySelectorEditorComponent, EntitySelectorToolbarComponent, GaugeConfigDialogComponent, GaugeWidgetComponent, HeatmapConfigDialogComponent, HeatmapWidgetComponent, KpiConfigDialogComponent, KpiWidgetComponent, LineChartConfigDialogComponent, LineChartWidgetComponent, MESHBOARD_OPTIONS, MESHBOARD_TENANT_ID_PROVIDER, MarkdownConfigDialogComponent, MarkdownWidgetComponent, MeshBoardDataService, MeshBoardGridService, MeshBoardManagerDialogComponent, MeshBoardPersistenceService, MeshBoardSettingsDialogComponent, MeshBoardSettingsResult, MeshBoardStateService, MeshBoardViewComponent, PieChartConfigDialogComponent, PieChartWidgetComponent, QuerySelectorComponent, RuntimeEntitySelectorComponent, ServiceHealthConfigDialogComponent, ServiceHealthWidgetComponent, StatsGridConfigDialogComponent, StatsGridWidgetComponent, StatusIndicatorConfigDialogComponent, StatusIndicatorWidgetComponent, StatusListConfigDialogComponent, StatusListWidgetComponent, SummaryCardConfigDialogComponent, SummaryCardWidgetComponent, TableConfigDialogComponent, TableWidgetComponent, TableWidgetDataSourceDirective, WidgetFactoryService, WidgetGroupComponent, WidgetGroupConfigDialogComponent, WidgetNotConfiguredComponent, WidgetRegistryService, provideDefaultWidgets, provideMeshBoard, provideProcessWidget, provideWidgetRegistrations, registerDefaultWidgets, registerProcessWidget };
|
|
30224
|
+
export { AddWidgetDialogComponent, AiInsightsConfigDialogComponent, AiInsightsService, AiInsightsWidgetComponent, AlertBannerConfigDialogComponent, AlertBannerWidgetComponent, AlertListConfigDialogComponent, AlertListWidgetComponent, AssociationsConfigDialogComponent, BarChartConfigDialogComponent, BarChartWidgetComponent, DashboardDataService, DashboardGridService, EditModeStateService, EditWidgetDialogComponent, EntityAssociationsWidgetComponent, EntityCardConfigDialogComponent, EntityCardWidgetComponent, EntityDetailDialogComponent, EntitySelectorEditorComponent, EntitySelectorToolbarComponent, GaugeConfigDialogComponent, GaugeWidgetComponent, HeatmapConfigDialogComponent, HeatmapWidgetComponent, KpiConfigDialogComponent, KpiWidgetComponent, LineChartConfigDialogComponent, LineChartWidgetComponent, MESHBOARD_OPTIONS, MESHBOARD_TENANT_ID_PROVIDER, MarkdownConfigDialogComponent, MarkdownWidgetComponent, MeshBoardDataService, MeshBoardGridService, MeshBoardManagerDialogComponent, MeshBoardPersistenceService, MeshBoardSettingsDialogComponent, MeshBoardSettingsResult, MeshBoardStateService, MeshBoardViewComponent, PieChartConfigDialogComponent, PieChartWidgetComponent, QueryExecutorService, QuerySelectorComponent, RuntimeEntitySelectorComponent, ServiceHealthConfigDialogComponent, ServiceHealthWidgetComponent, StatsGridConfigDialogComponent, StatsGridWidgetComponent, StatusIndicatorConfigDialogComponent, StatusIndicatorWidgetComponent, StatusListConfigDialogComponent, StatusListWidgetComponent, SummaryCardConfigDialogComponent, SummaryCardWidgetComponent, TableConfigDialogComponent, TableWidgetComponent, TableWidgetDataSourceDirective, WidgetFactoryService, WidgetGroupComponent, WidgetGroupConfigDialogComponent, WidgetNotConfiguredComponent, WidgetRegistryService, classifyQuery, provideDefaultWidgets, provideMeshBoard, provideProcessWidget, provideWidgetRegistrations, queryFamily, registerDefaultWidgets, registerProcessWidget };
|
|
29637
30225
|
//# sourceMappingURL=meshmakers-octo-meshboard.mjs.map
|