LP1879335 Manage Authorities Angular WIP
authorBill Erickson <berickxx@gmail.com>
Mon, 18 May 2020 19:37:46 +0000 (15:37 -0400)
committerBill Erickson <berickxx@gmail.com>
Mon, 18 May 2020 19:37:46 +0000 (15:37 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/cat/authority/authority.module.ts
Open-ILS/src/eg2/src/app/staff/cat/authority/browse.component.html
Open-ILS/src/eg2/src/app/staff/cat/authority/browse.component.ts
Open-ILS/src/eg2/src/app/staff/cat/authority/browse.service.ts [new file with mode: 0644]
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/eg2/src/app/staff/share/bib-list/bib-list.component.html
Open-ILS/src/eg2/src/app/staff/share/bib-list/bib-list.component.ts
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.data.manage-authority-grids.sql [new file with mode: 0644]

index ef2acbe..8686b1a 100644 (file)
@@ -6,6 +6,7 @@ import {MarcEditModule} from '@eg/staff/share/marc-edit/marc-edit.module';
 import {AuthorityMarcEditComponent} from './marc-edit.component';
 import {BrowseAuthorityComponent} from './browse.component';
 import {ManageAuthorityComponent} from './manage.component';
+import {BrowseService} from './browse.service';
 import {BibListModule} from '@eg/staff/share/bib-list/bib-list.module';
 
 @NgModule({
@@ -22,6 +23,7 @@ import {BibListModule} from '@eg/staff/share/bib-list/bib-list.module';
     BibListModule
   ],
   providers: [
+    BrowseService
   ]
 })
 
index 6c0794a..237a844 100644 (file)
@@ -1,6 +1,8 @@
 <eg-staff-banner bannerText="Manage Authority Records" i18n-bannerText>
 </eg-staff-banner>
 
+<eg-string #rowSelected text="Row Selected for Merge" i18n-text></eg-string>
+
 <div class="row form-inline mb-3">
   <div class="col-lg-3">
     <div class="input-group">
@@ -9,7 +11,8 @@
       </div>
       <input type="text" class="form-control" placeholder="Search Term" 
         i18n-placeholder aria-describedby="search-term" 
-        (keyup.enter)="search()" [(ngModel)]="searchTerm">
+        (change)="search()"
+        (keyup.enter)="search()" [(ngModel)]="browse.searchTerm">
     </div>
   </div>
   <div class="col-lg-5">
         <span class="input-group-text" id="auth-axis" i18n>Authority Type</span>
       </div>
       <eg-combobox #axisCbox [(ngModel)]="authorityAxis" 
-        [entries]="authorityAxes" (onChange)="search()">
+        [entries]="browse.authorityAxes" (onChange)="search(0, $event)">
       </eg-combobox>
+      <!--
+      Hiding 'submit' button since it should never be necessary, plus it
+      can lead to firing duplicate searches if you're quick on the draw.
+      If we want it back, uncomment and add a [disabled] attribute to
+      prevent dupe searches.
       <button class="btn btn-outline-dark ml-2" (click)="search()" i18n>Submit</button>
+      -->
     </div>
   </div>
   <div class="col-lg-4 d-flex">
@@ -29,7 +38,7 @@
       <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()"/>
+        [(ngModel)]="browse.searchOffset" id="offset-input" (change)="search()"/>
       <button class="btn btn-outline-dark ml-2" (click)="search(1)" i18n>Next</button>
     </div>
   </div>
 </ng-template>
 
 <eg-grid #grid [dataSource]="dataSource" [disablePaging]="true"
+  [rowFlairIsEnabled]="true" [rowFlairCallback]="rowFlairCallback"
   [cellTextGenerator]="cellTextGenerator" persistKey="cat.authority.browse">
+
+  <eg-grid-toolbar-action label="Mark for Merge" i18n-label
+    (onClick)="markForMerge($event)"></eg-grid-toolbar-action>
+
+  <eg-grid-toolbar-action label="Un-Mark for Merge" i18n-label
+    (onClick)="unMarkForMerge($event)"></eg-grid-toolbar-action>
+
+  <eg-grid-toolbar-action label="Clear All Merge Marks" i18n-label
+    (onClick)="clearMergeSelection()"></eg-grid-toolbar-action>
+
   <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" 
