LPXXX MARC Batch update Angular port
authorBill Erickson <berickxx@gmail.com>
Thu, 21 May 2020 21:10:13 +0000 (17:10 -0400)
committerBill Erickson <berickxx@gmail.com>
Thu, 21 May 2020 21:10:13 +0000 (17:10 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/cat/marcbatch/marcbatch.component.html
Open-ILS/src/eg2/src/app/staff/cat/marcbatch/marcbatch.component.ts
Open-ILS/src/eg2/src/app/staff/cat/marcbatch/marcbatch.module.ts
Open-ILS/src/eg2/src/app/staff/cat/marcbatch/routing.module.ts
Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/TemplateBatchBibUpdate.pm

index 1ea34b0..aeaf002 100644 (file)
       <div class="col-lg-3" i18n>Record Source: </div>
       <div class="col-lg-6">
         <select class="form-control" [(ngModel)]="source">
-          <option value='bucket' i18n>Bucket</option>
-          <option value='csv' i18n>CSV File</option>
-          <option value='id' i18n>Bib Record ID</option>
+          <option value='b' i18n>Bucket</option>
+          <option value='c' i18n>CSV File</option>
+          <option value='r' i18n>Bib Record ID</option>
         </select>
       </div>
     </div>
     <div class="row mt-2 pt-2 pb-2 border">
-      <ng-container *ngIf="source == 'bucket'">
+      <ng-container *ngIf="source == 'b'">
         <div class="col-lg-3" i18n>Bucket named: </div>
         <div class="col-lg-6">
           <eg-combobox [entries]="buckets" (onChange)="bucketChanged($event)">
           </eg-combobox>
         </div>
       </ng-container>
-      <ng-container *ngIf="source == 'id'">
+      <ng-container *ngIf="source == 'r'">
         <div class="col-lg-3" i18n>Record ID: </div>
         <div class="col-lg-3">
           <input type="text" class="form-control" [(ngModel)]="recordId"/>
         </div>
       </ng-container>
-      <ng-container *ngIf="source == 'csv'">
+      <ng-container *ngIf="source == 'c'">
         <div class="col-lg-12">
           <div class="row">
             <div class="col-lg-3" i18n>Column: </div>
     </div>
     <div class="row mt-2 pt-2 pb-2 border">
       <div class="col-lg-12">
-        <button class="btn btn-outline-dark" (click)="process()" i18n>Go!</button>
+        <button class="btn btn-outline-dark" 
+          [disabled]="disableSave()" (click)="process()" i18n>Go!</button>
       </div>
     </div>
     <div class="row mt-2 p-2" *ngIf="processing">
         </eg-progress-inline>
       </div>
     </div>
+    <div class="row mt-2 p-2" *ngIf="!processing && progressMax">
+      <div class="col-lg-10 offset-lg-1">
+        <div i18n>Processing Complete</div>
+        <div i18n>Success count: {{this.numSucceeded}}</div>
+        <div i18n>Failed count: {{this.numFailed}}</div>
+      </div>
+    </div>
   </div>
 </div>
index 1b37b70..478aa2f 100644 (file)
@@ -1,11 +1,16 @@
 import {Component, OnInit, ViewChild, Renderer2} from '@angular/core';
 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {HttpClient} from '@angular/common/http';
 import {tap} from 'rxjs/operators';
 import {NetService} from '@eg/core/net.service';
 import {AuthService} from '@eg/core/auth.service';
 import {PcrudService} from '@eg/core/pcrud.service';
 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
 import {MarcRecord, MarcField} from '@eg/staff/share/marc-edit/marcrecord';
+import {AnonCacheService} from '@eg/share/util/anon-cache.service';
+
+const MERGE_TEMPLATE_PATH = '/opac/extras/merge_template';
+const SESSION_POLL_INTERVAL = 2; // seconds
 
 interface TemplateRule {
     ruleType: 'r' | 'a' | 'd';
@@ -22,12 +27,12 @@ interface TemplateRule {
 export class MarcBatchComponent implements OnInit {
 
     session: string;
-    source: 'bucket' | 'csv' | 'id' = 'bucket';
+    source: 'b' | 'c' | 'r' = 'b';
     buckets: ComboboxEntry[];
     bucket: number;
     recordId: number;
     csvColumn = 0;
-    selectedFile: File;
+    csvFile: File;
     xactPerRecord = false;
     templateRules: TemplateRule[] = [];
     record: MarcRecord;
@@ -35,21 +40,21 @@ export class MarcBatchComponent implements OnInit {
     processing = false;
     progressMax: number = null;
     progressValue: number = null;
+    numSucceeded = 0;
+    numFailed = 0;
 
     constructor(
         private router: Router,
         private route: ActivatedRoute,
+        private http: HttpClient,
         private renderer: Renderer2,
         private net: NetService,
         private pcrud: PcrudService,
-        private auth: AuthService
+        private auth: AuthService,
+        private cache: AnonCacheService
     ) {}
 
     ngOnInit() {
-        this.route.paramMap.subscribe((params: ParamMap) => {
-            this.session = this.route.snapshot.paramMap.get('session');
-        });
-
         this.load();
     }
 
@@ -160,23 +165,90 @@ export class MarcBatchComponent implements OnInit {
     }
 
     fileSelected($event) {
-       this.selectedFile = $event.target.files[0];
+       this.csvFile = $event.target.files[0];
+    }
+
+    disableSave(): boolean {
+        if (!this.record || !this.source || this.processing) {
+            return true;
+        }
+
+        if (!this.processing && this.progressMax) {
+            // Just completed a session.
+            return true;
+        }
+
+        if (this.source === 'b') {
+            return !this.bucket;
+
+        } else if (this.source === 'c') {
+            return (!this.csvColumn || !this.csvFile);
+
+        } else if (this.source === 'r') {
+            return !this.recordId;
+        }
     }
 
 
     process() {
         this.processing = true;
-        this.postForm()
-        .then(_ => this.pollProgress())
-        .then(_ => this.processing = false);
+        this.progressValue = null;
+        this.progressMax = null;
+        this.numSucceeded = 0;
+        this.numFailed = 0;
+
+        this.postForm().then(_ => this.pollProgress());
     }
 
     postForm(): Promise<any> {
-        return Promise.resolve();
+
+        const formData: FormData = new FormData();
+        formData.append('ses', this.auth.token());
+        formData.append('skipui', '1');
+        formData.append('template', this.record.toXml());
+        formData.append('recordSource', this.source);
+
+        if (this.source === 'b') {
+            formData.append('containerid', this.bucket + '');
+
+        } else if (this.source === 'c') {
+            formData.append('idcolumn', this.csvColumn + '');
+            formData.append('idfile', this.csvFile, this.csvFile.name);
+
+        } else if (this.source === 'r') {
+            formData.append('recid', this.recordId + '');
+        }
+
+        return this.http.post(
+            MERGE_TEMPLATE_PATH, formData, {responseType: 'text'})
+        .pipe(tap(cacheKey => this.session = cacheKey))
+        .toPromise();
     }
 
     pollProgress(): Promise<any> {
-        return Promise.resolve();
+        console.debug('Polling session ', this.session);
+
+        return this.cache.getItem(this.session, 'batch_edit_progress')
+        .then(progress => {
+            // {"success":"t","complete":1,"failed":0,"succeeded":252}
+
+            if (!progress) {
+                console.error('No batch edit session found for ', this.session);
+                return;
+            }
+
+            this.progressValue = progress.succeeded;
+            this.progressMax = progress.total;
+            this.numSucceeded = progress.succeeded;
+            this.numFailed = progress.failed;
+
+            if (progress.complete) {
+                this.processing = false;
+                return;
+            }
+
+            setTimeout(() => this.pollProgress(), SESSION_POLL_INTERVAL * 1000);
+        });
     }
 }
 
index c16eb6a..bf81553 100644 (file)
@@ -3,6 +3,7 @@ import {StaffCommonModule} from '@eg/staff/common.module';
 import {CommonWidgetsModule} from '@eg/share/common-widgets.module';
 import {MarcBatchRoutingModule} from './routing.module';
 import {MarcBatchComponent} from './marcbatch.component';
+import {HttpClientModule} from '@angular/common/http';
 
 @NgModule({
   declarations: [
@@ -10,6 +11,7 @@ import {MarcBatchComponent} from './marcbatch.component';
   ],
   imports: [
     StaffCommonModule,
+    HttpClientModule,
     CommonWidgetsModule,
     MarcBatchRoutingModule
   ],
index 0c9ad4b..bb268e6 100644 (file)
@@ -5,9 +5,6 @@ import {MarcBatchComponent} from './marcbatch.component';
 const routes: Routes = [{
     path: '',
     component: MarcBatchComponent
-  }, {
-    path: ':session',
-    component: MarcBatchComponent
 }];
 
 @NgModule({
index 4c4d977..fd4df38 100644 (file)
@@ -263,11 +263,13 @@ sub template_overlay_container {
         $template = $e->retrieve_biblio_record_entry( $titem->target_biblio_record_entry )->marc;
     }
 
+    my $num_total = scalar(@$items);
     my $num_failed = 0;
     my $num_succeeded = 0;
 
     $conn->respond_complete(
-        $actor->request('open-ils.actor.anon_cache.set_value', $auth, batch_edit_progress => {})->gather(1)
+        $actor->request('open-ils.actor.anon_cache.set_value', $auth, 
+            batch_edit_progress => {total => $num_total})->gather(1)
     ) if ($actor);
 
     for my $item ( @$items ) {
@@ -291,6 +293,7 @@ sub template_overlay_container {
             $actor->request(
                 'open-ils.actor.anon_cache.set_value', $auth,
                 batch_edit_progress => {
+                    total     => $num_total,
                     succeeded => $num_succeeded,
                     failed    => $num_failed
                 },
@@ -308,6 +311,7 @@ sub template_overlay_container {
                         batch_edit_progress => {
                             complete => 1,
                             success  => 'f',
+                            total     => $num_total,
                             succeeded => $num_succeeded,
                             failed    => $num_failed,
                         }
@@ -331,6 +335,7 @@ sub template_overlay_container {
                 batch_edit_progress => {
                     complete => 1,
                     success  => 't',
+                    total     => $num_total,
                     succeeded => $num_succeeded,
                     failed    => $num_failed,
                 }
@@ -345,6 +350,7 @@ sub template_overlay_container {
                 batch_edit_progress => {
                     complete => 1,
                     success  => 'f',
+                    total     => $num_total,
                     succeeded => $num_succeeded,
                     failed    => $num_failed,
                 }
index 27a03e1..777ab8f 100644 (file)
@@ -4,7 +4,7 @@ use warnings;
 use bytes;
 
 use Apache2::Log;
-use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND :log);
+use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND HTTP_BAD_REQUEST :log);
 use APR::Const    -compile => qw(:error SUCCESS);
 use APR::Table;
 
@@ -56,11 +56,16 @@ sub handler {
     my $cgi = new CGI;
 
     my $authid = $cgi->cookie('ses') || $cgi->param('ses');
+
+    # Avoid sending the HTML to the caller.  Final response will
+    # will just be the cache key or HTTP_BAD_REQUEST on error.
+    my $skipui = $cgi->param('skipui');
+
     my $usr = verify_login($authid);
-    return show_template($r) unless ($usr);
+    return show_template($r, $skipui) unless ($usr);
 
     my $template = $cgi->param('template');
-    return show_template($r) unless ($template);
+    return show_template($r, $skipui) unless ($template);
 
 
     my $rsource = $cgi->param('recordSource');
@@ -118,7 +123,7 @@ sub handler {
     unless (@records) {
         $e->request('open-ils.cstore.transaction.rollback')->gather(1);
         $e->disconnect;
-        return show_template($r);
+        return show_template($r, $skipui);
     }
 
     # we have a template and some record ids, so...
@@ -176,7 +181,8 @@ sub handler {
         ->request('open-ils.cat.container.template_overlay.background', $authid, $bucket->id)
         ->gather(1);
 
-    return show_processing_template($r, $bucket->id, \@records, $cache_key);
+    return show_processing_template(
+      $r, $bucket->id, \@records, $cache_key, $skipui);
 }
 
 sub verify_login {
@@ -201,6 +207,13 @@ sub show_processing_template {
     my $bid = shift;
     my $recs = shift;
     my $cache_key = shift;
+    my $skipui = shift;
+
+    if ($skipui) {
+        $r->content_type('text/plain');
+        $r->print($cache_key);
+        return Apache2::Const::OK;
+    }
 
     my $rec_string = @$recs;
 
@@ -366,6 +379,11 @@ HTML
 
 sub show_template {
     my $r = shift;
+    my $skipui = shift;
+
+    # Makes no sense to call the API in such a way that the caller
+    # is returned the UI code if skipui is set.
+    return Apache2::Const::HTTP_BAD_REQUEST if $skipui;
 
     $r->content_type('text/html');
     $r->print(<<'HTML');