Conditional Fields
Sym gives you the ability to dynamically modify the access request form as a user is filling it out.
Overview
When making access requests, the information needed may vary. With on_change
functions, you can change the fields displayed, which ones are required, and more-- all on the fly based on what the user has already entered into the form.
Terraform Provider version requirements
This feature requires that your Sym Terraform Provider version is >=
3.4.2
. If it is not and you would like to use this feature, please upgrade your version withterraform init -upgrade
.
Implementation
Only two things are needed to start using on_change
functions:
- At least one
prompt_field
- A new Python file where the
on_change
function will be defined
To start, add the Python logic to a prompt_field
by setting the on_change
attribute:
resource "sym_flow" "this" {
...
params {
prompt_field {
name = "urgency"
type = "string"
required = true
allowed_values = ["Low", "Medium", "High"]
# When the value of the "urgency" field changes in the request form,
# the code defined in on_change.py will be run.
on_change = file("${path.module}/on_change.py")
}
prompt_field {
name = "reason"
type = "string"
required = false
}
}
}
Then, add an on_change_<field_name>
function to the new Python file. In this case, since the file is attached to the urgency
field, we need to define an on_change_urgency
field:
def on_change_urgency(username, form):
# Right now, this function does nothing.
return form
If your prompt field name has dashes, then replace the dashes with underscores when naming your
on_change
function.For example, if your
prompt_field
hasname = "my-field"
, then your function should be namedon_change_my_field
.
All on_change
functions take two arguments:
username
-- This will be the email of the user filling out the request form.form
-- This is asym.sdk.forms.SymPromptField
Python object representing the current state of the form. See the SDK docs for a full API reference.
and should return the same form they were given after making any desired modifications. For example, to make the reason
field required for "High"
urgency requests, set the reason
field to be required and then return the form containing it:
def on_change_urgency(username, form):
# All fields defined on the sym_flow are available in form.fields at:
# form.fields["<prompt_field.name>"]
reason_field = form.fields["reason"]
urgency_field = form.fields["urgency"]
if urgency_field.value == "High":
# If the value of the "urgency" field is "High", then the "reason" field
# should be required.
reason_field.required = True
else:
# If the value of the "urgency" field is NOT "High", then the "reason" field
# should not be required.
reason_field.required = False
# Return the form containing modified field.
return form
After running terraform apply
to apply these changes, the Flow will call the on_change
function whenever the urgency
field value is changed:

A demo of on_change_urgency
toggling whether the reason
field is required.
Limitations
Because
on_change
functions are called on the fly while a user is filling out a form, they must execute very fast or the user will be left waiting. Due to this,sym.sdk.integrations
functions are not available, nor is therequests
library, as making REST API calls would slow down the experience immensely.
Examples
Conditionally display a field
To hide or display certain prompt_fields
conditionally, toggle the visible
attribute. The following example will display a required reason
field only if the urgency
is "High"
:
def on_change_urgency(username, form):
# All fields defined on the sym_flow are available in form.fields at:
# form.fields["<prompt_field.name>"]
reason_field = form.fields["reason"]
urgency_field = form.fields["urgency"]
if urgency_field.value == "High":
# If the value of the "urgency" field is "High", then the "reason" field
# should be displayed.
reason_field.visible = True
else:
# If the value of the "urgency" field is NOT "High", then the "reason" field
# should not be displayed.
reason_field.visible = False
# Return the form containing modified field.
return form
resource "sym_flow" "this" {
...
params {
prompt_field {
name = "urgency"
type = "string"
required = true
allowed_values = ["Low", "Medium", "High"]
# When the value of the "urgency" field changes in the request form,
# the code defined in on_change.py will be run.
on_change = file("${path.module}/on_change.py")
}
prompt_field {
name = "reason"
type = "string"
required = true
# This field will not be displayed in the request form.
# Invisible fields are always optional, and `required = true` will not be
# enforced unless an `on_change` function sets `visible = true`.
visible = false
}
}
}
Conditionally change field options
It may be useful to change the options available in a select field based on a different field's value. The following example will not allow requesters to ask for "Admin" permissions to the "Production" AWS account, but will allow it for the "Staging" AWS account:
def on_change_aws_account_name(username, form):
# All fields defined on the sym_flow are available in form.fields at:
# form.fields["<prompt_field.name>"]
aws_account_name_field = form.fields["aws_account_name"]
access_level_field = form.fields["access_level"]
if aws_account_name_field.value == "Production":
# Get the allowed_values defined in Terraform, but filter out "Admin".
all_options_except_admin = [option for option in access_level_field.original_allowed_values if option.value != "Admin"]
# Set the current list of allowed_values to the filtered list.
access_level_field.current_allowed_values = all_options_except_admin
else:
# If the "Production" account is not selected, all allowed_values defined in Terraform are allowed.
access_level_field.current_allowed_values = access_level_field.original_allowed_values
# Return the form containing modified field.
return form
resource "sym_flow" "this" {
...
params {
prompt_field {
name = "aws_account_name"
type = "string"
allowed_values = ["Staging", "Production"]
# When the value of the "aws_account_name" field changes in the request form,
# the code defined in on_change.py will be run.
on_change = file("${path.module}/on_change.py")
}
prompt_field {
name = "access_level"
type = "string"
allowed_values = ["Admin", "Read", "Write"]
}
}
}
React to Target selection
To conditionally change fields or field options based on Target selection, include the target_id
as a field in your sym_flow
resource. This will make the target_id
available as a prompt_field
in your SDK implementation.
Then, in your on_change
method, you can:
- Resolve the
target_id_field
to its select options. - Validate that an option has been selected
- Fetch the target's label for comparison
- Apply any downstream logic from there
resource "sym_flow" "this" {
...
params {
prompt_field {
name = "target_id"
type = "string"
required = true
on_change = file("${path.module}/on_change.py")
}
# Other prompt fields, etc.
}
}
def on_change_target_id(username, form):
# Get the target_id field from the form
target_id_field = form.fields["target_id"]
# Get any currently selected target option
target_selection = [option for option in target_id_field.original_allowed_values if str(option.value) == str(target_id_field.value)]
# If a Target has been selected...
if target_selection:
# Then get the Target's label (this is what is displayed in the dropdown)
target_label = target_selection[0].label
...
Using Flow vars in on_change
on_change
If you have defined a vars
map in your sym_flow
resource, this will be exposed as the flow_vars
dictionary in the SymPromptForm
object used to invoke your on_change
function. Remember, while you may pass in other primitives (e.g. bool
, int
) as a value to sym_flow.vars
, they will be cast to strings when you apply your configuration. As a result, SymPromptForm.flow_vars
is defined as a Dict[str, str]
; if you wish to access these values as different types, remember to recast them before using them.
resource "sym_flow" "this" {
...
vars = {
a_string_var = "foo"
an_int_var = 5
}
params {
prompt_field {
name = "my_field"
type = "string"
required = true
on_change = file("${path.module}/on_change.py")
}
# Other prompt fields, etc.
}
}
def on_change_my_field(username, form):
# The vars map is exposed under `form.flow_vars`
# Prints "foo"
print(form.flow_vars["a_string_var"])
# Don't forget to cast other primitives if you wish to use them as their respective types!
an_int_var = int(form.flow_vars["an_int_var"])
# Prints "True"
print(an_int_var > 1)
Troubleshooting
An argument named "on_change" is not expected here.
An argument named "on_change" is not expected here.
Please ensure that your on_change
block is defined inside of a prompt_field
block and that your Sym Provider version is >= 3.4.2
.
Updated 3 days ago