Skip to content

Conversation

guan404ming
Copy link
Contributor

@guan404ming guan404ming commented Aug 21, 2025

Related PR

Closes: #54506

How

  • Enables HITL forms to be preloaded with default options and parameters via URL search parameters
.../required_actions?options=["option 1","option 2"]&params={"key":"value"}
Screen.Recording.2025-08-21.at.10.32.04.PM.mov

^ Add meaningful description above
Read the Pull Request Guidelines for more information.
In case of fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
In case of a new dependency, check compliance with the ASF 3rd Party License Policy.
In case of backwards incompatible changes please leave a note in a newsfragment file, named {pr_number}.significant.rst or {issue_number}.significant.rst, in airflow-core/newsfragments.

@boring-cyborg boring-cyborg bot added the area:UI Related to UI/UX. For Frontend Developers. label Aug 21, 2025
@guan404ming guan404ming changed the title Allow preloaded options and params through search params Allow preloaded hitl options and params through search params Aug 21, 2025
@jscheffl
Copy link
Contributor

Thanks for adding this feature. That totally makes sense. Just I am not 100% good with the choice of implementation. Similar we had in Airflow 2.x for the trifger form (and I just noticed that this feature is not implemented in 3.x... so need to clean docs...) - if you take alook to https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/params.html:

To pre-populate values in the form when publishing a link to the trigger form you can call the trigger URL /dags/<dag_name>/trigger and add query parameter to the URL in the form name=value, for example /dags/example_params_ui_tutorial/trigger?required_field=some%20text. To pre-define the run id of the DAG run, use the URL parameter run_id.

Can you make it for the feature here the same, so that no params={...} need to be used but you can directly call via http://.../required_actions?key=value?

@Lee-W
Copy link
Member

Lee-W commented Aug 22, 2025

Thanks for adding this feature. That totally makes sense. Just I am not 100% good with the choice of implementation. Similar we had in Airflow 2.x for the trifger form (and I just noticed that this feature is not implemented in 3.x... so need to clean docs...) - if you take alook to airflow.apache.org/docs/apache-airflow/stable/core-concepts/params.html:

To pre-populate values in the form when publishing a link to the trigger form you can call the trigger URL /dags/<dag_name>/trigger and add query parameter to the URL in the form name=value, for example /dags/example_params_ui_tutorial/trigger?required_field=some%20text. To pre-define the run id of the DAG run, use the URL parameter run_id.

Can you make it for the feature here the same, so that no params={...} need to be used but you can directly call via http://.../required_actions?key=value?

Not sure whether it's ideal in this case as we have both options and params here. If we do things this way, it will be confusing when users want something like params_input={"options": ...} (it probably doesn't make as much sense but should be valid)

@guan404ming
Copy link
Contributor Author

guan404ming commented Aug 22, 2025

Not sure whether it's ideal in this case as we have both options and params here. If we do things this way, it will be confusing when users want something like params_input={"options": ...} (it probably doesn't make as much sense but should be valid)

I also have the concern about that case. Since we already preserve the options, it would cause conflict like @Lee-W said if we use ?key=value here.

IMO The preload behavior in this PR is scoped only to HitlResponseForm and won’t affect other forms. In this sense, options and params could remain preserved specifically for hitl without impacting other pages. Other pages could still use http://.../required_actions?key=value to preload data (once we implement additional preload behaviors later).

@jscheffl
Copy link
Contributor

I do not understand your concerns here. (I mean the technical reason, maybe due to mis-understanding) - can you elaborate the reason a bit more?

I would propose to make parameter input passing consistent to minimize mental load, encoding a params dict in a single value is a lot of mental load if somebody wants to prepare a URL, also quotes and special chars need to be encoded.

besides this proper docs are needed describing this to users as interface, else somebody need to be expert in source tree in order to make a parameterized call.

@Lee-W
Copy link
Member

Lee-W commented Aug 23, 2025

I do not understand your concerns here. (I mean the technical reason, maybe due to mis-understanding) - can you elaborate the reason a bit more?

I would propose to make parameter input passing consistent to minimize mental load, encoding a params dict in a single value is a lot of mental load if somebody wants to prepare a URL, also quotes and special chars need to be encoded.

besides this proper docs are needed describing this to users as interface, else somebody need to be expert in source tree in order to make a parameterized call.

The main reason is that we're not just adding parameter input, but also the option we want to preload. If there's only parameter input, I'd say we should go with key=value.... But now we have 2 things to add, options and params, and options might be a valid key for params. If we receive options=..., should we view it as a options or show it be part of params

@jscheffl
Copy link
Contributor

