Page tree

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Business description

In our company, we need to improve our cost process. To make it easier and reduce the number of mistakes, we want to restrict the visibility of the fields and the available options. Moreover, in a few cases there , more explanations are more explains required, when while in the other cases, they are optional.

Solution Brief

To resolve that situation, we will prepare a dynamic form using decision tables. Thanks to that, we could control the visibility or requirement of available fields on the form. Except for setting suitable configuration configurations like screen schemes, we need to create three decision tables. The first one will relate existing Cost Types to available Cost Groups. The second will have similar functionality - it will connect chosen Cost Group to available Cost Categories. The last one will determinate determine the obligatory of set value. For each of them, we will use the Collect as Hit Policy to get all suitable entries.

Example Decision Table

Image Modified

Script

Info
titleNote

In this script, we use some superclasses, which help us in code management. For more information, see Usage of base groovy classes and utilities page.

...

Code Block
languagegroovy
themeRDark
@WithPlugin([
		"eu.rivetgroup.atlas.rvg-jira-app-base-plugin",
		"eu.rivetgroup.atlas.rvg-jira-decision-tables-plugin"
])
class TestCostFormBehaviors extends BaseDecisionTableBehaviors

Firstly, there is an import of required functionalities from add-ons and class definitiondefinitions. Here we extend another base class - BaseDecisionTableBehaviors, which support Which supports behavior in real-time.

Code Block
languagegroovy
themeRDark
issueFieldOperator.config.exceptionHandlingStrategy.valueActiveCheckStrategy
	.setCategoryCheckOn(true)
	.setSourceHandlingMode(ExceptionHandlingMode.IGNORE)

Business requirements tell us, we need to reject some of the available options. To do that, we turn on the active check of values but disable throwing and reporting related to it its exceptions , because we just want to remove unnecessary options silently - it is a purpose purposeful action.

Code Block
languagegroovy
themeRDark
this.costTypeFieldOper = issueFieldOperator.forField(FieldUtils.getCustomField(CustomFieldCode.COST_TYPE))
this.costGroupFieldOper = issueFieldOperator.forField(FieldUtils.getCustomField(CustomFieldCode.COST_GROUP))
this.costCategoryFieldOper = issueFieldOperator.forField(FieldUtils.getCustomField(CustomFieldCode.COST_CATEGORY))
this.amountFieldOper = issueFieldOperator.forField(FieldUtils.getCustomField(CustomFieldCode.AMOUNT))
this.customCostCategoryFieldOper = issueFieldOperator.forField(FieldUtils.getCustomField(CustomFieldCode.CUSTOM_COST_CATEGORY))
this.costReasonFieldOper = issueFieldOperator.forField(FieldUtils.getCustomField(CustomFieldCode.COST_REASON))

References to related fields. We strongly suggest to exclude excluding such relations to the external file, what which will help you get a higher quality.

Thanks to them, we can get possible operations for all fields needed to call decision tables and process the result. Field operations allows to allow taking no care about the particular field type in many scenarios - code is much less complex and easier to read.

Code Block
languagegroovy
themeRDark
setIgnoreInitialFieldBehaviorCallMode([
	this.costTypeFieldOper.field.id,
	this.costGroupFieldOper.field.id,
	this.costCategoryFieldOper.field.id,
	this.amountFieldOper.field.id
])

Used Scriptrunner handle handles all changes of the fields. We don't need to do additional operations during initialize form, which are also handled by the tool. To resolve that problem, we ignore initial calls on purpose and concentrate on the next changes.

Code Block
languagegroovy
themeRDark
if (formInit) {
	processCostGroupOptions()
	processCostCategoryOptions()
	processCustomCostCategoryAndReason()
} else if (fieldChanged == costTypeFieldOper.field.id) {
	processCostGroupOptions()
	processCostCategoryOptions()
	processCustomCostCategoryAndReason()
} else if (fieldChanged == costGroupFieldOper.field.id) {
	processCostCategoryOptions()
	processCustomCostCategoryAndReason()
} else if (fieldChanged == costCategoryFieldOper.field.id) {
	processCustomCostCategoryAndReason()
} else if (fieldChanged == amountFieldOper.field.id) {
	processCustomCostCategoryAndReason()
}

