@genesislcap/grid-pro 15.50.0 → 15.50.2
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/dist/custom-elements.json +128 -42
- package/dist/dts/datasource/dataserver-result.filter.d.ts +12 -0
- package/dist/dts/datasource/dataserver-result.filter.d.ts.map +1 -1
- package/dist/dts/datasource/infinite.resource.d.ts +23 -18
- package/dist/dts/datasource/infinite.resource.d.ts.map +1 -1
- package/dist/dts/datasource/server-side.resource-base.d.ts.map +1 -1
- package/dist/dts/datasource/server-side.resource-dataserver.d.ts +32 -0
- package/dist/dts/datasource/server-side.resource-dataserver.d.ts.map +1 -1
- package/dist/dts/grid-pro-genesis-datasource/grid-pro-genesis-datasource.d.ts +26 -6
- package/dist/dts/grid-pro-genesis-datasource/grid-pro-genesis-datasource.d.ts.map +1 -1
- package/dist/esm/datasource/dataserver-result.filter.js +32 -0
- package/dist/esm/datasource/infinite.resource.js +66 -72
- package/dist/esm/datasource/server-side.resource-base.js +7 -4
- package/dist/esm/datasource/server-side.resource-dataserver.js +84 -12
- package/dist/esm/grid-pro-genesis-datasource/grid-pro-genesis-datasource.js +100 -15
- package/dist/grid-pro.d.ts +49 -24
- package/package.json +12 -12
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { __awaiter } from "tslib";
|
|
2
|
-
import { MessageType, } from '@genesislcap/foundation-comms';
|
|
2
|
+
import { dataserverPagingStatus, describeDataserverPagingStop, MessageType, resolveMaxView, } from '@genesislcap/foundation-comms';
|
|
3
3
|
import { logger } from '../utils';
|
|
4
|
-
import { filterDataserverResult } from './dataserver-result.filter';
|
|
4
|
+
import { cachedRowKeyResolver, filterDataserverResult } from './dataserver-result.filter';
|
|
5
5
|
import { DeliveredBlockLedger } from './delivered-block.ledger';
|
|
6
6
|
import { convertFilterModelToCriteria } from './filter.utils';
|
|
7
7
|
/**
|
|
@@ -115,16 +115,12 @@ export class GenesisInfiniteDatasource {
|
|
|
115
115
|
this.rowData = new Map();
|
|
116
116
|
/** Blocks waiting for the cache to reach their range. @internal */
|
|
117
117
|
this.pendingBlocks = new Set();
|
|
118
|
-
/** Highest absolute row index already handed to AG Grid. @internal */
|
|
119
|
-
this.deliveredRowCount = 0;
|
|
120
118
|
/** False until the first message arrives on the subscription. @internal */
|
|
121
119
|
this.streamStarted = false;
|
|
122
120
|
/** Whether a MORE_ROWS request is awaiting its batch. @internal */
|
|
123
121
|
this.moreRowsInFlight = false;
|
|
124
122
|
/** Whether the current subscription was opened with a CRITERIA_MATCH. @internal */
|
|
125
123
|
this.hasCriteria = false;
|
|
126
|
-
/** So the MAX_VIEW truncation warning is logged once per subscription. @internal */
|
|
127
|
-
this.viewTruncationWarned = false;
|
|
128
124
|
this.createSnapshotFunc = options.createSnapshotFunc;
|
|
129
125
|
this.createDataserverStreamFunc = options.createDataserverStreamFunc;
|
|
130
126
|
this.getMoreRowsFunc = options.getMoreRowsFunc;
|
|
@@ -371,7 +367,7 @@ export class GenesisInfiniteDatasource {
|
|
|
371
367
|
};
|
|
372
368
|
// The cache may already cover this block - one batch can span several blocks, and blocks are
|
|
373
369
|
// re-requested after a purge - and once the stream has ended nothing more will arrive.
|
|
374
|
-
if (this.streamStarted && (this.rowData.size >= endRow || this.
|
|
370
|
+
if (this.streamStarted && (this.rowData.size >= endRow || this.streamEnded)) {
|
|
375
371
|
this.settleBlock(block, true);
|
|
376
372
|
return;
|
|
377
373
|
}
|
|
@@ -395,7 +391,7 @@ export class GenesisInfiniteDatasource {
|
|
|
395
391
|
* @internal
|
|
396
392
|
*/
|
|
397
393
|
pumpMoreRows() {
|
|
398
|
-
if (this.moreRowsInFlight || this.
|
|
394
|
+
if (this.moreRowsInFlight || this.streamEnded || this.pendingBlocks.size === 0) {
|
|
399
395
|
return;
|
|
400
396
|
}
|
|
401
397
|
// No subscription, or none that has identified itself yet: a MORE_ROWS against a stale
|
|
@@ -430,8 +426,6 @@ export class GenesisInfiniteDatasource {
|
|
|
430
426
|
if ((error === null || error === void 0 ? void 0 : error.status) === GenesisInfiniteDatasource.HTTP_NOT_FOUND) {
|
|
431
427
|
logger.warn(`Infinite DATASERVER paging is not available on this connection (MORE_ROWS answered 404, expected over HTTP/feature.forceHttp). ${this.resourceName} is truncated to the ${this.rowData.size} row(s) already delivered; use a websocket connection, or a REQUEST_SERVER resource, to page further.`);
|
|
432
428
|
this.moreRows = false;
|
|
433
|
-
// The MAX_VIEW warning would misdiagnose this truncation as a view cap.
|
|
434
|
-
this.viewTruncationWarned = true;
|
|
435
429
|
this.settleCoveredBlocks();
|
|
436
430
|
return;
|
|
437
431
|
}
|
|
@@ -445,7 +439,7 @@ export class GenesisInfiniteDatasource {
|
|
|
445
439
|
* @internal
|
|
446
440
|
*/
|
|
447
441
|
handleDataserverMessage(message) {
|
|
448
|
-
var _a, _b, _c, _d, _e, _f;
|
|
442
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
449
443
|
const messageType = message === null || message === void 0 ? void 0 : message.MESSAGE_TYPE;
|
|
450
444
|
if (messageType === MessageType.LOGOFF_ACK || messageType === MessageType.MSG_NACK) {
|
|
451
445
|
// Report it (same messages as the SSRM dataserver resource) - without this a logged-off
|
|
@@ -482,7 +476,8 @@ export class GenesisInfiniteDatasource {
|
|
|
482
476
|
this.notifyLiveChange(filtered);
|
|
483
477
|
return;
|
|
484
478
|
}
|
|
485
|
-
const
|
|
479
|
+
const answersMoreRows = this.moreRowsInFlight;
|
|
480
|
+
const solicited = answersMoreRows || this.pendingBlocks.size > 0 || !this.streamStarted;
|
|
486
481
|
this.moreRowsInFlight = false;
|
|
487
482
|
if ((message === null || message === void 0 ? void 0 : message.MORE_ROWS) !== undefined) {
|
|
488
483
|
this.moreRows = message.MORE_ROWS;
|
|
@@ -503,10 +498,50 @@ export class GenesisInfiniteDatasource {
|
|
|
503
498
|
return;
|
|
504
499
|
}
|
|
505
500
|
}
|
|
501
|
+
if (!this.streamEnded) {
|
|
502
|
+
this.notePagingStatus(answersMoreRows && !((_g = filtered === null || filtered === void 0 ? void 0 : filtered.inserts) === null || _g === void 0 ? void 0 : _g.length) && (message === null || message === void 0 ? void 0 : message.MORE_ROWS) === true);
|
|
503
|
+
}
|
|
506
504
|
this.streamStarted = true;
|
|
507
505
|
this.settleCoveredBlocks();
|
|
508
506
|
this.pumpMoreRows();
|
|
509
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* Stops the walk when the view is full at MAX_VIEW (checked by row count, which saves asking for
|
|
510
|
+
* the empty page) or a MORE_ROWS came back empty, and says why.
|
|
511
|
+
* @param emptyReply - Whether this message answered a MORE_ROWS with no rows.
|
|
512
|
+
* @internal
|
|
513
|
+
*/
|
|
514
|
+
notePagingStatus(emptyReply) {
|
|
515
|
+
var _a;
|
|
516
|
+
const maxView = (_a = this.resourceParams) === null || _a === void 0 ? void 0 : _a.MAX_VIEW;
|
|
517
|
+
const status = dataserverPagingStatus({
|
|
518
|
+
moreRows: this.moreRows,
|
|
519
|
+
emptyReply,
|
|
520
|
+
rowsHeld: this.rowData.size,
|
|
521
|
+
maxView,
|
|
522
|
+
});
|
|
523
|
+
if (status !== 'view-full' && status !== 'stalled') {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
this.pagingStopped = status;
|
|
527
|
+
const warning = describeDataserverPagingStop({
|
|
528
|
+
status,
|
|
529
|
+
resourceName: this.resourceName,
|
|
530
|
+
rowsHeld: this.rowData.size,
|
|
531
|
+
maxView,
|
|
532
|
+
emptyReply,
|
|
533
|
+
// ROWS_COUNT ignores CRITERIA_MATCH, so it is no total for a filtered view.
|
|
534
|
+
totalRows: this.hasCriteria ? undefined : this.serverRowsCount,
|
|
535
|
+
attribute: 'max-view',
|
|
536
|
+
});
|
|
537
|
+
if (warning) {
|
|
538
|
+
logger.warn(warning);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/** Whether the stream will deliver no more rows: the data ran out, or paging stopped. @internal */
|
|
542
|
+
get streamEnded() {
|
|
543
|
+
return this.moreRows === false || this.pagingStopped !== undefined;
|
|
544
|
+
}
|
|
510
545
|
/**
|
|
511
546
|
* Reports a server-pushed change to the host so the grid can catch up.
|
|
512
547
|
* @internal
|
|
@@ -551,11 +586,13 @@ export class GenesisInfiniteDatasource {
|
|
|
551
586
|
}, GenesisInfiniteDatasource.BLOCK_RESOLVE_TIMEOUT_MS);
|
|
552
587
|
}
|
|
553
588
|
/**
|
|
554
|
-
* Merges a batch into the cumulative cache.
|
|
589
|
+
* Merges a batch's INSERT and MODIFY rows into the cumulative cache.
|
|
590
|
+
* @remarks A DELETE never reaches here: any batch carrying one drops the cache instead (see
|
|
591
|
+
* notifyLiveChange), as removing a delivered row would shift the slice of every later block.
|
|
555
592
|
* @internal
|
|
556
593
|
*/
|
|
557
594
|
accumulate(result) {
|
|
558
|
-
var _a, _b
|
|
595
|
+
var _a, _b;
|
|
559
596
|
const rows = new Map(this.rowData);
|
|
560
597
|
(_a = result.inserts) === null || _a === void 0 ? void 0 : _a.forEach((row) => {
|
|
561
598
|
const key = row === null || row === void 0 ? void 0 : row[this.rowId];
|
|
@@ -566,41 +603,20 @@ export class GenesisInfiniteDatasource {
|
|
|
566
603
|
}
|
|
567
604
|
rows.set(key, row);
|
|
568
605
|
});
|
|
569
|
-
//
|
|
606
|
+
// Under a custom row-id a pushed row is identified only by its ROW_REF.
|
|
607
|
+
const resolveKey = cachedRowKeyResolver(rows, this.rowId);
|
|
608
|
+
// Live MODIFY rows may be partial, so merge into the cached row instead of clobbering it. A
|
|
609
|
+
// row resolved through its ROW_REF carries the custom id as undefined; keep the cached one.
|
|
570
610
|
(_b = result.updates) === null || _b === void 0 ? void 0 : _b.forEach((row) => {
|
|
571
|
-
const key = row
|
|
611
|
+
const key = resolveKey(row);
|
|
572
612
|
if (key === undefined) {
|
|
573
613
|
return;
|
|
574
614
|
}
|
|
575
615
|
const existing = rows.get(key);
|
|
576
|
-
rows.set(key, existing ? Object.assign(Object.assign({}, existing), row) : row);
|
|
577
|
-
});
|
|
578
|
-
(_c = result.deletes) === null || _c === void 0 ? void 0 : _c.forEach((row) => {
|
|
579
|
-
const key = row === null || row === void 0 ? void 0 : row[this.rowId];
|
|
580
|
-
if (key === undefined) {
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
// Rows already handed to AG Grid sit at fixed absolute indices; removing one from the
|
|
584
|
-
// cache would shift the slice offsets of every later block.
|
|
585
|
-
const index = this.cacheIndexOf(rows, key);
|
|
586
|
-
if (index === -1 || (this.deliveredRowCount > 0 && index < this.deliveredRowCount)) {
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
rows.delete(key);
|
|
616
|
+
rows.set(key, existing ? Object.assign(Object.assign(Object.assign({}, existing), row), { [this.rowId]: key }) : row);
|
|
590
617
|
});
|
|
591
618
|
this.rowData = rows;
|
|
592
619
|
}
|
|
593
|
-
/** Position of a key in the cache, or -1. @internal */
|
|
594
|
-
cacheIndexOf(rows, key) {
|
|
595
|
-
let index = 0;
|
|
596
|
-
for (const mapKey of rows.keys()) {
|
|
597
|
-
if (mapKey === key) {
|
|
598
|
-
return index;
|
|
599
|
-
}
|
|
600
|
-
index += 1;
|
|
601
|
-
}
|
|
602
|
-
return -1;
|
|
603
|
-
}
|
|
604
620
|
/**
|
|
605
621
|
* Settles every pending block the cache now covers.
|
|
606
622
|
* @internal
|
|
@@ -608,7 +624,7 @@ export class GenesisInfiniteDatasource {
|
|
|
608
624
|
settleCoveredBlocks() {
|
|
609
625
|
// Iterate a copy: settling a block removes it from the set.
|
|
610
626
|
for (const block of [...this.pendingBlocks]) {
|
|
611
|
-
if (this.rowData.size >= block.endRow || this.
|
|
627
|
+
if (this.rowData.size >= block.endRow || this.streamEnded) {
|
|
612
628
|
this.settleBlock(block, true);
|
|
613
629
|
}
|
|
614
630
|
}
|
|
@@ -630,16 +646,15 @@ export class GenesisInfiniteDatasource {
|
|
|
630
646
|
return;
|
|
631
647
|
}
|
|
632
648
|
const rows = [...this.rowData.values()].slice(block.startRow, block.endRow);
|
|
633
|
-
|
|
634
|
-
//
|
|
635
|
-
//
|
|
636
|
-
//
|
|
637
|
-
const lastRow = this.
|
|
649
|
+
// Once the stream has ended the cache is every row the grid can have, filter included.
|
|
650
|
+
// Before that, an unfiltered total gives the grid an accurate scrollbar, capped at MAX_VIEW
|
|
651
|
+
// since the view never holds more, while a filtered view has to stay open-ended - see
|
|
652
|
+
// serverRowsCount.
|
|
653
|
+
const lastRow = this.streamEnded
|
|
638
654
|
? this.rowData.size
|
|
639
|
-
: this.hasCriteria
|
|
655
|
+
: this.hasCriteria || this.serverRowsCount === undefined
|
|
640
656
|
? -1
|
|
641
|
-
: ((_a = this.
|
|
642
|
-
this.warnIfViewTruncated();
|
|
657
|
+
: Math.min(this.serverRowsCount, resolveMaxView((_a = this.resourceParams) === null || _a === void 0 ? void 0 : _a.MAX_VIEW));
|
|
643
658
|
logger.debug('Infinite DATASERVER block settled', {
|
|
644
659
|
startRow: block.startRow,
|
|
645
660
|
endRow: block.endRow,
|
|
@@ -689,26 +704,6 @@ export class GenesisInfiniteDatasource {
|
|
|
689
704
|
(_b = this.onRowsWithdrawnFunc) === null || _b === void 0 ? void 0 : _b.call(this, withdrawnRowIds);
|
|
690
705
|
}
|
|
691
706
|
}
|
|
692
|
-
/**
|
|
693
|
-
* Warns when the view filled up before the data ran out.
|
|
694
|
-
* @remarks The server stops sending at MAX_VIEW rows and reports MORE_ROWS false, which is
|
|
695
|
-
* indistinguishable from the end of the data - the grid would silently show a truncated view
|
|
696
|
-
* and a row count smaller than the resource's. Raising `max-view` past the row count is the
|
|
697
|
-
* fix, so say so rather than leaving it to be discovered.
|
|
698
|
-
* @internal
|
|
699
|
-
*/
|
|
700
|
-
warnIfViewTruncated() {
|
|
701
|
-
var _a, _b;
|
|
702
|
-
if (this.viewTruncationWarned ||
|
|
703
|
-
this.moreRows !== false ||
|
|
704
|
-
this.hasCriteria ||
|
|
705
|
-
this.serverRowsCount === undefined ||
|
|
706
|
-
this.rowData.size >= this.serverRowsCount) {
|
|
707
|
-
return;
|
|
708
|
-
}
|
|
709
|
-
this.viewTruncationWarned = true;
|
|
710
|
-
logger.warn(`${this.resourceName} stopped at ${this.rowData.size} of ${this.serverRowsCount} rows: the DATASERVER view is capped by MAX_VIEW (${(_b = (_a = this.resourceParams) === null || _a === void 0 ? void 0 : _a.MAX_VIEW) !== null && _b !== void 0 ? _b : 'server default'}). Raise max-view past the row count to reach the rest.`);
|
|
711
|
-
}
|
|
712
707
|
/** @internal */
|
|
713
708
|
failAllPendingBlocks() {
|
|
714
709
|
// Iterate a copy: settling a block removes it from the set.
|
|
@@ -740,12 +735,11 @@ export class GenesisInfiniteDatasource {
|
|
|
740
735
|
this.subscriptionKey = undefined;
|
|
741
736
|
this.sourceRef = undefined;
|
|
742
737
|
this.rowData = new Map();
|
|
743
|
-
this.deliveredRowCount = 0;
|
|
744
738
|
this.streamStarted = false;
|
|
745
739
|
this.serverRowsCount = undefined;
|
|
746
740
|
this.hasCriteria = false;
|
|
747
|
-
this.viewTruncationWarned = false;
|
|
748
741
|
this.moreRows = undefined;
|
|
742
|
+
this.pagingStopped = undefined;
|
|
749
743
|
this.moreRowsInFlight = false;
|
|
750
744
|
}
|
|
751
745
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { __awaiter, __decorate } from "tslib";
|
|
2
|
-
import { Auth, Connect } from '@genesislcap/foundation-comms';
|
|
2
|
+
import { Auth, Connect, resolveMaxView, } from '@genesislcap/foundation-comms';
|
|
3
3
|
import { UUID } from '@genesislcap/foundation-utils';
|
|
4
4
|
import { DeliveredBlockLedger } from './delivered-block.ledger';
|
|
5
5
|
import { convertDateFilterToCriteria, convertNumberFilterToCriteria, convertSetFilterToCriteria, convertTextFilterToCriteria, } from './filter.utils';
|
|
@@ -284,17 +284,20 @@ export class BaseServerSideDatasource {
|
|
|
284
284
|
const currentLastRowNumber = params.request.endRow;
|
|
285
285
|
this.calculatedRowsCount = this.clientRowsCount || currentLastRowNumber;
|
|
286
286
|
let rowCount;
|
|
287
|
+
// The cap GSF applies: a MAX_VIEW of 0 (what an empty or non-numeric `max-view` arrives as) is
|
|
288
|
+
// an unlimited view, not an empty one, and a missing one is the server default of 1000.
|
|
289
|
+
const viewCap = resolveMaxView(this.maxView);
|
|
287
290
|
// Infinite scroll: getRows() only resolves a block once the cumulative row cache covers
|
|
288
291
|
// its full extent or the stream reported no more data, so blocks are either full or final.
|
|
289
292
|
if (!this.moreRows) {
|
|
290
293
|
// Dataset ends here: rowData.size is the deduplicated loaded count. Deliberately not
|
|
291
294
|
// serverRowsCount (GSF's ROWS_COUNT ignores CRITERIA_MATCH) nor clientRowsCount
|
|
292
295
|
// (non-deduplicated running sum, overcounts on live streams).
|
|
293
|
-
rowCount = Math.min(this.rowData.size,
|
|
296
|
+
rowCount = Math.min(this.rowData.size, viewCap);
|
|
294
297
|
}
|
|
295
|
-
else if (this.rowData.size >=
|
|
298
|
+
else if (this.rowData.size >= viewCap) {
|
|
296
299
|
// Hard cap - the view can't grow past maxView.
|
|
297
|
-
rowCount =
|
|
300
|
+
rowCount = viewCap;
|
|
298
301
|
}
|
|
299
302
|
else {
|
|
300
303
|
// Full block, more may follow: leave undefined so AG Grid keeps requesting blocks
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { __awaiter } from "tslib";
|
|
2
|
-
import { MessageType, logger, } from '@genesislcap/foundation-comms';
|
|
2
|
+
import { dataserverPagingStatus, describeDataserverPagingStop, MessageType, logger, } from '@genesislcap/foundation-comms';
|
|
3
3
|
import { DOM } from '@microsoft/fast-element';
|
|
4
4
|
import { getAvailableIndexes, getAvailableIndexFields } from '../utils';
|
|
5
|
-
import { filterDataserverResult } from './dataserver-result.filter';
|
|
5
|
+
import { cachedRowKeyResolver, filterDataserverResult } from './dataserver-result.filter';
|
|
6
6
|
import { BaseServerSideDatasource } from './server-side.resource-base';
|
|
7
7
|
/**
|
|
8
8
|
* The DATASERVER IServerSideDatasource implementation, used for SSRM (Server-Side Row Model) in the grid.
|
|
@@ -69,6 +69,8 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
69
69
|
this.structuralPushPending = false;
|
|
70
70
|
this.streamStarted = false;
|
|
71
71
|
this.moreRowsInFlight = false;
|
|
72
|
+
this.pagingStopped = undefined;
|
|
73
|
+
this.reportedRowsCount = undefined;
|
|
72
74
|
yield _super.refreshDatasource.call(this, params);
|
|
73
75
|
});
|
|
74
76
|
}
|
|
@@ -99,9 +101,10 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
99
101
|
? Number(params.request.endRow)
|
|
100
102
|
: blockStartRow + Number(this.maxRows);
|
|
101
103
|
// Infinite scroll keeps a session-cumulative row cache (see handleCurrentStreamLoad);
|
|
102
|
-
// when it already covers this block, or the stream ended, no further
|
|
103
|
-
// will arrive for this request - resolve it synchronously from the cache.
|
|
104
|
-
const blockAlreadyCovered = this.currentSequenceId >= 1 &&
|
|
104
|
+
// when it already covers this block, or the stream ended or the view filled, no further
|
|
105
|
+
// stream messages will arrive for this request - resolve it synchronously from the cache.
|
|
106
|
+
const blockAlreadyCovered = this.currentSequenceId >= 1 &&
|
|
107
|
+
(this.rowData.size >= blockEndRow || !this.moreRows || this.pagingStopped !== undefined);
|
|
105
108
|
// A re-read is the moment the grid asks for current rows. When the cache holds a change the
|
|
106
109
|
// grid never saw (a DELETE it was not told about), serving the slice would hand back the
|
|
107
110
|
// deleted row, so refresh instead: the grid drops every block and re-reads from the server.
|
|
@@ -205,7 +208,7 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
205
208
|
this.completeEmptyResult(params);
|
|
206
209
|
return;
|
|
207
210
|
}
|
|
208
|
-
if (this.rowData.size >= blockEndRow || !this.moreRows) {
|
|
211
|
+
if (this.rowData.size >= blockEndRow || !this.moreRows || this.pagingStopped !== undefined) {
|
|
209
212
|
DOM.queueUpdate(() => resolveBlock(safetyTimer));
|
|
210
213
|
}
|
|
211
214
|
};
|
|
@@ -236,7 +239,7 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
236
239
|
* @internal
|
|
237
240
|
*/
|
|
238
241
|
dispatchStreamMessage(dataserverResult) {
|
|
239
|
-
var _a, _b, _c, _d, _e;
|
|
242
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
240
243
|
const messageType = dataserverResult === null || dataserverResult === void 0 ? void 0 : dataserverResult.MESSAGE_TYPE;
|
|
241
244
|
if (messageType &&
|
|
242
245
|
(messageType === MessageType.LOGOFF_ACK || messageType === MessageType.MSG_NACK)) {
|
|
@@ -245,8 +248,12 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
245
248
|
}
|
|
246
249
|
return;
|
|
247
250
|
}
|
|
251
|
+
const answersMoreRows = this.moreRowsInFlight;
|
|
248
252
|
if ((dataserverResult === null || dataserverResult === void 0 ? void 0 : dataserverResult.MORE_ROWS) !== undefined) {
|
|
249
253
|
this.moreRows = dataserverResult.MORE_ROWS;
|
|
254
|
+
// Only replies carry MORE_ROWS, and an empty one carries no ROW either: it still answers
|
|
255
|
+
// the request, so a later push must not be taken for the page.
|
|
256
|
+
this.moreRowsInFlight = false;
|
|
250
257
|
}
|
|
251
258
|
if (dataserverResult === null || dataserverResult === void 0 ? void 0 : dataserverResult.ROW) {
|
|
252
259
|
const nextMessage = filterDataserverResult(dataserverResult, this.rowId);
|
|
@@ -262,7 +269,7 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
262
269
|
// prepended it to the cache and shifted every later block's slice against the grid.
|
|
263
270
|
const carriesPushedChanges = ((_b = (_a = nextMessage.updates) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 || ((_d = (_c = nextMessage.deletes) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0;
|
|
264
271
|
const solicited = !carriesPushedChanges &&
|
|
265
|
-
(
|
|
272
|
+
(answersMoreRows || this.pendingBlocks.size > 0 || !this.streamStarted);
|
|
266
273
|
this.moreRowsInFlight = false;
|
|
267
274
|
this.streamStarted = true;
|
|
268
275
|
this.handleCurrentStreamLoad(nextMessage, solicited);
|
|
@@ -272,12 +279,72 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
272
279
|
if (this.serverRowsCount === 0) {
|
|
273
280
|
this.serverRowsCount = (_e = dataserverResult.ROWS_COUNT) !== null && _e !== void 0 ? _e : this.rowData.size;
|
|
274
281
|
}
|
|
282
|
+
if (typeof dataserverResult.ROWS_COUNT === 'number') {
|
|
283
|
+
this.reportedRowsCount = dataserverResult.ROWS_COUNT;
|
|
284
|
+
}
|
|
275
285
|
}
|
|
276
286
|
}
|
|
287
|
+
// Only when paging off the server's cursor: under VIEW_NUMBER pagination GSF sizes the view to
|
|
288
|
+
// a single page, which never fills.
|
|
289
|
+
if (!(Number((_f = this.resourceParams) === null || _f === void 0 ? void 0 : _f.VIEW_NUMBER) > 0)) {
|
|
290
|
+
this.notePagingStatus(answersMoreRows && !((_g = dataserverResult === null || dataserverResult === void 0 ? void 0 : dataserverResult.ROW) === null || _g === void 0 ? void 0 : _g.length) && (dataserverResult === null || dataserverResult === void 0 ? void 0 : dataserverResult.MORE_ROWS) === true);
|
|
291
|
+
}
|
|
277
292
|
for (const block of this.pendingBlocks) {
|
|
278
293
|
block.handle(dataserverResult);
|
|
279
294
|
}
|
|
280
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Rows the server's view holds, as far as the cache knows: deleted rows kept as tombstones
|
|
298
|
+
* below the delivered watermark have already left it.
|
|
299
|
+
* @internal
|
|
300
|
+
*/
|
|
301
|
+
get viewRowsHeld() {
|
|
302
|
+
return this.rowData.size - this.retainedDeletedRowIds.size;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Stops paging when the view is full at MAX_VIEW (checked by row count, which saves asking for
|
|
306
|
+
* the empty page) or a MORE_ROWS came back empty, says why, and pages again once a full view
|
|
307
|
+
* shrinks.
|
|
308
|
+
* @param emptyReply - Whether this message answered a MORE_ROWS with no rows.
|
|
309
|
+
* @internal
|
|
310
|
+
*/
|
|
311
|
+
notePagingStatus(emptyReply) {
|
|
312
|
+
const status = dataserverPagingStatus({
|
|
313
|
+
moreRows: this.moreRows,
|
|
314
|
+
emptyReply,
|
|
315
|
+
rowsHeld: this.viewRowsHeld,
|
|
316
|
+
maxView: this.maxView,
|
|
317
|
+
});
|
|
318
|
+
if (status === 'more' && this.pagingStopped === 'view-full') {
|
|
319
|
+
this.pagingStopped = undefined;
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if ((status !== 'view-full' && status !== 'stalled') || this.pagingStopped) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
this.pagingStopped = status;
|
|
326
|
+
const warning = describeDataserverPagingStop({
|
|
327
|
+
status,
|
|
328
|
+
resourceName: this.resourceName,
|
|
329
|
+
rowsHeld: this.viewRowsHeld,
|
|
330
|
+
maxView: this.maxView,
|
|
331
|
+
emptyReply,
|
|
332
|
+
// ROWS_COUNT ignores CRITERIA_MATCH, so it is no total for a filtered view.
|
|
333
|
+
totalRows: this.getResourceParam('CRITERIA_MATCH') ? undefined : this.reportedRowsCount,
|
|
334
|
+
attribute: 'max-view',
|
|
335
|
+
});
|
|
336
|
+
if (warning) {
|
|
337
|
+
logger.warn(warning);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Once paging stops the dataset ends where the cache does, so the grid shows no rows past it.
|
|
342
|
+
* @internal
|
|
343
|
+
*/
|
|
344
|
+
getCorrectRowCount(params) {
|
|
345
|
+
const rowCount = super.getCorrectRowCount(params);
|
|
346
|
+
return this.pagingStopped ? this.rowData.size : rowCount;
|
|
347
|
+
}
|
|
281
348
|
/**
|
|
282
349
|
* Dataserver-specific sorting setup that validates columns are part of an index.
|
|
283
350
|
* This is required because dataserver needs indexed columns for efficient sorting.
|
|
@@ -354,6 +421,8 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
354
421
|
this.structuralPushPending = false;
|
|
355
422
|
this.streamStarted = false;
|
|
356
423
|
this.moreRowsInFlight = false;
|
|
424
|
+
this.pagingStopped = undefined;
|
|
425
|
+
this.reportedRowsCount = undefined;
|
|
357
426
|
yield _super.destroy.call(this);
|
|
358
427
|
});
|
|
359
428
|
}
|
|
@@ -382,18 +451,21 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
382
451
|
this.structuralPushPending = true;
|
|
383
452
|
}
|
|
384
453
|
});
|
|
454
|
+
// Under a custom row-id a pushed row is identified only by its ROW_REF.
|
|
455
|
+
const resolveKey = cachedRowKeyResolver(rows, this.rowId);
|
|
385
456
|
// Live QUERY_UPDATE messages can arrive while a block is pending: MODIFY rows may be
|
|
386
|
-
// partial, so merge into the cached row instead of clobbering it.
|
|
457
|
+
// partial, so merge into the cached row instead of clobbering it. A row resolved through its
|
|
458
|
+
// ROW_REF carries the custom id as undefined; keep the cached one.
|
|
387
459
|
(_b = result.updates) === null || _b === void 0 ? void 0 : _b.forEach((updateData) => {
|
|
388
|
-
const key = updateData
|
|
460
|
+
const key = resolveKey(updateData);
|
|
389
461
|
if (key === undefined) {
|
|
390
462
|
return;
|
|
391
463
|
}
|
|
392
464
|
const existing = rows.get(key);
|
|
393
|
-
rows.set(key, existing ? Object.assign(Object.assign({}, existing), updateData) : updateData);
|
|
465
|
+
rows.set(key, existing ? Object.assign(Object.assign(Object.assign({}, existing), updateData), { [this.rowId]: key }) : updateData);
|
|
394
466
|
});
|
|
395
467
|
(_c = result.deletes) === null || _c === void 0 ? void 0 : _c.forEach((deleteData) => {
|
|
396
|
-
const key = deleteData
|
|
468
|
+
const key = resolveKey(deleteData);
|
|
397
469
|
if (key === undefined || !rows.has(key)) {
|
|
398
470
|
return;
|
|
399
471
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { __awaiter, __decorate } from "tslib";
|
|
2
|
-
import { MessageType, ResourceType, dataServerResultFilter, normaliseCriteria, } from '@genesislcap/foundation-comms';
|
|
2
|
+
import { MessageType, ResourceType, dataServerResultFilter, dataserverPagingStatus, describeDataserverPagingStop, normaliseCriteria, } from '@genesislcap/foundation-comms';
|
|
3
3
|
import { LifecycleMixin } from '@genesislcap/foundation-utils';
|
|
4
4
|
import { DOM, attr, customElement } from '@microsoft/fast-element';
|
|
5
5
|
import { BehaviorSubject, EMPTY } from 'rxjs';
|
|
@@ -60,6 +60,8 @@ let GridProGenesisDatasource = class GridProGenesisDatasource extends LifecycleM
|
|
|
60
60
|
this.requiresFullRowDataAndColDefs = true;
|
|
61
61
|
this._lastMoreRows = false;
|
|
62
62
|
this._isMoreRowsResult = false;
|
|
63
|
+
/** How many MORE_ROWS sent by loadMore() are awaiting their reply. @internal */
|
|
64
|
+
this._pendingLoadMore = 0;
|
|
63
65
|
this._nextOffset = 0;
|
|
64
66
|
this._loadMoreCount = 0;
|
|
65
67
|
this.onDatasourceReady = () => {
|
|
@@ -195,6 +197,9 @@ let GridProGenesisDatasource = class GridProGenesisDatasource extends LifecycleM
|
|
|
195
197
|
this.clearRowData();
|
|
196
198
|
this._lastMoreRows = false;
|
|
197
199
|
this._isMoreRowsResult = false;
|
|
200
|
+
this._pendingLoadMore = 0;
|
|
201
|
+
this._serverMoreRows = undefined;
|
|
202
|
+
this._pagingStopped = undefined;
|
|
198
203
|
this._nextOffset = 0;
|
|
199
204
|
this._loadMoreCount = 0;
|
|
200
205
|
this.$emit(datasourceEventNames.destroy);
|
|
@@ -334,6 +339,9 @@ let GridProGenesisDatasource = class GridProGenesisDatasource extends LifecycleM
|
|
|
334
339
|
// Reset load more tracking on reload
|
|
335
340
|
this._nextOffset = 0;
|
|
336
341
|
this._loadMoreCount = 0;
|
|
342
|
+
this._pendingLoadMore = 0;
|
|
343
|
+
this._serverMoreRows = undefined;
|
|
344
|
+
this._pagingStopped = undefined;
|
|
337
345
|
this.datasource.updateLoadMoreCount(0);
|
|
338
346
|
this.datasource.stream = undefined;
|
|
339
347
|
this.clearRowData(withoutColumnDefs);
|
|
@@ -401,24 +409,22 @@ let GridProGenesisDatasource = class GridProGenesisDatasource extends LifecycleM
|
|
|
401
409
|
return;
|
|
402
410
|
}
|
|
403
411
|
this.dataSubWasLoggedOff = false;
|
|
404
|
-
|
|
405
|
-
this.updateLoadMoreStatus(result.MORE_ROWS, result.SOURCE_REF);
|
|
406
|
-
}
|
|
412
|
+
const answersLoadMore = hasMoreRowsInResult && this.takeLoadMoreReply();
|
|
407
413
|
if (result.ROW) {
|
|
408
|
-
|
|
409
|
-
this.handleStreamResult(nextMessage);
|
|
410
|
-
if (hasMoreRowsInResult && result.MORE_ROWS !== this._lastMoreRows) {
|
|
411
|
-
const moreRowsDetail = {
|
|
412
|
-
moreRows: result.MORE_ROWS,
|
|
413
|
-
sourceRef: result.SOURCE_REF,
|
|
414
|
-
};
|
|
415
|
-
this.$emit(gridProGenesisDatasourceEventNames.moreRowsChanged, moreRowsDetail);
|
|
416
|
-
this._lastMoreRows = result.MORE_ROWS;
|
|
417
|
-
}
|
|
414
|
+
this.handleStreamResult(dataServerResultFilter(result));
|
|
418
415
|
}
|
|
419
416
|
else {
|
|
420
417
|
this.handleStreamResult(result);
|
|
421
418
|
}
|
|
419
|
+
// Read after the rows are applied, so a push that fills or shrinks the view counts.
|
|
420
|
+
const moreRows = this.notePagingStatus(result, hasMoreRowsInResult, answersLoadMore);
|
|
421
|
+
if (moreRows !== undefined && (hasMoreRowsInResult || moreRows !== this._lastMoreRows)) {
|
|
422
|
+
this.updateLoadMoreStatus(moreRows, result.SOURCE_REF);
|
|
423
|
+
// REQUEST_SERVER pages by OFFSET and has never fired more-rows-changed.
|
|
424
|
+
if (!this.isRequestServer) {
|
|
425
|
+
this.emitMoreRowsChanged(moreRows, result.SOURCE_REF);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
422
428
|
});
|
|
423
429
|
}
|
|
424
430
|
catch (error) {
|
|
@@ -453,6 +459,75 @@ let GridProGenesisDatasource = class GridProGenesisDatasource extends LifecycleM
|
|
|
453
459
|
this.applyDataserverData(result);
|
|
454
460
|
}
|
|
455
461
|
}
|
|
462
|
+
/**
|
|
463
|
+
* Counts off one reply to an outstanding loadMore().
|
|
464
|
+
* @returns Whether a loadMore() was awaiting this reply.
|
|
465
|
+
* @internal
|
|
466
|
+
*/
|
|
467
|
+
takeLoadMoreReply() {
|
|
468
|
+
const answersLoadMore = this._pendingLoadMore > 0;
|
|
469
|
+
this._pendingLoadMore = Math.max(0, this._pendingLoadMore - 1);
|
|
470
|
+
return answersLoadMore;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Works out whether more rows can be loaded after a stream message.
|
|
474
|
+
* @remarks On DATASERVER, Load More turns off once the view is full at MAX_VIEW, as every
|
|
475
|
+
* further MORE_ROWS would come back empty (see `dataserverPagingStatus`), or after a MORE_ROWS
|
|
476
|
+
* came back empty while the view had room. It turns back on when a full view shrinks, since GSF
|
|
477
|
+
* fills the freed room on the next MORE_ROWS. A moving view never fills, and under VIEW_NUMBER
|
|
478
|
+
* pagination the view is a single page, so both follow MORE_ROWS alone, as REQUEST_SERVER does.
|
|
479
|
+
* @returns Whether more rows can be loaded, or `undefined` before the server has said.
|
|
480
|
+
* @internal
|
|
481
|
+
*/
|
|
482
|
+
notePagingStatus(result, hasMoreRowsInResult, answersLoadMore) {
|
|
483
|
+
var _a;
|
|
484
|
+
if (hasMoreRowsInResult) {
|
|
485
|
+
this._serverMoreRows = result.MORE_ROWS;
|
|
486
|
+
}
|
|
487
|
+
if (this._serverMoreRows === undefined) {
|
|
488
|
+
return undefined;
|
|
489
|
+
}
|
|
490
|
+
if (this.isRequestServer || Number(this.viewNumber) > 0) {
|
|
491
|
+
return hasMoreRowsInResult ? this._serverMoreRows : undefined;
|
|
492
|
+
}
|
|
493
|
+
// The MAX_VIEW the logon actually sent.
|
|
494
|
+
const maxView = +this.maxView;
|
|
495
|
+
const emptyReply = answersLoadMore && result.MORE_ROWS === true && !((_a = result.ROW) === null || _a === void 0 ? void 0 : _a.length);
|
|
496
|
+
const status = dataserverPagingStatus({
|
|
497
|
+
moreRows: this._serverMoreRows,
|
|
498
|
+
emptyReply,
|
|
499
|
+
rowsHeld: this.rowData.size,
|
|
500
|
+
maxView,
|
|
501
|
+
movingView: this.movingView,
|
|
502
|
+
});
|
|
503
|
+
if (status === 'more' && this._pagingStopped === 'view-full') {
|
|
504
|
+
this._pagingStopped = undefined;
|
|
505
|
+
}
|
|
506
|
+
else if ((status === 'view-full' || status === 'stalled') && !this._pagingStopped) {
|
|
507
|
+
this._pagingStopped = status;
|
|
508
|
+
const warning = describeDataserverPagingStop({
|
|
509
|
+
status,
|
|
510
|
+
resourceName: this.resourceName,
|
|
511
|
+
rowsHeld: this.rowData.size,
|
|
512
|
+
maxView,
|
|
513
|
+
emptyReply,
|
|
514
|
+
attribute: 'max-view',
|
|
515
|
+
});
|
|
516
|
+
if (warning) {
|
|
517
|
+
logger.warn(warning);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return status === 'more' && !this._pagingStopped;
|
|
521
|
+
}
|
|
522
|
+
/** Emits `more-rows-changed` when the availability of more rows changes. @internal */
|
|
523
|
+
emitMoreRowsChanged(moreRows, sourceRef) {
|
|
524
|
+
if (moreRows === this._lastMoreRows) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
const moreRowsDetail = { moreRows, sourceRef };
|
|
528
|
+
this.$emit(gridProGenesisDatasourceEventNames.moreRowsChanged, moreRowsDetail);
|
|
529
|
+
this._lastMoreRows = moreRows;
|
|
530
|
+
}
|
|
456
531
|
/**
|
|
457
532
|
* Updates the status bar based on more rows status
|
|
458
533
|
* @param moreRows - Whether more rows are available
|
|
@@ -661,6 +736,11 @@ let GridProGenesisDatasource = class GridProGenesisDatasource extends LifecycleM
|
|
|
661
736
|
loadMore() {
|
|
662
737
|
switch (this.datasource.resourceType) {
|
|
663
738
|
case ResourceType.DATASERVER:
|
|
739
|
+
// Every further MORE_ROWS comes back empty, and on GSF 8.15 still skips rows.
|
|
740
|
+
if (this._pagingStopped) {
|
|
741
|
+
logger.debug(`loadMore() ignored: paging ${this.resourceName} stopped (${this._pagingStopped})`);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
664
744
|
let viewNumber;
|
|
665
745
|
if (this.viewNumber) {
|
|
666
746
|
this._loadMoreCount = +this.viewNumber + 1;
|
|
@@ -669,7 +749,12 @@ let GridProGenesisDatasource = class GridProGenesisDatasource extends LifecycleM
|
|
|
669
749
|
else {
|
|
670
750
|
this._loadMoreCount += 1;
|
|
671
751
|
}
|
|
672
|
-
this.
|
|
752
|
+
this._pendingLoadMore += 1;
|
|
753
|
+
Promise.resolve(this.connect.getMoreRows(this._sourceRef, viewNumber)).catch((error) => {
|
|
754
|
+
// No reply is coming, so the next message carrying MORE_ROWS does not answer this one.
|
|
755
|
+
this._pendingLoadMore = Math.max(0, this._pendingLoadMore - 1);
|
|
756
|
+
logger.error(`Failed to load more rows for ${this.resourceName}`, error);
|
|
757
|
+
});
|
|
673
758
|
break;
|
|
674
759
|
case ResourceType.REQUEST_SERVER:
|
|
675
760
|
this._loadMoreCount += 1;
|