LP1823982 Vandelay Match Set new tree repair
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / cat / vandelay / match-set-expression.component.ts
1 import {Component, OnInit, ViewChild, AfterViewInit, Input} from '@angular/core';
2 import {IdlObject, IdlService} from '@eg/core/idl.service';
3 import {PcrudService} from '@eg/core/pcrud.service';
4 import {NetService} from '@eg/core/net.service';
5 import {AuthService} from '@eg/core/auth.service';
6 import {OrgService} from '@eg/core/org.service';
7 import {Tree, TreeNode} from '@eg/share/tree/tree';
8 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
9 import {StringService} from '@eg/share/string/string.service';
10 import {MatchSetNewPointComponent} from './match-set-new-point.component';
11
12 @Component({
13   selector: 'eg-match-set-expression',
14   templateUrl: 'match-set-expression.component.html'
15 })
16 export class MatchSetExpressionComponent implements OnInit {
17
18     // Match set arrives from parent async.
19     matchSet_: IdlObject;
20     @Input() set matchSet(ms: IdlObject) {
21         this.matchSet_ = ms;
22         if (ms && !this.initDone) {
23             this.matchSetType = ms.mtype();
24             this.initDone = true;
25             this.refreshTree();
26         }
27     }
28
29     tree: Tree;
30     initDone: boolean;
31     matchSetType: string;
32     changesMade: boolean;
33
34     // Current type of new match point
35     newPointType: string;
36     newId: number;
37
38     @ViewChild('newPoint') newPoint: MatchSetNewPointComponent;
39
40     constructor(
41         private idl: IdlService,
42         private pcrud: PcrudService,
43         private net: NetService,
44         private auth: AuthService,
45         private org: OrgService,
46         private strings: StringService
47     ) {
48         this.newId = -1;
49     }
50
51     ngOnInit() {}
52
53     refreshTree(): Promise<any> {
54         if (!this.matchSet_) { return Promise.resolve(); }
55
56         return this.pcrud.search('vmsp',
57             {match_set: this.matchSet_.id()}, {},
58             {atomic: true, authoritative: true}
59         ).toPromise().then(points => {
60             if (points.length > 0) {
61                 this.ingestMatchPoints(points);
62             } else {
63                 this.addRootNode();
64             }
65         });
66     }
67
68     // When creating a new tree, add a stub boolean node
69     // as the root so the tree has something to render.
70     addRootNode() {
71
72         const point = this.idl.create('vmsp');
73         point.id(this.newId--);
74         point.isnew(true);
75         point.match_set(this.matchSet_.id());
76         point.children([]);
77         point.bool_op('AND');
78
79         const node: TreeNode = new TreeNode({
80             id: point.id(),
81             callerData: {point: point}
82         });
83
84         this.tree = new Tree(node);
85         this.setNodeLabel(node, point);
86     }
87
88     // Tree-ify a set of match points.
89     ingestMatchPoints(points: IdlObject[]) {
90         const nodes = [];
91         const idmap: any = {};
92
93         // massage data, create tree nodes
94         points.forEach(point => {
95
96             point.negate(point.negate() === 't' ? true : false);
97             point.heading(point.heading() === 't' ? true : false);
98             point.children([]);
99
100             const node = new TreeNode({
101                 id: point.id(),
102                 expanded: true,
103                 callerData: {point: point}
104             });
105             idmap[node.id + ''] = node;
106             this.setNodeLabel(node, point).then(() => nodes.push(node));
107         });
108
109         // apply the tree parent/child relationships
110         points.forEach(point => {
111             const node = idmap[point.id() + ''];
112             if (point.parent()) {
113                 idmap[point.parent() + ''].children.push(node);
114             } else {
115                 this.tree = new Tree(node);
116             }
117         });
118     }
119
120     setNodeLabel(node: TreeNode, point: IdlObject): Promise<any> {
121         if (node.label) { return Promise.resolve(null); }
122         return Promise.all([
123             this.getPointLabel(point, true).then(txt => node.label = txt),
124             this.getPointLabel(point, false).then(
125                 txt => node.callerData.slimLabel = txt)
126         ]);
127     }
128
129     getPointLabel(point: IdlObject, showmatch?: boolean): Promise<string> {
130         return this.strings.interpolate(
131             'staff.cat.vandelay.matchpoint.label',
132             {point: point, showmatch: showmatch}
133         );
134     }
135
136     nodeClicked(node: TreeNode) {}
137
138     deleteNode() {
139         this.changesMade = true;
140         const node = this.tree.selectedNode();
141         this.tree.removeNode(node);
142     }
143
144     hasSelectedNode(): boolean {
145         return Boolean(this.tree.selectedNode());
146     }
147
148     isRootNode(): boolean {
149         const node = this.tree.selectedNode();
150         if (node && this.tree.findParentNode(node) === null) {
151             return true;
152         }
153         return false;
154     }
155
156     selectedIsBool(): boolean {
157         if (this.tree) {
158             const node = this.tree.selectedNode();
159             return node && node.callerData.point.bool_op();
160         }
161         return false;
162     }
163
164     addChildNode() {
165         this.changesMade = true;
166
167         const pnode = this.tree.selectedNode();
168         const point = this.idl.create('vmsp');
169         point.id(this.newId--);
170         point.isnew(true);
171         point.parent(pnode.id);
172         point.match_set(this.matchSet_.id());
173         point.children([]);
174
175         const ptype = this.newPoint.values.pointType;
176
177         if (ptype === 'bool') {
178             point.bool_op(this.newPoint.values.boolOp);
179
180         } else {
181
182             if (ptype === 'attr') {
183                 point.svf(this.newPoint.values.recordAttr);
184
185             } else if (ptype === 'marc') {
186                 point.tag(this.newPoint.values.marcTag);
187                 point.subfield(this.newPoint.values.marcSf);
188             } else if (ptype === 'heading') {
189                 point.heading(true);
190             }
191
192             point.negate(this.newPoint.values.negate);
193             point.quality(this.newPoint.values.matchScore);
194         }
195
196         const node: TreeNode = new TreeNode({
197             id: point.id(),
198             callerData: {point: point}
199         });
200
201         // Match points are added to the DB only when the tree is saved.
202         this.setNodeLabel(node, point).then(() => pnode.children.push(node));
203     }
204
205     expressionAsString(): string {
206         if (!this.tree) { return ''; }
207
208         const renderNode = (node: TreeNode): string => {
209             if (!node) { return ''; }
210
211             if (node.children.length) {
212                 return '(' + node.children.map(renderNode).join(
213                     ' ' + node.callerData.slimLabel + ' ') + ')';
214             } else if (!node.callerData.point.bool_op()) {
215                 return node.callerData.slimLabel;
216             } else {
217                 return '()';
218             }
219         };
220
221         return renderNode(this.tree.rootNode);
222     }
223
224     // Server API deletes and recreates the tree on update.
225     // It manages parent/child relationships via the children array.
226     // We only need send the current tree in a form the API recognizes.
227     saveTree(): Promise<any> {
228
229
230         const compileTree = (node?: TreeNode) => {
231
232             if (!node) { node = this.tree.rootNode; }
233
234             const point = node.callerData.point;
235
236             node.children.forEach(child =>
237                 point.children().push(compileTree(child)));
238
239             return point;
240         };
241
242         const rootPoint: IdlObject = compileTree();
243
244         return this.net.request(
245             'open-ils.vandelay',
246             'open-ils.vandelay.match_set.update',
247             this.auth.token(), this.matchSet_.id(), rootPoint
248         ).toPromise().then(
249             ok => this.refreshTree(),
250             err => console.error(err)
251         );
252     }
253 }
254