I do not understand your concerns here. (I mean the technical reason, maybe due to mis-understanding) - can you elaborate the reason a bit more?
I would propose to make parameter input passing consistent to minimize mental load, encoding a params dict in a single value is a lot of mental load if somebody wants to prepare a URL, also quotes and special chars need to be encoded.
besides this proper docs are needed describing this to users as interface, else somebody need to be expert in source tree in order to make a parameterized call.

The main reason is that we're not just adding parameter input, but also the option we want to preload. If there's only parameter input, I'd say we should go with key=value.... But now we have 2 things to add, options and params, and options might be a valid key for params. If we receive options=..., should we view it as a options or show it be part of params

Mhm, I do not understand about the "options" - this is what the user needs to confirm anyway? We decided not making a 1-click solution so the "2 click solution" here with pre-populated fields would anyway require to select the user to click on "option1", "option2", "option3" manually. What would be pre-selectable for the user with passing the "option"? Is this not the core idea of the second click the user needs to make?

Still if mis-understood the "options", we could make this a special field naming like "_options" and/or reserve the key such that if the form has the same it is just not possilbe to pre-populate. Even if this is a restriction it is much easier to be able to pass values to the form w/o the need to build and envode a JSON dict in a URL as param value.

@Lee-W
Copy link
Member

Lee-W commented Aug 23, 2025

I do not understand your concerns here. (I mean the technical reason, maybe due to mis-understanding) - can you elaborate the reason a bit more?
I would propose to make parameter input passing consistent to minimize mental load, encoding a params dict in a single value is a lot of mental load if somebody wants to prepare a URL, also quotes and special chars need to be encoded.
besides this proper docs are needed describing this to users as interface, else somebody need to be expert in source tree in order to make a parameterized call.

The main reason is that we're not just adding parameter input, but also the option we want to preload. If there's only parameter input, I'd say we should go with key=value.... But now we have 2 things to add, options and params, and options might be a valid key for params. If we receive options=..., should we view it as a options or show it be part of params

Mhm, I do not understand about the "options" - this is what the user needs to confirm anyway? We decided not making a 1-click solution so the "2 click solution" here with pre-populated fields would anyway require to select the user to click on "option1", "option2", "option3" manually. What would be pre-selectable for the user with passing the "option"? Is this not the core idea of the second click the user needs to make?

The demo video seems to miss the "options" part. But the idea is that whenever we receive ?...=...&...=... we'll render the page like multiple options and have the options pre-loaded.

image

The users still need to click the Respond button, but not necessarily the options.

Still if mis-understood the "options", we could make this a special field naming like "_options" and/or reserve the key such that if the form has the same it is just not possilbe to pre-populate. Even if this is a restriction it is much easier to be able to pass values to the form w/o the need to build and envode a JSON dict in a URL as param value.

I don't think I have enough frontend knowledge to judge whether decoding a json dict from a frontend perspective is complicated, so I'll leave this to @guan404ming . From the backend perspective, there will be a utility method once this is merged, so users typically don't need to deal with JSON encoding. If we decide to go with _options, I'll add another check to ensure this unusual key is not used.

@jscheffl
Copy link
Contributor

The users still need to click the Respond button, but not necessarily the options.

Still if mis-understood the "options", we could make this a special field naming like "_options" and/or reserve the key such that if the form has the same it is just not possilbe to pre-populate. Even if this is a restriction it is much easier to be able to pass values to the form w/o the need to build and envode a JSON dict in a URL as param value.

I don't think I have enough frontend knowledge to judge whether decoding a json dict from a frontend perspective is complicated, so I'll leave this to @guan404ming . From the backend perspective, there will be a utility method once this is merged, so users typically don't need to deal with JSON encoding. If we decide to go with _options, I'll add another check to ensure this unusual key is not used.

Ah, okay, so passing the "options" would be only beneficial if multiple options should be pre-selected, as single options would render as a button directly.

My argument is mostly not about the decoding (this can be done as code, I am sure this is not a big challenge) but about the complexity somebody need to invest to create a URL with the right parameters to pre-select. Making a URL with .../respond?key=value is much easier than .../respond?params={"key":"value"} which would need to properly URLencoded to .../respond?params=%7B%22key%22%3A%22value%22%7D and if it is not working like expected then it is very hard to debug.

@Lee-W
Copy link
Member

Lee-W commented Aug 23, 2025

The users still need to click the Respond button, but not necessarily the options.

Still if mis-understood the "options", we could make this a special field naming like "_options" and/or reserve the key such that if the form has the same it is just not possilbe to pre-populate. Even if this is a restriction it is much easier to be able to pass values to the form w/o the need to build and envode a JSON dict in a URL as param value.