index 0724231..2b8c779 100644 (file)
@@ -7,8 +7,11 @@ 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, GridCellTextGenerator} from '@eg/share/grid/grid';
-import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+import {GridContext, GridDataSource, GridCellTextGenerator,
+    GridRowFlairEntry} from '@eg/share/grid/grid';
+import {ComboboxEntry, ComboboxComponent} from '@eg/share/combobox/combobox.component';
+import {BrowseService} from './browse.service';
+import {StringComponent} from '@eg/share/string/string.component';
 
 /* Find, merge, and edit authority records */
 
@@ -18,83 +21,82 @@ import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
 })
 export class BrowseAuthorityComponent implements OnInit {
 
-    // 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;
     cellTextGenerator: GridCellTextGenerator;
 
+    rowFlairCallback: (row: any) => GridRowFlairEntry;
+
     @ViewChild('grid', {static: false}) grid: GridComponent;
+    @ViewChild('axisCbox', {static: false}) axisCbox: ComboboxComponent;
+    @ViewChild('rowSelected', {static: false}) rowSelected: StringComponent;
 
     constructor(
         private net: NetService,
         private org: OrgService,
-        private pcrud: PcrudService
-    ) {
-    }
+        private pcrud: PcrudService,
+        private browse: BrowseService
+    ) {}
 
     ngOnInit() {
+        this.browse.fetchAxes();
+        this.setupGrid();
+    }
 
-        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);
-        });
-
+    setupGrid() {
         this.dataSource = new GridDataSource();
 
-        this.dataSource.getRows = (pager: Pager, sort: any): Observable<any> => {
-            return this.loadAuthorities();
+        this.dataSource.getRows =
+            (pager: Pager, sort: any): Observable<any> => {
+
+            if (this.authorityAxis) {
+                this.browse.authorityAxis = this.authorityAxis.id;
+
+            } else {
+                if (this.browse.authorityAxis) {
+                    this.axisCbox.selectedId = this.browse.authorityAxis;
+                    this.authorityAxis = this.axisCbox.selected;
+                } else {
+                    return empty();
+                }
+            }
+
+            return this.browse.loadAuthorities();
         }
 
         this.cellTextGenerator = {
             heading: row => row.heading
         }
-    }
 
-    loadAuthorities(): Observable<any> {
-
-        if (!this.searchTerm || !this.authorityAxis.id) {
-            return empty();
+        this.rowFlairCallback = (row: any): GridRowFlairEntry => {
+            const flair = {icon: null, title: null};
+            if (this.browse.markedForMerge[row.authority.id()]) {
+                flair.icon = 'merge_type';
+                flair.title = this.rowSelected.text;
+            }
+            return flair;
         }
+    }
 
-        return this.net.request(
-            'open-ils.supercat',
-            'open-ils.supercat.authority.browse.by_axis',
-            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 => {
+    markForMerge(rows: any[]) {
+        rows.forEach(row =>
+            this.browse.markedForMerge[row.authority.id()] = true);
+    }
 
-            const oOrg = this.org.get(authMeta.authority.owner());
+    unMarkForMerge(rows: any[]) {
+        rows.forEach(row =>
+            delete this.browse.markedForMerge[row.authority.id()]);
+    }
 
-            return {
-                authority: authMeta.authority,
-                link_count: authMeta.linked_bibs.length,
-                heading: authMeta.heading,
-                thesaurus: authMeta.thesaurus,
-                thesaurus_code: authMeta.thesaurus_code,
-                owner: oOrg ? oOrg.shortname() : ''
-            };
-        }));
+    clearMergeSelection() {
+        this.browse.markedForMerge = {};
     }
 
     search(offset?: number) {
-        if (offset) { this.searchOffset += offset; }
-
+        if (offset) {
+            this.browse.searchOffset += offset;
+        }
         this.grid.reload();
     }
 }
