LP1818288 WS Option to pre-fetch record holds
authorBill Erickson <berickxx@gmail.com>
Mon, 4 Mar 2019 16:12:33 +0000 (11:12 -0500)
committerDan Wells <dbw2@calvin.edu>
Thu, 18 Apr 2019 19:17:37 +0000 (15:17 -0400)
Adds a workstation setting allowing staff to decide whether to pre-fetch
all holds on the record detail holds tab, to perform sorting paging in
the client, or to leave the sorting/paging on the server.

Improves client-side sorting in the grid.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Dan Wells <dbw2@calvin.edu>

Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-checkbox.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html
Open-ILS/src/eg2/src/app/share/grid/grid.ts
Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.html
Open-ILS/src/eg2/src/app/staff/share/holds/grid.component.html
Open-ILS/src/eg2/src/app/staff/share/holds/grid.component.ts
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.data.catalog-holds-prefetch.sql [new file with mode: 0644]

index 8765917..7ee3019 100644 (file)
@@ -12,6 +12,10 @@ export class GridToolbarCheckboxComponent implements OnInit {
     // Note most input fields should match class fields for GridColumn
     @Input() label: string;
 
+    // Set the render time value.
+    // This does NOT fire the onChange handler.
+    @Input() initialValue: boolean;
+
     // This is an input instead of an Output because the handler is
     // passed off to the grid context for maintenance -- events
     // are not fired directly from this component.
@@ -32,6 +36,7 @@ export class GridToolbarCheckboxComponent implements OnInit {
         const cb = new GridToolbarCheckbox();
         cb.label = this.label;
         cb.onChange = this.onChange;
+        cb.isChecked = this.initialValue;
 
         this.grid.context.toolbarCheckboxes.push(cb);
     }
index 52c3ae1..5dd307f 100644 (file)
@@ -18,6 +18,7 @@
       <ng-container *ngFor="let cb of gridContext.toolbarCheckboxes">
         <label class="form-check-label">
           <input class="form-check-input" type="checkbox"
+            [(ngModel)]="cb.isChecked"
             (click)="cb.onChange.emit($event.target.checked)"/>
             {{cb.label}}
         </label>
index f7e681d..7c30438 100644 (file)
@@ -542,18 +542,34 @@ export class GridContext {
     sortLocalData() {
 
         const sortDefs = this.dataSource.sort.map(sort => {
+            const column = this.columnSet.getColByName(sort.name);
+
             const def = {
                 name: sort.name,
                 dir: sort.dir,
-                col: this.columnSet.getColByName(sort.name)
+                col: column
             };
 
             if (!def.col.comparator) {
-                def.col.comparator = (a, b) => {
-                    if (a < b) { return -1; }
-                    if (a > b) { return 1; }
-                    return 0;
-                };
+                switch (def.col.datatype) {
+                    case 'id':
+                    case 'money':
+                    case 'int':
+                        def.col.comparator = (a, b) => {
+                            a = Number(a);
+                            b = Number(b);
+                            if (a < b) { return -1; }
+                            if (a > b) { return 1; }
+                            return 0;
+                        };
+                        break;
+                    default:
+                        def.col.comparator = (a, b) => {
+                            if (a < b) { return -1; }
+                            if (a > b) { return 1; }
+                            return 0;
+                        };
+                }
             }
 
             return def;
@@ -574,8 +590,6 @@ export class GridContext {
                 const diff = sortDef.col.comparator(valueA, valueB);
                 if (diff === 0) { continue; }
 
-                console.log(valueA, valueB, diff);
-
                 return sortDef.dir === 'DESC' ? -diff : diff;
             }
 
@@ -927,6 +941,7 @@ export class GridToolbarButton {
 
 export class GridToolbarCheckbox {
     label: string;
+    isChecked: boolean;
     onChange: EventEmitter<boolean>;
 }
 
index ff34750..ad75118 100644 (file)
@@ -52,6 +52,7 @@
       <ngb-tab title="View Holds" i18n-title id="holds">
         <ng-template ngbTabContent>
           <eg-holds-grid [recordId]="recordId"
+            preFetchSetting="catalog.record.holds.prefetch"
             persistKey="cat.catalog.wide_holds"
             [defaultSort]="[{name:'request_time',dir:'asc'}]"
             [initialPickupLib]="currentSearchOrg()"></eg-holds-grid>
index 62d269b..db3d31b 100644 (file)
@@ -16,7 +16,7 @@
     </eg-hold-detail>
   </ng-container>
 
-  <ng-container *ngIf="mode == 'list'">
+  <ng-container *ngIf="mode == 'list' && initComplete()">
 
     <div class="row" *ngIf="!hidePickupLibFilter">
       <div class="col-lg-4">
     </div>
 
     <eg-grid #holdsGrid [dataSource]="gridDataSource" [sortable]="true"
+      [useLocalSort]="enablePreFetch"
       [multiSortable]="true" [persistKey]="persistKey"
       (onRowActivate)="showDetail($event)">
 
+      <eg-grid-toolbar-checkbox (onChange)="preFetchHolds($event)"
+        [initialValue]="enablePreFetch" i18n-label label="Pre-Fetch All Holds">
+      </eg-grid-toolbar-checkbox>
+
       <eg-grid-toolbar-action
         i18n-label label="Show Hold Details" i18n-group group="Hold"
         (onClick)="showDetails($event)"></eg-grid-toolbar-action>
@@ -74,7 +79,7 @@
         i18-group group="Hold" i18n-label label="Cancel Hold"
         (onClick)="showCancelDialog($event)"></eg-grid-toolbar-action>
 
-      <eg-grid-column i18n-label label="Hold ID" path='id' [index]="true">
+      <eg-grid-column i18n-label label="Hold ID" path='id' [index]="true" datatype="id">
       </eg-grid-column>
 
       <ng-template #barcodeTmpl let-hold="row">
           name='title' [cellTemplate]="titleTmpl"></eg-grid-column>
       <eg-grid-column i18n-label label="Author" path='author'
           [hidden]="true"></eg-grid-column>
-      <eg-grid-column i18n-label label="Potential Items" path='potentials'>
+      <eg-grid-column i18n-label label="Potential Items" path='potentials' datatype="int">
       </eg-grid-column>
       <eg-grid-column i18n-label label="Status" path='status_string'>
       </eg-grid-column>
       <eg-grid-column i18n-label label="Queue Position"
-          path='relative_queue_position' [hidden]="true"></eg-grid-column>
+          path='relative_queue_position' [hidden]="true" datatype="int"></eg-grid-column>
       <eg-grid-column path='usr_id' i18n-label label="User ID" [hidden]="true"></eg-grid-column>
       <eg-grid-column path='usr_usrname' i18n-label label="Username" [hidden]="true"></eg-grid-column>
 
index e0e894d..af27574 100644 (file)
@@ -5,6 +5,7 @@ import {NetService} from '@eg/core/net.service';
 import {OrgService} from '@eg/core/org.service';
 import {AuthService} from '@eg/core/auth.service';
 import {Pager} from '@eg/share/util/pager';
+import {ServerStoreService} from '@eg/core/server-store.service';
 import {GridDataSource} from '@eg/share/grid/grid';
 import {GridComponent} from '@eg/share/grid/grid.component';
 import {ProgressDialogComponent} from '@eg/share/dialog/progress.component';
@@ -33,6 +34,12 @@ export class HoldsGridComponent implements OnInit {
     // Grid persist key
     @Input() persistKey: string;
 
+    @Input() preFetchSetting: string;
+        // If set, all holds are fetched on grid load and sorting/paging all
+    // happens in the client.  If false, sorting and paging occur on
+    // the server.
+    enablePreFetch: boolean;
+
     // How to sort when no sort parameters have been applied
     // via grid controls.  This uses the eg-grid sort format:
     // [{name: fname, dir: 'asc'}, {name: fname2, dir: 'desc'}]
@@ -46,7 +53,6 @@ export class HoldsGridComponent implements OnInit {
     detailHold: any;
     editHolds: number[];
     transferTarget: number;
-    copyStatuses: {[id: string]: IdlObject};
 
     @ViewChild('holdsGrid') private holdsGrid: GridComponent;
     @ViewChild('progressDialog')
@@ -104,34 +110,59 @@ export class HoldsGridComponent implements OnInit {
     constructor(
         private net: NetService,
         private org: OrgService,
+        private store: ServerStoreService,
         private auth: AuthService
     ) {
         this.gridDataSource = new GridDataSource();
-        this.copyStatuses = {};
+        this.enablePreFetch = null;
     }
 
     ngOnInit() {
         this.initDone = true;
         this.pickupLib = this.org.get(this.initialPickupLib);
 
-        this.gridDataSource.getRows = (pager: Pager, sort: any[]) => {
+        if (this.preFetchSetting) {
 
-            if (this.defaultSort && sort.length === 0) {
-                // Only use initial sort if sorting has not been modified
-                // by the grid's own sort controls.
-                sort = this.defaultSort;
-            }
+                this.store.getItem(this.preFetchSetting).then(
+                    applied => this.enablePreFetch = Boolean(applied)
+                );
+
+        }
 
-            // sorting not currently supported
+        if (!this.defaultSort) {
+            this.defaultSort = [{name: 'request_time', dir: 'asc'}];
+        }
+
+        this.gridDataSource.getRows = (pager: Pager, sort: any[]) => {
+            sort = sort.length > 0 ? sort : this.defaultSort;
             return this.fetchHolds(pager, sort);
         };
     }
 
+    // Returns true after all data/settings/etc required to render the
+    // grid have been fetched.
+    initComplete(): boolean {
+        return this.enablePreFetch !== null;
+    }
+
     pickupLibChanged(org: IdlObject) {
         this.pickupLib = org;
         this.holdsGrid.reload();
     }
 
+    preFetchHolds(apply: boolean) {
+        this.enablePreFetch = apply;
+
+        if (apply) {
+            setTimeout(() => this.holdsGrid.reload());
+        }
+
+        if (this.preFetchSetting) {
+            // fire and forget
+            this.store.setItem(this.preFetchSetting, apply);
+        }
+    }
+
     applyFilters(): any {
         const filters: any = {
             is_staff_request: true,
@@ -167,11 +198,16 @@ export class HoldsGridComponent implements OnInit {
         const filters = this.applyFilters();
 
         const orderBy: any = [];
-        sort.forEach(obj => {
-            const subObj: any = {};
-            subObj[obj.name] = {dir: obj.dir, nulls: 'last'};
-            orderBy.push(subObj);
-        });
+        if (sort.length > 0) {
+            sort.forEach(obj => {
+                const subObj: any = {};
+                subObj[obj.name] = {dir: obj.dir, nulls: 'last'};
+                orderBy.push(subObj);
+            });
+        }
+
+        const limit = this.enablePreFetch ? null : pager.limit;
+        const offset = this.enablePreFetch ? 0 : pager.offset;
 
         let observer: Observer<any>;
         const observable = new Observable(obs => observer = obs);
@@ -183,10 +219,7 @@ export class HoldsGridComponent implements OnInit {
         this.net.request(
             'open-ils.circ',
             'open-ils.circ.hold.wide_hash.stream',
-            // Pre-fetch all holds consistent with AngJS version
-            this.auth.token(), filters, orderBy
-            // Alternatively, fetch holds in pages.
-            // this.auth.token(), filters, orderBy, pager.limit, pager.offset
+            this.auth.token(), filters, orderBy, limit, offset
         ).subscribe(
             holdData => {
 
index 184d80c..3c8a257 100644 (file)
@@ -19874,7 +19874,6 @@ INSERT INTO config.org_unit_setting_type
         'bool'
     );
 
-
 INSERT INTO config.usr_activity_type 
     (id, ewhat, ehow, egroup, enabled, transient, label)
 VALUES (
@@ -19897,3 +19896,13 @@ VALUES (
     oils_i18n_gettext(30, 'Generic Verify', 'cuat', 'label')
 );
 
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+    'catalog.record.holds.prefetch', 'cat', 'bool',
+    oils_i18n_gettext(
+        'catalog.record.holds.prefetch',
+        'Pre-Fetch Record Holds',
+        'cwst', 'label'
+    )
+);
+
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.catalog-holds-prefetch.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.catalog-holds-prefetch.sql
new file mode 100644 (file)
index 0000000..8de3ff2
--- /dev/null
@@ -0,0 +1,15 @@
+BEGIN;
+
+--SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+    'catalog.record.holds.prefetch', 'cat', 'bool',
+    oils_i18n_gettext(
+        'catalog.record.holds.prefetch',
+        'Pre-Fetch Record Holds',
+        'cwst', 'label'
+    )
+);
+
+COMMIT;