Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timelion typeahead for argument values #14801

Merged
merged 10 commits into from
Nov 14, 2017

Conversation

nreese
Copy link
Contributor

@nreese nreese commented Nov 6, 2017

Adds typeahead support for timelion argument values, #9730

type_ahead

@cjcenizal
Copy link
Contributor

I think the bug causing the functional test failures has been fixed in master.

Copy link
Contributor

@cjcenizal cjcenizal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, nice work man! This is a solid improvement.

I haven't finished going through everything but I had a couple of more fundamental changes I wanted to share with you before I went any further. Love to hear your thoughts.

const message = JSON.parse(e.message);
const location = message.location;

if (message.type === 'incompleteFunction') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be a switch/case now?

}

return help.suggestions;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was having a hard time following the logic around the conditions within this function. I think we could make this a little easier to follow if we split this up into three functions, and made the consuming code responsible for handling the condition. This will probably be easier to test, too.

  return {
    hasDynamicSuggestionsForArgument: (functionName, argName) => {
      return (customHandlers[functionName] && customHandlers[functionName][argName]);
    },

    getDynamicSuggestionsForArgument: async (functionName, argName, functionArgs, partialInput = '') => {
      return await customHandlers[functionName][argName](partialInput, functionArgs);
    },

    getStaticSuggestionsForInput: (partialInput = '', staticSuggestions = []) => {
      if (partialInput) {
        return staticSuggestions.filter(suggestion => {
          return suggestion.name.includes(partialInput);
        });
      }

      return staticSuggestions;
    },
  };