diff --git a/Open-ILS/src/eg2/src/app/staff/cat/authority/browse.service.ts b/Open-ILS/src/eg2/src/app/staff/cat/authority/browse.service.ts
new file mode 100644 (file)
index 0000000..83701c1
--- /dev/null
@@ -0,0 +1,81 @@
+import {Injectable} from '@angular/core';
+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 {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+/* Browse APIS and state maintenance */
+
+@Injectable()
+export class BrowseService {
+
+    // 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: string;
+    authorityAxes: ComboboxEntry[];
+    markedForMerge: {[id: number]: boolean} = {};
+
+    constructor(
+        private net: NetService,
+        private org: OrgService,
+        private pcrud: PcrudService
+    ) {}
+
+    fetchAxes(): Promise<any> {
+        if (this.authorityAxes) {
+            return Promise.resolve(this.authorityAxes);
+        }
+
+        this.pcrud.retrieveAll('aba', {}, {atomic: true})
+        .pipe(map(axes => {
+                this.authorityAxes = axes
+                    .map(axis => ({id: axis.code(), label: axis.name()}))
+                    .sort((a1, a2) => a1.label < a2.label ? -1 : 1);
+        })).toPromise();
+
+    }
+
+    loadAuthorities(): Observable<any> {
+
+        if (!this.searchTerm || !this.authorityAxis) {
+            return empty();
+        }
+
+        return this.net.request(
+            'open-ils.supercat',
+            'open-ils.supercat.authority.browse.by_axis',
+            this.authorityAxis, 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 {
+                authority: authMeta.authority,
+                link_count: authMeta.linked_bibs.length,
+                heading: authMeta.heading,
+                thesaurus: authMeta.thesaurus,
+                thesaurus_code: authMeta.thesaurus_code,
+                owner: oOrg ? oOrg.shortname() : ''
+            };
+        }));
+    }
+}
+
+
index 6fa633f..2eb4759 100644 (file)
@@ -1,6 +1,17 @@
 <eg-staff-banner bannerText="Manage Authority Record #{{authId}}" i18n-bannerText>
 </eg-staff-banner>
 
+<div class="row mb-2">
+  <div class="col-lg-3">
+    <a routerLink="/staff/cat/authority/browse">
+      <button class="btn btn-outline-dark">
+        <span class="material-icons material-mat-icon-shrunk-in-button">arrow_back</span>
+        <span class="pl-1" i18n>Return to Browse</span>
+      </button>
+    </a>
+  </div>
+</div>
+
 <ngb-tabset #authTabs [activeId]="authTab" 
   (tabChange)="beforeTabChange($event)">
   <ngb-tab title="Linked Bibs" i18n-title id="bibs">
