LP1879335 Manage Authorities Angular WIP
authorBill Erickson <berickxx@gmail.com>
Fri, 15 May 2020 21:51:09 +0000 (17:51 -0400)
committerBill Erickson <berickxx@gmail.com>
Mon, 18 May 2020 14:25:23 +0000 (10:25 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/cat/authority/manage.component.html
Open-ILS/src/eg2/src/app/staff/cat/authority/manage.component.ts
Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Authority.pm

index ab508f8..bf619be 100644 (file)
@@ -1,10 +1,62 @@
 <eg-staff-banner bannerText="Manage Authority Records" i18n-bannerText>
 </eg-staff-banner>
 
-<eg-grid #grid [dataSource]="dataSource">
-  <eg-grid-column name="id" label="ID" i18n-label [index]="true"></eg-grid-column>
-  <eg-grid-column name="link_count" label="Linked Bibs" i18n-label></eg-grid-column>
-  <eg-grid-column name="heading" label="Heading" i18n-label></eg-grid-column>
-  <eg-grid-column name="control_set" label="Control Set" i18n-label></eg-grid-column>
-  <eg-grid-column name="thesaurus" label="Thesaurus" i18n-label></eg-grid-column>
+<div class="row form-inline mb-3">
+  <div class="col-lg-3">
+    <div class="input-group">
+      <div class="input-group-prepend">
+        <span class="input-group-text" id="search-term" i18n>Search Term</span>
+      </div>
+      <input type="text" class="form-control" placeholder="Search Term" 
+        i18n-placeholder aria-describedby="search-term" 
+        (keyup.enter)="search()" [(ngModel)]="searchTerm">
+    </div>
+  </div>
+  <div class="col-lg-5">
+    <div class="input-group">
+      <div class="input-group-prepend">
+        <span class="input-group-text" id="auth-axis" i18n>Authority Type</span>
+      </div>
+      <eg-combobox #axisCbox [(ngModel)]="authorityAxis" 
+        [entries]="authorityAxes" (onChange)="search()">
+      </eg-combobox>
+      <button class="btn btn-outline-dark ml-2" (click)="search()" i18n>Submit</button>
+    </div>
+  </div>
+  <div class="col-lg-4 d-flex">
+    <div class="flex-1"></div><!-- push right -->
+    <div class="form-inline">
+      <button class="btn btn-outline-dark ml-2" (click)="search(-1)" i18n>Previous</button>
+      <label for='offset-input' class="form-control ml-2" i18n>Page</label>
+      <input class="form-control" type="number" 
+        [(ngModel)]="searchOffset" id="offset-input" (change)="search()"/>
+      <button class="btn btn-outline-dark ml-2" (click)="search(1)" i18n>Next</button>
+    </div>
+  </div>
+</div>
+
+<eg-grid #grid [dataSource]="dataSource" [disablePaging]="true">
+  <eg-grid-column name="id" label="ID" path="authority.id" i18n-label 
+    [index]="true" flex="1"></eg-grid-column>
+  <eg-grid-column name="link_count" label="Linked Bibs" 
+    i18n-label flex="1"></eg-grid-column>
+  <eg-grid-column name="heading" label="Heading" i18n-label flex="3"></eg-grid-column>
+  <eg-grid-column name="control_set" path="authority.control_set.name" 
+    label="Control Set" i18n-label flex="1"></eg-grid-column>
+  <eg-grid-column name="thesaurus" label="Thesaurus" i18n-label flex="1"></eg-grid-column>
+  <eg-grid-column name="thesaurus_code" label="Thesaurus Code" 
+    i18n-label flex="1"></eg-grid-column>
+  <eg-grid-column name="creator" label="Creator" i18n-label
+    path="authority.creator.usrname" flex="1"></eg-grid-column>
+  <eg-grid-column name="create_date" label="Create Date" i18n-label
+    path="authority.create_date" flex="1" datatype="timestamp"></eg-grid-column>
+  <eg-grid-column name="edit_date" label="Edit Date" i18n-label
+    path="authority.edit_date" flex="1" datatype="timestamp"></eg-grid-column>
+  <eg-grid-column name="source" label="Source" i18n-label
+    path="authority.source" flex="1"></eg-grid-column>
+  <eg-grid-column name="owner" label="Owner" i18n-label
+    path="authority.owner" flex="1"></eg-grid-column>
+
+
 </eg-grid>
+
index f0f7a3e..661857d 100644 (file)
@@ -1,64 +1,97 @@
 import {Component, OnInit, ViewChild} from '@angular/core';
-import {Observable} from 'rxjs';
+import {Observable, empty} from 'rxjs';
 import {map, switchMap} from 'rxjs/operators';
 import {IdlObject} from '@eg/core/idl.service';
 import {Pager} from '@eg/share/util/pager';
 import {NetService} from '@eg/core/net.service';
 import {PcrudService} from '@eg/core/pcrud.service';
+import {OrgService} from '@eg/core/org.service';
+import {GridComponent} from '@eg/share/grid/grid.component';
 import {GridContext, GridDataSource} from '@eg/share/grid/grid';
 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
 
 /* Find, merge, and edit authority records */
 
 @Component({
-  templateUrl: 'manage.component.html'
+  templateUrl: 'manage.component.html',
+  styles: [`#offset-input { width: 4em; }`]
 })
 export class ManageAuthorityComponent implements OnInit {
 
-    authorityAxis: string;
+    // Grid paging is disabled in this UI to support browsing in
+    // both directions.  Define our own paging trackers.
+    pageSize = 15;
+    searchOffset = 0;
+
+    searchTerm: string;
+    authorityAxis: ComboboxEntry;
     authorityAxes: ComboboxEntry[];
     dataSource: GridDataSource;
 
+    @ViewChild('grid', {static: false}) grid: GridComponent;
+
     constructor(
         private net: NetService,
+        private org: OrgService,
         private pcrud: PcrudService
     ) {
     }
 
     ngOnInit() {
 
-        // TODO fetch axes
+        this.pcrud.retrieveAll('aba', {}, {atomic: true})
+        .subscribe(axes => {
+                this.authorityAxes = axes
+                    .map(axis => ({id: axis.code(), label: axis.name()}))
+                    .sort((a1, a2) => a1.label < a2.label ? -1 : 1);
+        });
 
         this.dataSource = new GridDataSource();
 
         this.dataSource.getRows = (pager: Pager, sort: any): Observable<any> => {
-            return this.loadAuthorities(pager, sort);
+            return this.loadAuthorities();
         }
     }
 
-    loadAuthorities(pager: Pager, sort: any): Observable<any> {
+    loadAuthorities(): Observable<any> {
+
+        if (!this.searchTerm || !this.authorityAxis.id) {
+            return empty();
+        }
 
         return this.net.request(
             'open-ils.supercat',
             'open-ils.supercat.authority.browse.by_axis',
-            'subject', 'g', pager.limit, pager.offset
+            this.authorityAxis.id, this.searchTerm,
+            this.pageSize, this.searchOffset
 
         ).pipe(switchMap(authIds => {
+
             return this.net.request(
                 'open-ils.search',
                 'open-ils.search.authority.main_entry', authIds
             );
 
         })).pipe(map(authMeta => {
+
+            const oOrg = this.org.get(authMeta.authority.owner());
+
             return {
-                id: authMeta.authority_id,
+                authority: authMeta.authority,
                 link_count: authMeta.linked_bibs.length,
-                heading: authMeta.heading.value(),
-                control_set: authMeta.control_set.name(),
-                thesaurus: authMeta.heading.thesaurus()
+                heading: authMeta.heading,
+                thesaurus: authMeta.thesaurus,
+                thesaurus_code: authMeta.thesaurus_code,
+                owner: oOrg ? oOrg.shortname() : ''
             };
         }));
     }
+
+    search(offset?: number) {
+        if (offset) { this.searchOffset += offset; }
+
+        this.grid.reload();
+    }
 }
 
 
index c8e99cb..549593a 100644 (file)
@@ -4,6 +4,9 @@ use strict; use warnings;
 
 use OpenILS::Utils::Fieldmapper;
 use OpenILS::Application::AppUtils;
+use MARC::Record;
+use MARC::File::XML (BinaryEncoding => 'UTF-8');
+use MARC::Charset;
 use XML::LibXML;
 use XML::LibXSLT;
 use OpenILS::Utils::CStoreEditor q/:funcs/;
@@ -353,10 +356,12 @@ __PACKAGE__->register_method(
         return => {
             desc => q/
                 Stream of authority metadata objects.
-                {   authority_id: $id,
-                    heading: $main_entry_heading, # fleshed atag
-                    control_set: $control_set,
-                    linked_bibs: [$id1, $id2, ...]
+                {   authority: are_object,
+                    heading: heading_text,
+                    thesaurus: short_code,
+                    thesaurus_code: code,
+                    control_set: control_set_object,
+                    linked_bibs: [id1, id2, ...]
                 }
             /,
             type => 'object'
@@ -376,34 +381,43 @@ sub authority_main_entry {
         my $rec = $e->retrieve_authority_record_entry([
             $auth_id, {
                 flesh => 1,
-                flesh_fields => {are => [qw/control_set bib_links/]}
+                flesh_fields => {are => [qw/control_set bib_links creator/]}
             }
         ]) or return $e->event;
 
-        my $main_entry = $e->json_query({
-            select => {ash => [qw/id/]},
-            from => {ash => 'acsaf'},
-            where => {
-                '+ash' => {record => $auth_id},
-                '+acsaf' => {main_entry => undef}
-            },
-            limit => 1
-        })->[0];
-
         my $response = {
-            authority_id => $auth_id,
+            authority => $rec,
             control_set => $rec->control_set,
             linked_bibs => [ map {$_->bib} @{$rec->bib_links} ]
         };
 
-        if ($main_entry) {
-            $response->{heading} = 
-                $e->search_authority_simple_heading([
-                    {id => $main_entry->{id}},
-                    {flesh => 1, flesh_fields => {ash => [qw/atag/]}}
-                ])->[0];
+        # Extract the heading and thesaurus.
+        # In theory this data has already been extracted in the DB,
+        # but my results vary quite a bit from the previous authority
+        # manage interface.  I took the MARC parsing approach because
+        # it matches the logic (and results) of the previous UI.
+
+        my $marc = MARC::Record->new_from_xml($rec->marc);
+        my $heading_field = $marc->field('1..');
+        $response->{heading} = $heading_field->as_string if $heading_field;
+
+        my $field_008 = $marc->field('008');
+        if ($field_008) {
+            my $thes = substr($field_008->data, 11, 1);
+
+            if (defined $thes) {
+                $response->{thesaurus} = $thes;
+
+                if ($thes ne 'z') { # 'z' ('Other') maps to many entries
+                    my $thesaurus = 
+                        $e->search_authority_thesaurus({short_code => $thes})->[0];
+
+                    $response->{thesaurus_code} = $thesaurus->code if $thesaurus;
+                }
+            }
         }
 
+        $rec->clear_marc;
         $client->respond($response);
     }