I don't think I have enough frontend knowledge to judge whether decoding a json dict from a frontend perspective is complicated, so I'll leave this to @guan404ming . From the backend perspective, there will be a utility method once this is merged, so users typically don't need to deal with JSON encoding. If we decide to go with _options, I'll add another check to ensure this unusual key is not used.

Ah, okay, so passing the "options" would be only beneficial if multiple options should be pre-selected, as single options would render as a button directly.

From what I last discussed with @guan404ming , single options will work this way (like multiple options)if users are using this URL this way.

My argument is mostly not about the decoding (this can be done as code, I am sure this is not a big challenge) but about the complexity somebody need to invest to create a URL with the right parameters to pre-select. Making a URL with .../respond?key=value is much easier than .../respond?params={"key":"value"} which would need to properly URLencoded to .../respond?params=%7B%22key%22%3A%22value%22%7D and if it is not working like expected then it is very hard to debug.

My issue is that _options, params.key1, and params.key2 are not logically on the same level. But 7B%22key%22%3A%22value%22%7D also seems to be a valid concern. As the latter might cause more trouble , we probably could go with ?_options=...&key1=...

@guan404ming
Copy link
Contributor Author

guan404ming commented Aug 23, 2025

Approach Pros Cons Viewpoint
...?params={...} (JSON encoding) - Clear structure (no conflict between params and options)
- Good extensibility
- Requires JSON encode/URL encode, hard to write and debug
- Higher cognitive load for users
Concern about options vs params conflict
...?_options=...&key=value (flat query) - Intuitive syntax, consistent with Airflow 2.x
- No encoding needed, easy to debug
- Lower cost for users to prepare URLs
- _options and params are not on the same logical level
- Need to reserve _options to avoid conflicts with normal keys
Preference for simplicity, consistency, and usability

I created this table to summarize the discussion. Personally, I find Jens's concern reasonable if we try to think from user’s perspective. The flat query style feels more comfortable and approachable. Especially since some users of this feature may not be technical. I think reserving _options is a suitable solution to balance usability with potential conflicts where Wei and I concern about.

From what I last discussed with @guan404ming, single options would behave like multiple options if users pass them through the URL.

I initially implemented it this way. However, after manual testing, I found it was not consistent with our original one-click design in single option. Therefore, I switched to preloading with default options and I think it would be more consistent and user-friendly. Next, I will adjust the preload implementation and update the demo with screenshots covering:

  1. Single option preloading
  2. Multiple option preloading
  3. Key-value pair params

@guan404ming guan404ming marked this pull request as draft August 23, 2025 16:36
@guan404ming guan404ming force-pushed the hitl-preload branch 2 times, most recently from d1f236b to ae80b63 Compare August 24, 2025 07:57
@guan404ming
Copy link
Contributor Author

guan404ming commented Aug 24, 2025

Two update in url

  1. use _options for preload options and flat key-value pair for preload params in hitl
  2. change supported list format from _options=["option 1", "option 2"] to _options=option 1, option 2 (avoid to use json-like format to make it more user friendly)
.../required_actions?_options=option 1,option 2&key1=value1&key2=value2...
  1. Single option preloading (?_options=option 1)
Screen.Recording.2025-08-24.at.3.55.55.PM.mov
  1. Multiple option preloading (?_options=option 1,option 2)
Screen.Recording.2025-08-24.at.3.54.40.PM.mov
  1. Key-value pair params (?information=test)
Screen.Recording.2025-08-24.at.3.52.22.PM.mov

@Lee-W
Copy link
Member

Lee-W commented Aug 24, 2025

1. Single option preloading (`?_options=option 1`)

Screen.Recording.2025-08-24.at.3.55.55.PM.mov

I feel we probably need a Respond button for cases that have a default (like this one). Otherwise, users like me might be confused and don't know what to do next 🤔 Not a blocker though.

2. Multiple option preloading (`?_options=option 1,option 2`)

Screen.Recording.2025-08-24.at.3.54.40.PM.mov

I probably need to check whether , exists in each option from the backend?

@jscheffl
Copy link
Contributor

@guan404ming / @Lee-W thanks for the discussion! My concerns are fixed by the rework and looks very good for me!

I probably need to check whether , exists in each option from the backend?

That is really a limitation... that would be a + for having JSON support... mhm. I'd think it would be OK to assume no , is within the options and if somebody complaints then it can be addressed also in future. Would be really a nice case in my view.

One option as resolution could be to offer to support JSON as well as plain text style... but might be un-needed complexity if nobody is using/needing it. Would make it easy for users to consume and still would leave the option for the nice cases... I would defer this to a future if really somebody needs it.

@Lee-W
Copy link
Member

