LP#1855780 Angular Notification/Action Triggers port
authorMike Risher <mrisher@catalyte.io>
Thu, 19 Dec 2019 17:56:41 +0000 (17:56 +0000)
committerJane Sandberg <sandbergja@gmail.com>
Wed, 17 Nov 2021 19:55:11 +0000 (11:55 -0800)
Port Notification Action Triggers from DOJO to Angular.  This consists of 4 grids, each
navigated to by its corresponding tab. The Trigger Event Definitions grid allows
cloning of records.  When editing an event defintion one can also edit parameters,
environments, and run tests.

Signed-off-by: Mike Risher <mrisher@catalyte.io>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Signed-off-by: Jane Sandberg <sandbergja@gmail.com>
Open-ILS/src/eg2/src/app/staff/admin/local/admin-local-splash.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts
Open-ILS/src/eg2/src/app/staff/admin/local/triggers/trigger-edit.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/triggers/trigger-edit.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/triggers/triggers.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/triggers/triggers.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/triggers/triggers.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/triggers/triggers_routing.module.ts [new file with mode: 0644]

index e460ca4..b852b98 100644 (file)
@@ -51,7 +51,7 @@
     <eg-link-table-link i18n-label label="Non-Cataloged Types Editor" 
       routerLink="/staff/admin/local/config/non_cataloged_type"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Notifications / Action Triggers" 
-      url="/eg/staff/admin/local/action_trigger/event_definition"></eg-link-table-link>
+      routerLink="/staff/admin/local/action_trigger/event_definition"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Patrons with Negative Balances" 
       url="/eg/staff/admin/local/circ/neg_balance_users"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Permission Tree Display Entries" 
