Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion airflow/example_dags/example_params_ui_tutorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
33 changes: 29 additions & 4 deletions airflow/www/static/js/trigger.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

/* global document, CodeMirror, window */
/* global document, CodeMirror, window, $ */

let jsonForm;
const objectFields = new Map();
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -104,14 +116,15 @@ 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_")) {
if (
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 },
Expand All @@ -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 {
Expand Down Expand Up @@ -174,7 +195,9 @@ function initForm() {
setTimeout(updateJSONconf, 100);
}
}
initForm();
$(document).ready(() => {
initForm();
});

window.updateJSONconf = updateJSONconf;

Expand Down Expand Up @@ -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;
}
Expand Down
39 changes: 36 additions & 3 deletions airflow/www/templates/airflow/trigger.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,35 @@
{% elif "enum" in form_details.schema and form_details.schema.enum %}
<select class="my_select2 form-control" name="element_{{ form_key }}" id="element_{{ form_key }}" data-placeholder="Select Value"
onchange="updateJSONconf();"
{%- if "integer" in form_details.schema.type or "number" in form_details.schema.type %} valuetype="number"{% endif %}
{%- if not "null" in form_details.schema.type %} required=""{% endif %}>
{% for option in form_details.schema.enum -%}
<option{% if form_details.value == option %} selected="true"{% endif %}>{{ option }}</option>
<option value="{{ option }}"
{%- if option == form_details.value %} selected{% endif %}>
{%- if form_details.schema.values_display and option|string in form_details.schema.values_display -%}
{{ form_details.schema.values_display[option|string] }}
{%- else -%}
{{ option }}
{%- endif -%}
</option>
{% endfor -%}
</select>
{% elif form_details.schema and "array" in form_details.schema.type %}
{% if "examples" in form_details.schema and form_details.schema.examples %}
<select multiple name="element_{{ form_key }}" id="element_{{ form_key }}" class="select2-drop-mask" valuetype="multiselect"
onchange="updateJSONconf();"{% if not "null" in form_details.schema.type %} required=""{% endif %}>
{% for option in form_details.schema.examples -%}
<option value="{{ option }}"
{%- if option in form_details.value %} selected{% endif %}>
{%- if form_details.schema.values_display and option|string in form_details.schema.values_display -%}
{{ form_details.schema.values_display[option|string] }}
{%- else -%}
{{ option }}
{%- endif -%}
</option>
{% endfor %}
</select>
{% else %}
<textarea class="form-control" name="element_{{ form_key }}" id="element_{{ form_key }}" valuetype="array" rows="6"
{%- if not "null" in form_details.schema.type %} required=""{% endif -%}>
{%- if form_details.value is sequence %}
Expand All @@ -86,23 +109,33 @@
{%- endfor -%}
{% endif -%}
</textarea>
{% endif %}
{% elif form_details.schema and "object" in form_details.schema.type %}
<textarea class="form-control" name="element_{{ form_key }}" id="element_{{ form_key }}" valuetype="object" rows="6"
{%- if not "null" in form_details.schema.type %} required=""{% endif -%}>
{{- form_details.value | tojson() -}}
</textarea>
{% elif form_details.schema and ("integer" in form_details.schema.type or "number" in form_details.schema.type) %}
<input class="form-control" name="element_{{ form_key }}" id="element_{{ form_key }}" placeholder="" valuetype="number" type="number"
<input class="form-control" name="element_{{ form_key }}" id="element_{{ form_key }}" valuetype="number" type="number"
value="{% if form_details.value %}{{ form_details.value }}{% endif %}"
{%- if form_details.schema.minimum %} min="{{ form_details.schema.minimum }}"{% endif %}
{%- if form_details.schema.maximum %} max="{{ form_details.schema.maximum }}"{% endif %}
{%- if form_details.schema.type and not "null" in form_details.schema.type %} required=""{% endif %} />
{% else %}
<input class="form-control" name="element_{{ form_key }}" id="element_{{ form_key }}" placeholder="" type="text"
<input class="form-control" name="element_{{ form_key }}" id="element_{{ form_key }}"
type="{% if "examples" in form_details.schema and form_details.schema.examples %}search{% else %}text{% endif %}"
value="{% if form_details.value %}{{ form_details.value }}{% endif %}"
{%- if form_details.schema and form_details.schema.minLength %} minlength="{{ form_details.schema.minLength }}"{% endif %}
{%- if form_details.schema and form_details.schema.maxLength %} maxlength="{{ form_details.schema.maxLength }}"{% endif %}
{%- if "examples" in form_details.schema and form_details.schema.examples %} list="list_{{ form_key }}" placeholder="Start typing to see proposal values."{% endif %}
{%- if form_details.schema and form_details.schema.type and not "null" in form_details.schema.type %} required=""{% endif %} />
{% if "examples" in form_details.schema and form_details.schema.examples %}
<datalist id="list_{{ form_key }}">
{% for option in form_details.schema.examples -%}
<option>{{ option }}</option>
{% endfor -%}
</datalist>
{% endif %}
{% endif %}
{% if form_details.description -%}
<span class="help-block">{{ form_details.description }}</span>
Expand Down
10 changes: 8 additions & 2 deletions docs/apache-airflow/core-concepts/params.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down