blob: 656b247d37aecbfc8f736b674cc628d2fd6b8a11 [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 d3, define, module, require, exports */
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery',
'd3',
'nf.Connection',
'nf.Common',
'nf.Client',
'nf.CanvasUtils',
'nf.Dialog'],
function ($, d3, nfConnection, nfCommon, nfClient, nfCanvasUtils, nfDialog) {
return (nf.ProcessGroup = factory($, d3, nfConnection, nfCommon, nfClient, nfCanvasUtils, nfDialog));
});
} else if (typeof exports === 'object' && typeof module === 'object') {
module.exports = (nf.ProcessGroup =
factory(require('jquery'),
require('d3'),
require('nf.Connection'),
require('nf.Common'),
require('nf.Client'),
require('nf.CanvasUtils'),
require('nf.Dialog')));
} else {
nf.ProcessGroup = factory(root.$,
root.d3,
root.nf.Connection,
root.nf.Common,
root.nf.Client,
root.nf.CanvasUtils,
root.nf.Dialog);
}
}(this, function ($, d3, nfConnection, nfCommon, nfClient, nfCanvasUtils, nfDialog) {
'use strict';
var nfConnectable;
var nfDraggable;
var nfSelectable;
var nfContextMenu;
var PREVIEW_NAME_LENGTH = 30;
var dimensions = {
width: 384,
height: 176
};
// ----------------------------
// process groups currently on the graph
// ----------------------------
var processGroupMap;
// -----------------------------------------------------------
// cache for components that are added/removed from the canvas
// -----------------------------------------------------------
var removedCache;
var addedCache;
// --------------------
// component containers
// --------------------
var processGroupContainer;
// --------------------------
// privately scoped functions
// --------------------------
/**
* Determines whether the specified process group is under version control.
*
* @param d
*/
var isUnderVersionControl = function (d) {
return nfCommon.isDefinedAndNotNull(d.versionedFlowState);
};
/**
* Selects the process group elements against the current process group map.
*/
var select = function () {
return processGroupContainer.selectAll('g.process-group').data(processGroupMap.values(), function (d) {
return d.id;
});
};
/**
* Renders the process groups in the specified selection.
*
* @param {selection} entered The selection of process groups to be rendered
* @param {boolean} selected Whether the process group should be selected
* @return the entered selection
*/
var renderProcessGroups = function (entered, selected) {
if (entered.empty()) {
return entered;
}
var processGroup = entered.append('g')
.attrs({
'id': function (d) {
return 'id-' + d.id;
},
'class': 'process-group component'
})
.classed('selected', selected)
.call(nfCanvasUtils.position);
// ----
// body
// ----
// process group border
processGroup.append('rect')
.attrs({
'class': 'border',
'width': function (d) {
return d.dimensions.width;
},
'height': function (d) {
return d.dimensions.height;
},
'fill': 'transparent',
'stroke': 'transparent'
});
// process group body
processGroup.append('rect')
.attrs({
'class': 'body',
'width': function (d) {
return d.dimensions.width;
},
'height': function (d) {
return d.dimensions.height;
},
'filter': 'url(#component-drop-shadow)',
'stroke-width': 0
});
// process group name background
processGroup.append('rect')
.attrs({
'width': function (d) {
return d.dimensions.width;
},
'height': 32,
'fill': '#b8c6cd'
});
// process group name
processGroup.append('text')
.attrs({
'x': 10,
'y': 20,
'width': 300,
'height': 16,
'class': 'process-group-name'
});
// process group name
processGroup.append('text')
.attrs({
'x': 10,
'y': 21,
'class': 'version-control'
});
// always support selecting and navigation
processGroup.on('dblclick', function (d) {
// enter this group on double click
nfProcessGroup.enterGroup(d.id);
})
.call(nfSelectable.activate).call(nfContextMenu.activate);
// only support dragging, connection, and drag and drop if appropriate
processGroup.filter(function (d) {
return d.permissions.canWrite && d.permissions.canRead;
})
.on('mouseover.drop', function (d) {
// Using mouseover/out to workaround chrome issue #122746
// get the target and ensure its not already been marked for drop
var target = d3.select(this);
if (!target.classed('drop')) {
var targetData = target.datum();
// see if there is a selection being dragged
var drag = d3.select('rect.drag-selection');
if (!drag.empty()) {
// filter the current selection by this group
var selection = nfCanvasUtils.getSelection().filter(function (d) {
return targetData.id === d.id;
});
// ensure this group isn't in the selection
if (selection.empty()) {
// mark that we are hovering over a drop area if appropriate
target.classed('drop', function () {
// get the current selection and ensure its disconnected
return nfConnection.isDisconnected(nfCanvasUtils.getSelection());
});
}
}
}
})
.on('mouseout.drop', function (d) {
// mark that we are no longer hovering over a drop area unconditionally
d3.select(this).classed('drop', false);
})
.call(nfDraggable.activate)
.call(nfConnectable.activate);
return processGroup;
};
// attempt of space between component count and icon for process group contents
var CONTENTS_SPACER = 10;
var CONTENTS_VALUE_SPACER = 5;
/**
* Updates the process groups in the specified selection.
*
* @param {selection} updated The process groups to be updated
*/
var updateProcessGroups = function (updated) {
if (updated.empty()) {
return;
}
// process group border authorization
updated.select('rect.border')
.classed('unauthorized', function (d) {
return d.permissions.canRead === false;
});
// process group body authorization
updated.select('rect.body')
.classed('unauthorized', function (d) {
return d.permissions.canRead === false;
});
updated.each(function (processGroupData) {
var processGroup = d3.select(this);
var details = processGroup.select('g.process-group-details');
// update the component behavior as appropriate
nfCanvasUtils.editable(processGroup, nfConnectable, nfDraggable);
// if this processor is visible, render everything
if (processGroup.classed('visible')) {
if (details.empty()) {
details = processGroup.append('g').attr('class', 'process-group-details');
// -------------------
// contents background
// -------------------
details.append('rect')
.attrs({
'x': 0,
'y': 32,
'width': function () {
return processGroupData.dimensions.width
},
'height': 24,
'fill': '#e3e8eb'
});
details.append('rect')
.attrs({
'x': 0,
'y': function () {
return processGroupData.dimensions.height - 24;
},
'width': function () {
return processGroupData.dimensions.width;
},
'height': 24,
'fill': '#e3e8eb'
});
// --------
// contents
// --------
// transmitting icon
details.append('text')
.attrs({
'x': 10,
'y': 49,
'class': 'process-group-transmitting process-group-contents-icon',
'font-family': 'FontAwesome'
})
.text('\uf140')
.append("title")
.text("Transmitting Remote Process Groups");
// transmitting count
details.append('text')
.attrs({
'y': 49,
'class': 'process-group-transmitting-count process-group-contents-count'
});
// not transmitting icon
details.append('text')
.attrs({
'y': 49,
'class': 'process-group-not-transmitting process-group-contents-icon',
'font-family': 'flowfont'
})
.text('\ue80a')
.append("title")
.text("Not Transmitting Remote Process Groups");
// not transmitting count
details.append('text')
.attrs({
'y': 49,
'class': 'process-group-not-transmitting-count process-group-contents-count'
});
// running icon
details.append('text')
.attrs({
'y': 49,
'class': 'process-group-running process-group-contents-icon',
'font-family': 'FontAwesome'
})
.text('\uf04b')
.append("title")
.text("Running Components");
// running count
details.append('text')
.attrs({
'y': 49,
'class': 'process-group-running-count process-group-contents-count'
});
// stopped icon
details.append('text')
.attrs({
'y': 49,
'class': 'process-group-stopped process-group-contents-icon',
'font-family': 'FontAwesome'
})
.text('\uf04d')
.append("title")
.text("Stopped Components");
// stopped count
details.append('text')
.attrs({
'y': 49,
'class': 'process-group-stopped-count process-group-contents-count'
});
// invalid icon
details.append('text')
.attrs({
'y': 49,
'class': 'process-group-invalid process-group-contents-icon',
'font-family': 'FontAwesome'
})
.text('\uf071')
.append("title")
.text("Invalid Components");
// invalid count
details.append('text')
.attrs({
'y': 49,
'class': 'process-group-invalid-count process-group-contents-count'
});
// disabled icon
details.append('text')
.attrs({
'y': 49,
'class': 'process-group-disabled process-group-contents-icon',
'font-family': 'flowfont'
})
.text('\ue802')
.append("title")
.text("Disabled Components");
// disabled count
details.append('text')
.attrs({
'y': 49,
'class': 'process-group-disabled-count process-group-contents-count'
});
// up to date icon
details.append('text')
.attrs({
'x': 10,
'y': function () {
return processGroupData.dimensions.height - 7;
},
'class': 'process-group-up-to-date process-group-contents-icon',
'font-family': 'FontAwesome'
})
.text('\uf00c')
.append("title")
.text("Up to date Versioned Process Groups");
// up to date count
details.append('text')
.attrs({
'y': function () {
return processGroupData.dimensions.height - 7;
},
'class': 'process-group-up-to-date-count process-group-contents-count'
});
// locally modified icon
details.append('text')
.attrs({
'y': function () {
return processGroupData.dimensions.height - 7;
},
'class': 'process-group-locally-modified process-group-contents-icon',
'font-family': 'FontAwesome'
})
.text('\uf069')
.append("title")
.text("Locally modified Versioned Process Groups");
// locally modified count
details.append('text')
.attrs({
'y': function () {
return processGroupData.dimensions.height - 7;
},
'class': 'process-group-locally-modified-count process-group-contents-count'
});
// stale icon
details.append('text')
.attrs({
'y': function () {
return processGroupData.dimensions.height - 7;
},
'class': 'process-group-stale process-group-contents-icon',
'font-family': 'FontAwesome'
})
.text('\uf0aa')
.append("title")
.text("Stale Versioned Process Groups");
// stale count
details.append('text')
.attrs({
'y': function () {
return processGroupData.dimensions.height - 7;
},
'class': 'process-group-stale-count process-group-contents-count'
});
// locally modified and stale icon
details.append('text')
.attrs({
'y': function () {
return processGroupData.dimensions.height - 7;
},
'class': 'process-group-locally-modified-and-stale process-group-contents-icon',
'font-family': 'FontAwesome'
})
.text('\uf06a')
.append("title")
.text("Locally modified and stale Versioned Process Groups");
// locally modified and stale count
details.append('text')
.attrs({
'y': function () {
return processGroupData.dimensions.height - 7;
},
'class': 'process-group-locally-modified-and-stale-count process-group-contents-count'
});
// sync failure icon
details.append('text')
.attrs({
'y': function () {
return processGroupData.dimensions.height - 7;
},
'class': 'process-group-sync-failure process-group-contents-icon',
'font-family': 'FontAwesome'
})
.text('\uf128')
.append("title")
.text("Sync failure Versioned Process Groups");
// sync failure count
details.append('text')
.attrs({
'y': function () {
return processGroupData.dimensions.height - 7;
},
'class': 'process-group-sync-failure-count process-group-contents-count'
});
// ----------------
// stats background
// ----------------
// queued
details.append('rect')
.attrs({
'width': function () {
return processGroupData.dimensions.width;
},
'height': 19,
'x': 0,
'y': 66,
'fill': '#f4f6f7'
});
// border
details.append('rect')
.attrs({
'width': function () {
return processGroupData.dimensions.width;
},
'height': 1,
'x': 0,
'y': 84,
'fill': '#c7d2d7'
});
// in
details.append('rect')
.attrs({
'width': function () {
return processGroupData.dimensions.width;
},
'height': 19,
'x': 0,
'y': 85,
'fill': '#ffffff'
});
// border
details.append('rect')
.attrs({
'width': function () {
return processGroupData.dimensions.width;
},
'height': 1,
'x': 0,
'y': 103,
'fill': '#c7d2d7'
});
// read/write
details.append('rect')
.attrs({
'width': function () {
return processGroupData.dimensions.width;
},
'height': 19,
'x': 0,
'y': 104,
'fill': '#f4f6f7'
});
// border
details.append('rect')
.attrs({
'width': function () {
return processGroupData.dimensions.width;
},
'height': 1,
'x': 0,
'y': 122,
'fill': '#c7d2d7'
});
// out
details.append('rect')
.attrs({
'width': function () {
return processGroupData.dimensions.width;
},
'height': 19,
'x': 0,
'y': 123,
'fill': '#ffffff'
});
// -----
// stats
// -----
// stats label container
var processGroupStatsLabel = details.append('g')
.attrs({
'transform': 'translate(6, 75)'
});
// queued label
processGroupStatsLabel.append('text')
.attrs({
'width': 73,
'height': 10,
'x': 4,
'y': 5,
'class': 'stats-label'
})
.text('Queued');
// in label
processGroupStatsLabel.append('text')
.attrs({
'width': 73,
'height': 10,
'x': 4,
'y': 24,
'class': 'stats-label'
})
.text('In');
// read/write label
processGroupStatsLabel.append('text')
.attrs({
'width': 73,
'height': 10,
'x': 4,
'y': 42,
'class': 'stats-label'
})
.text('Read/Write');
// out label
processGroupStatsLabel.append('text')
.attrs({
'width': 73,
'height': 10,
'x': 4,
'y': 60,
'class': 'stats-label'
})
.text('Out');
// stats value container
var processGroupStatsValue = details.append('g')
.attrs({
'transform': 'translate(95, 75)'
});
// queued value
var queuedText = processGroupStatsValue.append('text')
.attrs({
'width': 180,
'height': 10,
'x': 4,
'y': 5,
'class': 'process-group-queued stats-value'
});
// queued count
queuedText.append('tspan')
.attrs({
'class': 'count'
});
// queued size
queuedText.append('tspan')
.attrs({
'class': 'size'
});
// in value
var inText = processGroupStatsValue.append('text')
.attrs({
'width': 180,
'height': 10,
'x': 4,
'y': 24,
'class': 'process-group-in stats-value'
});
// in count
inText.append('tspan')
.attrs({
'class': 'count'
});
// in size
inText.append('tspan')
.attrs({
'class': 'size'
});
// in
inText.append('tspan')
.attrs({
'class': 'ports'
});
// in (remote)
inText.append('tspan')
.attrs({
'class': 'public-ports'
});
// read/write value
processGroupStatsValue.append('text')
.attrs({
'width': 180,
'height': 10,
'x': 4,
'y': 42,
'class': 'process-group-read-write stats-value'
});
// out value
var outText = processGroupStatsValue.append('text')
.attrs({
'width': 180,
'height': 10,
'x': 4,
'y': 60,
'class': 'process-group-out stats-value'
});
// out ports
outText.append('tspan')
.attrs({
'class': 'ports'
});
// out ports (remote)
outText.append('tspan')
.attrs({
'class': 'public-ports'
});
// out count
outText.append('tspan')
.attrs({
'class': 'count'
});
// out size
outText.append('tspan')
.attrs({
'class': 'size'
});
// stats value container
var processGroupStatsInfo = details.append('g')
.attrs({
'transform': 'translate(335, 75)'
});
// in info
processGroupStatsInfo.append('text')
.attrs({
'width': 25,
'height': 10,
'x': 4,
'y': 24,
'class': 'stats-info'
})
.text('5 min');
// read/write info
processGroupStatsInfo.append('text')
.attrs({
'width': 25,
'height': 10,
'x': 4,
'y': 42,
'class': 'stats-info'
})
.text('5 min');
// out info
processGroupStatsInfo.append('text')
.attrs({
'width': 25,
'height': 10,
'x': 4,
'y': 60,
'class': 'stats-info'
})
.text('5 min');
// --------
// comments
// --------
details.append('path')
.attrs({
'class': 'component-comments',
'transform': 'translate(' + (processGroupData.dimensions.width - 2) + ', ' + (processGroupData.dimensions.height - 10) + ')',
'd': 'm0,0 l0,8 l-8,0 z'
});
// -------------------
// active thread count
// -------------------
// active thread count
details.append('text')
.attrs({
'class': 'active-thread-count-icon',
'y': 20
})
.text('\ue83f');
// active thread icon
details.append('text')
.attrs({
'class': 'active-thread-count',
'y': 20
});
// ---------
// bulletins
// ---------
// bulletin background
details.append('rect')
.attrs({
'class': 'bulletin-background',
'x': function () {
return processGroupData.dimensions.width - 24;
},
'y': 32,
'width': 24,
'height': 24
});
// bulletin icon
details.append('text')
.attrs({
'class': 'bulletin-icon',
'x': function () {
return processGroupData.dimensions.width - 17;
},
'y': 49
})
.text('\uf24a');
}
// update transmitting
var transmitting = details.select('text.process-group-transmitting')
.classed('transmitting', function (d) {
return d.permissions.canRead && d.activeRemotePortCount > 0;
})
.classed('zero', function (d) {
return d.permissions.canRead && d.activeRemotePortCount === 0;
});
var transmittingCount = details.select('text.process-group-transmitting-count')
.attr('x', function () {
var transmittingCountX = parseInt(transmitting.attr('x'), 10);
return transmittingCountX + Math.round(transmitting.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
})
.text(function (d) {
return d.activeRemotePortCount;
});
transmittingCount.append("title").text("Transmitting Remote Process Groups");
// update not transmitting
var notTransmitting = details.select('text.process-group-not-transmitting')
.classed('not-transmitting', function (d) {
return d.permissions.canRead && d.inactiveRemotePortCount > 0;
})
.classed('zero', function (d) {
return d.permissions.canRead && d.inactiveRemotePortCount === 0;
})
.attr('x', function () {
var transmittingX = parseInt(transmittingCount.attr('x'), 10);
return transmittingX + Math.round(transmittingCount.node().getComputedTextLength()) + CONTENTS_SPACER;
});
var notTransmittingCount = details.select('text.process-group-not-transmitting-count')
.attr('x', function () {
var notTransmittingCountX = parseInt(notTransmitting.attr('x'), 10);
return notTransmittingCountX + Math.round(notTransmitting.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
})
.text(function (d) {
return d.inactiveRemotePortCount;
});
notTransmittingCount.append("title").text("Not transmitting Remote Process Groups")
// update running
var running = details.select('text.process-group-running')
.classed('running', function (d) {
return d.permissions.canRead && d.component.runningCount > 0;
})
.classed('zero', function (d) {
return d.permissions.canRead && d.component.runningCount === 0;
})
.attr('x', function () {
var notTransmittingX = parseInt(notTransmittingCount.attr('x'), 10);
return notTransmittingX + Math.round(notTransmittingCount.node().getComputedTextLength()) + CONTENTS_SPACER;
});
var runningCount = details.select('text.process-group-running-count')
.attr('x', function () {
var runningCountX = parseInt(running.attr('x'), 10);
return runningCountX + Math.round(running.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
})
.text(function (d) {
return d.runningCount;
});
runningCount.append("title").text("Running Components");
// update stopped
var stopped = details.select('text.process-group-stopped')
.classed('stopped', function (d) {
return d.permissions.canRead && d.component.stoppedCount > 0;
})
.classed('zero', function (d) {
return d.permissions.canRead && d.component.stoppedCount === 0;
})
.attr('x', function () {
var runningX = parseInt(runningCount.attr('x'), 10);
return runningX + Math.round(runningCount.node().getComputedTextLength()) + CONTENTS_SPACER;
});
var stoppedCount = details.select('text.process-group-stopped-count')
.attr('x', function () {
var stoppedCountX = parseInt(stopped.attr('x'), 10);
return stoppedCountX + Math.round(stopped.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
})
.text(function (d) {
return d.stoppedCount;
});
stoppedCount.append("title").text("Stopped Components");
// update invalid
var invalid = details.select('text.process-group-invalid')
.classed('invalid', function (d) {
return d.permissions.canRead && d.component.invalidCount > 0;
})
.classed('zero', function (d) {
return d.permissions.canRead && d.component.invalidCount === 0;
})
.attr('x', function () {
var stoppedX = parseInt(stoppedCount.attr('x'), 10);
return stoppedX + Math.round(stoppedCount.node().getComputedTextLength()) + CONTENTS_SPACER;
});
var invalidCount = details.select('text.process-group-invalid-count')
.attr('x', function () {
var invalidCountX = parseInt(invalid.attr('x'), 10);
return invalidCountX + Math.round(invalid.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
})
.text(function (d) {
return d.invalidCount;
});
invalidCount.append("title").text("Invalid Components");
// update disabled
var disabled = details.select('text.process-group-disabled')
.classed('disabled', function (d) {
return d.permissions.canRead && d.component.disabledCount > 0;
})
.classed('zero', function (d) {
return d.permissions.canRead && d.component.disabledCount === 0;
})
.attr('x', function () {
var invalidX = parseInt(invalidCount.attr('x'), 10);
return invalidX + Math.round(invalidCount.node().getComputedTextLength()) + CONTENTS_SPACER;
});
var disabledCount = details.select('text.process-group-disabled-count')
.attr('x', function () {
var disabledCountX = parseInt(disabled.attr('x'), 10);
return disabledCountX + Math.round(disabled.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
})
.text(function (d) {
return d.disabledCount;
});
disabledCount.append("title").text("Disabled Components");
// up to date current
var upToDate = details.select('text.process-group-up-to-date')
.classed('up-to-date', function (d) {
return d.permissions.canRead && d.component.upToDateCount > 0;
})
.classed('zero', function (d) {
return d.permissions.canRead && d.component.upToDateCount === 0;
});
var upToDateCount = details.select('text.process-group-up-to-date-count')
.attr('x', function () {
var updateToDateCountX = parseInt(upToDate.attr('x'), 10);
return updateToDateCountX + Math.round(upToDate.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
})
.text(function (d) {
return d.upToDateCount;
});
upToDateCount.append("title").text("Up to date Versioned Process Groups");
// update locally modified
var locallyModified = details.select('text.process-group-locally-modified')
.classed('locally-modified', function (d) {
return d.permissions.canRead && d.component.locallyModifiedCount > 0;
})
.classed('zero', function (d) {
return d.permissions.canRead && d.component.locallyModifiedCount === 0;
})
.attr('x', function () {
var upToDateX = parseInt(upToDateCount.attr('x'), 10);
return upToDateX + Math.round(upToDateCount.node().getComputedTextLength()) + CONTENTS_SPACER;
});
var locallyModifiedCount = details.select('text.process-group-locally-modified-count')
.attr('x', function () {
var locallyModifiedCountX = parseInt(locallyModified.attr('x'), 10);
return locallyModifiedCountX + Math.round(locallyModified.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
})
.text(function (d) {
return d.locallyModifiedCount;
});
locallyModifiedCount.append("title").text("Locally modified Versioned Process Groups");
// update stale
var stale = details.select('text.process-group-stale')
.classed('stale', function (d) {
return d.permissions.canRead && d.component.staleCount > 0;
})
.classed('zero', function (d) {
return d.permissions.canRead && d.component.staleCount === 0;
})
.attr('x', function () {
var locallyModifiedX = parseInt(locallyModifiedCount.attr('x'), 10);
return locallyModifiedX + Math.round(locallyModifiedCount.node().getComputedTextLength()) + CONTENTS_SPACER;
});
var staleCount = details.select('text.process-group-stale-count')
.attr('x', function () {
var staleCountX = parseInt(stale.attr('x'), 10);
return staleCountX + Math.round(stale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
})
.text(function (d) {
return d.staleCount;
});
staleCount.append("title").text("Stale Versioned Process Groups");
// update locally modified and stale
var locallyModifiedAndStale = details.select('text.process-group-locally-modified-and-stale')
.classed('locally-modified-and-stale', function (d) {
return d.permissions.canRead && d.component.locallyModifiedAndStaleCount > 0;
})
.classed('zero', function (d) {
return d.permissions.canRead && d.component.locallyModifiedAndStaleCount === 0;
})
.attr('x', function () {
var staleX = parseInt(staleCount.attr('x'), 10);
return staleX + Math.round(staleCount.node().getComputedTextLength()) + CONTENTS_SPACER;
});
var locallyModifiedAndStaleCount = details.select('text.process-group-locally-modified-and-stale-count')
.attr('x', function () {
var locallyModifiedAndStaleCountX = parseInt(locallyModifiedAndStale.attr('x'), 10);
return locallyModifiedAndStaleCountX + Math.round(locallyModifiedAndStale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
})
.text(function (d) {
return d.locallyModifiedAndStaleCount;
});
locallyModifiedAndStaleCount.append("title").text("Locally modified and stale Versioned Process Groups");
// update sync failure
var syncFailure = details.select('text.process-group-sync-failure')
.classed('sync-failure', function (d) {
return d.permissions.canRead && d.component.syncFailureCount > 0;
})
.classed('zero', function (d) {
return d.permissions.canRead && d.component.syncFailureCount === 0;
})
.attr('x', function () {
var syncFailureX = parseInt(locallyModifiedAndStaleCount.attr('x'), 10);
return syncFailureX + Math.round(locallyModifiedAndStaleCount.node().getComputedTextLength()) + CONTENTS_SPACER - 2;
});
var syncFailureCount = details.select('text.process-group-sync-failure-count')
.attr('x', function () {
var syncFailureCountX = parseInt(syncFailure.attr('x'), 10);
return syncFailureCountX + Math.round(syncFailure.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
})
.text(function (d) {
return d.syncFailureCount;
});
syncFailureCount.append("title").text("Sync failure Versioned Process Groups");
// update version control information
var versionControl = processGroup.select('text.version-control')
.styles({
'visibility': isUnderVersionControl(processGroupData) ? 'visible' : 'hidden',
'fill': function () {
if (isUnderVersionControl(processGroupData)) {
var vciState = processGroupData.versionedFlowState;
if (vciState === 'SYNC_FAILURE') {
return '#666666';
} else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') {
return '#BA554A';
} else if (vciState === 'STALE') {
return '#BA554A';
} else if (vciState === 'LOCALLY_MODIFIED') {
return '#666666';
} else {
return '#1A9964';
}
} else {
return '#000';
}
}
})
.text(function () {
if (isUnderVersionControl(processGroupData)) {
var vciState = processGroupData.versionedFlowState;
if (vciState === 'SYNC_FAILURE') {
return '\uf128'
} else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') {
return '\uf06a';
} else if (vciState === 'STALE') {
return '\uf0aa';
} else if (vciState === 'LOCALLY_MODIFIED') {
return '\uf069';
} else {
return '\uf00c';
}
} else {
return '';
}
});
if (processGroupData.permissions.canRead) {
// version control tooltip
versionControl.each(function () {
// get the tip
var tip = d3.select('#version-control-tip-' + processGroupData.id);
// if there are validation errors generate a tooltip
if (isUnderVersionControl(processGroupData)) {
// create the tip if necessary
if (tip.empty()) {
tip = d3.select('#process-group-tooltips').append('div')
.attr('id', function () {
return 'version-control-tip-' + processGroupData.id;
})
.attr('class', 'tooltip nifi-tooltip');
}
// update the tip
tip.html(function () {
var vci = processGroupData.component.versionControlInformation;
var versionControlTip = $('<div></div>').text('Tracking to "' + vci.flowName + '" Version ' + vci.version + ' in "' + vci.registryName + ' - ' + vci.bucketName + '"');
var versionControlStateTip = $('<div></div>').text(nfCommon.getVersionControlTooltip(vci));
return $('<div></div>').append(versionControlTip).append('<br/>').append(versionControlStateTip).html();
});
// add the tooltip
nfCanvasUtils.canvasTooltip(tip, d3.select(this));
} else {
// remove the tip if necessary
if (!tip.empty()) {
tip.remove();
}
}
});
// update the process group comments
processGroup.select('path.component-comments')
.style('visibility', nfCommon.isBlank(processGroupData.component.comments) ? 'hidden' : 'visible')
.each(function () {
// get the tip
var tip = d3.select('#comments-tip-' + processGroupData.id);
// if there are validation errors generate a tooltip
if (nfCommon.isBlank(processGroupData.component.comments)) {
// remove the tip if necessary
if (!tip.empty()) {
tip.remove();
}
} else {
// create the tip if necessary
if (tip.empty()) {
tip = d3.select('#process-group-tooltips').append('div')
.attr('id', function () {
return 'comments-tip-' + processGroupData.id;
})
.attr('class', 'tooltip nifi-tooltip');
}
// update the tip
tip.text(processGroupData.component.comments);
// add the tooltip
nfCanvasUtils.canvasTooltip(tip, d3.select(this));
}
});
// update the process group name
processGroup.select('text.process-group-name')
.attrs({
'x': function () {
if (isUnderVersionControl(processGroupData)) {
var versionControlX = parseInt(versionControl.attr('x'), 10);
return versionControlX + Math.round(versionControl.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
} else {
return 10;
}
},
'width': function () {
if (isUnderVersionControl(processGroupData)) {
var versionControlX = parseInt(versionControl.attr('x'), 10);
var processGroupNameX = parseInt(d3.select(this).attr('x'), 10);
return 300 - (processGroupNameX - versionControlX);
} else {
return 300;
}
}
})
.each(function (d) {
var processGroupName = d3.select(this);
// reset the process group name to handle any previous state
processGroupName.text(null).selectAll('title').remove();
// apply ellipsis to the process group name as necessary
nfCanvasUtils.ellipsis(processGroupName, d.component.name);
})
.append('title')
.text(function (d) {
return d.component.name;
});
} else {
// clear the process group comments
processGroup.select('path.component-comments').style('visibility', 'hidden');
// clear the process group name
processGroup.select('text.process-group-name')
.attrs({
'x': 10,
'width': 316
})
.text(null);
// clear tooltips
processGroup.call(removeTooltips);
}
// populate the stats
processGroup.call(updateProcessGroupStatus);
} else {
if (processGroupData.permissions.canRead) {
// update the process group name
processGroup.select('text.process-group-name')
.text(function (d) {
var name = d.component.name;
if (name.length > PREVIEW_NAME_LENGTH) {
return name.substring(0, PREVIEW_NAME_LENGTH) + String.fromCharCode(8230);
} else {
return name;
}
});
} else {
// clear the process group name
processGroup.select('text.process-group-name').text(null);
}
// remove the tooltips
processGroup.call(removeTooltips);
// remove the details if necessary
if (!details.empty()) {
details.remove();
}
}
});
};
/**
* Updates the process group status.
*
* @param {selection} updated The process groups to be updated
*/
var updateProcessGroupStatus = function (updated) {
if (updated.empty()) {
return;
}
// queued count value
updated.select('text.process-group-queued tspan.count')
.text(function (d) {
return nfCommon.substringBeforeFirst(d.status.aggregateSnapshot.queued, ' ');
});
// queued size value
updated.select('text.process-group-queued tspan.size')
.text(function (d) {
return ' ' + nfCommon.substringAfterFirst(d.status.aggregateSnapshot.queued, ' ');
});
// in count value
updated.select('text.process-group-in tspan.count')
.text(function (d) {
return nfCommon.substringBeforeFirst(d.status.aggregateSnapshot.input, ' ');
});
// in size value
updated.select('text.process-group-in tspan.size')
.text(function (d) {
return ' ' + nfCommon.substringAfterFirst(d.status.aggregateSnapshot.input, ' ');
});
// in ports value
updated.select('text.process-group-in tspan.ports')
.text(function (d) {
return ' ' + String.fromCharCode(8594) + ' ' + d.inputPortCount;
});
// in ports value (remote)
updated.select('text.process-group-in tspan.public-ports')
.text(function (d) {
return d.publicInputPortCount > 0 ? ' (' + d.publicInputPortCount + ' remote)' : '';
});
// read/write value
updated.select('text.process-group-read-write')
.text(function (d) {
return d.status.aggregateSnapshot.read + ' / ' + d.status.aggregateSnapshot.written;
});
// out ports value
updated.select('text.process-group-out tspan.ports')
.text(function (d) {
return d.outputPortCount;
});
// out ports value (remote)
updated.select('text.process-group-out tspan.public-ports')
.text(function (d) {
return d.publicOutputPortCount > 0 ? ' (' + d.publicOutputPortCount + ' remote) ' : '';
});
// out count value
updated.select('text.process-group-out tspan.count')
.text(function (d) {
return ' ' + String.fromCharCode(8594) + ' ' + nfCommon.substringBeforeFirst(d.status.aggregateSnapshot.output, ' ');
});
// out size value
updated.select('text.process-group-out tspan.size')
.text(function (d) {
return ' ' + nfCommon.substringAfterFirst(d.status.aggregateSnapshot.output, ' ');
});
updated.each(function (d) {
var processGroup = d3.select(this);
var offset = 0;
// -------------------
// active thread count
// -------------------
nfCanvasUtils.activeThreadCount(processGroup, d, function (off) {
offset = off;
});
// ---------
// bulletins
// ---------
processGroup.select('rect.bulletin-background').classed('has-bulletins', function () {
return !nfCommon.isEmpty(d.status.aggregateSnapshot.bulletins);
});
nfCanvasUtils.bulletins(processGroup, d, function () {
return d3.select('#process-group-tooltips');
}, offset);
});
};
/**
* Removes the process groups in the specified selection.
*
* @param {selection} removed The process groups to be removed
*/
var removeProcessGroups = function (removed) {
if (removed.empty()) {
return;
}
removed.call(removeTooltips).remove();
};
/**
* Removes the tooltips for the process groups in the specified selection.
*
* @param {selection} removed
*/
var removeTooltips = function (removed) {
removed.each(function (d) {
// remove any associated tooltips
$('#bulletin-tip-' + d.id).remove();
$('#version-control-tip-' + d.id).remove();
$('#comments-tip-' + d.id).remove();
});
};
var nfProcessGroup = {
/**
* Initializes of the Process Group handler.
*
* @param nfConnectableRef The nfConnectable module.
* @param nfDraggableRef The nfDraggable module.
* @param nfSelectableRef The nfSelectable module.
* @param nfContextMenuRef The nfContextMenu module.
*/
init: function (nfConnectableRef, nfDraggableRef, nfSelectableRef, nfContextMenuRef) {
nfConnectable = nfConnectableRef;
nfDraggable = nfDraggableRef;
nfSelectable = nfSelectableRef;
nfContextMenu = nfContextMenuRef;
processGroupMap = d3.map();
removedCache = d3.map();
addedCache = d3.map();
// create the process group container
processGroupContainer = d3.select('#canvas').append('g')
.attrs({
'pointer-events': 'all',
'class': 'process-groups'
});
},
/**
* Adds the specified process group entity.
*
* @param processGroupEntities The process group
* @param options Configuration options
*/
add: function (processGroupEntities, options) {
var selectAll = false;
if (nfCommon.isDefinedAndNotNull(options)) {
selectAll = nfCommon.isDefinedAndNotNull(options.selectAll) ? options.selectAll : selectAll;
}
// get the current time
var now = new Date().getTime();
var add = function (processGroupEntity) {
addedCache.set(processGroupEntity.id, now);
// add the process group
processGroupMap.set(processGroupEntity.id, $.extend({
type: 'ProcessGroup',
dimensions: dimensions
}, processGroupEntity));
};
// determine how to handle the specified process groups
if ($.isArray(processGroupEntities)) {
$.each(processGroupEntities, function (_, processGroupEntity) {
add(processGroupEntity);
});
} else if (nfCommon.isDefinedAndNotNull(processGroupEntities)) {
add(processGroupEntities);
}
// select
var selection = select();
// enter
var entered = renderProcessGroups(selection.enter(), selectAll);
// update
updateProcessGroups(selection.merge(entered));
},
/**
* Populates the graph with the specified process groups.
*
* @argument {object | array} processGroupEntities The process groups to add
* @argument {object} options Configuration options
*/
set: function (processGroupEntities, options) {
var selectAll = false;
var transition = false;
var overrideRevisionCheck = false;
if (nfCommon.isDefinedAndNotNull(options)) {
selectAll = nfCommon.isDefinedAndNotNull(options.selectAll) ? options.selectAll : selectAll;
transition = nfCommon.isDefinedAndNotNull(options.transition) ? options.transition : transition;
overrideRevisionCheck = nfCommon.isDefinedAndNotNull(options.overrideRevisionCheck) ? options.overrideRevisionCheck : overrideRevisionCheck;
}
var set = function (proposedProcessGroupEntity) {
var currentProcessGroupEntity = processGroupMap.get(proposedProcessGroupEntity.id);
// set the process group if appropriate due to revision and wasn't previously removed
if ((nfClient.isNewerRevision(currentProcessGroupEntity, proposedProcessGroupEntity) && !removedCache.has(proposedProcessGroupEntity.id)) || overrideRevisionCheck === true) {
processGroupMap.set(proposedProcessGroupEntity.id, $.extend({
type: 'ProcessGroup',
dimensions: dimensions
}, proposedProcessGroupEntity));
}
};
// determine how to handle the specified process groups
if ($.isArray(processGroupEntities)) {
$.each(processGroupMap.keys(), function (_, key) {
var currentProcessGroupEntity = processGroupMap.get(key);
var isPresent = $.grep(processGroupEntities, function (proposedProcessGroupEntity) {
return proposedProcessGroupEntity.id === currentProcessGroupEntity.id;
});
// if the current process group is not present and was not recently added, remove it
if (isPresent.length === 0 && !addedCache.has(key)) {
processGroupMap.remove(key);
}
});
$.each(processGroupEntities, function (_, processGroupEntity) {
set(processGroupEntity);
});
} else if (nfCommon.isDefinedAndNotNull(processGroupEntities)) {
set(processGroupEntities);
}
// select
var selection = select();
// enter
var entered = renderProcessGroups(selection.enter(), selectAll);
// update
var updated = selection.merge(entered);
updated.call(updateProcessGroups).call(nfCanvasUtils.position, transition);
// exit
selection.exit().call(removeProcessGroups);
},
/**
* If the process group id is specified it is returned. If no process group id
* specified, all process groups are returned.
*
* @param {string} id
*/
get: function (id) {
if (nfCommon.isUndefined(id)) {
return processGroupMap.values();
} else {
return processGroupMap.get(id);
}
},
/**
* If the process group id is specified it is refresh according to the current
* state. If no process group id is specified, all process groups are refreshed.
*
* @param {string} id Optional
*/
refresh: function (id) {
if (nfCommon.isDefinedAndNotNull(id)) {
d3.select('#id-' + id).call(updateProcessGroups);
} else {
d3.selectAll('g.process-group').call(updateProcessGroups);
}
},
/**
* Refreshes the components necessary after a pan event.
*/
pan: function () {
d3.selectAll('g.process-group.entering, g.process-group.leaving').call(updateProcessGroups);
},
/**
* Reloads the process group state from the server and refreshes the UI.
* If the process group is currently unknown, this function reloads the canvas.
*
* @param {string} id The process group id
*/
reload: function (id) {
if (processGroupMap.has(id)) {
var processGroupEntity = processGroupMap.get(id);
return $.ajax({
type: 'GET',
url: processGroupEntity.uri,
dataType: 'json'
}).done(function (response) {
nfProcessGroup.set(response);
});
}
},
/**
* Positions the component.
*
* @param {string} id The id
*/
position: function (id) {
d3.select('#id-' + id).call(nfCanvasUtils.position);
},
/**
* Removes the specified process group.
*
* @param {string} processGroupIds The process group id(s)
*/
remove: function (processGroupIds) {
var now = new Date().getTime();
if ($.isArray(processGroupIds)) {
$.each(processGroupIds, function (_, processGroupId) {
removedCache.set(processGroupId, now);
processGroupMap.remove(processGroupId);
});
} else {
removedCache.set(processGroupIds, now);
processGroupMap.remove(processGroupIds);
}
// apply the selection and handle all removed process groups
select().exit().call(removeProcessGroups);
},
/**
* Removes all process groups.
*/
removeAll: function () {
nfProcessGroup.remove(processGroupMap.keys());
},
/**
* Expires the caches up to the specified timestamp.
*
* @param timestamp
*/
expireCaches: function (timestamp) {
var expire = function (cache) {
cache.each(function (entryTimestamp, id) {
if (timestamp > entryTimestamp) {
cache.remove(id);
}
});
};
expire(addedCache);
expire(removedCache);
},
/**
* Enters the specified group.
*
* @param {string} groupId
*/
enterGroup: function (groupId) {
// hide the context menu
nfContextMenu.hide();
// reload the graph
return nfCanvasUtils.reload(groupId).done(function () {
// attempt to restore the view
var viewRestored = nfCanvasUtils.restoreUserView();
// if the view was not restore attempt to fit
if (viewRestored === false) {
nfCanvasUtils.fitCanvas();
}
// update URL deep linking params
nfCanvasUtils.setURLParameters(groupId, d3.select());
}).fail(function () {
nfDialog.showOkDialog({
headerText: 'Process Group',
dialogContent: 'Unable to enter the selected group.'
});
});
}
};
return nfProcessGroup;
}));