diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/nfeditor/languages/nfel.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/nfeditor/languages/nfel.js index 0cf813cb87..7bcb7e3618 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/nfeditor/languages/nfel.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/nfeditor/languages/nfel.js @@ -38,29 +38,66 @@ 'use strict'; /** - * Formats the specified arguments for the EL function tooltip. + * Formats the specified function definition. * - * @param {type} args - * @returns {String} + * @param details + * @returns {jQuery|HTMLElement} */ - var formatArguments = function(args) { - if ($.isEmptyObject(args)) { - return 'None'; - } else { - var formatted = '
'; - return formatted; + var formatDetails = function (details) { + var detailsContainer = $('
'); + + // add the detail name + $('
').text(details.name).appendTo(detailsContainer); + + // add the detail description + $('
').text(details.description).appendTo(detailsContainer); + + // add the function arguments + if (typeof details.args !== 'undefined') { + var argumentsContainer = $('
').appendTo(detailsContainer); + $('
Arguments
').appendTo(argumentsContainer); + + if ($.isEmptyObject(details.args)) { + $('None').appendTo(argumentsContainer); + } else { + $('
').appendTo(argumentsContainer); + + // add the argument + var argumentContainer = $('').appendTo(argumentsContainer); + $.each(details.args, function (key, value) { + var argName = $('').text(key); + var argDescription = $('').text(value); + $('
  • ').append(argName).append(' - ').append(argDescription).appendTo(argumentContainer); + }); + } } + + // add the function subject + if (typeof details.subject !== 'undefined') { + var subjectContainer = $('
    ').appendTo(detailsContainer); + $('
    Subject
    ').appendTo(subjectContainer); + $('

    ').text(details.subject).appendTo(subjectContainer); + $('
    ').appendTo(subjectContainer); + } + + // add the function return type + if (typeof details.returnType !== 'undefined') { + var returnTypeContainer = $('
    ').appendTo(detailsContainer); + $('
    Returns
    ').appendTo(returnTypeContainer); + $('

    ').text(details.returnType).appendTo(returnTypeContainer); + $('
    ').appendTo(returnTypeContainer); + } + + return detailsContainer; }; + var parameterKeyRegex = /^[a-zA-Z0-9-_. ]+/; + + var parameters = []; + var parameterRegex = new RegExp('^$'); + + var parameterDetails = {}; + var subjectlessFunctions = []; var functions = []; @@ -88,7 +125,7 @@ // Determine if this function supports running subjectless if (subjectless.length) { subjectlessFunctions.push(name); - subject = 'None'; + subject = 'None'; } // Determine if this function supports running with a subject @@ -105,26 +142,14 @@ args[argName.text()] = argDescription.text(); }); - // format the function tooltip - functionDetails[name] = - '
    ' + - '
    ' + name + '
    ' + - '
    ' + description + '
    ' + - '
    ' + - '
    Arguments
    ' + - formatArguments(args) + - '
    ' + - '
    ' + - '
    Subject
    ' + - '

    ' + subject + '

    ' + - '
    ' + - '
    ' + - '
    ' + - '
    Returns
    ' + - '

    ' + returnType + '

    ' + - '
    ' + - '
    ' + - '
    '; + // record the function details + functionDetails[name] = { + name: name, + description: description, + args: args, + subject: subject, + returnType: returnType + }; }); }).always(function() { // build the regex for all functions discovered @@ -139,37 +164,40 @@ var EXPRESSION = 'expression'; var ARGUMENTS = 'arguments'; var ARGUMENT = 'argument'; + var PARAMETER = 'parameter'; var INVALID = 'invalid'; /** * Handles dollars identifies on the stream. * - * @param {object} stream The character stream - * @param {object} states The states + * @param {string} startChar The start character + * @param {string} context The context to transition to if we match on the specified start character + * @param {object} stream The character stream + * @param {object} states The states */ - var handleDollar = function (stream, states) { - // determine the number of sequential dollars - var dollarCount = 0; + var handleStart = function (startChar, context, stream, states) { + // determine the number of sequential start chars + var startCharCount = 0; stream.eatWhile(function (ch) { - if (ch === '$') { - dollarCount++; + if (ch === startChar) { + startCharCount++; return true; } return false; }); - // if there is an even number of consecutive dollars this expression is escaped - if (dollarCount % 2 === 0) { + // if there is an even number of consecutive start chars this expression is escaped + if (startCharCount % 2 === 0) { // do not style an escaped expression return null; } - // if there was an odd number of consecutive dollars and there was more than 1 - if (dollarCount > 1) { + // if there was an odd number of consecutive start chars and there was more than 1 + if (startCharCount > 1) { // back up one char so we can process the start sequence next iteration stream.backUp(1); - // do not style the preceeding dollars + // do not style the preceding start chars return null; } @@ -180,17 +208,16 @@ // new expression start states.push({ - context: EXPRESSION + context: context }); // consume any addition whitespace stream.eatSpace(); return 'bracket'; - } else { - // not a valid start sequence - return null; } + // not a valid start sequence + return null; }; /** @@ -317,7 +344,7 @@ var renderer = function(element, self, data) { var item = $('
    ').text(data.text); var li = $(element).qtip({ - content: functionDetails[data.text], + content: formatDetails(data.details), style: { classes: 'nifi-tooltip nf-tooltip', tip: false, @@ -346,6 +373,23 @@ return { + /** + * Sets the available parameters. + * + * @param parameterListing + */ + setParameters: function (parameterListing) { + parameters = []; + parameterDetails = {}; + + parameterListing.forEach(function (parameter) { + parameters.push(parameter.name); + parameterDetails[parameter.name] = parameter; + }); + + parameterRegex = new RegExp('^((' + parameters.join(')|(') + '))$'); + }, + /** * Returns an object that provides syntax highlighting for NiFi expression language. */ @@ -410,8 +454,17 @@ // if we've hit some comments... will consume the remainder of the line if (current === '#') { - stream.skipToEnd(); - return 'comment'; + // consume the pound + stream.next(); + + var afterPound = stream.peek(); + if (afterPound !== '{') { + stream.skipToEnd(); + return 'comment'; + } else { + // unconsume the pound + stream.backUp(1); + } } // get the current state @@ -477,7 +530,7 @@ // nested expression // ----------------- - var expressionDollarResult = handleDollar(stream, states); + var expressionDollarResult = handleStart('$', EXPRESSION, stream, states); // if we've found an embedded expression we need to... if (expressionDollarResult !== null) { @@ -486,6 +539,21 @@ } return expressionDollarResult; + } else if (current === '#') { + // -------------------------- + // nested parameter reference + // -------------------------- + + // handle the nested parameter reference + var parameterReferenceResult = handleStart('#', PARAMETER, stream, states); + + // if we've found an embedded parameter reference we need to... + if (parameterReferenceResult !== null) { + // transition back to subject when this parameter reference completes + state.context = SUBJECT; + } + + return parameterReferenceResult; } else if (current === '}') { // ----------------- // end of expression @@ -737,7 +805,7 @@ // ----------------- // handle the nested expression - var argumentDollarResult = handleDollar(stream, states); + var argumentDollarResult = handleStart('$', EXPRESSION, stream, states); // if we've found an embedded expression we need to... if (argumentDollarResult !== null) { @@ -746,6 +814,82 @@ } return argumentDollarResult; + } else if (current === '#') { + // -------------------------- + // nested parameter reference + // -------------------------- + + // handle the nested parameter reference + var parameterReferenceResult = handleStart('#', PARAMETER, stream, states); + + // if we've found an embedded parameter reference we need to... + if (parameterReferenceResult !== null) { + // transition back to arguments when this parameter reference completes + state.context = ARGUMENTS; + } + + return parameterReferenceResult; + } else { + // ---------- + // unexpected + // ---------- + + // consume and move along + stream.skipToEnd(); + state.context = INVALID; + + // unexpected... + return null; + } + } + + // within a parameter reference + if (state.context === PARAMETER) { + // attempt to extract a parameter name + var parameterName = stream.match(parameterKeyRegex, false); + + // if the result returned a match + if (parameterName !== null && parameterName.length === 1) { + // consume the entire token to ensure the whole function + // name is matched. this is an issue with functions like + // substring and substringAfter since 'substringA' would + // match the former and when we really want to autocomplete + // against the latter. + stream.match(parameterKeyRegex); + + // see if this matches a known function and is followed by ( + if (parameterRegex.test(parameterName)) { + // ------------------ + // resolved parameter + // ------------------ + + // style for function + return 'builtin'; + } else { + // -------------------- + // unresolved parameter + // -------------------- + + // style for function + return 'string'; + } + } + + if (current === '}') { + // ----------------- + // end of expression + // ----------------- + + // consume the close + stream.next(); + + // signifies the end of an parameter reference + if (typeof states.pop() === 'undefined') { + return null; + } else { + // style as expression + return 'bracket'; + } } else { // ---------- // unexpected @@ -762,7 +906,12 @@ // signifies the potential start of an expression if (current === '$') { - return handleDollar(stream, states); + return handleStart('$', EXPRESSION, stream, states); + } + + // signifies the potential start of a parameter reference + if (current === '#') { + return handleStart('#', PARAMETER, stream, states); } // signifies the end of an expression @@ -810,9 +959,15 @@ return context === EXPRESSION || context === SUBJECT_OR_FUNCTION; }; + // whether or not the current context is within a parameter reference + var isParameterReference = function (context) { + // attempting to match a function name or already successfully matched a function name + return context === PARAMETER; + }; + // only support suggestion in certain cases var context = state.context; - if (!isSubjectlessFunction(context) && !isFunction(context)) { + if (!isSubjectlessFunction(context) && !isFunction(context) && !isParameterReference(context)) { return null; } @@ -823,19 +978,29 @@ var trimmed = $.trim(value); // identify potential patterns and increment the start location appropriately - if (trimmed === '${' || trimmed === ':') { + if (trimmed === '${' || trimmed === ':' || trimmed === '#{') { includeAll = true; token.start += value.length; } - var getCompletions = function(functions) { + var options = functions; + var useFunctionDetails = true; + if (isSubjectlessFunction(context)) { + options = subjectlessFunctions; + } else if (isParameterReference(context)) { + options = parameters; + useFunctionDetails = false; + } + + var getCompletions = function(options) { var found = []; - $.each(functions, function (i, funct) { - if ($.inArray(funct, found) === -1) { - if (includeAll || funct.toLowerCase().indexOf(value) === 0) { + $.each(options, function (i, opt) { + if ($.inArray(opt, found) === -1) { + if (includeAll || opt.toLowerCase().indexOf(value) === 0) { found.push({ - text: funct, + text: opt, + details: useFunctionDetails ? functionDetails[opt] : parameterDetails[opt], render: renderer }); } @@ -846,7 +1011,7 @@ }; // get the suggestions for the current context - var completionList = getCompletions(isSubjectlessFunction(context) ? subjectlessFunctions : functions); + var completionList = getCompletions(options); completionList = completionList.sort(function (a, b) { var aLower = a.text.toLowerCase(); var bLower = b.text.toLowerCase(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/nfeditor/languages/nfpr.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/nfeditor/languages/nfpr.js index 23e5af4721..ef9c8d795a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/nfeditor/languages/nfpr.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/nfeditor/languages/nfpr.js @@ -57,6 +57,8 @@ return parameterContainer; }; + var parameterKeyRegex = /^[a-zA-Z0-9-_. ]+/; + var parameters = []; var parameterRegex = new RegExp('^$'); @@ -311,10 +313,8 @@ return null; } - // within a function + // within a parameter reference if (state.context === PARAMETER) { - var parameterKeyRegex = /^[a-zA-Z0-9-_. ]+/; - // attempt to extract a parameter name var parameterName = stream.match(parameterKeyRegex, false); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js index ce5dea58d1..b8a79d9f08 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js @@ -1257,6 +1257,34 @@ columns: { value: { editor: getNfEditor(function (propertyDescriptor) { + // set the available parameters + // TODO - obtain actual parameters and filter accordingly to sensitivity + nf.nfel.setParameters([ + { + name: 'param 1', + sensitive: false, + description: 'this is the description for param 1', + value: 'value 1' + }, + { + name: 'param 2', + sensitive: true, + description: 'this is the description for param 2', + value: 'value 2' + }, + { + name: 'param 3', + sensitive: false, + value: 'value 3' + }, + { + name: 'param 4', + sensitive: false, + description: 'this is the description for param 4', + value: 'value 4' + } + ]); + return nf.nfel; }) }