Active Choices — Patterns and Pitfalls
Active Choices is the Jenkins plugin that powers every reactive parameter on the Create / Update / Deploy / Manage jobs. This page collects the patterns that actually work in this codebase and the traps we have stepped on.
Parameter types we use
| Plugin class | When to use it |
|---|---|
|
Source parameters that drive other parameters via |
|
Display-only panels derived from others. Example: |
Hidden |
Cross-panel state channel, injected by |
|
|
Selection markers
PT_CHECKBOX values arrive with :selected / :disabled suffixes per entry. Consumers must strip them before comparing:
def selected = (binding.variables['SELECTED_SERVICES'] ?: '')
.toString()
.split(',')
.collect { it.trim().replaceAll(/:selected$|:disabled$/, '') }
.findAll { it }
Trailing-comma trap (DRP)
DynamicReferenceParameter serialises its chosen value with a trailing comma, e.g. "manage-owners,". Literal comparisons fail silently. Always strip:
def raw = (binding.variables['ACTION'] ?: '').toString()
def clean = raw.replaceAll(/^[\s,]+|[\s,]+$/, '')
if (clean == 'manage-owners') { ... }
Inside a triple-quoted qaUiScripts body the regex is written as /^|[\\s,]$/ (see escaping contract).
Sandbox approval (sandbox: false)
Our scripts run with sandbox: false because they use enough of the Jenkins API that the restricted sandbox rejects them. That puts every new script body through In-process Script Approval:
-
First build after a script change may silently render the fallback ("Error").
-
Go to
Manage Jenkins → In-process Script Approval(direct link), approve the pending entry, reload the form.
Symptoms that usually mean "not approved" rather than "bug in the script":
-
Only the fallback HTML shows up, with no message in the build console.
-
Any value of
referencedParametersstill shows the fallback. -
Jenkins System Log → filter on
org.biouno.unochoiceorScriptApprovalshows the rejection.
Check the approval list before debugging script logic.
Forcing pending approvals to surface
Jenkins only registers a script hash for approval after the job body actually runs setupParameters() and submits the script. If you changed a vars/*.groovy or qaUiScripts.groovy script body but the approval page shows nothing pending, the job has not re-registered the new hash yet. Force it:
-
Open the job config, e.g.
QA-Environments → <job-name> → Configure— for the Deploy job that isQA-Environments → Deploy-Environment → Configure(config link). -
Untick
This project is parameterizedand Save. This lets you trigger the pipeline directly instead of landing on the parameter form (which would just render the unapproved fallback). -
Click Build Now. The build may fail — that is expected; all we need is for
call()→setupParameters()to run and submit the new script bodies. -
Reload In-process Script Approval and check whether new entries appeared. Approve them.
-
Re-tick
This project is parameterized, Save, and run the job normally — the reactive parameters now render instead of the fallback.
| If step 4 still shows nothing to approve, the script hash is already approved (an identical body was approved before) and the fallback has a different cause — go back to the quiet-failure checklist. |
Referenced parameters + reactivity
Every reactive parameter lists its dependencies in referencedParameters as a comma-separated String:
[$class: 'DynamicReferenceParameter',
name: 'SERVICE_VARIABLES',
script: [
$class: 'GroovyScript',
script: [classpath: [], sandbox: false,
script: qaUiScripts.serviceVariablesScript('create')]
],
referencedParameters: 'DATABASE_TYPE,SELECTED_SERVICES,_UI_STATE',
choiceType: 'ET_FORMATTED_HTML']
Rules:
-
referencedParametersmust be a String, not a List. -
Listed parameters must be declared before the dependent one in the
parameters([…])list. -
Active Choices looks up values via
binding.variables[paramName]inside the body. Never reach forparams.*— that binding does not exist in the parameter sandbox.
Dep graph and gating UI
Several panels are "gated": until the env name (Update / Manage / Deploy) or a required selection is in place, the panel shows a short "pick a value first" banner instead of its main body. This is centralised in qaUiScripts.envGateHtml(body) and envGateChoices(body):
return qaUiScripts.envGateChoices("""
// main body — only reached when ENV_NAME is bound
""")
Consumers can rely on the gate wrapper; they do not need to repeat the env-name check themselves.
When something quietly goes wrong
A checklist that has saved us more than once:
-
Is the script approved? (
Manage Jenkins → In-process Script Approval) -
Did the backslash count halve when the String went through the outer parser? (Use the escaping contract table.)
-
Are all consumers stripping
:selected/:disabledfrom checkbox values and trailing commas from DRP values? -
Is the upstream parameter a
CascadeChoiceParameter(not a DRP)? If the source is a DRP, consumers will never see the change. -
Are all referenced parameters declared before the dependent one?
-
If the body contains GString interpolation: the value was bound at build time, not at render time. Rebuild the job to rebind.