qaUiScripts — Build-time Active Choices Factory

Large Active Choices parameter scripts (DEP_GRAPH, SERVICE_VARIABLES, RESOURCE_ESTIMATE, FINAL_PREVIEW, ALLOW_MISSING_DEPS, GLOBAL_VARIABLES, OWNERS_LIST, OWNERS_PREVIEW, plus shared CSS+JS) are produced by vars/qaUiScripts.groovy and reused across the Create / Update / Delete / Manage jobs.

Build-time composition, not runtime dispatch

Every method on qaUiScripts returns a String. That String is consumed at pipeline build time — inside setupParameters() on the master, while the shared library is loaded — and embedded into an Active Choices parameter:

script: [
    sandbox: false,
    script:  qaUiScripts.depGraphScript()   // build-time composition
]

The returned body then runs later, every time Jenkins re-renders the parameter form, inside the Active Choices sandbox. The shared library is not available there.

Do not reference utils. or qaUiScripts. from inside the returned string. Build-time composition only. A stray utils.foo(…​) inside the body produces a MissingPropertyException: No such property: utils at render time.

For mode-parameterised methods (create vs update), the mode resolves at build time too — no runtime branching inside the sandbox body.

Available methods

Method Mode-parameter Purpose

sharedCssJs()

no

The CSS+JS superset injected via the hidden _UI_STATE parameter. Every reactive panel leans on these classes.

envGateHtml(body)

no

Wrap a panel body in the "pick an env name first" gate used by updateEnvStep, deployEnvStep, and manageEnvStep.

envGateChoices(body)

no

Same gate for CascadeChoice parameters (radio / dropdown / checkbox sources).

depGraphScript()

no

SVG dependency-graph renderer driven by SELECTED_SERVICES and DATABASE_TYPE.

serviceVariablesScript(String mode)

create / update

Per-service .env editor. update mode overlays the current env’s existing values on top.

resourceEstimateScript()

no

CPU / RAM / disk estimate summed from QAConfig.Resources.SERVICE_RESOURCES.

finalPreviewScript(String mode)

create / update

Validation banner + summary. update mode additionally references the CHANGE_DIFF derived parameter.

allowMissingDepsScript()

no

Opt-in checkbox surfaced when findMissingMandatoryDeps() returns non-empty — lets the operator proceed at their own risk.

globalVariablesScript(String mode)

create / update

Rich .env editor. update mode preloads the env’s existing .env.<db> content.

ownersListScript()

no

Multi-checkbox picker of Jenkins logins for manage-owners. Uses a CascadeChoiceParameter + PT_CHECKBOX under the hood.

ownersPreviewScript()

no

Live HTML diff of proposed vs saved meta.owners[].

Build-time vs runtime

These methods run on the Jenkins master during setupParameters() — the shared library is available there. The Active Choices sandbox that executes the returned body later has no access to the shared library. Any utils.* call inside the returned string is a runtime MissingPropertyException.

Cross-job communication via _UI_STATE

sharedCssJs() injects a hidden parameter named _UI_STATE that every reactive panel reads and writes. It carries an aggregate JSON snapshot of the current form so derived parameters (SERVICE_VARIABLES, FINAL_PREVIEW, DEP_GRAPH) can render without chaining through the referencedParameters cascade. This matters because DynamicReferenceParameter cannot drive another parameter (see DRP vs CascadeChoice).

Escaping contract

The strings returned by qaUiScripts. methods live inside Groovy triple-quoted literals ('''…​'''). When the outer parser reads the source file, it processes *one level of escape sequences. What reaches the Active Choices sandbox is what is left over.

Consequence: anything that must survive to the sandbox has to be double-escaped at source.

In the Groovy source file Inside the sandbox body Intended regex

/\\s+/

/\s+/

whitespace class

/\\n/

/\n/

newline

\\"

\"

literal double quote

"${foo}"

"${foo}"

GString interpolation at build time — captures the value of foo in the enclosing step

A single-backslash regex like /\s+/ written inside '''…​''' gets compiled by the outer parser, which reads \s as an unknown escape and silently emits s. That is why the symptom is often a fallback rendered at runtime with no stack trace — the script compiled to something that is not a regex.

String-multiply + GString concat trap

Groovy’s CPS interpreter swallows the inner text when you chain String * int + GString in a single expression:

// BAD — CPS drops the '${envName}' substring
echo ('=' * 40) + "Env: ${envName}" + ('=' * 40)

// GOOD — build explicit String locals first
def bar   = '=' * 40
def title = "Env: ${envName}"
echo bar + title + bar

This trap is not specific to qaUiScripts.groovy, but the "build each String part as a local first" rule shows up most often in the banner / summary renderers.

DRP trailing-comma trap

DynamicReferenceParameter submits values with a trailing comma ("value,"). Anything that compares a DRP value to a string literal must strip leading / trailing commas and whitespace first:

def raw = (binding.variables[paramName] ?: '').toString()
def clean = raw.replaceAll(/^[\s,]+|[\s,]+$/, '')
if (clean == 'manage-owners') { ... }

Inside a triple-quoted qaUiScripts body, that regex needs to be written as /^|[\\s,]$/.

Summary

  • qaUiScripts.* — returns a String, called during setupParameters().

  • Inside that String: no shared library, double-escape slashy regex, mind the _UI_STATE hidden parameter, and strip DRP trailing commas before comparing.

  • Anything structured that multiple panels need should travel through _UI_STATE, not through referencedParameters.