[NIFI-3035] add deep linking URL to canvas components and PGs. This closes #1559

This commit is contained in:
Scott Aslan 2017-03-15 16:19:02 -04:00 committed by Matt Gilman
parent d1ebddce98
commit a90a770244
No known key found for this signature in database
GPG Key ID: DF61EC19432AEE37
16 changed files with 312 additions and 25 deletions

View File

@ -445,6 +445,29 @@ For details see http://qtip2.com
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
This product bundles 'url-search-params' which is available under an MIT style license.
For details see https://github.com/WebReflection/url-search-params
Copyright (C) 2015 by WebReflection
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
This product bundles 'jQuery MiniColors' which is available under the MIT License.
For details see http://www.abeautifulsite.net/

View File

@ -474,6 +474,29 @@ For details see http://qtip2.com
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
This product bundles 'url-search-params' which is available under an MIT style license.
For details see https://github.com/WebReflection/url-search-params
Copyright (C) 2015 by WebReflection
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
This product bundles 'jQuery MiniColors' which is available under the MIT License.
For details see http://www.abeautifulsite.net/

View File

@ -226,6 +226,10 @@
<!-- reset.css -->
<include>reset.css/reset.css</include>
<include>reset.css/README.md</include>
<!-- URLSearchParams Polyfill -->
<include>url-search-params/build/url-search-params.js</include>
<include>url-search-params/README.md</include>
<include>url-search-params/LICENSE.txt</include>
</includes>
</resource>
</resources>

View File

@ -14,7 +14,8 @@
"jsonlint": "1.6.2",
"JSON2": "0.1.0",
"reset.css": "2.0.2",
"d3": "3.5.17"
"d3": "3.5.17",
"url-search-params": "0.6.1"
},
"description": "Apache NiFi 3rd party client side resources.",
"repository": {

View File

@ -422,6 +422,29 @@ For details see http://qtip2.com
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
This product bundles 'url-search-params' which is available under an MIT style license.
For details see https://github.com/WebReflection/url-search-params
Copyright (C) 2015 by WebReflection
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
This product bundles 'jQuery MiniColors' which is available under the MIT License.
For details see http://www.abeautifulsite.net/

View File

@ -40,6 +40,7 @@
<link rel="stylesheet" href="fonts/flowfont/flowfont.css" type="text/css" />
<link rel="stylesheet" href="assets/angular-material/angular-material.min.css" type="text/css" />
<link rel="stylesheet" href="assets/font-awesome/css/font-awesome.min.css" type="text/css" />
<script type="text/javascript" src="assets/url-search-params/build/url-search-params.js"></script>
<script type="text/javascript" src="js/codemirror/lib/codemirror-compressed.js"></script>
<script type="text/javascript" src="assets/d3/d3.min.js"></script>
<script type="text/javascript" src="assets/jquery/dist/jquery.min.js"></script>

View File

@ -136,6 +136,9 @@
'selectAll': true
});
// update component visibility
nfGraph.updateVisibility();
// update the birdseye
nfBirdseye.refresh();
}).fail(nfErrorHandler.handleAjaxError);

View File

@ -141,6 +141,9 @@
'selectAll': true
});
// update component visibility
nfGraph.updateVisibility();
// update the birdseye
nfBirdseye.refresh();
}).fail(nfErrorHandler.handleAjaxError);

View File

