LP#1482400: when activating a PO, provide better progress updates
authorGalen Charlton <gmc@esilibrary.com>
Thu, 6 Aug 2015 22:15:54 +0000 (22:15 +0000)
committerMike Rylander <mrylander@gmail.com>
Tue, 18 Aug 2015 15:33:27 +0000 (11:33 -0400)
This patch improves how progress is reported during the activation
of a purchase order that has a large number of assets (bibs and
copies) to create.  Specifically, it:

[1] Shows a progress bar during the asset creation phase.
[2] Calculates a linear throttle for updating the record counts
    widget. The value is based on the maximum number of bibs and
    items that could be created during the activation, divided by 20
    (which is an empirically derived number of streaming responses
    that can be sent back before the XUL client starts having
    difficulty).
[3] During the asset-creation phase, the number of records created is
    displayed above the progress bar.
[4] Adds a title to the progress bar that's displayed during the
    second phase of order activation.

To test, after applying the patch:

[1] Create a purchase order that has a large number of
    brief bibs and copies to create during activation.  For
    example, 30 line items and 60 copies.
[2] Activate the purchase order.  You should see the following
    after entering the record import queue and submitting:

    (a) Progress dialog whose title is "Creating bib, call number,
        and copy records.."
    (b) The progress dialog displays and updates text above the
        progress bar indicating how many records it has processed,
        e.g., "Bib Records Merged/Import: 25"
    (b) The record update counts should be updated at a regular
        frequency.
    (c) When the asset creation phase is done, the progress bar's
        title should change to "Activating purchase order..."

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
Signed-off-by: Mike Rylander <mrylander@gmail.com>
Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm
Open-ILS/web/js/dojo/openils/acq/nls/acq.js
Open-ILS/web/js/dojo/openils/widget/ProgressDialog.js
Open-ILS/web/js/ui/default/acq/common/li_table.js
Open-ILS/web/js/ui/default/acq/po/view_po.js

index 4dec15d..69be3d7 100644 (file)
@@ -4,6 +4,10 @@ use OpenSRF::AppSession;
 use OpenSRF::EX qw/:try/;
 use strict; use warnings;
 
 use OpenSRF::EX qw/:try/;
 use strict; use warnings;
 
+# empirically derived number of responses we can
+# stream back before the XUL client has indigestion
+use constant MAX_RESPONSES => 20;
+
 sub new {
     my($class, %args) = @_;
     my $self = bless(\%args, $class);
 sub new {
     my($class, %args) = @_;
     my $self = bless(\%args, $class);
@@ -24,6 +28,7 @@ sub new {
     };
     $self->{cache} = {};
     $self->throttle(4) unless $self->throttle;
     };
     $self->{cache} = {};
     $self->throttle(4) unless $self->throttle;
+    $self->exponential_falloff(1) unless $self->exponential_falloff;
     $self->{post_proc_queue} = [];
     $self->{last_respond_progress} = 0;
     return $self;
     $self->{post_proc_queue} = [];
     $self->{last_respond_progress} = 0;
     return $self;
@@ -39,6 +44,11 @@ sub throttle {
     $self->{throttle} = $val if $val;
     return $self->{throttle};
 }
     $self->{throttle} = $val if $val;
     return $self->{throttle};
 }
