diff --git a/airflow/example_dags/example_params_ui_tutorial.py b/airflow/example_dags/example_params_ui_tutorial.py
index 3d2ec97c9976b..c07f9fc7c2bd9 100644
--- a/airflow/example_dags/example_params_ui_tutorial.py
+++ b/airflow/example_dags/example_params_ui_tutorial.py
@@ -55,7 +55,7 @@
"most_loved_number": Param(
42,
type="integer",
- title="You favorite number",
+ title="Your favorite number",
description_html="""Everybody should have a favorite number. Not only math teachers.
If you can not think of any at the moment please think of the 42 which is very famous because
of the book
@@ -71,6 +71,62 @@
description="You can use JSON schema enum's to generate drop down selection boxes.",
enum=[f"value {i}" for i in range(16, 64)],
),
+ # You can also label the selected values via values_display attribute
+ "pick_with_label": Param(
+ 3,
+ type="number",
+ title="Select one Number",
+ description="With drop down selections you can also have nice display labels for the values.",
+ enum=[*range(1, 10)],
+ values_display={
+ 1: "One",
+ 2: "Two",
+ 3: "Three",
+ 4: "Four - is like you take three and get one for free!",
+ 5: "Five",
+ 6: "Six",
+ 7: "Seven",
+ 8: "Eight",
+ 9: "Nine",
+ },
+ ),
+ # If you want to have a list box with proposals but not enforcing a fixed list
+ # then you can use the examples feature of JSON schema
+ "proposals": Param(
+ "some value",
+ type="string",
+ title="Field with proposals",
+ description="You can use JSON schema examples's to generate drop down selection boxes "
+ "but allow also to enter custom values. Try typing an 'a' and see options.",
+ examples=(
+ "Alpha,Bravo,Charlie,Delta,Echo,Foxtrot,Golf,Hotel,India,Juliett,Kilo,Lima,Mike,November,Oscar,Papa,"
+ "Quebec,Romeo,Sierra,Tango,Uniform,Victor,Whiskey,X-ray,Yankee,Zulu"
+ ).split(","),
+ ),
+ # If you want to select multiple items from a fixed list JSON schema des not allow to use enum
+ # In this case the type "array" is being used together with "examples" as pick list
+ "multi_select": Param(
+ ["two", "three"],
+ "Select from the list of options.",
+ type="array",
+ title="Multi Select",
+ examples=["one", "two", "three", "four", "five"],
+ ),
+ # A multiple options selection can also be combined with values_display
+ "multi_select_with_label": Param(
+ ["2", "3"],
+ "Select from the list of options. See that options can have nicer text and still technical values"
+ "are propagated as values during trigger to the DAG.",
+ type="array",
+ title="Multi Select with Labels",
+ examples=["1", "2", "3", "4", "5"],
+ values_display={
+ "1": "One box of choccolate",
+ "2": "Two bananas",
+ "3": "Three apples",
+ # Note: Value display mapping does not need to be complete.s
+ },
+ ),
# Boolean as proper parameter with description
"bool": Param(
True,
diff --git a/airflow/www/static/js/trigger.js b/airflow/www/static/js/trigger.js
index 27fcc099726cf..6ada3d615eaed 100644
--- a/airflow/www/static/js/trigger.js
+++ b/airflow/www/static/js/trigger.js
@@ -17,7 +17,7 @@
* under the License.
*/
-/* global document, CodeMirror, window */
+/* global document, CodeMirror, window, $ */
let jsonForm;
const objectFields = new Map();
@@ -46,7 +46,19 @@ function updateJSONconf() {
values[values.length] = lines[j].trim();
}
}
- params[keyName] = values.length === 0 ? params[keyName] : values;
+ params[keyName] = values.length === 0 ? null : values;
+ } else if (
+ elements[i].attributes.valuetype &&
+ elements[i].attributes.valuetype.value === "multiselect"
+ ) {
+ const { options } = elements[i];
+ const values = [];
+ for (let j = 0; j < options.length; j += 1) {
+ if (options[j].selected) {
+ values[values.length] = options[j].value;
+ }
+ }
+ params[keyName] = values.length === 0 ? null : values;
} else if (elements[i].value.length === 0) {
params[keyName] = null;
} else if (
@@ -104,7 +116,7 @@ function initForm() {
jsonForm.setSize(null, height);
if (formHasFields) {
- // Apply JSON formatting and linting to all object fields in the form
+ // Initialize jQuery and Chakra fields
const elements = document.getElementById("trigger_form");
for (let i = 0; i < elements.length; i += 1) {
if (elements[i].name && elements[i].name.startsWith("element_")) {
@@ -112,6 +124,7 @@ function initForm() {
elements[i].attributes.valuetype &&
elements[i].attributes.valuetype.value === "object"
) {
+ // Apply JSON formatting and linting to all object fields in the form
const field = CodeMirror.fromTextArea(elements[i], {
lineNumbers: true,
mode: { name: "javascript", json: true },
@@ -120,6 +133,14 @@ function initForm() {
});
field.on("blur", updateJSONconf);
objectFields.set(elements[i].name, field);
+ } else if (elements[i].nodeName === "SELECT") {
+ // Activate select2 multi select boxes
+ const elementId = `#${elements[i].name}`;
+ $(elementId).select2({
+ placeholder: "Select Values",
+ allowClear: true,
+ });
+ elements[i].addEventListener("blur", updateJSONconf);
} else if (elements[i].type === "checkbox") {
elements[i].addEventListener("change", updateJSONconf);
} else {
@@ -174,7 +195,9 @@ function initForm() {
setTimeout(updateJSONconf, 100);
}
}
-initForm();
+$(document).ready(() => {
+ initForm();
+});
window.updateJSONconf = updateJSONconf;
@@ -212,6 +235,8 @@ function setRecentConfig(e) {
objectFields
.get(`element_${keys[i]}`)
.setValue(JSON.stringify(newValue, null, 4));
+ } else if (element.nodeName === "SELECT") {
+ $(`#${element.name}`).select2("val", [newValue]);
} else {
element.value = newValue;
}
diff --git a/airflow/www/templates/airflow/trigger.html b/airflow/www/templates/airflow/trigger.html
index a7969f7cbb9da..56ad656883660 100644
--- a/airflow/www/templates/airflow/trigger.html
+++ b/airflow/www/templates/airflow/trigger.html
@@ -72,12 +72,35 @@
{% elif "enum" in form_details.schema and form_details.schema.enum %}
{% elif form_details.schema and "array" in form_details.schema.type %}
+ {% if "examples" in form_details.schema and form_details.schema.examples %}
+
+ {% else %}
+ {% endif %}
{% elif form_details.schema and "object" in form_details.schema.type %}
{% elif form_details.schema and ("integer" in form_details.schema.type or "number" in form_details.schema.type) %}
-
{% else %}
-
+ {% if "examples" in form_details.schema and form_details.schema.examples %}
+
+ {% endif %}
{% endif %}
{% if form_details.description -%}
{{ form_details.description }}
diff --git a/docs/apache-airflow/core-concepts/params.rst b/docs/apache-airflow/core-concepts/params.rst
index eacaef3c5d2fd..7619d56171d46 100644
--- a/docs/apache-airflow/core-concepts/params.rst
+++ b/docs/apache-airflow/core-concepts/params.rst
@@ -189,13 +189,19 @@ The following features are supported in the Trigger UI Form:
You can add the parameters ``minimum`` and ``maximum`` to restrict number range accepted.
- ``boolean``: Generates a toggle button to be used as ``True`` or ``False``.
- ``date``, ``datetime`` and ``time``: Generate date and/or time picker
- - ``array``: Generates a HTML multi line text field, every line edited will be made into a string array as value
+ - ``array``: Generates a HTML multi line text field, every line edited will be made into a string array as value.
+ if you add the attribute ``example`` with a list, a multi-value select option will be generated.
- ``object``: Generates a JSON entry field
- Note: Per default if you specify a type, a field will be made required with input - because of JSON validation.
If you want to have a field value being added optional only, you must allow JSON schema validation allowing null values via:
``type=["null", "string"]``
-- The Param attribute ``enum`` generates a drop-down select list. As of JSON validation, a value must be selected.
+- The Param attribute ``enum`` generates a drop-down select list for scalar values. As of JSON validation, a value must be selected or
+ the field must be marked as optional explicit.
+- If you want to present proposals for scalar values (not restricting the user to a fixed ``enum`` as above) you can make use of
+ ``examples`` which is a list of items.
+- For select drop-downs generated via ``enum`` or multi-value selects you can add the attribute ``values_display`` with a dict and
+ map data values to display labels.
- If a form field is left empty, it is passed as ``None`` value to the params dict.
- Form fields are rendered in the order of definition.
- If you want to add sections to the Form, add the parameter ``section`` to each field. The text will be used as section label.