@ -406,18 +406,24 @@
* @param {selection} selection The selection
*/
show: function (selection) {
// deselect the current selection
var currentlySelected = nfCanvasUtils.getSelection();
currentlySelected.classed('selected', false);
// select only the component/connection in question
selection.classed('selected', true);
if (selection.size() === 1) {
// deselect the current selection
var currentlySelected = nfCanvasUtils.getSelection();
currentlySelected.classed('selected', false);
// select only the component/connection in question
selection.classed('selected', true);
nfActions.center(selection);
// inform Angular app that values have changed
nfNgBridge.digest();
} else {
nfNgBridge.injector.get('navigateCtrl').zoomFit();
}
// update URL deep linking params
nfCanvasUtils.setURLParameters(nfCanvasUtils.getGroupId(), selection);
// inform Angular app that values have changed
nfNgBridge.digest();
},
/**
@ -935,6 +941,9 @@
}
}
// update URL deep linking params
nfCanvasUtils.setURLParameters();
// refresh the birdseye
nfBirdseye.refresh();
// inform Angular app values have changed
@ -986,6 +995,9 @@
nfConnection.remove(components.get('Connection'));
}
// update URL deep linking params
nfCanvasUtils.setURLParameters();
// refresh the birdseye
nfBirdseye.refresh();

View File

@ -241,8 +241,8 @@
/**
* Shows the specified component in the specified group.
*
* @argument {string} groupId The id of the group
* @argument {string} componentId The id of the component
* @param {string} groupId The id of the group
* @param {string} componentId The id of the component
*/
showComponent: function (groupId, componentId) {
// ensure the group id is specified
@ -257,12 +257,12 @@
// reload
nfCanvas.reload().done(function () {
deferred.resolve();
}).fail(function () {
}).fail(function (xhr, status, error) {
nfDialog.showOkDialog({
headerText: 'Process Group',
headerText: 'Error',
dialogContent: 'Unable to load the group for the specified component.'
});
deferred.reject();
deferred.reject(xhr, status, error);
});
} else {
deferred.resolve();
@ -277,7 +277,7 @@
nfActions.show(component);
} else {
nfDialog.showOkDialog({
headerText: 'Process Group',
headerText: 'Error',
dialogContent: 'Unable to find the specified component.'
});
}
@ -285,6 +285,159 @@
}
},
/**
* Displays the URL deep link on the canvas.
*
* @param forceCanvasLoad Boolean enabling the update of the URL parameters.
*/
showDeepLink: function (forceCanvasLoad) {
// deselect components
nfCanvasUtils.getSelection().classed('selected', false);
// close the ok dialog if open
if ($('#nf-ok-dialog').is(':visible') === true) {
$('#nf-ok-dialog').modal('hide');
}
// Feature detection and browser support for URLSearchParams
if ('URLSearchParams' in window) {
var urlSearchParams = new URL(window.location).searchParams;
var groupId = nfCanvasUtils.getGroupId();
var componentIds = [];
if (urlSearchParams.get('processGroupId')) {
groupId = urlSearchParams.get('processGroupId');
}
if (urlSearchParams.get('componentIds')) {
componentIds = urlSearchParams.get('componentIds').split(',');
}
// load the graph but do not update the browser history
if (componentIds.length >= 1) {
return nfCanvasUtils.showComponents(groupId, componentIds, forceCanvasLoad);
} else {
return nfCanvasUtils.getComponentByType('ProcessGroup').enterGroup(groupId);
}
}
},
/**
* Shows the specified components in the specified group.
*
* @param {string} groupId The id of the group
* @param {array} componentIds The ids of the components
* @param {bool} forceCanvasLoad Boolean to force reload of the canvas.
*/
showComponents: function (groupId, componentIds, forceCanvasLoad) {
// ensure the group id is specified
if (nfCommon.isDefinedAndNotNull(groupId)) {
// initiate a graph refresh
var refreshGraph = $.Deferred(function (deferred) {
// load a different group if necessary
if (groupId !== nfCanvas.getGroupId() || forceCanvasLoad) {
// set the new group id
nfCanvas.setGroupId(groupId);
// reload
nfCanvas.reload().done(function () {
deferred.resolve();
}).fail(function (xhr, status, error) {
nfDialog.showOkDialog({
headerText: 'Error',
dialogContent: 'Unable to enter the selected group.'
});
deferred.reject(xhr, status, error);
});
} else {
deferred.resolve();
}
}).promise();
// when the refresh has completed, select the match
refreshGraph.done(function () {
// get the components to select
var components = d3.selectAll('g.component, g.connection').filter(function (d) {
if (componentIds.indexOf(d.id) >= 0) {
// remove located components from array so that only unfound components will remain
componentIds.splice(componentIds.indexOf(d.id), 1);
return d;
}
});
if (componentIds.length > 0) {
var dialogContent = $('<p></p>').text('Specified component(s) not found: ' + componentIds.join(', ') + '.').append('<br/><br/>').append($('<p>Unable to select component(s).</p>'));
nfDialog.showOkDialog({
headerText: 'Error',
dialogContent: dialogContent
});
}
nfActions.show(components);
});
return refreshGraph;
}
},
MAX_URL_LENGTH: 2000, // the maximum (suggested) safe string length of a URL supported by all browsers and application servers
/**
* Set the parameters of the URL.
*
* @param groupId The process group id.
* @param selections The component ids.
*/
setURLParameters: function (groupId, selections) {
// Feature detection and browser support for URLSearchParams
if ('URLSearchParams' in window) {
if (!nfCommon.isDefinedAndNotNull(groupId)) {
groupId = nfCanvasUtils.getGroupId();
}
if (!nfCommon.isDefinedAndNotNull(selections)) {
selections = nfCanvasUtils.getSelection();
}
var selectedComponentIds = [];
selections.each(function (selection) {
selectedComponentIds.push(selection.id);
});
// get all URL parameters
var params = new URL(window.location).searchParams;
params.set('processGroupId', groupId);
params.set('componentIds', selectedComponentIds.sort());
// create object whose keys are the parameter name and the values are the parameter values
var paramsObject = {};
params.forEach(function (v, k) {
paramsObject[k] = v;
});
var url = new URL(window.location);
var newUrl = url.origin + url.pathname;
if (nfCommon.isDefinedAndNotNull(nfCanvasUtils.getParentGroupId()) || selectedComponentIds.length > 0) {
if (!nfCommon.isDefinedAndNotNull(nfCanvasUtils.getParentGroupId())) {
// we are in the root group so set processGroupId param value to 'root' alias
paramsObject['processGroupId'] = 'root';
}
if ((url.origin + url.pathname + '?' + $.param(paramsObject)).length <= nfCanvasUtils.MAX_URL_LENGTH) {
newUrl = url.origin + url.pathname + '?' + $.param(paramsObject);
} else if (nfCommon.isDefinedAndNotNull(nfCanvasUtils.getParentGroupId())) {
// silently remove all component ids
paramsObject['componentIds'] = '';
newUrl = url.origin + url.pathname + '?' + $.param(paramsObject);
}
}
window.history.replaceState({'previous_url': url.href}, window.document.title, newUrl);
}
},
/**
* Gets the currently selected components and connections.
*

View File

@ -284,6 +284,9 @@
// since the context menu event propagated back to the canvas, clear the selection
nfCanvasUtils.getSelection().classed('selected', false);
// update URL deep linking params
nfCanvasUtils.setURLParameters();
// show the context menu on the canvas
nfContextMenu.show();
@ -553,9 +556,15 @@
// remove the selection box
selectionBox.remove();
// update URL deep linking params
nfCanvasUtils.setURLParameters();
} else if (panning === false) {
// deselect as necessary if we are not panning
nfCanvasUtils.getSelection().classed('selected', false);
// update URL deep linking params
nfCanvasUtils.setURLParameters();
}
// inform Angular app values have changed
@ -697,6 +706,10 @@
} else if (evt.keyCode === 65) {
// ctrl-a
nfActions.selectAll();
// update URL deep linking params
nfCanvasUtils.setURLParameters();
nfNgBridge.digest();
// only want to prevent default if the action was performed, otherwise default select all would be overridden

View File

@ -310,6 +310,9 @@
.on('mousedown.selection', function () {
// select the connection when clicking the selectable path
nfSelectable.select(d3.select(this.parentNode));
// update URL deep linking params
nfCanvasUtils.setURLParameters();
})
.call(nfContextMenu.activate);
};
@ -630,6 +633,9 @@
.on('mousedown.selection', function () {
// select the connection when clicking the label
nfSelectable.select(d3.select(this.parentNode));
// update URL deep linking params
nfCanvasUtils.setURLParameters();
})
.call(nfContextMenu.activate);
@ -660,6 +666,9 @@
.on('mousedown.selection', function () {
// select the connection when clicking the label
nfSelectable.select(d3.select(this.parentNode));
// update URL deep linking params
nfCanvasUtils.setURLParameters();
})
.call(nfContextMenu.activate);
@ -740,6 +749,9 @@
.on('mousedown.selection', function () {
// select the connection when clicking the label
nfSelectable.select(d3.select(this.parentNode));
// update URL deep linking params
nfCanvasUtils.setURLParameters();
})
.call(nfContextMenu.activate);
@ -777,6 +789,9 @@
.on('mousedown.selection', function () {
// select the connection when clicking the label
nfSelectable.select(d3.select(this.parentNode));
// update URL deep linking params
nfCanvasUtils.setURLParameters();
})
.call(nfContextMenu.activate);

View File

@ -206,8 +206,8 @@
nfProcessor.init(nfConnectable, nfDraggable, nfSelectable, nfContextMenu);
nfConnection.init(nfSelectable, nfContextMenu, nfConnectionConfiguration);
// load the graph
return nfProcessGroup.enterGroup(nfCanvasUtils.getGroupId());
// display the deep link
return nfCanvasUtils.showDeepLink(true);
},
/**
@ -389,6 +389,9 @@
updateVisibility: function () {
updateComponentVisibility();
nfGraph.pan();
// update URL deep linking params
nfCanvasUtils.setURLParameters();
},
/**

View File

@ -1279,6 +1279,10 @@
transition: true
});
}
// update URL deep linking params
nfCanvasUtils.setURLParameters(groupId, d3.select());
}).fail(function () {
nfDialog.showOkDialog({
headerText: 'Process Group',

View File

@ -21,21 +21,24 @@
if (typeof define === 'function' && define.amd) {
define(['d3',
'nf.ng.Bridge',
'nf.ContextMenu'],
function (d3, nfNgBridge, nfContextMenu) {
return (nf.Selectable = factory(d3, nfNgBridge, nfContextMenu));
'nf.ContextMenu',
'nf.CanvasUtils'],
function (d3, nfNgBridge, nfContextMenu, nfCanvasUtils) {
return (nf.Selectable = factory(d3, nfNgBridge, nfContextMenu, nfCanvasUtils));
});
} else if (typeof exports === 'object' && typeof module === 'object') {
module.exports = (nf.Selectable =
factory(require('d3'),
require('nf.ng.Bridge'),
require('nf.ContextMenu')));
require('nf.ContextMenu'),
require('nf.CanvasUtils')));
} else {
nf.Selectable = factory(root.d3,
root.nf.ng.Bridge,
root.nf.ContextMenu);
root.nf.ContextMenu,
root.nf.CanvasUtils);
}
}(this, function (d3, nfNgBridge, nfContextMenu) {
}(this, function (d3, nfNgBridge, nfContextMenu, nfCanvasUtils) {
'use strict';
var nfSelectable = {
@ -77,6 +80,9 @@
components.on('mousedown.selection', function () {
// get the clicked component to update selection
nfSelectable.select(d3.select(this));
// update URL deep linking params
nfCanvasUtils.setURLParameters();
});
}
};

View File

@ -70,7 +70,7 @@
// regardless of whether the dialog is already visible, the new content will be appended
var content = $('<p></p>').append(options.dialogContent);
$('#nf-ok-dialog-content').append(content);
$('#nf-ok-dialog-content').append(content).append('</br>');
// update the button model
$('#nf-ok-dialog').modal('setButtonModel', [{