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

Create form field number #8634

Merged
merged 39 commits into from
Nov 28, 2024
Merged

Create form field number #8634

merged 39 commits into from
Nov 28, 2024

Conversation

Devessier
Copy link
Contributor

@Devessier Devessier commented Nov 21, 2024

  • Refactor VariableTagInput to have a reusable low-level TipTap editor
  • Create three primitive form fields:
    • Text
    • Number
    • Boolean

Notes

  • We should automatically recognize the placeholder to use for every FormFieldInput, as it's done for FieldInputs.

Design decisions

Our main challenge was for variables and inputs to be able to communicate between each other. We chose an API that adds some duplication but remains simple and doesn't rely on "hacks" to work. Common styles are centralized.

Demo

"Workflow" mode with variables:

CleanShot 2024-11-26 at 10 43 25@2x

FormFieldInput mode, without variables:

CleanShot 2024-11-26 at 10 44 26@2x

Behavior difference between fields that can contain variables and static content, and inputs that can have either a variable value or a static value:

CleanShot 2024-11-26 at 10 47 13@2x

@Devessier Devessier marked this pull request as ready for review November 26, 2024 09:49
@martmull
Copy link
Contributor

martmull commented Nov 26, 2024

Functional testing observations

  • Boolean, we should be able to set no value at all, currently we are forced to set true or false (we might update the <BooleanFieldInput /> component) @Bonapara what do you think?
  • Number, we should not be able to type "string" values in input, only numbers
    image
  • Number, I think we should not allow float values either
    image
  • Number and Boolean, we should not be able to set string variable in boolean input, only boolean variables
    image

@thomtrp
Copy link
Contributor

thomtrp commented Nov 26, 2024

  • Boolean, we should be able to set no value at all, currently we are forced to set true or false (we might update the <BooleanFieldInput /> component) @Bonapara what do you think?

@martmull this is a discussion we got 👍 This is the current behavior of our Boolean field, this is why we kept the same. It should not be seen as a primitive boolean field but more as a True/False field. If the user needs a 3 options field (like null, false, true) he can still use the select field for this.

Copy link
Contributor

@martmull martmull left a comment

Choose a reason for hiding this comment

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

Hey nice job, some comments

Input={
<StyledBooleanInputContainer>
<BooleanInput
value={draftValue as boolean}
Copy link
Contributor

Choose a reason for hiding this comment

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

we should remove as boolean and use a format to boolean utils

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's an important subject, and we should discuss it first. There are two policies: either we cast values to the type we expect or discard values that don't match their expected type. By casting, I mean doing things like this: String(mightBeAStringOrSomethingElse).

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should be sure that draftValue is a boolean

defaultValue,
placeholder,
onPersist,
multiline,
Copy link
Contributor

Choose a reason for hiding this comment

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

looks like multiline is not used. If not used we should remove it and related code

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'm not sure to understand why multiline wouldn't be used. It appears to be used multiple times in the commend for me. Am I missing something?

Copy link
Contributor

Choose a reason for hiding this comment

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

By saying not used, i mean i don't see any component that set this multiline parameter to true:

<ComponentUsingMultilineTrue
...
multiline
...
/>

Did I miss one of them?

) : (
<StyledVariableContainer>
<SortOrFilterChip
labelValue={extractVariableLabel(draftValue as string)}
Copy link
Contributor

Choose a reason for hiding this comment

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

we should remove as string

};