On an any change, we need to react. As you could see above, we have a specific field structure with dependencies. Each function serves another field, so depends on change reason, we have to execute other another sequence depending on the change reason.

Code Block
languagegroovy
themeRDark
def allowedOptions = new LinkedHashSet<Option>()
	if (costTypeVal != null) {
		def dtCostCategoryResult = decisionTableManager.getDMNDecisionTableOperations(
			"costCategories",
			"costGroup"
		).executeQuery([
			"costType": costTypeFieldOper.getBusinessKeyFromObjectValue(costTypeVal).first()
			])
		allowedOptions.addAll(
					costGroupFieldOper.getValueByBusinessKey(
							dtCostCategoryResult.multiNonNull().collect({elem -> elem.get("costGroup")})
		).castElem(Option.class).multiNonNull()
	)
}

Due to getting a list of options, we need to receive not only one possibility. To allowed options, we add all of the found by the decision table engine.


Code Block
languagegroovy
themeRDark
new OptionFieldBehaviorOperator(this, costGroupFieldOper)
	.setRequired(true)
	.setSelectTheOnlyOption(true)
	.setHidden(costTypeVal == null)
	.setForceShowWhenHasValue(true)
	.setAllowedOptions(allowedOptions)
	.processField()

At In the end, we need to make planned changes in form. To do that, we prepare a new operator and process it. During creation we We set a field as a required during creation, block multi-choice possibility, add calculated options, or set visibility based on other filed valuefilled values.


Code Block
languagegroovy
themeRDark
private void processCostCategoryOptions() {

	def costGroupVal = getOptionFromFormValue(getFieldById(costGroupFieldOper.field.id))
	def allowedOptions = new LinkedHashSet<Option>()
	if (costGroupVal != null) {
		def dtCostCategoryResult = decisionTableManager.getDMNDecisionTableOperations(
			"costCategories",
			"costCategory"
		).executeQuery([
			"costGroup": costGroupFieldOper.getBusinessKeyFromObjectValue(costGroupVal).first()
		])
		allowedOptions.addAll(
					costCategoryFieldOper.getValueByBusinessKey(dtCostCategoryResult.multiNonNull().collect({it.get("costCategory")})
					).castElem(Option.class).multiNonNull()
		)
	}
	new OptionFieldBehaviorOperator(this, costCategoryFieldOper)
		.setSelectTheOnlyOption(true)
		.setRequired(true)
		.setAllowNoneOption(true)
		.setHidden(costGroupVal == null)
		.setAllowedOptions(allowedOptions)
		.setForceShowWhenHasValue(true)
		.processField()
}

In similar way Similarly, we process changes on other in another field. Only The only difference is a relations relation - to a suitable decision table or "parent" field.

...

Service the last decision table is more similar to the previous examples. We get chosen values from fields and use them in query queries to decide about fields obligatory.

Code Block
languagegroovy
themeRDark
def isCustomCat = dtCostCategoryResult.multiNonNull().collect({it.get("customCategory")})
				.find{(Boolean.TRUE == it)} != null
def firstEntry = dtCostCategoryResult.first();
def requireCostReason = firstEntry != null && Boolean.TRUE == firstEntry.get("requiresCostReason")

def customCostCategoryBehaviorOper = new BasicFieldBehaviorOperator(this, customCostCategoryFieldOper.field.id)
	.setRequired(isCustomCat)
	.setHidden(!isCustomCat)
	.setForceShowWhenHasValue(true)
if (!formInit && !isCustomCat) {
	customCostCategoryBehaviorOper.setNewObjectValue(null)
}
customCostCategoryBehaviorOper.processField()
getFieldById(costReasonFieldOper.field.id).setRequired(requireCostReason)

Setting requirement of unmentioned field requirements for the unmentioned fields is also easy. Base on the result decision tables job, we can know if the chosen fields should be required. As you see, we use a comparison between the logical value and Boolean.TRUE - thanks to that, we handle null values. Meanwhile, we also set to operator null value - that is the way how to clear custom category when some parent fields were changed.

...

Attachments:

View file
nameTestCostFormBehaviors.groovy
height250

...