LP#1779158 Vandelay export form
authorBill Erickson <berickxx@gmail.com>
Thu, 12 Jul 2018 21:31:30 +0000 (17:31 -0400)
committerBill Erickson <berickxx@gmail.com>
Thu, 11 Oct 2018 18:56:30 +0000 (14:56 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/cat/vandelay/export.component.html
Open-ILS/src/eg2/src/app/staff/cat/vandelay/export.component.ts
Open-ILS/src/eg2/src/app/staff/cat/vandelay/import.component.css [deleted file]
Open-ILS/src/eg2/src/app/staff/cat/vandelay/import.component.html
Open-ILS/src/eg2/src/app/staff/cat/vandelay/import.component.ts
Open-ILS/src/eg2/src/app/staff/cat/vandelay/vandelay.component.html

index 944a368..71f960f 100644 (file)
@@ -1 +1,117 @@
-EXPORT
+<h2 i18n>Export Records</h2>
+
+<div class="common-form striped-even form-validated">
+  <div class="row">
+    <div class="col-lg-6">
+      <div class="row"><label>Select a Record Source</label></div>
+      <ngb-accordion [closeOthers]="true" activeIds="csv" 
+        (panelChange)="sourceChange($event)">
+        <ngb-panel id="csv" title="CSV File">
+          <ng-template ngbPanelContent>
+            <div class="row">
+              <div class="col-lg-6">
+                <label i18n>Use Field Number</label>
+              </div>
+              <div class="col-lg-6">
+                <input type="number" class="form-control" 
+                  [(ngModel)]="fieldNumber"
+                  i18n-placeholder placeholder="Starts at 0..."/>
+              </div>
+            </div>
+            <div class="row">
+              <div class="col-lg-6">
+                <label i18n>From CSV file</label>
+              </div>
+              <div class="col-lg-6">
+                <input #fileSelector (change)="fileSelected($event)" 
+                  class="form-control" type="file"/>
+              </div>
+            </div>
+          </ng-template>
+        </ngb-panel>
+        <ngb-panel id="record-id" title="Record ID">
+          <ng-template ngbPanelContent>
+            <div class="row">
+              <div class="col-lg-6">
+                <label i18n>Record ID</label>
+              </div>
+              <div class="col-lg-6">
+                <input type="number" class="form-control" [(ngModel)]="recordId"/>
+              </div>
+            </div>
+          </ng-template>
+        </ngb-panel>
+        <ngb-panel id="bucket-id" title="Bucket">
+          <ng-template ngbPanelContent>
+            <div class="row">
+              <div class="col-lg-6">
+                <label i18n>Bucket ID</label>
+              </div>
+              <div class="col-lg-6">
+                <input type="number" class="form-control" [(ngModel)]="bucketId"/>
+              </div>
+            </div>
+          </ng-template>
+        </ngb-panel>
+      </ngb-accordion>
+    </div><!-- col -->
+    <div class="col-lg-6">
+      <div class="row">
+        <div class="col-lg-6">
+          <label i18n>Record Type</label>
+        </div>
+        <div class="col-lg-6">
+          <select class="form-control" [(ngModel)]="recordType">
+            <option i18n value="biblio">Bibliographic Records</option>
+            <option i18n value="authority">Authority Records</option>
+          </select>
+        </div>
+      </div>
+      <div class="row">
+        <div class="col-lg-6">
+          <label i18n>Record Format</label>
+        </div>
+        <div class="col-lg-6">
+          <select class="form-control" [(ngModel)]="recordFormat">
+            <option i18n value="USMARC">MARC21</option>
+            <option i18n value="UNIMARC">UNIMARC</option>
+            <option i18n value="XML">MARC XML</option>
+            <option i18n value="BRE">Evergreen Record Entry</option>
+          </select>
+        </div>
+      </div>
+      <div class="row">
+        <div class="col-lg-6">
+          <label i18n>Record Encoding</label>
+        </div>
+        <div class="col-lg-6">
+          <select class="form-control" [(ngModel)]="recordEncoding">
+            <option i18n value="UTF-8">UTF-8</option>
+            <option i18n value="MARC8">MARC8</option>
+          </select>
+        </div>
+      </div>
+      <div class="row">
+        <div class="col-lg-6">
+          <label i18n>Include holdings in Bibliographic Records</label>
+        </div>
+        <div class="col-lg-6">
+          <input class="form-check-input" type="checkbox" [(ngModel)]="includeHoldings">
+        </div>
+      </div>
+      <div class="row">
+        <div class="col-lg-10 offset-lg-1">
+          <button class="btn btn-success btn-lg btn-block font-weight-bold"
+            [disabled]="isExporting || !hasNeededData()" 
+            (click)="exportRecords()" i18n>Export</button>
+        </div>
+      </div>
+      <div class="row" [hidden]="!isExporting">
+        <div class="col-lg-10 offset-lg-1">
+          <eg-progress-inline #exportProgress></eg-progress-inline>
+        </div>
+      </div>
+    </div><!-- left col -->
+  </div><!-- row -->
+</div>
+
index 4f64a3e..577da79 100644 (file)
-import {Component, OnInit} from '@angular/core';
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {NgbPanelChangeEvent} from '@ng-bootstrap/ng-bootstrap';
+import {HttpClient, HttpRequest, HttpEventType} from '@angular/common/http';
+import {HttpResponse, HttpErrorResponse} from '@angular/common/http';
+import {saveAs} from 'file-saver/FileSaver';
+import {AuthService} from '@eg/core/auth.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {ProgressInlineComponent} from '@eg/share/dialog/progress-inline.component';
+import {VandelayService, VANDELAY_EXPORT_PATH} from './vandelay.service';
+
 
 @Component({
   templateUrl: 'export.component.html'
 })
 export class ExportComponent implements OnInit {
 
-    constructor() {}
+    recordSource: string;
+    fieldNumber: number;
+    selectedFile: File;
+    recordId: number;
+    bucketId: number;
+    recordType: string;
+    recordFormat: string;
+    recordEncoding: string;
+    includeHoldings: boolean;
+    isExporting: boolean;
+
+    @ViewChild('fileSelector') private fileSelector;
+    @ViewChild('exportProgress') 
+        private exportProgress: ProgressInlineComponent;
+
+    constructor(
+        private http: HttpClient, 
+        private toast: ToastService, 
+        private auth: AuthService
+    ) {
+        this.recordType = 'biblio';
+        this.recordFormat = 'USMARC';
+        this.recordEncoding = 'UTF-8';
+        this.includeHoldings = false;
+    }
 
     ngOnInit() {
     }
 
+    sourceChange($event: NgbPanelChangeEvent) {
+        this.recordSource = $event.panelId;
+    }
+
+    fileSelected($event) {
+       this.selectedFile = $event.target.files[0]; 
+    }
+
+    hasNeededData(): boolean {
+        return Boolean(
+            this.selectedFile || this.recordId || this.bucketId
+        );
+    }
+
+    exportRecords() {
+        this.isExporting = true;
+        this.exportProgress.update({value: 0});
+
+        const formData: FormData = new FormData();
+
+        formData.append('ses', this.auth.token());
+        formData.append('rectype', this.recordType);
+        formData.append('encoding', this.recordEncoding);
+        formData.append('format', this.recordFormat);
+
+        if (this.includeHoldings) {
+            formData.append('holdings', '1');
+        }
+
+        switch (this.recordSource) {
+
+            case 'csv':
+                formData.append('idcolumn', ''+this.fieldNumber);
+                formData.append('idfile', 
+                    this.selectedFile, this.selectedFile.name);
+                break;
+
+            case 'record-id':
+                formData.append('id', ''+this.recordId);
+                break;
+
+            case 'bucket-id':
+                formData.append('containerid', ''+this.bucketId);
+                break;
+        }
+        
+        this.sendExportRequest(formData);
+    }
+
+    sendExportRequest(formData: FormData) {
+
+        const fileName = `export.${this.recordType}.` +
+            `${this.recordEncoding}.${this.recordFormat}`;
+
+        const req = new HttpRequest('POST', VANDELAY_EXPORT_PATH, 
+            formData, {reportProgress: true, responseType: 'text'});
+
+        this.http.request(req).subscribe(
+            evt => {
+                console.log(evt);
+                if (evt.type === HttpEventType.DownloadProgress) {
+                    // File size not reported by server in advance.
+                    this.exportProgress.update({value: evt.loaded});
+
+                } else if (evt instanceof HttpResponse) {
+
+                    saveAs(new Blob([evt.body], 
+                        {type: 'application/octet-stream'}), fileName);
+
+                    this.isExporting = false;
+                }
+            },
+
+            (err: HttpErrorResponse) => {
+                console.error(err);
+                this.toast.danger(err.error);
+                this.isExporting = false;
+            }
+        );
+    }
 }
 
diff --git a/Open-ILS/src/eg2/src/app/staff/cat/vandelay/import.component.css b/Open-ILS/src/eg2/src/app/staff/cat/vandelay/import.component.css
deleted file mode 100644 (file)
index a640412..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-.import-form {
-  margin-right: 10px;
-  margin-left: 10px;
-  font-size: 95%;
-}
-
-.import-form .row {
-  margin: 5px;
-  padding: 3px;
-}
-
-.import-form .row:nth-child(even) {
-  background-color: rgba(0,0,0,.03);
-  border-top: 1px solid rgba(0,0,0,.125);
-  border-bottom: 1px solid rgba(0,0,0,.125);
-}
-
-.import-form label {
-  font-weight: bold;
-}
-
-.import-form input[type="checkbox"] {
-  /* BS adds a negative left margin */
-  margin-left: 0px;
-}
index 5e20104..2d4b150 100644 (file)
@@ -8,8 +8,8 @@
   </div>
 </div>
 
-<div class="import-form form-validated">
-  <h2 i18n>MARC File Upload</h2>
+<h2 i18n>MARC File Upload</h2>
+<div class="common-form striped-odd form-validated ml-3 mr-3">
   <div class="row">
     <div class="col-lg-3">
       <label i18n>Record Type</label>
index 035379c..6689e1a 100644 (file)
@@ -27,8 +27,7 @@ interface ImportOptions {
 }
 
 @Component({
-  templateUrl: 'import.component.html',
-  styleUrls: ['import.component.css']
+  templateUrl: 'import.component.html'
 })
 export class ImportComponent implements OnInit, AfterViewInit, OnDestroy {
 
@@ -346,7 +345,7 @@ export class ImportComponent implements OnInit, AfterViewInit, OnDestroy {
 
             (err: HttpErrorResponse) => {
                 console.error(err);
-                this.toast.danger(err.error.error);
+                this.toast.danger(err.error);
             }
         )).toPromise();
     }
index a5e8966..4104491 100644 (file)
@@ -1,14 +1,7 @@
-<!--  
-Do we need this?  I think it's fairly obvious what page we're on and
-it takes up a good bit of vertical space which is needed by the large
-import form.
-<eg-staff-banner bannerText="MARC Record Import/Export" i18n-bannerText>
-</eg-staff-banner>
--->
 
 <ul class="nav nav-pills nav-fill pb-4">
   <li class="nav-item">
-    <a class="nav-link disabled" [ngClass]="{active: tab=='export'}" 
+    <a class="nav-link" [ngClass]="{active: tab=='export'}" 
       routerLink="/staff/cat/vandelay/export" i18n>Export</a>
   </li>
   <li class="nav-item">
@@ -41,5 +34,6 @@ import form.
   </li>
 </ul>
 
+<!-- load nav-specific page -->
 <router-outlet></router-outlet>