const handleChange = (newText: string) => {
setDraftValue(newText);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think

    if (!canBeCastAsNumberOrNull(newValue)) {
      return;
    }

should be used here!

Copy link
Contributor

Choose a reason for hiding this comment

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

we should also check for decimal and return if there is any

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 duplicated the logic of the NumberFieldInput file; I'm unsure if we want to behave differently than this component.

onPersist(false);
}}
onVariableTagInsert={(variable) => {
setDraftValue(variable);
Copy link
Contributor

Choose a reason for hiding this comment

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

we should check that variable is a boolean or not, and only set if boolean. That should be possible as variable type is provided in outputSchema

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can improve this in another PR. However, it will require many changes, which we must consider beforehand.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Created another issue to track that: #8787.

onPersist(null);
}}
onVariableTagInsert={(variable) => {
setDraftValue(variable);
Copy link
Contributor

Choose a reason for hiding this comment

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

we should check that variable has a type number, the best would be to remove the option in variable dropdown

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned here, I think we can check it later: #8634 (comment).


export const extractVariableLabel = (rawVariable: string) => {
const variableWithoutBrackets = rawVariable.replace(
/\{\{([^{}]+)\}\}/g,
Copy link
Contributor

Choose a reason for hiding this comment

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

can we use REGEX_VARIABLE_TAG here? I don't get the functional difference between the 2 regex

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This regex captures what's inside brackets. The other one also captures brackets. We could refactor it to use a single regex.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok nice, lets refactor then

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 tried to reduce the number of regexes created to deal with variables, but every regex has a specific behavior, and I don't think it's excellent to unify them. We could make a one-size-fits-all regex, but it will necessarily be more complex and less readable, as well as the code using the regex. I think it's better to keep specialized regexes.

Copy link
Contributor

@thomtrp thomtrp left a comment

Choose a reason for hiding this comment

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

@Devessier I am not sure to get the full picture here. I was expecting an architecture like:

  • Primitive input (Boolean, Number, Text) that would be a common base for Form Field Input and Field Input
  • Primitive form field input (BooleanFormFieldInput, NumberFormFieldInput...)
  • Global component FormFieldInput to switch on it
  • WorkflowFormFieldInput that send the update functions, the variable component as right element and call the FormFieldInput as main input component.
  • Hard to implement for tiptap, so we could have a special WorkflowFormFieldInputWithTipTap (not the real component name)

In your code:

  • I do not see FormFieldInput actually used? To me it was supposed to be the base for workflow forms but also the futur forms without variables we will have to implement
  • It seems that we will have to add a WorkflowFieldInput for each type. But except for text using tiptap, isn't the logic to store value and use variables always the same?

Happy to discuss the architecture, maybe this is unrealistic. I would also like your opinion @lucasbordeau @charlesBochet

@Devessier
Copy link
Contributor Author

@Devessier I am not sure to get the full picture here. I was expecting an architecture like:

  • Primitive input (Boolean, Number, Text) that would be a common base for Form Field Input and Field Input
  • Primitive form field input (BooleanFormFieldInput, NumberFormFieldInput...)
  • Global component FormFieldInput to switch on it
  • WorkflowFormFieldInput that send the update functions, the variable component as right element and call the FormFieldInput as main input component.
  • Hard to implement for tiptap, so we could have a special WorkflowFormFieldInputWithTipTap (not the real component name)

In your code:

  • I do not see FormFieldInput actually used? To me it was supposed to be the base for workflow forms but also the futur forms without variables we will have to implement
  • It seems that we will have to add a WorkflowFieldInput for each type. But except for text using tiptap, isn't the logic to store value and use variables always the same?

Happy to discuss the architecture, maybe this is unrealistic. I would also like your opinion @lucasbordeau

I'm unsure how we could have a standalone FormFieldInput component that could also be enhanced in WorkflowFormFieldInput. I might miss something, but I am happy to consider doing things differently.

Let's take WorkflowFormNumberFieldInput as an example. When we want to unlink a variable, we must call setDraftValue. However, if the WorkflowFormNumberFieldInput component was relying on FormNumberFieldInput to work, and provided that FormNumberFieldInput would need to own draftValue and setDraftValue to work as a standalone component, the parent component would need a way to set the state of its child component.
We could solve this by putting the input's state on Recoil, but it only solves some problems.

The FormNumberFieldInput component renders the FormFieldInputBase component, which gives it a standalone component look. The WorkflowFormNumberFieldInput component does so with WorkflowFormFieldInputBase.

We could find solutions to reuse standalone components and enhance them with variables, but I wonder if this is the simplest way.
Instead, I suggest reusing base components and common logic. For instance, both the WorkflowFormNumberFieldInput and FormNumberFieldInput components rely on the TextInput component.
We could also extract the common logic in a hook. For instance, draftValue, persistNumber and handleChange are common.
I didn't implement that in my PR, but I think it would be the way to go once we understood the common logic for each component.

I prefer some duplication of templating code (like creating more components) to trying to make things work together and finding in two weeks that one component shouldn't work the same way in workflows as it does in usual forms. Furthermore, we don't yet have a use case for FormFieldInput.

Input={
<StyledInput
placeholder={placeholder}
value={String(draftValue)}
Copy link
Contributor

Choose a reason for hiding this comment

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

To remain consistent with the codebase

Suggested change
value={String(draftValue)}
value={`${draftValue}`}

<StyledInput
inputId={inputId}
placeholder={placeholder}
value={String(draftValue)}
Copy link
Contributor

Choose a reason for hiding this comment

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

same value={${...}}

Copy link
Contributor

@thomtrp thomtrp left a comment

Choose a reason for hiding this comment

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

Amazing work @Devessier!! 💪 🔥

Let's wait for @lucasbordeau review but LGTM!

const currentValue = formData[field.name] as JsonValue;

return (
<FormFieldInput
Copy link
Contributor

Choose a reason for hiding this comment

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

😍

},
});

const handleVariableTagInsert = (variable: string) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we avoid using just "variable" alone, either variableValue, variableContent or variableName ? As a first-time reader I don't clearly understand, which for this "meta" part of the code is important.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes 👌 Done.

@Devessier Devessier force-pushed the create-form-field-number branch from 786c826 to 4e5dc98 Compare November 28, 2024 14:17
@Devessier Devessier force-pushed the create-form-field-number branch from 4e5dc98 to fe39cfa Compare November 28, 2024 16:50
@Devessier Devessier enabled auto-merge (squash) November 28, 2024 16:51
@Devessier Devessier merged commit d73dc1a into main Nov 28, 2024
19 checks passed
@Devessier Devessier deleted the create-form-field-number branch November 28, 2024 17:03
Copy link

Thanks @Devessier for your contribution!
This marks your 30th PR on the repo. You're top 1% of all our contributors 🎉
See contributor page - Share on LinkedIn - Share on Twitter

Contributions

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.

5 participants