@meshmakers/octo-meshboard 3.4.160 → 3.4.170
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,273 @@ 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
|
+
const hasOverride = args.from != null || args.to != null || args.interval != null || args.limit != null || args.queryMode != null;
|
|
2911
|
+
if (!hasOverride) {
|
|
2912
|
+
return undefined;
|
|
2913
|
+
}
|
|
2914
|
+
return {
|
|
2915
|
+
from: args.from ?? undefined,
|
|
2916
|
+
to: args.to ?? undefined,
|
|
2917
|
+
interval: args.interval ?? undefined,
|
|
2918
|
+
limit: args.limit ?? undefined,
|
|
2919
|
+
queryMode: args.queryMode ?? QueryModeDto.DefaultDto
|
|
2920
|
+
};
|
|
2921
|
+
}
|
|
2922
|
+
mapColumns(columns) {
|
|
2923
|
+
if (!columns)
|
|
2924
|
+
return [];
|
|
2925
|
+
const result = [];
|
|
2926
|
+
for (const col of columns) {
|
|
2927
|
+
if (!col?.attributePath)
|
|
2928
|
+
continue;
|
|
2929
|
+
result.push({
|
|
2930
|
+
attributePath: col.attributePath,
|
|
2931
|
+
attributeValueType: col.attributeValueType ?? null,
|
|
2932
|
+
aggregationType: col.aggregationType ?? null
|
|
2933
|
+
});
|
|
2934
|
+
}
|
|
2935
|
+
return result;
|
|
2936
|
+
}
|
|
2937
|
+
mapRuntimeRows(rows) {
|
|
2938
|
+
if (!rows)
|
|
2939
|
+
return [];
|
|
2940
|
+
const result = [];
|
|
2941
|
+
for (const row of rows) {
|
|
2942
|
+
if (!row)
|
|
2943
|
+
continue;
|
|
2944
|
+
const r = row;
|
|
2945
|
+
result.push({
|
|
2946
|
+
__typename: r.__typename,
|
|
2947
|
+
rtId: r.rtId ?? null,
|
|
2948
|
+
ckTypeId: r.ckTypeId ?? null,
|
|
2949
|
+
cells: this.mapCells(r.cells?.items)
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
return result;
|
|
2953
|
+
}
|
|
2954
|
+
mapStreamDataRows(rows) {
|
|
2955
|
+
if (!rows)
|
|
2956
|
+
return [];
|
|
2957
|
+
const result = [];
|
|
2958
|
+
for (const row of rows) {
|
|
2959
|
+
if (!row)
|
|
2960
|
+
continue;
|
|
2961
|
+
const r = row;
|
|
2962
|
+
const ckTypeId = typeof r.ckTypeId === 'string' ? r.ckTypeId : (r.ckTypeId?.fullName ?? null);
|
|
2963
|
+
result.push({
|
|
2964
|
+
__typename: r.__typename ?? 'StreamDataQueryRow',
|
|
2965
|
+
rtId: r.rtId ?? null,
|
|
2966
|
+
ckTypeId,
|
|
2967
|
+
timestamp: r.timestamp ?? null,
|
|
2968
|
+
rtWellKnownName: r.rtWellKnownName ?? null,
|
|
2969
|
+
rtCreationDateTime: r.rtCreationDateTime ?? null,
|
|
2970
|
+
rtChangedDateTime: r.rtChangedDateTime ?? null,
|
|
2971
|
+
cells: this.mapCells(r.cells?.items)
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
return result;
|
|
2975
|
+
}
|
|
2976
|
+
mapCells(cells) {
|
|
2977
|
+
if (!cells)
|
|
2978
|
+
return [];
|
|
2979
|
+
const result = [];
|
|
2980
|
+
for (const cell of cells) {
|
|
2981
|
+
if (!cell?.attributePath)
|
|
2982
|
+
continue;
|
|
2983
|
+
result.push({ attributePath: cell.attributePath, value: cell.value });
|
|
2984
|
+
}
|
|
2985
|
+
return result;
|
|
2986
|
+
}
|
|
2987
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: QueryExecutorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2988
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: QueryExecutorService, providedIn: 'root' });
|
|
2989
|
+
}
|
|
2990
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: QueryExecutorService, decorators: [{
|
|
2991
|
+
type: Injectable,
|
|
2992
|
+
args: [{ providedIn: 'root' }]
|
|
2993
|
+
}] });
|
|
2994
|
+
|
|
2590
2995
|
const GetAssociationTargetsDocumentDto = gql `
|
|
2591
2996
|
query getAssociationTargets($rtId: OctoObjectId!, $ckTypeId: String!, $targetCkTypeId: String!, $roleId: String!, $direction: GraphDirection!, $first: Int, $attributeNames: [String]) {
|
|
2592
2997
|
runtime {
|
|
@@ -2859,7 +3264,7 @@ class MeshBoardDataService {
|
|
|
2859
3264
|
getDashboardEntityGQL = inject(GetDashboardEntityDtoGQL);
|
|
2860
3265
|
getCkModelsWithStateGQL = inject(GetCkModelsWithStateDtoGQL);
|
|
2861
3266
|
getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
|
|
2862
|
-
|
|
3267
|
+
queryExecutor = inject(QueryExecutorService);
|
|
2863
3268
|
getAssociationTargetsGQL = inject(GetAssociationTargetsDtoGQL);
|
|
2864
3269
|
getCkTypeAttributesGQL = inject(GetCkTypeAttributesForMeshboardDtoGQL);
|
|
2865
3270
|
apollo = inject(Apollo);
|
|
@@ -3070,8 +3475,10 @@ class MeshBoardDataService {
|
|
|
3070
3475
|
async fetchRepeaterData(dataSource) {
|
|
3071
3476
|
const maxItems = dataSource.maxItems ?? 50;
|
|
3072
3477
|
if (dataSource.queryRtId) {
|
|
3073
|
-
// Query Mode: Execute persistent query
|
|
3074
|
-
|
|
3478
|
+
// Query Mode: Execute persistent query (runtime or stream-data).
|
|
3479
|
+
// `queryFamily` may be undefined for legacy configs — the executor falls
|
|
3480
|
+
// back to a one-time lookup keyed by rtId.
|
|
3481
|
+
return this.fetchRepeaterFromQuery(dataSource.queryFamily, dataSource.queryRtId, maxItems);
|
|
3075
3482
|
}
|
|
3076
3483
|
else if (dataSource.ckTypeId) {
|
|
3077
3484
|
// Entity Mode: Load entities by CK type
|
|
@@ -3081,49 +3488,33 @@ class MeshBoardDataService {
|
|
|
3081
3488
|
return [];
|
|
3082
3489
|
}
|
|
3083
3490
|
/**
|
|
3084
|
-
* Fetches repeater data from a persistent query.
|
|
3085
|
-
*
|
|
3491
|
+
* Fetches repeater data from a persistent query (runtime or stream-data).
|
|
3492
|
+
* Always sends `streamDataArgs` when a time filter is active — the runtime
|
|
3493
|
+
* path ignores the field, so this is safe regardless of the resolved family.
|
|
3086
3494
|
*/
|
|
3087
|
-
async fetchRepeaterFromQuery(queryRtId, maxItems) {
|
|
3495
|
+
async fetchRepeaterFromQuery(family, queryRtId, maxItems) {
|
|
3088
3496
|
try {
|
|
3089
|
-
const
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
}
|
|
3497
|
+
const streamDataArgs = this.buildRepeaterStreamDataArgs();
|
|
3498
|
+
const result = await firstValueFrom(this.queryExecutor.execute(family, queryRtId, {
|
|
3499
|
+
first: maxItems,
|
|
3500
|
+
streamDataArgs
|
|
3094
3501
|
}));
|
|
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
3502
|
const items = [];
|
|
3105
|
-
for (const row of rows) {
|
|
3106
|
-
if (!row)
|
|
3107
|
-
continue;
|
|
3108
|
-
// Extract rtId from RtSimpleQueryRow if available
|
|
3503
|
+
for (const row of result.rows) {
|
|
3109
3504
|
const rtId = row.rtId ?? `row-${items.length}`;
|
|
3110
|
-
const ckTypeId = row.ckTypeId ??
|
|
3111
|
-
// Build attributes map from cells
|
|
3505
|
+
const ckTypeId = row.ckTypeId ?? result.associatedCkTypeId ?? '';
|
|
3506
|
+
// Build attributes map from cells; expose both sanitised (`a_b`) and
|
|
3507
|
+
// original (`a.b`) keys so widget configs can address either form.
|
|
3112
3508
|
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)
|
|
3509
|
+
for (const cell of row.cells) {
|
|
3118
3510
|
const sanitizedPath = cell.attributePath.replace(/\./g, '_');
|
|
3119
3511
|
attributes.set(sanitizedPath, cell.value);
|
|
3120
|
-
// Also store with original path for flexibility
|
|
3121
3512
|
attributes.set(cell.attributePath, cell.value);
|
|
3122
3513
|
}
|
|
3123
3514
|
items.push({
|
|
3124
3515
|
rtId,
|
|
3125
|
-
ckTypeId,
|
|
3126
|
-
rtWellKnownName: attributes.get('rtWellKnownName'),
|
|
3516
|
+
ckTypeId: ckTypeId ?? '',
|
|
3517
|
+
rtWellKnownName: row.rtWellKnownName ?? attributes.get('rtWellKnownName'),
|
|
3127
3518
|
attributes
|
|
3128
3519
|
});
|
|
3129
3520
|
}
|
|
@@ -3134,6 +3525,13 @@ class MeshBoardDataService {
|
|
|
3134
3525
|
return [];
|
|
3135
3526
|
}
|
|
3136
3527
|
}
|
|
3528
|
+
buildRepeaterStreamDataArgs() {
|
|
3529
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
3530
|
+
if (!range) {
|
|
3531
|
+
return undefined;
|
|
3532
|
+
}
|
|
3533
|
+
return { from: range.from, to: range.to };
|
|
3534
|
+
}
|
|
3137
3535
|
/**
|
|
3138
3536
|
* Fetches repeater data from entities by CK type.
|
|
3139
3537
|
* Maps entities to RepeaterDataItem objects.
|
|
@@ -4252,9 +4650,19 @@ function processPieChartData(rows, categoryField, valueField) {
|
|
|
4252
4650
|
|
|
4253
4651
|
class KpiWidgetComponent {
|
|
4254
4652
|
dataService = inject(DashboardDataService);
|
|
4255
|
-
|
|
4653
|
+
queryExecutor = inject(QueryExecutorService);
|
|
4256
4654
|
stateService = inject(MeshBoardStateService);
|
|
4257
4655
|
variableService = inject(MeshBoardVariableService);
|
|
4656
|
+
/**
|
|
4657
|
+
* Row __typenames KPI extraction recognises.
|
|
4658
|
+
* Runtime queries discriminate; stream-data queries collapse all kinds
|
|
4659
|
+
* (simple / aggregation / grouped / downsampling) into `StreamDataQueryRow`.
|
|
4660
|
+
*/
|
|
4661
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
4662
|
+
'RtAggregationQueryRow',
|
|
4663
|
+
'RtGroupingAggregationQueryRow',
|
|
4664
|
+
'StreamDataQueryRow'
|
|
4665
|
+
]);
|
|
4258
4666
|
config;
|
|
4259
4667
|
arrowUpIcon = arrowUpIcon;
|
|
4260
4668
|
arrowDownIcon = arrowDownIcon;
|
|
@@ -4506,43 +4914,29 @@ class KpiWidgetComponent {
|
|
|
4506
4914
|
this._isLoading.set(true);
|
|
4507
4915
|
this._error.set(null);
|
|
4508
4916
|
try {
|
|
4509
|
-
// Convert widget filters to GraphQL format
|
|
4510
4917
|
const fieldFilter = this.convertFiltersToDto(this.config.filters);
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4918
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
4919
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
4920
|
+
// unconditionally because the runtime path ignores it.
|
|
4921
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
4922
|
+
const result = await firstValueFrom(this.queryExecutor.execute(dataSource.queryFamily, dataSource.queryRtId, {
|
|
4923
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
4924
|
+
streamDataArgs
|
|
4516
4925
|
}).pipe(catchError(err => {
|
|
4517
4926
|
console.error('Error loading KPI query data:', err);
|
|
4518
4927
|
throw err;
|
|
4519
4928
|
})));
|
|
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
4929
|
let value = 0;
|
|
4533
4930
|
const queryMode = this.config.queryMode ?? 'simpleCount';
|
|
4534
4931
|
switch (queryMode) {
|
|
4535
4932
|
case 'simpleCount':
|
|
4536
|
-
|
|
4537
|
-
value = queryResult.rows?.totalCount ?? 0;
|
|
4933
|
+
value = result.totalCount;
|
|
4538
4934
|
break;
|
|
4539
4935
|
case 'aggregation':
|
|
4540
|
-
|
|
4541
|
-
value = this.extractAggregationValue(queryResult);
|
|
4936
|
+
value = this.extractAggregationValue(result);
|
|
4542
4937
|
break;
|
|
4543
4938
|
case 'groupedAggregation':
|
|
4544
|
-
|
|
4545
|
-
value = this.extractGroupedAggregationValue(queryResult);
|
|
4939
|
+
value = this.extractGroupedAggregationValue(result);
|
|
4546
4940
|
break;
|
|
4547
4941
|
}
|
|
4548
4942
|
// Create a synthetic entity with the value
|
|
@@ -4561,48 +4955,39 @@ class KpiWidgetComponent {
|
|
|
4561
4955
|
this._isLoading.set(false);
|
|
4562
4956
|
}
|
|
4563
4957
|
}
|
|
4958
|
+
buildStreamDataArgs() {
|
|
4959
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
4960
|
+
if (!range) {
|
|
4961
|
+
return undefined;
|
|
4962
|
+
}
|
|
4963
|
+
return { from: range.from, to: range.to };
|
|
4964
|
+
}
|
|
4564
4965
|
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 ?? ''));
|
|
4966
|
+
const firstRow = queryResult.rows.find(row => KpiWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''));
|
|
4569
4967
|
if (!firstRow)
|
|
4570
4968
|
return 0;
|
|
4571
|
-
const queryRow = firstRow;
|
|
4572
|
-
const cells = queryRow.cells?.items ?? [];
|
|
4573
|
-
// Find the value field or use the first cell
|
|
4574
4969
|
const valueField = this.config.queryValueField;
|
|
4575
|
-
for (const cell of cells) {
|
|
4576
|
-
if (!cell?.attributePath)
|
|
4577
|
-
continue;
|
|
4970
|
+
for (const cell of firstRow.cells) {
|
|
4578
4971
|
if (valueField && matchesAttributePath(cell.attributePath, valueField)) {
|
|
4579
4972
|
return this.extractCellValue(cell.value);
|
|
4580
4973
|
}
|
|
4581
4974
|
}
|
|
4582
4975
|
// Fallback: return first cell value if no specific field configured
|
|
4583
|
-
|
|
4584
|
-
return firstCell ? this.extractCellValue(firstCell.value) : 0;
|
|
4976
|
+
return firstRow.cells.length > 0 ? this.extractCellValue(firstRow.cells[0].value) : 0;
|
|
4585
4977
|
}
|
|
4586
4978
|
extractGroupedAggregationValue(queryResult) {
|
|
4587
|
-
const rows = queryResult.rows?.items ?? [];
|
|
4588
|
-
const supportedRowTypes = ['RtGroupingAggregationQueryRow', 'RtAggregationQueryRow'];
|
|
4589
4979
|
const categoryField = this.config.queryCategoryField;
|
|
4590
4980
|
const categoryValue = this.config.queryCategoryValue;
|
|
4591
4981
|
const valueField = this.config.queryValueField;
|
|
4592
4982
|
if (!categoryField || !categoryValue || !valueField) {
|
|
4593
4983
|
return 0;
|
|
4594
4984
|
}
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
if (!row || !supportedRowTypes.includes(row.__typename ?? ''))
|
|
4985
|
+
for (const row of queryResult.rows) {
|
|
4986
|
+
if (!KpiWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''))
|
|
4598
4987
|
continue;
|
|
4599
|
-
const queryRow = row;
|
|
4600
|
-
const cells = queryRow.cells?.items ?? [];
|
|
4601
4988
|
let categoryMatch = false;
|
|
4602
4989
|
let value = 0;
|
|
4603
|
-
for (const cell of cells) {
|
|
4604
|
-
if (!cell?.attributePath)
|
|
4605
|
-
continue;
|
|
4990
|
+
for (const cell of row.cells) {
|
|
4606
4991
|
if (matchesAttributePath(cell.attributePath, categoryField) && String(cell.value) === categoryValue) {
|
|
4607
4992
|
categoryMatch = true;
|
|
4608
4993
|
}
|
|
@@ -4692,8 +5077,18 @@ class KpiConfigDialogComponent {
|
|
|
4692
5077
|
getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
|
|
4693
5078
|
ckTypeSelectorService = inject(CkTypeSelectorService);
|
|
4694
5079
|
attributeSelectorService = inject(AttributeSelectorService);
|
|
4695
|
-
executeRuntimeQueryGQL = inject(ExecuteRuntimeQueryDtoGQL);
|
|
4696
5080
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
5081
|
+
queryExecutor = inject(QueryExecutorService);
|
|
5082
|
+
/**
|
|
5083
|
+
* Row __typenames the dialog recognises when collecting distinct category
|
|
5084
|
+
* values from a query result for the grouped-aggregation category picker.
|
|
5085
|
+
*/
|
|
5086
|
+
static INTROSPECTION_ROW_TYPES = new Set([
|
|
5087
|
+
'RtSimpleQueryRow',
|
|
5088
|
+
'RtAggregationQueryRow',
|
|
5089
|
+
'RtGroupingAggregationQueryRow',
|
|
5090
|
+
'StreamDataQueryRow'
|
|
5091
|
+
]);
|
|
4697
5092
|
meshBoardStateService = inject(MeshBoardStateService);
|
|
4698
5093
|
windowRef = inject(WindowRef);
|
|
4699
5094
|
ckTypeSelectorInput;
|
|
@@ -4712,6 +5107,7 @@ class KpiConfigDialogComponent {
|
|
|
4712
5107
|
initialDataSourceType;
|
|
4713
5108
|
initialQueryRtId;
|
|
4714
5109
|
initialQueryName;
|
|
5110
|
+
initialQueryFamily;
|
|
4715
5111
|
initialQueryMode;
|
|
4716
5112
|
initialQueryValueField;
|
|
4717
5113
|
initialQueryCategoryField;
|
|
@@ -4985,6 +5381,7 @@ class KpiConfigDialogComponent {
|
|
|
4985
5381
|
return;
|
|
4986
5382
|
}
|
|
4987
5383
|
if (this.dataSourceType === 'persistentQuery' && this.selectedPersistentQuery) {
|
|
5384
|
+
const family = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
4988
5385
|
this.windowRef.close({
|
|
4989
5386
|
dataSourceType: 'persistentQuery',
|
|
4990
5387
|
ckTypeId: '',
|
|
@@ -4992,6 +5389,7 @@ class KpiConfigDialogComponent {
|
|
|
4992
5389
|
valueAttribute: '',
|
|
4993
5390
|
queryRtId: this.selectedPersistentQuery.rtId,
|
|
4994
5391
|
queryName: this.selectedPersistentQuery.name,
|
|
5392
|
+
queryFamily: family,
|
|
4995
5393
|
queryMode: this.queryMode,
|
|
4996
5394
|
queryValueField: this.form.queryValueField || undefined,
|
|
4997
5395
|
queryCategoryField: this.form.queryCategoryField || undefined,
|
|
@@ -5058,35 +5456,16 @@ class KpiConfigDialogComponent {
|
|
|
5058
5456
|
const rtId = queryRtId || this.selectedPersistentQuery?.rtId;
|
|
5059
5457
|
if (!rtId)
|
|
5060
5458
|
return;
|
|
5459
|
+
// queryFamily may be undefined when the selected query metadata is missing —
|
|
5460
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
5461
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
5061
5462
|
this.isLoadingQueryColumns = true;
|
|
5062
5463
|
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
|
-
}
|
|
5464
|
+
this.queryColumns = await this.fetchColumnsForFamily(family, rtId);
|
|
5465
|
+
// Category values for grouped aggregation are loaded on-demand by
|
|
5466
|
+
// loadCategoryValuesForField — only when a categoryField is actually selected.
|
|
5467
|
+
if (this.queryColumns.length > 0 && this.queryMode === 'groupedAggregation' && this.form.queryCategoryField) {
|
|
5468
|
+
await this.loadCategoryValuesForField(rtId, this.form.queryCategoryField);
|
|
5090
5469
|
}
|
|
5091
5470
|
}
|
|
5092
5471
|
catch (error) {
|
|
@@ -5097,6 +5476,40 @@ class KpiConfigDialogComponent {
|
|
|
5097
5476
|
this.isLoadingQueryColumns = false;
|
|
5098
5477
|
}
|
|
5099
5478
|
}
|
|
5479
|
+
/**
|
|
5480
|
+
* Loads column metadata for the picker. Runtime queries use the
|
|
5481
|
+
* metadata-only resolver (no aggregation executed); stream-data queries
|
|
5482
|
+
* fall back to executing the query with `first: 1`. When `family` is
|
|
5483
|
+
* unknown (legacy configs), the executor resolves it once by rtId lookup.
|
|
5484
|
+
*/
|
|
5485
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
5486
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
5487
|
+
if (resolvedFamily === 'runtime') {
|
|
5488
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
5489
|
+
variables: { rtId }
|
|
5490
|
+
}));
|
|
5491
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
5492
|
+
if (!queryItem)
|
|
5493
|
+
return [];
|
|
5494
|
+
return (queryItem.columns ?? [])
|
|
5495
|
+
.filter((c) => c !== null)
|
|
5496
|
+
// Column AttributePath is already in the engine's wire form for aggregation /
|
|
5497
|
+
// grouping columns (e.g. `quantity_sum`, `operatingstatus`) so picker entries
|
|
5498
|
+
// double as both the visible label and the stored config value.
|
|
5499
|
+
.map(c => ({
|
|
5500
|
+
attributePath: c.attributePath ?? '',
|
|
5501
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
5502
|
+
aggregationType: c.aggregationType ?? null
|
|
5503
|
+
}));
|
|
5504
|
+
}
|
|
5505
|
+
// Stream-data: execute with a tiny page just to surface columns.
|
|
5506
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
5507
|
+
return sdResult.columns.map(c => ({
|
|
5508
|
+
attributePath: c.attributePath,
|
|
5509
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
5510
|
+
aggregationType: c.aggregationType ?? null
|
|
5511
|
+
}));
|
|
5512
|
+
}
|
|
5100
5513
|
async onCategoryFieldChange(categoryField) {
|
|
5101
5514
|
this.form.queryCategoryField = categoryField;
|
|
5102
5515
|
this.form.queryCategoryValue = '';
|
|
@@ -5108,36 +5521,23 @@ class KpiConfigDialogComponent {
|
|
|
5108
5521
|
async loadCategoryValuesForField(queryRtId, categoryField) {
|
|
5109
5522
|
this.isLoadingCategoryValues = true;
|
|
5110
5523
|
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
|
-
}
|
|
5524
|
+
// family may be undefined here — the executor falls back to a lookup.
|
|
5525
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
5526
|
+
const result = await firstValueFrom(this.queryExecutor.execute(family, queryRtId, { first: 100 }));
|
|
5527
|
+
const values = new Set();
|
|
5528
|
+
for (const row of result.rows) {
|
|
5529
|
+
if (!KpiConfigDialogComponent.INTROSPECTION_ROW_TYPES.has(row.__typename ?? ''))
|
|
5530
|
+
continue;
|
|
5531
|
+
for (const cell of row.cells) {
|
|
5532
|
+
if (matchesAttributePath(cell.attributePath, categoryField) && cell.value !== null && cell.value !== undefined) {
|
|
5533
|
+
values.add(String(cell.value));
|
|
5134
5534
|
}
|
|
5135
5535
|
}
|
|
5136
|
-
this.categoryValues = Array.from(values).map(v => ({
|
|
5137
|
-
value: v,
|
|
5138
|
-
displayValue: v
|
|
5139
|
-
}));
|
|
5140
5536
|
}
|
|
5537
|
+
this.categoryValues = Array.from(values).map(v => ({
|
|
5538
|
+
value: v,
|
|
5539
|
+
displayValue: v
|
|
5540
|
+
}));
|
|
5141
5541
|
}
|
|
5142
5542
|
catch (error) {
|
|
5143
5543
|
console.error('Error loading category values:', error);
|
|
@@ -5154,7 +5554,7 @@ class KpiConfigDialogComponent {
|
|
|
5154
5554
|
this.filters = updatedFilters;
|
|
5155
5555
|
}
|
|
5156
5556
|
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: `
|
|
5557
|
+
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
5558
|
<div class="config-container">
|
|
5159
5559
|
|
|
5160
5560
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -5524,7 +5924,7 @@ class KpiConfigDialogComponent {
|
|
|
5524
5924
|
</button>
|
|
5525
5925
|
</div>
|
|
5526
5926
|
</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"] }] });
|
|
5927
|
+
`, 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
5928
|
}
|
|
5529
5929
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: KpiConfigDialogComponent, decorators: [{
|
|
5530
5930
|
type: Component,
|
|
@@ -5941,6 +6341,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
5941
6341
|
type: Input
|
|
5942
6342
|
}], initialQueryName: [{
|
|
5943
6343
|
type: Input
|
|
6344
|
+
}], initialQueryFamily: [{
|
|
6345
|
+
type: Input
|
|
5944
6346
|
}], initialQueryMode: [{
|
|
5945
6347
|
type: Input
|
|
5946
6348
|
}], initialQueryValueField: [{
|
|
@@ -7364,7 +7766,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
7364
7766
|
*/
|
|
7365
7767
|
class TableWidgetDataSourceDirective extends OctoGraphQlDataSource {
|
|
7366
7768
|
getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
|
|
7367
|
-
|
|
7769
|
+
queryExecutor = inject(QueryExecutorService);
|
|
7368
7770
|
stateService = inject(MeshBoardStateService);
|
|
7369
7771
|
variableService = inject(MeshBoardVariableService);
|
|
7370
7772
|
_config = null;
|
|
@@ -7493,86 +7895,81 @@ class TableWidgetDataSourceDirective extends OctoGraphQlDataSource {
|
|
|
7493
7895
|
}
|
|
7494
7896
|
}
|
|
7495
7897
|
/**
|
|
7496
|
-
*
|
|
7497
|
-
*
|
|
7898
|
+
* Row __typenames the table widget knows how to flatten. Runtime queries
|
|
7899
|
+
* use three discriminated variants; stream-data queries collapse all
|
|
7900
|
+
* kinds (simple / aggregation / grouped / downsampling) into a single
|
|
7901
|
+
* `StreamDataQueryRow` type.
|
|
7902
|
+
*/
|
|
7903
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
7904
|
+
'RtSimpleQueryRow',
|
|
7905
|
+
'RtAggregationQueryRow',
|
|
7906
|
+
'RtGroupingAggregationQueryRow',
|
|
7907
|
+
'StreamDataQueryRow'
|
|
7908
|
+
]);
|
|
7909
|
+
/**
|
|
7910
|
+
* Fetches data from a persistent query (runtime or stream-data).
|
|
7911
|
+
* Family is determined from the cached `queryFamily` on the data source
|
|
7912
|
+
* (set by the config dialog when the user picks a query) and defaults to
|
|
7913
|
+
* `'runtime'` for legacy configs that predate stream-data support.
|
|
7498
7914
|
*/
|
|
7499
7915
|
fetchPersistentQueryData(dataSource, queryOptions) {
|
|
7500
|
-
// Convert widget-configured filters to GraphQL format (with variable resolution)
|
|
7501
7916
|
const fieldFilter = this.convertFiltersToDto(this._config?.filters);
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7917
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
7918
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
7919
|
+
// unconditionally because the runtime path ignores it.
|
|
7920
|
+
//
|
|
7921
|
+
// Precedence: MeshBoard time filter > query's intrinsic time bounds.
|
|
7922
|
+
// When no time filter is active, the persistent query uses its own bounds.
|
|
7923
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
7924
|
+
return this.queryExecutor.execute(dataSource.queryFamily, dataSource.queryRtId, {
|
|
7925
|
+
first: queryOptions.state.take ?? this._config?.pageSize ?? 10,
|
|
7926
|
+
after: GraphQL.offsetToCursor(queryOptions.state.skip ?? 0),
|
|
7927
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
7928
|
+
streamDataArgs
|
|
7509
7929
|
}).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 ?? ''),
|
|
7930
|
+
// Extract columns from query response and update signal.
|
|
7931
|
+
// Replace dots with underscores for grid compatibility (Kendo treats dots as nested paths).
|
|
7932
|
+
const columns = result.columns.map(c => ({
|
|
7933
|
+
attributePath: this.sanitizeFieldName(c.attributePath),
|
|
7524
7934
|
attributeValueType: c.attributeValueType ?? ''
|
|
7525
7935
|
}));
|
|
7526
7936
|
this._queryColumns.set(columns);
|
|
7527
|
-
// Emit event to notify component that columns have been loaded
|
|
7528
7937
|
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
7938
|
const columnPaths = new Set(columns.map(c => c.attributePath));
|
|
7534
7939
|
const hasRtIdColumn = columnPaths.has('rtId');
|
|
7535
7940
|
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);
|
|
7941
|
+
const data = result.rows
|
|
7942
|
+
.filter(row => TableWidgetDataSourceDirective.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''))
|
|
7943
|
+
.map((row, index) => this.queryRowToRecord(row, columns, hasRtIdColumn, hasCkTypeIdColumn, index));
|
|
7944
|
+
return new FetchResultTyped(data, result.totalCount);
|
|
7571
7945
|
}), catchError$1(err => {
|
|
7572
7946
|
console.error('Error fetching query data:', err);
|
|
7573
7947
|
return of(new FetchResultTyped([], 0));
|
|
7574
7948
|
}));
|
|
7575
7949
|
}
|
|
7950
|
+
/**
|
|
7951
|
+
* Flattens a unified `QueryResultRow` into a Kendo-grid-friendly record.
|
|
7952
|
+
* Cells are stored under their matching column's `attributePath` rather than
|
|
7953
|
+
* the cell's own — the engine emits cell paths in wire-form with a function
|
|
7954
|
+
* suffix (e.g. cell path `meterreading_count` for column path `meterReading`).
|
|
7955
|
+
* `matchesAttributePath` reconciles both forms.
|
|
7956
|
+
*/
|
|
7957
|
+
queryRowToRecord(row, columns, hasRtIdColumn, hasCkTypeIdColumn, index) {
|
|
7958
|
+
const record = {};
|
|
7959
|
+
if (hasRtIdColumn) {
|
|
7960
|
+
record['rtId'] = row.rtId ?? `agg-${index}`;
|
|
7961
|
+
}
|
|
7962
|
+
if (hasCkTypeIdColumn) {
|
|
7963
|
+
record['ckTypeId'] = row.ckTypeId ?? '';
|
|
7964
|
+
}
|
|
7965
|
+
for (const cell of row.cells) {
|
|
7966
|
+
const matchingColumn = columns.find(col => matchesAttributePath(cell.attributePath, col.attributePath));
|
|
7967
|
+
if (matchingColumn) {
|
|
7968
|
+
record[matchingColumn.attributePath] = cell.value;
|
|
7969
|
+
}
|
|
7970
|
+
}
|
|
7971
|
+
return record;
|
|
7972
|
+
}
|
|
7576
7973
|
/**
|
|
7577
7974
|
* Converts query columns to TableColumn format for display.
|
|
7578
7975
|
*/
|
|
@@ -7615,6 +8012,18 @@ class TableWidgetDataSourceDirective extends OctoGraphQlDataSource {
|
|
|
7615
8012
|
const variables = this.stateService.getVariables();
|
|
7616
8013
|
return this.variableService.convertToFieldFilterDto(filters, variables);
|
|
7617
8014
|
}
|
|
8015
|
+
/**
|
|
8016
|
+
* Builds `StreamDataExecutionArgs` from the MeshBoard's current time filter.
|
|
8017
|
+
* Returns `undefined` when no filter is active so the persistent query's
|
|
8018
|
+
* own bounds apply.
|
|
8019
|
+
*/
|
|
8020
|
+
buildStreamDataArgs() {
|
|
8021
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
8022
|
+
if (!range) {
|
|
8023
|
+
return undefined;
|
|
8024
|
+
}
|
|
8025
|
+
return { from: range.from, to: range.to };
|
|
8026
|
+
}
|
|
7618
8027
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TableWidgetDataSourceDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
7619
8028
|
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
8029
|
{
|
|
@@ -7850,6 +8259,7 @@ class TableConfigDialogComponent {
|
|
|
7850
8259
|
initialSortable;
|
|
7851
8260
|
initialQueryRtId;
|
|
7852
8261
|
initialQueryName;
|
|
8262
|
+
initialQueryFamily;
|
|
7853
8263
|
columnsIcon = columnsIcon;
|
|
7854
8264
|
sortIcon = sortAscIcon;
|
|
7855
8265
|
filterIcon = filterIcon;
|
|
@@ -8071,6 +8481,9 @@ class TableConfigDialogComponent {
|
|
|
8071
8481
|
operator: f.operator,
|
|
8072
8482
|
comparisonValue: f.comparisonValue
|
|
8073
8483
|
}));
|
|
8484
|
+
// Derive family from the selected query's CK type so the runtime executor
|
|
8485
|
+
// can route correctly without an extra lookup.
|
|
8486
|
+
const family = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
8074
8487
|
this.windowRef.close({
|
|
8075
8488
|
dataSourceType: 'persistentQuery',
|
|
8076
8489
|
ckTypeId: '', // Not used for persistent query
|
|
@@ -8079,6 +8492,7 @@ class TableConfigDialogComponent {
|
|
|
8079
8492
|
filters: queryFilterDtos,
|
|
8080
8493
|
queryRtId: this.selectedPersistentQuery.rtId,
|
|
8081
8494
|
queryName: this.selectedPersistentQuery.name,
|
|
8495
|
+
queryFamily: family,
|
|
8082
8496
|
pageSize: this.form.pageSize,
|
|
8083
8497
|
sortable: this.form.sortable
|
|
8084
8498
|
});
|
|
@@ -8095,7 +8509,7 @@ class TableConfigDialogComponent {
|
|
|
8095
8509
|
this.windowRef.close();
|
|
8096
8510
|
}
|
|
8097
8511
|
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: [
|
|
8512
|
+
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
8513
|
AttributeSelectorDialogService,
|
|
8100
8514
|
AttributeSortSelectorDialogService
|
|
8101
8515
|
], 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 +8712,7 @@ class TableConfigDialogComponent {
|
|
|
8298
8712
|
</button>
|
|
8299
8713
|
</div>
|
|
8300
8714
|
</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"] }] });
|
|
8715
|
+
`, 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
8716
|
}
|
|
8303
8717
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TableConfigDialogComponent, decorators: [{
|
|
8304
8718
|
type: Component,
|
|
@@ -8545,6 +8959,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
8545
8959
|
type: Input
|
|
8546
8960
|
}], initialQueryName: [{
|
|
8547
8961
|
type: Input
|
|
8962
|
+
}], initialQueryFamily: [{
|
|
8963
|
+
type: Input
|
|
8548
8964
|
}] } });
|
|
8549
8965
|
|
|
8550
8966
|
class GaugeWidgetComponent {
|
|
@@ -8563,7 +8979,12 @@ class GaugeWidgetComponent {
|
|
|
8563
8979
|
},
|
|
8564
8980
|
];
|
|
8565
8981
|
dataService = inject(DashboardDataService);
|
|
8566
|
-
|
|
8982
|
+
queryExecutor = inject(QueryExecutorService);
|
|
8983
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
8984
|
+
'RtAggregationQueryRow',
|
|
8985
|
+
'RtGroupingAggregationQueryRow',
|
|
8986
|
+
'StreamDataQueryRow'
|
|
8987
|
+
]);
|
|
8567
8988
|
stateService = inject(MeshBoardStateService);
|
|
8568
8989
|
variableService = inject(MeshBoardVariableService);
|
|
8569
8990
|
config;
|
|
@@ -8673,43 +9094,29 @@ class GaugeWidgetComponent {
|
|
|
8673
9094
|
this._isLoading.set(true);
|
|
8674
9095
|
this._error.set(null);
|
|
8675
9096
|
try {
|
|
8676
|
-
// Convert widget filters to GraphQL format
|
|
8677
9097
|
const fieldFilter = this.convertFiltersToDto(this.config.filters);
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
9098
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
9099
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
9100
|
+
// unconditionally because the runtime path ignores it.
|
|
9101
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
9102
|
+
const result = await firstValueFrom(this.queryExecutor.execute(dataSource.queryFamily, dataSource.queryRtId, {
|
|
9103
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
9104
|
+
streamDataArgs
|
|
8683
9105
|
}).pipe(catchError(err => {
|
|
8684
9106
|
console.error('Error loading Gauge query data:', err);
|
|
8685
9107
|
throw err;
|
|
8686
9108
|
})));
|
|
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
9109
|
let value = 0;
|
|
8700
9110
|
const queryMode = this.config.queryMode ?? 'simpleCount';
|
|
8701
9111
|
switch (queryMode) {
|
|
8702
9112
|
case 'simpleCount':
|
|
8703
|
-
|
|
8704
|
-
value = queryResult.rows?.totalCount ?? 0;
|
|
9113
|
+
value = result.totalCount;
|
|
8705
9114
|
break;
|
|
8706
9115
|
case 'aggregation':
|
|
8707
|
-
|
|
8708
|
-
value = this.extractAggregationValue(queryResult);
|
|
9116
|
+
value = this.extractAggregationValue(result);
|
|
8709
9117
|
break;
|
|
8710
9118
|
case 'groupedAggregation':
|
|
8711
|
-
|
|
8712
|
-
value = this.extractGroupedAggregationValue(queryResult);
|
|
9119
|
+
value = this.extractGroupedAggregationValue(result);
|
|
8713
9120
|
break;
|
|
8714
9121
|
}
|
|
8715
9122
|
// Create a synthetic entity with the value
|
|
@@ -8728,48 +9135,38 @@ class GaugeWidgetComponent {
|
|
|
8728
9135
|
this._isLoading.set(false);
|
|
8729
9136
|
}
|
|
8730
9137
|
}
|
|
9138
|
+
buildStreamDataArgs() {
|
|
9139
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
9140
|
+
if (!range) {
|
|
9141
|
+
return undefined;
|
|
9142
|
+
}
|
|
9143
|
+
return { from: range.from, to: range.to };
|
|
9144
|
+
}
|
|
8731
9145
|
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 ?? ''));
|
|
9146
|
+
const firstRow = queryResult.rows.find(row => GaugeWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''));
|
|
8736
9147
|
if (!firstRow)
|
|
8737
9148
|
return 0;
|
|
8738
|
-
const queryRow = firstRow;
|
|
8739
|
-
const cells = queryRow.cells?.items ?? [];
|
|
8740
|
-
// Find the value field or use the first numeric cell
|
|
8741
9149
|
const valueField = this.config.queryValueField;
|
|
8742
|
-
for (const cell of cells) {
|
|
8743
|
-
if (!cell?.attributePath)
|
|
8744
|
-
continue;
|
|
9150
|
+
for (const cell of firstRow.cells) {
|
|
8745
9151
|
if (valueField && matchesAttributePath(cell.attributePath, valueField)) {
|
|
8746
9152
|
return this.parseNumericValue(cell.value);
|
|
8747
9153
|
}
|
|
8748
9154
|
}
|
|
8749
|
-
|
|
8750
|
-
const firstCell = cells.find(c => c !== null);
|
|
8751
|
-
return firstCell ? this.parseNumericValue(firstCell.value) : 0;
|
|
9155
|
+
return firstRow.cells.length > 0 ? this.parseNumericValue(firstRow.cells[0].value) : 0;
|
|
8752
9156
|
}
|
|
8753
9157
|
extractGroupedAggregationValue(queryResult) {
|
|
8754
|
-
const rows = queryResult.rows?.items ?? [];
|
|
8755
|
-
const supportedRowTypes = ['RtGroupingAggregationQueryRow', 'RtAggregationQueryRow'];
|
|
8756
9158
|
const categoryField = this.config.queryCategoryField;
|
|
8757
9159
|
const categoryValue = this.config.queryCategoryValue;
|
|
8758
9160
|
const valueField = this.config.queryValueField;
|
|
8759
9161
|
if (!categoryField || !categoryValue || !valueField) {
|
|
8760
9162
|
return 0;
|
|
8761
9163
|
}
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
if (!row || !supportedRowTypes.includes(row.__typename ?? ''))
|
|
9164
|
+
for (const row of queryResult.rows) {
|
|
9165
|
+
if (!GaugeWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''))
|
|
8765
9166
|
continue;
|
|
8766
|
-
const queryRow = row;
|
|
8767
|
-
const cells = queryRow.cells?.items ?? [];
|
|
8768
9167
|
let categoryMatch = false;
|
|
8769
9168
|
let value = 0;
|
|
8770
|
-
for (const cell of cells) {
|
|
8771
|
-
if (!cell?.attributePath)
|
|
8772
|
-
continue;
|
|
9169
|
+
for (const cell of row.cells) {
|
|
8773
9170
|
if (matchesAttributePath(cell.attributePath, categoryField) && String(cell.value) === categoryValue) {
|
|
8774
9171
|
categoryMatch = true;
|
|
8775
9172
|
}
|
|
@@ -9157,8 +9554,18 @@ class GaugeConfigDialogComponent {
|
|
|
9157
9554
|
getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
|
|
9158
9555
|
ckTypeSelectorService = inject(CkTypeSelectorService);
|
|
9159
9556
|
attributeSelectorService = inject(AttributeSelectorService);
|
|
9160
|
-
executeRuntimeQueryGQL = inject(ExecuteRuntimeQueryDtoGQL);
|
|
9161
9557
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
9558
|
+
queryExecutor = inject(QueryExecutorService);
|
|
9559
|
+
/**
|
|
9560
|
+
* Row __typenames the dialog recognises when collecting distinct category
|
|
9561
|
+
* values from a query result for the grouped-aggregation category picker.
|
|
9562
|
+
*/
|
|
9563
|
+
static INTROSPECTION_ROW_TYPES = new Set([
|
|
9564
|
+
'RtSimpleQueryRow',
|
|
9565
|
+
'RtAggregationQueryRow',
|
|
9566
|
+
'RtGroupingAggregationQueryRow',
|
|
9567
|
+
'StreamDataQueryRow'
|
|
9568
|
+
]);
|
|
9162
9569
|
meshBoardStateService = inject(MeshBoardStateService);
|
|
9163
9570
|
windowRef = inject(WindowRef);
|
|
9164
9571
|
ckTypeSelectorInput;
|
|
@@ -9181,6 +9588,7 @@ class GaugeConfigDialogComponent {
|
|
|
9181
9588
|
initialDataSourceType;
|
|
9182
9589
|
initialQueryRtId;
|
|
9183
9590
|
initialQueryName;
|
|
9591
|
+
initialQueryFamily;
|
|
9184
9592
|
initialQueryMode;
|
|
9185
9593
|
initialQueryValueField;
|
|
9186
9594
|
initialQueryCategoryField;
|
|
@@ -9428,6 +9836,7 @@ class GaugeConfigDialogComponent {
|
|
|
9428
9836
|
}))
|
|
9429
9837
|
: undefined;
|
|
9430
9838
|
if (this.dataSourceType === 'persistentQuery' && this.selectedPersistentQuery) {
|
|
9839
|
+
const family = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
9431
9840
|
this.windowRef.close({
|
|
9432
9841
|
dataSourceType: 'persistentQuery',
|
|
9433
9842
|
ckTypeId: '',
|
|
@@ -9435,6 +9844,7 @@ class GaugeConfigDialogComponent {
|
|
|
9435
9844
|
valueAttribute: '',
|
|
9436
9845
|
queryRtId: this.selectedPersistentQuery.rtId,
|
|
9437
9846
|
queryName: this.selectedPersistentQuery.name,
|
|
9847
|
+
queryFamily: family,
|
|
9438
9848
|
queryMode: this.queryMode,
|
|
9439
9849
|
queryValueField: this.form.queryValueField || undefined,
|
|
9440
9850
|
queryCategoryField: this.form.queryCategoryField || undefined,
|
|
@@ -9507,31 +9917,13 @@ class GaugeConfigDialogComponent {
|
|
|
9507
9917
|
}
|
|
9508
9918
|
async loadQueryColumnsAndValues(queryRtId) {
|
|
9509
9919
|
this.isLoadingQueryColumns = true;
|
|
9920
|
+
// queryFamily may be undefined when the selected query metadata is missing —
|
|
9921
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
9922
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
9510
9923
|
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
|
-
}
|
|
9924
|
+
this.queryColumns = await this.fetchColumnsForFamily(family, queryRtId);
|
|
9925
|
+
if (this.queryColumns.length > 0 && this.queryMode === 'groupedAggregation' && this.form.queryCategoryField) {
|
|
9926
|
+
await this.loadCategoryValuesForField(queryRtId, this.form.queryCategoryField);
|
|
9535
9927
|
}
|
|
9536
9928
|
}
|
|
9537
9929
|
catch (error) {
|
|
@@ -9542,6 +9934,36 @@ class GaugeConfigDialogComponent {
|
|
|
9542
9934
|
this.isLoadingQueryColumns = false;
|
|
9543
9935
|
}
|
|
9544
9936
|
}
|
|
9937
|
+
/**
|
|
9938
|
+
* Runtime queries use the metadata-only resolver (no aggregation executed);
|
|
9939
|
+
* stream-data queries fall back to executing the query with `first: 1`.
|
|
9940
|
+
* When `family` is unknown (legacy configs), the executor resolves it once
|
|
9941
|
+
* by rtId lookup.
|
|
9942
|
+
*/
|
|
9943
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
9944
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
9945
|
+
if (resolvedFamily === 'runtime') {
|
|
9946
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
9947
|
+
variables: { rtId }
|
|
9948
|
+
}));
|
|
9949
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
9950
|
+
if (!queryItem)
|
|
9951
|
+
return [];
|
|
9952
|
+
return (queryItem.columns ?? [])
|
|
9953
|
+
.filter((c) => c !== null)
|
|
9954
|
+
.map(c => ({
|
|
9955
|
+
attributePath: c.attributePath ?? '',
|
|
9956
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
9957
|
+
aggregationType: c.aggregationType ?? null
|
|
9958
|
+
}));
|
|
9959
|
+
}
|
|
9960
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
9961
|
+
return sdResult.columns.map(c => ({
|
|
9962
|
+
attributePath: c.attributePath,
|
|
9963
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
9964
|
+
aggregationType: c.aggregationType ?? null
|
|
9965
|
+
}));
|
|
9966
|
+
}
|
|
9545
9967
|
async onCategoryFieldChange(categoryField) {
|
|
9546
9968
|
this.form.queryCategoryField = categoryField;
|
|
9547
9969
|
this.form.queryCategoryValue = '';
|
|
@@ -9552,37 +9974,24 @@ class GaugeConfigDialogComponent {
|
|
|
9552
9974
|
}
|
|
9553
9975
|
async loadCategoryValuesForField(queryRtId, categoryField) {
|
|
9554
9976
|
this.isLoadingCategoryValues = true;
|
|
9977
|
+
// family may be undefined — the executor falls back to a lookup.
|
|
9978
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
9555
9979
|
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
|
-
}
|
|
9980
|
+
const result = await firstValueFrom(this.queryExecutor.execute(family, queryRtId, { first: 100 }));
|
|
9981
|
+
const values = new Set();
|
|
9982
|
+
for (const row of result.rows) {
|
|
9983
|
+
if (!GaugeConfigDialogComponent.INTROSPECTION_ROW_TYPES.has(row.__typename ?? ''))
|
|
9984
|
+
continue;
|
|
9985
|
+
for (const cell of row.cells) {
|
|
9986
|
+
if (matchesAttributePath(cell.attributePath, categoryField) && cell.value !== null && cell.value !== undefined) {
|
|
9987
|
+
values.add(String(cell.value));
|
|
9579
9988
|
}
|
|
9580
9989
|
}
|
|
9581
|
-
this.categoryValues = Array.from(values).map(v => ({
|
|
9582
|
-
value: v,
|
|
9583
|
-
displayValue: v
|
|
9584
|
-
}));
|
|
9585
9990
|
}
|
|
9991
|
+
this.categoryValues = Array.from(values).map(v => ({
|
|
9992
|
+
value: v,
|
|
9993
|
+
displayValue: v
|
|
9994
|
+
}));
|
|
9586
9995
|
}
|
|
9587
9996
|
catch (error) {
|
|
9588
9997
|
console.error('Error loading category values:', error);
|
|
@@ -9599,7 +10008,7 @@ class GaugeConfigDialogComponent {
|
|
|
9599
10008
|
this.filters = updatedFilters;
|
|
9600
10009
|
}
|
|
9601
10010
|
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: `
|
|
10011
|
+
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
10012
|
<div class="config-container">
|
|
9604
10013
|
|
|
9605
10014
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -9997,7 +10406,7 @@ class GaugeConfigDialogComponent {
|
|
|
9997
10406
|
</button>
|
|
9998
10407
|
</div>
|
|
9999
10408
|
</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"] }] });
|
|
10409
|
+
`, 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
10410
|
}
|
|
10002
10411
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GaugeConfigDialogComponent, decorators: [{
|
|
10003
10412
|
type: Component,
|
|
@@ -10450,6 +10859,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
10450
10859
|
type: Input
|
|
10451
10860
|
}], initialQueryName: [{
|
|
10452
10861
|
type: Input
|
|
10862
|
+
}], initialQueryFamily: [{
|
|
10863
|
+
type: Input
|
|
10453
10864
|
}], initialQueryMode: [{
|
|
10454
10865
|
type: Input
|
|
10455
10866
|
}], initialQueryValueField: [{
|
|
@@ -10463,7 +10874,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
10463
10874
|
}] } });
|
|
10464
10875
|
|
|
10465
10876
|
class PieChartWidgetComponent {
|
|
10466
|
-
|
|
10877
|
+
queryExecutor = inject(QueryExecutorService);
|
|
10878
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
10879
|
+
'RtSimpleQueryRow',
|
|
10880
|
+
'RtAggregationQueryRow',
|
|
10881
|
+
'RtGroupingAggregationQueryRow',
|
|
10882
|
+
'StreamDataQueryRow'
|
|
10883
|
+
]);
|
|
10467
10884
|
dataService = inject(MeshBoardDataService);
|
|
10468
10885
|
stateService = inject(MeshBoardStateService);
|
|
10469
10886
|
variableService = inject(MeshBoardVariableService);
|
|
@@ -10588,35 +11005,22 @@ class PieChartWidgetComponent {
|
|
|
10588
11005
|
* Note: isNotConfigured() check in loadData() ensures queryRtId is set.
|
|
10589
11006
|
*/
|
|
10590
11007
|
async loadPersistentQueryData(dataSource) {
|
|
10591
|
-
// Convert widget filters to GraphQL format
|
|
10592
11008
|
const fieldFilter = this.convertFiltersToDto(this.config.filters);
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
11009
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
11010
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
11011
|
+
// unconditionally because the runtime path ignores it.
|
|
11012
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
11013
|
+
const result = await firstValueFrom(this.queryExecutor.execute(dataSource.queryFamily, dataSource.queryRtId, {
|
|
11014
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
11015
|
+
streamDataArgs
|
|
10598
11016
|
}).pipe(catchError(err => {
|
|
10599
11017
|
console.error('Error loading Pie Chart data:', err);
|
|
10600
11018
|
throw err;
|
|
10601
11019
|
})));
|
|
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
11020
|
// Extract columns to verify configured fields are present. Both forms (original CK
|
|
10615
11021
|
// path and engine wire-form) are accepted so saved configs survive the engine's
|
|
10616
11022
|
// switch to wire-form keys without a migration.
|
|
10617
|
-
const columnPaths =
|
|
10618
|
-
.filter((c) => c !== null)
|
|
10619
|
-
.map(c => c.attributePath ?? '');
|
|
11023
|
+
const columnPaths = result.columns.map(c => c.attributePath);
|
|
10620
11024
|
const categoryFieldPresent = columnPaths.some(p => matchesAttributePath(p, this.config.categoryField));
|
|
10621
11025
|
const valueFieldPresent = columnPaths.some(p => matchesAttributePath(p, this.config.valueField));
|
|
10622
11026
|
if (!categoryFieldPresent || !valueFieldPresent) {
|
|
@@ -10624,20 +11028,12 @@ class PieChartWidgetComponent {
|
|
|
10624
11028
|
this._isLoading.set(false);
|
|
10625
11029
|
return;
|
|
10626
11030
|
}
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
const supportedRowTypes = ['RtSimpleQueryRow', 'RtAggregationQueryRow', 'RtGroupingAggregationQueryRow'];
|
|
10630
|
-
const chartData = rows
|
|
10631
|
-
.filter((row) => row !== null)
|
|
10632
|
-
.filter(row => supportedRowTypes.includes(row.__typename ?? ''))
|
|
11031
|
+
const chartData = result.rows
|
|
11032
|
+
.filter(row => PieChartWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''))
|
|
10633
11033
|
.map(row => {
|
|
10634
|
-
const queryRow = row;
|
|
10635
|
-
const cells = queryRow.cells?.items ?? [];
|
|
10636
11034
|
let category = '';
|
|
10637
11035
|
let value = 0;
|
|
10638
|
-
for (const cell of cells) {
|
|
10639
|
-
if (!cell?.attributePath)
|
|
10640
|
-
continue;
|
|
11036
|
+
for (const cell of row.cells) {
|
|
10641
11037
|
if (matchesAttributePath(cell.attributePath, this.config.categoryField)) {
|
|
10642
11038
|
category = String(cell.value ?? '');
|
|
10643
11039
|
}
|
|
@@ -10652,6 +11048,13 @@ class PieChartWidgetComponent {
|
|
|
10652
11048
|
this._chartData.set(chartData);
|
|
10653
11049
|
this._isLoading.set(false);
|
|
10654
11050
|
}
|
|
11051
|
+
buildStreamDataArgs() {
|
|
11052
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
11053
|
+
if (!range) {
|
|
11054
|
+
return undefined;
|
|
11055
|
+
}
|
|
11056
|
+
return { from: range.from, to: range.to };
|
|
11057
|
+
}
|
|
10655
11058
|
/**
|
|
10656
11059
|
* Converts widget filter configuration to GraphQL FieldFilterDto format.
|
|
10657
11060
|
* Resolves MeshBoard variables in filter values before conversion.
|
|
@@ -10755,6 +11158,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
10755
11158
|
*/
|
|
10756
11159
|
class PieChartConfigDialogComponent {
|
|
10757
11160
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
11161
|
+
queryExecutor = inject(QueryExecutorService);
|
|
10758
11162
|
stateService = inject(MeshBoardStateService);
|
|
10759
11163
|
windowRef = inject(WindowRef);
|
|
10760
11164
|
querySelector;
|
|
@@ -10762,6 +11166,7 @@ class PieChartConfigDialogComponent {
|
|
|
10762
11166
|
initialDataSourceType;
|
|
10763
11167
|
initialQueryRtId;
|
|
10764
11168
|
initialQueryName;
|
|
11169
|
+
initialQueryFamily;
|
|
10765
11170
|
initialChartType;
|
|
10766
11171
|
initialCategoryField;
|
|
10767
11172
|
initialValueField;
|
|
@@ -10922,37 +11327,19 @@ class PieChartConfigDialogComponent {
|
|
|
10922
11327
|
}
|
|
10923
11328
|
async loadQueryColumns(queryRtId) {
|
|
10924
11329
|
this.isLoadingColumns = true;
|
|
11330
|
+
// family may be undefined when the selected query metadata is missing —
|
|
11331
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
11332
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
10925
11333
|
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
|
-
}
|
|
11334
|
+
this.queryColumns = await this.fetchColumnsForFamily(family, queryRtId);
|
|
11335
|
+
// Auto-select fields if only 2 columns (typical for grouped aggregations)
|
|
11336
|
+
if (this.queryColumns.length === 2 && !this.form.categoryField && !this.form.valueField) {
|
|
11337
|
+
const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
|
|
11338
|
+
const valueColumn = this.queryColumns.find(c => numericTypes.includes(c.attributeValueType));
|
|
11339
|
+
const categoryColumn = this.queryColumns.find(c => c !== valueColumn);
|
|
11340
|
+
if (valueColumn && categoryColumn) {
|
|
11341
|
+
this.form.valueField = valueColumn.attributePath;
|
|
11342
|
+
this.form.categoryField = categoryColumn.attributePath;
|
|
10956
11343
|
}
|
|
10957
11344
|
}
|
|
10958
11345
|
}
|
|
@@ -10964,6 +11351,35 @@ class PieChartConfigDialogComponent {
|
|
|
10964
11351
|
this.isLoadingColumns = false;
|
|
10965
11352
|
}
|
|
10966
11353
|
}
|
|
11354
|
+
/**
|
|
11355
|
+
* Runtime queries use the metadata-only resolver (no aggregation executed);
|
|
11356
|
+
* stream-data queries fall back to executing the query with `first: 1`
|
|
11357
|
+
* because the SD path has no dedicated column-introspection endpoint today.
|
|
11358
|
+
*/
|
|
11359
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
11360
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
11361
|
+
if (resolvedFamily === 'runtime') {
|
|
11362
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
11363
|
+
variables: { rtId }
|
|
11364
|
+
}));
|
|
11365
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
11366
|
+
if (!queryItem)
|
|
11367
|
+
return [];
|
|
11368
|
+
return (queryItem.columns ?? [])
|
|
11369
|
+
.filter((c) => c !== null)
|
|
11370
|
+
.map(c => ({
|
|
11371
|
+
attributePath: c.attributePath ?? '',
|
|
11372
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
11373
|
+
aggregationType: c.aggregationType ?? null
|
|
11374
|
+
}));
|
|
11375
|
+
}
|
|
11376
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
11377
|
+
return sdResult.columns.map(c => ({
|
|
11378
|
+
attributePath: c.attributePath,
|
|
11379
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
11380
|
+
aggregationType: c.aggregationType ?? null
|
|
11381
|
+
}));
|
|
11382
|
+
}
|
|
10967
11383
|
onFiltersChange(updatedFilters) {
|
|
10968
11384
|
this.filters = updatedFilters;
|
|
10969
11385
|
}
|
|
@@ -10993,6 +11409,7 @@ class PieChartConfigDialogComponent {
|
|
|
10993
11409
|
return;
|
|
10994
11410
|
result.queryRtId = this.selectedPersistentQuery.rtId;
|
|
10995
11411
|
result.queryName = this.selectedPersistentQuery.name;
|
|
11412
|
+
result.queryFamily = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
10996
11413
|
}
|
|
10997
11414
|
else if (this.form.dataSourceType === 'constructionKitQuery') {
|
|
10998
11415
|
result.ckQueryTarget = this.form.ckQueryTarget;
|
|
@@ -11004,7 +11421,7 @@ class PieChartConfigDialogComponent {
|
|
|
11004
11421
|
this.windowRef.close();
|
|
11005
11422
|
}
|
|
11006
11423
|
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: `
|
|
11424
|
+
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
11425
|
<div class="config-container">
|
|
11009
11426
|
|
|
11010
11427
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -11202,7 +11619,7 @@ class PieChartConfigDialogComponent {
|
|
|
11202
11619
|
</button>
|
|
11203
11620
|
</div>
|
|
11204
11621
|
</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"] }] });
|
|
11622
|
+
`, 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
11623
|
}
|
|
11207
11624
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PieChartConfigDialogComponent, decorators: [{
|
|
11208
11625
|
type: Component,
|
|
@@ -11424,6 +11841,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
11424
11841
|
type: Input
|
|
11425
11842
|
}], initialQueryName: [{
|
|
11426
11843
|
type: Input
|
|
11844
|
+
}], initialQueryFamily: [{
|
|
11845
|
+
type: Input
|
|
11427
11846
|
}], initialChartType: [{
|
|
11428
11847
|
type: Input
|
|
11429
11848
|
}], initialCategoryField: [{
|
|
@@ -11453,7 +11872,13 @@ const CHART_TYPE_MAPPING = {
|
|
|
11453
11872
|
stackedBar100: { type: 'bar', stack: '100%' }
|
|
11454
11873
|
};
|
|
11455
11874
|
class BarChartWidgetComponent {
|
|
11456
|
-
|
|
11875
|
+
queryExecutor = inject(QueryExecutorService);
|
|
11876
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
11877
|
+
'RtSimpleQueryRow',
|
|
11878
|
+
'RtAggregationQueryRow',
|
|
11879
|
+
'RtGroupingAggregationQueryRow',
|
|
11880
|
+
'StreamDataQueryRow'
|
|
11881
|
+
]);
|
|
11457
11882
|
stateService = inject(MeshBoardStateService);
|
|
11458
11883
|
variableService = inject(MeshBoardVariableService);
|
|
11459
11884
|
config;
|
|
@@ -11562,38 +11987,19 @@ class BarChartWidgetComponent {
|
|
|
11562
11987
|
this._isLoading.set(true);
|
|
11563
11988
|
this._error.set(null);
|
|
11564
11989
|
try {
|
|
11565
|
-
// Convert widget filters to GraphQL format
|
|
11566
11990
|
const fieldFilter = this.convertFiltersToDto(this.config.filters);
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
11570
|
-
|
|
11571
|
-
|
|
11991
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
11992
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
11993
|
+
// unconditionally because the runtime path ignores it.
|
|
11994
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
11995
|
+
const result = await firstValueFrom(this.queryExecutor.execute(queryDataSource.queryFamily, queryDataSource.queryRtId, {
|
|
11996
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
11997
|
+
streamDataArgs
|
|
11572
11998
|
}).pipe(catchError(err => {
|
|
11573
11999
|
console.error('Error loading Bar Chart data:', err);
|
|
11574
12000
|
throw err;
|
|
11575
12001
|
})));
|
|
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
|
|
12002
|
+
const filteredRows = result.rows.filter(row => BarChartWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''));
|
|
11597
12003
|
if (this.isDynamicSeriesMode()) {
|
|
11598
12004
|
this.processDynamicSeriesData(filteredRows);
|
|
11599
12005
|
}
|
|
@@ -11608,6 +12014,13 @@ class BarChartWidgetComponent {
|
|
|
11608
12014
|
this._isLoading.set(false);
|
|
11609
12015
|
}
|
|
11610
12016
|
}
|
|
12017
|
+
buildStreamDataArgs() {
|
|
12018
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
12019
|
+
if (!range) {
|
|
12020
|
+
return undefined;
|
|
12021
|
+
}
|
|
12022
|
+
return { from: range.from, to: range.to };
|
|
12023
|
+
}
|
|
11611
12024
|
/**
|
|
11612
12025
|
* Processes data in Static Series Mode.
|
|
11613
12026
|
* Each series in config.series corresponds to a separate numeric field.
|
|
@@ -11620,13 +12033,9 @@ class BarChartWidgetComponent {
|
|
|
11620
12033
|
seriesMap.set(seriesConfig.field, []);
|
|
11621
12034
|
}
|
|
11622
12035
|
for (const row of filteredRows) {
|
|
11623
|
-
const queryRow = row;
|
|
11624
|
-
const cells = queryRow.cells?.items ?? [];
|
|
11625
12036
|
let categoryValue = '';
|
|
11626
12037
|
const rowValues = new Map();
|
|
11627
|
-
for (const cell of cells) {
|
|
11628
|
-
if (!cell?.attributePath)
|
|
11629
|
-
continue;
|
|
12038
|
+
for (const cell of row.cells) {
|
|
11630
12039
|
if (matchesAttributePath(cell.attributePath, this.config.categoryField)) {
|
|
11631
12040
|
categoryValue = this.formatCategoryValue(cell.value);
|
|
11632
12041
|
}
|
|
@@ -11683,14 +12092,10 @@ class BarChartWidgetComponent {
|
|
|
11683
12092
|
const allCategories = new Set();
|
|
11684
12093
|
const allSeriesGroups = new Set();
|
|
11685
12094
|
for (const row of filteredRows) {
|
|
11686
|
-
const queryRow = row;
|
|
11687
|
-
const cells = queryRow.cells?.items ?? [];
|
|
11688
12095
|
let categoryValue = '';
|
|
11689
12096
|
let seriesGroupValue = '';
|
|
11690
12097
|
let numericValue = 0;
|
|
11691
|
-
for (const cell of cells) {
|
|
11692
|
-
if (!cell?.attributePath)
|
|
11693
|
-
continue;
|
|
12098
|
+
for (const cell of row.cells) {
|
|
11694
12099
|
if (matchesAttributePath(cell.attributePath, categoryField)) {
|
|
11695
12100
|
categoryValue = this.formatCategoryValue(cell.value);
|
|
11696
12101
|
}
|
|
@@ -11941,12 +12346,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
11941
12346
|
*/
|
|
11942
12347
|
class BarChartConfigDialogComponent {
|
|
11943
12348
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
12349
|
+
queryExecutor = inject(QueryExecutorService);
|
|
11944
12350
|
stateService = inject(MeshBoardStateService);
|
|
11945
12351
|
windowRef = inject(WindowRef);
|
|
11946
12352
|
querySelector;
|
|
11947
12353
|
// Initial values for editing
|
|
11948
12354
|
initialQueryRtId;
|
|
11949
12355
|
initialQueryName;
|
|
12356
|
+
initialQueryFamily;
|
|
11950
12357
|
initialChartType;
|
|
11951
12358
|
initialCategoryField;
|
|
11952
12359
|
initialSeries;
|
|
@@ -12078,41 +12485,23 @@ class BarChartConfigDialogComponent {
|
|
|
12078
12485
|
}
|
|
12079
12486
|
async loadQueryColumns(queryRtId) {
|
|
12080
12487
|
this.isLoadingColumns = true;
|
|
12488
|
+
// family may be undefined when the selected query metadata is missing —
|
|
12489
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
12490
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
12081
12491
|
try {
|
|
12082
|
-
|
|
12083
|
-
//
|
|
12084
|
-
const
|
|
12085
|
-
|
|
12086
|
-
|
|
12492
|
+
this.queryColumns = await this.fetchColumnsForFamily(family, queryRtId);
|
|
12493
|
+
// Filter numeric and non-numeric columns
|
|
12494
|
+
const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
|
|
12495
|
+
this.numericColumns = this.queryColumns.filter(c => numericTypes.includes(c.attributeValueType));
|
|
12496
|
+
this.nonNumericColumns = this.queryColumns.filter(c => !numericTypes.includes(c.attributeValueType));
|
|
12497
|
+
// Auto-select fields if possible and not editing
|
|
12498
|
+
if (!this.initialQueryRtId && this.queryColumns.length >= 2) {
|
|
12499
|
+
const categoryColumn = this.queryColumns.find(c => !numericTypes.includes(c.attributeValueType));
|
|
12500
|
+
if (categoryColumn) {
|
|
12501
|
+
this.form.categoryField = categoryColumn.attributePath;
|
|
12087
12502
|
}
|
|
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
|
-
}
|
|
12503
|
+
if (this.numericColumns.length > 0) {
|
|
12504
|
+
this.selectedSeriesFields = this.numericColumns.map(c => c.attributePath);
|
|
12116
12505
|
}
|
|
12117
12506
|
}
|
|
12118
12507
|
}
|
|
@@ -12126,6 +12515,35 @@ class BarChartConfigDialogComponent {
|
|
|
12126
12515
|
this.isLoadingColumns = false;
|
|
12127
12516
|
}
|
|
12128
12517
|
}
|
|
12518
|
+
/**
|
|
12519
|
+
* Runtime queries use the metadata-only resolver (no aggregation executed);
|
|
12520
|
+
* stream-data queries fall back to executing the query with `first: 1`
|
|
12521
|
+
* because the SD path has no dedicated column-introspection endpoint today.
|
|
12522
|
+
*/
|
|
12523
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
12524
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
12525
|
+
if (resolvedFamily === 'runtime') {
|
|
12526
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
12527
|
+
variables: { rtId }
|
|
12528
|
+
}));
|
|
12529
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
12530
|
+
if (!queryItem)
|
|
12531
|
+
return [];
|
|
12532
|
+
return (queryItem.columns ?? [])
|
|
12533
|
+
.filter((c) => c !== null)
|
|
12534
|
+
.map(c => ({
|
|
12535
|
+
attributePath: c.attributePath ?? '',
|
|
12536
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
12537
|
+
aggregationType: c.aggregationType ?? null
|
|
12538
|
+
}));
|
|
12539
|
+
}
|
|
12540
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
12541
|
+
return sdResult.columns.map(c => ({
|
|
12542
|
+
attributePath: c.attributePath,
|
|
12543
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
12544
|
+
aggregationType: c.aggregationType ?? null
|
|
12545
|
+
}));
|
|
12546
|
+
}
|
|
12129
12547
|
onSeriesFieldsChange(fields) {
|
|
12130
12548
|
this.selectedSeriesFields = fields;
|
|
12131
12549
|
}
|
|
@@ -12155,11 +12573,13 @@ class BarChartConfigDialogComponent {
|
|
|
12155
12573
|
comparisonValue: f.comparisonValue
|
|
12156
12574
|
}))
|
|
12157
12575
|
: undefined;
|
|
12576
|
+
const family = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
12158
12577
|
const result = {
|
|
12159
12578
|
ckTypeId: '',
|
|
12160
12579
|
rtId: '',
|
|
12161
12580
|
queryRtId: this.selectedPersistentQuery.rtId,
|
|
12162
12581
|
queryName: this.selectedPersistentQuery.name,
|
|
12582
|
+
queryFamily: family,
|
|
12163
12583
|
chartType: this.form.chartType,
|
|
12164
12584
|
categoryField: this.form.categoryField,
|
|
12165
12585
|
series,
|
|
@@ -12187,7 +12607,7 @@ class BarChartConfigDialogComponent {
|
|
|
12187
12607
|
this.windowRef.close();
|
|
12188
12608
|
}
|
|
12189
12609
|
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: `
|
|
12610
|
+
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
12611
|
<div class="config-container">
|
|
12192
12612
|
|
|
12193
12613
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -12448,7 +12868,7 @@ class BarChartConfigDialogComponent {
|
|
|
12448
12868
|
</button>
|
|
12449
12869
|
</div>
|
|
12450
12870
|
</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"] }] });
|
|
12871
|
+
`, 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
12872
|
}
|
|
12453
12873
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BarChartConfigDialogComponent, decorators: [{
|
|
12454
12874
|
type: Component,
|
|
@@ -12731,6 +13151,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
12731
13151
|
type: Input
|
|
12732
13152
|
}], initialQueryName: [{
|
|
12733
13153
|
type: Input
|
|
13154
|
+
}], initialQueryFamily: [{
|
|
13155
|
+
type: Input
|
|
12734
13156
|
}], initialChartType: [{
|
|
12735
13157
|
type: Input
|
|
12736
13158
|
}], initialCategoryField: [{
|
|
@@ -12756,7 +13178,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
12756
13178
|
}] } });
|
|
12757
13179
|
|
|
12758
13180
|
class LineChartWidgetComponent {
|
|
12759
|
-
|
|
13181
|
+
queryExecutor = inject(QueryExecutorService);
|
|
13182
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
13183
|
+
'RtSimpleQueryRow',
|
|
13184
|
+
'RtAggregationQueryRow',
|
|
13185
|
+
'RtGroupingAggregationQueryRow',
|
|
13186
|
+
'StreamDataQueryRow'
|
|
13187
|
+
]);
|
|
12760
13188
|
stateService = inject(MeshBoardStateService);
|
|
12761
13189
|
variableService = inject(MeshBoardVariableService);
|
|
12762
13190
|
config;
|
|
@@ -12868,36 +13296,18 @@ class LineChartWidgetComponent {
|
|
|
12868
13296
|
this._error.set(null);
|
|
12869
13297
|
try {
|
|
12870
13298
|
const fieldFilter = this.convertFiltersToDto(this.config.filters);
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
13299
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
13300
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
13301
|
+
// unconditionally because the runtime path ignores it.
|
|
13302
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
13303
|
+
const result = await firstValueFrom(this.queryExecutor.execute(queryDataSource.queryFamily, queryDataSource.queryRtId, {
|
|
13304
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
13305
|
+
streamDataArgs
|
|
12876
13306
|
}).pipe(catchError(err => {
|
|
12877
13307
|
console.error('Error loading Line Chart data:', err);
|
|
12878
13308
|
throw err;
|
|
12879
13309
|
})));
|
|
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 ?? ''));
|
|
13310
|
+
const filteredRows = result.rows.filter(row => LineChartWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''));
|
|
12901
13311
|
this.processData(filteredRows);
|
|
12902
13312
|
this._isLoading.set(false);
|
|
12903
13313
|
}
|
|
@@ -12907,6 +13317,13 @@ class LineChartWidgetComponent {
|
|
|
12907
13317
|
this._isLoading.set(false);
|
|
12908
13318
|
}
|
|
12909
13319
|
}
|
|
13320
|
+
buildStreamDataArgs() {
|
|
13321
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
13322
|
+
if (!range) {
|
|
13323
|
+
return undefined;
|
|
13324
|
+
}
|
|
13325
|
+
return { from: range.from, to: range.to };
|
|
13326
|
+
}
|
|
12910
13327
|
/**
|
|
12911
13328
|
* Processes query rows into line chart data.
|
|
12912
13329
|
* Groups by seriesGroupField, orders by categoryField (date), supports multi-axis by unitField.
|
|
@@ -12922,15 +13339,11 @@ class LineChartWidgetComponent {
|
|
|
12922
13339
|
const allSeriesGroups = new Set();
|
|
12923
13340
|
const seriesUnitMap = new Map(); // seriesGroup -> unit
|
|
12924
13341
|
for (const row of filteredRows) {
|
|
12925
|
-
const queryRow = row;
|
|
12926
|
-
const cells = queryRow.cells?.items ?? [];
|
|
12927
13342
|
let categoryValue = '';
|
|
12928
13343
|
let seriesGroupValue = '';
|
|
12929
13344
|
let numericValue = 0;
|
|
12930
13345
|
let unitValue = '';
|
|
12931
|
-
for (const cell of cells) {
|
|
12932
|
-
if (!cell?.attributePath)
|
|
12933
|
-
continue;
|
|
13346
|
+
for (const cell of row.cells) {
|
|
12934
13347
|
if (matchesAttributePath(cell.attributePath, categoryField)) {
|
|
12935
13348
|
categoryValue = String(cell.value ?? '');
|
|
12936
13349
|
}
|
|
@@ -13226,12 +13639,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
13226
13639
|
*/
|
|
13227
13640
|
class LineChartConfigDialogComponent {
|
|
13228
13641
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
13642
|
+
queryExecutor = inject(QueryExecutorService);
|
|
13229
13643
|
stateService = inject(MeshBoardStateService);
|
|
13230
13644
|
windowRef = inject(WindowRef);
|
|
13231
13645
|
querySelector;
|
|
13232
13646
|
// Initial values for editing
|
|
13233
13647
|
initialQueryRtId;
|
|
13234
13648
|
initialQueryName;
|
|
13649
|
+
initialQueryFamily;
|
|
13235
13650
|
initialChartType;
|
|
13236
13651
|
initialCategoryField;
|
|
13237
13652
|
initialSeriesGroupField;
|
|
@@ -13335,27 +13750,14 @@ class LineChartConfigDialogComponent {
|
|
|
13335
13750
|
}
|
|
13336
13751
|
async loadQueryColumns(queryRtId) {
|
|
13337
13752
|
this.isLoadingColumns = true;
|
|
13753
|
+
// family may be undefined when the selected query metadata is missing —
|
|
13754
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
13755
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
13338
13756
|
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
|
-
}
|
|
13757
|
+
this.queryColumns = await this.fetchColumnsForFamily(family, queryRtId);
|
|
13758
|
+
const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
|
|
13759
|
+
this.numericColumns = this.queryColumns.filter(c => numericTypes.includes(c.attributeValueType));
|
|
13760
|
+
this.nonNumericColumns = this.queryColumns.filter(c => !numericTypes.includes(c.attributeValueType));
|
|
13359
13761
|
}
|
|
13360
13762
|
catch (error) {
|
|
13361
13763
|
console.error('Error loading query columns:', error);
|
|
@@ -13367,6 +13769,35 @@ class LineChartConfigDialogComponent {
|
|
|
13367
13769
|
this.isLoadingColumns = false;
|
|
13368
13770
|
}
|
|
13369
13771
|
}
|
|
13772
|
+
/**
|
|
13773
|
+
* Runtime queries use the metadata-only resolver (no aggregation executed);
|
|
13774
|
+
* stream-data queries fall back to executing the query with `first: 1`
|
|
13775
|
+
* because the SD path has no dedicated column-introspection endpoint today.
|
|
13776
|
+
*/
|
|
13777
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
13778
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
13779
|
+
if (resolvedFamily === 'runtime') {
|
|
13780
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
13781
|
+
variables: { rtId }
|
|
13782
|
+
}));
|
|
13783
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
13784
|
+
if (!queryItem)
|
|
13785
|
+
return [];
|
|
13786
|
+
return (queryItem.columns ?? [])
|
|
13787
|
+
.filter((c) => c !== null)
|
|
13788
|
+
.map(c => ({
|
|
13789
|
+
attributePath: c.attributePath ?? '',
|
|
13790
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
13791
|
+
aggregationType: c.aggregationType ?? null
|
|
13792
|
+
}));
|
|
13793
|
+
}
|
|
13794
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
13795
|
+
return sdResult.columns.map(c => ({
|
|
13796
|
+
attributePath: c.attributePath,
|
|
13797
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
13798
|
+
aggregationType: c.aggregationType ?? null
|
|
13799
|
+
}));
|
|
13800
|
+
}
|
|
13370
13801
|
onFiltersChange(updatedFilters) {
|
|
13371
13802
|
this.filters = updatedFilters;
|
|
13372
13803
|
}
|
|
@@ -13380,11 +13811,13 @@ class LineChartConfigDialogComponent {
|
|
|
13380
13811
|
comparisonValue: f.comparisonValue
|
|
13381
13812
|
}))
|
|
13382
13813
|
: undefined;
|
|
13814
|
+
const family = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
13383
13815
|
const result = {
|
|
13384
13816
|
ckTypeId: '',
|
|
13385
13817
|
rtId: '',
|
|
13386
13818
|
queryRtId: this.selectedPersistentQuery.rtId,
|
|
13387
13819
|
queryName: this.selectedPersistentQuery.name,
|
|
13820
|
+
queryFamily: family,
|
|
13388
13821
|
chartType: this.form.chartType,
|
|
13389
13822
|
categoryField: this.form.categoryField,
|
|
13390
13823
|
seriesGroupField: this.form.seriesGroupField,
|
|
@@ -13408,7 +13841,7 @@ class LineChartConfigDialogComponent {
|
|
|
13408
13841
|
this.windowRef.close();
|
|
13409
13842
|
}
|
|
13410
13843
|
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: `
|
|
13844
|
+
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
13845
|
<div class="config-container">
|
|
13413
13846
|
|
|
13414
13847
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -13611,7 +14044,7 @@ class LineChartConfigDialogComponent {
|
|
|
13611
14044
|
</button>
|
|
13612
14045
|
</div>
|
|
13613
14046
|
</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"] }] });
|
|
14047
|
+
`, 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
14048
|
}
|
|
13616
14049
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LineChartConfigDialogComponent, decorators: [{
|
|
13617
14050
|
type: Component,
|
|
@@ -13836,6 +14269,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
13836
14269
|
type: Input
|
|
13837
14270
|
}], initialQueryName: [{
|
|
13838
14271
|
type: Input
|
|
14272
|
+
}], initialQueryFamily: [{
|
|
14273
|
+
type: Input
|
|
13839
14274
|
}], initialChartType: [{
|
|
13840
14275
|
type: Input
|
|
13841
14276
|
}], initialCategoryField: [{
|
|
@@ -13876,7 +14311,13 @@ function buildGradientRanges(min, max, colors) {
|
|
|
13876
14311
|
}));
|
|
13877
14312
|
}
|
|
13878
14313
|
class HeatmapWidgetComponent {
|
|
13879
|
-
|
|
14314
|
+
queryExecutor = inject(QueryExecutorService);
|
|
14315
|
+
static SUPPORTED_ROW_TYPES = new Set([
|
|
14316
|
+
'RtSimpleQueryRow',
|
|
14317
|
+
'RtAggregationQueryRow',
|
|
14318
|
+
'RtGroupingAggregationQueryRow',
|
|
14319
|
+
'StreamDataQueryRow'
|
|
14320
|
+
]);
|
|
13880
14321
|
stateService = inject(MeshBoardStateService);
|
|
13881
14322
|
variableService = inject(MeshBoardVariableService);
|
|
13882
14323
|
config;
|
|
@@ -13996,36 +14437,18 @@ class HeatmapWidgetComponent {
|
|
|
13996
14437
|
this._error.set(null);
|
|
13997
14438
|
try {
|
|
13998
14439
|
const fieldFilter = this.convertFiltersToDto(this.config.filters);
|
|
13999
|
-
|
|
14000
|
-
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
14440
|
+
// queryFamily may be undefined for legacy widget configs — the executor
|
|
14441
|
+
// falls back to a one-time lookup by rtId. streamDataArgs is sent
|
|
14442
|
+
// unconditionally because the runtime path ignores it.
|
|
14443
|
+
const streamDataArgs = this.buildStreamDataArgs();
|
|
14444
|
+
const result = await firstValueFrom(this.queryExecutor.execute(queryDataSource.queryFamily, queryDataSource.queryRtId, {
|
|
14445
|
+
fieldFilter: fieldFilter ?? undefined,
|
|
14446
|
+
streamDataArgs
|
|
14004
14447
|
}).pipe(catchError(err => {
|
|
14005
14448
|
console.error('Error loading Heatmap data:', err);
|
|
14006
14449
|
throw err;
|
|
14007
14450
|
})));
|
|
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 ?? ''));
|
|
14451
|
+
const filteredRows = result.rows.filter(row => HeatmapWidgetComponent.SUPPORTED_ROW_TYPES.has(row.__typename ?? ''));
|
|
14029
14452
|
this.processHeatmapData(filteredRows);
|
|
14030
14453
|
this._isLoading.set(false);
|
|
14031
14454
|
}
|
|
@@ -14035,6 +14458,13 @@ class HeatmapWidgetComponent {
|
|
|
14035
14458
|
this._isLoading.set(false);
|
|
14036
14459
|
}
|
|
14037
14460
|
}
|
|
14461
|
+
buildStreamDataArgs() {
|
|
14462
|
+
const range = this.stateService.resolveCurrentTimeRange();
|
|
14463
|
+
if (!range) {
|
|
14464
|
+
return undefined;
|
|
14465
|
+
}
|
|
14466
|
+
return { from: range.from, to: range.to };
|
|
14467
|
+
}
|
|
14038
14468
|
/**
|
|
14039
14469
|
* Processes query rows into heatmap data.
|
|
14040
14470
|
* When dateEndField is configured, auto-detects the interval width and shows sub-hour columns.
|
|
@@ -14047,14 +14477,10 @@ class HeatmapWidgetComponent {
|
|
|
14047
14477
|
const aggregation = this.config.aggregation ?? 'count';
|
|
14048
14478
|
const parsedRows = [];
|
|
14049
14479
|
for (const row of filteredRows) {
|
|
14050
|
-
const queryRow = row;
|
|
14051
|
-
const cells = queryRow.cells?.items ?? [];
|
|
14052
14480
|
let dateFrom = null;
|
|
14053
14481
|
let dateTo = null;
|
|
14054
14482
|
let numericValue = 1; // default for count
|
|
14055
|
-
for (const cell of cells) {
|
|
14056
|
-
if (!cell?.attributePath)
|
|
14057
|
-
continue;
|
|
14483
|
+
for (const cell of row.cells) {
|
|
14058
14484
|
if (matchesAttributePath(cell.attributePath, dateField)) {
|
|
14059
14485
|
dateFrom = this.parseDate(cell.value);
|
|
14060
14486
|
}
|
|
@@ -14381,12 +14807,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
14381
14807
|
|
|
14382
14808
|
class HeatmapConfigDialogComponent {
|
|
14383
14809
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
14810
|
+
queryExecutor = inject(QueryExecutorService);
|
|
14384
14811
|
stateService = inject(MeshBoardStateService);
|
|
14385
14812
|
windowRef = inject(WindowRef);
|
|
14386
14813
|
querySelector;
|
|
14387
14814
|
// Initial values for editing
|
|
14388
14815
|
initialQueryRtId;
|
|
14389
14816
|
initialQueryName;
|
|
14817
|
+
initialQueryFamily;
|
|
14390
14818
|
initialDateField;
|
|
14391
14819
|
initialDateEndField;
|
|
14392
14820
|
initialValueField;
|
|
@@ -14508,36 +14936,22 @@ class HeatmapConfigDialogComponent {
|
|
|
14508
14936
|
}
|
|
14509
14937
|
async loadQueryColumns(queryRtId) {
|
|
14510
14938
|
this.isLoadingColumns = true;
|
|
14939
|
+
// family may be undefined when the selected query metadata is missing —
|
|
14940
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
14941
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
14511
14942
|
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
|
-
}
|
|
14943
|
+
this.queryColumns = await this.fetchColumnsForFamily(family, queryRtId);
|
|
14944
|
+
const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
|
|
14945
|
+
const dateTimeTypes = ['DATE_TIME', 'DATETIME', 'DATE'];
|
|
14946
|
+
this.numericColumns = this.queryColumns.filter(c => numericTypes.includes(c.attributeValueType));
|
|
14947
|
+
this.dateTimeColumns = this.queryColumns.filter(c => dateTimeTypes.includes(c.attributeValueType));
|
|
14948
|
+
// If no explicit datetime columns found, also allow string columns
|
|
14949
|
+
// (datetime values are sometimes returned as strings)
|
|
14950
|
+
if (this.dateTimeColumns.length === 0) {
|
|
14951
|
+
this.dateTimeColumns = this.queryColumns;
|
|
14952
|
+
}
|
|
14953
|
+
if (!this.initialQueryRtId && this.dateTimeColumns.length > 0) {
|
|
14954
|
+
this.form.dateField = this.dateTimeColumns[0].attributePath;
|
|
14541
14955
|
}
|
|
14542
14956
|
}
|
|
14543
14957
|
catch (error) {
|
|
@@ -14550,6 +14964,35 @@ class HeatmapConfigDialogComponent {
|
|
|
14550
14964
|
this.isLoadingColumns = false;
|
|
14551
14965
|
}
|
|
14552
14966
|
}
|
|
14967
|
+
/**
|
|
14968
|
+
* Runtime queries use the metadata-only resolver (no aggregation executed);
|
|
14969
|
+
* stream-data queries fall back to executing the query with `first: 1`
|
|
14970
|
+
* because the SD path has no dedicated column-introspection endpoint today.
|
|
14971
|
+
*/
|
|
14972
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
14973
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
14974
|
+
if (resolvedFamily === 'runtime') {
|
|
14975
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
14976
|
+
variables: { rtId }
|
|
14977
|
+
}));
|
|
14978
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
14979
|
+
if (!queryItem)
|
|
14980
|
+
return [];
|
|
14981
|
+
return (queryItem.columns ?? [])
|
|
14982
|
+
.filter((c) => c !== null)
|
|
14983
|
+
.map(c => ({
|
|
14984
|
+
attributePath: c.attributePath ?? '',
|
|
14985
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
14986
|
+
aggregationType: c.aggregationType ?? null
|
|
14987
|
+
}));
|
|
14988
|
+
}
|
|
14989
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
14990
|
+
return sdResult.columns.map(c => ({
|
|
14991
|
+
attributePath: c.attributePath,
|
|
14992
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
14993
|
+
aggregationType: c.aggregationType ?? null
|
|
14994
|
+
}));
|
|
14995
|
+
}
|
|
14553
14996
|
onFiltersChange(updatedFilters) {
|
|
14554
14997
|
this.filters = updatedFilters;
|
|
14555
14998
|
}
|
|
@@ -14563,11 +15006,13 @@ class HeatmapConfigDialogComponent {
|
|
|
14563
15006
|
comparisonValue: f.comparisonValue
|
|
14564
15007
|
}))
|
|
14565
15008
|
: undefined;
|
|
15009
|
+
const family = queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined;
|
|
14566
15010
|
const result = {
|
|
14567
15011
|
ckTypeId: '',
|
|
14568
15012
|
rtId: '',
|
|
14569
15013
|
queryRtId: this.selectedPersistentQuery.rtId,
|
|
14570
15014
|
queryName: this.selectedPersistentQuery.name,
|
|
15015
|
+
queryFamily: family,
|
|
14571
15016
|
dateField: this.form.dateField,
|
|
14572
15017
|
dateEndField: this.form.dateEndField || undefined,
|
|
14573
15018
|
valueField: this.form.valueField || undefined,
|
|
@@ -14586,7 +15031,7 @@ class HeatmapConfigDialogComponent {
|
|
|
14586
15031
|
this.windowRef.close();
|
|
14587
15032
|
}
|
|
14588
15033
|
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: `
|
|
15034
|
+
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
15035
|
<div class="config-container">
|
|
14591
15036
|
|
|
14592
15037
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -14795,7 +15240,7 @@ class HeatmapConfigDialogComponent {
|
|
|
14795
15240
|
</button>
|
|
14796
15241
|
</div>
|
|
14797
15242
|
</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"] }] });
|
|
15243
|
+
`, 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
15244
|
}
|
|
14800
15245
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HeatmapConfigDialogComponent, decorators: [{
|
|
14801
15246
|
type: Component,
|
|
@@ -15026,6 +15471,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
15026
15471
|
type: Input
|
|
15027
15472
|
}], initialQueryName: [{
|
|
15028
15473
|
type: Input
|
|
15474
|
+
}], initialQueryFamily: [{
|
|
15475
|
+
type: Input
|
|
15029
15476
|
}], initialDateField: [{
|
|
15030
15477
|
type: Input
|
|
15031
15478
|
}], initialDateEndField: [{
|
|
@@ -16603,6 +17050,7 @@ class WidgetGroupConfigDialogComponent {
|
|
|
16603
17050
|
ckTypeSelectorService = inject(CkTypeSelectorService);
|
|
16604
17051
|
attributeSelectorService = inject(AttributeSelectorService);
|
|
16605
17052
|
getRuntimeQueryColumnsGQL = inject(GetRuntimeQueryColumnsDtoGQL);
|
|
17053
|
+
queryExecutor = inject(QueryExecutorService);
|
|
16606
17054
|
getCkTypeAvailableQueryColumnsGQL = inject(GetCkTypeAvailableQueryColumnsDtoGQL);
|
|
16607
17055
|
meshBoardStateService = inject(MeshBoardStateService);
|
|
16608
17056
|
windowRef = inject(WindowRef);
|
|
@@ -16611,6 +17059,7 @@ class WidgetGroupConfigDialogComponent {
|
|
|
16611
17059
|
initialDataSourceMode;
|
|
16612
17060
|
initialQueryRtId;
|
|
16613
17061
|
initialQueryName;
|
|
17062
|
+
initialQueryFamily;
|
|
16614
17063
|
initialCkTypeId;
|
|
16615
17064
|
initialFilters;
|
|
16616
17065
|
initialMaxItems;
|
|
@@ -16767,26 +17216,12 @@ class WidgetGroupConfigDialogComponent {
|
|
|
16767
17216
|
}
|
|
16768
17217
|
async loadQueryColumns(queryRtId) {
|
|
16769
17218
|
this.isLoadingColumns = true;
|
|
17219
|
+
// family may be undefined when the selected query metadata is missing —
|
|
17220
|
+
// fetchColumnsForFamily resolves it via the executor's one-time lookup.
|
|
17221
|
+
const family = queryFamily(this.selectedPersistentQuery?.ckTypeId) ?? this.initialQueryFamily;
|
|
16770
17222
|
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
|
-
}
|
|
17223
|
+
this.availableColumns = await this.fetchColumnsForFamily(family, queryRtId);
|
|
17224
|
+
this.filteredColumns.set(this.availableColumns);
|
|
16790
17225
|
}
|
|
16791
17226
|
catch (error) {
|
|
16792
17227
|
console.error('Error loading query columns:', error);
|
|
@@ -16797,6 +17232,35 @@ class WidgetGroupConfigDialogComponent {
|
|
|
16797
17232
|
this.isLoadingColumns = false;
|
|
16798
17233
|
}
|
|
16799
17234
|
}
|
|
17235
|
+
/**
|
|
17236
|
+
* Runtime queries use the metadata-only resolver (no aggregation executed);
|
|
17237
|
+
* stream-data queries fall back to executing the query with `first: 1`
|
|
17238
|
+
* because the SD path has no dedicated column-introspection endpoint today.
|
|
17239
|
+
*/
|
|
17240
|
+
async fetchColumnsForFamily(family, rtId) {
|
|
17241
|
+
const resolvedFamily = family ?? await this.queryExecutor.resolveFamily(rtId);
|
|
17242
|
+
if (resolvedFamily === 'runtime') {
|
|
17243
|
+
const result = await firstValueFrom(this.getRuntimeQueryColumnsGQL.fetch({
|
|
17244
|
+
variables: { rtId }
|
|
17245
|
+
}));
|
|
17246
|
+
const queryItem = result.data?.runtime?.runtimeQuery?.items?.[0];
|
|
17247
|
+
if (!queryItem)
|
|
17248
|
+
return [];
|
|
17249
|
+
return (queryItem.columns ?? [])
|
|
17250
|
+
.filter((c) => c !== null)
|
|
17251
|
+
.map(c => ({
|
|
17252
|
+
attributePath: c.attributePath ?? '',
|
|
17253
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
17254
|
+
aggregationType: c.aggregationType ?? null
|
|
17255
|
+
}));
|
|
17256
|
+
}
|
|
17257
|
+
const sdResult = await firstValueFrom(this.queryExecutor.executeStreamData(rtId, { first: 1 }));
|
|
17258
|
+
return sdResult.columns.map(c => ({
|
|
17259
|
+
attributePath: c.attributePath,
|
|
17260
|
+
attributeValueType: c.attributeValueType ?? '',
|
|
17261
|
+
aggregationType: c.aggregationType ?? null
|
|
17262
|
+
}));
|
|
17263
|
+
}
|
|
16800
17264
|
// ============================================================================
|
|
16801
17265
|
// CK Type Methods
|
|
16802
17266
|
// ============================================================================
|
|
@@ -16889,11 +17353,15 @@ class WidgetGroupConfigDialogComponent {
|
|
|
16889
17353
|
comparisonValue: f.comparisonValue
|
|
16890
17354
|
}))
|
|
16891
17355
|
: undefined;
|
|
17356
|
+
const family = this.dataSourceMode === 'persistentQuery' && this.selectedPersistentQuery
|
|
17357
|
+
? queryFamily(this.selectedPersistentQuery.ckTypeId) ?? this.initialQueryFamily ?? undefined
|
|
17358
|
+
: undefined;
|
|
16892
17359
|
const result = {
|
|
16893
17360
|
ckTypeId: this.dataSourceMode === 'ckType' ? (this.selectedCkType?.rtCkTypeId ?? '') : '',
|
|
16894
17361
|
dataSourceMode: this.dataSourceMode,
|
|
16895
17362
|
queryRtId: this.dataSourceMode === 'persistentQuery' ? this.selectedPersistentQuery?.rtId : undefined,
|
|
16896
17363
|
queryName: this.dataSourceMode === 'persistentQuery' ? this.selectedPersistentQuery?.name : undefined,
|
|
17364
|
+
queryFamily: family,
|
|
16897
17365
|
filters: this.dataSourceMode === 'ckType' ? filtersDto : undefined,
|
|
16898
17366
|
maxItems: this.form.maxItems,
|
|
16899
17367
|
childTemplate,
|
|
@@ -16931,7 +17399,7 @@ class WidgetGroupConfigDialogComponent {
|
|
|
16931
17399
|
this.windowRef.close();
|
|
16932
17400
|
}
|
|
16933
17401
|
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: `
|
|
17402
|
+
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
17403
|
<div class="config-container">
|
|
16936
17404
|
|
|
16937
17405
|
<div class="config-form" [class.loading]="isLoadingInitial">
|
|
@@ -17251,7 +17719,7 @@ class WidgetGroupConfigDialogComponent {
|
|
|
17251
17719
|
</button>
|
|
17252
17720
|
</div>
|
|
17253
17721
|
</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"] }] });
|
|
17722
|
+
`, 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
17723
|
}
|
|
17256
17724
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: WidgetGroupConfigDialogComponent, decorators: [{
|
|
17257
17725
|
type: Component,
|
|
@@ -17595,6 +18063,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
17595
18063
|
type: Input
|
|
17596
18064
|
}], initialQueryName: [{
|
|
17597
18065
|
type: Input
|
|
18066
|
+
}], initialQueryFamily: [{
|
|
18067
|
+
type: Input
|
|
17598
18068
|
}], initialCkTypeId: [{
|
|
17599
18069
|
type: Input
|
|
17600
18070
|
}], initialFilters: [{
|
|
@@ -24996,11 +25466,16 @@ function parseConfig(data) {
|
|
|
24996
25466
|
*/
|
|
24997
25467
|
function buildDataSourceFromPersisted(data, config) {
|
|
24998
25468
|
if (data.dataSourceType === 'systemQuery' || data.dataSourceType === 'persistentQuery') {
|
|
24999
|
-
|
|
25469
|
+
const persisted = {
|
|
25000
25470
|
type: 'persistentQuery',
|
|
25001
25471
|
queryRtId: data.dataSourceRtId ?? config['queryRtId'] ?? '',
|
|
25002
25472
|
queryName: config['queryName']
|
|
25003
25473
|
};
|
|
25474
|
+
const persistedFamily = config['queryFamily'];
|
|
25475
|
+
if (persistedFamily === 'runtime' || persistedFamily === 'streamData') {
|
|
25476
|
+
persisted.queryFamily = persistedFamily;
|
|
25477
|
+
}
|
|
25478
|
+
return persisted;
|
|
25004
25479
|
}
|
|
25005
25480
|
if (data.dataSourceType === 'static') {
|
|
25006
25481
|
return { type: 'static' };
|
|
@@ -25120,6 +25595,7 @@ function registerDefaultWidgets(registry) {
|
|
|
25120
25595
|
initialDataSourceType: dataSourceType,
|
|
25121
25596
|
initialQueryRtId: isPersistentQuery ? kpiWidget.dataSource.queryRtId : undefined,
|
|
25122
25597
|
initialQueryName: isPersistentQuery ? kpiWidget.dataSource.queryName : undefined,
|
|
25598
|
+
initialQueryFamily: isPersistentQuery ? kpiWidget.dataSource.queryFamily : undefined,
|
|
25123
25599
|
initialQueryMode: kpiWidget.queryMode,
|
|
25124
25600
|
initialQueryValueField: kpiWidget.queryValueField,
|
|
25125
25601
|
initialQueryCategoryField: kpiWidget.queryCategoryField,
|
|
@@ -25164,7 +25640,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25164
25640
|
const dataSource = {
|
|
25165
25641
|
type: 'persistentQuery',
|
|
25166
25642
|
queryRtId: result.queryRtId,
|
|
25167
|
-
queryName: result.queryName
|
|
25643
|
+
queryName: result.queryName,
|
|
25644
|
+
queryFamily: result.queryFamily
|
|
25168
25645
|
};
|
|
25169
25646
|
return {
|
|
25170
25647
|
...widget,
|
|
@@ -25235,7 +25712,10 @@ function registerDefaultWidgets(registry) {
|
|
|
25235
25712
|
filters: widget.filters,
|
|
25236
25713
|
...(isPersistentQuery && {
|
|
25237
25714
|
queryName: widget.dataSource.queryName,
|
|
25238
|
-
queryRtId: widget.dataSource.queryRtId
|
|
25715
|
+
queryRtId: widget.dataSource.queryRtId,
|
|
25716
|
+
...(widget.dataSource.queryFamily && {
|
|
25717
|
+
queryFamily: widget.dataSource.queryFamily
|
|
25718
|
+
})
|
|
25239
25719
|
})
|
|
25240
25720
|
}
|
|
25241
25721
|
};
|
|
@@ -25399,7 +25879,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25399
25879
|
initialPageSize: tableWidget.pageSize,
|
|
25400
25880
|
initialSortable: tableWidget.sortable,
|
|
25401
25881
|
initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
|
|
25402
|
-
initialQueryName: isPersistentQuery ? dataSource.queryName : undefined
|
|
25882
|
+
initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
|
|
25883
|
+
initialQueryFamily: isPersistentQuery ? dataSource.queryFamily : undefined
|
|
25403
25884
|
};
|
|
25404
25885
|
},
|
|
25405
25886
|
applyConfigResult: (widget, result) => {
|
|
@@ -25408,7 +25889,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25408
25889
|
const dataSource = {
|
|
25409
25890
|
type: 'persistentQuery',
|
|
25410
25891
|
queryRtId: result.queryRtId,
|
|
25411
|
-
queryName: result.queryName
|
|
25892
|
+
queryName: result.queryName,
|
|
25893
|
+
queryFamily: result.queryFamily
|
|
25412
25894
|
};
|
|
25413
25895
|
// Convert filters from DTO to widget format
|
|
25414
25896
|
const filters = result.filters?.map(f => ({
|
|
@@ -25462,11 +25944,12 @@ function registerDefaultWidgets(registry) {
|
|
|
25462
25944
|
// SOLID: Serialization for persistence
|
|
25463
25945
|
toPersistedConfig: (widget) => {
|
|
25464
25946
|
const isPersistentQuery = widget.dataSource.type === 'persistentQuery';
|
|
25947
|
+
const queryDataSource = isPersistentQuery ? widget.dataSource : null;
|
|
25465
25948
|
return {
|
|
25466
25949
|
dataSourceType: isPersistentQuery ? 'persistentQuery' : 'runtimeEntity',
|
|
25467
25950
|
dataSourceCkTypeId: widget.dataSource.type === 'runtimeEntity' ? widget.dataSource.ckTypeId : undefined,
|
|
25468
25951
|
dataSourceRtId: isPersistentQuery
|
|
25469
|
-
?
|
|
25952
|
+
? queryDataSource.queryRtId
|
|
25470
25953
|
: (widget.dataSource.type === 'runtimeEntity' ? widget.dataSource.rtId : undefined),
|
|
25471
25954
|
config: {
|
|
25472
25955
|
columns: widget.columns,
|
|
@@ -25474,9 +25957,10 @@ function registerDefaultWidgets(registry) {
|
|
|
25474
25957
|
filters: widget.filters,
|
|
25475
25958
|
pageSize: widget.pageSize,
|
|
25476
25959
|
sortable: widget.sortable,
|
|
25477
|
-
...(
|
|
25478
|
-
queryName:
|
|
25479
|
-
queryRtId:
|
|
25960
|
+
...(queryDataSource && {
|
|
25961
|
+
queryName: queryDataSource.queryName,
|
|
25962
|
+
queryRtId: queryDataSource.queryRtId,
|
|
25963
|
+
...(queryDataSource.queryFamily && { queryFamily: queryDataSource.queryFamily })
|
|
25480
25964
|
})
|
|
25481
25965
|
}
|
|
25482
25966
|
};
|
|
@@ -25521,6 +26005,7 @@ function registerDefaultWidgets(registry) {
|
|
|
25521
26005
|
initialDataSourceType: isPersistentQuery ? 'persistentQuery' : 'runtimeEntity',
|
|
25522
26006
|
initialQueryRtId: isPersistentQuery ? gaugeWidget.dataSource.queryRtId : undefined,
|
|
25523
26007
|
initialQueryName: isPersistentQuery ? gaugeWidget.dataSource.queryName : undefined,
|
|
26008
|
+
initialQueryFamily: isPersistentQuery ? gaugeWidget.dataSource.queryFamily : undefined,
|
|
25524
26009
|
initialQueryMode: gaugeWidget.queryMode,
|
|
25525
26010
|
initialQueryValueField: gaugeWidget.queryValueField,
|
|
25526
26011
|
initialQueryCategoryField: gaugeWidget.queryCategoryField,
|
|
@@ -25550,7 +26035,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25550
26035
|
const dataSource = {
|
|
25551
26036
|
type: 'persistentQuery',
|
|
25552
26037
|
queryRtId: result.queryRtId,
|
|
25553
|
-
queryName: result.queryName
|
|
26038
|
+
queryName: result.queryName,
|
|
26039
|
+
queryFamily: result.queryFamily
|
|
25554
26040
|
};
|
|
25555
26041
|
return {
|
|
25556
26042
|
...widget,
|
|
@@ -25632,7 +26118,10 @@ function registerDefaultWidgets(registry) {
|
|
|
25632
26118
|
filters: widget.filters,
|
|
25633
26119
|
...(isPersistentQuery && {
|
|
25634
26120
|
queryName: widget.dataSource.queryName,
|
|
25635
|
-
queryRtId: widget.dataSource.queryRtId
|
|
26121
|
+
queryRtId: widget.dataSource.queryRtId,
|
|
26122
|
+
...(widget.dataSource.queryFamily && {
|
|
26123
|
+
queryFamily: widget.dataSource.queryFamily
|
|
26124
|
+
})
|
|
25636
26125
|
})
|
|
25637
26126
|
}
|
|
25638
26127
|
};
|
|
@@ -25701,6 +26190,7 @@ function registerDefaultWidgets(registry) {
|
|
|
25701
26190
|
initialDataSourceType: dataSource.type,
|
|
25702
26191
|
initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
|
|
25703
26192
|
initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
|
|
26193
|
+
initialQueryFamily: isPersistentQuery ? dataSource.queryFamily : undefined,
|
|
25704
26194
|
initialCkQueryTarget: isCkQuery ? dataSource.queryTarget : undefined,
|
|
25705
26195
|
initialCkGroupBy: isCkQuery ? dataSource.groupBy : undefined,
|
|
25706
26196
|
initialChartType: pieWidget.chartType,
|
|
@@ -25733,7 +26223,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25733
26223
|
dataSource = {
|
|
25734
26224
|
type: 'persistentQuery',
|
|
25735
26225
|
queryRtId: result.queryRtId ?? '',
|
|
25736
|
-
queryName: result.queryName
|
|
26226
|
+
queryName: result.queryName,
|
|
26227
|
+
queryFamily: result.queryFamily
|
|
25737
26228
|
};
|
|
25738
26229
|
}
|
|
25739
26230
|
return {
|
|
@@ -25795,6 +26286,9 @@ function registerDefaultWidgets(registry) {
|
|
|
25795
26286
|
legendPosition: widget.legendPosition,
|
|
25796
26287
|
queryName: dataSource.queryName,
|
|
25797
26288
|
queryRtId: dataSource.queryRtId,
|
|
26289
|
+
...(dataSource.queryFamily && {
|
|
26290
|
+
queryFamily: dataSource.queryFamily
|
|
26291
|
+
}),
|
|
25798
26292
|
filters: widget.filters
|
|
25799
26293
|
}
|
|
25800
26294
|
};
|
|
@@ -25856,6 +26350,7 @@ function registerDefaultWidgets(registry) {
|
|
|
25856
26350
|
return {
|
|
25857
26351
|
initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
|
|
25858
26352
|
initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
|
|
26353
|
+
initialQueryFamily: isPersistentQuery ? dataSource.queryFamily : undefined,
|
|
25859
26354
|
initialChartType: barWidget.chartType,
|
|
25860
26355
|
initialCategoryField: barWidget.categoryField,
|
|
25861
26356
|
initialSeries: barWidget.series,
|
|
@@ -25873,7 +26368,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25873
26368
|
const dataSource = {
|
|
25874
26369
|
type: 'persistentQuery',
|
|
25875
26370
|
queryRtId: result.queryRtId,
|
|
25876
|
-
queryName: result.queryName
|
|
26371
|
+
queryName: result.queryName,
|
|
26372
|
+
queryFamily: result.queryFamily
|
|
25877
26373
|
};
|
|
25878
26374
|
// Convert filters from DTO to widget format
|
|
25879
26375
|
const filters = result.filters?.map(f => ({
|
|
@@ -25929,6 +26425,9 @@ function registerDefaultWidgets(registry) {
|
|
|
25929
26425
|
defaultBarColor: widget.defaultBarColor,
|
|
25930
26426
|
queryName: widget.dataSource.queryName,
|
|
25931
26427
|
queryRtId: widget.dataSource.queryRtId,
|
|
26428
|
+
...(widget.dataSource.queryFamily && {
|
|
26429
|
+
queryFamily: widget.dataSource.queryFamily
|
|
26430
|
+
}),
|
|
25932
26431
|
filters: widget.filters
|
|
25933
26432
|
}
|
|
25934
26433
|
}),
|
|
@@ -25973,6 +26472,7 @@ function registerDefaultWidgets(registry) {
|
|
|
25973
26472
|
return {
|
|
25974
26473
|
initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
|
|
25975
26474
|
initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
|
|
26475
|
+
initialQueryFamily: isPersistentQuery ? dataSource.queryFamily : undefined,
|
|
25976
26476
|
initialChartType: lineWidget.chartType,
|
|
25977
26477
|
initialCategoryField: lineWidget.categoryField,
|
|
25978
26478
|
initialSeriesGroupField: lineWidget.seriesGroupField,
|
|
@@ -25989,7 +26489,8 @@ function registerDefaultWidgets(registry) {
|
|
|
25989
26489
|
const dataSource = {
|
|
25990
26490
|
type: 'persistentQuery',
|
|
25991
26491
|
queryRtId: result.queryRtId,
|
|
25992
|
-
queryName: result.queryName
|
|
26492
|
+
queryName: result.queryName,
|
|
26493
|
+
queryFamily: result.queryFamily
|
|
25993
26494
|
};
|
|
25994
26495
|
const filters = result.filters?.map(f => ({
|
|
25995
26496
|
attributePath: f.attributePath,
|
|
@@ -26040,6 +26541,9 @@ function registerDefaultWidgets(registry) {
|
|
|
26040
26541
|
referenceLines: widget.referenceLines,
|
|
26041
26542
|
queryName: widget.dataSource.queryName,
|
|
26042
26543
|
queryRtId: widget.dataSource.queryRtId,
|
|
26544
|
+
...(widget.dataSource.queryFamily && {
|
|
26545
|
+
queryFamily: widget.dataSource.queryFamily
|
|
26546
|
+
}),
|
|
26043
26547
|
filters: widget.filters
|
|
26044
26548
|
}
|
|
26045
26549
|
}),
|
|
@@ -26316,6 +26820,7 @@ function registerDefaultWidgets(registry) {
|
|
|
26316
26820
|
initialDataSourceMode: hasQuery ? 'persistentQuery' : (hasCkType ? 'ckType' : 'persistentQuery'),
|
|
26317
26821
|
initialQueryRtId: hasQuery ? dataSource.queryRtId : undefined,
|
|
26318
26822
|
initialQueryName: hasQuery ? dataSource.queryName : undefined,
|
|
26823
|
+
initialQueryFamily: hasQuery ? dataSource.queryFamily : undefined,
|
|
26319
26824
|
initialCkTypeId: hasCkType ? dataSource.ckTypeId : undefined,
|
|
26320
26825
|
initialFilters: hasCkType ? dataSource.filters : undefined,
|
|
26321
26826
|
initialMaxItems: dataSource.type === 'repeaterQuery' ? dataSource.maxItems : undefined,
|
|
@@ -26334,6 +26839,7 @@ function registerDefaultWidgets(registry) {
|
|
|
26334
26839
|
type: 'repeaterQuery',
|
|
26335
26840
|
queryRtId: result.dataSourceMode === 'persistentQuery' ? result.queryRtId : undefined,
|
|
26336
26841
|
queryName: result.dataSourceMode === 'persistentQuery' ? result.queryName : undefined,
|
|
26842
|
+
queryFamily: result.dataSourceMode === 'persistentQuery' ? result.queryFamily : undefined,
|
|
26337
26843
|
ckTypeId: useCkType ? result.ckTypeId : undefined,
|
|
26338
26844
|
filters: useCkType && result.filters ? result.filters.map(f => ({
|
|
26339
26845
|
attributePath: f.attributePath,
|
|
@@ -26384,6 +26890,7 @@ function registerDefaultWidgets(registry) {
|
|
|
26384
26890
|
dataSourceCkTypeId: !hasQuery ? dataSource.ckTypeId : undefined,
|
|
26385
26891
|
config: {
|
|
26386
26892
|
queryName: dataSource.queryName,
|
|
26893
|
+
...(dataSource.queryFamily && { queryFamily: dataSource.queryFamily }),
|
|
26387
26894
|
maxItems: dataSource.maxItems,
|
|
26388
26895
|
filters: dataSource.filters,
|
|
26389
26896
|
childTemplate: widget.childTemplate,
|
|
@@ -26399,10 +26906,14 @@ function registerDefaultWidgets(registry) {
|
|
|
26399
26906
|
fromPersistedConfig: (data, base) => {
|
|
26400
26907
|
const config = parseConfig(data);
|
|
26401
26908
|
const hasQuery = !!data.dataSourceRtId;
|
|
26909
|
+
const persistedFamily = config['queryFamily'];
|
|
26402
26910
|
const dataSource = {
|
|
26403
26911
|
type: 'repeaterQuery',
|
|
26404
26912
|
queryRtId: hasQuery ? data.dataSourceRtId ?? undefined : undefined,
|
|
26405
26913
|
queryName: hasQuery ? config['queryName'] : undefined,
|
|
26914
|
+
queryFamily: hasQuery && (persistedFamily === 'runtime' || persistedFamily === 'streamData')
|
|
26915
|
+
? persistedFamily
|
|
26916
|
+
: undefined,
|
|
26406
26917
|
ckTypeId: !hasQuery ? data.dataSourceCkTypeId ?? undefined : undefined,
|
|
26407
26918
|
filters: !hasQuery ? config['filters'] : undefined,
|
|
26408
26919
|
maxItems: config['maxItems']
|
|
@@ -26505,6 +27016,7 @@ function registerDefaultWidgets(registry) {
|
|
|
26505
27016
|
return {
|
|
26506
27017
|
initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
|
|
26507
27018
|
initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
|
|
27019
|
+
initialQueryFamily: isPersistentQuery ? dataSource.queryFamily : undefined,
|
|
26508
27020
|
initialDateField: heatmapWidget.dateField,
|
|
26509
27021
|
initialDateEndField: heatmapWidget.dateEndField,
|
|
26510
27022
|
initialValueField: heatmapWidget.valueField,
|
|
@@ -26522,7 +27034,8 @@ function registerDefaultWidgets(registry) {
|
|
|
26522
27034
|
const dataSource = {
|
|
26523
27035
|
type: 'persistentQuery',
|
|
26524
27036
|
queryRtId: result.queryRtId,
|
|
26525
|
-
queryName: result.queryName
|
|
27037
|
+
queryName: result.queryName,
|
|
27038
|
+
queryFamily: result.queryFamily
|
|
26526
27039
|
};
|
|
26527
27040
|
const filters = result.filters?.map(f => ({
|
|
26528
27041
|
attributePath: f.attributePath,
|
|
@@ -26575,6 +27088,9 @@ function registerDefaultWidgets(registry) {
|
|
|
26575
27088
|
valueMultiplier: widget.valueMultiplier,
|
|
26576
27089
|
queryName: widget.dataSource.queryName,
|
|
26577
27090
|
queryRtId: widget.dataSource.queryRtId,
|
|
27091
|
+
...(widget.dataSource.queryFamily && {
|
|
27092
|
+
queryFamily: widget.dataSource.queryFamily
|
|
27093
|
+
}),
|
|
26578
27094
|
filters: widget.filters
|
|
26579
27095
|
}
|
|
26580
27096
|
}),
|
|
@@ -27534,7 +28050,8 @@ class MeshBoardSettingsResult {
|
|
|
27534
28050
|
timeFilter;
|
|
27535
28051
|
rtWellKnownName;
|
|
27536
28052
|
entitySelectors;
|
|
27537
|
-
|
|
28053
|
+
autoRefreshSeconds;
|
|
28054
|
+
constructor(name, description, columns, rowHeight, gap, variables, timeFilter, rtWellKnownName, entitySelectors, autoRefreshSeconds) {
|
|
27538
28055
|
this.name = name;
|
|
27539
28056
|
this.description = description;
|
|
27540
28057
|
this.columns = columns;
|
|
@@ -27544,6 +28061,7 @@ class MeshBoardSettingsResult {
|
|
|
27544
28061
|
this.timeFilter = timeFilter;
|
|
27545
28062
|
this.rtWellKnownName = rtWellKnownName;
|
|
27546
28063
|
this.entitySelectors = entitySelectors;
|
|
28064
|
+
this.autoRefreshSeconds = autoRefreshSeconds;
|
|
27547
28065
|
}
|
|
27548
28066
|
}
|
|
27549
28067
|
/**
|
|
@@ -27559,6 +28077,12 @@ class MeshBoardSettingsDialogComponent {
|
|
|
27559
28077
|
columns = 6;
|
|
27560
28078
|
rowHeight = 200;
|
|
27561
28079
|
gap = 16;
|
|
28080
|
+
/**
|
|
28081
|
+
* Auto-refresh interval in seconds. `0` disables auto-refresh and is the default.
|
|
28082
|
+
* When > 0 the MeshBoard view re-polls all widgets at this interval while the
|
|
28083
|
+
* tab is visible.
|
|
28084
|
+
*/
|
|
28085
|
+
autoRefreshSeconds = 0;
|
|
27562
28086
|
variables = [];
|
|
27563
28087
|
entitySelectors = [];
|
|
27564
28088
|
entitySelectorEditing = false;
|
|
@@ -27576,7 +28100,8 @@ class MeshBoardSettingsDialogComponent {
|
|
|
27576
28100
|
return (this.name.trim().length > 0 &&
|
|
27577
28101
|
this.columns >= 1 && this.columns <= 12 &&
|
|
27578
28102
|
this.rowHeight >= 100 && this.rowHeight <= 1000 &&
|
|
27579
|
-
this.gap >= 0 && this.gap <= 100
|
|
28103
|
+
this.gap >= 0 && this.gap <= 100 &&
|
|
28104
|
+
this.autoRefreshSeconds >= 0 && this.autoRefreshSeconds <= 3600);
|
|
27580
28105
|
}
|
|
27581
28106
|
/**
|
|
27582
28107
|
* Sets the initial values for the form fields.
|
|
@@ -27588,6 +28113,7 @@ class MeshBoardSettingsDialogComponent {
|
|
|
27588
28113
|
this.columns = settings.columns;
|
|
27589
28114
|
this.rowHeight = settings.rowHeight;
|
|
27590
28115
|
this.gap = settings.gap;
|
|
28116
|
+
this.autoRefreshSeconds = settings.autoRefreshSeconds ?? 0;
|
|
27591
28117
|
this.variables = settings.variables ? [...settings.variables] : [];
|
|
27592
28118
|
this.entitySelectors = settings.entitySelectors ? settings.entitySelectors.map(es => ({ ...es })) : [];
|
|
27593
28119
|
this.timeFilterEnabled = settings.timeFilter?.enabled ?? false;
|
|
@@ -27629,7 +28155,7 @@ class MeshBoardSettingsDialogComponent {
|
|
|
27629
28155
|
enabled: this.timeFilterEnabled,
|
|
27630
28156
|
defaultSelection: this.timeFilterEnabled ? this.defaultSelection : undefined
|
|
27631
28157
|
};
|
|
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);
|
|
28158
|
+
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
28159
|
this.windowRef.close(result);
|
|
27634
28160
|
}
|
|
27635
28161
|
/**
|
|
@@ -27639,7 +28165,7 @@ class MeshBoardSettingsDialogComponent {
|
|
|
27639
28165
|
this.windowRef.close();
|
|
27640
28166
|
}
|
|
27641
28167
|
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"] }] });
|
|
28168
|
+
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
28169
|
}
|
|
27644
28170
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MeshBoardSettingsDialogComponent, decorators: [{
|
|
27645
28171
|
type: Component,
|
|
@@ -27655,7 +28181,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
27655
28181
|
VariablesEditorComponent,
|
|
27656
28182
|
EntitySelectorEditorComponent,
|
|
27657
28183
|
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"] }]
|
|
28184
|
+
], 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
28185
|
}] });
|
|
27660
28186
|
|
|
27661
28187
|
/**
|
|
@@ -28520,6 +29046,10 @@ class MeshBoardViewComponent {
|
|
|
28520
29046
|
// Config dialog state
|
|
28521
29047
|
configDialogSubscription = null;
|
|
28522
29048
|
navigationSubscription = null;
|
|
29049
|
+
// Auto-refresh polling
|
|
29050
|
+
autoRefreshTimerId = null;
|
|
29051
|
+
autoRefreshActiveSeconds = 0;
|
|
29052
|
+
visibilityListener = () => this.evaluateAutoRefresh();
|
|
28523
29053
|
// State signals
|
|
28524
29054
|
config = this.stateService.meshBoardConfig;
|
|
28525
29055
|
isEditMode = this.editModeService.isEditMode;
|
|
@@ -28601,6 +29131,16 @@ class MeshBoardViewComponent {
|
|
|
28601
29131
|
}
|
|
28602
29132
|
}
|
|
28603
29133
|
});
|
|
29134
|
+
// Effect to re-evaluate the auto-refresh timer whenever the MeshBoard
|
|
29135
|
+
// config changes (e.g. user edits autoRefreshSeconds in settings, or a
|
|
29136
|
+
// different board is loaded). Re-reads on the same interval skip the
|
|
29137
|
+
// restart inside evaluateAutoRefresh().
|
|
29138
|
+
effect(() => {
|
|
29139
|
+
// Touch the signals we depend on so Angular re-fires the effect.
|
|
29140
|
+
const seconds = this.config().autoRefreshSeconds ?? 0;
|
|
29141
|
+
void seconds;
|
|
29142
|
+
this.evaluateAutoRefresh();
|
|
29143
|
+
});
|
|
28604
29144
|
// Update breadcrumb after each navigation (BreadCrumbService recreates items on NavigationEnd)
|
|
28605
29145
|
if (this.breadCrumbService) {
|
|
28606
29146
|
this.navigationSubscription = this.router.events
|
|
@@ -28687,6 +29227,13 @@ class MeshBoardViewComponent {
|
|
|
28687
29227
|
this.initialLoadComplete = true;
|
|
28688
29228
|
// Update breadcrumb with MeshBoard name
|
|
28689
29229
|
this.updateBreadcrumb();
|
|
29230
|
+
// Wire visibility-aware auto-refresh. The config-watching effect already
|
|
29231
|
+
// re-evaluates on signal changes; this listener handles the tab going
|
|
29232
|
+
// background → foreground without a config change.
|
|
29233
|
+
if (typeof document !== 'undefined') {
|
|
29234
|
+
document.addEventListener('visibilitychange', this.visibilityListener);
|
|
29235
|
+
}
|
|
29236
|
+
this.evaluateAutoRefresh();
|
|
28690
29237
|
this._isInitialized.set(true);
|
|
28691
29238
|
}
|
|
28692
29239
|
catch (err) {
|
|
@@ -28717,6 +29264,41 @@ class MeshBoardViewComponent {
|
|
|
28717
29264
|
ngOnDestroy() {
|
|
28718
29265
|
this.closeConfigDialog();
|
|
28719
29266
|
this.navigationSubscription?.unsubscribe();
|
|
29267
|
+
this.stopAutoRefresh();
|
|
29268
|
+
if (typeof document !== 'undefined') {
|
|
29269
|
+
document.removeEventListener('visibilitychange', this.visibilityListener);
|
|
29270
|
+
}
|
|
29271
|
+
}
|
|
29272
|
+
/**
|
|
29273
|
+
* Starts, restarts, or stops the auto-refresh timer based on the MeshBoard
|
|
29274
|
+
* config and document visibility. Called on init, whenever config changes,
|
|
29275
|
+
* and whenever the tab visibility changes.
|
|
29276
|
+
*
|
|
29277
|
+
* Pause-on-hidden saves bandwidth and avoids Apollo cache thrashing when
|
|
29278
|
+
* the user has the tab in the background.
|
|
29279
|
+
*/
|
|
29280
|
+
evaluateAutoRefresh() {
|
|
29281
|
+
const seconds = this.config().autoRefreshSeconds ?? 0;
|
|
29282
|
+
const tabVisible = typeof document === 'undefined' || !document.hidden;
|
|
29283
|
+
if (seconds <= 0 || !tabVisible) {
|
|
29284
|
+
this.stopAutoRefresh();
|
|
29285
|
+
return;
|
|
29286
|
+
}
|
|
29287
|
+
if (this.autoRefreshTimerId !== null && this.autoRefreshActiveSeconds === seconds) {
|
|
29288
|
+
return; // already running with the same interval
|
|
29289
|
+
}
|
|
29290
|
+
this.stopAutoRefresh();
|
|
29291
|
+
this.autoRefreshActiveSeconds = seconds;
|
|
29292
|
+
this.autoRefreshTimerId = setInterval(() => {
|
|
29293
|
+
void this.refresh();
|
|
29294
|
+
}, seconds * 1000);
|
|
29295
|
+
}
|
|
29296
|
+
stopAutoRefresh() {
|
|
29297
|
+
if (this.autoRefreshTimerId !== null) {
|
|
29298
|
+
clearInterval(this.autoRefreshTimerId);
|
|
29299
|
+
this.autoRefreshTimerId = null;
|
|
29300
|
+
}
|
|
29301
|
+
this.autoRefreshActiveSeconds = 0;
|
|
28720
29302
|
}
|
|
28721
29303
|
/**
|
|
28722
29304
|
* Preloads data for all widgets to improve initial rendering performance.
|
|
@@ -29633,5 +30215,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
29633
30215
|
* Generated bundle index. Do not edit.
|
|
29634
30216
|
*/
|
|
29635
30217
|
|
|
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 };
|
|
30218
|
+
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
30219
|
//# sourceMappingURL=meshmakers-octo-meshboard.mjs.map
|