index 7ddf38a..1234e76 100644 (file)
@@ -8,8 +8,6 @@ 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 */
@@ -23,9 +21,6 @@ export class ManageAuthorityComponent implements OnInit {
     authTab = 'bibs';
     authMeta: any;
 
-    bibsDataSource: GridDataSource;
-    @ViewChild('bibsGrid', {static: false}) bibsGrid: GridComponent;
-
     constructor(
         private router: Router,
         private route: ActivatedRoute,
@@ -36,13 +31,6 @@ export class ManageAuthorityComponent implements OnInit {
     }
 
     ngOnInit() {
-
-        this.bibsDataSource = new GridDataSource();
-
-        this.bibsDataSource.getRows = (pager: Pager, sort: any): Observable<any> => {
-            return this.loadLinkedBibs();
-        }
-
         this.route.paramMap.subscribe((params: ParamMap) => {
             this.authTab = params.get('tab') || 'bibs';
             const id = +params.get('id');
@@ -74,12 +62,6 @@ export class ManageAuthorityComponent implements OnInit {
             `/staff/cat/authority/manage/${this.authId}/${this.authTab}`;
         this.router.navigate([url]);
     }
-
-
-    loadLinkedBibs(): Observable<any> {
-
-        return empty();
-    }
 }
 
 
index df01a5c..6c0ffca 100644 (file)
@@ -6,11 +6,26 @@
 </ng-template>
 
 <eg-grid #grid [dataSource]="dataSource" idlClass="rmsr" [sortable]="true"
-  [cellTextGenerator]="cellTextGenerator" [persistKey]="gridPersistKey">
+  [cellTextGenerator]="cellTextGenerator" [persistKey]="gridPersistKey"
+  [showDeclaredFieldsOnly]="true">
+
+  <eg-grid-column name="id" label="ID" i18n-label flex="1"></eg-grid-column>
 
   <eg-grid-column name="title" [cellTemplate]="titleTemplate"
-    label="Title" i18n-label></eg-grid-column>
+    label="Title" i18n-label flex="3"></eg-grid-column>
 
   <eg-grid-column name="author" label="Author" i18n-label></eg-grid-column>
 
+  <eg-grid-column name="creator" label="Creator" i18n-label [sortable]="false"
+    path="biblio_record.creator.usrname" flex="1"></eg-grid-column>
+
+  <eg-grid-column name="create_date" label="Create Date" i18n-label
+    [sortable]="false" path="biblio_record.create_date" flex="1"></eg-grid-column>
+
+  <eg-grid-column name="editor" label="Editor" i18n-label [sortable]="false"
+    path="biblio_record.editor.usrname" flex="1"></eg-grid-column>
+
+  <eg-grid-column name="edit_date" label="Edit Date" i18n-label
+    [sortable]="false" path="biblio_record.edit_date" flex="1"></eg-grid-column>
+
 </eg-grid>
index 84d8a37..8c6b7fc 100644 (file)
@@ -11,7 +11,7 @@ import {GridContext, GridDataSource, GridCellTextGenerator} from '@eg/share/grid
 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
 
 
-/* List of bib records and associated actions */
+/* Grid of bib records and associated actions. */
 
 @Component({
   templateUrl: 'bib-list.component.html',
@@ -21,9 +21,6 @@ export class BibListComponent implements OnInit {
 
     // Display bibs linked to this authority record.
     @Input() bibIds: number[];
-
-    @Input() bucketId: number; // TODO
-
     @Input() gridPersistKey: string;
 
     dataSource: GridDataSource;
@@ -51,6 +48,7 @@ export class BibListComponent implements OnInit {
         }
 
         this.cellTextGenerator = {
+            title: row => row.title
         };
     }
 
@@ -59,13 +57,18 @@ export class BibListComponent implements OnInit {
             return empty();
         }
 
+        const orderBy: any = {rmsr: 'title'};
+        if (sort.length) {
+            orderBy.rmsr = sort[0].name + ' ' + sort[0].dir;
+        }
+
         return this.pcrud.search('rmsr', {id: this.bibIds}, {
-            order_by: {}, /* todo */
+            order_by: orderBy,
             limit: pager.limit,
             offset: pager.offset,
             flesh: 2,
             flesh_fields: {
-                rmsr: ['bib_record'],
+                rmsr: ['biblio_record'],
                 bre: ['creator', 'editor']
             }
         });
index b6959bb..d6a989e 100644 (file)
@@ -20414,4 +20414,19 @@ VALUES (
     )
 );
 
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+    'eg.grid.cat.authority.browse', 'gui', 'object',
+    oils_i18n_gettext(
+    'eg.grid.cat.authority.browse',
+    'Grid Config: eg.grid.cat.authority.browse',
+    'cwst', 'label')
+), (
+    'eg.grid.cat.authority.manage.bibs', 'gui', 'object',
+    oils_i18n_gettext(
+    'eg.grid.cat.authority.manage.bibs',
+    'Grid Config: eg.grid.cat.authority.manage.bibs',
+    'cwst', 'label')
+);
+
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.manage-authority-grids.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.manage-authority-grids.sql
new file mode 100644 (file)
index 0000000..4236704
--- /dev/null
@@ -0,0 +1,20 @@
+BEGIN;
+
+-- SELECT evergreen.upgrade_deps_block_check('TODO', :eg_version);
+
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+    'eg.grid.cat.authority.browse', 'gui', 'object',
+    oils_i18n_gettext(
+    'eg.grid.cat.authority.browse',
+    'Grid Config: eg.grid.cat.authority.browse',
+    'cwst', 'label')
+), (
+    'eg.grid.cat.authority.manage.bibs', 'gui', 'object',
+    oils_i18n_gettext(
+    'eg.grid.cat.authority.manage.bibs',
+    'Grid Config: eg.grid.cat.authority.manage.bibs',
+    'cwst', 'label')
+);
+
+COMMIT;