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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 * Modifications to the original nifi code for the ONAP project are made
18 * available under the Apache License, Version 2.0
21 /* global define, module, require, exports */
23 (function (root, factory) {
24 if (typeof define === 'function' && define.amd) {
34 function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) {
35 return (nf.ConnectionConfiguration = factory($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection));
37 } else if (typeof exports === 'object' && typeof module === 'object') {
38 module.exports = (nf.ConnectionConfiguration =
39 factory(require('jquery'),
41 require('nf.ErrorHandler'),
44 require('nf.Storage'),
46 require('nf.CanvasUtils'),
47 require('nf.Connection')));
49 nf.ConnectionConfiguration = factory(root.$,
59 }(this, function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) {
65 var defaultBackPressureObjectThreshold;
66 var defaultBackPressureDataSizeThreshold;
68 var CONNECTION_OFFSET_Y_INCREMENT = 75;
69 var CONNECTION_OFFSET_X_INCREMENT = 200;
74 prioritizers: '../nifi-api/flow/prioritizers'
79 * Removes the temporary if necessary.
81 var removeTempEdge = function () {
82 d3.select('path.connector').remove();
86 * Activates dialog's button model refresh on a connection relationships change.
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');
96 * Initializes the source in the new connection dialog.
98 * @argument {selection} source The source
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);
112 // resolve the deferred
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.'
126 }).fail(function () {
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);
141 connectionSourceDeferred = initializeSourceFunnel(source);
144 // finish initialization when appropriate
145 connectionSourceDeferred.done(function () {
147 }).fail(function () {
155 * Initializes the source when the source is an input port.
157 * @argument {selection} source The source
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;
165 // populate the port information
166 $('#input-port-source').show();
167 $('#input-port-source-name').text(inputPortName).attr('title', inputPortName);
169 // populate the connection source details
170 $('#connection-source-id').val(inputPortData.id);
171 $('#connection-source-component-id').val(inputPortData.id);
173 // populate the group details
174 $('#connection-source-group-id').val(nfCanvasUtils.getGroupId());
175 $('#connection-source-group-name').text(nfCanvasUtils.getGroupName());
177 // resolve the deferred
183 * Initializes the source when the source is an input port.
185 * @argument {selection} source The source
187 var initializeSourceFunnel = function (source) {
188 return $.Deferred(function (deferred) {
189 // get the funnel data
190 var funnelData = source.datum();
192 // populate the port information
193 $('#funnel-source').show();
195 // populate the connection source details
196 $('#connection-source-id').val(funnelData.id);
197 $('#connection-source-component-id').val(funnelData.id);
199 // populate the group details
200 $('#connection-source-group-id').val(nfCanvasUtils.getGroupId());
201 $('#connection-source-group-name').text(nfCanvasUtils.getGroupName());
203 // resolve the deferred
209 * Initializes the source when the source is a processor.
211 * @argument {selection} source The source
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';
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);
225 // populate the connection source details
226 $('#connection-source-id').val(processorData.id);
227 $('#connection-source-component-id').val(processorData.id);
229 // populate the group details
230 $('#connection-source-group-id').val(nfCanvasUtils.getGroupId());
231 $('#connection-source-group-name').text(nfCanvasUtils.getGroupName());
233 // show the available relationships
234 $('#relationship-names-container').show();
236 deferred.resolve(processorData.component);
241 * Initializes the source when the source is a process group.
243 * @argument {selection} source The source
245 var initializeSourceProcessGroup = function (source) {
246 return $.Deferred(function (deferred) {
247 // get the process group data
248 var processGroupData = source.datum();
252 url: config.urls.api + '/flow/process-groups/' + encodeURIComponent(processGroupData.id),
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;
259 // show the output port 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;
266 text: component.name,
268 description: nfCommon.escapeHtml(component.comments)
273 // only proceed if there are output ports
274 if (!nfCommon.isEmpty(options)) {
275 $('#output-port-source').show();
278 options.sort(function (a, b) {
279 return a.text.localeCompare(b.text);
283 $('#output-port-options').combo({
286 select: function (option) {
287 $('#connection-source-id').val(option.value);
291 // populate the connection details
292 $('#connection-source-component-id').val(processGroup.id);
294 // populate the group details
295 $('#connection-source-group-id').val(processGroup.id);
296 $('#connection-source-group-name').text(processGroupName);
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) + '\'.';
305 // there are no output ports for this process group
306 nfDialog.showOkDialog({
307 headerText: 'Connection Configuration',
308 dialogContent: message
316 }).fail(function (xhr, status, error) {
318 nfErrorHandler.handleAjaxError(xhr, status, error);
326 * Initializes the source when the source is a remote process group.
328 * @argument {selection} source The source
330 var initializeSourceRemoteProcessGroup = function (source) {
331 return $.Deferred(function (deferred) {
332 // get the remote process group data
333 var remoteProcessGroupData = source.datum();
337 url: remoteProcessGroupData.uri,
339 }).done(function (response) {
340 var remoteProcessGroup = response.component;
341 var remoteProcessGroupContents = remoteProcessGroup.contents;
343 // only proceed if there are output ports
344 if (!nfCommon.isEmpty(remoteProcessGroupContents.outputPorts)) {
345 $('#output-port-source').show();
347 // show the output port options
349 $.each(remoteProcessGroupContents.outputPorts, function (i, outputPort) {
351 text: outputPort.name,
352 value: outputPort.id,
353 disabled: outputPort.exists === false,
354 description: nfCommon.escapeHtml(outputPort.comments)
359 options.sort(function (a, b) {
360 return a.text.localeCompare(b.text);
364 $('#output-port-options').combo({
367 select: function (option) {
368 $('#connection-source-id').val(option.value);
372 // populate the connection details
373 $('#connection-source-component-id').val(remoteProcessGroup.id);
375 // populate the group details
376 $('#connection-source-group-id').val(remoteProcessGroup.id);
377 $('#connection-source-group-name').text(remoteProcessGroup.name);
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.'
392 }).fail(function (xhr, status, error) {
394 nfErrorHandler.handleAjaxError(xhr, status, error);
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
411 $.each(processor.relationships, function (i, relationship) {
412 createRelationshipOption(relationship.name);
416 }).fail(function () {
420 } else if (nfCanvasUtils.isRemoteProcessGroup(destination)) {
421 return initializeDestinationRemoteProcessGroup(destination);
422 } else if (nfCanvasUtils.isFunnel(destination)) {
423 return initializeDestinationFunnel(destination);
425 return initializeDestinationProcessGroup(destination);
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;
434 $('#output-port-destination').show();
435 $('#output-port-destination-name').text(outputPortName).attr('title', outputPortName);
437 // populate the connection destination details
438 $('#connection-destination-id').val(outputPortData.id);
439 $('#connection-destination-component-id').val(outputPortData.id);
441 // populate the group details
442 $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId());
443 $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName());
449 var initializeDestinationFunnel = function (destination) {
450 return $.Deferred(function (deferred) {
451 var funnelData = destination.datum();
453 $('#funnel-destination').show();
455 // populate the connection destination details
456 $('#connection-destination-id').val(funnelData.id);
457 $('#connection-destination-component-id').val(funnelData.id);
459 // populate the group details
460 $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId());
461 $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName());
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';
473 $('#processor-destination').show();
474 $('#processor-destination-name').text(processorName).attr('title', processorName);
475 $('#processor-destination-type').text(processorType).attr('title', processorType);
477 // populate the connection destination details
478 $('#connection-destination-id').val(processorData.id);
479 $('#connection-destination-component-id').val(processorData.id);
481 // populate the group details
482 $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId());
483 $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName());
485 deferred.resolve(processorData.component);
490 * Initializes the destination when the destination is a process group.
492 * @argument {selection} destination The destination
494 var initializeDestinationProcessGroup = function (destination) {
495 return $.Deferred(function (deferred) {
496 var processGroupData = destination.datum();
500 url: config.urls.api + '/flow/process-groups/' + encodeURIComponent(processGroupData.id),
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;
507 // show the input port options
509 $.each(processGroupContents.inputPorts, function (i, inputPort) {
511 text: inputPort.permissions.canRead ? inputPort.component.name : inputPort.id,
513 description: inputPort.permissions.canRead ? nfCommon.escapeHtml(inputPort.component.comments) : null
517 // only proceed if there are output ports
518 if (!nfCommon.isEmpty(options)) {
519 $('#input-port-destination').show();
522 options.sort(function (a, b) {
523 return a.text.localeCompare(b.text);
527 $('#input-port-options').combo({
530 select: function (option) {
531 $('#connection-destination-id').val(option.value);
535 // populate the connection details
536 $('#connection-destination-component-id').val(processGroup.id);
538 // populate the group details
539 $('#connection-destination-group-id').val(processGroup.id);
540 $('#connection-destination-group-name').text(processGroupName);
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.'
555 }).fail(function (xhr, status, error) {
557 nfErrorHandler.handleAjaxError(xhr, status, error);
565 * Initializes the source when the source is a remote process group.
567 * @argument {selection} destination The destination
568 * @argument {object} connectionDestination The connection destination object
570 var initializeDestinationRemoteProcessGroup = function (destination, connectionDestination) {
571 return $.Deferred(function (deferred) {
572 var remoteProcessGroupData = destination.datum();
576 url: remoteProcessGroupData.uri,
578 }).done(function (response) {
579 var remoteProcessGroup = response.component;
580 var remoteProcessGroupContents = remoteProcessGroup.contents;
582 // only proceed if there are output ports
583 if (!nfCommon.isEmpty(remoteProcessGroupContents.inputPorts)) {
584 $('#input-port-destination').show();
586 // show the input port options
588 $.each(remoteProcessGroupContents.inputPorts, function (i, inputPort) {
590 text: inputPort.name,
592 disabled: inputPort.exists === false,
593 description: nfCommon.escapeHtml(inputPort.comments)
598 options.sort(function (a, b) {
599 return a.text.localeCompare(b.text);
603 $('#input-port-options').combo({
606 select: function (option) {
607 $('#connection-destination-id').val(option.value);
611 // populate the connection details
612 $('#connection-destination-component-id').val(remoteProcessGroup.id);
614 // populate the group details
615 $('#connection-destination-group-id').val(remoteProcessGroup.id);
616 $('#connection-destination-group-name').text(remoteProcessGroup.name);
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.'
631 }).fail(function (xhr, status, error) {
633 nfErrorHandler.handleAjaxError(xhr, status, error);
641 * Initializes the source panel for groups.
643 * @argument {selection} source The source of the connection
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;
650 // populate the port information
651 $('#read-only-output-port-source').show();
653 // populate the component information
654 $('#connection-source-component-id').val(sourceData.id);
656 // populate the group details
657 $('#connection-source-group-id').val(sourceData.id);
658 $('#connection-source-group-name').text(sourceName);
660 // resolve the deferred
666 * Initializes the source in the existing connection dialog.
668 * @argument {selection} source The source
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);
678 return initializeSourceReadOnlyGroup(source);
683 * Initializes the destination in the existing connection dialog.
685 * @argument {selection} destination The destination
686 * @argument {object} connectionDestination The connection destination object
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);
698 return initializeDestinationProcessGroup(destination);
703 * Creates an option for the specified relationship name.
705 * @argument {string} name The relationship name
707 var createRelationshipOption = function (name) {
708 var nameSplit = name.split(":");
709 var nameLabel = name;
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];
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');
725 * Adds a new connection.
727 * @argument {array} selectedRelationships The selected relationships
729 var addConnection = function (selectedRelationships) {
730 // get the connection details
731 var sourceId = $('#connection-source-id').val();
732 var destinationId = $('#connection-destination-id').val();
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);
740 // get the source/destination data
741 var sourceData = source.datum();
742 var destinationData = destination.datum();
744 // add bend points if we're dealing with a self loop
746 if (sourceComponentId === destinationComponentId) {
748 x: sourceData.position.x + (sourceData.dimensions.width),
749 y: sourceData.position.y + (sourceData.dimensions.height / 2)
752 var xOffset = nfConnection.config.selfLoopXOffset;
753 var yOffset = nfConnection.config.selfLoopYOffset;
755 'x': (rightCenter.x + xOffset),
756 'y': (rightCenter.y - yOffset)
759 'x': (rightCenter.x + xOffset),
760 'y': (rightCenter.y + yOffset)
763 var existingConnections = [];
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);
772 // if the connection is between these same components, consider it for collisions
773 if ((connectionSourceComponentId === sourceComponentId && connectionDestinationComponentId === destinationComponentId) ||
774 (connectionDestinationComponentId === sourceComponentId && connectionSourceComponentId === destinationComponentId)) {
776 // record all connections between these two components in question
777 existingConnections.push(connectionForSourceComponent);
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;
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)];
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;
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) {
815 // vertical lines are adjusted in the x space
816 if (existingConnection.bends[0].x === x) {
826 // find the mid point on the connection
827 var xCandidate = (sourceMiddle[0] + destinationMiddle[0]) / 2;
828 var yCandidate = (sourceMiddle[1] + destinationMiddle[1]) / 2;
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) {
838 'x': (xCandidate - xStep),
839 'y': (yCandidate - yStep)
842 } else if (collides(xCandidate + xStep, yCandidate + yStep) === false) {
844 'x': (xCandidate + xStep),
845 'y': (yCandidate + yStep)
850 if (isMoreHorizontal) {
851 yStep += CONNECTION_OFFSET_Y_INCREMENT;
853 xStep += CONNECTION_OFFSET_X_INCREMENT;
860 // determine the source group id
861 var sourceGroupId = $('#connection-source-group-id').val();
862 var destinationGroupId = $('#connection-destination-group-id').val();
864 // determine the source and destination types
865 var sourceType = nfCanvasUtils.getConnectableTypeForSource(source);
866 var destinationType = nfCanvasUtils.getConnectableTypeForDestination(destination);
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';
879 if (validateSettings()) {
880 var connectionEntity = {
881 'revision': nfClient.getRevision({
886 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
888 'name': connectionName,
891 'groupId': sourceGroupId,
896 'groupId': destinationGroupId,
897 'type': destinationType
899 'selectedRelationships': selectedRelationships,
900 'flowFileExpiration': flowFileExpiration,
901 'backPressureDataSizeThreshold': backPressureDataSizeThreshold,
902 'backPressureObjectThreshold': backPressureObjectThreshold,
904 'prioritizers': prioritizers,
905 'loadBalanceStrategy': loadBalanceStrategy,
906 'loadBalancePartitionAttribute': loadBalancePartitionAttribute,
907 'loadBalanceCompression': loadBalanceCompression
911 // create the new connection
914 url: config.urls.api + '/process-groups/' + encodeURIComponent(nfCanvasUtils.getGroupId()) + '/connections',
915 data: JSON.stringify(connectionEntity),
917 contentType: 'application/json'
918 }).done(function (response) {
919 // add the connection
921 'connections': [response]
926 // reload the connections source/destination components
927 nfCanvasUtils.reloadConnectionSourceAndDestination(sourceComponentId, destinationComponentId);
929 // update component visibility
930 nfGraph.updateVisibility();
932 // update the birdseye
933 nfBirdseye.refresh();
934 }).fail(function (xhr, status, error) {
936 nfErrorHandler.handleAjaxError(xhr, status, error);
942 * Updates an existing connection.
944 * @argument {array} selectedRelationships The selected relationships
946 var updateConnection = function (selectedRelationships) {
947 // get the connection details
948 var connectionId = $('#connection-id').text();
949 var connectionUri = $('#connection-uri').val();
951 // get the source details
952 var sourceComponentId = $('#connection-source-component-id').val();
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);
959 // get the destination details
960 var destinationId = $('#connection-destination-id').val();
961 var destinationGroupId = $('#connection-destination-group-id').val();
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';
974 if (validateSettings()) {
975 var d = nfConnection.get(connectionId);
976 var connectionEntity = {
977 'revision': nfClient.getRevision(d),
978 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
981 'name': connectionName,
984 'groupId': destinationGroupId,
985 'type': destinationType
987 'selectedRelationships': selectedRelationships,
988 'flowFileExpiration': flowFileExpiration,
989 'backPressureDataSizeThreshold': backPressureDataSizeThreshold,
990 'backPressureObjectThreshold': backPressureObjectThreshold,
991 'prioritizers': prioritizers,
992 'loadBalanceStrategy': loadBalanceStrategy,
993 'loadBalancePartitionAttribute': loadBalancePartitionAttribute,
994 'loadBalanceCompression': loadBalanceCompression
998 // update the connection
1002 data: JSON.stringify(connectionEntity),
1004 contentType: 'application/json'
1005 }).done(function (response) {
1006 // update this connection
1007 nfConnection.set(response);
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),
1018 nfErrorHandler.handleAjaxError(xhr, status, error);
1022 return $.Deferred(function (deferred) {
1029 * Returns an array of selected relationship names.
1031 var getSelectedRelationships = function () {
1032 // get all available relationships
1033 var availableRelationships = $('#relationship-names');
1034 var selectedRelationships = [];
1036 // go through each relationship to determine which are selected
1037 $.each(availableRelationships.children(), function (i, relationshipElement) {
1038 var relationship = $(relationshipElement);
1040 // get each relationship and its corresponding checkbox
1041 var relationshipCheck = relationship.children('div.available-relationship');
1043 // see if this relationship has been selected
1044 if (relationshipCheck.hasClass('checkbox-checked')) {
1045 selectedRelationships.push(relationship.children('span.relationship-name-value').text());
1049 return selectedRelationships;
1053 * Validates the specified settings.
1055 var validateSettings = function () {
1058 // validate the settings
1059 if (nfCommon.isBlank($('#flow-file-expiration').val())) {
1060 errors.push('File expiration must be specified');
1062 if (!$.isNumeric($('#back-pressure-object-threshold').val())) {
1063 errors.push('Back pressure object threshold must be an integer value');
1065 if (nfCommon.isBlank($('#back-pressure-data-size-threshold').val())) {
1066 errors.push('Back pressure data size threshold must be specified');
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"');
1073 if (errors.length > 0) {
1074 nfDialog.showOkDialog({
1075 headerText: 'Connection Configuration',
1076 dialogContent: nfCommon.formatUnorderedList(errors)
1085 * Resets the dialog.
1087 var resetDialog = function () {
1088 // reset the prioritizers
1089 var selectedList = $('#prioritizer-selected');
1090 var availableList = $('#prioritizer-available');
1091 selectedList.children().detach().appendTo(availableList);
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;
1101 // clear the available list and re-insert each list item
1102 $.each(listItems, function () {
1105 $.each(listItems, function () {
1106 $(this).appendTo(availableList);
1110 $('#connection-name').val('');
1111 $('#relationship-names').css('border-width', '0').empty();
1112 $('#relationship-names-container').show();
1114 // clear the id field
1115 nfCommon.clearField('connection-id');
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();
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();
1130 // clear and destination details
1131 $('#connection-source-id').val('');
1132 $('#connection-source-component-id').val('');
1133 $('#connection-source-group-id').val('');
1135 // clear any destination details
1136 $('#connection-destination-id').val('');
1137 $('#connection-destination-component-id').val('');
1138 $('#connection-destination-group-id').val('');
1141 $('#output-port-options').empty();
1142 $('#input-port-options').empty();
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]);
1149 // see if the temp edge needs to be removed
1153 var nfConnectionConfiguration = {
1156 * Initialize the connection configuration.
1158 * @param nfBirdseyeRef The nfBirdseye module.
1159 * @param nfGraphRef The nfGraph module.
1161 init: function (nfBirdseyeRef, nfGraphRef, defaultBackPressureObjectThresholdRef, defaultBackPressureDataSizeThresholdRef) {
1162 nfBirdseye = nfBirdseyeRef;
1163 nfGraph = nfGraphRef;
1165 defaultBackPressureObjectThreshold = defaultBackPressureObjectThresholdRef;
1166 defaultBackPressureDataSizeThreshold = defaultBackPressureDataSizeThresholdRef;
1168 // initially hide the relationship names container
1169 $('#relationship-names-container').show();
1171 // initialize the configure connection dialog
1172 $('#connection-configuration').modal({
1173 scrollableContentStyle: 'scrollable',
1174 headerText: 'Configure Connection',
1176 close: function () {
1177 // reset the dialog on close
1181 nfCommon.toggleScrollable($('#' + this.find('.tab-container').attr('id') + '-content').get(0));
1186 // initialize the properties tabs
1187 $('#connection-configuration-tabs').tabbs({
1189 selectedTabStyle: 'selected-tab',
1190 scrollableTabContentStyle: 'scrollable',
1193 tabContentId: 'connection-details-tab-content'
1196 tabContentId: 'connection-settings-tab-content'
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();
1209 $('#load-balance-partition-attribute-setting-separator').hide();
1210 $('#load-balance-partition-attribute-setting').hide();
1212 if (selectedOption.value === 'DO_NOT_LOAD_BALANCE') {
1213 $('#load-balance-compression-setting').hide();
1215 $('#load-balance-compression-setting').show();
1221 // initialize the load balance compression combo
1222 $('#load-balance-compression-combo').combo({
1223 options: nfCommon.loadBalanceCompressionOptions
1226 // load the processor prioritizers
1229 url: config.urls.prioritizers,
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);
1237 // make the prioritizer containers sortable
1238 $('#prioritizer-available, #prioritizer-selected').sortable({
1239 containment: $('#connection-settings-tab-content').find('.settings-right'),
1241 placeholder: 'ui-state-highlight',
1245 $('#prioritizer-available, #prioritizer-selected').disableSelection();
1246 }).fail(nfErrorHandler.handleAjaxError);
1250 * Adds the specified prioritizer to the specified container.
1252 * @argument {string} prioritizerContainer The dom Id of the prioritizer container
1253 * @argument {object} prioritizerType The type of prioritizer
1255 addAvailablePrioritizer: function (prioritizerContainer, prioritizerType) {
1256 var type = prioritizerType.type;
1257 var name = nfCommon.substringAfterLast(type, '.');
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);
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));
1272 * Shows the dialog for creating a new connection.
1274 * @argument {string} sourceId The source id
1275 * @argument {string} destinationId The destination id
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);
1282 if (source.empty() || destination.empty()) {
1286 // initialize the connection dialog
1287 $.when(initializeSourceNewConnectionDialog(source), initializeDestinationNewConnectionDialog(destination)).done(function () {
1289 if (nfCanvasUtils.isProcessor(source) || nfCanvasUtils.isProcessor(destination)) {
1290 addDialogRelationshipsChangeListener();
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');
1298 // configure the button model
1299 $('#connection-configuration').modal('setButtonModel', [{
1306 disabled: function () {
1307 // ensure some relationships were selected
1308 return getSelectedRelationships().length === 0;
1311 click: function () {
1312 addConnection(getSelectedRelationships());
1315 $('#connection-configuration').modal('hide');
1320 buttonText: 'Cancel',
1327 click: function () {
1328 $('#connection-configuration').modal('hide');
1333 // configure the button model
1334 $('#connection-configuration').modal('setButtonModel', [{
1342 click: function () {
1343 // add the connection
1347 $('#connection-configuration').modal('hide');
1352 buttonText: 'Cancel',
1359 click: function () {
1360 $('#connection-configuration').modal('hide');
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);
1371 // select the first tab
1372 $('#connection-configuration-tabs').find('li:first').click();
1374 // configure the header and show the dialog
1375 $('#connection-configuration').modal('setHeaderText', 'Create Connection').modal('show');
1377 // add the ellipsis if necessary
1378 $('#connection-configuration div.relationship-name').ellipsis();
1380 // fill in the connection id
1381 nfCommon.populateField('connection-id', null);
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');
1388 }).fail(function () {
1389 // see if the temp edge needs to be removed
1395 * Shows the configuration for the specified connection. If a destination is
1396 * specified it will be considered a new destination.
1398 * @argument {selection} selection The connection entry
1399 * @argument {selection} destination Optional new destination
1401 showConfiguration: function (selection, destination) {
1402 return $.Deferred(function (deferred) {
1403 var connectionEntry = selection.datum();
1404 var connection = connectionEntry.component;
1406 // identify the source component
1407 var sourceComponentId = nfCanvasUtils.getConnectionSourceComponentId(connectionEntry);
1408 var source = d3.select('#id-' + sourceComponentId);
1410 // identify the destination component
1411 if (nfCommon.isUndefinedOrNull(destination)) {
1412 var destinationComponentId = nfCanvasUtils.getConnectionDestinationComponentId(connectionEntry);
1413 destination = d3.select('#id-' + destinationComponentId);
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;
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
1428 availableRelationships = [];
1431 var processorData = destination.datum();
1432 $.each(processorData.component.relationships, function (i, relationship) {
1433 availableRelationships.push(relationship.name);
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);
1444 addDialogRelationshipsChangeListener();
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');
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');
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);
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();
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
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);
1491 // select the load balance combos
1492 $('#load-balance-strategy-combo').combo('setSelectedOption', {
1493 value: connection.loadBalanceStrategy
1495 $('#load-balance-compression-combo').combo('setSelectedOption', {
1496 value: connection.loadBalanceCompression
1498 $('#load-balance-partition-attribute').val(connection.loadBalancePartitionAttribute);
1500 // format the connection id
1501 nfCommon.populateField('connection-id', connection.id);
1503 // handle each prioritizer
1504 $.each(connection.prioritizers, function (i, type) {
1505 $('#prioritizer-available').children('li[id="' + type + '"]').detach().appendTo('#prioritizer-selected');
1508 // store the connection details
1509 $('#connection-uri').val(connectionEntry.uri);
1511 // configure the button model
1512 $('#connection-configuration').modal('setButtonModel', [{
1513 buttonText: 'Apply',
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;
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 () {
1533 }).fail(function () {
1537 // there are no relationships, but the source wasn't a processor, so update anyway
1538 updateConnection(undefined).done(function () {
1540 }).fail(function () {
1546 $('#connection-configuration').modal('hide');
1551 buttonText: 'Cancel',
1558 click: function () {
1560 $('#connection-configuration').modal('hide');
1562 // reject the deferred
1568 // show the details dialog
1569 $('#connection-configuration').modal('setHeaderText', 'Configure Connection').modal('show');
1571 // add the ellipsis if necessary
1572 $('#connection-configuration div.relationship-name').ellipsis();
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');
1579 }).fail(function () {
1586 return nfConnectionConfiguration;