Add DCAE MOD design tool project
[dcaegen2/platform.git] / mod / designtool / designtool-web / src / main / webapp / js / nf / canvas / nf-process-group.js
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  * Modifications to the original nifi code for the ONAP project are made
18  * available under the Apache License, Version 2.0
19  */
20
21 /* global d3, define, module, require, exports */
22
23 (function (root, factory) {
24     if (typeof define === 'function' && define.amd) {
25         define(['jquery',
26                 'd3',
27                 'nf.Connection',
28                 'nf.Common',
29                 'nf.Client',
30                 'nf.CanvasUtils',
31                 'nf.Dialog'],
32             function ($, d3, nfConnection, nfCommon, nfClient, nfCanvasUtils, nfDialog) {
33                 return (nf.ProcessGroup = factory($, d3, nfConnection, nfCommon, nfClient, nfCanvasUtils, nfDialog));
34             });
35     } else if (typeof exports === 'object' && typeof module === 'object') {
36         module.exports = (nf.ProcessGroup =
37             factory(require('jquery'),
38                 require('d3'),
39                 require('nf.Connection'),
40                 require('nf.Common'),
41                 require('nf.Client'),
42                 require('nf.CanvasUtils'),
43                 require('nf.Dialog')));
44     } else {
45         nf.ProcessGroup = factory(root.$,
46             root.d3,
47             root.nf.Connection,
48             root.nf.Common,
49             root.nf.Client,
50             root.nf.CanvasUtils,
51             root.nf.Dialog);
52     }
53 }
54 (this, function ($, d3, nfConnection, nfCommon, nfClient, nfCanvasUtils, nfDialog) {
55     'use strict';
56
57     var nfConnectable;
58     var nfDraggable;
59     var nfSelectable;
60     var nfContextMenu;
61
62     var PREVIEW_NAME_LENGTH = 30;
63
64     var dimensions = {
65         width: 380,
66         height: 172
67     };
68
69     // ----------------------------
70     // process groups currently on the graph
71     // ----------------------------
72
73     var processGroupMap;
74
75     // -----------------------------------------------------------
76     // cache for components that are added/removed from the canvas
77     // -----------------------------------------------------------
78
79     var removedCache;
80     var addedCache;
81
82     // --------------------
83     // component containers
84     // --------------------
85
86     var processGroupContainer;
87
88     // --------------------------
89     // privately scoped functions
90     // --------------------------
91
92     /**
93      * Determines whether the specified process group is under version control.
94      *
95      * @param d
96      */
97     var isUnderVersionControl = function (d) {
98         return nfCommon.isDefinedAndNotNull(d.versionedFlowState);
99     };
100
101     /**
102      * Selects the process group elements against the current process group map.
103      */
104     var select = function () {
105         return processGroupContainer.selectAll('g.process-group').data(processGroupMap.values(), function (d) {
106             return d.id;
107         });
108     };
109
110
111     /**
112      * Renders the process groups in the specified selection.
113      *
114      * @param {selection} entered           The selection of process groups to be rendered
115      * @param {boolean} selected            Whether the process group should be selected
116      * @return the entered selection
117      */
118     var renderProcessGroups = function (entered, selected) {
119         if (entered.empty()) {
120             return entered;
121         }
122
123         var processGroup = entered.append('g')
124             .attrs({
125                 'id': function (d) {
126                     return 'id-' + d.id;
127                 },
128                 'class': 'process-group component'
129             })
130             .classed('selected', selected)
131             .call(nfCanvasUtils.position);
132
133         // ----
134         // body
135         // ----
136
137         // process group border
138         processGroup.append('rect')
139             .attrs({
140                 'class': 'border',
141                 'width': function (d) {
142                     return d.dimensions.width;
143                 },
144                 'height': function (d) {
145                     return d.dimensions.height;
146                 },
147                 'fill': 'transparent',
148                 'stroke': 'transparent'
149             });
150
151         // process group body
152         processGroup.append('rect')
153             .attrs({
154                 'class': 'body',
155                 'width': function (d) {
156                     return d.dimensions.width;
157                 },
158                 'height': function (d) {
159                     return d.dimensions.height;
160                 },
161                 'filter': 'url(#component-drop-shadow)',
162                 'stroke-width': 0
163             });
164
165         // process group name background
166         processGroup.append('rect')
167             .attrs({
168                 'width': function (d) {
169                     return d.dimensions.width;
170                 },
171                 'height': 32,
172                 'fill': '#b8c6cd'
173             });
174
175         // process group name
176         processGroup.append('text')
177             .attrs({
178                 'x': 10,
179                 'y': 20,
180                 'width': 300,
181                 'height': 16,
182                 'class': 'process-group-name'
183             });
184
185         // process group name
186         processGroup.append('text')
187             .attrs({
188                 'x': 10,
189                 'y': 21,
190                 'class': 'version-control'
191                    });
192
193             console.log(processGroup);
194
195
196         // always support selecting and navigation
197         processGroup.on('dblclick', function (d) {
198             // enter this group on double click
199             nfProcessGroup.enterGroup(d.id);
200         })
201             .call(nfSelectable.activate).call(nfContextMenu.activate);
202
203         // only support dragging, connection, and drag and drop if appropriate
204         processGroup.filter(function (d) {
205             return d.permissions.canWrite && d.permissions.canRead;
206         })
207             .on('mouseover.drop', function (d) {
208                 // Using mouseover/out to workaround chrome issue #122746
209
210                 // get the target and ensure its not already been marked for drop
211                 var target = d3.select(this);
212                 if (!target.classed('drop')) {
213                     var targetData = target.datum();
214
215                     // see if there is a selection being dragged
216                     var drag = d3.select('rect.drag-selection');
217                     if (!drag.empty()) {
218                         // filter the current selection by this group
219                         var selection = nfCanvasUtils.getSelection().filter(function (d) {
220                             return targetData.id === d.id;
221                         });
222
223                         // ensure this group isn't in the selection
224                         if (selection.empty()) {
225                             // mark that we are hovering over a drop area if appropriate
226                             target.classed('drop', function () {
227                                 // get the current selection and ensure its disconnected
228                                 return nfConnection.isDisconnected(nfCanvasUtils.getSelection());
229                             });
230                         }
231                     }
232                 }
233             })
234             .on('mouseout.drop', function (d) {
235                 // mark that we are no longer hovering over a drop area unconditionally
236                 d3.select(this).classed('drop', false);
237             })
238             .call(nfDraggable.activate)
239             .call(nfConnectable.activate);
240
241         return processGroup;
242     };
243
244     // attempt of space between component count and icon for process group contents
245     var CONTENTS_SPACER = 10;
246     var CONTENTS_VALUE_SPACER = 5;
247
248     /**
249      * Updates the process groups in the specified selection.
250      *
251      * @param {selection} updated               The process groups to be updated
252      */
253     var updateProcessGroups = function (updated) {
254         if (updated.empty()) {
255             return;
256         }
257
258         // process group border authorization
259         updated.select('rect.border')
260             .classed('unauthorized', function (d) {
261                 return d.permissions.canRead === false;
262             });
263
264         // process group body authorization
265         updated.select('rect.body')
266             .classed('unauthorized', function (d) {
267                 return d.permissions.canRead === false;
268             });
269
270         updated.each(function (processGroupData) {
271             var processGroup = d3.select(this);
272             var details = processGroup.select('g.process-group-details');
273
274             // update the component behavior as appropriate
275             nfCanvasUtils.editable(processGroup, nfConnectable, nfDraggable);
276
277             // if this processor is visible, render everything
278             if (processGroup.classed('visible')) {
279                 if (details.empty()) {
280                     details = processGroup.append('g').attr('class', 'process-group-details');
281
282                     // -------------------
283                     // contents background
284                     // -------------------
285
286                     details.append('rect')
287                         .attrs({
288                             'x': 0,
289                             'y': 32,
290                             'width': function () {
291                                 return processGroupData.dimensions.width
292                             },
293                             'height': 24,
294                             'fill': '#e3e8eb'
295                         });
296
297                     details.append('rect')
298                         .attrs({
299                             'x': 0,
300                             'y': function () {
301                                 return processGroupData.dimensions.height - 24;
302                             },
303                             'width': function () {
304                                 return processGroupData.dimensions.width;
305                             },
306                             'height': 24,
307                             'fill': '#e3e8eb'
308                         });
309
310                     // --------
311                     // contents
312                     // --------
313
314                     // transmitting icon
315 //                    details.append('text')
316 //                        .attrs({
317 //                            'x': 10,
318 //                            'y': 49,
319 //                            'class': 'process-group-transmitting process-group-contents-icon',
320 //                            'font-family': 'FontAwesome'
321 //                        })
322 //                        .text('\uf140')
323 //                        .append("title")
324 //                        .text("Transmitting Remote Process Groups");
325
326
327                     // transmitting count
328 //                    details.append('text')
329 //                        .attrs({
330 //                            'y': 49,
331 //                            'class': 'process-group-transmitting-count process-group-contents-count'
332 //                        });
333
334                     // not transmitting icon
335 //                    details.append('text')
336 //                        .attrs({
337 //                            'y': 49,
338 //                            'class': 'process-group-not-transmitting process-group-contents-icon',
339 //                            'font-family': 'flowfont'
340 //                        })
341 //                        .text('\ue80a')
342 //                        .append("title")
343 //                        .text("Not Transmitting Remote Process Groups");
344
345                     // not transmitting count
346 //                    details.append('text')
347 //                        .attrs({
348 //                            'y': 49,
349 //                            'class': 'process-group-not-transmitting-count process-group-contents-count'
350 //                        });
351
352                     // running icon
353 //                    details.append('text')
354 //                        .attrs({
355 //                            'y': 49,
356 //                            'class': 'process-group-running process-group-contents-icon',
357 //                            'font-family': 'FontAwesome'
358 //                        })
359 //                        .text('\uf04b')
360 //                        .append("title")
361 //                        .text("Running Components");
362
363                     // running count
364 //                    details.append('text')
365 //                        .attrs({
366 //                            'y': 49,
367 //                            'class': 'process-group-running-count process-group-contents-count'
368 //                        });
369
370                     // stopped icon
371 //                    details.append('text')
372 //                        .attrs({
373 //                            'y': 49,
374 //                            'class': 'process-group-stopped process-group-contents-icon',
375 //                            'font-family': 'FontAwesome'
376 //                        })
377 //                        .text('\uf04d')
378 //                        .append("title")
379 //                        .text("Stopped Components");
380
381                     // stopped count
382 //                    details.append('text')
383 //                        .attrs({
384 //                            'y': 49,
385 //                            'class': 'process-group-stopped-count process-group-contents-count'
386 //                        });
387
388                     // invalid icon
389 //                    details.append('text')
390 //                        .attrs({
391 //                            'y': 49,
392 //                            'class': 'process-group-invalid process-group-contents-icon',
393 //                            'font-family': 'FontAwesome'
394 //                        })
395 //                        .text('\uf071')
396 //                        .append("title")
397 //                        .text("Invalid Components");
398
399                     // invalid count
400 //                    details.append('text')
401 //                        .attrs({
402 //                            'y': 49,
403 //                            'class': 'process-group-invalid-count process-group-contents-count'
404 //                        });
405
406                     // disabled icon
407 //                    details.append('text')
408 //                        .attrs({
409 //                            'y': 49,
410 //                            'class': 'process-group-disabled process-group-contents-icon',
411 //                            'font-family': 'flowfont'
412 //                        })
413 //                        .text('\ue802')
414 //                        .append("title")
415 //                        .text("Disabled Components");
416
417                     // disabled count
418 //                    details.append('text')
419 //                        .attrs({
420 //                            'y': 49,
421 //                            'class': 'process-group-disabled-count process-group-contents-count'
422 //                        });
423
424                     // up to date icon
425                     details.append('text')
426                         .attrs({
427                             'x': 10,
428                             'y': function () {
429                                 return processGroupData.dimensions.height - 7;
430                             },
431                             'class': 'process-group-up-to-date process-group-contents-icon',
432                             'font-family': 'FontAwesome'
433                         })
434                         .text('\uf00c')
435                         .append("title")
436                         .text("Up to date Versioned Process Groups");
437
438                     // up to date count
439                     details.append('text')
440                         .attrs({
441                             'y': function () {
442                                 return processGroupData.dimensions.height - 7;
443                             },
444                             'class': 'process-group-up-to-date-count process-group-contents-count'
445                         });
446
447                     // locally modified icon
448                     details.append('text')
449                         .attrs({
450                             'y': function () {
451                                 return processGroupData.dimensions.height - 7;
452                             },
453                             'class': 'process-group-locally-modified process-group-contents-icon',
454                             'font-family': 'FontAwesome'
455                         })
456                         .text('\uf069')
457                         .append("title")
458                         .text("Locally modified Versioned Process Groups");
459
460                     // locally modified count
461                     details.append('text')
462                         .attrs({
463                             'y': function () {
464                                 return processGroupData.dimensions.height - 7;
465                             },
466                             'class': 'process-group-locally-modified-count process-group-contents-count'
467                         });
468
469                     // stale icon
470                     details.append('text')
471                         .attrs({
472                             'y': function () {
473                                 return processGroupData.dimensions.height - 7;
474                             },
475                             'class': 'process-group-stale process-group-contents-icon',
476                             'font-family': 'FontAwesome'
477                         })
478                         .text('\uf0aa')
479                         .append("title")
480                         .text("Stale Versioned Process Groups");
481
482                     // stale count
483                     details.append('text')
484                         .attrs({
485                             'y': function () {
486                                 return processGroupData.dimensions.height - 7;
487                             },
488                             'class': 'process-group-stale-count process-group-contents-count'
489                         });
490
491                     // locally modified and stale icon
492                     details.append('text')
493                         .attrs({
494                             'y': function () {
495                                 return processGroupData.dimensions.height - 7;
496                             },
497                             'class': 'process-group-locally-modified-and-stale process-group-contents-icon',
498                             'font-family': 'FontAwesome'
499                         })
500                         .text('\uf06a')
501                         .append("title")
502                         .text("Locally modified and stale Versioned Process Groups");
503
504                     // locally modified and stale count
505                     details.append('text')
506                         .attrs({
507                             'y': function () {
508                                 return processGroupData.dimensions.height - 7;
509                             },
510                             'class': 'process-group-locally-modified-and-stale-count process-group-contents-count'
511                         });
512
513                     // sync failure icon
514                     details.append('text')
515                         .attrs({
516                             'y': function () {
517                                 return processGroupData.dimensions.height - 7;
518                             },
519                             'class': 'process-group-sync-failure process-group-contents-icon',
520                             'font-family': 'FontAwesome'
521                         })
522                         .text('\uf128')
523                         .append("title")
524                         .text("Sync failure Versioned Process Groups");
525
526                     // sync failure count
527                     details.append('text')
528                         .attrs({
529                             'y': function () {
530                                 return processGroupData.dimensions.height - 7;
531                             },
532                             'class': 'process-group-sync-failure-count process-group-contents-count'
533                         });
534
535                     // ----------------
536                     // stats background
537                     // ----------------
538
539                     // queued
540                     details.append('rect')
541                         .attrs({
542                             'width': function () {
543                                 return processGroupData.dimensions.width;
544                             },
545                             'height': 19,
546                             'x': 0,
547                             'y': 66,
548                             'fill': '#f4f6f7'
549                         });
550
551                     // border
552                     details.append('rect')
553                         .attrs({
554                             'width': function () {
555                                 return processGroupData.dimensions.width;
556                             },
557                             'height': 1,
558                             'x': 0,
559                             'y': 84,
560                             'fill': '#c7d2d7'
561                         });
562
563                     // in
564                     details.append('rect')
565                         .attrs({
566                             'width': function () {
567                                 return processGroupData.dimensions.width;
568                             },
569                             'height': 19,
570                             'x': 0,
571                             'y': 85,
572                             'fill': '#ffffff'
573                         });
574
575                     // border
576                     details.append('rect')
577                         .attrs({
578                             'width': function () {
579                                 return processGroupData.dimensions.width;
580                             },
581                             'height': 1,
582                             'x': 0,
583                             'y': 103,
584                             'fill': '#c7d2d7'
585                         });
586
587                     // read/write
588                     details.append('rect')
589                         .attrs({
590                             'width': function () {
591                                 return processGroupData.dimensions.width;
592                             },
593                             'height': 19,
594                             'x': 0,
595                             'y': 104,
596                             'fill': '#f4f6f7'
597                         });
598
599                     // border
600                     details.append('rect')
601                         .attrs({
602                             'width': function () {
603                                 return processGroupData.dimensions.width;
604                             },
605                             'height': 1,
606                             'x': 0,
607                             'y': 122,
608                             'fill': '#c7d2d7'
609                         });
610
611                     // out
612                     details.append('rect')
613                         .attrs({
614                             'width': function () {
615                                 return processGroupData.dimensions.width;
616                             },
617                             'height': 19,
618                             'x': 0,
619                             'y': 123,
620                             'fill': '#ffffff'
621                         });
622
623                     // -----
624                     // stats
625                     // -----
626
627                     // stats label container
628                     var processGroupStatsLabel = details.append('g')
629                         .attrs({
630                             'transform': 'translate(6, 75)'
631                         });
632
633                     // queued label
634                     processGroupStatsLabel.append('text')
635                         .attrs({
636                             'width': 73,
637                             'height': 10,
638                             'x': 4,
639                             'y': 5,
640                             'class': 'stats-label'
641                         })
642                         .text('Queued');
643
644                     // in label
645                     processGroupStatsLabel.append('text')
646                         .attrs({
647                             'width': 73,
648                             'height': 10,
649                             'x': 4,
650                             'y': 24,
651                             'class': 'stats-label'
652                         })
653                         .text('In');
654
655                     // read/write label
656                     processGroupStatsLabel.append('text')
657                         .attrs({
658                             'width': 73,
659                             'height': 10,
660                             'x': 4,
661                             'y': 42,
662                             'class': 'stats-label'
663                         })
664                         .text('Read/Write');
665
666                     // out label
667                     processGroupStatsLabel.append('text')
668                         .attrs({
669                             'width': 73,
670                             'height': 10,
671                             'x': 4,
672                             'y': 60,
673                             'class': 'stats-label'
674                         })
675                         .text('Out');
676
677                     // stats value container
678                     var processGroupStatsValue = details.append('g')
679                         .attrs({
680                             'transform': 'translate(95, 75)'
681                         });
682
683                     // queued value
684                     var queuedText = processGroupStatsValue.append('text')
685                         .attrs({
686                             'width': 180,
687                             'height': 10,
688                             'x': 4,
689                             'y': 5,
690                             'class': 'process-group-queued stats-value'
691                         });
692
693                     // queued count
694                     queuedText.append('tspan')
695                         .attrs({
696                             'class': 'count'
697                         });
698
699                     // queued size
700                     queuedText.append('tspan')
701                         .attrs({
702                             'class': 'size'
703                         });
704
705                     // in value
706                     var inText = processGroupStatsValue.append('text')
707                         .attrs({
708                             'width': 180,
709                             'height': 10,
710                             'x': 4,
711                             'y': 24,
712                             'class': 'process-group-in stats-value'
713                         });
714
715                     // in count
716                     inText.append('tspan')
717                         .attrs({
718                             'class': 'count'
719                         });
720
721                     // in size
722                     inText.append('tspan')
723                         .attrs({
724                             'class': 'size'
725                         });
726
727                     // in
728                     inText.append('tspan')
729                         .attrs({
730                             'class': 'ports'
731                         });
732
733                     // read/write value
734                     processGroupStatsValue.append('text')
735                         .attrs({
736                             'width': 180,
737                             'height': 10,
738                             'x': 4,
739                             'y': 42,
740                             'class': 'process-group-read-write stats-value'
741                         });
742
743                     // out value
744                     var outText = processGroupStatsValue.append('text')
745                         .attrs({
746                             'width': 180,
747                             'height': 10,
748                             'x': 4,
749                             'y': 60,
750                             'class': 'process-group-out stats-value'
751                         });
752
753                     // out ports
754                     outText.append('tspan')
755                         .attrs({
756                             'class': 'ports'
757                         });
758
759                     // out count
760                     outText.append('tspan')
761                         .attrs({
762                             'class': 'count'
763                         });
764
765                     // out size
766                     outText.append('tspan')
767                         .attrs({
768                             'class': 'size'
769                         });
770
771                     // stats value container
772                     var processGroupStatsInfo = details.append('g')
773                         .attrs({
774                             'transform': 'translate(335, 75)'
775                         });
776
777                     // in info
778                     processGroupStatsInfo.append('text')
779                         .attrs({
780                             'width': 25,
781                             'height': 10,
782                             'x': 4,
783                             'y': 24,
784                             'class': 'stats-info'
785                         })
786                         .text('5 min');
787
788                     // read/write info
789                     processGroupStatsInfo.append('text')
790                         .attrs({
791                             'width': 25,
792                             'height': 10,
793                             'x': 4,
794                             'y': 42,
795                             'class': 'stats-info'
796                         })
797                         .text('5 min');
798
799                     // out info
800                     processGroupStatsInfo.append('text')
801                         .attrs({
802                             'width': 25,
803                             'height': 10,
804                             'x': 4,
805                             'y': 60,
806                             'class': 'stats-info'
807                         })
808                         .text('5 min');
809
810                     // --------
811                     // comments
812                     // --------
813
814                     details.append('path')
815                         .attrs({
816                             'class': 'component-comments',
817                             'transform': 'translate(' + (processGroupData.dimensions.width - 2) + ', ' + (processGroupData.dimensions.height - 10) + ')',
818                             'd': 'm0,0 l0,8 l-8,0 z'
819                         });
820
821                     // -------------------
822                     // active thread count
823                     // -------------------
824
825                     // active thread count
826                     details.append('text')
827                         .attrs({
828                             'class': 'active-thread-count-icon',
829                             'y': 20
830                         })
831                         .text('\ue83f');
832
833                     // active thread icon
834                     details.append('text')
835                         .attrs({
836                             'class': 'active-thread-count',
837                             'y': 20
838                         });
839
840                     // ---------
841                     // bulletins
842                     // ---------
843
844                     // bulletin background
845                     details.append('rect')
846                         .attrs({
847                             'class': 'bulletin-background',
848                             'x': function () {
849                                 return processGroupData.dimensions.width - 24;
850                             },
851                             'y': 32,
852                             'width': 24,
853                             'height': 24
854                         });
855
856                     // bulletin icon
857                     details.append('text')
858                         .attrs({
859                             'class': 'bulletin-icon',
860                             'x': function () {
861                                 return processGroupData.dimensions.width - 17;
862                             },
863                             'y': 49
864                         })
865                         .text('\uf24a');
866                 }
867
868                 // update transmitting
869                 var transmitting = details.select('text.process-group-transmitting')
870                     .classed('transmitting', function (d) {
871                         return d.permissions.canRead && d.activeRemotePortCount > 0;
872                     })
873                     .classed('zero', function (d) {
874                         return d.permissions.canRead && d.activeRemotePortCount === 0;
875                     });
876                 var transmittingCount = details.select('text.process-group-transmitting-count')
877                     .attr('x', function () {
878                         var transmittingCountX = parseInt(transmitting.attr('x'), 10);
879                         return transmittingCountX + Math.round(transmitting.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
880                     })
881                     .text(function (d) {
882                         return d.activeRemotePortCount;
883                     });
884                 transmittingCount.append("title").text("Transmitting Remote Process Groups");
885
886                 // update not transmitting
887                 var notTransmitting = details.select('text.process-group-not-transmitting')
888                     .classed('not-transmitting', function (d) {
889                         return d.permissions.canRead && d.inactiveRemotePortCount > 0;
890                     })
891                     .classed('zero', function (d) {
892                         return d.permissions.canRead && d.inactiveRemotePortCount === 0;
893                     })
894                     .attr('x', function () {
895                         var transmittingX = parseInt(transmittingCount.attr('x'), 10);
896                         return transmittingX + Math.round(transmittingCount.node().getComputedTextLength()) + CONTENTS_SPACER;
897                     });
898                 var notTransmittingCount = details.select('text.process-group-not-transmitting-count')
899                     .attr('x', function () {
900                         var notTransmittingCountX = parseInt(notTransmitting.attr('x'), 10);
901                         return notTransmittingCountX + Math.round(notTransmitting.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
902                     })
903                     .text(function (d) {
904                         return d.inactiveRemotePortCount;
905                     });
906                 notTransmittingCount.append("title").text("Not transmitting Remote Process Groups")
907
908                 // update running
909                 var running = details.select('text.process-group-running')
910                     .classed('running', function (d) {
911                         return d.permissions.canRead && d.component.runningCount > 0;
912                     })
913                     .classed('zero', function (d) {
914                         return d.permissions.canRead && d.component.runningCount === 0;
915                     })
916                     .attr('x', function () {
917                         var notTransmittingX = parseInt(notTransmittingCount.attr('x'), 10);
918                         return notTransmittingX + Math.round(notTransmittingCount.node().getComputedTextLength()) + CONTENTS_SPACER;
919                     });
920                 var runningCount = details.select('text.process-group-running-count')
921                     .attr('x', function () {
922                         var runningCountX = parseInt(running.attr('x'), 10);
923                         return runningCountX + Math.round(running.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
924                     })
925                     .text(function (d) {
926                         return d.runningCount;
927                     });
928                 runningCount.append("title").text("Running Components");
929
930                 // update stopped
931                 var stopped = details.select('text.process-group-stopped')
932                     .classed('stopped', function (d) {
933                         return d.permissions.canRead && d.component.stoppedCount > 0;
934                     })
935                     .classed('zero', function (d) {
936                         return d.permissions.canRead && d.component.stoppedCount === 0;
937                     })
938                     .attr('x', function () {
939                         var runningX = parseInt(runningCount.attr('x'), 10);
940                         return runningX + Math.round(runningCount.node().getComputedTextLength()) + CONTENTS_SPACER;
941                     });
942                 var stoppedCount = details.select('text.process-group-stopped-count')
943                     .attr('x', function () {
944                         var stoppedCountX = parseInt(stopped.attr('x'), 10);
945                         return stoppedCountX + Math.round(stopped.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
946                     })
947                     .text(function (d) {
948                         return d.stoppedCount;
949                     });
950                 stoppedCount.append("title").text("Stopped Components");
951
952                 // update invalid
953                 var invalid = details.select('text.process-group-invalid')
954                     .classed('invalid', function (d) {
955                         return d.permissions.canRead && d.component.invalidCount > 0;
956                     })
957                     .classed('zero', function (d) {
958                         return d.permissions.canRead && d.component.invalidCount === 0;
959                     })
960                     .attr('x', function () {
961                         var stoppedX = parseInt(stoppedCount.attr('x'), 10);
962                         return stoppedX + Math.round(stoppedCount.node().getComputedTextLength()) + CONTENTS_SPACER;
963                     });
964                 var invalidCount = details.select('text.process-group-invalid-count')
965                     .attr('x', function () {
966                         var invalidCountX = parseInt(invalid.attr('x'), 10);
967                         return invalidCountX + Math.round(invalid.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
968                     })
969                     .text(function (d) {
970                         return d.invalidCount;
971                     });
972                 invalidCount.append("title").text("Invalid Components");
973
974                 // update disabled
975                 var disabled = details.select('text.process-group-disabled')
976                     .classed('disabled', function (d) {
977                         return d.permissions.canRead && d.component.disabledCount > 0;
978                     })
979                     .classed('zero', function (d) {
980                         return d.permissions.canRead && d.component.disabledCount === 0;
981                     })
982                     .attr('x', function () {
983                         var invalidX = parseInt(invalidCount.attr('x'), 10);
984                         return invalidX + Math.round(invalidCount.node().getComputedTextLength()) + CONTENTS_SPACER;
985                     });
986                 var disabledCount = details.select('text.process-group-disabled-count')
987                     .attr('x', function () {
988                         var disabledCountX = parseInt(disabled.attr('x'), 10);
989                         return disabledCountX + Math.round(disabled.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
990                     })
991                     .text(function (d) {
992                         return d.disabledCount;
993                     });
994                 disabledCount.append("title").text("Disabled Components");
995
996                 // up to date current
997                 var upToDate = details.select('text.process-group-up-to-date')
998                     .classed('up-to-date', function (d) {
999                         return d.permissions.canRead && d.component.upToDateCount > 0;
1000                     })
1001                     .classed('zero', function (d) {
1002                         return d.permissions.canRead && d.component.upToDateCount === 0;
1003                     });
1004                 var upToDateCount = details.select('text.process-group-up-to-date-count')
1005                     .attr('x', function () {
1006                         var updateToDateCountX = parseInt(upToDate.attr('x'), 10);
1007                         return updateToDateCountX + Math.round(upToDate.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
1008                     })
1009                     .text(function (d) {
1010                         return d.upToDateCount;
1011                     });
1012                 upToDateCount.append("title").text("Up to date Versioned Process Groups");
1013
1014                 // update locally modified
1015                 var locallyModified = details.select('text.process-group-locally-modified')
1016                     .classed('locally-modified', function (d) {
1017                         return d.permissions.canRead && d.component.locallyModifiedCount > 0;
1018                     })
1019                     .classed('zero', function (d) {
1020                         return d.permissions.canRead && d.component.locallyModifiedCount === 0;
1021                     })
1022                     .attr('x', function () {
1023                         var upToDateX = parseInt(upToDateCount.attr('x'), 10);
1024                         return upToDateX + Math.round(upToDateCount.node().getComputedTextLength()) + CONTENTS_SPACER;
1025                     });
1026                 var locallyModifiedCount = details.select('text.process-group-locally-modified-count')
1027                     .attr('x', function () {
1028                         var locallyModifiedCountX = parseInt(locallyModified.attr('x'), 10);
1029                         return locallyModifiedCountX + Math.round(locallyModified.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
1030                     })
1031                     .text(function (d) {
1032                         return d.locallyModifiedCount;
1033                     });
1034                 locallyModifiedCount.append("title").text("Locally modified Versioned Process Groups");
1035
1036                 // update stale
1037                 var stale = details.select('text.process-group-stale')
1038                     .classed('stale', function (d) {
1039                         return d.permissions.canRead && d.component.staleCount > 0;
1040                     })
1041                     .classed('zero', function (d) {
1042                         return d.permissions.canRead && d.component.staleCount === 0;
1043                     })
1044                     .attr('x', function () {
1045                         var locallyModifiedX = parseInt(locallyModifiedCount.attr('x'), 10);
1046                         return locallyModifiedX + Math.round(locallyModifiedCount.node().getComputedTextLength()) + CONTENTS_SPACER;
1047                     });
1048                 var staleCount = details.select('text.process-group-stale-count')
1049                     .attr('x', function () {
1050                         var staleCountX = parseInt(stale.attr('x'), 10);
1051                         return staleCountX + Math.round(stale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
1052                     })
1053                     .text(function (d) {
1054                         return d.staleCount;
1055                     });
1056                 staleCount.append("title").text("Stale Versioned Process Groups");
1057
1058                 // update locally modified and stale
1059                 var locallyModifiedAndStale = details.select('text.process-group-locally-modified-and-stale')
1060                     .classed('locally-modified-and-stale', function (d) {
1061                         return d.permissions.canRead && d.component.locallyModifiedAndStaleCount > 0;
1062                     })
1063                     .classed('zero', function (d) {
1064                         return d.permissions.canRead && d.component.locallyModifiedAndStaleCount === 0;
1065                     })
1066                     .attr('x', function () {
1067                         var staleX = parseInt(staleCount.attr('x'), 10);
1068                         return staleX + Math.round(staleCount.node().getComputedTextLength()) + CONTENTS_SPACER;
1069                     });
1070                 var locallyModifiedAndStaleCount = details.select('text.process-group-locally-modified-and-stale-count')
1071                     .attr('x', function () {
1072                         var locallyModifiedAndStaleCountX = parseInt(locallyModifiedAndStale.attr('x'), 10);
1073                         return locallyModifiedAndStaleCountX + Math.round(locallyModifiedAndStale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
1074                     })
1075                     .text(function (d) {
1076                         return d.locallyModifiedAndStaleCount;
1077                     });
1078                 locallyModifiedAndStaleCount.append("title").text("Locally modified and stale Versioned Process Groups");
1079
1080                 // update sync failure
1081                 var syncFailure = details.select('text.process-group-sync-failure')
1082                     .classed('sync-failure', function (d) {
1083                         return d.permissions.canRead && d.component.syncFailureCount > 0;
1084                     })
1085                     .classed('zero', function (d) {
1086                         return d.permissions.canRead && d.component.syncFailureCount === 0;
1087                     })
1088                     .attr('x', function () {
1089                         var syncFailureX = parseInt(locallyModifiedAndStaleCount.attr('x'), 10);
1090                         return syncFailureX + Math.round(locallyModifiedAndStaleCount.node().getComputedTextLength()) + CONTENTS_SPACER - 2;
1091                     });
1092                 var syncFailureCount = details.select('text.process-group-sync-failure-count')
1093                     .attr('x', function () {
1094                         var syncFailureCountX = parseInt(syncFailure.attr('x'), 10);
1095                         return syncFailureCountX + Math.round(syncFailure.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
1096                     })
1097                     .text(function (d) {
1098                         return d.syncFailureCount;
1099                     });
1100                 syncFailureCount.append("title").text("Sync failure Versioned Process Groups");
1101
1102                 /**
1103                 *  update version control information
1104                 * @author: Renu
1105                 * @desc for lines 1110-1201: based on state of the process group, environment selection and submit button enable/disable
1106                 */
1107                 var versionControl = processGroup.select('text.version-control')
1108                     .styles({
1109                         'visibility': isUnderVersionControl(processGroupData) ? 'visible' : 'hidden',
1110                         'fill': function () {
1111                             if (isUnderVersionControl(processGroupData)) {
1112                                 var vciState = processGroupData.versionedFlowState;
1113                                 if (vciState === 'SYNC_FAILURE') {
1114                                           $('#environmentType').prop('disabled', true);
1115                                             if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
1116                                                 $('#operate-submit-btn').prop('disabled', false);
1117                                            }else{$('#operate-submit-btn').prop('disabled', true);}
1118
1119                                     return '#666666';
1120                                 } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') {
1121                                      console.log("locally but stale in style");
1122                                     $('#environmentType').prop('disabled', true);
1123                                        if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
1124                                            $('#operate-submit-btn').prop('disabled', false);
1125                                       }else{$('#operate-submit-btn').prop('disabled', true);}
1126
1127                                     return '#BA554A';
1128                                 } else if (vciState === 'STALE') {
1129                                      console.log("stale in style");
1130                                        $('#environmentType').prop('disabled', true);
1131                                        if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
1132                                            $('#operate-submit-btn').prop('disabled', false);
1133                                       }else{$('#operate-submit-btn').prop('disabled', true);}
1134                                                                           return '#BA554A';
1135                                 } else if (vciState === 'LOCALLY_MODIFIED') {
1136                                  console.log("locally modified in style");
1137                                           $('#environmentType').prop('disabled', true);
1138                                           if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
1139                                                  $('#operate-submit-btn').prop('disabled', false);
1140                                           }else{$('#operate-submit-btn').prop('disabled', true);}
1141
1142                                     return '#666666';
1143                                 } else {
1144                                     return '#1A9964';
1145                                   $('#environmentType').prop('disabled', false);
1146                                    if($('#environmentType').val() &&  !$('#environmentType').prop('disabled')){
1147                                         $('#operate-submit-btn').prop('disabled', false);
1148                                      }else{$('#operate-submit-btn').prop('disabled', true);}
1149                                 }
1150                             } else {
1151                                 $('#environmentType').prop('disabled', true);
1152                                 return '#000';
1153                             }
1154                         }
1155                     })
1156                     .text(function () {
1157                         if (isUnderVersionControl(processGroupData)) {
1158                             var vciState = processGroupData.versionedFlowState;
1159                             if (vciState === 'SYNC_FAILURE') {
1160                                     $('#environmentType').prop('disabled', true);
1161                                              if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
1162                                                  $('#operate-submit-btn').prop('disabled', false);
1163                                             }else{$('#operate-submit-btn').prop('disabled', true);}
1164
1165                                 return '\uf128'
1166                             } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') {
1167                               console.log("locally but stale in text");
1168                                      $('#environmentType').prop('disabled', true);
1169                                        if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
1170                                            $('#operate-submit-btn').prop('disabled', false);
1171                                       }else{$('#operate-submit-btn').prop('disabled', true);}
1172
1173                                 return '\uf06a';
1174                             } else if (vciState === 'STALE') {
1175                                console.log("stale in text");
1176                                    $('#environmentType').prop('disabled', true);
1177                                        if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
1178                                            $('#operate-submit-btn').prop('disabled', false);
1179                                       }else{$('#operate-submit-btn').prop('disabled', true);}
1180                                return '\uf0aa';
1181                             } else if (vciState === 'LOCALLY_MODIFIED') {
1182                                 console.log("locally modified in text");
1183                                         $('#environmentType').prop('disabled', true);
1184                                         if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
1185                                             $('#operate-submit-btn').prop('disabled', false);
1186                                        }else{$('#operate-submit-btn').prop('disabled', true);}
1187                                return '\uf069';
1188                             } else {
1189                                 return '\uf00c';
1190                                 $('#environmentType').prop('disabled', false);
1191                                 if($('#environmentType').val() && !$('#environmentType').prop('disabled')){
1192                                    $('#operate-submit-btn').prop('disabled', false);
1193                                 }else{$('#operate-submit-btn').prop('disabled', true);}
1194                             }
1195                         } else {
1196                             $('#environmentType').prop('disabled', true);
1197                             return '';
1198                         }
1199                     });
1200
1201                 if (processGroupData.permissions.canRead) {
1202                     // version control tooltip
1203                     versionControl.each(function () {
1204                             // get the tip
1205                             var tip = d3.select('#version-control-tip-' + processGroupData.id);
1206
1207                             // if there are validation errors generate a tooltip
1208                             if (isUnderVersionControl(processGroupData)) {
1209                                 // create the tip if necessary
1210                                 if (tip.empty()) {
1211                                     tip = d3.select('#process-group-tooltips').append('div')
1212                                         .attr('id', function () {
1213                                             return 'version-control-tip-' + processGroupData.id;
1214                                         })
1215                                         .attr('class', 'tooltip nifi-tooltip');
1216                                 }
1217
1218                                 // update the tip
1219                                 tip.html(function () {
1220                                     var vci = processGroupData.component.versionControlInformation;
1221                                     var versionControlTip = $('<div></div>').text('Tracking to "' + vci.flowName + '" version ' + vci.version + ' in "' + vci.registryName + ' - ' + vci.bucketName + '"');
1222                                     var versionControlStateTip = $('<div></div>').text(nfCommon.getVersionControlTooltip(vci));
1223                                     return $('<div></div>').append(versionControlTip).append('<br/>').append(versionControlStateTip).html();
1224                                 });
1225
1226                                 // add the tooltip
1227                                 nfCanvasUtils.canvasTooltip(tip, d3.select(this));
1228                             } else {
1229                                 // remove the tip if necessary
1230                                 if (!tip.empty()) {
1231                                     tip.remove();
1232                                 }
1233                             }
1234                         });
1235
1236                     // update the process group comments
1237                     processGroup.select('path.component-comments')
1238                         .style('visibility', nfCommon.isBlank(processGroupData.component.comments) ? 'hidden' : 'visible')
1239                         .each(function () {
1240                             // get the tip
1241                             var tip = d3.select('#comments-tip-' + processGroupData.id);
1242
1243                             // if there are validation errors generate a tooltip
1244                             if (nfCommon.isBlank(processGroupData.component.comments)) {
1245                                 // remove the tip if necessary
1246                                 if (!tip.empty()) {
1247                                     tip.remove();
1248                                 }
1249                             } else {
1250                                 // create the tip if necessary
1251                                 if (tip.empty()) {
1252                                     tip = d3.select('#process-group-tooltips').append('div')
1253                                         .attr('id', function () {
1254                                             return 'comments-tip-' + processGroupData.id;
1255                                         })
1256                                         .attr('class', 'tooltip nifi-tooltip');
1257                                 }
1258
1259                                 // update the tip
1260                                 tip.text(processGroupData.component.comments);
1261
1262                                 // add the tooltip
1263                                 nfCanvasUtils.canvasTooltip(tip, d3.select(this));
1264                             }
1265                         });
1266
1267                     // update the process group name
1268                     processGroup.select('text.process-group-name')
1269                         .attrs({
1270                             'x': function () {
1271                                 if (isUnderVersionControl(processGroupData)) {
1272                                     var versionControlX = parseInt(versionControl.attr('x'), 10);
1273                                     return versionControlX + Math.round(versionControl.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
1274                                 } else {
1275                                     return 10;
1276                                 }
1277                             },
1278                             'width': function () {
1279                                 if (isUnderVersionControl(processGroupData)) {
1280                                     var versionControlX = parseInt(versionControl.attr('x'), 10);
1281                                     var processGroupNameX = parseInt(d3.select(this).attr('x'), 10);
1282                                     return 300 - (processGroupNameX - versionControlX);
1283                                 } else {
1284                                     return 300;
1285                                 }
1286                             }
1287                         })
1288                         .each(function (d) {
1289                             var processGroupName = d3.select(this);
1290
1291                             // reset the process group name to handle any previous state
1292                             processGroupName.text(null).selectAll('title').remove();
1293
1294                             // apply ellipsis to the process group name as necessary
1295                             nfCanvasUtils.ellipsis(processGroupName, d.component.name);
1296                         })
1297                         .append('title')
1298                         .text(function (d) {
1299                             return d.component.name;
1300                         });
1301                 } else {
1302                     // clear the process group comments
1303                     processGroup.select('path.component-comments').style('visibility', 'hidden');
1304
1305                     // clear the process group name
1306                     processGroup.select('text.process-group-name')
1307                         .attrs({
1308                             'x': 10,
1309                             'width': 316
1310                         })
1311                         .text(null);
1312
1313                     // clear tooltips
1314                     processGroup.call(removeTooltips);
1315                 }
1316
1317                 // populate the stats
1318                 processGroup.call(updateProcessGroupStatus);
1319             } else {
1320                 if (processGroupData.permissions.canRead) {
1321                     // update the process group name
1322                     processGroup.select('text.process-group-name')
1323                         .text(function (d) {
1324                             var name = d.component.name;
1325                             if (name.length > PREVIEW_NAME_LENGTH) {
1326                                 return name.substring(0, PREVIEW_NAME_LENGTH) + String.fromCharCode(8230);
1327                             } else {
1328                                 return name;
1329                             }
1330                         });
1331                 } else {
1332                     // clear the process group name
1333                     processGroup.select('text.process-group-name').text(null);
1334                 }
1335
1336                 // remove the tooltips
1337                 processGroup.call(removeTooltips);
1338
1339                 // remove the details if necessary
1340                 if (!details.empty()) {
1341                     details.remove();
1342                 }
1343             }
1344         });
1345     };
1346
1347     /**
1348      * Updates the process group status.
1349      *
1350      * @param {selection} updated           The process groups to be updated
1351      */
1352     var updateProcessGroupStatus = function (updated) {
1353         if (updated.empty()) {
1354             return;
1355         }
1356
1357         // queued count value
1358         updated.select('text.process-group-queued tspan.count')
1359             .text(function (d) {
1360                 return nfCommon.substringBeforeFirst(d.status.aggregateSnapshot.queued, ' ');
1361             });
1362
1363         // queued size value
1364         updated.select('text.process-group-queued tspan.size')
1365             .text(function (d) {
1366                 return ' ' + nfCommon.substringAfterFirst(d.status.aggregateSnapshot.queued, ' ');
1367             });
1368
1369         // in count value
1370         updated.select('text.process-group-in tspan.count')
1371             .text(function (d) {
1372                 return nfCommon.substringBeforeFirst(d.status.aggregateSnapshot.input, ' ');
1373             });
1374
1375         // in size value
1376         updated.select('text.process-group-in tspan.size')
1377             .text(function (d) {
1378                 return ' ' + nfCommon.substringAfterFirst(d.status.aggregateSnapshot.input, ' ');
1379             });
1380
1381         // in ports value
1382         updated.select('text.process-group-in tspan.ports')
1383             .text(function (d) {
1384                 return ' ' + String.fromCharCode(8594) + ' ' + d.inputPortCount;
1385             });
1386
1387         // read/write value
1388         updated.select('text.process-group-read-write')
1389             .text(function (d) {
1390                 return d.status.aggregateSnapshot.read + ' / ' + d.status.aggregateSnapshot.written;
1391             });
1392
1393         // out ports value
1394         updated.select('text.process-group-out tspan.ports')
1395             .text(function (d) {
1396                 return d.outputPortCount + ' ' + String.fromCharCode(8594) + ' ';
1397             });
1398
1399         // out count value
1400         updated.select('text.process-group-out tspan.count')
1401             .text(function (d) {
1402                 return nfCommon.substringBeforeFirst(d.status.aggregateSnapshot.output, ' ');
1403             });
1404
1405         // out size value
1406         updated.select('text.process-group-out tspan.size')
1407             .text(function (d) {
1408                 return ' ' + nfCommon.substringAfterFirst(d.status.aggregateSnapshot.output, ' ');
1409             });
1410
1411         updated.each(function (d) {
1412             var processGroup = d3.select(this);
1413             var offset = 0;
1414
1415             // -------------------
1416             // active thread count
1417             // -------------------
1418
1419             nfCanvasUtils.activeThreadCount(processGroup, d, function (off) {
1420                 offset = off;
1421             });
1422
1423             // ---------
1424             // bulletins
1425             // ---------
1426
1427             processGroup.select('rect.bulletin-background').classed('has-bulletins', function () {
1428                 return !nfCommon.isEmpty(d.status.aggregateSnapshot.bulletins);
1429             });
1430
1431             nfCanvasUtils.bulletins(processGroup, d, function () {
1432                 return d3.select('#process-group-tooltips');
1433             }, offset);
1434         });
1435     };
1436
1437     /**
1438      * Removes the process groups in the specified selection.
1439      *
1440      * @param {selection} removed               The process groups to be removed
1441      */
1442     var removeProcessGroups = function (removed) {
1443         if (removed.empty()) {
1444             return;
1445         }
1446
1447         removed.call(removeTooltips).remove();
1448     };
1449
1450     /**
1451      * Removes the tooltips for the process groups in the specified selection.
1452      *
1453      * @param {selection} removed
1454      */
1455     var removeTooltips = function (removed) {
1456         removed.each(function (d) {
1457             // remove any associated tooltips
1458             $('#bulletin-tip-' + d.id).remove();
1459             $('#version-control-tip-' + d.id).remove();
1460             $('#comments-tip-' + d.id).remove();
1461         });
1462     };
1463
1464     var nfProcessGroup = {
1465         /**
1466          * Initializes of the Process Group handler.
1467          *
1468          * @param nfConnectableRef   The nfConnectable module.
1469          * @param nfDraggableRef   The nfDraggable module.
1470          * @param nfSelectableRef   The nfSelectable module.
1471          * @param nfContextMenuRef   The nfContextMenu module.
1472          */
1473         init: function (nfConnectableRef, nfDraggableRef, nfSelectableRef, nfContextMenuRef) {
1474             nfConnectable = nfConnectableRef;
1475             nfDraggable = nfDraggableRef;
1476             nfSelectable = nfSelectableRef;
1477             nfContextMenu = nfContextMenuRef;
1478
1479             processGroupMap = d3.map();
1480             removedCache = d3.map();
1481             addedCache = d3.map();
1482
1483             // create the process group container
1484             processGroupContainer = d3.select('#canvas').append('g')
1485                 .attrs({
1486                     'pointer-events': 'all',
1487                     'class': 'process-groups'
1488                 });
1489         },
1490
1491         /**
1492          * Adds the specified process group entity.
1493          *
1494          * @param processGroupEntities       The process group
1495          * @param options           Configuration options
1496          */
1497         add: function (processGroupEntities, options) {
1498             var selectAll = false;
1499             if (nfCommon.isDefinedAndNotNull(options)) {
1500                 selectAll = nfCommon.isDefinedAndNotNull(options.selectAll) ? options.selectAll : selectAll;
1501             }
1502
1503             // get the current time
1504             var now = new Date().getTime();
1505
1506             var add = function (processGroupEntity) {
1507                 addedCache.set(processGroupEntity.id, now);
1508
1509                 // add the process group
1510                 processGroupMap.set(processGroupEntity.id, $.extend({
1511                     type: 'ProcessGroup',
1512                     dimensions: dimensions
1513                 }, processGroupEntity));
1514             };
1515
1516             // determine how to handle the specified process groups
1517             if ($.isArray(processGroupEntities)) {
1518                 $.each(processGroupEntities, function (_, processGroupEntity) {
1519                     add(processGroupEntity);
1520                 });
1521             } else if (nfCommon.isDefinedAndNotNull(processGroupEntities)) {
1522                 add(processGroupEntities);
1523             }
1524
1525             // select
1526             var selection = select();
1527
1528             // enter
1529             var entered = renderProcessGroups(selection.enter(), selectAll);
1530
1531             // update
1532             updateProcessGroups(selection.merge(entered));
1533         },
1534
1535         /**
1536          * Populates the graph with the specified process groups.
1537          *
1538          * @argument {object | array} processGroupEntities                    The process groups to add
1539          * @argument {object} options                Configuration options
1540          */
1541         set: function (processGroupEntities, options) {
1542             var selectAll = false;
1543             var transition = false;
1544             var overrideRevisionCheck = false;
1545             if (nfCommon.isDefinedAndNotNull(options)) {
1546                 selectAll = nfCommon.isDefinedAndNotNull(options.selectAll) ? options.selectAll : selectAll;
1547                 transition = nfCommon.isDefinedAndNotNull(options.transition) ? options.transition : transition;
1548                 overrideRevisionCheck = nfCommon.isDefinedAndNotNull(options.overrideRevisionCheck) ? options.overrideRevisionCheck : overrideRevisionCheck;
1549             }
1550
1551             var set = function (proposedProcessGroupEntity) {
1552                 var currentProcessGroupEntity = processGroupMap.get(proposedProcessGroupEntity.id);
1553
1554                 // set the process group if appropriate due to revision and wasn't previously removed
1555                 if ((nfClient.isNewerRevision(currentProcessGroupEntity, proposedProcessGroupEntity) && !removedCache.has(proposedProcessGroupEntity.id)) || overrideRevisionCheck === true) {
1556                     processGroupMap.set(proposedProcessGroupEntity.id, $.extend({
1557                         type: 'ProcessGroup',
1558                         dimensions: dimensions
1559                     }, proposedProcessGroupEntity));
1560                 }
1561             };
1562
1563             // determine how to handle the specified process groups
1564             if ($.isArray(processGroupEntities)) {
1565                 $.each(processGroupMap.keys(), function (_, key) {
1566                     var currentProcessGroupEntity = processGroupMap.get(key);
1567                     var isPresent = $.grep(processGroupEntities, function (proposedProcessGroupEntity) {
1568                         return proposedProcessGroupEntity.id === currentProcessGroupEntity.id;
1569                     });
1570
1571                     // if the current process group is not present and was not recently added, remove it
1572                     if (isPresent.length === 0 && !addedCache.has(key)) {
1573                         processGroupMap.remove(key);
1574                     }
1575                 });
1576                 $.each(processGroupEntities, function (_, processGroupEntity) {
1577                     set(processGroupEntity);
1578                 });
1579             } else if (nfCommon.isDefinedAndNotNull(processGroupEntities)) {
1580                 set(processGroupEntities);
1581             }
1582
1583             // select
1584             var selection = select();
1585
1586             // enter
1587             var entered = renderProcessGroups(selection.enter(), selectAll);
1588
1589             // update
1590             var updated = selection.merge(entered);
1591             updated.call(updateProcessGroups).call(nfCanvasUtils.position, transition);
1592
1593             // exit
1594             selection.exit().call(removeProcessGroups);
1595         },
1596
1597         /**
1598          * If the process group id is specified it is returned. If no process group id
1599          * specified, all process groups are returned.
1600          *
1601          * @param {string} id
1602          */
1603         get: function (id) {
1604             if (nfCommon.isUndefined(id)) {
1605                 return processGroupMap.values();
1606             } else {
1607                 return processGroupMap.get(id);
1608             }
1609         },
1610
1611         /**
1612          * If the process group id is specified it is refresh according to the current
1613          * state. If no process group id is specified, all process groups are refreshed.
1614          *
1615          * @param {string} id      Optional
1616          */
1617         refresh: function (id) {
1618             if (nfCommon.isDefinedAndNotNull(id)) {
1619                 d3.select('#id-' + id).call(updateProcessGroups);
1620             } else {
1621                 d3.selectAll('g.process-group').call(updateProcessGroups);
1622             }
1623         },
1624
1625         /**
1626          * Refreshes the components necessary after a pan event.
1627          */
1628         pan: function () {
1629             d3.selectAll('g.process-group.entering, g.process-group.leaving').call(updateProcessGroups);
1630         },
1631
1632         /**
1633          * Reloads the process group state from the server and refreshes the UI.
1634          * If the process group is currently unknown, this function reloads the canvas.
1635          *
1636          * @param {string} id The process group id
1637          */
1638         reload: function (id) {
1639             if (processGroupMap.has(id)) {
1640                 var processGroupEntity = processGroupMap.get(id);
1641                 return $.ajax({
1642                     type: 'GET',
1643                     url: processGroupEntity.uri,
1644                     dataType: 'json'
1645                 }).done(function (response) {
1646                     nfProcessGroup.set(response);
1647                 });
1648             }
1649         },
1650
1651         /**
1652          * Positions the component.
1653          *
1654          * @param {string} id   The id
1655          */
1656         position: function (id) {
1657             d3.select('#id-' + id).call(nfCanvasUtils.position);
1658         },
1659
1660         /**
1661          * Removes the specified process group.
1662          *
1663          * @param {string} processGroupIds      The process group id(s)
1664          */
1665         remove: function (processGroupIds) {
1666             var now = new Date().getTime();
1667
1668             if ($.isArray(processGroupIds)) {
1669                 $.each(processGroupIds, function (_, processGroupId) {
1670                     removedCache.set(processGroupId, now);
1671                     processGroupMap.remove(processGroupId);
1672                 });
1673             } else {
1674                 removedCache.set(processGroupIds, now);
1675                 processGroupMap.remove(processGroupIds);
1676             }
1677
1678             // apply the selection and handle all removed process groups
1679             select().exit().call(removeProcessGroups);
1680         },
1681
1682         /**
1683          * Removes all process groups.
1684          */
1685         removeAll: function () {
1686             nfProcessGroup.remove(processGroupMap.keys());
1687         },
1688
1689         /**
1690          * Expires the caches up to the specified timestamp.
1691          *
1692          * @param timestamp
1693          */
1694         expireCaches: function (timestamp) {
1695             var expire = function (cache) {
1696                 cache.each(function (entryTimestamp, id) {
1697                     if (timestamp > entryTimestamp) {
1698                         cache.remove(id);
1699                     }
1700                 });
1701             };
1702
1703             expire(addedCache);
1704             expire(removedCache);
1705         },
1706
1707         /**
1708          * Enters the specified group.
1709          *
1710          * @param {string} groupId
1711          */
1712         enterGroup: function (groupId) {
1713
1714             // hide the context menu
1715             nfContextMenu.hide();
1716
1717             // set the new group id
1718             nfCanvasUtils.setGroupId(groupId);
1719
1720             // reload the graph
1721             return nfCanvasUtils.reload().done(function () {
1722
1723                 // attempt to restore the view
1724                 var viewRestored = nfCanvasUtils.restoreUserView();
1725
1726                 // if the view was not restore attempt to fit
1727                 if (viewRestored === false) {
1728                     nfCanvasUtils.fitCanvas();
1729                 }
1730
1731                 // update URL deep linking params
1732                 nfCanvasUtils.setURLParameters(groupId, d3.select());
1733
1734             }).fail(function () {
1735                 nfDialog.showOkDialog({
1736                     headerText: 'Process Group',
1737                     dialogContent: 'Unable to enter the selected group.'
1738                 });
1739             });
1740         }
1741     };
1742
1743     return nfProcessGroup;
1744 }));