Lee-W commented Aug 24, 2025

@guan404ming / @Lee-W thanks for the discussion! My concerns are fixed by the rework and looks very good for me!

I probably need to check whether , exists in each option from the backend?

That is really a limitation... that would be a + for having JSON support... mhm. I'd think it would be OK to assume no , is within the options and if somebody complaints then it can be addressed also in future. Would be really a nice case in my view.

One option as resolution could be to offer to support JSON as well as plain text style... but might be un-needed complexity if nobody is using/needing it. Would make it easy for users to consume and still would leave the option for the nice cases... I would defer this to a future if really somebody needs it.

Thanks for jumping in on the discussion and helping us improve this feature! 🙌

I totally agree that restricting _options and excluding , makes sense. If anyone really needs these, we can open a new discussion about whether we want to go for it and how to make it happen.

@guan404ming
Copy link
Contributor Author

guan404ming commented Aug 24, 2025

I feel we probably need a Respond button for cases that have a default (like this one). Otherwise, users like me might be confused and don't know what to do next 🤔 Not a blocker though.

Here is the version with a respond button for single option preload case and I think I don't have strong opinion here since both of them look good to me. First one persist the consistent one-click ux and second one make the user flow more natural. What do you think?

Screen.Recording.2025-08-24.at.5.31.30.PM.mov

I totally agree that restricting _options and excluding , makes sense. If anyone really needs these, we can open a new discussion about whether we want to go for it and how to make it happen.

Agree with this.

@jscheffl
Copy link
Contributor

Here is the version with a respond button for single option preload case and I think I don't have strong opinion here since both of them look good to me. First one persist the consistent one-click ux and second one make the user flow more natural. What do you think?

I am not sure about the implementation complexity that you need to have the "single option" then in a dropdown and having a respond button... smells a bit more complex. Also UX would be a nit in-consistent. No strong feeling on this but I feel the previous way that still 3 buttons are presented but the pre-select is highlighted is a nit more consistent. Especially looking at a "Reject"/"Approve" scenario (Where in the current HITL Example because of the timeout the Reject is always proposed as default, if you post a link with a 2-click Approve option then approve would be highlighted.

@Lee-W
Copy link
Member

Lee-W commented Aug 24, 2025

No strong opinion either. Then I'll let @guan404ming decide what's the best 🙂

@guan404ming
Copy link
Contributor Author

guan404ming commented Aug 24, 2025

I am not sure about the implementation complexity that you need to have the "single option" then in a dropdown and having a respond button... smells a bit more complex.

The implementation itself wouldn’t be too complex, since our FlexibleForm can render fields based on the passed value type.
However, the logic feels a bit unintuitive and could be harder to maintain.

No strong opinion either. Then I'll let @guan404ming decide what's the best 🙂

I would personally prefer to stick with the current approach (highlight the preload options) for now for more consistent UX but we can revisit and adjust later based on user feedback.

Thanks for all discussion to make this feature better! 🚀

@bbovenzi
Copy link
Contributor

bbovenzi commented Aug 25, 2025

I think a simple encodeURIComponent(JSON.stringify(options)) is the simplest option and leaves less risk to use parsing it wrong. URL readability isn't a big concern here. And when filling out the form, we simply ignore any option which doesn't fit a dropdown.

We should probably have a "Submit" button on more forms. The number of clicks is less important than it being clear what you're about to do.

I agree that "Approve" should probably get the default main button styling.

@guan404ming
Copy link
Contributor Author

guan404ming commented Aug 27, 2025

I think a simple encodeURIComponent(JSON.stringify(options)) is the simplest option and leaves less risk to use parsing it wrong. URL readability isn't a big concern here. And when filling out the form, we simply ignore any option which doesn't fit a dropdown.

Agree with making it simple, I think it would be easy for user to prepare it after we got the utility function to generate url with preload options.

Thus currently the url with preloaded options would be like this

...?_options=["option 1", "option 2"]

We should probably have a "Submit" button on more forms. The number of clicks is less important than it being clear what you're about to do. I agree that "Approve" should probably get the default main button styling.

Nice catch, I like this perspective to keep the safety on the first place. I've update the render logic like:

  • show option button with preloaded option highlighted in Approval case (preloaded with "Approve")
image
  • show option dropdown and respond button in the other cases (preloaded with "option 4" and "option 5")
image

@Lee-W Lee-W merged commit d855d60 into apache:main Aug 30, 2025
55 checks passed
@github-project-automation github-project-automation bot moved this from In review to Done in AIP-90 - Human in the loop Aug 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:UI Related to UI/UX. For Frontend Developers.
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Allow preloaded options, input through .../required_actions?options=...&inputs=...
4 participants