NIFI-5661: Adding Load Balance config UI

Incorporated review comments.
Move combo options to a common place.

This closes #3046
This commit is contained in:
Koji Kawamura 2018-09-14 21:18:04 +09:00 committed by Matt Gilman
parent a6f722222a
commit 8a751e8018
No known key found for this signature in database
GPG Key ID: DF61EC19432AEE37
8 changed files with 339 additions and 44 deletions

View File

@ -42,22 +42,57 @@
<input type="text" id="flow-file-expiration" name="flow-file-expiration" class="setting-input"/>
</div>
</div>
<div class="setting">
<div class="setting-name">
Back pressure object threshold
<div class="fa fa-question-circle" alt="Info" title="The maximum number of objects that can be queued before back pressure is applied."></div>
<div class="multi-column-settings">
<div class="setting">
<div class="setting-name">
Back Pressure<br/>Object threshold
<div class="fa fa-question-circle" alt="Info" title="The maximum number of objects that can be queued before back pressure is applied."></div>
</div>
<div class="setting-field">
<input type="text" id="back-pressure-object-threshold" name="back-pressure-object-threshold" class="setting-input"/>
</div>
</div>
<div class="setting-field">
<input type="text" id="back-pressure-object-threshold" name="back-pressure-object-threshold" class="setting-input"/>
<div class="separator">&nbsp;</div>
<div class="setting">
<div class="setting-name">
&nbsp;<br/>Size threshold
<div class="fa fa-question-circle" alt="Info" title="The maximum data size of objects that can be queued before back pressure is applied."></div>
</div>
<div class="setting-field">
<input type="text" id="back-pressure-data-size-threshold" name="back-pressure-data-size-threshold" class="setting-input"/>
</div>
</div>
</div>
<div class="setting">
<div class="setting-name">
Back pressure data size threshold
<div class="fa fa-question-circle" alt="Info" title="The maximum data size of objects that can be queued before back pressure is applied."></div>
<div id="load-balance-settings">
<div class="multi-column-settings">
<div class="setting">
<div class="setting-name">
Load Balance Strategy
<div class="fa fa-question-circle" alt="Info" title="How to load balance the data in this Connection across the nodes in the cluster."></div>
</div>
<div class="setting-field">
<div id="load-balance-strategy-combo"></div>
</div>
</div>
<div id="load-balance-partition-attribute-setting-separator" class="separator">&nbsp;</div>
<div id="load-balance-partition-attribute-setting" class="setting">
<div class="setting-name">
Attribute Name
<div class="fa fa-question-circle" alt="Info" title="The FlowFile Attribute to use for determining which node a FlowFile will go to."></div>
</div>
<div class="setting-field">
<input type="text" id="load-balance-partition-attribute" name="load-balance-partition-attribute" class="setting-input"/>
</div>
</div>
</div>
<div class="setting-field">
<input type="text" id="back-pressure-data-size-threshold" name="back-pressure-data-size-threshold" class="setting-input"/>
<div id="load-balance-compression-setting" class="setting">
<div class="setting-name">
Load Balance Compression
<div class="fa fa-question-circle" alt="Info" title="Whether or not data should be compressed when being transferred between nodes in the cluster."></div>
</div>
<div class="setting-field">
<div id="load-balance-compression-combo"></div>
</div>
</div>
</div>
</div>

View File

@ -83,25 +83,60 @@
</div>
<div class="clear"></div>
</div>
<div class="setting">
<div class="setting-name">
Back pressure object threshold
<div class="fa fa-question-circle" alt="Info" title="The maximum number of objects that can be queued before back pressure is applied."></div>
<div class="multi-column-settings">
<div class="setting">
<div class="setting-name">
Back Pressure<br/>Object threshold
<div class="fa fa-question-circle" alt="Info" title="The maximum number of objects that can be queued before back pressure is applied."></div>
</div>
<div class="setting-field">
<span id="read-only-back-pressure-object-threshold"></span>
</div>
<div class="clear"></div>
</div>
<div class="setting-field">
<span id="read-only-back-pressure-object-threshold"></span>
<div class="separator">&nbsp;</div>
<div class="setting">
<div class="setting-name">
&nbsp;<br/>Size threshold
<div class="fa fa-question-circle" alt="Info" title="The maximum data size of objects that can be queued before back pressure is applied."></div>
</div>
<div class="setting-field">
<span id="read-only-back-pressure-data-size-threshold"></span>
</div>
<div class="clear"></div>
</div>
<div class="clear"></div>
</div>
<div class="setting">
<div class="setting-name">
Back pressure data size threshold
<div class="fa fa-question-circle" alt="Info" title="The maximum data size of objects that can be queued before back pressure is applied."></div>
<div id="read-only-load-balance-settings">
<div class="multi-column-settings">
<div class="setting">
<div class="setting-name">
Load Balance Strategy
<div class="fa fa-question-circle" alt="Info" title="How to load balance the data in this Connection across the nodes in the cluster."></div>
</div>
<div class="setting-field">
<div id="read-only-load-balance-strategy"></div>
</div>
</div>
<div class="separator">&nbsp;</div>
<div id="read-only-load-balance-partition-attribute-setting" class="setting">
<div class="setting-name">
Attribute Name
<div class="fa fa-question-circle" alt="Info" title="The FlowFile Attribute to use for determining which node a FlowFile will go to."></div>
</div>
<div class="setting-field">
<span id="read-only-load-balance-partition-attribute"></span>
</div>
</div>
</div>
<div class="setting-field">
<span id="read-only-back-pressure-data-size-threshold"></span>
<div id="read-only-load-balance-compression-setting" class="setting">
<div class="setting-name">
Load Balance Compression
<div class="fa fa-question-circle" alt="Info" title="Whether or not data should be compressed when being transferred between nodes in the cluster."></div>
</div>
<div class="setting-field">
<div id="read-only-load-balance-compression"></div>
</div>
</div>
<div class="clear"></div>
</div>
</div>
<div class="spacer">&nbsp;</div>

View File

@ -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;
}
}
.multi-column-settings {
display: flex;
}
.multi-column-settings .setting {
flex-grow: 1;
}
.multi-column-settings .separator {
min-width: 5px;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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 () {

View File

@ -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;

View File

@ -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) {