- Back pressure data size threshold
-
+
+
+
+
+ Load Balance Strategy
+
+
+
+
+
+
-
-
+
+
+ Load Balance Compression
+
+
+
-
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/connection-details.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/connection-details.css
index ce944859f5..c58febc4c1 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/connection-details.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/connection-details.css
@@ -22,7 +22,7 @@
z-index: 1301;
display: none;
width: 700px;
- height: 400px;
+ height: 500px;
}
#connection-details div.configuration-tab {
@@ -40,4 +40,16 @@
#connection-details div.relationship-name {
display: inline-block;
line-height: normal;
-}
\ No newline at end of file
+}
+
+.multi-column-settings {
+ display: flex;
+}
+
+.multi-column-settings .setting {
+ flex-grow: 1;
+}
+
+.multi-column-settings .separator {
+ min-width: 5px;
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/graph.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/graph.css
index 15daa76bec..8b1717bb89 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/graph.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/graph.css
@@ -320,13 +320,25 @@ g.connection path.connection-path.unauthorized {
stroke-dasharray: 3,3;
}
-text.connection-from-run-status, text.connection-to-run-status, text.expiration-icon {
+text.connection-from-run-status, text.connection-to-run-status, text.expiration-icon, text.load-balance-icon {
fill: #728e9b;
font-family: FontAwesome;
- font-size: 10px;
+ font-size: 12px;
text-shadow: 0 0 4px rgba(255,255,255,1);
}
+text.load-balance-icon-active {
+ fill: #44a3cf
+}
+
+text.load-balance-icon-184 {
+ transform-origin: 189px 10px 0;
+}
+
+text.load-balance-icon-200 {
+ transform-origin: 205px 10px 0;
+}
+
text.connection-from-run-status.is-missing-port, text.connection-to-run-status.is-missing-port {
fill: #cf9f5d;
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection-configuration.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection-configuration.js
index 2d7938c9c9..eb58df3fca 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection-configuration.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection-configuration.js
@@ -23,13 +23,14 @@
'd3',
'nf.ErrorHandler',
'nf.Common',
+ 'nf.ClusterSummary',
'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));
+ function ($, d3, nfErrorHandler, nfCommon, nfClusterSummary, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) {
+ return (nf.ConnectionConfiguration = factory($, d3, nfErrorHandler, nfCommon, nfClusterSummary, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection));
});
} else if (typeof exports === 'object' && typeof module === 'object') {
module.exports = (nf.ConnectionConfiguration =
@@ -37,6 +38,7 @@
require('d3'),
require('nf.ErrorHandler'),
require('nf.Common'),
+ require('nf.ClusterSummary'),
require('nf.Dialog'),
require('nf.Storage'),
require('nf.Client'),
@@ -47,13 +49,14 @@
root.d3,
root.nf.ErrorHandler,
root.nf.Common,
+ root.nf.ClusterSummary,
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) {
+}(this, function ($, d3, nfErrorHandler, nfCommon, nfClusterSummary, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfConnection) {
'use strict';
var nfBirdseye;
@@ -1007,6 +1010,10 @@
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);
@@ -1025,7 +1032,10 @@
'flowFileExpiration': flowFileExpiration,
'backPressureDataSizeThreshold': backPressureDataSizeThreshold,
'backPressureObjectThreshold': backPressureObjectThreshold,
- 'prioritizers': prioritizers
+ 'prioritizers': prioritizers,
+ 'loadBalanceStrategy': loadBalanceStrategy,
+ 'loadBalancePartitionAttribute': loadBalancePartitionAttribute,
+ 'loadBalanceCompression': loadBalanceCompression
}
};
@@ -1099,6 +1109,10 @@
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({
@@ -1222,6 +1236,32 @@
}]
});
+ // 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',
@@ -1393,6 +1433,20 @@
$('#back-pressure-object-threshold').val(connection.backPressureObjectThreshold);
$('#back-pressure-data-size-threshold').val(connection.backPressureDataSizeThreshold);
+ // select the load balance combos
+ if (nfClusterSummary.isConnectedToCluster()) {
+ $('#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);
+ $('#load-balance-settings').show();
+ } else {
+ $('#load-balance-settings').hide();
+ }
+
// format the connection id
nfCommon.populateField('connection-id', connection.id);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js
index 145e1a360d..e6d7b7d247 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js
@@ -60,7 +60,7 @@
// the dimensions for the connection label
var dimensions = {
- width: 200
+ width: 216
};
// width of a backpressure indicator - half of width, left/right padding, left/right border
@@ -257,6 +257,16 @@
return false;
};
+ /**
+ * Determines whether load-balance is configured for the specified connection.
+ *
+ * @param {object} connection
+ * @return {boolean} Whether load-balance is configured
+ */
+ var isLoadBalanceConfigured = function (connection) {
+ return nfCommon.isDefinedAndNotNull(connection.loadBalanceStrategy) && 'DO_NOT_LOAD_BALANCE' !== connection.loadBalanceStrategy;
+ };
+
/**
* Sorts the specified connections according to the z index.
*
@@ -886,7 +896,7 @@
connectionFrom.append('text')
.attrs({
'class': 'connection-from-run-status',
- 'x': 185,
+ 'x': 200,
'y': 14
});
} else {
@@ -995,7 +1005,7 @@
connectionTo.append('text')
.attrs({
'class': 'connection-to-run-status',
- 'x': 185,
+ 'x': 200,
'y': 14
});
} else {
@@ -1195,11 +1205,23 @@
'class': 'size'
});
+ // load balance icon
+ // x is set dynamically to slide to right, depending on whether expiration icon is shown.
+ queued.append('text')
+ .attrs({
+ 'class': 'load-balance-icon',
+ 'y': 14
+ })
+ .text(function () {
+ return '\uf042';
+ })
+ .append('title');
+
// expiration icon
queued.append('text')
.attrs({
'class': 'expiration-icon',
- 'x': 185,
+ 'x': 200,
'y': 14
})
.text(function () {
@@ -1341,6 +1363,52 @@
}
});
+ // determine whether or not to show the load-balance icon
+ connectionLabelContainer.select('text.load-balance-icon')
+ .classed('hidden', function () {
+ if (d.permissions.canRead) {
+ return !isLoadBalanceConfigured(d.component);
+ } else {
+ return true;
+ }
+ }).classed('load-balance-icon-active fa-rotate-90', function (d) {
+ return d.permissions.canRead && d.component.loadBalanceStatus === 'LOAD_BALANCE_ACTIVE';
+
+ }).classed('load-balance-icon-184', function() {
+ return d.permissions.canRead && isExpirationConfigured(d.component);
+
+ }).classed('load-balance-icon-200', function() {
+ return d.permissions.canRead && !isExpirationConfigured(d.component);
+
+ }).attr('x', function() {
+ return d.permissions.canRead && isExpirationConfigured(d.component) ? 184 : 200;
+
+ }).select('title').text(function () {
+ if (d.permissions.canRead) {
+ var loadBalanceStrategy = nfCommon.getComboOptionText(nfCommon.loadBalanceStrategyOptions, d.component.loadBalanceStrategy);
+ if ('PARTITION_BY_ATTRIBUTE' === d.component.loadBalanceStrategy) {
+ loadBalanceStrategy += ' (' + d.component.loadBalancePartitionAttribute + ')'
+ }
+
+ var loadBalanceCompression = 'no compression';
+ switch (d.component.loadBalanceCompression) {
+ case 'COMPRESS_ATTRIBUTES_ONLY':
+ loadBalanceCompression = '\'Attribute\' compression';
+ break;
+ case 'COMPRESS_ATTRIBUTES_AND_CONTENT':
+ loadBalanceCompression = '\'Attribute and content\' compression';
+ break;
+ }
+ var loadBalanceStatus = 'LOAD_BALANCE_ACTIVE' === d.component.loadBalanceStatus ? ' Actively balancing...' : '';
+ return 'Load Balance is configured'
+ + ' with \'' + loadBalanceStrategy + '\' strategy'
+ + ' and ' + loadBalanceCompression + '.'
+ + loadBalanceStatus;
+ } else {
+ return '';
+ }
+ });
+
// determine whether or not to show the expiration icon
connectionLabelContainer.select('text.expiration-icon')
.classed('hidden', function () {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
index c07b95d9cb..8dca0d8557 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
@@ -1191,6 +1191,42 @@
MILLIS_PER_MINUTE: 60000,
MILLIS_PER_SECOND: 1000,
+ /**
+ * Constants for combo options.
+ */
+ loadBalanceStrategyOptions: [{
+ text: 'Do not load balance',
+ value: 'DO_NOT_LOAD_BALANCE',
+ description: 'Do not load balance FlowFiles between nodes in the cluster.'
+ }, {
+ text: 'Partition by attribute',
+ value: 'PARTITION_BY_ATTRIBUTE',
+ description: 'Determine which node to send a given FlowFile to based on the value of a user-specified FlowFile Attribute.'
+ + ' All FlowFiles that have the same value for said Attribute will be sent to the same node in the cluster.'
+ }, {
+ text: 'Round robin',
+ value: 'ROUND_ROBIN',
+ description: 'FlowFiles will be distributed to nodes in the cluster in a Round-Robin fashion.'
+ }, {
+ text: 'Single node',
+ value: 'SINGLE_NODE',
+ description: 'All FlowFiles will be sent to the same node. Which node they are sent to is not defined.'
+ }],
+
+ loadBalanceCompressionOptions: [{
+ text: 'Do not compress',
+ value: 'DO_NOT_COMPRESS',
+ description: 'FlowFiles will not be compressed'
+ }, {
+ text: 'Compress attributes only',
+ value: 'COMPRESS_ATTRIBUTES_ONLY',
+ description: 'FlowFiles\' attributes will be compressed, but the FlowFiles\' contents will not be'
+ }, {
+ text: 'Compress attributes and content',
+ value: 'COMPRESS_ATTRIBUTES_AND_CONTENT',
+ description: 'FlowFiles\' attributes and content will be compressed'
+ }],
+
/**
* Formats the specified duration.
*
@@ -1650,7 +1686,22 @@
*/
getComponentName: function (entity) {
return entity.permissions.canRead === true ? entity.component.name : entity.id;
+ },
+
+ /**
+ * Find the corresponding combo option text from a combo option values.
+ *
+ * @param {object} options The combo option array
+ * @param {string} value The target value
+ * @returns {string} The matched option text or undefined if not found
+ */
+ getComboOptionText: function (options, value) {
+ var matchedOption = options.find(function (option) {
+ return option.value === value;
+ });
+ return nfCommon.isDefinedAndNotNull(matchedOption) ? matchedOption.text : undefined;
}
+
};
return nfCommon;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-connection-details.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-connection-details.js
index a6538a905c..b1632b3397 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-connection-details.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-connection-details.js
@@ -21,20 +21,23 @@
if (typeof define === 'function' && define.amd) {
define(['jquery',
'nf.Common',
+ 'nf.ClusterSummary',
'nf.ErrorHandler'],
- function ($, nfCommon, nfErrorHandler) {
- return (nf.ConnectionDetails = factory($, nfCommon, nfErrorHandler));
+ function ($, nfCommon, nfClusterSummary, nfErrorHandler) {
+ return (nf.ConnectionDetails = factory($, nfCommon, nfClusterSummary, nfErrorHandler));
});
} else if (typeof exports === 'object' && typeof module === 'object') {
module.exports = (nf.ConnectionDetails = factory(require('jquery'),
require('nf.Common'),
+ require('nf.ClusterSummary'),
require('nf.ErrorHandler')));
} else {
nf.ConnectionDetails = factory(root.$,
root.nf.Common,
+ root.nf.ClusterSummary,
root.nf.ErrorHandler);
}
-}(this, function ($, nfCommon, nfErrorHandler) {
+}(this, function ($, nfCommon, nfClusterSummary, nfErrorHandler) {
'use strict';
/**
@@ -427,9 +430,12 @@
$('#read-only-relationship-names').css('border-width', '0').empty();
// clear the connection settings
- $('#read-only-flow-file-expiration').text('');
- $('#read-only-back-pressure-object-threshold').text('');
- $('#read-only-back-pressure-data-size-threshold').text('');
+ nfCommon.clearField('read-only-flow-file-expiration');
+ nfCommon.clearField('read-only-back-pressure-object-threshold');
+ nfCommon.clearField('read-only-back-pressure-data-size-threshold');
+ nfCommon.clearField('read-only-load-balance-strategy');
+ nfCommon.clearField('read-only-load-balance-partition-attribute');
+ nfCommon.clearField('read-only-load-balance-compression');
$('#read-only-prioritizers').empty();
},
open: function () {
@@ -446,6 +452,7 @@
* @argument {string} connectionId The connection id
*/
showDetails: function (groupId, connectionId) {
+
// get the group details
var groupXhr = $.ajax({
type: 'GET',
@@ -521,6 +528,27 @@
nfCommon.populateField('read-only-flow-file-expiration', connection.flowFileExpiration);
nfCommon.populateField('read-only-back-pressure-object-threshold', connection.backPressureObjectThreshold);
nfCommon.populateField('read-only-back-pressure-data-size-threshold', connection.backPressureDataSizeThreshold);
+ nfCommon.populateField('read-only-load-balance-strategy', nfCommon.getComboOptionText(nfCommon.loadBalanceStrategyOptions, connection.loadBalanceStrategy));
+ nfCommon.populateField('read-only-load-balance-partition-attribute', connection.loadBalancePartitionAttribute);
+ nfCommon.populateField('read-only-load-balance-compression', nfCommon.getComboOptionText(nfCommon.loadBalanceCompressionOptions, connection.loadBalanceCompression));
+
+ // Show the appropriate load-balance configurations
+ if (nfClusterSummary.isConnectedToCluster()) {
+ if (connection.loadBalanceStrategy === 'PARTITION_BY_ATTRIBUTE') {
+ $('#read-only-load-balance-partition-attribute-setting').show();
+ } else {
+ $('#read-only-load-balance-partition-attribute-setting').hide();
+ }
+ if (connection.loadBalanceStrategy === 'DO_NOT_LOAD_BALANCE') {
+ $('#read-only-load-balance-compression-setting').hide();
+ } else {
+ $('#read-only-load-balance-compression-setting').show();
+ }
+ $('#read-only-load-balance-settings').show();
+ } else {
+ $('#read-only-load-balance-settings').hide();
+ }
+
// prioritizers
if (nfCommon.isDefinedAndNotNull(connection.prioritizers) && connection.prioritizers.length > 0) {