basesite-shared-grid-lib 21.0.1-beta.5 → 21.0.1-beta.7
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.1-beta.7.tgz +0 -0
- package/fesm2022/basesite-shared-grid-lib.mjs +166 -27
- 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.1-beta.5.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
|
/**
|
|
@@ -549,11 +549,49 @@ class OdataProvider {
|
|
|
549
549
|
if (beforeRequest) {
|
|
550
550
|
beforeRequest(options);
|
|
551
551
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
552
|
+
// OData wraps nested fields with '/' (e.g. parent/child); the response
|
|
553
|
+
// mirrors that shape, so we walk the path instead of using the raw key.
|
|
554
|
+
// We also fall back to a case-insensitive lookup because some OData
|
|
555
|
+
// services return PascalCase property names regardless of the request
|
|
556
|
+
// casing (e.g. asked for `dacStatus`, response is `DacStatus`).
|
|
557
|
+
const readField = (row) => {
|
|
558
|
+
if (row == null)
|
|
559
|
+
return undefined;
|
|
560
|
+
if (Object.prototype.hasOwnProperty.call(row, field))
|
|
561
|
+
return row[field];
|
|
562
|
+
const segments = String(field).split('.');
|
|
563
|
+
let current = row;
|
|
564
|
+
for (const seg of segments) {
|
|
565
|
+
if (current == null)
|
|
566
|
+
return undefined;
|
|
567
|
+
if (Object.prototype.hasOwnProperty.call(current, seg)) {
|
|
568
|
+
current = current[seg];
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
// Case-insensitive key match as a last resort.
|
|
572
|
+
const lower = seg.toLowerCase();
|
|
573
|
+
const match = Object.keys(current).find(k => k.toLowerCase() === lower);
|
|
574
|
+
if (!match)
|
|
575
|
+
return undefined;
|
|
576
|
+
current = current[match];
|
|
577
|
+
}
|
|
578
|
+
return current;
|
|
579
|
+
};
|
|
580
|
+
me.callApi(me.toQuery(options))
|
|
581
|
+
.then((x) => {
|
|
582
|
+
const values = x ? me.getOdataResult(x) || [] : [];
|
|
583
|
+
const mapped = values.map((y) => readField(y));
|
|
584
|
+
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));
|
|
585
|
+
// Helpful diagnostic when the response shape doesn't match the field.
|
|
586
|
+
if (values.length > 0 && mapped.every((v) => v === undefined)) {
|
|
587
|
+
console.warn('[OdataProvider] getFilterValuesParams: response had', values.length, 'rows but none contained field', field, '— sample row keys:', Object.keys(values[0] || {}));
|
|
556
588
|
}
|
|
589
|
+
callback(mapped);
|
|
590
|
+
})
|
|
591
|
+
.catch((err) => {
|
|
592
|
+
console.error('[OdataProvider] getFilterValuesParams failed for field', field, err);
|
|
593
|
+
// Always invoke the callback so AG Grid's set filter doesn't hang.
|
|
594
|
+
callback([]);
|
|
557
595
|
});
|
|
558
596
|
};
|
|
559
597
|
/**
|
|
@@ -2086,6 +2124,10 @@ class GridLibraryComponent {
|
|
|
2086
2124
|
this.tooltipShowDelay = 500;
|
|
2087
2125
|
this.tooltipHideDelay = 4000;
|
|
2088
2126
|
this.rowModelType = 'clientSide';
|
|
2127
|
+
// Per-field cache of distinct values for fill-cell set filters. Populated
|
|
2128
|
+
// by the OData groupby fallback in _getFillFilterValues and cleared when
|
|
2129
|
+
// the underlying grid data is refreshed.
|
|
2130
|
+
this._fillFilterValuesCache = new Map();
|
|
2089
2131
|
this._Design_Manager = 'Design Manager';
|
|
2090
2132
|
this._Integration_Lead = 'Integration Lead';
|
|
2091
2133
|
this._Tool_Owner = 'Tool Owner';
|
|
@@ -2385,6 +2427,9 @@ class GridLibraryComponent {
|
|
|
2385
2427
|
// handles case-insensitive matching server-side via tolower().
|
|
2386
2428
|
values: (params) => this._getFillFilterValues(res, params),
|
|
2387
2429
|
refreshValuesOnOpen: true,
|
|
2430
|
+
// Open with nothing checked so a user picking one option triggers
|
|
2431
|
+
// a single backend request instead of "uncheck all + pick one".
|
|
2432
|
+
defaultToNothingSelected: true,
|
|
2388
2433
|
buttons: ['reset'],
|
|
2389
2434
|
// Pretty label for each checkbox; falls back to the value itself
|
|
2390
2435
|
valueFormatter: (p) => {
|
|
@@ -2527,7 +2572,31 @@ class GridLibraryComponent {
|
|
|
2527
2572
|
}
|
|
2528
2573
|
const field = res?.field;
|
|
2529
2574
|
if (this.enableServerSidePaging && this.odataProvider) {
|
|
2530
|
-
|
|
2575
|
+
// Cache OData distinct-values per field so repeated filter-opens for
|
|
2576
|
+
// fill-cell columns don't re-hit the backend. Cache is invalidated on
|
|
2577
|
+
// grid data refresh (see _invalidateFillFilterCache).
|
|
2578
|
+
const cached = this._fillFilterValuesCache.get(field);
|
|
2579
|
+
if (cached) {
|
|
2580
|
+
params.success(cached);
|
|
2581
|
+
return;
|
|
2582
|
+
}
|
|
2583
|
+
try {
|
|
2584
|
+
this.odataProvider.getFilterValuesParams(field, (data) => {
|
|
2585
|
+
const cleaned = (data || []).filter(v => v !== null && v !== undefined && v !== '');
|
|
2586
|
+
// Only cache non-empty successful responses so a transient
|
|
2587
|
+
// failure doesn't permanently stick an empty list.
|
|
2588
|
+
if (cleaned.length > 0) {
|
|
2589
|
+
this._fillFilterValuesCache.set(field, cleaned);
|
|
2590
|
+
}
|
|
2591
|
+
// Never leave the set-filter spinning: always invoke success,
|
|
2592
|
+
// even with an empty list.
|
|
2593
|
+
params.success(cleaned);
|
|
2594
|
+
}, undefined);
|
|
2595
|
+
}
|
|
2596
|
+
catch (err) {
|
|
2597
|
+
console.error('[grid-library] getFilterValuesParams threw for field', field, err);
|
|
2598
|
+
params.success([]);
|
|
2599
|
+
}
|
|
2531
2600
|
return;
|
|
2532
2601
|
}
|
|
2533
2602
|
const source = Array.isArray(this.rowData) ? this.rowData : [];
|
|
@@ -2582,28 +2651,86 @@ class GridLibraryComponent {
|
|
|
2582
2651
|
return false;
|
|
2583
2652
|
}
|
|
2584
2653
|
setExternalFilters(options) {
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2654
|
+
// Extract any caller-supplied $filter predicate from the configured
|
|
2655
|
+
// serverDataUrl so we can merge it correctly with whatever the provider
|
|
2656
|
+
// built. Without this, requests like `$apply=groupby((dacStatus))` get a
|
|
2657
|
+
// sibling `$filter=siteId eq 5` appended, which OData rejects because
|
|
2658
|
+
// after `groupby` the only remaining property is the grouped one.
|
|
2659
|
+
let serverUrl = this.serverDataUrl;
|
|
2660
|
+
let externalFilter = '';
|
|
2661
|
+
const filterIdx = serverUrl.indexOf('$filter=');
|
|
2662
|
+
if (filterIdx >= 0) {
|
|
2663
|
+
// Find the end of the $filter value (next & or end of string).
|
|
2664
|
+
const after = serverUrl.indexOf('&', filterIdx);
|
|
2665
|
+
externalFilter = serverUrl.substring(filterIdx + '$filter='.length, after === -1 ? serverUrl.length : after);
|
|
2666
|
+
// Strip the $filter clause (and its leading ? or &) from the base URL.
|
|
2667
|
+
const before = serverUrl.charAt(filterIdx - 1); // '?' or '&'
|
|
2668
|
+
const removeStart = filterIdx - 1;
|
|
2669
|
+
const removeEnd = after === -1 ? serverUrl.length : after;
|
|
2670
|
+
serverUrl =
|
|
2671
|
+
serverUrl.substring(0, removeStart) +
|
|
2672
|
+
(before === '?' && after !== -1 ? '?' : '') +
|
|
2673
|
+
serverUrl.substring(removeEnd + (before === '?' && after !== -1 ? 1 : 0));
|
|
2674
|
+
}
|
|
2675
|
+
// `options` always starts with '?' from the provider.
|
|
2676
|
+
// If we still need to attach to a URL that already has '?', flip it to '&'.
|
|
2677
|
+
if (serverUrl.indexOf('?') >= 0 && options.startsWith('?')) {
|
|
2678
|
+
options = '&' + options.substring(1);
|
|
2679
|
+
}
|
|
2680
|
+
if (!externalFilter) {
|
|
2681
|
+
return `${serverUrl}${options}`;
|
|
2682
|
+
}
|
|
2683
|
+
// Decode for matching; we'll re-emit the predicate as-is since OData
|
|
2684
|
+
// accepts both encoded and decoded forms within a query string.
|
|
2685
|
+
const decodedFilter = (() => {
|
|
2686
|
+
try {
|
|
2687
|
+
return decodeURIComponent(externalFilter);
|
|
2688
|
+
}
|
|
2689
|
+
catch {
|
|
2690
|
+
return externalFilter;
|
|
2691
|
+
}
|
|
2692
|
+
})();
|
|
2693
|
+
// Case 1: provider used $apply (e.g. groupby for set-filter values, or
|
|
2694
|
+
// any aggregation). The external predicate must run BEFORE the aggregation
|
|
2695
|
+
// as a filter() transformation, otherwise its columns no longer exist.
|
|
2696
|
+
const applyMatch = options.match(/([?&])\$apply=([^&]+)/);
|
|
2697
|
+
if (applyMatch) {
|
|
2698
|
+
const sep = applyMatch[1];
|
|
2699
|
+
const applyValue = applyMatch[2];
|
|
2700
|
+
const decodedApply = (() => {
|
|
2701
|
+
try {
|
|
2702
|
+
return decodeURIComponent(applyValue);
|
|
2703
|
+
}
|
|
2704
|
+
catch {
|
|
2705
|
+
return applyValue;
|
|
2706
|
+
}
|
|
2707
|
+
})();
|
|
2708
|
+
const merged = `filter(${decodedFilter})/${decodedApply}`;
|
|
2709
|
+
options = options.replace(`${sep}$apply=${applyValue}`, `${sep}$apply=${encodeURIComponent(merged)}`);
|
|
2710
|
+
return `${serverUrl}${options}`;
|
|
2711
|
+
}
|
|
2712
|
+
// Case 2: provider already has its own $filter. AND them together.
|
|
2713
|
+
const filterMatch = options.match(/([?&])\$filter=([^&]+)/);
|
|
2714
|
+
if (filterMatch) {
|
|
2715
|
+
const sep = filterMatch[1];
|
|
2716
|
+
const existing = filterMatch[2];
|
|
2717
|
+
const decodedExisting = (() => {
|
|
2718
|
+
try {
|
|
2719
|
+
return decodeURIComponent(existing);
|
|
2720
|
+
}
|
|
2721
|
+
catch {
|
|
2722
|
+
return existing;
|
|
2723
|
+
}
|
|
2724
|
+
})();
|
|
2725
|
+
const merged = `(${decodedExisting}) and (${decodedFilter})`;
|
|
2726
|
+
options = options.replace(`${sep}$filter=${existing}`, `${sep}$filter=${encodeURIComponent(merged)}`);
|
|
2727
|
+
return `${serverUrl}${options}`;
|
|
2605
2728
|
}
|
|
2606
|
-
|
|
2729
|
+
// Case 3: no provider filter and no $apply — just add the external $filter.
|
|
2730
|
+
const joiner = options.length === 0
|
|
2731
|
+
? (serverUrl.indexOf('?') >= 0 ? '&' : '?')
|
|
2732
|
+
: '&';
|
|
2733
|
+
return `${serverUrl}${options}${joiner}$filter=${encodeURIComponent(decodedFilter)}`;
|
|
2607
2734
|
}
|
|
2608
2735
|
onGridReady(params) {
|
|
2609
2736
|
this.gridAPI = params.api;
|
|
@@ -2619,8 +2746,17 @@ class GridLibraryComponent {
|
|
|
2619
2746
|
// Add timeout to prevent infinite loading
|
|
2620
2747
|
const API_TIMEOUT = 800000; //80 seconds
|
|
2621
2748
|
let hasRetried = false;
|
|
2749
|
+
// Fill-cell columns use the set filter, which only emits exact-match
|
|
2750
|
+
// predicates (eq / in). Wrapping those in tolower() forces the DB to
|
|
2751
|
+
// scan + lowercase every row (non-sargable), which was making filter
|
|
2752
|
+
// requests ~10x+ slower than baseline. Skip tolower() for these
|
|
2753
|
+
// columns by registering them as caseSensitive with the provider.
|
|
2754
|
+
const fillCellFields = (this.initialColumnDef ?? [])
|
|
2755
|
+
.filter((c) => c?.columnType === 'fill' && c?.field)
|
|
2756
|
+
.map((c) => c.field);
|
|
2622
2757
|
this.odataProvider = new OdataServerSideProvider({
|
|
2623
2758
|
isCaseSensitiveStringFilter: false,
|
|
2759
|
+
caseSensitiveColumns: fillCellFields,
|
|
2624
2760
|
callApi: (options) => {
|
|
2625
2761
|
const url = this.setExternalFilters(options);
|
|
2626
2762
|
console.log('Server-side API call:', url);
|
|
@@ -2647,6 +2783,7 @@ class GridLibraryComponent {
|
|
|
2647
2783
|
if (!hasRetried) {
|
|
2648
2784
|
hasRetried = true;
|
|
2649
2785
|
setTimeout(() => {
|
|
2786
|
+
this._fillFilterValuesCache.clear();
|
|
2650
2787
|
params.api.refreshServerSide({ purge: true });
|
|
2651
2788
|
}, 2000);
|
|
2652
2789
|
}
|
|
@@ -2663,6 +2800,7 @@ class GridLibraryComponent {
|
|
|
2663
2800
|
if (!hasRetried) {
|
|
2664
2801
|
hasRetried = true;
|
|
2665
2802
|
setTimeout(() => {
|
|
2803
|
+
this._fillFilterValuesCache.clear();
|
|
2666
2804
|
this.gridAPI.refreshServerSide({ purge: true });
|
|
2667
2805
|
}, 2000);
|
|
2668
2806
|
}
|
|
@@ -2758,6 +2896,7 @@ class GridLibraryComponent {
|
|
|
2758
2896
|
this.gridAPI.showLoadingOverlay();
|
|
2759
2897
|
this.gridAPI.resetColumnState();
|
|
2760
2898
|
this.gridAPI.setFilterModel(null);
|
|
2899
|
+
this._fillFilterValuesCache.clear();
|
|
2761
2900
|
this.gridColumnState = JSON.parse(overrideStateData);
|
|
2762
2901
|
this.setGridState();
|
|
2763
2902
|
setTimeout(() => { this.gridAPI.hideOverlay(); }, 500);
|