const activeArg = activeFunc.arguments.find((argument) => {
return inLocation(cursorPosition, argument.location);
});
// return argument_value suggestions when cursor is inside agrument value
if (activeArg && activeArg.type === 'namedArg' && inLocation(cursorPosition, activeArg.value.location)) {
// TODO - provide argument value suggestions once function list contains required data
return { list: [], location: activeArg.value.location, type: SUGGESTION_TYPE.ARGUMENT_VALUE };
const valueSuggestions = await getArgValueSuggestions(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would become:

    const {
      function: functionName,
      arguments: functionArgs,
    } = activeFunc;

    const {
      name: argName,
      value: { text: partialInput },
    } = activeArg;

    let valueSuggestions;

    if (hasDynamicSuggestionsForArgument(functionName, argName)) {
      valueSuggestions = await getDynamicSuggestionsForArgument(functionName, argName, functionArgs, partialInput);
    } else {
      const {
        suggestions: staticSuggestions,
      } = functionHelp.args.find((arg) => {
        return arg.name === activeArg.name;
      });

      valueSuggestions = getStaticSuggestionsForInput(partialInput, staticSuggestions);
    }

Copy link
Contributor

@cjcenizal cjcenizal Nov 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment pertains to my last comment at the bottom.

} catch (e) {
// The expression isn't correctly formatted, so JSON.parse threw an error.
return reject();
const valueSuggestions = await getArgValueSuggestions(message.name, null, argHelp, message.currentFunction, message.currentArgs);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this becomes:

        const {
          name: argName,
          currentFunction: functionName,
          currentArgs: functionArgs,
        } = message;

        let valueSuggestions = [];

        if (hasDynamicSuggestionsForArgument(functionName, argName)) {
          valueSuggestions = await getDynamicSuggestionsForArgument(functionName, argName, functionArgs);
        } else {
          const functionHelp = functionList.find(func => func.name === functionName);

          if (functionHelp) {
            valueSuggestions = functionHelp.args.find(arg => arg.name === argName);
          }
        }

Copy link
Contributor

@cjcenizal cjcenizal Nov 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment pertains to my last comment at the bottom.

const newCaretOffset = min + argumentName.length;
setCaretOffset(newCaretOffset);
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of repetition here. I think this becomes a little more maintainable if we extract the repetitive parts. I haven't tested this btw.

        const { min, max } = suggestibleFunctionLocation;
        let insertedValue;
        let insertPositionMinOffset = 0;

        switch (scope.suggestions.type) {
          case SUGGESTION_TYPE.FUNCTIONS: {
            // Position the caret inside of the function parentheses.
            insertedValue = `${scope.suggestions.list[suggestionIndex].name}()`;

            // Update the expression with the function.
            // min advanced one to not replace function '.'
            insertPositionMinOffset = 1;
            break;
          }
          case SUGGESTION_TYPE.ARGUMENTS: {
            // Position the caret after the '='
            insertedValue = `${scope.suggestions.list[suggestionIndex].name}=`;
            break;
          }
          case SUGGESTION_TYPE.ARGUMENT_VALUE: {
            // Position the caret after the '='
            insertedValue = `${scope.suggestions.list[suggestionIndex].name}`;
            break;
          }
        }

        const updatedExpression = insertAtLocation(insertedValue, scope.sheet, min + insertPositionMinOffset, max);
        scope.sheet = updatedExpression;

        const newCaretOffset = min + insertedValue.length;
        setCaretOffset(newCaretOffset);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had meant to go back and out the duplicated code to clean this switch statement but just forgot about it. Thanks for cleaning it up. Your code block worked great

Copy link
Contributor

@cjcenizal cjcenizal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Had a couple nitpicky suggestions but feel free to disregard if you don't like them.

activeArg.name,
activeArg.value.text,
functionHelp.args.find((arg) => {
const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to destructure the methods in argValueSuggestions, too.

const {
  hasDynamicSuggestionsForArgument,
  getDynamicSuggestionsForArgument,
  getStaticSuggestionsForInput,
} = argValueSuggestions;

This could make the code on lines 112 through 120 a bit terser.

return { list, location: message.location, type: SUGGESTION_TYPE.FUNCTIONS };
}
case 'incompleteArgument': {
const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same suggestion about destructuring argValueSuggestions applies here.

Copy link
Contributor

@cjcenizal cjcenizal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a couple more minor things.

@@ -13,7 +13,10 @@ export default new Chainable('fit', {
{
name: 'mode',
types: ['string'],
help: 'The algorithm to use for fitting the series to the target. One of: ' + _.keys(fitFunctions).join(', ')
help: 'The algorithm to use for fitting the series to the target. One of: ' + _.keys(fitFunctions).join(', '),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use a template literal here.

@@ -16,7 +16,10 @@ export default new Chainable('trend', {
{
name: 'mode',
types: ['string'],
help: 'The algorithm to use for generating the trend line. One of: ' + _.keys(validRegressions).join(', ')
help: 'The algorithm to use for generating the trend line. One of: ' + _.keys(validRegressions).join(', '),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too.

@@ -50,7 +50,10 @@ export default new Chainable('yaxis', {
{
name: 'units',
types: ['string', 'null'],
help: 'The function to use for formatting y-axis labels. One of: ' + _.values(tickFormatters).join(', ')
help: 'The function to use for formatting y-axis labels. One of: ' + _.values(tickFormatters).join(', '),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too.

Copy link
Contributor

@timroes timroes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nreese nreese merged commit 94b2b32 into elastic:master Nov 14, 2017
nreese added a commit to nreese/kibana that referenced this pull request Nov 14, 2017
* timelion argument value suggestions for legend function

* update functions with argument value suggestions

* use async/await instead of promise resolve/reject for suggestion generation

* lookup value suggestions for es index and timefield arguments

* custom handlers for es metric and split arguments

* update suggestions unit test

* remove duplicate code from insertSuggestion switch

* refactor arg_value_suggestions to provide three methods instead of single getSuggetions method

* template literal

* update es index argument desc to include note about type ahead support
nreese added a commit to nreese/kibana that referenced this pull request Nov 15, 2017
* timelion argument value suggestions for legend function

* update functions with argument value suggestions

* use async/await instead of promise resolve/reject for suggestion generation

* lookup value suggestions for es index and timefield arguments

* custom handlers for es metric and split arguments

* update suggestions unit test

* remove duplicate code from insertSuggestion switch

* refactor arg_value_suggestions to provide three methods instead of single getSuggetions method

* template literal

* update es index argument desc to include note about type ahead support
nreese added a commit that referenced this pull request Nov 15, 2017
* timelion argument value suggestions for legend function

* update functions with argument value suggestions

* use async/await instead of promise resolve/reject for suggestion generation

* lookup value suggestions for es index and timefield arguments

* custom handlers for es metric and split arguments

* update suggestions unit test

* remove duplicate code from insertSuggestion switch

* refactor arg_value_suggestions to provide three methods instead of single getSuggetions method

* template literal

* update es index argument desc to include note about type ahead support
chrisronline pushed a commit to chrisronline/kibana that referenced this pull request Nov 20, 2017
* timelion argument value suggestions for legend function

* update functions with argument value suggestions

* use async/await instead of promise resolve/reject for suggestion generation

* lookup value suggestions for es index and timefield arguments

* custom handlers for es metric and split arguments

* update suggestions unit test

* remove duplicate code from insertSuggestion switch

* refactor arg_value_suggestions to provide three methods instead of single getSuggetions method

* template literal

* update es index argument desc to include note about type ahead support
chrisronline pushed a commit to chrisronline/kibana that referenced this pull request Dec 1, 2017
* timelion argument value suggestions for legend function

* update functions with argument value suggestions

* use async/await instead of promise resolve/reject for suggestion generation

* lookup value suggestions for es index and timefield arguments

* custom handlers for es metric and split arguments

* update suggestions unit test

* remove duplicate code from insertSuggestion switch

* refactor arg_value_suggestions to provide three methods instead of single getSuggetions method

* template literal

* update es index argument desc to include note about type ahead support
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants