You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by sc...@apache.org on 2019/08/30 17:54:10 UTC

[nifi] 03/09: NIFI-6346: - Updating nfel to support referencing parameters.

This is an automated email from the ASF dual-hosted git repository.

scottyaslan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nifi.git

commit d93ae47afcdea8df6982f9d093830caa607a833c
Author: Matt Gilman <ma...@gmail.com>
AuthorDate: Tue Jun 4 15:04:42 2019 -0400

    NIFI-6346:
    - Updating nfel to support referencing parameters.
---
 .../webapp/js/jquery/nfeditor/languages/nfel.js    | 303 ++++++++++++++++-----
 .../webapp/js/jquery/nfeditor/languages/nfpr.js    |   6 +-
 .../jquery/propertytable/jquery.propertytable.js   |  28 ++
 3 files changed, 265 insertions(+), 72 deletions(-)

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 0cf813c..7bcb7e3 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 '<span class="unset">None</span>';
-        } else {
-            var formatted = '<div class="clear"></div><ul class="el-arguments">';
-            $.each(args, function(key, value) {
-                formatted += (
-                    '<li>' +
-                        '<span class="el-argument-name">' + key + '</span> - ' +
-                        value +
-                    '</li>'
-                );
-            });
-            formatted += '</ul>';
-            return formatted;
+    var formatDetails = function (details) {
+        var detailsContainer = $('<div></div>');
+
+        // add the detail name
+        $('<div class="el-name el-section"></div>').text(details.name).appendTo(detailsContainer);
+
+        // add the detail description
+        $('<div class="el-section"></div>').text(details.description).appendTo(detailsContainer);
+
+        // add the function arguments
+        if (typeof details.args !== 'undefined') {
+            var argumentsContainer = $('<div class="el-section"></div>').appendTo(detailsContainer);
+            $('<div class="el-header">Arguments</div>').appendTo(argumentsContainer);
+
+            if ($.isEmptyObject(details.args)) {
+                $('<span class="unset">None</span>').appendTo(argumentsContainer);
+            } else {
+                $('<div class="clear"></div>').appendTo(argumentsContainer);
+
+                // add the argument
+                var argumentContainer = $('<ul class="el-arguments"></ul>').appendTo(argumentsContainer);
+                $.each(details.args, function (key, value) {
+                    var argName = $('<span class="el-argument-name"></span>').text(key);
+                    var argDescription = $('<span></span>').text(value);
+                    $('<li></li>').append(argName).append(' - ').append(argDescription).appendTo(argumentContainer);
+                });
+            }
+        }
+
+        // add the function subject
+        if (typeof details.subject !== 'undefined') {
+            var subjectContainer = $('<div class="el-section"></div>').appendTo(detailsContainer);
+            $('<div class="el-header">Subject</div>').appendTo(subjectContainer);
+            $('<p></p>').text(details.subject).appendTo(subjectContainer);
+            $('<div class="clear"></div>').appendTo(subjectContainer);
+        }
+
+        // add the function return type
+        if (typeof details.returnType !== 'undefined') {
+            var returnTypeContainer = $('<div class="el-section"></div>').appendTo(detailsContainer);
+            $('<div class="el-header">Returns</div>').appendTo(returnTypeContainer);
+            $('<p></p>').text(details.returnType).appendTo(returnTypeContainer);
+            $('<div class="clear"></div>').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 = '<span class="unset">None</span>';
+                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] =
-                '<div>' +
-                    '<div class="el-name el-section">' + name + '</div>' +
-                    '<div class="el-section">' + description + '</div>' +
-                    '<div class="el-section">' +
-                        '<div class="el-header">Arguments</div>' +
-                        formatArguments(args) +
-                    '</div>' +
-                    '<div class="el-section">' +
-                        '<div class="el-header">Subject</div>' +
-                        '<p>' + subject + '</p>' +
-                        '<div class="clear"></div>' +
-                    '</div>' +
-                    '<div class="el-section">' +
-                        '<div class="el-header">Returns</div>' +
-                        '<p>' + returnType + '</p>' +
-                        '<div class="clear"></div>' +
-                    '</div>' +
-                '</div>';
+            // 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 = $('<div></div>').text(data.text);
         var li = $(element).qtip({
-            content: functionDetails[data.text],
+            content: formatDetails(data.details),
             style: {
                 classes: 'nifi-tooltip nf-tooltip',
                 tip: false,
@@ -347,6 +374,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.
          */
         color: function () {
@@ -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 23e5af4..ef9c8d7 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 ce5dea5..b8a79d9 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;
                             })
                         }