+sub exponential_falloff {
+    my($self, $val) = @_;
+    $self->{exponential_falloff} = $val if defined $val;
+    return $self->{exponential_falloff};
+}
 sub respond {
     my($self, %other_args) = @_;
     if($self->throttle and not %other_args) {
 sub respond {
     my($self, %other_args) = @_;
     if($self->throttle and not %other_args) {
@@ -48,7 +58,7 @@ sub respond {
     }
     $self->conn->respond({ %{$self->{args}}, %other_args });
     $self->{last_respond_progress} = $self->{args}->{progress};
     }
     $self->conn->respond({ %{$self->{args}}, %other_args });
     $self->{last_respond_progress} = $self->{args}->{progress};
-    $self->throttle($self->throttle * 2) unless $self->throttle >= 256;
+    $self->throttle($self->throttle * 2) if ($self->exponential_falloff() and $self->throttle < 256);
 }
 sub respond_complete {
     my($self, %other_args) = @_;
 }
 sub respond_complete {
     my($self, %other_args) = @_;
@@ -74,6 +84,12 @@ sub total {
     my($self, $val) = @_;
     $self->{args}->{total} = $val if defined $val;
     $self->{args}->{maximum} = $self->{args}->{total};
     my($self, $val) = @_;
     $self->{args}->{total} = $val if defined $val;
     $self->{args}->{maximum} = $self->{args}->{total};
+    if ($self->{args}->{maximum}) {
+        # if a total has been set, space responses linearly
+        $self->exponential_falloff(0);
+        $self->throttle(int($self->{args}->{maximum} / MAX_RESPONSES));
+        $self->throttle(4) if $self->throttle < 4;
+    }
     return $self->{args}->{total};
 }
 sub purchase_order {
     return $self->{args}->{total};
 }
 sub purchase_order {
@@ -1762,7 +1778,9 @@ sub create_po_assets {
         where => {'+acqpo' => {id => $po_id}}
     })->[0]->{id};
 
         where => {'+acqpo' => {id => $po_id}}
     })->[0]->{id};
 
-    $mgr->total(scalar(@$li_ids) + $lid_total);
+    # maximum number of Vandelay bib actions is twice
+    # the number line items (queue bib, then create it)
+    $mgr->total(scalar(@$li_ids) * 2 + $lid_total);
 
     create_lineitem_list_assets($mgr, $li_ids, $args->{vandelay}) 
         or return $e->die_event;
 
     create_lineitem_list_assets($mgr, $li_ids, $args->{vandelay}) 
         or return $e->die_event;
index 53793ce..0248ad6 100644 (file)
     "DUPE_PO_NAME_MSG" : "This name is already in use by another PO",
     "DUPE_PO_NAME_LINK" : "View PO",
     "PO_NAME_OPTIONAL" : "${0} (optional)",
     "DUPE_PO_NAME_MSG" : "This name is already in use by another PO",
     "DUPE_PO_NAME_LINK" : "View PO",
     "PO_NAME_OPTIONAL" : "${0} (optional)",
-    "LI_EXISTING_COPIES" : "There are ${0} existing copies for this bibliographic record at this location"
+    "LI_EXISTING_COPIES" : "There are ${0} existing copies for this bibliographic record at this location",
+    "LI_CREATING_ASSETS" : "Creating bib, call number, and copy records...",
+    "PO_ACTIVATING" : "Activating purchase order...",
+    "ACTIVATE_LI_PROCESSED"             : "Lineitems Processed: ${0}",
+    "ACTIVATE_VQBR_PROCESSED"           : "Vandelay Records Processed: ${0}",
+    "ACTIVATE_BIBS_PROCESSED"           : "Bib Records Merged/Imported: ${0}",
+    "ACTIVATE_LID_PROCESSED"            : "ACQ Copies Processed: ${0}",
+    "ACTIVATE_DEBITS_ACCRUED_PROCESSED" : "Debits Encumbered: ${0}",
+    "ACTIVATE_COPIES_PROCESSED"         : "Real Copies Processed: ${0}"
 }
 }
index 7d2d43d..977d490 100644 (file)
@@ -48,6 +48,22 @@ if(!dojo._hasResource['openils.widget.ProgressDialog']) {
                 }
                     
                 this.inherited(arguments);
                 }
                     
                 this.inherited(arguments);
+            },
+
+            update_message : function(msg) {
+                if(msg || (msg = this.message) ) {
+                    if(!this.msgDiv) {
+                        this.msgDiv = dojo.create('div', {innerHTML : msg});
+                        this.containerNode.insertBefore(this.msgDiv, this.progress.domNode);
+                    } else {
+                        this.msgDiv.innerHTML = msg;
+                    }
+                } else {
+                    if(this.msgDiv) {
+                        this.containerNode.removeChild(this.msgDiv);
+                        this.msgDiv = null;
+                    }
+                }
             }
         }
     );
             }
         }
     );
index bf4e4e3..5d0a841 100644 (file)
@@ -95,6 +95,7 @@ function AcqLiTable() {
         }
     );
     this.vlAgent = new VLAgent();
         }
     );
     this.vlAgent = new VLAgent();
