blob: 83871dbcf86831bf4117ea408f354fbc80e56a2b [file] [log] [blame]
/*
* 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.
*/
/* 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 connectionUpsertionInProgress = false;
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);
});
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, also check create or updation in progress
return getSelectedRelationships().length === 0 || isConnectionUpsertionInProgess();
},
handler: {
click: function () {
addConnection(getSelectedRelationships());
}
}
},
{
buttonText: 'Cancel',
color: {
base: '#E3E8EB',
hover: '#C7D2D7',
text: '#004849'
},
disabled: function() {
// when add button is clicked, should disable until the addition action is completed
return isConnectionUpsertionInProgess();
},
handler: {
click: function () {
$('#connection-configuration').modal('hide');
}
}
}]);
// 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 () {
// configure the button model
$('#connection-configuration').modal('setButtonModel', [{
buttonText: 'Add',
color: {
base: '#728E9B',
hover: '#004849',
text: '#ffffff'
},
disabled : function(){
// when network is slow, should disable the button
return isConnectionUpsertionInProgess();
},
handler: {
click: function () {
// add the connection
addConnection();
}
}
},
{
buttonText: 'Cancel',
color: {
base: '#E3E8EB',
hover: '#C7D2D7',
text: '#004849'
},
disabled : function(){
return isConnectionUpsertionInProgess();
},
handler: {
click: function () {
$('#connection-configuration').modal('hide');
}
}
}]);
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 = [];
var publicOutputPortCount = 0;
$.each(processGroupContents.outputPorts, function (i, outputPort) {
if (outputPort.allowRemoteAccess) {
publicOutputPortCount++;
return;
}
// 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 local output ports.';
if (nfCommon.isEmpty(processGroupContents.outputPorts) === false
&& processGroupContents.outputPorts.length > publicOutputPortCount) {
message = 'Not authorized for any local 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 div.setting-name').text('Within Remote Group')
$('#connection-remote-source-url').text(remoteProcessGroup.targetUri).show();
$('#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 initializeDestinationProcessor(destination);
} 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();
}).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) {
if (!inputPort.allowRemoteAccess) {
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 local 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 div.setting-name').text('Within Remote Group')
$('#connection-remote-destination-url').text(remoteProcessGroup.targetUri).show();
$('#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);
if (nfCanvasUtils.isRemoteProcessGroup(source)) {
$('#connection-source-group div.setting-name').text('Within Remote Group');
if (sourceData.permissions.canRead) {
$('#connection-remote-source-url').text(sourceData.component.targetUri).show();
}
}
// 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 relationshipLabel = $('<div class="relationship-name nf-checkbox-label ellipsis"></div>').text(name);
var relationshipValue = $('<span class="relationship-name-value hidden"></span>').text(name);
return $('<div class="available-relationship-container"><div class="available-relationship nf-checkbox checkbox-unchecked"></div>' +
'</div>').append(relationshipLabel).append(relationshipValue).appendTo('#relationship-names');
};
/**
* To set or reset the connection addition/update in progress
* @param {boolean} status the status of connection addition/update
*/
var setConnectionUpsertionInProgess = function(status)
{
var needToUpdateDOM = (connectionUpsertionInProgress !== status) ;
connectionUpsertionInProgress = status;
if(needToUpdateDOM){
$('#connection-configuration').modal('refreshButtons');
}
}
/**
* returns whether the connection addition/update in progress
*/
var isConnectionUpsertionInProgess = function()
{
return connectionUpsertionInProgress;
}
/**
* Adds a new connection.
*
* @argument {array} selectedRelationships The selected relationships
*/
var addConnection = function (selectedRelationships) {
// to handle the case of slow network
//the add/cancel buttons should be disabled
setConnectionUpsertionInProgess(true);
// 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
});
setConnectionUpsertionInProgess(false);
// close the dialog
$('#connection-configuration').modal('hide');
// 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){
// update the button status
setConnectionUpsertionInProgess(false);
nfErrorHandler.handleConfigurationUpdateAjaxError(xhr, status, error);
});
}
};
/**
* Updates an existing connection.
*
* @argument {array} selectedRelationships The selected relationships
*/
var updateConnection = function (selectedRelationships) {
//apply, cancel buttons should be disabled, while the connection update is in progress
setConnectionUpsertionInProgess(true);
// 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 updation progress status
setConnectionUpsertionInProgess(false);
// close the dialog
$('#connection-configuration').modal('hide');
// update this connection
nfConnection.set(response);
// reload the connections source/destination components
nfCanvasUtils.reloadConnectionSourceAndDestination(sourceComponentId, destinationComponentId);
}).fail(function(xhr, status, error){
setConnectionUpsertionInProgess(false);
nfErrorHandler.handleConfigurationUpdateAjaxError(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: 'Configuration Error',
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').hide();
// 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) {
nfBirdseye = nfBirdseyeRef;
nfGraph = nfGraphRef;
// initially hide the relationship names container
$('#relationship-names-container').hide();
// 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 = $('<li></li>').append($('<span style="float: left;"></span>').text(name)).attr('id', type).addClass('ui-state-default').appendTo(prioritizerList);
// add the description if applicable
if (nfCommon.isDefinedAndNotNull(prioritizerType.description)) {
$('<div class="fa fa-question-circle"></div>').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, defaultSettings) {
// select the source and destination
var source = d3.select('#id-' + sourceId);
var destination = d3.select('#id-' + destinationId);
if (source.empty() || destination.empty()) {
return;
}
// reset labels
$('#connection-source-group div.setting-name').text('Within Group')
$('#connection-destination-group div.setting-name').text('Within Group')
$('#connection-remote-source-url').hide();
$('#connection-remote-destination-url').hide();
// initialize the connection dialog
$.when(initializeSourceNewConnectionDialog(source), initializeDestinationNewConnectionDialog(destination)).done(function () {
// set the default values
$('#flow-file-expiration').val(defaultSettings.flowfileExpiration);
$('#back-pressure-object-threshold').val(defaultSettings.objectThreshold);
$('#back-pressure-data-size-threshold').val(defaultSettings.dataSizeThreshold);
// 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);
}
// reset labels
$('#connection-source-group div.setting-name').text('Within Group')
$('#connection-destination-group div.setting-name').text('Within Group')
$('#connection-remote-source-url').hide();
$('#connection-remote-destination-url').hide();
// initialize the connection dialog
$.when(initializeSourceEditConnectionDialog(source), initializeDestinationEditConnectionDialog(destination, connection.destination)).done(function () {
var availableRelationships = connection.availableRelationships;
var selectedRelationships = connection.selectedRelationships;
// 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)) {
return getSelectedRelationships().length === 0 || isConnectionUpsertionInProgess();
}
return isConnectionUpsertionInProgess();
},
handler: {
click: function () {
setConnectionUpsertionInProgess(true);
// see if we're working with a processor as the source
if (nfCanvasUtils.isProcessor(source)) {
// update the selected relationships
updateConnection(getSelectedRelationships()).done(function () {
deferred.resolve();
}).fail(function () {
deferred.reject();
}).always(function(){
setConnectionUpsertionInProgess(false);
});
} 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();
}).always(function(){
setConnectionUpsertionInProgess(false);
});
}
}
}
},
{
buttonText: 'Cancel',
color: {
base: '#E3E8EB',
hover: '#C7D2D7',
text: '#004849'
},
disabled: function(){
return isConnectionUpsertionInProgess();
},
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;
}));