From 9507f2f8d2ec616f01f5ee8825106300b95e8ddc Mon Sep 17 00:00:00 2001 From: Andrew Gauld Date: Fri, 7 Feb 2020 15:00:39 +0000 Subject: Add DCAE MOD design tool project Change-Id: I660b28ebfaa7e4b5f03a1df5fd17d126f58b7c14 Issue-ID: DCAEGEN2-1860 Signed-off-by: Andrew Gauld --- .../js/nf/canvas/nf-connection-configuration.js | 1587 ++++++++++++++++++++ 1 file changed, 1587 insertions(+) create mode 100644 mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-connection-configuration.js (limited to 'mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-connection-configuration.js') diff --git a/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-connection-configuration.js b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-connection-configuration.js new file mode 100644 index 0000000..0cdb1a1 --- /dev/null +++ b/mod/designtool/designtool-web/src/main/webapp/js/nf/canvas/nf-connection-configuration.js @@ -0,0 +1,1587 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications to the original nifi code for the ONAP project are made + * available under the Apache License, Version 2.0 + */ + +/* global define, module, require, exports */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery', + 'd3', + 'nf.ErrorHandler', + 'nf.Common', + 'nf.Dialog', + 'nf.Storage', + 'nf.Client', + 'nf.CanvasUtils', + 'nf.Connection'], + function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) { + return (nf.ConnectionConfiguration = factory($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection)); + }); + } else if (typeof exports === 'object' && typeof module === 'object') { + module.exports = (nf.ConnectionConfiguration = + factory(require('jquery'), + require('d3'), + require('nf.ErrorHandler'), + require('nf.Common'), + require('nf.Dialog'), + require('nf.Storage'), + require('nf.Client'), + require('nf.CanvasUtils'), + require('nf.Connection'))); + } else { + nf.ConnectionConfiguration = factory(root.$, + root.d3, + root.nf.ErrorHandler, + root.nf.Common, + root.nf.Dialog, + root.nf.Storage, + root.nf.Client, + root.nf.CanvasUtils, + root.nf.Connection); + } +}(this, function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) { + 'use strict'; + + var nfBirdseye; + var nfGraph; + + var defaultBackPressureObjectThreshold; + var defaultBackPressureDataSizeThreshold; + + var CONNECTION_OFFSET_Y_INCREMENT = 75; + var CONNECTION_OFFSET_X_INCREMENT = 200; + + var config = { + urls: { + api: '../nifi-api', + prioritizers: '../nifi-api/flow/prioritizers' + } + }; + + /** + * Removes the temporary if necessary. + */ + var removeTempEdge = function () { + d3.select('path.connector').remove(); + }; + + /** + * Activates dialog's button model refresh on a connection relationships change. + */ + var addDialogRelationshipsChangeListener = function() { + // refresh button model when a relationship selection changes + $('div.available-relationship').bind('change', function() { + $('#connection-configuration').modal('refreshButtons'); + }); + } + + /** + * Initializes the source in the new connection dialog. + * + * @argument {selection} source The source + */ + var initializeSourceNewConnectionDialog = function (source) { + // handle the selected source + if (nfCanvasUtils.isProcessor(source)) { + return $.Deferred(function (deferred) { + // initialize the source processor + initializeSourceProcessor(source).done(function (processor) { + if (!nfCommon.isEmpty(processor.relationships)) { + // populate the available connections + $.each(processor.relationships, function (i, relationship) { + createRelationshipOption(relationship.name); + }); + + // resolve the deferred + deferred.resolve(); + } else { + // there are no relationships for this processor + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: '\'' + nfCommon.escapeHtml(processor.name) + '\' does not support any relationships.' + }); + + // reset the dialog + resetDialog(); + + deferred.reject(); + } + }).fail(function () { + deferred.reject(); + }); + }).promise(); + } else { + return $.Deferred(function (deferred) { + // determine how to initialize the source + var connectionSourceDeferred; + if (nfCanvasUtils.isInputPort(source)) { + connectionSourceDeferred = initializeSourceInputPort(source); + } else if (nfCanvasUtils.isRemoteProcessGroup(source)) { + connectionSourceDeferred = initializeSourceRemoteProcessGroup(source); + } else if (nfCanvasUtils.isProcessGroup(source)) { + connectionSourceDeferred = initializeSourceProcessGroup(source); + } else { + connectionSourceDeferred = initializeSourceFunnel(source); + } + + // finish initialization when appropriate + connectionSourceDeferred.done(function () { + deferred.resolve(); + }).fail(function () { + deferred.reject(); + }); + }).promise(); + } + }; + + /** + * Initializes the source when the source is an input port. + * + * @argument {selection} source The source + */ + var initializeSourceInputPort = function (source) { + return $.Deferred(function (deferred) { + // get the input port data + var inputPortData = source.datum(); + var inputPortName = inputPortData.permissions.canRead ? inputPortData.component.name : inputPortData.id; + + // populate the port information + $('#input-port-source').show(); + $('#input-port-source-name').text(inputPortName).attr('title', inputPortName); + + // populate the connection source details + $('#connection-source-id').val(inputPortData.id); + $('#connection-source-component-id').val(inputPortData.id); + + // populate the group details + $('#connection-source-group-id').val(nfCanvasUtils.getGroupId()); + $('#connection-source-group-name').text(nfCanvasUtils.getGroupName()); + + // resolve the deferred + deferred.resolve(); + }).promise(); + }; + + /** + * Initializes the source when the source is an input port. + * + * @argument {selection} source The source + */ + var initializeSourceFunnel = function (source) { + return $.Deferred(function (deferred) { + // get the funnel data + var funnelData = source.datum(); + + // populate the port information + $('#funnel-source').show(); + + // populate the connection source details + $('#connection-source-id').val(funnelData.id); + $('#connection-source-component-id').val(funnelData.id); + + // populate the group details + $('#connection-source-group-id').val(nfCanvasUtils.getGroupId()); + $('#connection-source-group-name').text(nfCanvasUtils.getGroupName()); + + // resolve the deferred + deferred.resolve(); + }).promise(); + }; + + /** + * Initializes the source when the source is a processor. + * + * @argument {selection} source The source + */ + var initializeSourceProcessor = function (source) { + return $.Deferred(function (deferred) { + // get the processor data + var processorData = source.datum(); + var processorName = processorData.permissions.canRead ? processorData.component.name : processorData.id; + var processorType = processorData.permissions.canRead ? nfCommon.substringAfterLast(processorData.component.type, '.') : 'Processor'; + + // populate the source processor information + $('#processor-source').show(); + $('#processor-source-name').text(processorName).attr('title', processorName); + $('#processor-source-type').text(processorType).attr('title', processorType); + + // populate the connection source details + $('#connection-source-id').val(processorData.id); + $('#connection-source-component-id').val(processorData.id); + + // populate the group details + $('#connection-source-group-id').val(nfCanvasUtils.getGroupId()); + $('#connection-source-group-name').text(nfCanvasUtils.getGroupName()); + + // show the available relationships + $('#relationship-names-container').show(); + + deferred.resolve(processorData.component); + }); + }; + + /** + * Initializes the source when the source is a process group. + * + * @argument {selection} source The source + */ + var initializeSourceProcessGroup = function (source) { + return $.Deferred(function (deferred) { + // get the process group data + var processGroupData = source.datum(); + + $.ajax({ + type: 'GET', + url: config.urls.api + '/flow/process-groups/' + encodeURIComponent(processGroupData.id), + dataType: 'json' + }).done(function (response) { + var processGroup = response.processGroupFlow; + var processGroupName = response.permissions.canRead ? processGroup.breadcrumb.breadcrumb.name : processGroup.id; + var processGroupContents = processGroup.flow; + + // show the output port options + var options = []; + $.each(processGroupContents.outputPorts, function (i, outputPort) { + // require explicit access to the output port as it's the source of the connection + if (outputPort.permissions.canRead && outputPort.permissions.canWrite) { + var component = outputPort.component; + options.push({ + text: component.name, + value: component.id, + description: nfCommon.escapeHtml(component.comments) + }); + } + }); + + // only proceed if there are output ports + if (!nfCommon.isEmpty(options)) { + $('#output-port-source').show(); + + // sort the options + options.sort(function (a, b) { + return a.text.localeCompare(b.text); + }); + + // create the combo + $('#output-port-options').combo({ + options: options, + maxHeight: 300, + select: function (option) { + $('#connection-source-id').val(option.value); + } + }); + + // populate the connection details + $('#connection-source-component-id').val(processGroup.id); + + // populate the group details + $('#connection-source-group-id').val(processGroup.id); + $('#connection-source-group-name').text(processGroupName); + + deferred.resolve(); + } else { + var message = '\'' + nfCommon.escapeHtml(processGroupName) + '\' does not have any output ports.'; + if (nfCommon.isEmpty(processGroupContents.outputPorts) === false) { + message = 'Not authorized for any output ports in \'' + nfCommon.escapeHtml(processGroupName) + '\'.'; + } + + // there are no output ports for this process group + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: message + }); + + // reset the dialog + resetDialog(); + + deferred.reject(); + } + }).fail(function (xhr, status, error) { + // handle the error + nfErrorHandler.handleAjaxError(xhr, status, error); + + deferred.reject(); + }); + }).promise(); + }; + + /** + * Initializes the source when the source is a remote process group. + * + * @argument {selection} source The source + */ + var initializeSourceRemoteProcessGroup = function (source) { + return $.Deferred(function (deferred) { + // get the remote process group data + var remoteProcessGroupData = source.datum(); + + $.ajax({ + type: 'GET', + url: remoteProcessGroupData.uri, + dataType: 'json' + }).done(function (response) { + var remoteProcessGroup = response.component; + var remoteProcessGroupContents = remoteProcessGroup.contents; + + // only proceed if there are output ports + if (!nfCommon.isEmpty(remoteProcessGroupContents.outputPorts)) { + $('#output-port-source').show(); + + // show the output port options + var options = []; + $.each(remoteProcessGroupContents.outputPorts, function (i, outputPort) { + options.push({ + text: outputPort.name, + value: outputPort.id, + disabled: outputPort.exists === false, + description: nfCommon.escapeHtml(outputPort.comments) + }); + }); + + // sort the options + options.sort(function (a, b) { + return a.text.localeCompare(b.text); + }); + + // create the combo + $('#output-port-options').combo({ + options: options, + maxHeight: 300, + select: function (option) { + $('#connection-source-id').val(option.value); + } + }); + + // populate the connection details + $('#connection-source-component-id').val(remoteProcessGroup.id); + + // populate the group details + $('#connection-source-group-id').val(remoteProcessGroup.id); + $('#connection-source-group-name').text(remoteProcessGroup.name); + + deferred.resolve(); + } else { + // there are no relationships for this processor + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: '\'' + nfCommon.escapeHtml(remoteProcessGroup.name) + '\' does not have any output ports.' + }); + + // reset the dialog + resetDialog(); + + deferred.reject(); + } + }).fail(function (xhr, status, error) { + // handle the error + nfErrorHandler.handleAjaxError(xhr, status, error); + + deferred.reject(); + }); + }).promise(); + }; + + var initializeDestinationNewConnectionDialog = function (destination) { + if (nfCanvasUtils.isOutputPort(destination)) { + return initializeDestinationOutputPort(destination); + } else if (nfCanvasUtils.isProcessor(destination)) { + return $.Deferred(function (deferred) { + initializeDestinationProcessor(destination).done(function (processor) { + // Need to add the destination relationships because we need to + // provide this to wire up the publishers and subscribers correctly + // for a given connection since processors can have multiple + // relationships + $.each(processor.relationships, function (i, relationship) { + createRelationshipOption(relationship.name); + }); + + deferred.resolve(); + }).fail(function () { + deferred.reject(); + }); + }).promise(); + } else if (nfCanvasUtils.isRemoteProcessGroup(destination)) { + return initializeDestinationRemoteProcessGroup(destination); + } else if (nfCanvasUtils.isFunnel(destination)) { + return initializeDestinationFunnel(destination); + } else { + return initializeDestinationProcessGroup(destination); + } + }; + + var initializeDestinationOutputPort = function (destination) { + return $.Deferred(function (deferred) { + var outputPortData = destination.datum(); + var outputPortName = outputPortData.permissions.canRead ? outputPortData.component.name : outputPortData.id; + + $('#output-port-destination').show(); + $('#output-port-destination-name').text(outputPortName).attr('title', outputPortName); + + // populate the connection destination details + $('#connection-destination-id').val(outputPortData.id); + $('#connection-destination-component-id').val(outputPortData.id); + + // populate the group details + $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId()); + $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName()); + + deferred.resolve(); + }).promise(); + }; + + var initializeDestinationFunnel = function (destination) { + return $.Deferred(function (deferred) { + var funnelData = destination.datum(); + + $('#funnel-destination').show(); + + // populate the connection destination details + $('#connection-destination-id').val(funnelData.id); + $('#connection-destination-component-id').val(funnelData.id); + + // populate the group details + $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId()); + $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName()); + + deferred.resolve(); + }).promise(); + }; + + var initializeDestinationProcessor = function (destination) { + return $.Deferred(function (deferred) { + var processorData = destination.datum(); + var processorName = processorData.permissions.canRead ? processorData.component.name : processorData.id; + var processorType = processorData.permissions.canRead ? nfCommon.substringAfterLast(processorData.component.type, '.') : 'Processor'; + + $('#processor-destination').show(); + $('#processor-destination-name').text(processorName).attr('title', processorName); + $('#processor-destination-type').text(processorType).attr('title', processorType); + + // populate the connection destination details + $('#connection-destination-id').val(processorData.id); + $('#connection-destination-component-id').val(processorData.id); + + // populate the group details + $('#connection-destination-group-id').val(nfCanvasUtils.getGroupId()); + $('#connection-destination-group-name').text(nfCanvasUtils.getGroupName()); + + deferred.resolve(processorData.component); + }).promise(); + }; + + /** + * Initializes the destination when the destination is a process group. + * + * @argument {selection} destination The destination + */ + var initializeDestinationProcessGroup = function (destination) { + return $.Deferred(function (deferred) { + var processGroupData = destination.datum(); + + $.ajax({ + type: 'GET', + url: config.urls.api + '/flow/process-groups/' + encodeURIComponent(processGroupData.id), + dataType: 'json' + }).done(function (response) { + var processGroup = response.processGroupFlow; + var processGroupName = response.permissions.canRead ? processGroup.breadcrumb.breadcrumb.name : processGroup.id; + var processGroupContents = processGroup.flow; + + // show the input port options + var options = []; + $.each(processGroupContents.inputPorts, function (i, inputPort) { + options.push({ + text: inputPort.permissions.canRead ? inputPort.component.name : inputPort.id, + value: inputPort.id, + description: inputPort.permissions.canRead ? nfCommon.escapeHtml(inputPort.component.comments) : null + }); + }); + + // only proceed if there are output ports + if (!nfCommon.isEmpty(options)) { + $('#input-port-destination').show(); + + // sort the options + options.sort(function (a, b) { + return a.text.localeCompare(b.text); + }); + + // create the combo + $('#input-port-options').combo({ + options: options, + maxHeight: 300, + select: function (option) { + $('#connection-destination-id').val(option.value); + } + }); + + // populate the connection details + $('#connection-destination-component-id').val(processGroup.id); + + // populate the group details + $('#connection-destination-group-id').val(processGroup.id); + $('#connection-destination-group-name').text(processGroupName); + + deferred.resolve(); + } else { + // there are no relationships for this processor + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: '\'' + nfCommon.escapeHtml(processGroupName) + '\' does not have any input ports.' + }); + + // reset the dialog + resetDialog(); + + deferred.reject(); + } + }).fail(function (xhr, status, error) { + // handle the error + nfErrorHandler.handleAjaxError(xhr, status, error); + + deferred.reject(); + }); + }).promise(); + }; + + /** + * Initializes the source when the source is a remote process group. + * + * @argument {selection} destination The destination + * @argument {object} connectionDestination The connection destination object + */ + var initializeDestinationRemoteProcessGroup = function (destination, connectionDestination) { + return $.Deferred(function (deferred) { + var remoteProcessGroupData = destination.datum(); + + $.ajax({ + type: 'GET', + url: remoteProcessGroupData.uri, + dataType: 'json' + }).done(function (response) { + var remoteProcessGroup = response.component; + var remoteProcessGroupContents = remoteProcessGroup.contents; + + // only proceed if there are output ports + if (!nfCommon.isEmpty(remoteProcessGroupContents.inputPorts)) { + $('#input-port-destination').show(); + + // show the input port options + var options = []; + $.each(remoteProcessGroupContents.inputPorts, function (i, inputPort) { + options.push({ + text: inputPort.name, + value: inputPort.id, + disabled: inputPort.exists === false, + description: nfCommon.escapeHtml(inputPort.comments) + }); + }); + + // sort the options + options.sort(function (a, b) { + return a.text.localeCompare(b.text); + }); + + // create the combo + $('#input-port-options').combo({ + options: options, + maxHeight: 300, + select: function (option) { + $('#connection-destination-id').val(option.value); + } + }); + + // populate the connection details + $('#connection-destination-component-id').val(remoteProcessGroup.id); + + // populate the group details + $('#connection-destination-group-id').val(remoteProcessGroup.id); + $('#connection-destination-group-name').text(remoteProcessGroup.name); + + deferred.resolve(); + } else { + // there are no relationships for this processor + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: '\'' + nfCommon.escapeHtml(remoteProcessGroup.name) + '\' does not have any input ports.' + }); + + // reset the dialog + resetDialog(); + + deferred.reject(); + } + }).fail(function (xhr, status, error) { + // handle the error + nfErrorHandler.handleAjaxError(xhr, status, error); + + deferred.reject(); + }); + }).promise(); + }; + + /** + * Initializes the source panel for groups. + * + * @argument {selection} source The source of the connection + */ + var initializeSourceReadOnlyGroup = function (source) { + return $.Deferred(function (deferred) { + var sourceData = source.datum(); + var sourceName = sourceData.permissions.canRead ? sourceData.component.name : sourceData.id; + + // populate the port information + $('#read-only-output-port-source').show(); + + // populate the component information + $('#connection-source-component-id').val(sourceData.id); + + // populate the group details + $('#connection-source-group-id').val(sourceData.id); + $('#connection-source-group-name').text(sourceName); + + // resolve the deferred + deferred.resolve(); + }).promise(); + }; + + /** + * Initializes the source in the existing connection dialog. + * + * @argument {selection} source The source + */ + var initializeSourceEditConnectionDialog = function (source) { + if (nfCanvasUtils.isProcessor(source)) { + return initializeSourceProcessor(source); + } else if (nfCanvasUtils.isInputPort(source)) { + return initializeSourceInputPort(source); + } else if (nfCanvasUtils.isFunnel(source)) { + return initializeSourceFunnel(source); + } else { + return initializeSourceReadOnlyGroup(source); + } + }; + + /** + * Initializes the destination in the existing connection dialog. + * + * @argument {selection} destination The destination + * @argument {object} connectionDestination The connection destination object + */ + var initializeDestinationEditConnectionDialog = function (destination, connectionDestination) { + if (nfCanvasUtils.isProcessor(destination)) { + return initializeDestinationProcessor(destination); + } else if (nfCanvasUtils.isOutputPort(destination)) { + return initializeDestinationOutputPort(destination); + } else if (nfCanvasUtils.isRemoteProcessGroup(destination)) { + return initializeDestinationRemoteProcessGroup(destination, connectionDestination); + } else if (nfCanvasUtils.isFunnel(destination)) { + return initializeDestinationFunnel(destination); + } else { + return initializeDestinationProcessGroup(destination); + } + }; + + /** + * Creates an option for the specified relationship name. + * + * @argument {string} name The relationship name + */ + var createRelationshipOption = function (name) { + var nameSplit = name.split(":"); + var nameLabel = name; + + if (nameSplit.length > 1) { + // Example: publishes:data_transformation_format:1.0.0:message_router:stream_publish_url + var pubSub = nameSplit[0]; + pubSub = pubSub.charAt(0).toUpperCase() + pubSub.slice(1); + nameLabel = pubSub + " " + nameSplit[1] + "/" + nameSplit[2] + " on " + nameSplit[4]; + } + + var relationshipLabel = $('
').text(nameLabel); + var relationshipValue = $('').text(name); + return $('
' + + '
').append(relationshipLabel).append(relationshipValue).appendTo('#relationship-names'); + }; + + /** + * Adds a new connection. + * + * @argument {array} selectedRelationships The selected relationships + */ + var addConnection = function (selectedRelationships) { + // get the connection details + var sourceId = $('#connection-source-id').val(); + var destinationId = $('#connection-destination-id').val(); + + // get the selection components + var sourceComponentId = $('#connection-source-component-id').val(); + var source = d3.select('#id-' + sourceComponentId); + var destinationComponentId = $('#connection-destination-component-id').val(); + var destination = d3.select('#id-' + destinationComponentId); + + // get the source/destination data + var sourceData = source.datum(); + var destinationData = destination.datum(); + + // add bend points if we're dealing with a self loop + var bends = []; + if (sourceComponentId === destinationComponentId) { + var rightCenter = { + x: sourceData.position.x + (sourceData.dimensions.width), + y: sourceData.position.y + (sourceData.dimensions.height / 2) + }; + + var xOffset = nfConnection.config.selfLoopXOffset; + var yOffset = nfConnection.config.selfLoopYOffset; + bends.push({ + 'x': (rightCenter.x + xOffset), + 'y': (rightCenter.y - yOffset) + }); + bends.push({ + 'x': (rightCenter.x + xOffset), + 'y': (rightCenter.y + yOffset) + }); + } else { + var existingConnections = []; + + // get all connections for the source component + var connectionsForSourceComponent = nfConnection.getComponentConnections(sourceComponentId); + $.each(connectionsForSourceComponent, function (_, connectionForSourceComponent) { + // get the id for the source/destination component + var connectionSourceComponentId = nfCanvasUtils.getConnectionSourceComponentId(connectionForSourceComponent); + var connectionDestinationComponentId = nfCanvasUtils.getConnectionDestinationComponentId(connectionForSourceComponent); + + // if the connection is between these same components, consider it for collisions + if ((connectionSourceComponentId === sourceComponentId && connectionDestinationComponentId === destinationComponentId) || + (connectionDestinationComponentId === sourceComponentId && connectionSourceComponentId === destinationComponentId)) { + + // record all connections between these two components in question + existingConnections.push(connectionForSourceComponent); + } + }); + + // if there are existing connections between these components, ensure the new connection won't collide + if (existingConnections.length > 0) { + var avoidCollision = false; + $.each(existingConnections, function (_, existingConnection) { + // only consider multiple connections with no bend points a collision, the existance of + // bend points suggests that the user has placed the connection into a desired location + if (nfCommon.isEmpty(existingConnection.bends)) { + avoidCollision = true; + return false; + } + }); + + // if we need to avoid a collision + if (avoidCollision === true) { + // determine the middle of the source/destination components + var sourceMiddle = [sourceData.position.x + (sourceData.dimensions.width / 2), sourceData.position.y + (sourceData.dimensions.height / 2)]; + var destinationMiddle = [destinationData.position.x + (destinationData.dimensions.width / 2), destinationData.position.y + (destinationData.dimensions.height / 2)]; + + // detect if the line is more horizontal or vertical + var slope = ((sourceMiddle[1] - destinationMiddle[1]) / (sourceMiddle[0] - destinationMiddle[0])); + var isMoreHorizontal = slope <= 1 && slope >= -1; + + // determines if the specified coordinate collides with another connection + var collides = function (x, y) { + var collides = false; + $.each(existingConnections, function (_, existingConnection) { + if (!nfCommon.isEmpty(existingConnection.bends)) { + if (isMoreHorizontal) { + // horizontal lines are adjusted in the y space + if (existingConnection.bends[0].y === y) { + collides = true; + return false; + } + } else { + // vertical lines are adjusted in the x space + if (existingConnection.bends[0].x === x) { + collides = true; + return false; + } + } + } + }); + return collides; + }; + + // find the mid point on the connection + var xCandidate = (sourceMiddle[0] + destinationMiddle[0]) / 2; + var yCandidate = (sourceMiddle[1] + destinationMiddle[1]) / 2; + + // attempt to position this connection so it doesn't collide + var xStep = isMoreHorizontal ? 0 : CONNECTION_OFFSET_X_INCREMENT; + var yStep = isMoreHorizontal ? CONNECTION_OFFSET_Y_INCREMENT : 0; + var positioned = false; + while (positioned === false) { + // consider above and below, then increment and try again (if necessary) + if (collides(xCandidate - xStep, yCandidate - yStep) === false) { + bends.push({ + 'x': (xCandidate - xStep), + 'y': (yCandidate - yStep) + }); + positioned = true; + } else if (collides(xCandidate + xStep, yCandidate + yStep) === false) { + bends.push({ + 'x': (xCandidate + xStep), + 'y': (yCandidate + yStep) + }); + positioned = true; + } + + if (isMoreHorizontal) { + yStep += CONNECTION_OFFSET_Y_INCREMENT; + } else { + xStep += CONNECTION_OFFSET_X_INCREMENT; + } + } + } + } + } + + // determine the source group id + var sourceGroupId = $('#connection-source-group-id').val(); + var destinationGroupId = $('#connection-destination-group-id').val(); + + // determine the source and destination types + var sourceType = nfCanvasUtils.getConnectableTypeForSource(source); + var destinationType = nfCanvasUtils.getConnectableTypeForDestination(destination); + + // get the settings + var connectionName = $('#connection-name').val(); + var flowFileExpiration = $('#flow-file-expiration').val(); + var backPressureObjectThreshold = $('#back-pressure-object-threshold').val(); + var backPressureDataSizeThreshold = $('#back-pressure-data-size-threshold').val(); + var prioritizers = $('#prioritizer-selected').sortable('toArray'); + var loadBalanceStrategy = $('#load-balance-strategy-combo').combo('getSelectedOption').value; + var shouldLoadBalance = 'DO_NOT_LOAD_BALANCE' !== loadBalanceStrategy; + var loadBalancePartitionAttribute = shouldLoadBalance && 'PARTITION_BY_ATTRIBUTE' === loadBalanceStrategy ? $('#load-balance-partition-attribute').val() : ''; + var loadBalanceCompression = shouldLoadBalance ? $('#load-balance-compression-combo').combo('getSelectedOption').value : 'DO_NOT_COMPRESS'; + + if (validateSettings()) { + var connectionEntity = { + 'revision': nfClient.getRevision({ + 'revision': { + 'version': 0 + } + }), + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(), + 'component': { + 'name': connectionName, + 'source': { + 'id': sourceId, + 'groupId': sourceGroupId, + 'type': sourceType + }, + 'destination': { + 'id': destinationId, + 'groupId': destinationGroupId, + 'type': destinationType + }, + 'selectedRelationships': selectedRelationships, + 'flowFileExpiration': flowFileExpiration, + 'backPressureDataSizeThreshold': backPressureDataSizeThreshold, + 'backPressureObjectThreshold': backPressureObjectThreshold, + 'bends': bends, + 'prioritizers': prioritizers, + 'loadBalanceStrategy': loadBalanceStrategy, + 'loadBalancePartitionAttribute': loadBalancePartitionAttribute, + 'loadBalanceCompression': loadBalanceCompression + } + }; + + // create the new connection + $.ajax({ + type: 'POST', + url: config.urls.api + '/process-groups/' + encodeURIComponent(nfCanvasUtils.getGroupId()) + '/connections', + data: JSON.stringify(connectionEntity), + dataType: 'json', + contentType: 'application/json' + }).done(function (response) { + // add the connection + nfGraph.add({ + 'connections': [response] + }, { + 'selectAll': true + }); + + // reload the connections source/destination components + nfCanvasUtils.reloadConnectionSourceAndDestination(sourceComponentId, destinationComponentId); + + // update component visibility + nfGraph.updateVisibility(); + + // update the birdseye + nfBirdseye.refresh(); + }).fail(function (xhr, status, error) { + // handle the error + nfErrorHandler.handleAjaxError(xhr, status, error); + }); + } + }; + + /** + * Updates an existing connection. + * + * @argument {array} selectedRelationships The selected relationships + */ + var updateConnection = function (selectedRelationships) { + // get the connection details + var connectionId = $('#connection-id').text(); + var connectionUri = $('#connection-uri').val(); + + // get the source details + var sourceComponentId = $('#connection-source-component-id').val(); + + // get the destination details + var destinationComponentId = $('#connection-destination-component-id').val(); + var destination = d3.select('#id-' + destinationComponentId); + var destinationType = nfCanvasUtils.getConnectableTypeForDestination(destination); + + // get the destination details + var destinationId = $('#connection-destination-id').val(); + var destinationGroupId = $('#connection-destination-group-id').val(); + + // get the settings + var connectionName = $('#connection-name').val(); + var flowFileExpiration = $('#flow-file-expiration').val(); + var backPressureObjectThreshold = $('#back-pressure-object-threshold').val(); + var backPressureDataSizeThreshold = $('#back-pressure-data-size-threshold').val(); + var prioritizers = $('#prioritizer-selected').sortable('toArray'); + var loadBalanceStrategy = $('#load-balance-strategy-combo').combo('getSelectedOption').value; + var shouldLoadBalance = 'DO_NOT_LOAD_BALANCE' !== loadBalanceStrategy; + var loadBalancePartitionAttribute = shouldLoadBalance && 'PARTITION_BY_ATTRIBUTE' === loadBalanceStrategy ? $('#load-balance-partition-attribute').val() : ''; + var loadBalanceCompression = shouldLoadBalance ? $('#load-balance-compression-combo').combo('getSelectedOption').value : 'DO_NOT_COMPRESS'; + + if (validateSettings()) { + var d = nfConnection.get(connectionId); + var connectionEntity = { + 'revision': nfClient.getRevision(d), + 'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(), + 'component': { + 'id': connectionId, + 'name': connectionName, + 'destination': { + 'id': destinationId, + 'groupId': destinationGroupId, + 'type': destinationType + }, + 'selectedRelationships': selectedRelationships, + 'flowFileExpiration': flowFileExpiration, + 'backPressureDataSizeThreshold': backPressureDataSizeThreshold, + 'backPressureObjectThreshold': backPressureObjectThreshold, + 'prioritizers': prioritizers, + 'loadBalanceStrategy': loadBalanceStrategy, + 'loadBalancePartitionAttribute': loadBalancePartitionAttribute, + 'loadBalanceCompression': loadBalanceCompression + } + }; + + // update the connection + return $.ajax({ + type: 'PUT', + url: connectionUri, + data: JSON.stringify(connectionEntity), + dataType: 'json', + contentType: 'application/json' + }).done(function (response) { + // update this connection + nfConnection.set(response); + + // reload the connections source/destination components + nfCanvasUtils.reloadConnectionSourceAndDestination(sourceComponentId, destinationComponentId); + }).fail(function (xhr, status, error) { + if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) { + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: nfCommon.escapeHtml(xhr.responseText), + }); + } else { + nfErrorHandler.handleAjaxError(xhr, status, error); + } + }); + } else { + return $.Deferred(function (deferred) { + deferred.reject(); + }).promise(); + } + }; + + /** + * Returns an array of selected relationship names. + */ + var getSelectedRelationships = function () { + // get all available relationships + var availableRelationships = $('#relationship-names'); + var selectedRelationships = []; + + // go through each relationship to determine which are selected + $.each(availableRelationships.children(), function (i, relationshipElement) { + var relationship = $(relationshipElement); + + // get each relationship and its corresponding checkbox + var relationshipCheck = relationship.children('div.available-relationship'); + + // see if this relationship has been selected + if (relationshipCheck.hasClass('checkbox-checked')) { + selectedRelationships.push(relationship.children('span.relationship-name-value').text()); + } + }); + + return selectedRelationships; + }; + + /** + * Validates the specified settings. + */ + var validateSettings = function () { + var errors = []; + + // validate the settings + if (nfCommon.isBlank($('#flow-file-expiration').val())) { + errors.push('File expiration must be specified'); + } + if (!$.isNumeric($('#back-pressure-object-threshold').val())) { + errors.push('Back pressure object threshold must be an integer value'); + } + if (nfCommon.isBlank($('#back-pressure-data-size-threshold').val())) { + errors.push('Back pressure data size threshold must be specified'); + } + if ($('#load-balance-strategy-combo').combo('getSelectedOption').value === 'PARTITION_BY_ATTRIBUTE' + && nfCommon.isBlank($('#load-balance-partition-attribute').val())) { + errors.push('Cannot set Load Balance Strategy to "Partition by attribute" without providing a partitioning "Attribute Name"'); + } + + if (errors.length > 0) { + nfDialog.showOkDialog({ + headerText: 'Connection Configuration', + dialogContent: nfCommon.formatUnorderedList(errors) + }); + return false; + } else { + return true; + } + }; + + /** + * Resets the dialog. + */ + var resetDialog = function () { + // reset the prioritizers + var selectedList = $('#prioritizer-selected'); + var availableList = $('#prioritizer-available'); + selectedList.children().detach().appendTo(availableList); + + // sort the available list + var listItems = availableList.children('li').get(); + listItems.sort(function (a, b) { + var compA = $(a).text().toUpperCase(); + var compB = $(b).text().toUpperCase(); + return (compA < compB) ? -1 : (compA > compB) ? 1 : 0; + }); + + // clear the available list and re-insert each list item + $.each(listItems, function () { + $(this).detach(); + }); + $.each(listItems, function () { + $(this).appendTo(availableList); + }); + + // reset the fields + $('#connection-name').val(''); + $('#relationship-names').css('border-width', '0').empty(); + $('#relationship-names-container').show(); + + // clear the id field + nfCommon.clearField('connection-id'); + + // hide all the connection source panels + $('#processor-source').hide(); + $('#input-port-source').hide(); + $('#output-port-source').hide(); + $('#read-only-output-port-source').hide(); + $('#funnel-source').hide(); + + // hide all the connection destination panels + $('#processor-destination').hide(); + $('#input-port-destination').hide(); + $('#output-port-destination').hide(); + $('#funnel-destination').hide(); + + // clear and destination details + $('#connection-source-id').val(''); + $('#connection-source-component-id').val(''); + $('#connection-source-group-id').val(''); + + // clear any destination details + $('#connection-destination-id').val(''); + $('#connection-destination-component-id').val(''); + $('#connection-destination-group-id').val(''); + + // clear any ports + $('#output-port-options').empty(); + $('#input-port-options').empty(); + + // clear load balance settings + $('#load-balance-strategy-combo').combo('setSelectedOption', nfCommon.loadBalanceStrategyOptions[0]); + $('#load-balance-partition-attribute').val(''); + $('#load-balance-compression-combo').combo('setSelectedOption', nfCommon.loadBalanceCompressionOptions[0]); + + // see if the temp edge needs to be removed + removeTempEdge(); + }; + + var nfConnectionConfiguration = { + + /** + * Initialize the connection configuration. + * + * @param nfBirdseyeRef The nfBirdseye module. + * @param nfGraphRef The nfGraph module. + */ + init: function (nfBirdseyeRef, nfGraphRef, defaultBackPressureObjectThresholdRef, defaultBackPressureDataSizeThresholdRef) { + nfBirdseye = nfBirdseyeRef; + nfGraph = nfGraphRef; + + defaultBackPressureObjectThreshold = defaultBackPressureObjectThresholdRef; + defaultBackPressureDataSizeThreshold = defaultBackPressureDataSizeThresholdRef; + + // initially hide the relationship names container + $('#relationship-names-container').show(); + + // initialize the configure connection dialog + $('#connection-configuration').modal({ + scrollableContentStyle: 'scrollable', + headerText: 'Configure Connection', + handler: { + close: function () { + // reset the dialog on close + resetDialog(); + }, + open: function () { + nfCommon.toggleScrollable($('#' + this.find('.tab-container').attr('id') + '-content').get(0)); + } + } + }); + + // initialize the properties tabs + $('#connection-configuration-tabs').tabbs({ + tabStyle: 'tab', + selectedTabStyle: 'selected-tab', + scrollableTabContentStyle: 'scrollable', + tabs: [{ + name: 'Details', + tabContentId: 'connection-details-tab-content' + }, { + name: 'Settings', + tabContentId: 'connection-settings-tab-content' + }] + }); + + // initialize the load balance strategy combo + $('#load-balance-strategy-combo').combo({ + options: nfCommon.loadBalanceStrategyOptions, + select: function (selectedOption) { + // Show the appropriate configurations + if (selectedOption.value === 'PARTITION_BY_ATTRIBUTE') { + $('#load-balance-partition-attribute-setting-separator').show(); + $('#load-balance-partition-attribute-setting').show(); + } else { + $('#load-balance-partition-attribute-setting-separator').hide(); + $('#load-balance-partition-attribute-setting').hide(); + } + if (selectedOption.value === 'DO_NOT_LOAD_BALANCE') { + $('#load-balance-compression-setting').hide(); + } else { + $('#load-balance-compression-setting').show(); + } + } + }); + + + // initialize the load balance compression combo + $('#load-balance-compression-combo').combo({ + options: nfCommon.loadBalanceCompressionOptions + }); + + // load the processor prioritizers + $.ajax({ + type: 'GET', + url: config.urls.prioritizers, + dataType: 'json' + }).done(function (response) { + // create an element for each available prioritizer + $.each(response.prioritizerTypes, function (i, documentedType) { + nfConnectionConfiguration.addAvailablePrioritizer('#prioritizer-available', documentedType); + }); + + // make the prioritizer containers sortable + $('#prioritizer-available, #prioritizer-selected').sortable({ + containment: $('#connection-settings-tab-content').find('.settings-right'), + connectWith: 'ul', + placeholder: 'ui-state-highlight', + scroll: true, + opacity: 0.6 + }); + $('#prioritizer-available, #prioritizer-selected').disableSelection(); + }).fail(nfErrorHandler.handleAjaxError); + }, + + /** + * Adds the specified prioritizer to the specified container. + * + * @argument {string} prioritizerContainer The dom Id of the prioritizer container + * @argument {object} prioritizerType The type of prioritizer + */ + addAvailablePrioritizer: function (prioritizerContainer, prioritizerType) { + var type = prioritizerType.type; + var name = nfCommon.substringAfterLast(type, '.'); + + // add the prioritizers to the available list + var prioritizerList = $(prioritizerContainer); + var prioritizer = $('
  • ').append($('').text(name)).attr('id', type).addClass('ui-state-default').appendTo(prioritizerList); + + // add the description if applicable + if (nfCommon.isDefinedAndNotNull(prioritizerType.description)) { + $('
    ').appendTo(prioritizer).qtip($.extend({ + content: nfCommon.escapeHtml(prioritizerType.description) + }, nfCommon.config.tooltipConfig)); + } + }, + + /** + * Shows the dialog for creating a new connection. + * + * @argument {string} sourceId The source id + * @argument {string} destinationId The destination id + */ + createConnection: function (sourceId, destinationId) { + // select the source and destination + var source = d3.select('#id-' + sourceId); + var destination = d3.select('#id-' + destinationId); + + if (source.empty() || destination.empty()) { + return; + } + + // initialize the connection dialog + $.when(initializeSourceNewConnectionDialog(source), initializeDestinationNewConnectionDialog(destination)).done(function () { + + if (nfCanvasUtils.isProcessor(source) || nfCanvasUtils.isProcessor(destination)) { + addDialogRelationshipsChangeListener(); + + // if there is a single relationship auto select + var relationships = $('#relationship-names').children('div'); + if (relationships.length === 1) { + relationships.children('div.available-relationship').removeClass('checkbox-unchecked').addClass('checkbox-checked'); + } + + // configure the button model + $('#connection-configuration').modal('setButtonModel', [{ + buttonText: 'Add', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + disabled: function () { + // ensure some relationships were selected + return getSelectedRelationships().length === 0; + }, + handler: { + click: function () { + addConnection(getSelectedRelationships()); + + // close the dialog + $('#connection-configuration').modal('hide'); + } + } + }, + { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $('#connection-configuration').modal('hide'); + } + } + }]); + } else { + // configure the button model + $('#connection-configuration').modal('setButtonModel', [{ + buttonText: 'Add', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + handler: { + click: function () { + // add the connection + addConnection(); + + // close the dialog + $('#connection-configuration').modal('hide'); + } + } + }, + { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + $('#connection-configuration').modal('hide'); + } + } + }]); + } + + // set the default values + $('#flow-file-expiration').val('0 sec'); + $('#back-pressure-object-threshold').val(defaultBackPressureObjectThreshold); + $('#back-pressure-data-size-threshold').val(defaultBackPressureDataSizeThreshold); + + // select the first tab + $('#connection-configuration-tabs').find('li:first').click(); + + // configure the header and show the dialog + $('#connection-configuration').modal('setHeaderText', 'Create Connection').modal('show'); + + // add the ellipsis if necessary + $('#connection-configuration div.relationship-name').ellipsis(); + + // fill in the connection id + nfCommon.populateField('connection-id', null); + + // show the border if necessary + var relationshipNames = $('#relationship-names'); + if (relationshipNames.is(':visible') && relationshipNames.get(0).scrollHeight > Math.round(relationshipNames.innerHeight())) { + relationshipNames.css('border-width', '1px'); + } + }).fail(function () { + // see if the temp edge needs to be removed + removeTempEdge(); + }); + }, + + /** + * Shows the configuration for the specified connection. If a destination is + * specified it will be considered a new destination. + * + * @argument {selection} selection The connection entry + * @argument {selection} destination Optional new destination + */ + showConfiguration: function (selection, destination) { + return $.Deferred(function (deferred) { + var connectionEntry = selection.datum(); + var connection = connectionEntry.component; + + // identify the source component + var sourceComponentId = nfCanvasUtils.getConnectionSourceComponentId(connectionEntry); + var source = d3.select('#id-' + sourceComponentId); + + // identify the destination component + if (nfCommon.isUndefinedOrNull(destination)) { + var destinationComponentId = nfCanvasUtils.getConnectionDestinationComponentId(connectionEntry); + destination = d3.select('#id-' + destinationComponentId); + } + + // initialize the connection dialog + $.when(initializeSourceEditConnectionDialog(source), initializeDestinationEditConnectionDialog(destination, connection.destination)).done(function () { + var availableRelationships = connection.availableRelationships; + var selectedRelationships = connection.selectedRelationships; + + // Added this block to force add destination relationships to + // get blueprint generation working + if (nfCanvasUtils.isProcessor(destination)) { + if (availableRelationships == undefined) { + // When the source is a port, this could be null or + // undefined since the backend the attribute doesn't + // exist + availableRelationships = []; + } + + var processorData = destination.datum(); + $.each(processorData.component.relationships, function (i, relationship) { + availableRelationships.push(relationship.name); + }); + } + + // show the available relationship if applicable + if (nfCommon.isDefinedAndNotNull(availableRelationships) || nfCommon.isDefinedAndNotNull(selectedRelationships)) { + // populate the available connections + $.each(availableRelationships, function (i, name) { + createRelationshipOption(name); + }); + + addDialogRelationshipsChangeListener(); + + // ensure all selected relationships are present + // (may be undefined) and selected + $.each(selectedRelationships, function (i, name) { + // mark undefined relationships accordingly + if ($.inArray(name, availableRelationships) === -1) { + var option = createRelationshipOption(name); + $(option).children('div.relationship-name').addClass('undefined'); + } + + // ensure all selected relationships are checked + var relationships = $('#relationship-names').children('div'); + $.each(relationships, function (i, relationship) { + var relationshipName = $(relationship).children('span.relationship-name-value'); + if (relationshipName.text() === name) { + $(relationship).children('div.available-relationship').removeClass('checkbox-unchecked').addClass('checkbox-checked'); + } + }); + }); + } + + // if the source is a process group or remote process group, select the appropriate port if applicable + if (nfCanvasUtils.isProcessGroup(source) || nfCanvasUtils.isRemoteProcessGroup(source)) { + // populate the connection source details + $('#connection-source-id').val(connection.source.id); + $('#read-only-output-port-name').text(connection.source.name).attr('title', connection.source.name); + } + + // if the destination is a process gorup or remote process group, select the appropriate port if applicable + if (nfCanvasUtils.isProcessGroup(destination) || nfCanvasUtils.isRemoteProcessGroup(destination)) { + var destinationData = destination.datum(); + + // when the group ids differ, its a new destination component so we don't want to preselect any port + if (connection.destination.groupId === destinationData.id) { + $('#input-port-options').combo('setSelectedOption', { + value: connection.destination.id + }); + } + } + + // set the connection settings + $('#connection-name').val(connection.name); + $('#flow-file-expiration').val(connection.flowFileExpiration); + $('#back-pressure-object-threshold').val(connection.backPressureObjectThreshold); + $('#back-pressure-data-size-threshold').val(connection.backPressureDataSizeThreshold); + + // select the load balance combos + $('#load-balance-strategy-combo').combo('setSelectedOption', { + value: connection.loadBalanceStrategy + }); + $('#load-balance-compression-combo').combo('setSelectedOption', { + value: connection.loadBalanceCompression + }); + $('#load-balance-partition-attribute').val(connection.loadBalancePartitionAttribute); + + // format the connection id + nfCommon.populateField('connection-id', connection.id); + + // handle each prioritizer + $.each(connection.prioritizers, function (i, type) { + $('#prioritizer-available').children('li[id="' + type + '"]').detach().appendTo('#prioritizer-selected'); + }); + + // store the connection details + $('#connection-uri').val(connectionEntry.uri); + + // configure the button model + $('#connection-configuration').modal('setButtonModel', [{ + buttonText: 'Apply', + color: { + base: '#728E9B', + hover: '#004849', + text: '#ffffff' + }, + disabled: function () { + // ensure some relationships were selected with a processor as the source + if (nfCanvasUtils.isProcessor(source) || nfCanvasUtils.isProcessor(destination)) { + return getSelectedRelationships().length === 0; + } + return false; + }, + handler: { + click: function () { + // see if we're working with a processor as the source + if (nfCanvasUtils.isProcessor(source) || nfCanvasUtils.isProcessor(destination)) { + // update the selected relationships + updateConnection(getSelectedRelationships()).done(function () { + deferred.resolve(); + }).fail(function () { + deferred.reject(); + }); + } else { + // there are no relationships, but the source wasn't a processor, so update anyway + updateConnection(undefined).done(function () { + deferred.resolve(); + }).fail(function () { + deferred.reject(); + }); + } + + // close the dialog + $('#connection-configuration').modal('hide'); + } + } + }, + { + buttonText: 'Cancel', + color: { + base: '#E3E8EB', + hover: '#C7D2D7', + text: '#004849' + }, + handler: { + click: function () { + // hide the dialog + $('#connection-configuration').modal('hide'); + + // reject the deferred + deferred.reject(); + } + } + }]); + + // show the details dialog + $('#connection-configuration').modal('setHeaderText', 'Configure Connection').modal('show'); + + // add the ellipsis if necessary + $('#connection-configuration div.relationship-name').ellipsis(); + + // show the border if necessary + var relationshipNames = $('#relationship-names'); + if (relationshipNames.is(':visible') && relationshipNames.get(0).scrollHeight > Math.round(relationshipNames.innerHeight())) { + relationshipNames.css('border-width', '1px'); + } + }).fail(function () { + deferred.reject(); + }); + }).promise(); + } + }; + + return nfConnectionConfiguration; +})); -- cgit 1.2.3-korg