+    this.batchProgress = {};
 
     if (dojo.byId('acq-lit-apply-idents')) {
         dojo.byId('acq-lit-apply-idents').onclick = function() {
 
     if (dojo.byId('acq-lit-apply-idents')) {
         dojo.byId('acq-lit-apply-idents').onclick = function() {
@@ -2943,13 +2944,16 @@ function AcqLiTable() {
         this.show('acq-lit-progress-numbers');
         var self = this;
         var vlArgs = (noVl) ? {} : {vandelay : this.vlAgent.values()};
         this.show('acq-lit-progress-numbers');
         var self = this;
         var vlArgs = (noVl) ? {} : {vandelay : this.vlAgent.values()};
+        this.batchProgress = {};
+        progressDialog.show(false);
+        progressDialog.attr("title", localeStrings.LI_CREATING_ASSETS);
         fieldmapper.standardRequest(
             ['open-ils.acq', 'open-ils.acq.purchase_order.assets.create'],
             {   async: true,
                 params: [this.authtoken, this.isPO, vlArgs],
                 onresponse: function(r) {
                     var resp = openils.Util.readResponse(r);
         fieldmapper.standardRequest(
             ['open-ils.acq', 'open-ils.acq.purchase_order.assets.create'],
             {   async: true,
                 params: [this.authtoken, this.isPO, vlArgs],
                 onresponse: function(r) {
                     var resp = openils.Util.readResponse(r);
-                    self._updateProgressNumbers(resp, !Boolean(onAssetsCreated), onAssetsCreated);
+                    self._updateProgressNumbers(resp, !Boolean(onAssetsCreated), onAssetsCreated, true);
                 }
             }
         );
                 }
             }
         );
@@ -3249,9 +3253,16 @@ function AcqLiTable() {
         );
     };
 
         );
     };
 
-    this._updateProgressNumbers = function(resp, reloadOnComplete, onComplete) {
+    this._updateProgressNumbers = function(resp, reloadOnComplete, onComplete, clearProgressDialog) {
+        this._updateProgressDialog(resp);
         this.vlAgent.handleResponse(resp,
             function(resp, res) {
         this.vlAgent.handleResponse(resp,
             function(resp, res) {
+                if (clearProgressDialog) {
+                    progressDialog.update({ "progress": 100});
+                    progressDialog.update_message();
+                    progressDialog.hide();
+                    progressDialog.attr("title", "");
+                }
                 if(reloadOnComplete)
                      location.href = location.href;
                 if (onComplete)
                 if(reloadOnComplete)
                      location.href = location.href;
                 if (onComplete)
@@ -3260,6 +3271,21 @@ function AcqLiTable() {
         );
     }
 
         );
     }
 
+    this._updateProgressDialog = function(resp) {
+        progressDialog.update({ "progress": (resp.progress / resp.total) * 100 });
+        var keys = ['li', 'vqbr', 'bibs', 'lid', 'debits_accrued', 'copies'];
+        for (var i = 0; i < keys.length; i++) {
+            if (resp[keys[i]] > (this.batchProgress[keys[i]] || 0)) {
+                progressDialog.update_message(
+                    dojo.string.substitute(
+                        localeStrings["ACTIVATE_" + keys[i].toUpperCase() + "_PROCESSED"],
+                        [ resp[keys[i]] ]
+                    )
+                );
+            }
+        }
+        this.batchProgress = resp;
+    }
 
     this._createPO = function(fields) {
         var wantall = (fields.create_from == "all");
 
     this._createPO = function(fields) {
         var wantall = (fields.create_from == "all");
index dbe7705..9b76c08 100644 (file)
@@ -637,6 +637,7 @@ function activatePoStage2(noAssets) {
 
     var want_refresh = false;
     progressDialog.show(true);
 
     var want_refresh = false;
     progressDialog.show(true);
+    progressDialog.attr("title", localeStrings.PO_ACTIVATING);
     fieldmapper.standardRequest(
         ["open-ils.acq", "open-ils.acq.purchase_order.activate"], {
             "async": true,
     fieldmapper.standardRequest(
         ["open-ils.acq", "open-ils.acq.purchase_order.activate"], {
             "async": true,
@@ -650,11 +651,12 @@ function activatePoStage2(noAssets) {
                 }
             ],
             "onresponse": function(r) {
                 }
             ],
             "onresponse": function(r) {
-                progressDialog.hide();
                 activatePoButton.attr("disabled", false);
                 want_refresh = Boolean(openils.Util.readResponse(r));
             },
             "oncomplete": function() {
                 activatePoButton.attr("disabled", false);
                 want_refresh = Boolean(openils.Util.readResponse(r));
             },
             "oncomplete": function() {
+                progressDialog.hide();
+                progressDialog.attr("title", "");
                 if (want_refresh)
                     location.href = location.href;
             }
                 if (want_refresh)
                     location.href = location.href;
             }