basesite-shared-grid-lib 21.0.0-beta.4 → 21.0.0-beta.6
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.
- package/basesite-shared-grid-lib-21.0.0-beta.6.tgz +0 -0
- package/fesm2022/basesite-shared-grid-lib.mjs +166 -20
- package/fesm2022/basesite-shared-grid-lib.mjs.map +1 -1
- package/package.json +1 -1
- package/types/basesite-shared-grid-lib.d.ts +1 -0
- package/basesite-shared-grid-lib-21.0.0-beta.4.tgz +0 -0
|
Binary file
|
|
@@ -239,7 +239,7 @@ class OdataProvider {
|
|
|
239
239
|
query = query + '&' + customFilters.join('and');
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
-
query = query.replace('and$filter=', '
|
|
242
|
+
query = query.replace('and$filter=', '&$filter=');
|
|
243
243
|
return query;
|
|
244
244
|
};
|
|
245
245
|
/**
|
|
@@ -317,7 +317,15 @@ class OdataProvider {
|
|
|
317
317
|
if (!col.values || col.values.length === 0) {
|
|
318
318
|
return '';
|
|
319
319
|
}
|
|
320
|
-
|
|
320
|
+
// Fill-cell / set-filter columns are always equality-matched against
|
|
321
|
+
// a fixed list of backend-provided values, so the casing on both
|
|
322
|
+
// sides is already known to match. Skip the tolower() wrap that the
|
|
323
|
+
// generic string operators apply — wrapping the column in tolower()
|
|
324
|
+
// forces a full scan because LOWER(col) cannot use any index, and
|
|
325
|
+
// SQL Server's default collation is already case-insensitive.
|
|
326
|
+
// Resulting predicate: `col eq 'Value'` (single) or
|
|
327
|
+
// `(col eq 'V1' or col eq 'V2')` (multi).
|
|
328
|
+
const equalsClauses = col.values.map((v) => me.odataOperator.equals(colName, `'${me.encode(v)}'`));
|
|
321
329
|
return equalsClauses.length === 1
|
|
322
330
|
? equalsClauses[0]
|
|
323
331
|
: `(${equalsClauses.join(' or ')})`;
|
|
@@ -549,11 +557,49 @@ class OdataProvider {
|
|
|
549
557
|
if (beforeRequest) {
|
|
550
558
|
beforeRequest(options);
|
|
551
559
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
560
|
+
// OData wraps nested fields with '/' (e.g. parent/child); the response
|
|
561
|
+
// mirrors that shape, so we walk the path instead of using the raw key.
|
|
562
|
+
// We also fall back to a case-insensitive lookup because some OData
|
|
563
|
+
// services return PascalCase property names regardless of the request
|
|
564
|
+
// casing (e.g. asked for `dacStatus`, response is `DacStatus`).
|
|
565
|
+
const readField = (row) => {
|
|
566
|
+
if (row == null)
|
|
567
|
+
return undefined;
|
|
568
|
+
if (Object.prototype.hasOwnProperty.call(row, field))
|
|
569
|
+
return row[field];
|
|
570
|
+
const segments = String(field).split('.');
|
|
571
|
+
let current = row;
|
|
572
|
+
for (const seg of segments) {
|
|
573
|
+
if (current == null)
|
|
574
|
+
return undefined;
|
|
575
|
+
if (Object.prototype.hasOwnProperty.call(current, seg)) {
|
|
576
|
+
current = current[seg];
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
// Case-insensitive key match as a last resort.
|
|
580
|
+
const lower = seg.toLowerCase();
|
|
581
|
+
const match = Object.keys(current).find(k => k.toLowerCase() === lower);
|
|
582
|
+
if (!match)
|
|
583
|
+
return undefined;
|
|
584
|
+
current = current[match];
|
|
585
|
+
}
|
|
586
|
+
return current;
|
|
587
|
+
};
|
|
588
|
+
me.callApi(me.toQuery(options))
|
|
589
|
+
.then((x) => {
|
|
590
|
+
const values = x ? me.getOdataResult(x) || [] : [];
|
|
591
|
+
const mapped = values.map((y) => readField(y));
|
|
592
|
+
console.log('[OdataProvider] getFilterValuesParams response for', field, '— rows:', values.length, '— sample keys:', values[0] ? Object.keys(values[0]) : '(none)', '— mapped sample:', mapped.slice(0, 5));
|
|
593
|
+
// Helpful diagnostic when the response shape doesn't match the field.
|
|
594
|
+
if (values.length > 0 && mapped.every((v) => v === undefined)) {
|
|
595
|
+
console.warn('[OdataProvider] getFilterValuesParams: response had', values.length, 'rows but none contained field', field, '— sample row keys:', Object.keys(values[0] || {}));
|
|
556
596
|
}
|
|
597
|
+
callback(mapped);
|
|
598
|
+
})
|
|
599
|
+
.catch((err) => {
|
|
600
|
+
console.error('[OdataProvider] getFilterValuesParams failed for field', field, err);
|
|
601
|
+
// Always invoke the callback so AG Grid's set filter doesn't hang.
|
|
602
|
+
callback([]);
|
|
557
603
|
});
|
|
558
604
|
};
|
|
559
605
|
/**
|
|
@@ -2106,6 +2152,10 @@ class GridLibraryComponent {
|
|
|
2106
2152
|
this.tooltipShowDelay = 500;
|
|
2107
2153
|
this.tooltipHideDelay = 4000;
|
|
2108
2154
|
this.rowModelType = 'clientSide';
|
|
2155
|
+
// Per-field cache of distinct values for fill-cell set filters. Populated
|
|
2156
|
+
// by the OData groupby fallback in _getFillFilterValues and cleared when
|
|
2157
|
+
// the underlying grid data is refreshed.
|
|
2158
|
+
this._fillFilterValuesCache = new Map();
|
|
2109
2159
|
this._Design_Manager = 'Design Manager';
|
|
2110
2160
|
this._Integration_Lead = 'Integration Lead';
|
|
2111
2161
|
this._Tool_Owner = 'Tool Owner';
|
|
@@ -2405,6 +2455,9 @@ class GridLibraryComponent {
|
|
|
2405
2455
|
// handles case-insensitive matching server-side via tolower().
|
|
2406
2456
|
values: (params) => this._getFillFilterValues(res, params),
|
|
2407
2457
|
refreshValuesOnOpen: true,
|
|
2458
|
+
// Open with nothing checked so a user picking one option triggers
|
|
2459
|
+
// a single backend request instead of "uncheck all + pick one".
|
|
2460
|
+
defaultToNothingSelected: true,
|
|
2408
2461
|
buttons: ['reset'],
|
|
2409
2462
|
// Pretty label for each checkbox; falls back to the value itself
|
|
2410
2463
|
valueFormatter: (p) => {
|
|
@@ -2547,7 +2600,31 @@ class GridLibraryComponent {
|
|
|
2547
2600
|
}
|
|
2548
2601
|
const field = res?.field;
|
|
2549
2602
|
if (this.enableServerSidePaging && this.odataProvider) {
|
|
2550
|
-
|
|
2603
|
+
// Cache OData distinct-values per field so repeated filter-opens for
|
|
2604
|
+
// fill-cell columns don't re-hit the backend. Cache is invalidated on
|
|
2605
|
+
// grid data refresh (see _invalidateFillFilterCache).
|
|
2606
|
+
const cached = this._fillFilterValuesCache.get(field);
|
|
2607
|
+
if (cached) {
|
|
2608
|
+
params.success(cached);
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
try {
|
|
2612
|
+
this.odataProvider.getFilterValuesParams(field, (data) => {
|
|
2613
|
+
const cleaned = (data || []).filter(v => v !== null && v !== undefined && v !== '');
|
|
2614
|
+
// Only cache non-empty successful responses so a transient
|
|
2615
|
+
// failure doesn't permanently stick an empty list.
|
|
2616
|
+
if (cleaned.length > 0) {
|
|
2617
|
+
this._fillFilterValuesCache.set(field, cleaned);
|
|
2618
|
+
}
|
|
2619
|
+
// Never leave the set-filter spinning: always invoke success,
|
|
2620
|
+
// even with an empty list.
|
|
2621
|
+
params.success(cleaned);
|
|
2622
|
+
}, undefined);
|
|
2623
|
+
}
|
|
2624
|
+
catch (err) {
|
|
2625
|
+
console.error('[grid-library] getFilterValuesParams threw for field', field, err);
|
|
2626
|
+
params.success([]);
|
|
2627
|
+
}
|
|
2551
2628
|
return;
|
|
2552
2629
|
}
|
|
2553
2630
|
const source = Array.isArray(this.rowData) ? this.rowData : [];
|
|
@@ -2602,20 +2679,86 @@ class GridLibraryComponent {
|
|
|
2602
2679
|
return false;
|
|
2603
2680
|
}
|
|
2604
2681
|
setExternalFilters(options) {
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2682
|
+
// Extract any caller-supplied $filter predicate from the configured
|
|
2683
|
+
// serverDataUrl so we can merge it correctly with whatever the provider
|
|
2684
|
+
// built. Without this, requests like `$apply=groupby((dacStatus))` get a
|
|
2685
|
+
// sibling `$filter=siteId eq 5` appended, which OData rejects because
|
|
2686
|
+
// after `groupby` the only remaining property is the grouped one.
|
|
2687
|
+
let serverUrl = this.serverDataUrl;
|
|
2688
|
+
let externalFilter = '';
|
|
2689
|
+
const filterIdx = serverUrl.indexOf('$filter=');
|
|
2690
|
+
if (filterIdx >= 0) {
|
|
2691
|
+
// Find the end of the $filter value (next & or end of string).
|
|
2692
|
+
const after = serverUrl.indexOf('&', filterIdx);
|
|
2693
|
+
externalFilter = serverUrl.substring(filterIdx + '$filter='.length, after === -1 ? serverUrl.length : after);
|
|
2694
|
+
// Strip the $filter clause (and its leading ? or &) from the base URL.
|
|
2695
|
+
const before = serverUrl.charAt(filterIdx - 1); // '?' or '&'
|
|
2696
|
+
const removeStart = filterIdx - 1;
|
|
2697
|
+
const removeEnd = after === -1 ? serverUrl.length : after;
|
|
2698
|
+
serverUrl =
|
|
2699
|
+
serverUrl.substring(0, removeStart) +
|
|
2700
|
+
(before === '?' && after !== -1 ? '?' : '') +
|
|
2701
|
+
serverUrl.substring(removeEnd + (before === '?' && after !== -1 ? 1 : 0));
|
|
2702
|
+
}
|
|
2703
|
+
// `options` always starts with '?' from the provider.
|
|
2704
|
+
// If we still need to attach to a URL that already has '?', flip it to '&'.
|
|
2705
|
+
if (serverUrl.indexOf('?') >= 0 && options.startsWith('?')) {
|
|
2706
|
+
options = '&' + options.substring(1);
|
|
2707
|
+
}
|
|
2708
|
+
if (!externalFilter) {
|
|
2709
|
+
return `${serverUrl}${options}`;
|
|
2710
|
+
}
|
|
2711
|
+
// Decode for matching; we'll re-emit the predicate as-is since OData
|
|
2712
|
+
// accepts both encoded and decoded forms within a query string.
|
|
2713
|
+
const decodedFilter = (() => {
|
|
2714
|
+
try {
|
|
2715
|
+
return decodeURIComponent(externalFilter);
|
|
2716
|
+
}
|
|
2717
|
+
catch {
|
|
2718
|
+
return externalFilter;
|
|
2719
|
+
}
|
|
2720
|
+
})();
|
|
2721
|
+
// Case 1: provider used $apply (e.g. groupby for set-filter values, or
|
|
2722
|
+
// any aggregation). The external predicate must run BEFORE the aggregation
|
|
2723
|
+
// as a filter() transformation, otherwise its columns no longer exist.
|
|
2724
|
+
const applyMatch = options.match(/([?&])\$apply=([^&]+)/);
|
|
2725
|
+
if (applyMatch) {
|
|
2726
|
+
const sep = applyMatch[1];
|
|
2727
|
+
const applyValue = applyMatch[2];
|
|
2728
|
+
const decodedApply = (() => {
|
|
2729
|
+
try {
|
|
2730
|
+
return decodeURIComponent(applyValue);
|
|
2731
|
+
}
|
|
2732
|
+
catch {
|
|
2733
|
+
return applyValue;
|
|
2734
|
+
}
|
|
2735
|
+
})();
|
|
2736
|
+
const merged = `filter(${decodedFilter})/${decodedApply}`;
|
|
2737
|
+
options = options.replace(`${sep}$apply=${applyValue}`, `${sep}$apply=${encodeURIComponent(merged)}`);
|
|
2738
|
+
return `${serverUrl}${options}`;
|
|
2739
|
+
}
|
|
2740
|
+
// Case 2: provider already has its own $filter. AND them together.
|
|
2741
|
+
const filterMatch = options.match(/([?&])\$filter=([^&]+)/);
|
|
2742
|
+
if (filterMatch) {
|
|
2743
|
+
const sep = filterMatch[1];
|
|
2744
|
+
const existing = filterMatch[2];
|
|
2745
|
+
const decodedExisting = (() => {
|
|
2746
|
+
try {
|
|
2747
|
+
return decodeURIComponent(existing);
|
|
2748
|
+
}
|
|
2749
|
+
catch {
|
|
2750
|
+
return existing;
|
|
2751
|
+
}
|
|
2752
|
+
})();
|
|
2753
|
+
const merged = `(${decodedExisting}) and (${decodedFilter})`;
|
|
2754
|
+
options = options.replace(`${sep}$filter=${existing}`, `${sep}$filter=${encodeURIComponent(merged)}`);
|
|
2755
|
+
return `${serverUrl}${options}`;
|
|
2617
2756
|
}
|
|
2618
|
-
|
|
2757
|
+
// Case 3: no provider filter and no $apply — just add the external $filter.
|
|
2758
|
+
const joiner = options.length === 0
|
|
2759
|
+
? (serverUrl.indexOf('?') >= 0 ? '&' : '?')
|
|
2760
|
+
: '&';
|
|
2761
|
+
return `${serverUrl}${options}${joiner}$filter=${encodeURIComponent(decodedFilter)}`;
|
|
2619
2762
|
}
|
|
2620
2763
|
onGridReady(params) {
|
|
2621
2764
|
this.gridAPI = params.api;
|
|
@@ -2659,6 +2802,7 @@ class GridLibraryComponent {
|
|
|
2659
2802
|
if (!hasRetried) {
|
|
2660
2803
|
hasRetried = true;
|
|
2661
2804
|
setTimeout(() => {
|
|
2805
|
+
this._fillFilterValuesCache.clear();
|
|
2662
2806
|
params.api.refreshServerSide({ purge: true });
|
|
2663
2807
|
}, 2000);
|
|
2664
2808
|
}
|
|
@@ -2675,6 +2819,7 @@ class GridLibraryComponent {
|
|
|
2675
2819
|
if (!hasRetried) {
|
|
2676
2820
|
hasRetried = true;
|
|
2677
2821
|
setTimeout(() => {
|
|
2822
|
+
this._fillFilterValuesCache.clear();
|
|
2678
2823
|
this.gridAPI.refreshServerSide({ purge: true });
|
|
2679
2824
|
}, 2000);
|
|
2680
2825
|
}
|
|
@@ -2770,6 +2915,7 @@ class GridLibraryComponent {
|
|
|
2770
2915
|
this.gridAPI.showLoadingOverlay();
|
|
2771
2916
|
this.gridAPI.resetColumnState();
|
|
2772
2917
|
this.gridAPI.setFilterModel(null);
|
|
2918
|
+
this._fillFilterValuesCache.clear();
|
|
2773
2919
|
this.gridColumnState = JSON.parse(overrideStateData);
|
|
2774
2920
|
this.setGridState();
|
|
2775
2921
|
setTimeout(() => { this.gridAPI.hideOverlay(); }, 500);
|