index a859ca0..c3dd98e 100644 (file)
@@ -54,6 +54,10 @@ const routes: Routes = [{
     loadChildren: () =>
       import('./survey/survey.module').then(m => m.SurveyModule)
 }, {
+    path: 'action_trigger/event_definition',
+    loadChildren: () =>
+      import('./triggers/triggers.module').then(m => m.TriggersModule)
+}, {
     path: ':schema/:table',
     component: BasicAdminPageComponent
 }];
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/triggers/trigger-edit.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/triggers/trigger-edit.component.html
new file mode 100644 (file)
index 0000000..970f7c3
--- /dev/null
@@ -0,0 +1,91 @@
+<eg-staff-banner bannerText="{{this.evtDefName}}" i18n-bannerText>
+</eg-staff-banner>
+<ul ngbNav #editNav="ngbNav" class="nav-tabs mb-3"
+    [activeId]="editTab" (navChange)="onTabChange($event)">
+    <li [ngbNavItem]="'def'">
+        <a ngbNavLink i18n>Edit Definition</a>
+        <ng-template ngbNavContent>
+            <ng-template #textAreaTemplate let-field="field" let-record="record">
+                <textarea class="form-control" name="{{field.name}}"
+                    [readonly]="field.readOnly" [required]="field.isRequired()" 
+                    [ngModel]="record[field.name]()" 
+                    (ngModelChange)="record[field.name]($event)" style="height: 600px;">
+                </textarea>
+            </ng-template>
+            <eg-fm-record-editor #eventDialog idlClass="atevdef" displayMode="inline" 
+                recordId="{{this.evtDefId}}" mode="update" 
+                [fieldOptions]="{message_template:{customTemplate:{template:textAreaTemplate}},template:{customTemplate:{template:textAreaTemplate}}}"
+                fieldOrder="owner,name,hook,active,delay,delay_field,group_field,reactor,validator,repeat_delay,id,cleanup_failure,granularity,max_delay,message_library_path,message_template,message_title,message_usr_path,opt_in_setting,usr_field,retention_interval,cleanup_success,template">
+            </eg-fm-record-editor>
+        </ng-template>
+    </li>
+    <li ngbNavItem="'env'">
+        <a ngbNavLink i18n>Edit Environment</a>
+        <ng-template ngbNavContent>
+            <h3 class="mb-3">Trigger Event Environment</h3>
+            <eg-grid #envGrid idlClass="atenv" [dataSource]="envDataSource" 
+                showFields="id,path,collector,label"
+                (onRowActivate)="editSelected([$event])">
+                <eg-grid-toolbar-button label="New Environment" i18n-label
+                    [action]="createNewEnv"></eg-grid-toolbar-button>
+                <eg-grid-toolbar-action label="Edit Environment" i18n-label 
+                    [action]="editSelected"></eg-grid-toolbar-action>
+                <eg-grid-toolbar-action label="Delete Selected" i18n-label 
+                    (onClick)="deleteSelected($event)"></eg-grid-toolbar-action>
+            </eg-grid>
+            <eg-fm-record-editor #envDialog idlClass="atenv"
+                hiddenFields="event_def,id"></eg-fm-record-editor>
+        </ng-template>
+    </li>
+    <li ngbNavItem="'param'">
+        <a ngbNavLink i18n>Edit Parameters</a>
+        <ng-template ngbNavContent>
+            <h3 class="mb-3">Trigger Event Parameters</h3>
+            <eg-grid #paramGrid idlClass="atevparam" [dataSource]="paramDataSource" 
+                showFields="id,param,value" (onRowActivate)="editSelected([$event])">
+                <eg-grid-toolbar-button label="New Parameter" i18n-label
+                    [action]="createNewParam"></eg-grid-toolbar-button>
+                <eg-grid-toolbar-action label="Edit Parameter" i18n-label 
+                    [action]="editSelected"></eg-grid-toolbar-action>
+                <eg-grid-toolbar-action label="Delete Selected" i18n-label 
+                    (onClick)="deleteSelected($event)"></eg-grid-toolbar-action>
+            </eg-grid>
+            <eg-fm-record-editor #paramDialog idlClass="atevparam"
+                hiddenFields="event_def,id"></eg-fm-record-editor>
+        </ng-template>
+    </li>
+    <li ngbNavItem="'test'">
+        <a ngbNavLink i18n>Run Tests</a>
+        <ng-template ngbNavContent>
+            <h3 class="mb-3">Run Tests</h3>
+            <label id="barcode">Barcode of Circulating Copy </label>
+            <input aria-labelledby="barcode" type="text" [(ngModel)]="barcode" 
+                (keyup.enter)="runTest(barcode)" class="ml-2" />
+            <div></div>
+            <button class="btn btn-info" (click)="runTest(barcode)" i18n>Go</button>
+            <div [hidden]="!testDone">
+                <div class="mt-3 border rounded p-2 bg-light">
+                    <div>{{testErr1}}</div>
+                    <div>{{testErr2}}</div>
+                    <pre [hidden]="!testResult">{{testResult}}</pre>
+                </div>
+                <button class="btn btn-outline-dark mt-3" (click)="clearTestResults()" i18n>
+                    Clear Test Results
+                </button>
+            </div>
+        </ng-template>
+    </li>
+</ul>
+<div [ngbNavOutlet]="editNav"></div>
+
+<eg-string #createSuccessString i18n-text text="New entry Added"></eg-string>
+<eg-string #createErrString i18n-text text="Failed to create new entry"></eg-string>
+<eg-string #deleteFailedString i18n-text text="Delete of entry failed or was not allowed">
+</eg-string>
+<eg-string #deleteSuccessString i18n-text text="Delete of entry succeeded"></eg-string>
+<eg-string #updateFailedString i18n-text text="Update of entry failed or was not allowed">
+</eg-string>
+<eg-string #updateSuccessString i18n-text text="Update of entry succeeded"></eg-string>
+<eg-string #cloneFailedString i18n-text text="Clone of entry failed or was not allowed">
+</eg-string>
+<eg-string #cloneSuccessString i18n-text text="Clone of entry succeeded"></eg-string>
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/triggers/trigger-edit.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/triggers/trigger-edit.component.ts
new file mode 100644 (file)
index 0000000..a659d63
--- /dev/null
@@ -0,0 +1,199 @@
+import {Pager} from '@eg/share/util/pager';
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {GridDataSource} from '@eg/share/grid/grid';
+import {ActivatedRoute} from '@angular/router';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {NetService} from '@eg/core/net.service';
+import {AuthService} from '@eg/core/auth.service';
+import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+    templateUrl: './trigger-edit.component.html'
+})
+
+export class EditEventDefinitionComponent implements OnInit {
+
+    evtDefId: number;
+    evtDefName: String;
+
+    testErr1: String = '';
+    testErr2: String = '';
+    testResult: String = '';
+    testDone: Boolean = false;
+
+    envDataSource: GridDataSource = new GridDataSource();
+    paramDataSource: GridDataSource = new GridDataSource();
+
+    editTab: 'def' | 'env' | 'param' | 'test' = 'def';
+
+    @ViewChild('paramDialog') paramDialog: FmRecordEditorComponent;
+    @ViewChild('envDialog') envDialog: FmRecordEditorComponent;
+
+    @ViewChild('envGrid') envGrid: GridComponent;
+    @ViewChild('paramGrid') paramGrid: GridComponent;
+
+    @ViewChild('updateSuccessString') updateSuccessString: StringComponent;
+    @ViewChild('updateFailedString') updateFailedString: StringComponent;
+    @ViewChild('cloneSuccessString') cloneSuccessString: StringComponent;
+    @ViewChild('cloneFailedString') cloneFailedString: StringComponent;
+    @ViewChild('deleteFailedString') deleteFailedString: StringComponent;
+    @ViewChild('deleteSuccessString') deleteSuccessString: StringComponent;
+    @ViewChild('createSuccessString') createSuccessString: StringComponent;
+    @ViewChild('createErrString') createErrString: StringComponent;
+
+    constructor(
+        private idl: IdlService,
+        private pcrud: PcrudService,
+        private toast: ToastService,
+        private route: ActivatedRoute,
+        private net: NetService,
+        private auth: AuthService,
+    ) {
+    }
+
+    ngOnInit() {
+        this.evtDefId = parseInt(this.route.snapshot.paramMap.get('id'), 10);
+
+        // get current event def name to display on the banner
+        this.pcrud.search('atevdef',
+            {id: this.evtDefId}, {}).toPromise().then(rec => {
+            this.evtDefName = rec.name();
+        });
+
+        this.envDataSource.getRows = (pager: Pager, sort: any[]) => {
+            return this.pcrud.search('atenv',
+                {event_def: this.evtDefId}, {});
+        };
+
+        this.paramDataSource.getRows = (pager: Pager, sort: any[]) => {
+            return this.pcrud.search('atevparam',
+                {event_def: this.evtDefId}, {});
+        };
+    }
+
+    onTabChange(event: NgbNavChangeEvent) {
+        this.editTab = event.nextId;
+    }
+
+    createNewEnv = () => {
+        this.createNewThing(this.envDialog, this.envGrid, 'atenv');
+    }
+
+    createNewParam = () => {
+        this.createNewThing(this.paramDialog, this.paramGrid, 'atevparam');
+    }
+
+    createNewThing = (currentDialog: any, currentGrid: any, idl: any) => {
+        currentDialog.mode = 'create';
+        currentDialog.recordId = null;
+        const newRecord = this.idl.create(idl);
+        newRecord.event_def(this.evtDefId);
+        currentDialog.mode = 'create';
+        currentDialog.record = newRecord;
+        currentDialog.open({size: 'lg'}).subscribe(
+            ok => {
+                this.createSuccessString.current()
+                    .then(str => this.toast.success(str));
+                currentGrid.reload();
+            },
+            rejection => {
+                if (!rejection.dismissed) {
+                    this.createErrString.current()
+                        .then(str => this.toast.danger(str));
+                }
+            }
+        );
+    }
+
+    deleteSelected = (idlThings: IdlObject[]) => {
+        let currentGrid;
+        if (idlThings[0].classname === 'atenv') {
+            currentGrid = this.envGrid;
+        } else {
+            currentGrid = this.paramGrid;
+        }
+        idlThings.forEach(idlThing => idlThing.isdeleted(true));
+        this.pcrud.autoApply(idlThings).subscribe(
+            val => {
+                console.debug('deleted: ' + val);
+                this.deleteSuccessString.current()
+                    .then(str => this.toast.success(str));
+                currentGrid.reload();
+            },
+            err => {
+                this.deleteFailedString.current()
+                    .then(str => this.toast.danger(str));
+            }
+        );
+    }
+
+    editSelected = (selectedRecords: IdlObject[]) => {
+        const editOneThing = (record: IdlObject) => {
+            if (!record) { return; }
+            this.showEditDialog(record).then(
+                () => editOneThing(selectedRecords.shift()));
+        };
+        editOneThing(selectedRecords.shift());
+    }
+
+    showEditDialog = (selectedRecord: IdlObject): Promise<any> => {
+        let currentDialog;
+        let currentGrid;
+        if (selectedRecord.classname === 'atenv') {
+            currentDialog = this.envDialog;
+            currentGrid = this.envGrid;
+        } else {
+            currentDialog = this.paramDialog;
+            currentGrid = this.paramGrid;
+        }
+        currentDialog.mode = 'update';
+        const clone = this.idl.clone(selectedRecord);
+        currentDialog.record = clone;
+        return new Promise((resolve, reject) => {
+            currentDialog.open({size: 'lg'}).subscribe(
+                result => {
+                    this.updateSuccessString.current()
+                        .then(str => this.toast.success(str));
+                    currentGrid.reload();
+                    resolve(result);
+                },
+                error => {
+                    this.updateFailedString.current()
+                        .then(str => this.toast.danger(str));
+                    reject(error);
+               }
+           );
+       });
+    }
+
+    runTest = (barcode) => {
+        if (!barcode) {
+            return;
+        }
+        this.clearTestResults();
+        this.net.request(
+            'open-ils.circ', 'open-ils.circ.trigger_event_by_def_and_barcode.fire',
+            this.auth.token(), this.evtDefId, barcode
+        ).subscribe(res => {
+            this.testDone = true;
+            if (res.ilsevent) {
+                this.testErr1 = 'Event:  ' + res.ilsevent + ':  ' + res.textcode + ' ->';
+                this.testErr2 = res.desc;
+            } else {
+                this.testResult = res.template_output().data();
+            }
+        });
+    }
+
+    clearTestResults = () => {
+        this.testDone = false;
+        this.testErr1 = '';
+        this.testErr2 = '';
+        this.testResult = '';
+    }
+}
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/triggers/triggers.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/triggers/triggers.component.html
new file mode 100644 (file)
index 0000000..c578368
--- /dev/null
@@ -0,0 +1,123 @@
+<eg-staff-banner bannerText="Local Admin Notifications/Action Triggers" i18n-bannerText>
+</eg-staff-banner>
+
+<ng-template #textAreaTemplate let-field="field" let-record="record">
+    <textarea class="form-control" name="{{field.name}}" [readonly]="field.readOnly"
+        [required]="field.isRequired()" [ngModel]="record[field.name]()" 
+        (ngModelChange)="record[field.name]($event)" style="height: 600px;">
+    </textarea>
+</ng-template>
+<eg-fm-record-editor #eventDialog idlClass="atevdef"
+    [fieldOptions]="{message_template:{customTemplate:{template:textAreaTemplate}},template:{customTemplate:{template:textAreaTemplate}}}"
+    fieldOrder="owner,name,hook,active,delay,delay_field,group_field,reactor,validator,repeat_delay,id,cleanup_failure,granularity,max_delay,message_library_path,message_template,message_title,message_usr_path,opt_in_setting,usr_field,retention_interval,cleanup_success,template"
+    hiddenFields="id">
+</eg-fm-record-editor>
+<eg-fm-record-editor #hookDialog idlClass="ath"></eg-fm-record-editor>        
+<eg-fm-record-editor #reactorDialog idlClass="atreact"></eg-fm-record-editor>        
+<eg-fm-record-editor #validatorDialog idlClass="atval"></eg-fm-record-editor>        
+
+<ul ngbNav #triggerNav="ngbNav" [activeId]="triggerTab" class="nav-tabs"
+    (navChange)="onTabChange($event)">
+    <li [ngbNavItem]="'eventDefinitions'">
+      <a ngbNavLink i18n>Event Definitions</a>
+      <ng-template ngbNavContent>
+        <h4 class="mb-3 mt-3">Trigger Event Definitions</h4>
+        <eg-grid #eventsGrid idlClass="atevdef" [dataSource]="eventsDataSource" 
+            showFields="owner,name,hook,active,delay,delay_field,group_field,reactor,validator,repeat_delay,granularity,retention_interval"
+            [showLinkSelectors]="true" [sortable]="true" 
+            (onRowActivate)="editSelected([$event])" [filterable]="true">
+            <eg-grid-toolbar-button label="New Event Definition" i18n-label
+                [action]="createNewEvent"></eg-grid-toolbar-button>
+            <eg-grid-toolbar-action label="Edit Event Definition" i18n-label 
+                [action]="editEventDefinition"></eg-grid-toolbar-action>
+            <eg-grid-toolbar-action label="Clone Selected" i18n-label 
+                [action]="cloneSelected"></eg-grid-toolbar-action>
+            <eg-grid-toolbar-action label="Delete Selected" i18n-label 
+                (onClick)="deleteSelected($event)"></eg-grid-toolbar-action>
+            <eg-grid-column i18n-label label="Owning Library" path="owner">
+            </eg-grid-column>
+            <eg-grid-column i18n-label label="Name" path="name">
+            </eg-grid-column>
+            <eg-grid-column i18n-label label="Hook" path="hook">
+            </eg-grid-column>
+            <eg-grid-column i18n-label label="Enabled" path="active">
+            </eg-grid-column>
+            <eg-grid-column i18n-label label="Processing Delay" path="delay" [filterable]="false">
+            </eg-grid-column>
+            <eg-grid-column i18n-label label="Processing Delay Context Field" path="delay_field">
+            </eg-grid-column>
+            <eg-grid-column i18n-label label="Processing Group Context Field" path="group_field">
+            </eg-grid-column>
+            <eg-grid-column i18n-label label="Reactor" path="reactor">
+            </eg-grid-column>
+            <eg-grid-column i18n-label label="Validator" path="validator">
+            </eg-grid-column>
+            <eg-grid-column i18n-label label="Event Repeatability Delay" path="repeat_delay" [filterable]="false">
+            </eg-grid-column>
+            <eg-grid-column i18n-label label="Granularity" path="granularity">
+            </eg-grid-column>
+            <eg-grid-column i18n-label label="Retention Interval" path="retention_interval" [filterable]="false">
+            </eg-grid-column>
+        </eg-grid>
+      </ng-template>
+    </li>
+    <li [ngbNavItem]="'hooks'">
+      <a ngbNavLink i18n>Hooks</a>
+      <ng-template ngbNavContent>
+        <h4 class="mb-3 mt-3">Trigger Hooks</h4>
+            <eg-grid #hooksGrid idlClass="ath" [dataSource]="hooksDataSource"
+                (onRowActivate)="editSelected([$event])" [sortable]="true" [filterable]="true">
+                <eg-grid-toolbar-button label="New Hook" i18n-label [action]="createNewHook">
+                </eg-grid-toolbar-button>
+                <eg-grid-toolbar-action label="Edit Selected" i18n-label 
+                    [action]="editSelected"></eg-grid-toolbar-action>
+                <eg-grid-toolbar-action label="Delete Selected" i18n-label 
+                    (onClick)="deleteSelected($event)"></eg-grid-toolbar-action>
+            </eg-grid>
+      </ng-template>
+    </li>
+    <li [ngbNavItem]="'reactors'">
+      <a ngbNavLink i18n>Reactors</a>
+      <ng-template ngbNavContent>
+        <h4 class="mb-3 mt-3">Trigger Reactors</h4>
+            <eg-grid #reactorsGrid idlClass="atreact" [dataSource]="reactorsDataSource"
+              (onRowActivate)="editSelected([$event])" [sortable]="true" [filterable]="true">
+                <eg-grid-toolbar-button label="New Reactor" i18n-label 
+                    [action]="createNewReactor"></eg-grid-toolbar-button>
+                <eg-grid-toolbar-action label="Edit Selected" i18n-label 
+                    [action]="editSelected"></eg-grid-toolbar-action>
+                <eg-grid-toolbar-action label="Delete Selected" i18n-label 
+                    (onClick)="deleteSelected($event)"></eg-grid-toolbar-action>
+            </eg-grid>
+      </ng-template>
+    </li>
+    <li [ngbNavItem]="'validators'">
+        <a ngbNavLink i18n>Validators</a>
+        <ng-template ngbNavContent>
+            <h4 class="mb-3 mt-3">Trigger Validators</h4>
+            <eg-grid #validatorsGrid idlClass="atval" [dataSource]="validatorsDataSource"
+                (onRowActivate)="editSelected([$event])" [sortable]="true" [filterable]="true">
+                <eg-grid-toolbar-button label="New Validator" i18n-label 
+                    [action]="createNewValidator"></eg-grid-toolbar-button>
+                <eg-grid-toolbar-action label="Edit Selected" i18n-label 
+                    [action]="editSelected"></eg-grid-toolbar-action>
+                <eg-grid-toolbar-action label="Delete Selected" i18n-label 
+                    (onClick)="deleteSelected($event)"></eg-grid-toolbar-action>
+            </eg-grid>
+        </ng-template>
+      </li>
+  </ul>
+  
+<div [ngbNavOutlet]="triggerNav" class="mt-2"></div>
+  
+<eg-string #createSuccessString i18n-text text="New entry Added"></eg-string>
+<eg-string #createErrString i18n-text text="Failed to create new entry"></eg-string>
+<eg-string #deleteFailedString i18n-text text="Deletion of entry failed or was not allowed">
+</eg-string>
+<eg-string #deleteSuccessString i18n-text text="Delete of entry succeeded"></eg-string>
+<eg-string #updateFailedString i18n-text text="Update of entry failed or was not allowed">
+</eg-string>
+<eg-string #updateSuccessString i18n-text text="Update of entry succeeded"></eg-string>
+<eg-string #cloneFailedString i18n-text text="Clone of entry failed or was not allowed">
+</eg-string>
+<eg-string #cloneSuccessString i18n-text text="Clone of entry succeeded"></eg-string>
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/triggers/triggers.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/triggers/triggers.component.ts
new file mode 100644 (file)
index 0000000..a54424a
--- /dev/null
@@ -0,0 +1,251 @@
+import {Pager} from '@eg/share/util/pager';
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {GridDataSource} from '@eg/share/grid/grid';
+import {Router} from '@angular/router';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+    templateUrl: './triggers.component.html'
+})
+
+export class TriggersComponent implements OnInit {
+
+    eventsDataSource: GridDataSource = new GridDataSource();
+    hooksDataSource: GridDataSource = new GridDataSource();
+    reactorsDataSource: GridDataSource = new GridDataSource();
+    validatorsDataSource: GridDataSource = new GridDataSource();
+    triggerTab: 'eventDefinitions' | 'hooks' | 'reactors' | 'validators' = 'eventDefinitions';
+    idlClass: string;
+
+    @ViewChild('eventDialog', {static: false}) eventDialog: FmRecordEditorComponent;
+    @ViewChild('hookDialog', {static: false}) hookDialog: FmRecordEditorComponent;
+    @ViewChild('reactorDialog', {static: false}) reactorDialog: FmRecordEditorComponent;
+    @ViewChild('validatorDialog', {static: false}) validatorDialog: FmRecordEditorComponent;
+
+    @ViewChild('eventsGrid', {static: false}) eventsGrid: GridComponent;
+    @ViewChild('hooksGrid', {static: false}) hooksGrid: GridComponent;
+    @ViewChild('reactorsGrid', {static: false}) reactorsGrid: GridComponent;
+    @ViewChild('validatorsGrid', {static: false}) validatorsGrid: GridComponent;
+
+    @ViewChild('updateSuccessString', {static: false}) updateSuccessString: StringComponent;
+    @ViewChild('updateFailedString', {static: false}) updateFailedString: StringComponent;
+    @ViewChild('cloneSuccessString', {static: false}) cloneSuccessString: StringComponent;
+    @ViewChild('cloneFailedString', {static: false}) cloneFailedString: StringComponent;
+    @ViewChild('deleteFailedString', {static: false}) deleteFailedString: StringComponent;
+    @ViewChild('deleteSuccessString', {static: false}) deleteSuccessString: StringComponent;
+    @ViewChild('createSuccessString', {static: false}) createSuccessString: StringComponent;
+    @ViewChild('createErrString', {static: false}) createErrString: StringComponent;
+
+    constructor(
+        private idl: IdlService,
+        private pcrud: PcrudService,
+        private toast: ToastService,
+        private router: Router,
+    ) {
+    }
+
+    ngOnInit() {
+        this.eventsDataSource.getRows = (pager: Pager, sort: any[]) => {
+            const orderEventsBy: any = {atevdef: 'name'};
+            if (sort.length) {
+                orderEventsBy.atevdef = sort[0].name + ' ' + sort[0].dir;
+            }
+            return this.getData('atevdef', orderEventsBy, this.eventsDataSource, pager);
+        };
+
+        this.hooksDataSource.getRows = (pager: Pager, sort: any[]) => {
+            const orderHooksBy: any = {ath: 'key'};
+            if (sort.length) {
+                orderHooksBy.ath = sort[0].name + ' ' + sort[0].dir;
+            }
+            return this.getData('ath', orderHooksBy, this.hooksDataSource, pager);
+        };
+
+        this.reactorsDataSource.getRows = (pager: Pager, sort: any[]) => {
+            const orderReactorsBy: any = {atreact: 'module'};
+            if (sort.length) {
+                orderReactorsBy.atreact = sort[0].name + ' ' + sort[0].dir;
+            }
+            return this.getData('atreact', orderReactorsBy, this.reactorsDataSource, pager);
+        };
+
+        this.validatorsDataSource.getRows = (pager: Pager, sort: any[]) => {
+            const orderValidatorsBy: any = {atval: 'module'};
+            if (sort.length) {
+                orderValidatorsBy.atval = sort[0].name + ' ' + sort[0].dir;
+            }
+            return this.getData('atval', orderValidatorsBy, this.validatorsDataSource, pager);
+        };
+    }
+
+    getData(idlString: any, currentOrderBy: any, currentDataSource: any, pager: Pager) {
+        const base: Object = {};
+        base[this.idl.classes[idlString].pkey] = {'!=' : null};
+        const query: any = new Array();
+        query.push(base);
+        Object.keys(currentDataSource.filters).forEach(key => {
+            Object.keys(currentDataSource.filters[key]).forEach(key2 => {
+                query.push(currentDataSource.filters[key][key2]);
+            });
+        });
+        return this.pcrud.search(idlString,
+            query, {
+            offset: pager.offset,
+            limit: pager.limit,
+            order_by: currentOrderBy
+        });
+    }
+
+    onTabChange(event: NgbNavChangeEvent) {
+        this.triggerTab = event.nextId;
+    }
+
+    createNewEvent = () => {
+        this.createNewThing(this.eventDialog, this.eventsGrid);
+    }
+
+    createNewHook = () => {
+        this.createNewThing(this.hookDialog, this.hooksGrid);
+    }
+
+    createNewReactor = () => {
+        this.createNewThing(this.reactorDialog, this.reactorsGrid);
+    }
+
+    createNewValidator = () => {
+        this.createNewThing(this.validatorDialog, this.validatorsGrid);
+    }
+
+    createNewThing = (currentDialog: any, currentGrid: any) => {
+        currentDialog.mode = 'create';
+        currentDialog.recordId = null;
+        currentDialog.record = null;
+        currentDialog.open({size: 'lg'}).subscribe(
+            ok => {
+                this.createSuccessString.current()
+                    .then(str => this.toast.success(str));
+                currentGrid.reload();
+            },
+            rejection => {
+                if (!rejection.dismissed) {
+                    this.createErrString.current()
+                        .then(str => this.toast.danger(str));
+                }
+            }
+        );
+    }
+
+    editSelected = (selectedRecords: IdlObject[]) => {
+        if (this.triggerTab === 'eventDefinitions') {
+            this.editEventDefinition(selectedRecords);
+            return;
+        }
+        const editOneThing = (record: IdlObject) => {
+            if (!record) { return; }
+            this.showEditDialog(record).then(
+                () => editOneThing(selectedRecords.shift()));
+        };
+        editOneThing(selectedRecords.shift());
+    }
+
+    editEventDefinition = (selectedRecords: IdlObject[]) => {
+        const id = selectedRecords[0].id();
+        this.router.navigate(['/staff/admin/local/action_trigger/event_definition/' + id]);
+    }
+
+    lookUpIdl (idl: string) {
+        let currentDialog;
+        let currentGrid;
+        switch (idl) {
+            case 'atevdef':
+                currentDialog = this.eventDialog;
+                currentGrid = this.eventsGrid;
+                break;
+            case 'ath':
+                currentDialog = this.hookDialog;
+                currentGrid = this.hooksGrid;
+                break;
+            case 'atreact':
+                currentDialog = this.reactorDialog;
+                currentGrid = this.reactorsGrid;
+                break;
+            case 'atval':
+                currentDialog = this.validatorDialog;
+                currentGrid = this.validatorsGrid;
+                break;
+            default:
+                console.debug('Unknown class name');
+        }
+        return {currentDialog: currentDialog, currentGrid: currentGrid};
+    }
+
+    showEditDialog = (selectedRecord: IdlObject): Promise<any> => {
+        const idl = selectedRecord.classname;
+        const lookupResults = this.lookUpIdl(idl);
+        const currentDialog = lookupResults.currentDialog;
+        const currentGrid = lookupResults.currentGrid;
+        currentDialog.mode = 'update';
+        const clone = this.idl.clone(selectedRecord);
+        currentDialog.record = clone;
+        return new Promise((resolve, reject) => {
+            currentDialog.open({size: 'lg'}).subscribe(
+                result => {
+                    this.updateSuccessString.current()
+                        .then(str => this.toast.success(str));
+                    currentGrid.reload();
+                    resolve(result);
+                },
+                error => {
+                    this.updateFailedString.current()
+                        .then(str => this.toast.danger(str));
+                    reject(error);
+               }
+           );
+       });
+    }
+
+    deleteSelected = (idlThings: IdlObject[]) => {
+        const idl = idlThings[0].classname;
+        const currentGrid = this.lookUpIdl(idl).currentGrid;
+        idlThings.forEach(idlThing => idlThing.isdeleted(true));
+        this.pcrud.autoApply(idlThings).subscribe(
+            val => {
+                console.debug('deleted: ' + val);
+                this.deleteSuccessString.current()
+                    .then(str => this.toast.success(str));
+                currentGrid.reload();
+            },
+            err => {
+                this.deleteFailedString.current()
+                    .then(str => this.toast.danger(str));
+            }
+        );
+    }
+
+    cloneSelected = (idlThings: IdlObject[]) => {
+        const clone = this.idl.clone(idlThings[0]);
+        clone.id(null);
+        this.eventDialog.mode = 'create';
+        this.eventDialog.recordId = null;
+        this.eventDialog.record = clone;
+        this.eventDialog.open({size: 'lg'}).subscribe(
+            ok => {
+                this.cloneSuccessString.current()
+                    .then(str => this.toast.success(str));
+                this.eventsGrid.reload();
+            },
+            rejection => {
+                if (!rejection.dismissed) {
+                    this.cloneFailedString.current()
+                        .then(str => this.toast.danger(str));
+                }
+            }
+        );
+    }
+}
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/triggers/triggers.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/triggers/triggers.module.ts
new file mode 100644 (file)
index 0000000..f66eda1
--- /dev/null
@@ -0,0 +1,23 @@
+import {NgModule} from '@angular/core';
+import {AdminCommonModule} from '@eg/staff/admin/common.module';
+import {TriggersComponent} from './triggers.component';
+import {TriggersRoutingModule} from './triggers_routing.module';
+import {EditEventDefinitionComponent} from './trigger-edit.component';
+
+@NgModule({
+  declarations: [
+    TriggersComponent,
+    EditEventDefinitionComponent
+  ],
+  imports: [
+    AdminCommonModule,
+    TriggersRoutingModule,
+  ],
+  exports: [
+  ],
+  providers: [
+  ]
+})
+
+export class TriggersModule {
+}
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/triggers/triggers_routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/triggers/triggers_routing.module.ts
new file mode 100644 (file)
index 0000000..c6c48a1
--- /dev/null
@@ -0,0 +1,20 @@
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {TriggersComponent} from './triggers.component';
+import {EditEventDefinitionComponent} from './trigger-edit.component';
+
+const routes: Routes = [{
+    path: '',
+    component: TriggersComponent
+}, {
+    path: ':id',
+    component: EditEventDefinitionComponent
+}];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+
+export class TriggersRoutingModule {
+}