blob: 69736910123a01e3d80cde2b416c606616d924c2 [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 top, define, module, require, exports */
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery',
'Slick',
'nf.Common',
'nf.Dialog',
'nf.ErrorHandler',
'nf.Storage',
'nf.ng.Bridge'],
function ($, Slick, nfCommon, nfDialog, nfErrorHandler, nfStorage, nfNgBridge) {
return (nf.ng.ProvenanceTable = factory($, Slick, nfCommon, nfDialog, nfErrorHandler, nfStorage, nfNgBridge));
});
} else if (typeof exports === 'object' && typeof module === 'object') {
module.exports = (nf.ng.ProvenanceTable =
factory(require('jquery'),
require('Slick'),
require('nf.Common'),
require('nf.Dialog'),
require('nf.ErrorHandler'),
require('nf.Storage'),
require('nf.ng.Bridge')));
} else {
nf.ng.ProvenanceTable = factory(root.$,
root.Slick,
root.nf.Common,
root.nf.Dialog,
root.nf.ErrorHandler,
root.nf.Storage,
root.nf.ng.Bridge);
}
}(this, function ($, Slick, nfCommon, nfDialog, nfErrorHandler, nfStorage, nfNgBridge) {
'use strict';
var nfProvenanceTable = function (provenanceLineageCtrl) {
'use strict';
/**
* Configuration object used to hold a number of configuration items.
*/
var config = {
maxResults: 1000,
defaultStartTime: '00:00:00',
defaultEndTime: '23:59:59',
styles: {
hidden: 'hidden'
},
urls: {
searchOptions: '../nifi-api/provenance/search-options',
replays: '../nifi-api/provenance-events/replays',
provenance: '../nifi-api/provenance',
provenanceEvents: '../nifi-api/provenance-events/',
clusterSearch: '../nifi-api/flow/cluster/search-results',
d3Script: 'js/d3/build/d3.min.js',
lineageScript: 'js/nf/provenance/nf-provenance-lineage.js'
}
};
/**
* The last search performed
*/
var cachedQuery = {};
/**
* Downloads the content for the provenance event that is currently loaded in the specified direction.
*
* @param {string} direction
*/
var downloadContent = function (direction) {
var eventId = $('#provenance-event-id').text();
// build the url
var dataUri = config.urls.provenanceEvents + encodeURIComponent(eventId) + '/content/' + encodeURIComponent(direction);
var parameters = {};
// conditionally include the cluster node id
var clusterNodeId = $('#provenance-event-cluster-node-id').text();
if (!nfCommon.isBlank(clusterNodeId)) {
parameters['clusterNodeId'] = clusterNodeId;
}
// open the url
if ($.isEmptyObject(parameters)) {
window.open(dataUri);
} else {
window.open(dataUri + '?' + $.param(parameters));
}
};
/**
* Views the content for the provenance event that is currently loaded in the specified direction.
*
* @param {string} direction
*/
var viewContent = function (direction) {
var controllerUri = $('#nifi-controller-uri').text();
var eventId = $('#provenance-event-id').text();
// build the uri to the data
var dataUri = controllerUri + 'provenance-events/' + encodeURIComponent(eventId) + '/content/' + encodeURIComponent(direction);
var dataUriParameters = {};
// conditionally include the cluster node id
var clusterNodeId = $('#provenance-event-cluster-node-id').text();
if (!nfCommon.isBlank(clusterNodeId)) {
dataUriParameters['clusterNodeId'] = clusterNodeId;
}
// include parameters if necessary
if ($.isEmptyObject(dataUriParameters) === false) {
dataUri = dataUri + '?' + $.param(dataUriParameters);
}
// open the content viewer
var contentViewerUrl = $('#nifi-content-viewer-url').text();
// if there's already a query string don't add another ?... this assumes valid
// input meaning that if the url has already included a ? it also contains at
// least one query parameter
if (contentViewerUrl.indexOf('?') === -1) {
contentViewerUrl += '?';
} else {
contentViewerUrl += '&';
}
var contentViewerParameters = {
'ref': dataUri
};
// open the content viewer
window.open(contentViewerUrl + $.param(contentViewerParameters));
};
/**
* Initializes the details dialog.
*/
var initDetailsDialog = function () {
// initialize the properties tabs
$('#event-details-tabs').tabbs({
tabStyle: 'tab',
selectedTabStyle: 'selected-tab',
scrollableTabContentStyle: 'scrollable',
tabs: [{
name: 'Details',
tabContentId: 'event-details-tab-content'
}, {
name: 'Attributes',
tabContentId: 'attributes-tab-content'
}, {
name: 'Content',
tabContentId: 'content-tab-content'
}]
});
$('#event-details-dialog').modal({
scrollableContentStyle: 'scrollable',
headerText: 'Provenance Event',
buttons: [{
buttonText: 'Ok',
color: {
base: '#728E9B',
hover: '#004849',
text: '#ffffff'
},
handler: {
click: function () {
$('#event-details-dialog').modal('hide');
}
}
}],
handler: {
close: function () {
// clear the details
$('#additional-provenance-details').empty();
$('#attributes-container').empty();
$('#parent-flowfiles-container').empty();
$('#child-flowfiles-container').empty();
$('#provenance-event-cluster-node-id').text('');
$('#modified-attribute-toggle').removeClass('checkbox-checked').addClass('checkbox-unchecked');
},
open: function () {
nfCommon.toggleScrollable($('#' + this.find('.tab-container').attr('id') + '-content').get(0));
}
}
});
// toggle which attributes are visible
$('#modified-attribute-toggle').on('click', function () {
var unmodifiedAttributes = $('#attributes-container div.attribute-unmodified');
if (unmodifiedAttributes.is(':visible')) {
$('#attributes-container div.attribute-unmodified').hide();
} else {
$('#attributes-container div.attribute-unmodified').show();
}
});
// input download
$('#input-content-download').on('click', function () {
downloadContent('input');
});
// output download
$('#output-content-download').on('click', function () {
downloadContent('output');
});
// if a content viewer url is specified, use it
if (nfCommon.isContentViewConfigured()) {
// input view
$('#input-content-view').on('click', function () {
viewContent('input');
});
// output view
$('#output-content-view').on('click', function () {
viewContent('output');
});
}
// handle the replay and downloading
$('#replay-content').on('click', function () {
var replayEntity = {
'eventId': $('#provenance-event-id').text()
};
// conditionally include the cluster node id
var clusterNodeId = $('#provenance-event-cluster-node-id').text();
if (!nfCommon.isBlank(clusterNodeId)) {
replayEntity['clusterNodeId'] = clusterNodeId;
}
$.ajax({
type: 'POST',
url: config.urls.replays,
data: JSON.stringify(replayEntity),
dataType: 'json',
contentType: 'application/json'
}).done(function (response) {
nfDialog.showOkDialog({
headerText: 'Provenance',
dialogContent: 'Successfully submitted replay request.'
});
}).fail(nfErrorHandler.handleAjaxError);
$('#event-details-dialog').modal('hide');
});
// show the replay panel
$('#replay-details').show();
};
/**
* Initializes the search dialog.
*
* @param {boolean} isClustered Whether or not this NiFi clustered
*/
var initSearchDialog = function (isClustered, provenanceTableCtrl) {
// configure the start and end date picker
$('#provenance-search-start-date, #provenance-search-end-date').datepicker({
showAnim: '',
showOtherMonths: true,
selectOtherMonths: true
});
// initialize the default start date/time
$('#provenance-search-start-date').datepicker('setDate', '+0d');
$('#provenance-search-end-date').datepicker('setDate', '+0d');
$('#provenance-search-start-time').val('00:00:00');
$('#provenance-search-end-time').val('23:59:59');
// initialize the default file sizes
$('#provenance-search-minimum-file-size').val('');
$('#provenance-search-maximum-file-size').val('');
// allow users to be able to search a specific node
if (isClustered) {
// make the dialog larger to support the select location
$('#provenance-search-dialog').height(575);
// get the nodes in the cluster
$.ajax({
type: 'GET',
url: config.urls.clusterSearch,
dataType: 'json'
}).done(function (response) {
var nodeResults = response.nodeResults;
// create the searchable options
var searchableOptions = [{
text: 'cluster',
value: null
}];
// sort the nodes
nodeResults.sort(function (a, b) {
var compA = a.address.toUpperCase();
var compB = b.address.toUpperCase();
return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
});
// add each node
$.each(nodeResults, function (_, nodeResult) {
searchableOptions.push({
text: nodeResult.address,
value: nodeResult.id
});
});
// populate the combo
$('#provenance-search-location').combo({
options: searchableOptions
});
}).fail(nfErrorHandler.handleAjaxError);
// show the node search combo
$('#provenance-search-location-container').show();
}
// configure the search dialog
$('#provenance-search-dialog').modal({
scrollableContentStyle: 'scrollable',
headerText: 'Search Events',
buttons: [{
buttonText: 'Search',
color: {
base: '#728E9B',
hover: '#004849',
text: '#ffffff'
},
handler: {
click: function () {
$('#provenance-search-dialog').modal('hide');
var search = {};
// extract the start date time
var startDate = $.trim($('#provenance-search-start-date').val());
var startTime = $.trim($('#provenance-search-start-time').val());
if (startDate !== '') {
if (startTime === '') {
startTime = config.defaultStartTime;
$('#provenance-search-start-time').val(startTime);
}
search['startDate'] = startDate + ' ' + startTime + ' ' + $('.timezone:first').text();
}
// extract the end date time
var endDate = $.trim($('#provenance-search-end-date').val());
var endTime = $.trim($('#provenance-search-end-time').val());
if (endDate !== '') {
if (endTime === '') {
endTime = config.defaultEndTime;
$('#provenance-search-end-time').val(endTime);
}
search['endDate'] = endDate + ' ' + endTime + ' ' + $('.timezone:first').text();
}
// extract the min/max file size
var minFileSize = $.trim($('#provenance-search-minimum-file-size').val());
if (minFileSize !== '') {
search['minimumFileSize'] = minFileSize;
}
var maxFileSize = $.trim($('#provenance-search-maximum-file-size').val());
if (maxFileSize !== '') {
search['maximumFileSize'] = maxFileSize;
}
// limit search to a specific node
if (isClustered) {
var searchLocation = $('#provenance-search-location').combo('getSelectedOption');
if (searchLocation.value !== null) {
search['clusterNodeId'] = searchLocation.value;
}
}
// add the search criteria
search['searchTerms'] = getSearchCriteria();
// reload the table
provenanceTableCtrl.loadProvenanceTable(search);
}
}
},
{
buttonText: 'Cancel',
color: {
base: '#E3E8EB',
hover: '#C7D2D7',
text: '#004849'
},
handler: {
click: function () {
$('#provenance-search-dialog').modal('hide');
}
}
}]
});
return $.ajax({
type: 'GET',
url: config.urls.searchOptions,
dataType: 'json'
}).done(function (response) {
var provenanceOptions = response.provenanceOptions;
// load all searchable fields
$.each(provenanceOptions.searchableFields, function (_, field) {
appendSearchableField(field);
});
});
};
/**
* Initializes the provenance query dialog.
*/
var initProvenanceQueryDialog = function () {
// initialize the dialog
$('#provenance-query-dialog').modal({
scrollableContentStyle: 'scrollable',
headerText: 'Searching provenance events...'
});
};
/**
* Appends the specified searchable field to the search dialog.
*
* @param {type} field The searchable field
*/
var appendSearchableField = function (field) {
var searchableField = $('<div class="searchable-field"></div>').appendTo('#searchable-fields-container');
$('<span class="searchable-field-id hidden"></span>').text(field.id).appendTo(searchableField);
$('<div class="searchable-field-name setting-name"></div>').text(field.label).appendTo(searchableField);
$('<div class="searchable-field-value"><input type="text" class="searchable-field-input"/></div>').appendTo(searchableField);
$('<div class="searchable-checkbox-value nf-checkbox checkbox-unchecked"></div>').appendTo(searchableField);
$('<div class="searchable-checkbox-label nf-checkbox-label">Exclude from search results</div>').appendTo(searchableField);
$('<div class="searchable-checkbox-tooltip fa fa-question-circle" title="Query for all values except what is entered."></div>').appendTo(searchableField);
$('<div class="clear"></div>').appendTo(searchableField);
// make the searchable accessible for populating
if (field.id === 'ProcessorID') {
searchableField.find('input').addClass('searchable-component-id');
} else if (field.id === 'FlowFileUUID') {
searchableField.find('input').addClass('searchable-flowfile-uuid');
}
// ensure the no searchable fields message is hidden
$('#no-searchable-fields').hide();
};
/**
* Gets the search criteria that the user has specified.
*/
var getSearchCriteria = function () {
var searchCriteria = {};
$('#searchable-fields-container').children('div.searchable-field').each(function () {
var searchableField = $(this);
var fieldId = searchableField.children('span.searchable-field-id').text();
var searchValue = $.trim(searchableField.find('input.searchable-field-input').val());
var searchDetails = {};
// if the field isn't blank include it in the search
if (!nfCommon.isBlank(searchValue)) {
searchCriteria[fieldId] = searchDetails;
searchDetails["value"] = searchValue;
var inverse = "inverse";
var searchInverse = searchableField.find('div.searchable-checkbox-value').hasClass('checkbox-checked');
if (searchInverse == true)
{
searchDetails[inverse] = true;
} else {
searchDetails[inverse] = false;
}
}
});
return searchCriteria;
};
/**
* Initializes the provenance table.
*
* @param {boolean} isClustered Whether or not this instance is clustered
*/
var initProvenanceTable = function (isClustered, provenanceTableCtrl) {
// define the function for filtering the list
$('#provenance-filter').keyup(function () {
applyFilter();
});
// filter options
var filterOptions = [{
text: 'by component name',
value: 'componentName'
}, {
text: 'by component type',
value: 'componentType'
}, {
text: 'by type',
value: 'eventType'
}];
// if clustered, allowing filtering by node id
if (isClustered) {
filterOptions.push({
text: 'by node',
value: 'clusterNodeAddress'
});
}
// initialize the filter combo
$('#provenance-filter-type').combo({
options: filterOptions,
select: function (option) {
applyFilter();
}
});
// clear the current search
$('#clear-provenance-search').click(function () {
// clear each searchable field
$('#searchable-fields-container').find('input.searchable-field-input').each(function () {
$(this).val('');
});
// reset the default start date/time
$('#provenance-search-start-date').datepicker('setDate', '+0d');
$('#provenance-search-end-date').datepicker('setDate', '+0d');
$('#provenance-search-start-time').val('00:00:00');
$('#provenance-search-end-time').val('23:59:59');
// reset the minimum and maximum file size
$('#provenance-search-minimum-file-size').val('');
$('#provenance-search-maximum-file-size').val('');
// if we are clustered reset the selected option
if (isClustered) {
$('#provenance-search-location').combo('setSelectedOption', {
text: 'cluster'
});
}
// reset the stored query
cachedQuery = {};
// reload the table
provenanceTableCtrl.loadProvenanceTable();
});
// add hover effect and click handler for opening the dialog
$('#provenance-search-button').click(function () {
$('#provenance-search-dialog').modal('show');
});
// define a custom formatter for the more details column
var moreDetailsFormatter = function (row, cell, value, columnDef, dataContext) {
return '<div title="View Details" class="pointer show-event-details fa fa-info-circle"></div>';
};
// define how general values are formatted
var valueFormatter = function (row, cell, value, columnDef, dataContext) {
return nfCommon.formatValue(value);
};
// determine if the this page is in the shell
var isInShell = (top !== window);
// define how the column is formatted
var showLineageFormatter = function (row, cell, value, columnDef, dataContext) {
var markup = '';
// conditionally include the cluster node id
if (nfCommon.SUPPORTS_SVG) {
markup += '<div title="Show Lineage" class="pointer show-lineage icon icon-lineage"></div>';
}
// conditionally support going to the component
var isRemotePort = dataContext.componentType === 'Remote Input Port' || dataContext.componentType === 'Remote Output Port';
if (isInShell && nfCommon.isDefinedAndNotNull(dataContext.groupId) && isRemotePort === false) {
markup += '<div class="pointer go-to fa fa-long-arrow-right" title="Go To"></div>';
}
return markup;
};
// initialize the provenance table
var provenanceColumns = [
{
id: 'moreDetails',
name: '&nbsp;',
sortable: false,
resizable: false,
formatter: moreDetailsFormatter,
width: 50,
maxWidth: 50
},
{
id: 'eventTime',
name: 'Date/Time',
field: 'eventTime',
sortable: true,
defaultSortAsc: false,
resizable: true,
formatter: nfCommon.genericValueFormatter
},
{
id: 'eventType',
name: 'Type',
field: 'eventType',
sortable: true,
resizable: true,
formatter: nfCommon.genericValueFormatter
},
{
id: 'flowFileUuid',
name: 'FlowFile Uuid',
field: 'flowFileUuid',
sortable: true,
resizable: true,
formatter: nfCommon.genericValueFormatter
},
{
id: 'fileSize',
name: 'Size',
field: 'fileSize',
sortable: true,
defaultSortAsc: false,
resizable: true,
formatter: nfCommon.genericValueFormatter
},
{
id: 'componentName',
name: 'Component Name',
field: 'componentName',
sortable: true,
resizable: true,
formatter: valueFormatter
},
{
id: 'componentType',
name: 'Component Type',
field: 'componentType',
sortable: true,
resizable: true,
formatter: nfCommon.genericValueFormatter
}
];
// conditionally show the cluster node identifier
if (isClustered) {
provenanceColumns.push({
id: 'clusterNodeAddress',
name: 'Node',
field: 'clusterNodeAddress',
sortable: true,
resizable: true,
formatter: nfCommon.genericValueFormatter
});
}
// conditionally show the action column
if (nfCommon.SUPPORTS_SVG || isInShell) {
provenanceColumns.push({
id: 'actions',
name: '&nbsp;',
formatter: showLineageFormatter,
resizable: false,
sortable: false,
width: 50,
maxWidth: 50
});
}
var provenanceOptions = {
forceFitColumns: true,
enableTextSelectionOnCells: true,
enableCellNavigation: true,
enableColumnReorder: false,
autoEdit: false,
multiSelect: false,
rowHeight: 24
};
// create the remote model
var provenanceData = new Slick.Data.DataView({
inlineFilters: false
});
provenanceData.setItems([]);
provenanceData.setFilterArgs({
searchString: '',
property: 'name'
});
provenanceData.setFilter(filter);
// initialize the sort
sort({
columnId: 'eventTime',
sortAsc: false
}, provenanceData);
// initialize the grid
var provenanceGrid = new Slick.Grid('#provenance-table', provenanceData, provenanceColumns, provenanceOptions);
provenanceGrid.setSelectionModel(new Slick.RowSelectionModel());
provenanceGrid.registerPlugin(new Slick.AutoTooltips());
// initialize the grid sorting
provenanceGrid.setSortColumn('eventTime', false);
provenanceGrid.onSort.subscribe(function (e, args) {
sort({
columnId: args.sortCol.field,
sortAsc: args.sortAsc
}, provenanceData);
});
// configure a click listener
provenanceGrid.onClick.subscribe(function (e, args) {
var target = $(e.target);
// get the node at this row
var item = provenanceData.getItem(args.row);
// determine the desired action
if (provenanceGrid.getColumns()[args.cell].id === 'actions') {
if (target.hasClass('show-lineage')) {
provenanceLineageCtrl.showLineage(item.flowFileUuid, item.eventId.toString(), item.clusterNodeId, provenanceTableCtrl);
} else if (target.hasClass('go-to')) {
goTo(item);
}
} else if (provenanceGrid.getColumns()[args.cell].id === 'moreDetails') {
if (target.hasClass('show-event-details')) {
provenanceTableCtrl.showEventDetails(item.eventId, item.clusterNodeId);
}
}
});
// wire up the dataview to the grid
provenanceData.onRowCountChanged.subscribe(function (e, args) {
provenanceGrid.updateRowCount();
provenanceGrid.render();
// update the total number of displayed events if necessary
$('#displayed-events').text(nfCommon.formatInteger(args.current));
});
provenanceData.onRowsChanged.subscribe(function (e, args) {
provenanceGrid.invalidateRows(args.rows);
provenanceGrid.render();
});
// hold onto an instance of the grid
$('#provenance-table').data('gridInstance', provenanceGrid);
// initialize the number of displayed items
$('#displayed-events').text('0');
$('#total-events').text('0');
};
/**
* Applies the filter found in the filter expression text field.
*/
var applyFilter = function () {
// get the dataview
var provenanceGrid = $('#provenance-table').data('gridInstance');
// ensure the grid has been initialized
if (nfCommon.isDefinedAndNotNull(provenanceGrid)) {
var provenanceData = provenanceGrid.getData();
// update the search criteria
provenanceData.setFilterArgs({
searchString: getFilterText(),
property: $('#provenance-filter-type').combo('getSelectedOption').value
});
provenanceData.refresh();
}
};
/**
* Get the text out of the filter field. If the filter field doesn't
* have any text it will contain the text 'filter list' so this method
* accounts for that.
*/
var getFilterText = function () {
return $('#provenance-filter').val();
};
/**
* Performs the provenance filtering.
*
* @param {object} item The item subject to filtering
* @param {object} args Filter arguments
* @returns {Boolean} Whether or not to include the item
*/
var filter = function (item, args) {
if (args.searchString === '') {
return true;
}
try {
// perform the row filtering
var filterExp = new RegExp(args.searchString, 'i');
} catch (e) {
// invalid regex
return false;
}
return item[args.property].search(filterExp) >= 0;
};
/**
* Sorts the data according to the sort details.
*
* @param {type} sortDetails
* @param {type} data
*/
var sort = function (sortDetails, data) {
// defines a function for sorting
var comparer = function (a, b) {
if (sortDetails.columnId === 'eventTime') {
var aTime = nfCommon.parseDateTime(a[sortDetails.columnId]).getTime();
var bTime = nfCommon.parseDateTime(b[sortDetails.columnId]).getTime();
if (aTime === bTime) {
return a['id'] - b['id'];
} else {
return aTime - bTime;
}
} else if (sortDetails.columnId === 'fileSize') {
var aSize = nfCommon.parseSize(a[sortDetails.columnId]);
var bSize = nfCommon.parseSize(b[sortDetails.columnId]);
if (aSize === bSize) {
return a['id'] - b['id'];
} else {
return aSize - bSize;
}
} else {
var aString = nfCommon.isDefinedAndNotNull(a[sortDetails.columnId]) ? a[sortDetails.columnId] : '';
var bString = nfCommon.isDefinedAndNotNull(b[sortDetails.columnId]) ? b[sortDetails.columnId] : '';
if (aString === bString) {
return a['id'] - b['id'];
} else {
return aString === bString ? 0 : aString > bString ? 1 : -1;
}
}
};
// perform the sort
data.sort(comparer, sortDetails.sortAsc);
};
/**
* Submits a new provenance query.
*
* @argument {object} provenance The provenance query
* @returns {deferred}
*/
var submitProvenance = function (provenance) {
var provenanceEntity = {
'provenance': {
'request': $.extend({
maxResults: config.maxResults,
summarize: true,
incrementalResults: false
}, provenance)
}
};
// submit the provenance request
return $.ajax({
type: 'POST',
url: config.urls.provenance,
data: JSON.stringify(provenanceEntity),
dataType: 'json',
contentType: 'application/json'
}).fail(nfErrorHandler.handleAjaxError);
};
/**
* Gets the results from the provenance query for the specified id.
*
* @param {object} provenance
* @returns {deferred}
*/
var getProvenance = function (provenance) {
var url = provenance.uri;
if (nfCommon.isDefinedAndNotNull(provenance.request.clusterNodeId)) {
url += '?' + $.param({
clusterNodeId: provenance.request.clusterNodeId,
summarize: true,
incrementalResults: false
});
} else {
url += '?' + $.param({
summarize: true,
incrementalResults: false
});
}
return $.ajax({
type: 'GET',
url: url,
dataType: 'json'
}).fail(nfErrorHandler.handleAjaxError);
};
/**
* Cancels the specified provenance query.
*
* @param {object} provenance
* @return {deferred}
*/
var cancelProvenance = function (provenance) {
var url = provenance.uri;
if (nfCommon.isDefinedAndNotNull(provenance.request.clusterNodeId)) {
url += '?' + $.param({
clusterNodeId: provenance.request.clusterNodeId
});
}
return $.ajax({
type: 'DELETE',
url: url,
dataType: 'json'
}).fail(nfErrorHandler.handleAjaxError);
};
/**
* Checks the results of the specified provenance.
*
* @param {object} provenance
*/
var loadProvenanceResults = function (provenance, provenanceTableCtrl) {
var provenanceRequest = provenance.request;
var provenanceResults = provenance.results;
// ensure there are groups specified
if (nfCommon.isDefinedAndNotNull(provenanceResults.provenanceEvents)) {
var provenanceTable = $('#provenance-table').data('gridInstance');
var provenanceData = provenanceTable.getData();
// set the items
provenanceData.setItems(provenanceResults.provenanceEvents);
provenanceData.reSort();
provenanceTable.invalidate();
// update the stats last refreshed timestamp
$('#provenance-last-refreshed').text(provenanceResults.generated);
// update the oldest event available
$('#oldest-event').html(nfCommon.formatValue(provenanceResults.oldestEvent));
// record the server offset
provenanceTableCtrl.serverTimeOffset = provenanceResults.timeOffset;
// determines if the specified query is blank (no search terms, start or end date)
var isBlankQuery = function (query) {
return nfCommon.isUndefinedOrNull(query.startDate) && nfCommon.isUndefinedOrNull(query.endDate) && $.isEmptyObject(query.searchTerms);
};
// update the filter message based on the request
if (isBlankQuery(provenanceRequest)) {
var message = 'Showing the most recent ';
if (provenanceResults.totalCount >= config.maxResults) {
message += (nfCommon.formatInteger(config.maxResults) + ' of ' + provenanceResults.total + ' events, please refine the search.');
} else {
message += ('events.');
}
$('#provenance-query-message').text(message);
$('#clear-provenance-search').hide();
} else {
var message = 'Showing ';
if (provenanceResults.totalCount >= config.maxResults) {
message += (nfCommon.formatInteger(config.maxResults) + ' of ' + provenanceResults.total + ' events that match the specified query, please refine the search.');
} else {
message += ('the events that match the specified query.');
}
$('#provenance-query-message').text(message);
$('#clear-provenance-search').show();
}
// update the total number of events
$('#total-events').text(nfCommon.formatInteger(provenanceResults.provenanceEvents.length));
} else {
$('#total-events').text('0');
}
};
/**
* Goes to the specified component if possible.
*
* @argument {object} item The event it
*/
var goTo = function (item) {
// ensure the component is still present in the flow
if (nfCommon.isDefinedAndNotNull(item.groupId)) {
// only attempt this if we're within a frame
if (top !== window) {
// and our parent has canvas utils and shell defined
if (nfCommon.isDefinedAndNotNull(parent.nf) && nfCommon.isDefinedAndNotNull(parent.nf.CanvasUtils) && nfCommon.isDefinedAndNotNull(parent.nf.Shell)) {
parent.nf.CanvasUtils.showComponent(item.groupId, item.componentId);
parent.$('#shell-close-button').click();
}
}
}
};
function ProvenanceTableCtrl() {
/**
* The server time offset
*/
this.serverTimeOffset = null;
}
ProvenanceTableCtrl.prototype = {
constructor: ProvenanceTableCtrl,
/**
* Initializes the provenance table. Returns a deferred that will indicate when/if the table has initialized successfully.
*
* @param {boolean} isClustered Whether or not this instance is clustered
*/
init: function (isClustered) {
var provenanceTableCtrl = this;
return $.Deferred(function (deferred) {
// handles init failure
var failure = function (xhr, status, error) {
deferred.reject();
nfErrorHandler.handleAjaxError(xhr, status, error);
};
// initialize the lineage view
provenanceLineageCtrl.init();
// initialize the table view
initDetailsDialog();
initProvenanceQueryDialog();
initProvenanceTable(isClustered, provenanceTableCtrl);
initSearchDialog(isClustered, provenanceTableCtrl).done(function () {
deferred.resolve();
}).fail(failure);
}).promise();
},
/**
* Update the size of the grid based on its container's current size.
*/
resetTableSize: function () {
var provenanceGrid = $('#provenance-table').data('gridInstance');
if (nfCommon.isDefinedAndNotNull(provenanceGrid)) {
provenanceGrid.resizeCanvas();
}
},
/**
* Updates the value of the specified progress bar.
*
* @param {jQuery} progressBar
* @param {integer} value
* @returns {undefined}
*/
updateProgress: function (progressBar, value) {
// remove existing labels
progressBar.find('div.progress-label').remove();
progressBar.find('md-progress-linear').remove();
// update the progress bar
var label = $('<div class="progress-label"></div>').text(value + '%');
(nfNgBridge.injector.get('$compile')($('<md-progress-linear ng-cloak ng-value="' + value + '" class="md-hue-2" md-mode="determinate" aria-label="Progress"></md-progress-linear>'))(nfNgBridge.rootScope)).appendTo(progressBar);
progressBar.append(label);
},
/**
* Loads the provenance table with events according to the specified optional
* query. If not query is specified or it is empty, the most recent entries will
* be returned.
*
* @param {object} query
*/
loadProvenanceTable: function (query) {
var provenanceTableCtrl = this;
var provenanceProgress = $('#provenance-percent-complete');
// add support to cancel outstanding requests - when the button is pressed we
// could be in one of two stages, 1) waiting to GET the status or 2)
// in the process of GETting the status. Handle both cases by cancelling
// the setTimeout (1) and by setting a flag to indicate that a request has
// been request so we can ignore the results (2).
var cancelled = false;
var provenance = null;
var provenanceTimer = null;
// update the progress bar value
provenanceTableCtrl.updateProgress(provenanceProgress, 0);
// show the 'searching...' dialog
$('#provenance-query-dialog').modal('setButtonModel', [{
buttonText: 'Cancel',
color: {
base: '#E3E8EB',
hover: '#C7D2D7',
text: '#004849'
},
handler: {
click: function () {
cancelled = true;
// we are waiting for the next poll attempt
if (provenanceTimer !== null) {
// cancel it
clearTimeout(provenanceTimer);
// cancel the provenance
closeDialog();
}
}
}
}]).modal('show');
// -----------------------------
// determine the provenance query
// -----------------------------
// handle the specified query appropriately
if (nfCommon.isDefinedAndNotNull(query)) {
// store the last query performed
cachedQuery = query;
} else if (!$.isEmptyObject(cachedQuery)) {
// use the last query performed
query = cachedQuery;
} else {
// don't use a query
query = {};
}
// closes the searching dialog and cancels the query on the server
var closeDialog = function () {
// cancel the provenance results since we've successfully processed the results
if (nfCommon.isDefinedAndNotNull(provenance)) {
cancelProvenance(provenance);
}
// close the dialog
$('#provenance-query-dialog').modal('hide');
};
// polls the server for the status of the provenance
var pollProvenance = function () {
getProvenance(provenance).done(function (response) {
// update the provenance
provenance = response.provenance;
// process the provenance
processProvenanceResponse();
}).fail(closeDialog);
};
// processes the provenance
var processProvenanceResponse = function () {
// if the request was cancelled just ignore the current response
if (cancelled === true) {
closeDialog();
return;
}
// update the percent complete
provenanceTableCtrl.updateProgress(provenanceProgress, provenance.percentCompleted);
// process the results if they are finished
if (provenance.finished === true) {
// show any errors when the query finishes
if (!nfCommon.isEmpty(provenance.results.errors)) {
var errors = provenance.results.errors;
nfDialog.showOkDialog({
headerText: 'Provenance',
dialogContent: nfCommon.formatUnorderedList(errors),
});
}
// process the results
loadProvenanceResults(provenance, provenanceTableCtrl);
// hide the dialog
closeDialog();
} else {
// start the wait to poll again
provenanceTimer = setTimeout(function () {
// clear the timer since we've been invoked
provenanceTimer = null;
// poll provenance
pollProvenance();
}, 2000);
}
};
// once the query is submitted wait until its finished
submitProvenance(query).done(function (response) {
// update the provenance
provenance = response.provenance;
// process the results, if they are not done wait 1 second before trying again
processProvenanceResponse();
}).fail(closeDialog);
},
/**
* Gets the details for the specified event.
*
* @param {string} eventId
* @param {string} clusterNodeId The id of the node in the cluster where this event/flowfile originated
*/
getEventDetails: function (eventId, clusterNodeId) {
var url;
if (nfCommon.isDefinedAndNotNull(clusterNodeId)) {
url = config.urls.provenanceEvents + encodeURIComponent(eventId) + '?' + $.param({
clusterNodeId: clusterNodeId
});
} else {
url = config.urls.provenanceEvents + encodeURIComponent(eventId);
}
return $.ajax({
type: 'GET',
url: url,
dataType: 'json'
}).fail(nfErrorHandler.handleAjaxError);
},
/**
* Shows the details for the specified action.
*
* @param {string} eventId
* @param {string} clusterNodeId The id of the node in the cluster where this event/flowfile originated
*/
showEventDetails: function (eventId, clusterNodeId) {
provenanceTableCtrl.getEventDetails(eventId, clusterNodeId).done(function (response) {
var event = response.provenanceEvent;
// Hide or show dialog tabs as required if base properties are defined
var tabs = $('#event-details-tabs').find("li");
$(tabs).each(function(index) {
if ((event["attributes"] === undefined && index == 1) ||
(event["inputContentAvailable"] === undefined && index ==2)) {
$(this).hide();
} else {
$(this).show();
}
});
// ensure the details are selected in case other tabs we're previously selected and have been hidden
$(tabs).first().click();
// update the event details
$('#provenance-event-id').text(event.eventId);
$('#provenance-event-time').html(nfCommon.formatValue(event.eventTime)).ellipsis();
$('#provenance-event-type').html(nfCommon.formatValue(event.eventType)).ellipsis();
$('#provenance-event-flowfile-uuid').html(nfCommon.formatValue(event.flowFileUuid)).ellipsis();
$('#provenance-event-component-id').html(nfCommon.formatValue(event.componentId)).ellipsis();
$('#provenance-event-component-name').html(nfCommon.formatValue(event.componentName)).ellipsis();
$('#provenance-event-component-type').html(nfCommon.formatValue(event.componentType)).ellipsis();
$('#provenance-event-details').html(nfCommon.formatValue(event.details)).ellipsis();
// over the default tooltip with the actual byte count
var fileSize = $('#provenance-event-file-size').html(nfCommon.formatValue(event.fileSize)).ellipsis();
fileSize.attr('title', nfCommon.formatInteger(event.fileSizeBytes) + ' bytes');
// sets an duration
var setDuration = function (field, value) {
if (nfCommon.isDefinedAndNotNull(value)) {
if (value === 0) {
field.text('< 1ms');
} else {
field.text(nfCommon.formatDuration(value));
}
} else {
field.html('<span class="unset">No value set</span>');
}
};
// handle durations
setDuration($('#provenance-event-duration'), event.eventDuration);
setDuration($('#provenance-lineage-duration'), event.lineageDuration);
// formats an event detail
var formatEventDetail = function (label, value) {
$('<div class="event-detail"></div>').append(
$('<div class="detail-name"></div>').text(label)).append(
$('<div class="detail-value">' + nfCommon.formatValue(value) + '</div>').ellipsis()).append(
$('<div class="clear"></div>')).appendTo('#additional-provenance-details');
};
// conditionally show RECEIVE details
if (event.eventType === 'RECEIVE') {
formatEventDetail('Source FlowFile Id', event.sourceSystemFlowFileId);
formatEventDetail('Transit Uri', event.transitUri);
}
// conditionally show SEND details
if (event.eventType === 'SEND') {
formatEventDetail('Transit Uri', event.transitUri);
}
// conditionally show REMOTE_INVOCATION details
if (event.eventType === 'REMOTE_INVOCATION') {
formatEventDetail('Transit Uri', event.transitUri);
}
// conditionally show ADDINFO details
if (event.eventType === 'ADDINFO') {
formatEventDetail('Alternate Identifier Uri', event.alternateIdentifierUri);
}
// conditionally show ROUTE details
if (event.eventType === 'ROUTE') {
formatEventDetail('Relationship', event.relationship);
}
// conditionally show FETCH details
if (event.eventType === 'FETCH') {
formatEventDetail('Transit Uri', event.transitUri);
}
// conditionally show the cluster node identifier
if (nfCommon.isDefinedAndNotNull(event.clusterNodeId)) {
// save the cluster node id
$('#provenance-event-cluster-node-id').text(event.clusterNodeId);
// render the cluster node address
formatEventDetail('Node Address', event.clusterNodeAddress);
}
// populate the parent/child flowfile uuids
var parentUuids = $('#parent-flowfiles-container');
var childUuids = $('#child-flowfiles-container');
// handle parent flowfiles
if (nfCommon.isEmpty(event.parentUuids)) {
$('#parent-flowfile-count').text(0);
parentUuids.append('<span class="unset">No parents</span>');
} else {
$('#parent-flowfile-count').text(event.parentUuids.length);
$.each(event.parentUuids, function (_, uuid) {
$('<div></div>').text(uuid).appendTo(parentUuids);
});
}
// handle child flowfiles
if (nfCommon.isEmpty(event.childUuids)) {
$('#child-flowfile-count').text(0);
childUuids.append('<span class="unset">No children</span>');
} else {
$('#child-flowfile-count').text(event.childUuids.length);
$.each(event.childUuids, function (_, uuid) {
$('<div></div>').text(uuid).appendTo(childUuids);
});
}
// get the attributes container
var attributesContainer = $('#attributes-container');
// get any action details
$.each(event.attributes, function (_, attribute) {
// create the attribute record
var attributeRecord = $('<div class="attribute-detail"></div>')
.append($('<div class="attribute-name">' + nfCommon.formatValue(attribute.name) + '</div>').ellipsis())
.appendTo(attributesContainer);
// add the current value
attributeRecord
.append($('<div class="attribute-value">' + nfCommon.formatValue(attribute.value) + '</div>').ellipsis())
.append('<div class="clear"></div>');
// show the previous value if the property has changed
if (attribute.value !== attribute.previousValue) {
if (nfCommon.isDefinedAndNotNull(attribute.previousValue)) {
attributeRecord
.append($('<div class="modified-attribute-value">' + nfCommon.formatValue(attribute.previousValue) + '<span class="unset"> (previous)</span></div>').ellipsis())
.append('<div class="clear"></div>');
} else {
attributeRecord
.append($('<div class="unset" style="font-size: 13px; padding-top: 2px;">' + nfCommon.formatValue(attribute.previousValue) + '</div>').ellipsis())
.append('<div class="clear"></div>');
}
} else {
// mark this attribute as not modified
attributeRecord.addClass('attribute-unmodified');
}
});
var formatContentValue = function (element, value) {
if (nfCommon.isDefinedAndNotNull(value)) {
element.removeClass('unset').text(value);
} else {
element.addClass('unset').text('No value previously set');
}
};
// content
$('#input-content-header').text('Input Claim');
formatContentValue($('#input-content-container'), event.inputContentClaimContainer);
formatContentValue($('#input-content-section'), event.inputContentClaimSection);
formatContentValue($('#input-content-identifier'), event.inputContentClaimIdentifier);
formatContentValue($('#input-content-offset'), event.inputContentClaimOffset);
formatContentValue($('#input-content-bytes'), event.inputContentClaimFileSizeBytes);
// input content file size
var inputContentSize = $('#input-content-size');
formatContentValue(inputContentSize, event.inputContentClaimFileSize);
if (nfCommon.isDefinedAndNotNull(event.inputContentClaimFileSize)) {
// over the default tooltip with the actual byte count
inputContentSize.attr('title', nfCommon.formatInteger(event.inputContentClaimFileSizeBytes) + ' bytes');
}
formatContentValue($('#output-content-container'), event.outputContentClaimContainer);
formatContentValue($('#output-content-section'), event.outputContentClaimSection);
formatContentValue($('#output-content-identifier'), event.outputContentClaimIdentifier);
formatContentValue($('#output-content-offset'), event.outputContentClaimOffset);
formatContentValue($('#output-content-bytes'), event.outputContentClaimFileSizeBytes);
// output content file size
var outputContentSize = $('#output-content-size');
formatContentValue(outputContentSize, event.outputContentClaimFileSize);
if (nfCommon.isDefinedAndNotNull(event.outputContentClaimFileSize)) {
// over the default tooltip with the actual byte count
outputContentSize.attr('title', nfCommon.formatInteger(event.outputContentClaimFileSizeBytes) + ' bytes');
}
if (event.inputContentAvailable === true) {
$('#input-content-download').show();
if (nfCommon.isContentViewConfigured()) {
$('#input-content-view').show();
} else {
$('#input-content-view').hide();
}
} else {
$('#input-content-download').hide();
$('#input-content-view').hide();
}
if (event.outputContentAvailable === true) {
$('#output-content-download').show();
if (nfCommon.isContentViewConfigured()) {
$('#output-content-view').show();
} else {
$('#output-content-view').hide();
}
} else {
$('#output-content-download').hide();
$('#output-content-view').hide();
}
if (event.replayAvailable === true) {
$('#replay-content, #replay-content-connection').show();
formatContentValue($('#replay-connection-id'), event.sourceConnectionIdentifier);
$('#replay-content-message').hide();
} else {
$('#replay-content, #replay-content-connection').hide();
$('#replay-content-message').text(event.replayExplanation).show();
}
// show the dialog
$('#event-details-dialog').modal('show');
});
}
}
var provenanceTableCtrl = new ProvenanceTableCtrl();
return provenanceTableCtrl;
};
return nfProvenanceTable;
}));