LP1913458 Bucket Add/Delete Item Operations Batched
authorBill Erickson <berickxx@gmail.com>
Wed, 27 Jan 2021 16:55:56 +0000 (11:55 -0500)
committerJane Sandberg <sandbej@linnbenton.edu>
Fri, 26 Feb 2021 02:25:48 +0000 (18:25 -0800)
In the record bucket administration UI, the act of adding or removing
items from a bucket are now done in batch to avoid too many parallel
requests.

These changes include new batch create/delete API calls that can operate
on any bucket type, so other UI's can be similarly batched as needed.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jeff Davis <jdavis@sitka.bclibraries.ca>
Signed-off-by: Jane Sandberg <sandbej@linnbenton.edu>

Open-ILS/src/perlmods/lib/OpenILS/Application/Actor/Container.pm
Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js

index abeeea2..e468a0b 100644 (file)
@@ -396,6 +396,121 @@ sub item_create {
     return $item->id; 
 }
 
+__PACKAGE__->register_method(
+    method  => 'batch_add_items',
+    api_name    => 'open-ils.actor.container.item.create.batch',
+    stream      => 1,
+    max_bundle_count => 1,
+    signature => {
+        desc => 'Add items to a bucket',
+        params => [
+            {desc => 'Auth token', type => 'string'},
+            {desc => q/
+                Container class.  
+                Can be "copy", "call_number", "biblio_record_entry", or "user"'/,
+                type => 'string'},
+            {desc => 'Bucket ID', type => 'number'},
+            {desc => q/
+                Item target identifiers.  E.g. for record buckets,
+                the identifier would be the bib record id/, 
+                type => 'array'
+            },
+        ],
+        return => {
+            desc => 'Stream of new item Identifiers',
+            type => 'number'
+        }
+    }
+);
+
+sub batch_add_items {
+    my ($self, $client, $auth, $bucket_class, $bucket_id, $target_ids) = @_;
+
+    my $e = new_editor(authtoken => $auth, xact => 1);
+    return $e->die_event unless $e->checkauth;
+
+    my $constructor = "Fieldmapper::container::${bucket_class}_bucket_item";
+    my $create = "create_container_${bucket_class}_bucket_item";
+    my $retrieve = "retrieve_container_${bucket_class}_bucket";
+    my $column = "target_${bucket_class}";
+
+    my $bucket = $e->$retrieve($bucket_id) or return $e->die_event;
+
+    if ($bucket->owner ne $e->requestor->id) {
+        return $e->die_event unless $e->allowed('CREATE_CONTAINER_ITEM');
+    }
+
+    for my $target_id (@$target_ids) {
+
+        my $item = $constructor->new;
+        $item->bucket($bucket_id);
+        $item->$column($target_id);
+
+        return $e->die_event unless $e->$create($item);
+        $client->respond($target_id);
+    }
+
+    $e->commit;
+    return undef;
+}
+
+__PACKAGE__->register_method(
+    method  => 'batch_delete_items',
+    api_name    => 'open-ils.actor.container.item.delete.batch',
+    stream      => 1,
+    max_bundle_count => 1,
+    signature => {
+        desc => 'Remove items from a bucket',
+        params => [
+            {desc => 'Auth token', type => 'string'},
+            {desc => q/
+                Container class.  
+                Can be "copy", "call_number", "biblio_record_entry", or "user"'/,
+                type => 'string'},
+            {desc => q/
+                Item target identifiers.  E.g. for record buckets,
+                the identifier would be the bib record id/, 
+                type => 'array'
+            }
+        ],
+        return => {
+            desc => 'Stream of new removed target IDs',
+            type => 'number'
+        }
+    }
+);
+
+sub batch_delete_items {
+    my ($self, $client, $auth, $bucket_class, $bucket_id, $target_ids) = @_;
+
+    my $e = new_editor(authtoken => $auth, xact => 1);
+    return $e->die_event unless $e->checkauth;
+
+    my $delete = "delete_container_${bucket_class}_bucket_item";
+    my $search = "search_container_${bucket_class}_bucket_item";
+    my $retrieve = "retrieve_container_${bucket_class}_bucket";
+    my $column = "target_${bucket_class}";
+
+    my $bucket = $e->$retrieve($bucket_id) or return $e->die_event;
+
+    if ($bucket->owner ne $e->requestor->id) {
+        return $e->die_event unless $e->allowed('DELETE_CONTAINER_ITEM');
+    }
+
+    for my $target_id (@$target_ids) {
+
+        my $item = $e->$search({bucket => $bucket_id, $column => $target_id})->[0];
+        next unless $item;
+
+        return $e->die_event unless $e->$delete($item);
+        $client->respond($target_id);
+    }
+
+    $e->commit;
+    return undef;
+}
+
+
 
 
 __PACKAGE__->register_method(
index cd9b932..0474429 100644 (file)
@@ -284,26 +284,26 @@ function($scope,  $location,  $q,  $timeout,  $uibModal,
         if (recs.length == 0) return;
         bucketSvc.bucketNeedsRefresh = true;
 
-        angular.forEach(recs,
-            function(rec) {
-                var item = new egCore.idl.cbrebi();
-                item.bucket(bucketSvc.currentBucket.id());
-                item.target_biblio_record_entry(rec.id);
-                egCore.net.request(
-                    'open-ils.actor',
-                    'open-ils.actor.container.item.create', 
-                    egCore.auth.token(), 'biblio', item
-                ).then(function(resp) {
-
-                    // HACK: add the IDs of the added items so that the size
-                    // of the view list will grow (and update any UI looking at
-                    // the list size).  The data stored is inconsistent, but since
-                    // we are forcing a bucket refresh on the next rendering of 
-                    // the view pane, the list will be repaired.
-                    bucketSvc.currentBucket.items().push(resp);
-                });
+        var ids = recs.map(function(rec) { return rec.id; });
+
+        egCore.net.request(
+            'open-ils.actor',
+            'open-ils.actor.container.item.create.batch',
+            egCore.auth.token(), 'biblio_record_entry', 
+            bucketSvc.currentBucket.id(), ids
+
+        ).then(
+            null, // complete
+            null, // error
+            function(resp) {
+                // HACK: add the IDs of the added items so that the size
+                // of the view list will grow (and update any UI looking at
+                // the list size).  The data stored is inconsistent, but since
+                // we are forcing a bucket refresh on the next rendering of 
+                // the view pane, the list will be repaired.
+                bucketSvc.currentBucket.items().push(resp);
             }
-        );
+        )
     }
 
     $scope.openCreateBucketDialog = function() {
@@ -769,19 +769,29 @@ function($scope,  $q , $routeParams,  bucketSvc,  egCore,  $window,
     }
 
     $scope.detachRecords = function(records) {
-        var promises = [];
-        angular.forEach(records, function(rec) {
-            var item = bucketSvc.currentBucket.items().filter(
-                function(i) {
-                    return (i.target_biblio_record_entry() == rec.id)
-                }
-            );
-            if (item.length)
-                promises.push(bucketSvc.detachRecord(item[0].id()));
-        });
-
         bucketSvc.bucketNeedsRefresh = true;
-        return $q.all(promises).then(drawBucket);
+
+        var ids = records.map(function(rec) { return rec.id; });
+
+        return egCore.net.request(
+            'open-ils.actor',
+            'open-ils.actor.container.item.delete.batch',
+            egCore.auth.token(), 'biblio_record_entry', 
+            bucketSvc.currentBucket.id(), ids
+
+        ).then(
+            null, // complete
+            null, // error
+            function(resp) {
+                // Remove the items as the API responds so the UI can show
+                // the count of items decreasing.
+                bucketSvc.currentBucket.items(
+                    bucketSvc.currentBucket.items().filter(function(item) {
+                        return item.target_biblio_record_entry() != resp;
+                    })
+                );
+            }
+        ).then(drawBucket);
     }
 
     $scope.moveToPending = function(records) {