Add DCAE MOD design tool project
[dcaegen2/platform.git] / mod / designtool / designtool-web / src / main / webapp / js / nf / canvas / nf-connection-configuration.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 define, module, require, exports */
22
23 (function (root, factory) {
24     if (typeof define === 'function' && define.amd) {
25         define(['jquery',
26                 'd3',
27                 'nf.ErrorHandler',
28                 'nf.Common',
29                 'nf.Dialog',
30                 'nf.Storage',
31                 'nf.Client',
32                 'nf.CanvasUtils',
33                 'nf.Connection'],
34             function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) {
35                 return (nf.ConnectionConfiguration = factory($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection));
36             });
37     } else if (typeof exports === 'object' && typeof module === 'object') {
38         module.exports = (nf.ConnectionConfiguration =
39             factory(require('jquery'),
40                 require('d3'),
41                 require('nf.ErrorHandler'),
42                 require('nf.Common'),
43                 require('nf.Dialog'),
44                 require('nf.Storage'),
45                 require('nf.Client'),
46                 require('nf.CanvasUtils'),
47                 require('nf.Connection')));
48     } else {
49         nf.ConnectionConfiguration = factory(root.$,
50             root.d3,
51             root.nf.ErrorHandler,
52             root.nf.Common,
53             root.nf.Dialog,
54             root.nf.Storage,
55             root.nf.Client,
56             root.nf.CanvasUtils,
57             root.nf.Connection);
58     }
59 }(this, function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) {
60     'use strict';
61
62     var nfBirdseye;
63     var nfGraph;
64
65     var defaultBackPressureObjectThreshold;
66     var defaultBackPressureDataSizeThreshold;
67
68     var CONNECTION_OFFSET_Y_INCREMENT = 75;
69     var CONNECTION_OFFSET_X_INCREMENT = 200;
70
71     var config = {
72         urls: {
73             api: '../nifi-api',
74             prioritizers: '../nifi-api/flow/prioritizers'
75         }
76     };
77
78     /**
79      * Removes the temporary if necessary.
80      */
81     var removeTempEdge = function () {
82         d3.select('path.connector').remove();
83     };
84
85     /**
86      * Activates dialog's button model refresh on a connection relationships change.
87      */
88     var addDialogRelationshipsChangeListener = function() {
89         // refresh button model when a relationship selection changes
90         $('div.available-relationship').bind('change', function() {
91             $('#connection-configuration').modal('refreshButtons');
92         });
93     }
94
95     /**
96      * Initializes the source in the new connection dialog.
97      *
98      * @argument {selection} source        The source
99      */
100     var initializeSourceNewConnectionDialog = function (source) {
101         // handle the selected source
102         if (nfCanvasUtils.isProcessor(source)) {
103             return $.Deferred(function (deferred) {
104                 // initialize the source processor
105                 initializeSourceProcessor(source).done(function (processor) {
106                     if (!nfCommon.isEmpty(processor.relationships)) {
107                         // populate the available connections
108                         $.each(processor.relationships, function (i, relationship) {
109                             createRelationshipOption(relationship.name);
110                         });
111
112                         // resolve the deferred
113                         deferred.resolve();
114                     } else {
115                         // there are no relationships for this processor
116                         nfDialog.showOkDialog({
117                             headerText: 'Connection Configuration',
118                             dialogContent: '\'' + nfCommon.escapeHtml(processor.name) + '\' does not support any relationships.'
119                         });
120
121                         // reset the dialog
122                         resetDialog();
123
124                         deferred.reject();
125                     }
126                 }).fail(function () {
127                     deferred.reject();
128                 });
129             }).promise();
130         } else {
131             return $.Deferred(function (deferred) {
132                 // determine how to initialize the source
133                 var connectionSourceDeferred;
134                 if (nfCanvasUtils.isInputPort(source)) {
135                     connectionSourceDeferred = initializeSourceInputPort(source);
136                 } else if (nfCanvasUtils.isRemoteProcessGroup(source)) {
137                     connectionSourceDeferred = initializeSourceRemoteProcessGroup(source);
138                 } else if (nfCanvasUtils.isProcessGroup(source)) {
139                     connectionSourceDeferred = initializeSourceProcessGroup(source);
140                 } else {
141                     connectionSourceDeferred = initializeSourceFunnel(source);
142                 }
143
144                 // finish initialization when appropriate
145                 connectionSourceDeferred.done(function () {
146                     deferred.resolve();
147                 }).fail(function () {
148                     deferred.reject();
149                 });
150             }).promise();
151         }
152     };
153
154     /**
155      * Initializes the source when the source is an input port.
156      *
157      * @argument {selection} source        The source
158      */
159     var initializeSourceInputPort = function (source) {
160         return $.Deferred(function (deferred) {
161             // get the input port data
162             var inputPortData = source.datum();
163             var inputPortName = inputPortData.permissions.canRead ? inputPortData.component.name : inputPortData.id;
164
165             // populate the port information
166             $('#input-port-source').show();
167             $('#input-port-source-name').text(inputPortName).attr('title', inputPortName);
168
169             // populate the connection source details
170             $('#connection-source-id').val(inputPortData.id);
171             $('#connection-source-component-id').val(inputPortData.id);
172
173             // populate the group details
174             $('#connection-source-group-id').val(nfCanvasUtils.getGroupId());
175             $('#connection-source-group-name').text(nfCanvasUtils.getGroupName());
176
177             // resolve the deferred
178             deferred.resolve();
179         }).promise();
180     };
181
182     /**
183      * Initializes the source when the source is an input port.
184      *
185      * @argument {selection} source        The source
186      */
187     var initializeSourceFunnel = function (source) {
188         return $.Deferred(function (deferred) {
189             // get the funnel data
190             var funnelData = source.datum();
191
192             // populate the port information
193             $('#funnel-source').show();
194
195             // populate the connection source details
196             $('#connection-source-id').val(funnelData.id);
197             $('#connection-source-component-id').val(funnelData.id);
198
199             // populate the group details
200             $('#connection-source-group-id').val(nfCanvasUtils.getGroupId());
201             $('#connection-source-group-name').text(nfCanvasUtils.getGroupName());
202
203             // resolve the deferred
204             deferred.resolve();
205         }).promise();
206     };
207
208     /**
209      * Initializes the source when the source is a processor.
210      *
211      * @argument {selection} source        The source
212      */
213     var initializeSourceProcessor = function (source) {
214         return $.Deferred(function (deferred) {
215             // get the processor data
216             var processorData = source.datum();
217             var processorName = processorData.permissions.canRead ? processorData.component.name : processorData.id;
218             var processorType = processorData.permissions.canRead ? nfCommon.substringAfterLast(processorData.component.type, '.') : 'Processor';
219
220             // populate the source processor information
221             $('#processor-source').show();
222             $('#processor-source-name').text(processorName).attr('title', processorName);
223             $('#processor-source-type').text(processorType).attr('title', processorType);
224
225             // populate the connection source details
226             $('#connection-source-id').val(processorData.id);
227             $('#connection-source-component-id').val(processorData.id);
228
229             // populate the group details
230             $('#connection-source-group-id').val(nfCanvasUtils.getGroupId());
231             $('#connection-source-group-name').text(nfCanvasUtils.getGroupName());
232
233             // show the available relationships
234             $('#relationship-names-container').show();
235
236             deferred.resolve(processorData.component);
237         });
238     };
239
240     /**
241      * Initializes the source when the source is a process group.
242      *
243      * @argument {selection} source        The source
244      */
245     var initializeSourceProcessGroup = function (source) {
246         return $.Deferred(function (deferred) {
247             // get the process group data
248             var processGroupData = source.datum();
249
250             $.ajax({
251                 type: 'GET',
252                 url: config.urls.api + '/flow/process-groups/' + encodeURIComponent(processGroupData.id),
253                 dataType: 'json'
254             }).done(function (response) {
255                 var processGroup = response.processGroupFlow;
256                 var processGroupName = response.permissions.canRead ? processGroup.breadcrumb.breadcrumb.name : processGroup.id;
257                 var processGroupContents = processGroup.flow;
258
259                 // show the output port options
260                 var options = [];
261                 $.each(processGroupContents.outputPorts, function (i, outputPort) {
262                     // require explicit access to the output port as it's the source of the connection
263                     if (outputPort.permissions.canRead && outputPort.permissions.canWrite) {
264                         var component = outputPort.component;
265                         options.push({
266                             text: component.name,
267                             value: component.id,
268                             description: nfCommon.escapeHtml(component.comments)
269                         });
270                     }
271                 });
272
273                 // only proceed if there are output ports
274                 if (!nfCommon.isEmpty(options)) {
275                     $('#output-port-source').show();
276
277                     // sort the options
278                     options.sort(function (a, b) {
279                         return a.text.localeCompare(b.text);
280                     });
281
282                     // create the combo
283                     $('#output-port-options').combo({
284                         options: options,
285                         maxHeight: 300,
286                         select: function (option) {
287                             $('#connection-source-id').val(option.value);
288                         }
289                     });
290
291                     // populate the connection details
292                     $('#connection-source-component-id').val(processGroup.id);
293
294                     // populate the group details
295                     $('#connection-source-group-id').val(processGroup.id);
296                     $('#connection-source-group-name').text(processGroupName);
297
298                     deferred.resolve();
299                 } else {
300                     var message = '\'' + nfCommon.escapeHtml(processGroupName) + '\' does not have any output ports.';
301                     if (nfCommon.isEmpty(processGroupContents.outputPorts) === false) {
302                         message = 'Not authorized for any output ports in \'' + nfCommon.escapeHtml(processGroupName) + '\'.';
303                     }
304
305                     // there are no output ports for this process group
306                     nfDialog.showOkDialog({
307                         headerText: 'Connection Configuration',
308                         dialogContent: message
309                     });
310
311                     // reset the dialog
312                     resetDialog();
313
314                     deferred.reject();
315                 }
316             }).fail(function (xhr, status, error) {
317                 // handle the error
318                 nfErrorHandler.handleAjaxError(xhr, status, error);
319
320                 deferred.reject();
321             });
322         }).promise();
323     };
324
325     /**
326      * Initializes the source when the source is a remote process group.
327      *
328      * @argument {selection} source        The source
329      */
330     var initializeSourceRemoteProcessGroup = function (source) {
331         return $.Deferred(function (deferred) {
332             // get the remote process group data
333             var remoteProcessGroupData = source.datum();
334
335             $.ajax({
336                 type: 'GET',
337                 url: remoteProcessGroupData.uri,
338                 dataType: 'json'
339             }).done(function (response) {
340                 var remoteProcessGroup = response.component;
341                 var remoteProcessGroupContents = remoteProcessGroup.contents;
342
343                 // only proceed if there are output ports
344                 if (!nfCommon.isEmpty(remoteProcessGroupContents.outputPorts)) {
345                     $('#output-port-source').show();
346
347                     // show the output port options
348                     var options = [];
349                     $.each(remoteProcessGroupContents.outputPorts, function (i, outputPort) {
350                         options.push({
351                             text: outputPort.name,
352                             value: outputPort.id,
353                             disabled: outputPort.exists === false,
354                             description: nfCommon.escapeHtml(outputPort.comments)
355                         });
356                     });
357
358                     // sort the options
359                     options.sort(function (a, b) {
360                         return a.text.localeCompare(b.text);
361                     });
362
363                     // create the combo
364                     $('#output-port-options').combo({
365                         options: options,
366                         maxHeight: 300,
367                         select: function (option) {
368                             $('#connection-source-id').val(option.value);
369                         }
370                     });
371
372                     // populate the connection details
373                     $('#connection-source-component-id').val(remoteProcessGroup.id);
374
375                     // populate the group details
376                     $('#connection-source-group-id').val(remoteProcessGroup.id);
377                     $('#connection-source-group-name').text(remoteProcessGroup.name);
378
379                     deferred.resolve();
380                 } else {
381                     // there are no relationships for this processor
382                     nfDialog.showOkDialog({
383                         headerText: 'Connection Configuration',
384                         dialogContent: '\'' + nfCommon.escapeHtml(remoteProcessGroup.name) + '\' does not have any output ports.'
385                     });
386
387                     // reset the dialog
388                     resetDialog();
389
390                     deferred.reject();
391                 }
392             }).fail(function (xhr, status, error) {
393                 // handle the error
394                 nfErrorHandler.handleAjaxError(xhr, status, error);
395
396                 deferred.reject();
397             });
398         }).promise();
399     };
400
401     var initializeDestinationNewConnectionDialog = function (destination) {
402         if (nfCanvasUtils.isOutputPort(destination)) {
403             return initializeDestinationOutputPort(destination);
404         } else if (nfCanvasUtils.isProcessor(destination)) {
405             return $.Deferred(function (deferred) {
406               initializeDestinationProcessor(destination).done(function (processor) {
407                 // Need to add the destination relationships because we need to
408                 // provide this to wire up the publishers and subscribers correctly
409                 // for a given connection since processors can have multiple
410                 // relationships
411                 $.each(processor.relationships, function (i, relationship) {
412                     createRelationshipOption(relationship.name);
413                 });
414
415                 deferred.resolve();
416               }).fail(function () {
417                 deferred.reject();
418               });
419             }).promise();
420         } else if (nfCanvasUtils.isRemoteProcessGroup(destination)) {
421             return initializeDestinationRemoteProcessGroup(destination);
422         } else if (nfCanvasUtils.isFunnel(destination)) {
423             return initializeDestinationFunnel(destination);
424         } else {
425             return initializeDestinationProcessGroup(destination);
426         }
427     };
428
429     var initializeDestinationOutputPort = function (destination) {
430         return $.Deferred(function (deferred) {
431             var outputPortData = destination.datum();
432             var outputPortName = outputPortData.permissions.canRead ? outputPortData.component.name : outputPortData.id;
433
434             $('#output-port-destination').show();
435             $('#output-port-destination-name').text(outputPortName).attr('title', outputPortName);
436
437             // populate the connection destination details
438             $('#connection-destination-id').val(outputPortData.id);
439             $('#connection-destination-component-id').val(outputPortData.id);
440
441             // populate the group details
442             $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId());
443             $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName());
444
445             deferred.resolve();
446         }).promise();
447     };
448
449     var initializeDestinationFunnel = function (destination) {
450         return $.Deferred(function (deferred) {
451             var funnelData = destination.datum();
452
453             $('#funnel-destination').show();
454
455             // populate the connection destination details
456             $('#connection-destination-id').val(funnelData.id);
457             $('#connection-destination-component-id').val(funnelData.id);
458
459             // populate the group details
460             $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId());
461             $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName());
462
463             deferred.resolve();
464         }).promise();
465     };
466
467     var initializeDestinationProcessor = function (destination) {
468         return $.Deferred(function (deferred) {
469             var processorData = destination.datum();
470             var processorName = processorData.permissions.canRead ? processorData.component.name : processorData.id;
471             var processorType = processorData.permissions.canRead ? nfCommon.substringAfterLast(processorData.component.type, '.') : 'Processor';
472
473             $('#processor-destination').show();
474             $('#processor-destination-name').text(processorName).attr('title', processorName);
475             $('#processor-destination-type').text(processorType).attr('title', processorType);
476
477             // populate the connection destination details
478             $('#connection-destination-id').val(processorData.id);
479             $('#connection-destination-component-id').val(processorData.id);
480
481             // populate the group details
482             $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId());
483             $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName());
484
485             deferred.resolve(processorData.component);
486         }).promise();
487     };
488
489     /**
490      * Initializes the destination when the destination is a process group.
491      *
492      * @argument {selection} destination        The destination
493      */
494     var initializeDestinationProcessGroup = function (destination) {
495         return $.Deferred(function (deferred) {
496             var processGroupData = destination.datum();
497
498             $.ajax({
499                 type: 'GET',
500                 url: config.urls.api + '/flow/process-groups/' + encodeURIComponent(processGroupData.id),
501                 dataType: 'json'
502             }).done(function (response) {
503                 var processGroup = response.processGroupFlow;
504                 var processGroupName = response.permissions.canRead ? processGroup.breadcrumb.breadcrumb.name : processGroup.id;
505                 var processGroupContents = processGroup.flow;
506
507                 // show the input port options
508                 var options = [];
509                 $.each(processGroupContents.inputPorts, function (i, inputPort) {
510                     options.push({
511                         text: inputPort.permissions.canRead ? inputPort.component.name : inputPort.id,
512                         value: inputPort.id,
513                         description: inputPort.permissions.canRead ? nfCommon.escapeHtml(inputPort.component.comments) : null
514                     });
515                 });
516
517                 // only proceed if there are output ports
518                 if (!nfCommon.isEmpty(options)) {
519                     $('#input-port-destination').show();
520
521                     // sort the options
522                     options.sort(function (a, b) {
523                         return a.text.localeCompare(b.text);
524                     });
525
526                     // create the combo
527                     $('#input-port-options').combo({
528                         options: options,
529                         maxHeight: 300,
530                         select: function (option) {
531                             $('#connection-destination-id').val(option.value);
532                         }
533                     });
534
535                     // populate the connection details
536                     $('#connection-destination-component-id').val(processGroup.id);
537
538                     // populate the group details
539                     $('#connection-destination-group-id').val(processGroup.id);
540                     $('#connection-destination-group-name').text(processGroupName);
541
542                     deferred.resolve();
543                 } else {
544                     // there are no relationships for this processor
545                     nfDialog.showOkDialog({
546                         headerText: 'Connection Configuration',
547                         dialogContent: '\'' + nfCommon.escapeHtml(processGroupName) + '\' does not have any input ports.'
548                     });
549
550                     // reset the dialog
551                     resetDialog();
552
553                     deferred.reject();
554                 }
555             }).fail(function (xhr, status, error) {
556                 // handle the error
557                 nfErrorHandler.handleAjaxError(xhr, status, error);
558
559                 deferred.reject();
560             });
561         }).promise();
562     };
563
564     /**
565      * Initializes the source when the source is a remote process group.
566      *
567      * @argument {selection} destination        The destination
568      * @argument {object} connectionDestination The connection destination object
569      */
570     var initializeDestinationRemoteProcessGroup = function (destination, connectionDestination) {
571         return $.Deferred(function (deferred) {
572             var remoteProcessGroupData = destination.datum();
573
574             $.ajax({
575                 type: 'GET',
576                 url: remoteProcessGroupData.uri,
577                 dataType: 'json'
578             }).done(function (response) {
579                 var remoteProcessGroup = response.component;
580                 var remoteProcessGroupContents = remoteProcessGroup.contents;
581
582                 // only proceed if there are output ports
583                 if (!nfCommon.isEmpty(remoteProcessGroupContents.inputPorts)) {
584                     $('#input-port-destination').show();
585
586                     // show the input port options
587                     var options = [];
588                     $.each(remoteProcessGroupContents.inputPorts, function (i, inputPort) {
589                         options.push({
590                             text: inputPort.name,
591                             value: inputPort.id,
592                             disabled: inputPort.exists === false,
593                             description: nfCommon.escapeHtml(inputPort.comments)
594                         });
595                     });
596
597                     // sort the options
598                     options.sort(function (a, b) {
599                         return a.text.localeCompare(b.text);
600                     });
601
602                     // create the combo
603                     $('#input-port-options').combo({
604                         options: options,
605                         maxHeight: 300,
606                         select: function (option) {
607                             $('#connection-destination-id').val(option.value);
608                         }
609                     });
610
611                     // populate the connection details
612                     $('#connection-destination-component-id').val(remoteProcessGroup.id);
613
614                     // populate the group details
615                     $('#connection-destination-group-id').val(remoteProcessGroup.id);
616                     $('#connection-destination-group-name').text(remoteProcessGroup.name);
617
618                     deferred.resolve();
619                 } else {
620                     // there are no relationships for this processor
621                     nfDialog.showOkDialog({
622                         headerText: 'Connection Configuration',
623                         dialogContent: '\'' + nfCommon.escapeHtml(remoteProcessGroup.name) + '\' does not have any input ports.'
624                     });
625
626                     // reset the dialog
627                     resetDialog();
628
629                     deferred.reject();
630                 }
631             }).fail(function (xhr, status, error) {
632                 // handle the error
633                 nfErrorHandler.handleAjaxError(xhr, status, error);
634
635                 deferred.reject();
636             });
637         }).promise();
638     };
639
640     /**
641      * Initializes the source panel for groups.
642      *
643      * @argument {selection} source    The source of the connection
644      */
645     var initializeSourceReadOnlyGroup = function (source) {
646         return $.Deferred(function (deferred) {
647             var sourceData = source.datum();
648             var sourceName = sourceData.permissions.canRead ? sourceData.component.name : sourceData.id;
649
650             // populate the port information
651             $('#read-only-output-port-source').show();
652
653             // populate the component information
654             $('#connection-source-component-id').val(sourceData.id);
655
656             // populate the group details
657             $('#connection-source-group-id').val(sourceData.id);
658             $('#connection-source-group-name').text(sourceName);
659
660             // resolve the deferred
661             deferred.resolve();
662         }).promise();
663     };
664
665     /**
666      * Initializes the source in the existing connection dialog.
667      *
668      * @argument {selection} source        The source
669      */
670     var initializeSourceEditConnectionDialog = function (source) {
671         if (nfCanvasUtils.isProcessor(source)) {
672             return initializeSourceProcessor(source);
673         } else if (nfCanvasUtils.isInputPort(source)) {
674             return initializeSourceInputPort(source);
675         } else if (nfCanvasUtils.isFunnel(source)) {
676             return initializeSourceFunnel(source);
677         } else {
678             return initializeSourceReadOnlyGroup(source);
679         }
680     };
681
682     /**
683      * Initializes the destination in the existing connection dialog.
684      *
685      * @argument {selection} destination        The destination
686      * @argument {object} connectionDestination The connection destination object
687      */
688     var initializeDestinationEditConnectionDialog = function (destination, connectionDestination) {
689         if (nfCanvasUtils.isProcessor(destination)) {
690             return initializeDestinationProcessor(destination);
691         } else if (nfCanvasUtils.isOutputPort(destination)) {
692             return initializeDestinationOutputPort(destination);
693         } else if (nfCanvasUtils.isRemoteProcessGroup(destination)) {
694             return initializeDestinationRemoteProcessGroup(destination, connectionDestination);
695         } else if (nfCanvasUtils.isFunnel(destination)) {
696             return initializeDestinationFunnel(destination);
697         } else {
698             return initializeDestinationProcessGroup(destination);
699         }
700     };
701
702     /**
703      * Creates an option for the specified relationship name.
704      *
705      * @argument {string} name      The relationship name
706      */
707     var createRelationshipOption = function (name) {
708         var nameSplit = name.split(":");
709         var nameLabel = name;
710
711         if (nameSplit.length > 1) {
712             // Example: publishes:data_transformation_format:1.0.0:message_router:stream_publish_url
713             var pubSub = nameSplit[0];
714             pubSub = pubSub.charAt(0).toUpperCase() + pubSub.slice(1);
715             nameLabel = pubSub + " " + nameSplit[1] + "/" + nameSplit[2] + " on " + nameSplit[4];
716         }
717
718         var relationshipLabel = $('<div class="relationship-name nf-checkbox-label ellipsis"></div>').text(nameLabel);
719         var relationshipValue = $('<span class="relationship-name-value hidden"></span>').text(name);
720         return $('<div class="available-relationship-container"><div class="available-relationship nf-checkbox checkbox-unchecked"></div>' +
721             '</div>').append(relationshipLabel).append(relationshipValue).appendTo('#relationship-names');
722     };
723
724     /**
725      * Adds a new connection.
726      *
727      * @argument {array} selectedRelationships      The selected relationships
728      */
729     var addConnection = function (selectedRelationships) {
730         // get the connection details
731         var sourceId = $('#connection-source-id').val();
732         var destinationId = $('#connection-destination-id').val();
733
734         // get the selection components
735         var sourceComponentId = $('#connection-source-component-id').val();
736         var source = d3.select('#id-' + sourceComponentId);
737         var destinationComponentId = $('#connection-destination-component-id').val();
738         var destination = d3.select('#id-' + destinationComponentId);
739
740         // get the source/destination data
741         var sourceData = source.datum();
742         var destinationData = destination.datum();
743
744         // add bend points if we're dealing with a self loop
745         var bends = [];
746         if (sourceComponentId === destinationComponentId) {
747             var rightCenter = {
748                 x: sourceData.position.x + (sourceData.dimensions.width),
749                 y: sourceData.position.y + (sourceData.dimensions.height / 2)
750             };
751
752             var xOffset = nfConnection.config.selfLoopXOffset;
753             var yOffset = nfConnection.config.selfLoopYOffset;
754             bends.push({
755                 'x': (rightCenter.x + xOffset),
756                 'y': (rightCenter.y - yOffset)
757             });
758             bends.push({
759                 'x': (rightCenter.x + xOffset),
760                 'y': (rightCenter.y + yOffset)
761             });
762         } else {
763             var existingConnections = [];
764
765             // get all connections for the source component
766             var connectionsForSourceComponent = nfConnection.getComponentConnections(sourceComponentId);
767             $.each(connectionsForSourceComponent, function (_, connectionForSourceComponent) {
768                 // get the id for the source/destination component
769                 var connectionSourceComponentId = nfCanvasUtils.getConnectionSourceComponentId(connectionForSourceComponent);
770                 var connectionDestinationComponentId = nfCanvasUtils.getConnectionDestinationComponentId(connectionForSourceComponent);
771
772                 // if the connection is between these same components, consider it for collisions
773                 if ((connectionSourceComponentId === sourceComponentId && connectionDestinationComponentId === destinationComponentId) ||
774                     (connectionDestinationComponentId === sourceComponentId && connectionSourceComponentId === destinationComponentId)) {
775
776                     // record all connections between these two components in question
777                     existingConnections.push(connectionForSourceComponent);
778                 }
779             });
780
781             // if there are existing connections between these components, ensure the new connection won't collide
782             if (existingConnections.length > 0) {
783                 var avoidCollision = false;
784                 $.each(existingConnections, function (_, existingConnection) {
785                     // only consider multiple connections with no bend points a collision, the existance of 
786                     // bend points suggests that the user has placed the connection into a desired location
787                     if (nfCommon.isEmpty(existingConnection.bends)) {
788                         avoidCollision = true;
789                         return false;
790                     }
791                 });
792
793                 // if we need to avoid a collision
794                 if (avoidCollision === true) {
795                     // determine the middle of the source/destination components
796                     var sourceMiddle = [sourceData.position.x + (sourceData.dimensions.width / 2), sourceData.position.y + (sourceData.dimensions.height / 2)];
797                     var destinationMiddle = [destinationData.position.x + (destinationData.dimensions.width / 2), destinationData.position.y + (destinationData.dimensions.height / 2)];
798
799                     // detect if the line is more horizontal or vertical
800                     var slope = ((sourceMiddle[1] - destinationMiddle[1]) / (sourceMiddle[0] - destinationMiddle[0]));
801                     var isMoreHorizontal = slope <= 1 && slope >= -1;
802
803                     // determines if the specified coordinate collides with another connection
804                     var collides = function (x, y) {
805                         var collides = false;
806                         $.each(existingConnections, function (_, existingConnection) {
807                             if (!nfCommon.isEmpty(existingConnection.bends)) {
808                                 if (isMoreHorizontal) {
809                                     // horizontal lines are adjusted in the y space
810                                     if (existingConnection.bends[0].y === y) {
811                                         collides = true;
812                                         return false;
813                                     }
814                                 } else {
815                                     // vertical lines are adjusted in the x space
816                                     if (existingConnection.bends[0].x === x) {
817                                         collides = true;
818                                         return false;
819                                     }
820                                 }
821                             }
822                         });
823                         return collides;
824                     };
825
826                     // find the mid point on the connection
827                     var xCandidate = (sourceMiddle[0] + destinationMiddle[0]) / 2;
828                     var yCandidate = (sourceMiddle[1] + destinationMiddle[1]) / 2;
829
830                     // attempt to position this connection so it doesn't collide
831                     var xStep = isMoreHorizontal ? 0 : CONNECTION_OFFSET_X_INCREMENT;
832                     var yStep = isMoreHorizontal ? CONNECTION_OFFSET_Y_INCREMENT : 0;
833                     var positioned = false;
834                     while (positioned === false) {
835                         // consider above and below, then increment and try again (if necessary)
836                         if (collides(xCandidate - xStep, yCandidate - yStep) === false) {
837                             bends.push({
838                                 'x': (xCandidate - xStep),
839                                 'y': (yCandidate - yStep)
840                             });
841                             positioned = true;
842                         } else if (collides(xCandidate + xStep, yCandidate + yStep) === false) {
843                             bends.push({
844                                 'x': (xCandidate + xStep),
845                                 'y': (yCandidate + yStep)
846                             });
847                             positioned = true;
848                         }
849
850                         if (isMoreHorizontal) {
851                             yStep += CONNECTION_OFFSET_Y_INCREMENT;
852                         } else {
853                             xStep += CONNECTION_OFFSET_X_INCREMENT;
854                         }
855                     }
856                 }
857             }
858         }
859
860         // determine the source group id
861         var sourceGroupId = $('#connection-source-group-id').val();
862         var destinationGroupId = $('#connection-destination-group-id').val();
863
864         // determine the source and destination types
865         var sourceType = nfCanvasUtils.getConnectableTypeForSource(source);
866         var destinationType = nfCanvasUtils.getConnectableTypeForDestination(destination);
867
868         // get the settings
869         var connectionName = $('#connection-name').val();
870         var flowFileExpiration = $('#flow-file-expiration').val();
871         var backPressureObjectThreshold = $('#back-pressure-object-threshold').val();
872         var backPressureDataSizeThreshold = $('#back-pressure-data-size-threshold').val();
873         var prioritizers = $('#prioritizer-selected').sortable('toArray');
874         var loadBalanceStrategy = $('#load-balance-strategy-combo').combo('getSelectedOption').value;
875         var shouldLoadBalance = 'DO_NOT_LOAD_BALANCE' !== loadBalanceStrategy;
876         var loadBalancePartitionAttribute = shouldLoadBalance && 'PARTITION_BY_ATTRIBUTE' === loadBalanceStrategy ? $('#load-balance-partition-attribute').val() : '';
877         var loadBalanceCompression = shouldLoadBalance ? $('#load-balance-compression-combo').combo('getSelectedOption').value : 'DO_NOT_COMPRESS';
878
879         if (validateSettings()) {
880             var connectionEntity = {
881                 'revision': nfClient.getRevision({
882                     'revision': {
883                         'version': 0
884                     }
885                 }),
886                 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
887                 'component': {
888                     'name': connectionName,
889                     'source': {
890                         'id': sourceId,
891                         'groupId': sourceGroupId,
892                         'type': sourceType
893                     },
894                     'destination': {
895                         'id': destinationId,
896                         'groupId': destinationGroupId,
897                         'type': destinationType
898                     },
899                     'selectedRelationships': selectedRelationships,
900                     'flowFileExpiration': flowFileExpiration,
901                     'backPressureDataSizeThreshold': backPressureDataSizeThreshold,
902                     'backPressureObjectThreshold': backPressureObjectThreshold,
903                     'bends': bends,
904                     'prioritizers': prioritizers,
905                     'loadBalanceStrategy': loadBalanceStrategy,
906                     'loadBalancePartitionAttribute': loadBalancePartitionAttribute,
907                     'loadBalanceCompression': loadBalanceCompression
908                 }
909             };
910
911             // create the new connection
912             $.ajax({
913                 type: 'POST',
914                 url: config.urls.api + '/process-groups/' + encodeURIComponent(nfCanvasUtils.getGroupId()) + '/connections',
915                 data: JSON.stringify(connectionEntity),
916                 dataType: 'json',
917                 contentType: 'application/json'
918             }).done(function (response) {
919                 // add the connection
920                 nfGraph.add({
921                     'connections': [response]
922                 }, {
923                     'selectAll': true
924                 });
925
926                 // reload the connections source/destination components
927                 nfCanvasUtils.reloadConnectionSourceAndDestination(sourceComponentId, destinationComponentId);
928
929                 // update component visibility
930                 nfGraph.updateVisibility();
931
932                 // update the birdseye
933                 nfBirdseye.refresh();
934             }).fail(function (xhr, status, error) {
935                 // handle the error
936                 nfErrorHandler.handleAjaxError(xhr, status, error);
937             });
938         }
939     };
940
941     /**
942      * Updates an existing connection.
943      *
944      * @argument {array} selectedRelationships          The selected relationships
945      */
946     var updateConnection = function (selectedRelationships) {
947         // get the connection details
948         var connectionId = $('#connection-id').text();
949         var connectionUri = $('#connection-uri').val();
950
951         // get the source details
952         var sourceComponentId = $('#connection-source-component-id').val();
953
954         // get the destination details
955         var destinationComponentId = $('#connection-destination-component-id').val();
956         var destination = d3.select('#id-' + destinationComponentId);
957         var destinationType = nfCanvasUtils.getConnectableTypeForDestination(destination);
958
959         // get the destination details
960         var destinationId = $('#connection-destination-id').val();
961         var destinationGroupId = $('#connection-destination-group-id').val();
962
963         // get the settings
964         var connectionName = $('#connection-name').val();
965         var flowFileExpiration = $('#flow-file-expiration').val();
966         var backPressureObjectThreshold = $('#back-pressure-object-threshold').val();
967         var backPressureDataSizeThreshold = $('#back-pressure-data-size-threshold').val();
968         var prioritizers = $('#prioritizer-selected').sortable('toArray');
969         var loadBalanceStrategy = $('#load-balance-strategy-combo').combo('getSelectedOption').value;
970         var shouldLoadBalance = 'DO_NOT_LOAD_BALANCE' !== loadBalanceStrategy;
971         var loadBalancePartitionAttribute = shouldLoadBalance && 'PARTITION_BY_ATTRIBUTE' === loadBalanceStrategy ? $('#load-balance-partition-attribute').val() : '';
972         var loadBalanceCompression = shouldLoadBalance ? $('#load-balance-compression-combo').combo('getSelectedOption').value : 'DO_NOT_COMPRESS';
973
974         if (validateSettings()) {
975             var d = nfConnection.get(connectionId);
976             var connectionEntity = {
977                 'revision': nfClient.getRevision(d),
978                 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
979                 'component': {
980                     'id': connectionId,
981                     'name': connectionName,
982                     'destination': {
983                         'id': destinationId,
984                         'groupId': destinationGroupId,
985                         'type': destinationType
986                     },
987                     'selectedRelationships': selectedRelationships,
988                     'flowFileExpiration': flowFileExpiration,
989                     'backPressureDataSizeThreshold': backPressureDataSizeThreshold,
990                     'backPressureObjectThreshold': backPressureObjectThreshold,
991                     'prioritizers': prioritizers,
992                     'loadBalanceStrategy': loadBalanceStrategy,
993                     'loadBalancePartitionAttribute': loadBalancePartitionAttribute,
994                     'loadBalanceCompression': loadBalanceCompression
995                 }
996             };
997
998             // update the connection
999             return $.ajax({
1000                 type: 'PUT',
1001                 url: connectionUri,
1002                 data: JSON.stringify(connectionEntity),
1003                 dataType: 'json',
1004                 contentType: 'application/json'
1005             }).done(function (response) {
1006                 // update this connection
1007                 nfConnection.set(response);
1008
1009                 // reload the connections source/destination components
1010                 nfCanvasUtils.reloadConnectionSourceAndDestination(sourceComponentId, destinationComponentId);
1011             }).fail(function (xhr, status, error) {
1012                 if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) {
1013                     nfDialog.showOkDialog({
1014                         headerText: 'Connection Configuration',
1015                         dialogContent: nfCommon.escapeHtml(xhr.responseText),
1016                     });
1017                 } else {
1018                     nfErrorHandler.handleAjaxError(xhr, status, error);
1019                 }
1020             });
1021         } else {
1022             return $.Deferred(function (deferred) {
1023                 deferred.reject();
1024             }).promise();
1025         }
1026     };
1027
1028     /**
1029      * Returns an array of selected relationship names.
1030      */
1031     var getSelectedRelationships = function () {
1032         // get all available relationships
1033         var availableRelationships = $('#relationship-names');
1034         var selectedRelationships = [];
1035
1036         // go through each relationship to determine which are selected
1037         $.each(availableRelationships.children(), function (i, relationshipElement) {
1038             var relationship = $(relationshipElement);
1039
1040             // get each relationship and its corresponding checkbox
1041             var relationshipCheck = relationship.children('div.available-relationship');
1042
1043             // see if this relationship has been selected
1044             if (relationshipCheck.hasClass('checkbox-checked')) {
1045                 selectedRelationships.push(relationship.children('span.relationship-name-value').text());
1046             }
1047         });
1048
1049         return selectedRelationships;
1050     };
1051
1052     /**
1053      * Validates the specified settings.
1054      */
1055     var validateSettings = function () {
1056         var errors = [];
1057
1058         // validate the settings
1059         if (nfCommon.isBlank($('#flow-file-expiration').val())) {
1060             errors.push('File expiration must be specified');
1061         }
1062         if (!$.isNumeric($('#back-pressure-object-threshold').val())) {
1063             errors.push('Back pressure object threshold must be an integer value');
1064         }
1065         if (nfCommon.isBlank($('#back-pressure-data-size-threshold').val())) {
1066             errors.push('Back pressure data size threshold must be specified');
1067         }
1068         if ($('#load-balance-strategy-combo').combo('getSelectedOption').value === 'PARTITION_BY_ATTRIBUTE'
1069             && nfCommon.isBlank($('#load-balance-partition-attribute').val())) {
1070             errors.push('Cannot set Load Balance Strategy to "Partition by attribute" without providing a partitioning "Attribute Name"');
1071         }
1072
1073         if (errors.length > 0) {
1074             nfDialog.showOkDialog({
1075                 headerText: 'Connection Configuration',
1076                 dialogContent: nfCommon.formatUnorderedList(errors)
1077             });
1078             return false;
1079         } else {
1080             return true;
1081         }
1082     };
1083
1084     /**
1085      * Resets the dialog.
1086      */
1087     var resetDialog = function () {
1088         // reset the prioritizers
1089         var selectedList = $('#prioritizer-selected');
1090         var availableList = $('#prioritizer-available');
1091         selectedList.children().detach().appendTo(availableList);
1092
1093         // sort the available list
1094         var listItems = availableList.children('li').get();
1095         listItems.sort(function (a, b) {
1096             var compA = $(a).text().toUpperCase();
1097             var compB = $(b).text().toUpperCase();
1098             return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
1099         });
1100
1101         // clear the available list and re-insert each list item
1102         $.each(listItems, function () {
1103             $(this).detach();
1104         });
1105         $.each(listItems, function () {
1106             $(this).appendTo(availableList);
1107         });
1108
1109         // reset the fields
1110         $('#connection-name').val('');
1111         $('#relationship-names').css('border-width', '0').empty();
1112         $('#relationship-names-container').show();
1113
1114         // clear the id field
1115         nfCommon.clearField('connection-id');
1116
1117         // hide all the connection source panels
1118         $('#processor-source').hide();
1119         $('#input-port-source').hide();
1120         $('#output-port-source').hide();
1121         $('#read-only-output-port-source').hide();
1122         $('#funnel-source').hide();
1123
1124         // hide all the connection destination panels
1125         $('#processor-destination').hide();
1126         $('#input-port-destination').hide();
1127         $('#output-port-destination').hide();
1128         $('#funnel-destination').hide();
1129
1130         // clear and destination details
1131         $('#connection-source-id').val('');
1132         $('#connection-source-component-id').val('');
1133         $('#connection-source-group-id').val('');
1134
1135         // clear any destination details
1136         $('#connection-destination-id').val('');
1137         $('#connection-destination-component-id').val('');
1138         $('#connection-destination-group-id').val('');
1139
1140         // clear any ports
1141         $('#output-port-options').empty();
1142         $('#input-port-options').empty();
1143
1144         // clear load balance settings
1145         $('#load-balance-strategy-combo').combo('setSelectedOption', nfCommon.loadBalanceStrategyOptions[0]);
1146         $('#load-balance-partition-attribute').val('');
1147         $('#load-balance-compression-combo').combo('setSelectedOption', nfCommon.loadBalanceCompressionOptions[0]);
1148
1149         // see if the temp edge needs to be removed
1150         removeTempEdge();
1151     };
1152
1153     var nfConnectionConfiguration = {
1154
1155         /**
1156          * Initialize the connection configuration.
1157          *
1158          * @param nfBirdseyeRef   The nfBirdseye module.
1159          * @param nfGraphRef   The nfGraph module.
1160          */
1161         init: function (nfBirdseyeRef, nfGraphRef, defaultBackPressureObjectThresholdRef, defaultBackPressureDataSizeThresholdRef) {
1162             nfBirdseye = nfBirdseyeRef;
1163             nfGraph = nfGraphRef;
1164
1165             defaultBackPressureObjectThreshold = defaultBackPressureObjectThresholdRef;
1166             defaultBackPressureDataSizeThreshold = defaultBackPressureDataSizeThresholdRef;
1167
1168             // initially hide the relationship names container
1169             $('#relationship-names-container').show();
1170
1171             // initialize the configure connection dialog
1172             $('#connection-configuration').modal({
1173                 scrollableContentStyle: 'scrollable',
1174                 headerText: 'Configure Connection',
1175                 handler: {
1176                     close: function () {
1177                         // reset the dialog on close
1178                         resetDialog();
1179                     },
1180                     open: function () {
1181                         nfCommon.toggleScrollable($('#' + this.find('.tab-container').attr('id') + '-content').get(0));
1182                     }
1183                 }
1184             });
1185
1186             // initialize the properties tabs
1187             $('#connection-configuration-tabs').tabbs({
1188                 tabStyle: 'tab',
1189                 selectedTabStyle: 'selected-tab',
1190                 scrollableTabContentStyle: 'scrollable',
1191                 tabs: [{
1192                     name: 'Details',
1193                     tabContentId: 'connection-details-tab-content'
1194                 }, {
1195                     name: 'Settings',
1196                     tabContentId: 'connection-settings-tab-content'
1197                 }]
1198             });
1199
1200             // initialize the load balance strategy combo
1201             $('#load-balance-strategy-combo').combo({
1202                 options: nfCommon.loadBalanceStrategyOptions,
1203                 select: function (selectedOption) {
1204                     // Show the appropriate configurations
1205                     if (selectedOption.value === 'PARTITION_BY_ATTRIBUTE') {
1206                         $('#load-balance-partition-attribute-setting-separator').show();
1207                         $('#load-balance-partition-attribute-setting').show();
1208                     } else {
1209                         $('#load-balance-partition-attribute-setting-separator').hide();
1210                         $('#load-balance-partition-attribute-setting').hide();
1211                     }
1212                     if (selectedOption.value === 'DO_NOT_LOAD_BALANCE') {
1213                         $('#load-balance-compression-setting').hide();
1214                     } else {
1215                         $('#load-balance-compression-setting').show();
1216                     }
1217                 }
1218             });
1219
1220
1221             // initialize the load balance compression combo
1222             $('#load-balance-compression-combo').combo({
1223                 options: nfCommon.loadBalanceCompressionOptions
1224             });
1225
1226             // load the processor prioritizers
1227             $.ajax({
1228                 type: 'GET',
1229                 url: config.urls.prioritizers,
1230                 dataType: 'json'
1231             }).done(function (response) {
1232                 // create an element for each available prioritizer
1233                 $.each(response.prioritizerTypes, function (i, documentedType) {
1234                     nfConnectionConfiguration.addAvailablePrioritizer('#prioritizer-available', documentedType);
1235                 });
1236
1237                 // make the prioritizer containers sortable
1238                 $('#prioritizer-available, #prioritizer-selected').sortable({
1239                     containment: $('#connection-settings-tab-content').find('.settings-right'),
1240                     connectWith: 'ul',
1241                     placeholder: 'ui-state-highlight',
1242                     scroll: true,
1243                     opacity: 0.6
1244                 });
1245                 $('#prioritizer-available, #prioritizer-selected').disableSelection();
1246             }).fail(nfErrorHandler.handleAjaxError);
1247         },
1248
1249         /**
1250          * Adds the specified prioritizer to the specified container.
1251          *
1252          * @argument {string} prioritizerContainer      The dom Id of the prioritizer container
1253          * @argument {object} prioritizerType           The type of prioritizer
1254          */
1255         addAvailablePrioritizer: function (prioritizerContainer, prioritizerType) {
1256             var type = prioritizerType.type;
1257             var name = nfCommon.substringAfterLast(type, '.');
1258
1259             // add the prioritizers to the available list
1260             var prioritizerList = $(prioritizerContainer);
1261             var prioritizer = $('<li></li>').append($('<span style="float: left;"></span>').text(name)).attr('id', type).addClass('ui-state-default').appendTo(prioritizerList);
1262
1263             // add the description if applicable
1264             if (nfCommon.isDefinedAndNotNull(prioritizerType.description)) {
1265                 $('<div class="fa fa-question-circle"></div>').appendTo(prioritizer).qtip($.extend({
1266                     content: nfCommon.escapeHtml(prioritizerType.description)
1267                 }, nfCommon.config.tooltipConfig));
1268             }
1269         },
1270
1271         /**
1272          * Shows the dialog for creating a new connection.
1273          *
1274          * @argument {string} sourceId      The source id
1275          * @argument {string} destinationId The destination id
1276          */
1277         createConnection: function (sourceId, destinationId) {
1278             // select the source and destination
1279             var source = d3.select('#id-' + sourceId);
1280             var destination = d3.select('#id-' + destinationId);
1281
1282             if (source.empty() || destination.empty()) {
1283                 return;
1284             }
1285
1286             // initialize the connection dialog
1287             $.when(initializeSourceNewConnectionDialog(source), initializeDestinationNewConnectionDialog(destination)).done(function () {
1288
1289                 if (nfCanvasUtils.isProcessor(source) || nfCanvasUtils.isProcessor(destination)) {
1290                     addDialogRelationshipsChangeListener();
1291
1292                     // if there is a single relationship auto select
1293                     var relationships = $('#relationship-names').children('div');
1294                     if (relationships.length === 1) {
1295                         relationships.children('div.available-relationship').removeClass('checkbox-unchecked').addClass('checkbox-checked');
1296                     }
1297
1298                     // configure the button model
1299                     $('#connection-configuration').modal('setButtonModel', [{
1300                         buttonText: 'Add',
1301                         color: {
1302                             base: '#728E9B',
1303                             hover: '#004849',
1304                             text: '#ffffff'
1305                         },
1306                         disabled: function () {
1307                             // ensure some relationships were selected
1308                             return getSelectedRelationships().length === 0;
1309                         },
1310                         handler: {
1311                             click: function () {
1312                                 addConnection(getSelectedRelationships());
1313
1314                                 // close the dialog
1315                                 $('#connection-configuration').modal('hide');
1316                             }
1317                         }
1318                     },
1319                         {
1320                             buttonText: 'Cancel',
1321                             color: {
1322                                 base: '#E3E8EB',
1323                                 hover: '#C7D2D7',
1324                                 text: '#004849'
1325                             },
1326                             handler: {
1327                                 click: function () {
1328                                     $('#connection-configuration').modal('hide');
1329                                 }
1330                             }
1331                         }]);
1332                 } else {
1333                     // configure the button model
1334                     $('#connection-configuration').modal('setButtonModel', [{
1335                         buttonText: 'Add',
1336                         color: {
1337                             base: '#728E9B',
1338                             hover: '#004849',
1339                             text: '#ffffff'
1340                         },
1341                         handler: {
1342                             click: function () {
1343                                 // add the connection
1344                                 addConnection();
1345
1346                                 // close the dialog
1347                                 $('#connection-configuration').modal('hide');
1348                             }
1349                         }
1350                     },
1351                         {
1352                             buttonText: 'Cancel',
1353                             color: {
1354                                 base: '#E3E8EB',
1355                                 hover: '#C7D2D7',
1356                                 text: '#004849'
1357                             },
1358                             handler: {
1359                                 click: function () {
1360                                     $('#connection-configuration').modal('hide');
1361                                 }
1362                             }
1363                         }]);
1364                 }
1365
1366                 // set the default values
1367                 $('#flow-file-expiration').val('0 sec');
1368                 $('#back-pressure-object-threshold').val(defaultBackPressureObjectThreshold);
1369                 $('#back-pressure-data-size-threshold').val(defaultBackPressureDataSizeThreshold);
1370
1371                 // select the first tab
1372                 $('#connection-configuration-tabs').find('li:first').click();
1373
1374                 // configure the header and show the dialog
1375                 $('#connection-configuration').modal('setHeaderText', 'Create Connection').modal('show');
1376
1377                 // add the ellipsis if necessary
1378                 $('#connection-configuration div.relationship-name').ellipsis();
1379
1380                 // fill in the connection id
1381                 nfCommon.populateField('connection-id', null);
1382
1383                 // show the border if necessary
1384                 var relationshipNames = $('#relationship-names');
1385                 if (relationshipNames.is(':visible') && relationshipNames.get(0).scrollHeight > Math.round(relationshipNames.innerHeight())) {
1386                     relationshipNames.css('border-width', '1px');
1387                 }
1388             }).fail(function () {
1389                 // see if the temp edge needs to be removed
1390                 removeTempEdge();
1391             });
1392         },
1393
1394         /**
1395          * Shows the configuration for the specified connection. If a destination is
1396          * specified it will be considered a new destination.
1397          *
1398          * @argument {selection} selection         The connection entry
1399          * @argument {selection} destination          Optional new destination
1400          */
1401         showConfiguration: function (selection, destination) {
1402             return $.Deferred(function (deferred) {
1403                 var connectionEntry = selection.datum();
1404                 var connection = connectionEntry.component;
1405
1406                 // identify the source component
1407                 var sourceComponentId = nfCanvasUtils.getConnectionSourceComponentId(connectionEntry);
1408                 var source = d3.select('#id-' + sourceComponentId);
1409
1410                 // identify the destination component
1411                 if (nfCommon.isUndefinedOrNull(destination)) {
1412                     var destinationComponentId = nfCanvasUtils.getConnectionDestinationComponentId(connectionEntry);
1413                     destination = d3.select('#id-' + destinationComponentId);
1414                 }
1415
1416                 // initialize the connection dialog
1417                 $.when(initializeSourceEditConnectionDialog(source), initializeDestinationEditConnectionDialog(destination, connection.destination)).done(function () {
1418                     var availableRelationships = connection.availableRelationships;
1419                     var selectedRelationships = connection.selectedRelationships;
1420
1421                     // Added this block to force add destination relationships to
1422                     // get blueprint generation working
1423                     if (nfCanvasUtils.isProcessor(destination)) {
1424                       if (availableRelationships == undefined) {
1425                         // When the source is a port, this could be null or
1426                         // undefined since the backend the attribute doesn't
1427                         // exist
1428                         availableRelationships = [];
1429                       }
1430
1431                       var processorData = destination.datum();
1432                       $.each(processorData.component.relationships, function (i, relationship) {
1433                           availableRelationships.push(relationship.name);
1434                       });
1435                     }
1436
1437                     // show the available relationship if applicable
1438                     if (nfCommon.isDefinedAndNotNull(availableRelationships) || nfCommon.isDefinedAndNotNull(selectedRelationships)) {
1439                         // populate the available connections
1440                         $.each(availableRelationships, function (i, name) {
1441                             createRelationshipOption(name);
1442                         });
1443
1444                         addDialogRelationshipsChangeListener();
1445
1446                         // ensure all selected relationships are present
1447                         // (may be undefined) and selected
1448                         $.each(selectedRelationships, function (i, name) {
1449                             // mark undefined relationships accordingly
1450                             if ($.inArray(name, availableRelationships) === -1) {
1451                                 var option = createRelationshipOption(name);
1452                                 $(option).children('div.relationship-name').addClass('undefined');
1453                             }
1454
1455                             // ensure all selected relationships are checked
1456                             var relationships = $('#relationship-names').children('div');
1457                             $.each(relationships, function (i, relationship) {
1458                                 var relationshipName = $(relationship).children('span.relationship-name-value');
1459                                 if (relationshipName.text() === name) {
1460                                     $(relationship).children('div.available-relationship').removeClass('checkbox-unchecked').addClass('checkbox-checked');
1461                                 }
1462                             });
1463                         });
1464                     }
1465
1466                     // if the source is a process group or remote process group, select the appropriate port if applicable
1467                     if (nfCanvasUtils.isProcessGroup(source) || nfCanvasUtils.isRemoteProcessGroup(source)) {
1468                         // populate the connection source details
1469                         $('#connection-source-id').val(connection.source.id);
1470                         $('#read-only-output-port-name').text(connection.source.name).attr('title', connection.source.name);
1471                     }
1472
1473                     // if the destination is a process gorup or remote process group, select the appropriate port if applicable
1474                     if (nfCanvasUtils.isProcessGroup(destination) || nfCanvasUtils.isRemoteProcessGroup(destination)) {
1475                         var destinationData = destination.datum();
1476
1477                         // when the group ids differ, its a new destination component so we don't want to preselect any port
1478                         if (connection.destination.groupId === destinationData.id) {
1479                             $('#input-port-options').combo('setSelectedOption', {
1480                                 value: connection.destination.id
1481                             });
1482                         }
1483                     }
1484
1485                     // set the connection settings
1486                     $('#connection-name').val(connection.name);
1487                     $('#flow-file-expiration').val(connection.flowFileExpiration);
1488                     $('#back-pressure-object-threshold').val(connection.backPressureObjectThreshold);
1489                     $('#back-pressure-data-size-threshold').val(connection.backPressureDataSizeThreshold);
1490
1491                     // select the load balance combos
1492                     $('#load-balance-strategy-combo').combo('setSelectedOption', {
1493                         value: connection.loadBalanceStrategy
1494                     });
1495                     $('#load-balance-compression-combo').combo('setSelectedOption', {
1496                         value: connection.loadBalanceCompression
1497                     });
1498                     $('#load-balance-partition-attribute').val(connection.loadBalancePartitionAttribute);
1499
1500                     // format the connection id
1501                     nfCommon.populateField('connection-id', connection.id);
1502
1503                     // handle each prioritizer
1504                     $.each(connection.prioritizers, function (i, type) {
1505                         $('#prioritizer-available').children('li[id="' + type + '"]').detach().appendTo('#prioritizer-selected');
1506                     });
1507
1508                     // store the connection details
1509                     $('#connection-uri').val(connectionEntry.uri);
1510
1511                     // configure the button model
1512                     $('#connection-configuration').modal('setButtonModel', [{
1513                         buttonText: 'Apply',
1514                         color: {
1515                             base: '#728E9B',
1516                             hover: '#004849',
1517                             text: '#ffffff'
1518                         },
1519                         disabled: function () {
1520                             // ensure some relationships were selected with a processor as the source
1521                             if (nfCanvasUtils.isProcessor(source) || nfCanvasUtils.isProcessor(destination)) {
1522                                 return getSelectedRelationships().length === 0;
1523                             }
1524                             return false;
1525                         },
1526                         handler: {
1527                             click: function () {
1528                                 // see if we're working with a processor as the source
1529                                 if (nfCanvasUtils.isProcessor(source) || nfCanvasUtils.isProcessor(destination)) {
1530                                     // update the selected relationships
1531                                     updateConnection(getSelectedRelationships()).done(function () {
1532                                         deferred.resolve();
1533                                     }).fail(function () {
1534                                         deferred.reject();
1535                                     });
1536                                 } else {
1537                                     // there are no relationships, but the source wasn't a processor, so update anyway
1538                                     updateConnection(undefined).done(function () {
1539                                         deferred.resolve();
1540                                     }).fail(function () {
1541                                         deferred.reject();
1542                                     });
1543                                 }
1544
1545                                 // close the dialog
1546                                 $('#connection-configuration').modal('hide');
1547                             }
1548                         }
1549                     },
1550                         {
1551                             buttonText: 'Cancel',
1552                             color: {
1553                                 base: '#E3E8EB',
1554                                 hover: '#C7D2D7',
1555                                 text: '#004849'
1556                             },
1557                             handler: {
1558                                 click: function () {
1559                                     // hide the dialog
1560                                     $('#connection-configuration').modal('hide');
1561
1562                                     // reject the deferred
1563                                     deferred.reject();
1564                                 }
1565                             }
1566                         }]);
1567
1568                     // show the details dialog
1569                     $('#connection-configuration').modal('setHeaderText', 'Configure Connection').modal('show');
1570
1571                     // add the ellipsis if necessary
1572                     $('#connection-configuration div.relationship-name').ellipsis();
1573
1574                     // show the border if necessary
1575                     var relationshipNames = $('#relationship-names');
1576                     if (relationshipNames.is(':visible') && relationshipNames.get(0).scrollHeight > Math.round(relationshipNames.innerHeight())) {
1577                         relationshipNames.css('border-width', '1px');
1578                     }
1579                 }).fail(function () {
1580                     deferred.reject();
1581                 });
1582             }).promise();
1583         }
1584     };
1585
1586     return nfConnectionConfiguration;
1587 }));