blob: 986672dd2c20344c0215f28003ad22fb412bd038 [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.Common',
'nf.CanvasUtils',
'nf.ng.Bridge'],
function ($, d3, nfCommon, nfCanvasUtils, nfNgBridge) {
return (nf.ContextMenu = factory($, d3, nfCommon, nfCanvasUtils, nfNgBridge));
});
} else if (typeof exports === 'object' && typeof module === 'object') {
module.exports = (nf.ContextMenu =
factory(require('jquery'),
require('d3'),
require('nf.Common'),
require('nf.CanvasUtils'),
require('nf.ng.Bridge')));
} else {
nf.ContextMenu = factory(root.$,
root.d3,
root.nf.Common,
root.nf.CanvasUtils,
root.nf.ng.Bridge);
}
}(this, function ($, d3, nfCommon, nfCanvasUtils, nfNgBridge) {
'use strict';
var nfActions;
/**
* Returns whether the current group is not the root group.
*
* @param {selection} selection The selection of currently selected components
*/
var isNotRootGroup = function (selection) {
return nfCanvasUtils.getParentGroupId() !== null && selection.empty();
};
/**
* Determines whether the component in the specified selection is configurable.
*
* @param {selection} selection The selection of currently selected components
*/
var isConfigurable = function (selection) {
if(selection.size() == 1 &&
nfCanvasUtils.isProcessor(selection) &&
nfCanvasUtils.canModify(selection) &&
(nfCanvasUtils.isConfigurable(selection) ||
canTerminate(selection))){
return true;
} else {
return nfCanvasUtils.isConfigurable(selection);
}
};
/**
* Determines whether the component in the specified selection has variables.
*
* @param {selection} selection The selection of currently selected components
*/
var hasVariables = function (selection) {
return selection.empty() || nfCanvasUtils.isProcessGroup(selection);
};
/**
* Determines whether the component in the specified selection has a parameter context.
*
* @param {selection} selection The selection of currently selected components
*/
var hasParameterContext = function (selection) {
var parameterContext;
if (selection.empty()) {
parameterContext = nfCanvasUtils.getParameterContext();
} else if (nfCanvasUtils.isProcessGroup(selection)) {
var pg = selection.datum();
parameterContext = pg.parameterContext;
}
if (nfCommon.isDefinedAndNotNull(parameterContext)) {
return nfCommon.isDefinedAndNotNull(parameterContext) && parameterContext.permissions.canRead === true;
}
return false;
};
/**
* Determines whether the component in the specified selection has configuration details.
*
* @param {selection} selection The selection of currently selected components
*/
var hasDetails = function (selection) {
if(selection.size() == 1 &&
nfCanvasUtils.isProcessor(selection) &&
nfCanvasUtils.canModify(selection)){
return !isConfigurable(selection);
} else {
return nfCanvasUtils.hasDetails(selection);
}
};
/**
* Determines whether the components in the specified selection are deletable.
*
* @param {selection} selection The selection of currently selected components
*/
var isDeletable = function (selection) {
return nfCanvasUtils.areDeletable(selection);
};
/**
* Determines whether user can create a template from the components in the specified selection.
*
* @param {selection} selection The selection of currently selected components
*/
var canCreateTemplate = function (selection) {
return nfCanvasUtils.canWriteCurrentGroup() && (selection.empty() || nfCanvasUtils.canRead(selection));
};
/**
* Determines whether user can upload a template.
*
* @param {selection} selection The selection of currently selected components
*/
var canUploadTemplate = function (selection) {
return nfCanvasUtils.canWriteCurrentGroup() && selection.empty();
};
/**
* Determines whether components in the specified selection are group-able.
*
* @param {selection} selection The selection of currently selected components
*/
var canGroup = function (selection) {
return nfCanvasUtils.getComponentByType('Connection').isDisconnected(selection) && nfCanvasUtils.canModify(selection);
};
/**
* Determines whether components in the specified selection are enable-able.
*
* @param {selection} selection The selection of currently selected components
*/
var canEnable = function (selection) {
return nfCanvasUtils.canEnable(selection);
};
/**
* Determines whether components in the specified selection are diable-able.
*
* @param {selection} selection The selection of currently selected components
*/
var canDisable = function (selection) {
return nfCanvasUtils.canDisable(selection);
};
/**
* Determines whether user can manage policies of the components in the specified selection.
*
* @param {selection} selection The selection of currently selected components
*/
var canManagePolicies = function (selection) {
return nfCanvasUtils.isManagedAuthorizer() && nfCanvasUtils.canManagePolicies(selection);
};
/**
* Determines whether the components in the specified selection are runnable.
*
* @param {selection} selection The selection of currently selected components
*/
var isRunnable = function (selection) {
return nfCanvasUtils.areRunnable(selection);
};
/**
* Determines whether the components in the specified selection are stoppable.
*
* @param {selection} selection The selection of currently selected components
*/
var isStoppable = function (selection) {
return nfCanvasUtils.areStoppable(selection);
};
/**
* Determines whether the components in the specified selection can be terminated.
*
* @param {selection} selection The selections of currently selected components
*/
var canTerminate = function (selection) {
if (selection.size() !== 1) {
return false;
}
if (nfCanvasUtils.canOperate(selection) === false) {
return false;
}
var terminatable = false;
if (nfCanvasUtils.isProcessor(selection)) {
var selectionData = selection.datum();
var aggregateSnapshot = selectionData.status.aggregateSnapshot;
terminatable = aggregateSnapshot.runStatus !== 'Running' && aggregateSnapshot.activeThreadCount > 0;
}
return terminatable;
};
/**
* Determines whether the components in the specified selection support stats.
*
* @param {selection} selection The selection of currently selected components
*/
var supportsStats = function (selection) {
// ensure the correct number of components are selected
if (selection.size() !== 1) {
return false;
}
return nfCanvasUtils.isProcessor(selection) || nfCanvasUtils.isProcessGroup(selection) || nfCanvasUtils.isRemoteProcessGroup(selection) || nfCanvasUtils.isConnection(selection);
};
/**
* Determines whether the components in the specified selection has usage documentation.
*
* @param {selection} selection The selection of currently selected components
*/
var hasUsage = function (selection) {
// ensure the correct number of components are selected
if (selection.size() !== 1) {
return false;
}
if (nfCanvasUtils.canRead(selection) === false) {
return false;
}
return nfCanvasUtils.isProcessor(selection);
};
/**
* Determines whether there is one component selected.
*
* @param {selection} selection The selection of currently selected components
*/
var isNotConnection = function (selection) {
return selection.size() === 1 && !nfCanvasUtils.isConnection(selection);
};
/**
* Determines whether the components in the specified selection are copyable.
*
* @param {selection} selection The selection of currently selected components
*/
var isCopyable = function (selection) {
return nfCanvasUtils.isCopyable(selection);
};
/**
* Determines whether the components in the specified selection are pastable.
*
* @param {selection} selection The selection of currently selected components
*/
var isPastable = function (selection) {
return nfCanvasUtils.isPastable();
};
/**
* Determines whether the specified selection is empty.
*
* @param {selection} selection The seleciton
*/
var emptySelection = function (selection) {
return selection.empty();
};
/**
* Determines whether the componets in the specified selection support being moved to the front.
*
* @param {selection} selection The selection
*/
var canMoveToFront = function (selection) {
// ensure the correct number of components are selected
if (selection.size() !== 1) {
return false;
}
if (nfCanvasUtils.canModify(selection) === false) {
return false;
}
return nfCanvasUtils.isConnection(selection);
};
/**
* Determines whether the components in the specified selection are alignable.
*
* @param {selection} selection The selection
*/
var canAlign = function (selection) {
return nfCanvasUtils.canAlign(selection);
};
/**
* Determines whether the components in the specified selection are colorable.
*
* @param {selection} selection The selection
*/
var isColorable = function (selection) {
return nfCanvasUtils.isColorable(selection);
};
/**
* Determines whether the component in the specified selection is a connection.
*
* @param {selection} selection The selection
*/
var isConnection = function (selection) {
// ensure the correct number of components are selected
if (selection.size() !== 1) {
return false;
}
return nfCanvasUtils.isConnection(selection);
};
/**
* Determines whether the component in the specified selection could possibly have downstream components.
*
* @param {selection} selection The selection
*/
var hasDownstream = function (selection) {
// ensure the correct number of components are selected
if (selection.size() !== 1) {
return false;
}
return nfCanvasUtils.isFunnel(selection) || nfCanvasUtils.isProcessor(selection) || nfCanvasUtils.isProcessGroup(selection) ||
nfCanvasUtils.isRemoteProcessGroup(selection) || nfCanvasUtils.isInputPort(selection) ||
(nfCanvasUtils.isOutputPort(selection) && nfCanvasUtils.getParentGroupId() !== null);
};
/**
* Determines whether the component in the specified selection could possibly have upstream components.
*
* @param {selection} selection The selection
*/
var hasUpstream = function (selection) {
// ensure the correct number of components are selected
if (selection.size() !== 1) {
return false;
}
return nfCanvasUtils.isFunnel(selection) || nfCanvasUtils.isProcessor(selection) || nfCanvasUtils.isProcessGroup(selection) ||
nfCanvasUtils.isRemoteProcessGroup(selection) || nfCanvasUtils.isOutputPort(selection) ||
(nfCanvasUtils.isInputPort(selection) && nfCanvasUtils.getParentGroupId() !== null);
};
/**
* Determines whether the current selection is a stateful processor.
*
* @param {selection} selection
*/
var isStatefulProcessor = function (selection) {
// ensure the correct number of components are selected
if (selection.size() !== 1) {
return false;
}
if (nfCanvasUtils.canRead(selection) === false || nfCanvasUtils.canModify(selection) === false) {
return false;
}
if (nfCanvasUtils.isProcessor(selection)) {
var processorData = selection.datum();
return processorData.component.persistsState === true;
} else {
return false;
}
};
/**
* Determines whether the current selection is a processor with more than one version.
*
* @param {selection} selection
*/
var canChangeProcessorVersion = function (selection) {
// ensure the correct number of components are selected
if (selection.size() !== 1) {
return false;
}
if (nfCanvasUtils.canRead(selection) === false || nfCanvasUtils.canModify(selection) === false) {
return false;
}
if (nfCanvasUtils.isProcessor(selection)) {
var processorData = selection.datum();
return processorData.component.multipleVersionsAvailable === true;
} else {
return false;
}
};
/**
* Determines whether the current selection is a process group.
*
* @param {selection} selection
*/
var isProcessGroup = function (selection) {
// ensure the correct number of components are selected
if (selection.size() !== 1) {
return false;
}
return nfCanvasUtils.isProcessGroup(selection);
};
/**
* Determines whether the current selection is a processor.
*
* @param {selection} selection
*/
var isRunnableProcessor = function (selection) {
// ensure the correct number of components are selected
if (selection.size() !== 1) {
return false;
}
return isRunnable(selection) && nfCanvasUtils.isProcessor(selection);
};
/**
* Returns whether the process group supports downloading the current flow.
*
* @param selection
* @returns {boolean}
*/
var supportsDownloadFlow = function (selection) {
// download is allowed when either nothing is selected or a single readable process group is selected
return (selection.empty() && nfCanvasUtils.canReadCurrentGroup()) ||
(selection.size() === 1 && nfCanvasUtils.isProcessGroup(selection) && nfCanvasUtils.canRead(selection));
};
/**
* Determines whether the current selection supports flow versioning.
*
* @param selection
*/
var supportsFlowVersioning = function (selection) {
if (nfCommon.canVersionFlows() === false) {
return false;
}
if (selection.empty()) {
// prevent versioning of the root group
if (nfCanvasUtils.getParentGroupId() === null) {
return false;
}
// if not root group, ensure adequate permissions
return nfCanvasUtils.canReadCurrentGroup() && nfCanvasUtils.canWriteCurrentGroup();
}
if (isProcessGroup(selection) === true) {
return nfCanvasUtils.canRead(selection) && nfCanvasUtils.canModify(selection);
}
return false;
};
/**
* Determines whether the current selection supports starting flow versioning.
*
* @param selection
*/
var supportsStartFlowVersioning = function (selection) {
// ensure this selection supports flow versioning above
if (supportsFlowVersioning(selection) === false) {
return false;
}
if (selection.empty()) {
// check bread crumbs for version control information in the current group
var breadcrumbEntities = nfNgBridge.injector.get('breadcrumbsCtrl').getBreadcrumbs();
if (breadcrumbEntities.length > 0) {
var breadcrumbEntity = breadcrumbEntities[breadcrumbEntities.length - 1];
if (breadcrumbEntity.permissions.canRead) {
return nfCommon.isUndefinedOrNull(breadcrumbEntity.breadcrumb.versionControlInformation);
} else {
return false;
}
} else {
return false;
}
}
// check the selection for version control information
var processGroupData = selection.datum();
return nfCommon.isUndefinedOrNull(processGroupData.component.versionControlInformation);
};
/**
* Returns whether the process group support supports commit.
*
* @param selection
* @returns {boolean}
*/
var supportsCommitFlowVersion = function (selection) {
var versionControlInformation = getFlowVersionControlInformation(selection);
// check the selection for version control information
return versionControlInformation !== null &&
versionControlInformation.state === 'LOCALLY_MODIFIED';
};
/**
* Returns whether the process group support supports force commit.
*
* @param selection
* @returns {boolean}
*/
var supportsForceCommitFlowVersion = function (selection) {
var versionControlInformation = getFlowVersionControlInformation(selection);
// check the selection for version control information
return versionControlInformation !== null &&
versionControlInformation.state === 'LOCALLY_MODIFIED_AND_STALE';
};
/**
* Returns whether the process group supports revert local changes.
*
* @param selection
* @returns {boolean}
*/
var hasLocalChanges = function (selection) {
var versionControlInformation = getFlowVersionControlInformation(selection);
// check the selection for version control information
return versionControlInformation !== null &&
(versionControlInformation.state === 'LOCALLY_MODIFIED' ||
versionControlInformation.state === 'LOCALLY_MODIFIED_AND_STALE');
};
/**
* Returns whether the process group supports changing the flow version.
*
* @param selection
* @returns {boolean}
*/
var supportsChangeFlowVersion = function (selection) {
var versionControlInformation = getFlowVersionControlInformation(selection);
return versionControlInformation !== null &&
versionControlInformation.state !== 'LOCALLY_MODIFIED' &&
versionControlInformation.state !== 'LOCALLY_MODIFIED_AND_STALE' &&
versionControlInformation.state !== 'SYNC_FAILURE';
};
/**
* Determines whether the current selection supports stopping flow versioning.
*
* @param selection
* @returns {boolean}
*/
var supportsStopFlowVersioning = function (selection) {
var versionControlInformation = getFlowVersionControlInformation(selection);
return versionControlInformation !== null;
};
/**
* Convenience function to perform all flow versioning pre-checks and retrieve
* valid version information.
*
* @param selection
*/
var getFlowVersionControlInformation = function (selection) {
// ensure this selection supports flow versioning
if (supportsFlowVersioning(selection) === false) {
return null;
}
var versionControlInformation;
if (selection.empty()) {
// check bread crumbs for version control information in the current group
var breadcrumbEntities = nfNgBridge.injector.get('breadcrumbsCtrl').getBreadcrumbs();
if (breadcrumbEntities.length > 0) {
var breadcrumbEntity = breadcrumbEntities[breadcrumbEntities.length - 1];
if (breadcrumbEntity.permissions.canRead) {
versionControlInformation = breadcrumbEntity.breadcrumb.versionControlInformation;
} else {
return null;
}
} else {
return null;
}
} else {
var processGroupData = selection.datum();
versionControlInformation = processGroupData.component.versionControlInformation;
}
if (nfCommon.isDefinedAndNotNull(versionControlInformation)) {
return versionControlInformation;;
}
return null;
}
/**
* Determines whether the current selection could have provenance.
*
* @param {selection} selection
*/
var canAccessProvenance = function (selection) {
// ensure the correct number of components are selected
if (selection.size() !== 1) {
return false;
}
return !nfCanvasUtils.isLabel(selection) && !nfCanvasUtils.isConnection(selection) && !nfCanvasUtils.isProcessGroup(selection)
&& !nfCanvasUtils.isRemoteProcessGroup(selection) && nfCommon.canAccessProvenance();
};
/**
* Determines whether the current selection is a remote process group.
*
* @param {selection} selection
*/
var isRemoteProcessGroup = function (selection) {
// ensure the correct number of components are selected
if (selection.size() !== 1) {
return false;
}
if (nfCanvasUtils.canRead(selection) === false) {
return false;
}
return nfCanvasUtils.isRemoteProcessGroup(selection);
};
/**
* Determines if the components in the specified selection support starting transmission.
*
* @param {selection} selection
*/
var canStartTransmission = function (selection) {
if (nfCanvasUtils.canOperate(selection) === false) {
return false;
}
return nfCanvasUtils.canAllStartTransmitting(selection);
};
/**
* Determines if the components in the specified selection support stopping transmission.
*
* @param {selection} selection
*/
var canStopTransmission = function (selection) {
if (nfCanvasUtils.canOperate(selection) === false) {
return false;
}
return nfCanvasUtils.canAllStopTransmitting(selection);
};
/**
* Only DFMs can empty a queue.
*
* @param {selection} selection
*/
var canEmptyQueue = function (selection) {
return isConnection(selection);
};
/**
* Only DFMs can list a queue.
*
* @param {selection} selection
*/
var canListQueue = function (selection) {
return isConnection(selection);
};
/**
* Determines if the components in the specified selection can be moved into a parent group.
*
* @param {type} selection
*/
var canMoveToParent = function (selection) {
if (nfCanvasUtils.canModify(selection) === false) {
return false;
}
// TODO - also check can modify in parent
return !selection.empty() && nfCanvasUtils.getComponentByType('Connection').isDisconnected(selection) && nfCanvasUtils.getParentGroupId() !== null;
};
/**
* Closes sub menu's starting from the specified menu.
*
* @param menu menu
*/
var closeSubMenus = function (menu) {
menu.remove().find('.group-menu-item').each(function () {
var siblingGroupId = $(this).attr('id');
closeSubMenus($('#' + siblingGroupId + '-sub-menu'));
});
};
/**
* Adds a menu item to the context menu.
*
* {
* click: refresh (function),
* text: 'Start' (string),
* clazz: 'fa fa-refresh'
* }
*
* @param {jQuery} contextMenu The context menu
* @param {object} item The menu item configuration
*/
var addMenuItem = function (contextMenu, item) {
var menuItem = $('<div class="context-menu-item"></div>').attr('id', item.id).on('mouseenter', function () {
$(this).addClass('hover');
contextMenu.find('.group-menu-item').not('#' + item.id).each(function () {
var siblingGroupId = $(this).attr('id');
closeSubMenus($('#' + siblingGroupId + '-sub-menu'));
});
}).on('mouseleave', function () {
$(this).removeClass('hover');
}).appendTo(contextMenu);
// create the img and conditionally add the style
var img = $('<div class="context-menu-item-img"></div>').addClass(item['clazz']).appendTo(menuItem);
if (nfCommon.isDefinedAndNotNull(item['imgStyle'])) {
img.addClass(item['imgStyle']);
}
$('<div class="context-menu-item-text"></div>').text(item['text']).appendTo(menuItem);
if (item.isGroup) {
$('<div class="fa fa-caret-right context-menu-group-item-img"></div>').appendTo(menuItem);
}
$('<div class="clear"></div>').appendTo(menuItem);
return menuItem;
};
/**
* Positions and shows the context menu.
*
* @param {jQuery} contextMenu The context menu
* @param {object} options The context menu configuration
*/
var positionAndShow = function (contextMenu, options) {
contextMenu.css({
'left': options.x + 'px',
'top': options.y + 'px'
}).show();
};
/**
* Executes the specified action with the optional selection.
*
* @param {string} action
* @param {selection} selection
* @param {mouse event} evt
*/
var executeAction = function (action, selection, evt) {
// execute the action
nfActions[action](selection, evt);
// close the context menu
nfContextMenu.hide();
};
// defines the available actions and the conditions which they apply
var menuItems = [
{id: 'reload-menu-item', condition: emptySelection, menuItem: {clazz: 'fa fa-refresh', text: 'Refresh', action: 'reload'}},
{id: 'leave-group-menu-item', condition: isNotRootGroup, menuItem: {clazz: 'fa fa-level-up', text: 'Leave group', action: 'leaveGroup'}},
{separator: true},
{id: 'show-configuration-menu-item', condition: isConfigurable, menuItem: {clazz: 'fa fa-gear', text: 'Configure', action: 'showConfiguration'}},
{id: 'show-details-menu-item', condition: hasDetails, menuItem: {clazz: 'fa fa-gear', text: 'View configuration', action: 'showDetails'}},
{id: 'parameters-menu-item', condition: hasParameterContext, menuItem: {clazz: 'fa', text: 'Parameters', action: 'openParameterContext'}},
{id: 'variable-registry-menu-item', condition: hasVariables, menuItem: {clazz: 'fa', text: 'Variables', action: 'openVariableRegistry'}},
{separator: true},
{id: 'version-menu-item', groupMenuItem: {clazz: 'fa', text: 'Version'}, menuItems: [
{id: 'start-version-control-menu-item', condition: supportsStartFlowVersioning, menuItem: {clazz: 'fa fa-upload', text: 'Start version control', action: 'saveFlowVersion'}},
{separator: true},
{id: 'commit-menu-item', condition: supportsCommitFlowVersion, menuItem: {clazz: 'fa fa-upload', text: 'Commit local changes', action: 'saveFlowVersion'}},
{id: 'force-commit-menu-item', condition: supportsForceCommitFlowVersion, menuItem: {clazz: 'fa fa-upload', text: 'Commit local changes', action: 'forceSaveFlowVersion'}},
{id: 'local-changes-menu-item', condition: hasLocalChanges, menuItem: {clazz: 'fa', text: 'Show local changes', action: 'showLocalChanges'}},
{id: 'revert-menu-item', condition: hasLocalChanges, menuItem: {clazz: 'fa fa-undo', text: 'Revert local changes', action: 'revertLocalChanges'}},
{id: 'change-version-menu-item', condition: supportsChangeFlowVersion, menuItem: {clazz: 'fa', text: 'Change version', action: 'changeFlowVersion'}},
{separator: true},
{id: 'stop-version-control-menu-item', condition: supportsStopFlowVersioning, menuItem: {clazz: 'fa', text: 'Stop version control', action: 'stopVersionControl'}}
]},
{separator: true},
{id: 'enter-group-menu-item', condition: isProcessGroup, menuItem: {clazz: 'fa fa-sign-in', text: 'Enter group', action: 'enterGroup'}},
{separator: true},
{id: 'start-menu-item', condition: isRunnable, menuItem: {clazz: 'fa fa-play', text: 'Start', action: 'start'}},
{id: 'stop-menu-item', condition: isStoppable, menuItem: {clazz: 'fa fa-stop', text: 'Stop', action: 'stop'}},
{id: 'run-once-menu-item', condition: isRunnableProcessor, menuItem: {clazz: 'fa fa-caret-right', text: 'Run Once', action: 'runOnce'}},
{id: 'terminate-menu-item', condition: canTerminate, menuItem: {clazz: 'fa fa-hourglass-end', text: 'Terminate', action: 'terminate'}},
{id: 'enable-menu-item', condition: canEnable, menuItem: {clazz: 'fa fa-flash', text: 'Enable', action: 'enable'}},
{id: 'disable-menu-item', condition: canDisable, menuItem: {clazz: 'icon icon-enable-false', text: 'Disable', action: 'disable'}},
{id: 'enable-transmission-menu-item', condition: canStartTransmission, menuItem: {clazz: 'fa fa-bullseye', text: 'Enable transmission', action: 'enableTransmission'}},
{id: 'disable-transmission-menu-item', condition: canStopTransmission, menuItem: { clazz: 'icon icon-transmit-false', text: 'Disable transmission', action: 'disableTransmission'}},
{separator: true},
{id: 'data-provenance-menu-item', condition: canAccessProvenance, menuItem: {clazz: 'icon icon-provenance', imgStyle: 'context-menu-provenance', text: 'View data provenance', action: 'openProvenance'}},
{id: 'show-stats-menu-item', condition: supportsStats, menuItem: {clazz: 'fa fa-area-chart', text: 'View status history', action: 'showStats'}},
{id: 'view-state-menu-item', condition: isStatefulProcessor, menuItem: {clazz: 'fa fa-tasks', text: 'View state', action: 'viewState'}},
{id: 'list-queue-menu-item', condition: canListQueue, menuItem: {clazz: 'fa fa-list', text: 'List queue', action: 'listQueue'}},
{id: 'show-usage-menu-item', condition: hasUsage, menuItem: {clazz: 'fa fa-book', text: 'View usage', action: 'showUsage'}},
{id: 'view-menu-item', groupMenuItem: {clazz: 'icon icon-connect', text: 'View connections'}, menuItems: [
{id: 'show-upstream-menu-item', condition: hasUpstream, menuItem: {clazz: 'icon', text: 'Upstream', action: 'showUpstream'}},
{id: 'show-downstream-menu-item', condition: hasDownstream, menuItem: {clazz: 'icon', text: 'Downstream', action: 'showDownstream'}}
]},
{separator: true},
{id: 'refresh-remote-flow-menu-item', condition: isRemoteProcessGroup, menuItem: {clazz: 'fa fa-refresh', text: 'Refresh remote', action: 'refreshRemoteFlow'}},
{separator: true},
{id: 'remote-ports-menu-item', condition: isRemoteProcessGroup, menuItem: {clazz: 'fa fa-cloud', text: 'Manage remote ports', action: 'remotePorts'}},
{id: 'manage-policies-menu-item', condition: canManagePolicies, menuItem: {clazz: 'fa fa-key', text: 'Manage access policies', action: 'managePolicies'}},
{id: 'change-version-menu-item', condition: canChangeProcessorVersion, menuItem: {clazz: 'fa fa-exchange', text: 'Change version', action: 'changeVersion'}},
{separator: true},
{id: 'show-source-menu-item', condition: isConnection, menuItem: {clazz: 'fa fa-long-arrow-left', text: 'Go to source', action: 'showSource'}},
{id: 'show-destination-menu-item', condition: isConnection, menuItem: {clazz: 'fa fa-long-arrow-right', text: 'Go to destination', action: 'showDestination'}},
{separator: true},
{id: 'align-menu-item', groupMenuItem: {clazz: 'fa', text: 'Align'}, menuItems: [
{id: 'align-horizontal-menu-item', condition: canAlign, menuItem: { clazz: 'fa fa-align-center fa-rotate-90', text: 'Horizontally', action: 'alignHorizontal'}},
{id: 'align-vertical-menu-item', condition: canAlign, menuItem: {clazz: 'fa fa-align-center', text: 'Vertically', action: 'alignVertical'}}
]},
{id: 'to-front-menu-item', condition: canMoveToFront, menuItem: {clazz: 'fa fa-clone', text: 'Bring to front', action: 'toFront'}},
{id: 'center-menu-item', condition: isNotConnection, menuItem: {clazz: 'fa fa-crosshairs', text: 'Center in view', action: 'center'}},
{id: 'fill-color-menu-item', condition: isColorable, menuItem: {clazz: 'fa fa-paint-brush', text: 'Change color', action: 'fillColor'}},
{id: 'open-uri-menu-item', condition: isRemoteProcessGroup, menuItem: {clazz: 'fa fa-external-link', text: 'Go to', action: 'openUri'}},
{separator: true},
{id: 'move-into-parent-menu-item', condition: canMoveToParent, menuItem: {clazz: 'fa fa-arrows', text: 'Move to parent group', action: 'moveIntoParent'}},
{id: 'group-menu-item', condition: canGroup, menuItem: {clazz: 'icon icon-group', text: 'Group', action: 'group'}},
{separator: true},
{id: 'download-menu-item', condition: supportsDownloadFlow, menuItem: {clazz: 'fa', text: 'Download flow definition', action: 'downloadFlow'}},
{separator: true},
{id: 'upload-template-menu-item', condition: canUploadTemplate, menuItem: {clazz: 'icon icon-template-import', text: 'Upload template', action: 'uploadTemplate'}},
{id: 'template-menu-item', condition: canCreateTemplate, menuItem: {clazz: 'icon icon-template-save', text: 'Create template', action: 'template'}},
{separator: true},
{id: 'copy-menu-item', condition: isCopyable, menuItem: {clazz: 'fa fa-copy', text: 'Copy', action: 'copy'}},
{id: 'paste-menu-item', condition: isPastable, menuItem: {clazz: 'fa fa-paste', text: 'Paste', action: 'paste'}},
{separator: true},
{id: 'empty-queue-menu-item', condition: canEmptyQueue, menuItem: {clazz: 'fa fa-minus-circle', text: 'Empty queue', action: 'emptyQueue'}},
{id: 'empty-all-queues-menu-item', condition: isProcessGroup, menuItem: {clazz: 'fa fa-minus-circle', text: 'Empty all queues', action: 'emptyAllQueues'}},
{id: 'empty-all-queues-menu-item-noselection', condition: emptySelection, menuItem: {clazz: 'fa fa-minus-circle', text: 'Empty all queues', action: 'emptyAllQueues'}},
{id: 'delete-menu-item', condition: isDeletable, menuItem: {clazz: 'fa fa-trash', text: 'Delete', action: 'delete'}}
];
var nfContextMenu = {
/**
* Initialize the context menu.
*
* @param nfActionsRef The nfActions module.
*/
init: function (nfActionsRef) {
nfActions = nfActionsRef;
$('#context-menu').on('contextmenu', function (evt) {
// stop propagation and prevent default
evt.preventDefault();
evt.stopPropagation();
});
},
/**
* Shows the context menu.
*/
show: function () {
// hide the menu if currently visible
nf.ContextMenu.hide();
var contextMenu = $('#context-menu').empty();
var canvasBody = $('#canvas-body').get(0);
var bannerFooter = $('#banner-footer').get(0);
var breadCrumb = $('#breadcrumbs').get(0);
// get the current selection
var selection = nfCanvasUtils.getSelection();
// get the location for the context menu
var position = d3.mouse(canvasBody);
// determines if the specified menu positioned at x would overflow the available width
var overflowRight = function (x, menu) {
return x + menu.width() > canvasBody.clientWidth;
};
// determines if the specified menu positioned at y would overflow the available height
var overflowBottom = function (y, menu) {
return y + menu.height() > (canvasBody.clientHeight - breadCrumb.clientHeight - bannerFooter.clientHeight);
};
// adds a menu item
var addItem = function (menu, id, item) {
// add the menu item
addMenuItem(menu, {
id: id,
clazz: item.clazz,
imgStyle: item.imgStyle,
text: item.text,
isGroup: false
}).on('click', function (evt) {
executeAction(item.action, selection, evt);
}).on('contextmenu', function (evt) {
executeAction(item.action, selection, evt);
// stop propagation and prevent default
evt.preventDefault();
evt.stopPropagation();
});
};
// adds a group item
var addGroupItem = function (menu, groupId, groupItem, applicableGroupItems) {
// add the menu item
addMenuItem(menu, {
id: groupId,
clazz: groupItem.clazz,
imgStyle: groupItem.imgStyle,
text: groupItem.text,
isGroup: true
}).addClass('group-menu-item').on('mouseenter', function () {
// see if this submenu item is already open
if ($('#' + groupId + '-sub-menu').length == 0) {
var groupMenuItem = $(this);
var contextMenuPosition = menu.position();
var groupMenuItemPosition = groupMenuItem.position();
var x = contextMenuPosition.left + groupMenuItemPosition.left + groupMenuItem.width();
var y = contextMenuPosition.top + groupMenuItemPosition.top;
var subMenu = $('<div class="context-menu unselectable sub-menu"></div>').attr('id', groupId + '-sub-menu').appendTo('body');
processMenuItems(subMenu, applicableGroupItems);
// make sure the sub menu is not hidden by the browser boundaries
if (overflowRight(x, subMenu)) {
x -= (subMenu.width() + groupMenuItem.width() - 4);
}
if (overflowBottom(y, subMenu)) {
y -= (subMenu.height() - groupMenuItem.height());
}
subMenu.css({
top: y + 'px',
left: x + 'px'
}).show();
}
});
};
// whether or not a group item should be included
var includeGroupItem = function (groupItem) {
if (groupItem.separator) {
return true;
} else if (groupItem.menuItem) {
return groupItem.condition(selection);
} else {
var descendantItems = [];
$.each(groupItem.menuItems, function (_, descendantItem) {
if (includeGroupItem(descendantItem)) {
descendantItems.push(descendantItem);
}
});
return descendantItems.length > 0;
}
};
// adds the specified items to the specified menu
var processMenuItems = function (menu, items) {
var allowSeparator = false;
$.each(items, function (_, item) {
if (item.separator && allowSeparator) {
$('<div class="context-menu-item-separator"></div>').appendTo(menu);
allowSeparator = false;
} else {
if (processMenuItem(menu, item)) {
allowSeparator = true;
}
}
});
// ensure the last child isn't a separator
var last = menu.children().last();
if (last.hasClass('context-menu-item-separator')) {
last.remove();
}
};
// adds the specified item to the specified menu if the conditions are met, returns if the item was added
var processMenuItem = function (menu, i) {
var included = false;
if (i.menuItem) {
included = i.condition(selection);
if (included) {
addItem(menu, i.id, i.menuItem);
}
} else if (i.groupMenuItem) {
var applicableGroupItems = [];
$.each(i.menuItems, function (_, groupItem) {
if (includeGroupItem(groupItem)) {
applicableGroupItems.push(groupItem);
}
});
// ensure the included menu items includes more than just separators
var includedMenuItems = $.grep(applicableGroupItems, function (gi) {
return nfCommon.isUndefinedOrNull(gi.separator);
});
included = includedMenuItems.length > 0;
if (included) {
addGroupItem(menu, i.id, i.groupMenuItem, applicableGroupItems);
}
}
return included;
};
// consider each component action for the current selection
processMenuItems(contextMenu, menuItems);
// make sure the context menu is not hidden by the browser boundaries
if (overflowRight(position[0], contextMenu)) {
position[0] = canvasBody.clientWidth - contextMenu.width() - 2;
}
if (overflowBottom(position[1], contextMenu)) {
position[1] = canvasBody.clientHeight - breadCrumb.clientHeight - bannerFooter.clientHeight - contextMenu.height() - 9;
}
// show the context menu
positionAndShow(contextMenu, {
'x': position[0],
'y': position[1]
});
// inform Angular app incase we've click on the canvas
nfNgBridge.digest();
},
/**
* Hides the context menu.
*/
hide: function () {
$('#context-menu').hide();
$('div.context-menu.sub-menu').remove();
},
/**
* Activates the context menu for the components in the specified selection.
*
* @param {selection} components The components to enable the context menu for
*/
activate: function (components) {
components.on('contextmenu.selection', function () {
// get the clicked component to update selection
nfContextMenu.show();
// stop propagation and prevent default
d3.event.preventDefault();
d3.event.stopPropagation();
});
}
};
return nfContextMenu;
}));