diff --git a/system/i18n/preside-objects/cfflow_workflow_instance.properties b/system/i18n/preside-objects/cfflow_workflow_instance.properties index 3a16b52b36..44c9eabb67 100644 --- a/system/i18n/preside-objects/cfflow_workflow_instance.properties +++ b/system/i18n/preside-objects/cfflow_workflow_instance.properties @@ -10,6 +10,7 @@ field.sub_sub_reference.title=Sub reference field.completed.title=Completed field.state.title=State field.instance_histories.title=Instance histories +field.instance_steps.title=Instance steps field.current_step.title=Current step field.current_status.title=Current status field.datecreated.title=Started at diff --git a/system/preside-objects/cfflow/cfflow_workflow_instance.cfc b/system/preside-objects/cfflow/cfflow_workflow_instance.cfc index 08fd0bf78a..3148364ef7 100755 --- a/system/preside-objects/cfflow/cfflow_workflow_instance.cfc +++ b/system/preside-objects/cfflow/cfflow_workflow_instance.cfc @@ -20,7 +20,8 @@ component { property name="state" type="string" dbtype="longtext" autofilter=false; - property name="instance_histories"relationship="one-to-many" relatedto="cfflow_workflow_instance_history" relationshipKey="instance" cloneable=false; + property name="instance_histories" relationship="one-to-many" relatedto="cfflow_workflow_instance_history" relationshipKey="instance" cloneable=false; + property name="instance_steps" relationship="one-to-many" relatedto="cfflow_workflow_instance_step" relationshipKey="instance" cloneable=false; property name="current_step" formula="MAX( ${prefix}instance_histories.result )" batcheditable=false autofilter=false control="none" renderer="webflowInstanceStepTitle"; property name="current_status" formula="${prefix}id" renderer="webflowInstanceStatus"; diff --git a/system/services/rulesEngine/RulesEngineExpressionReaderService.cfc b/system/services/rulesEngine/RulesEngineExpressionReaderService.cfc index 848cbb1dfa..996d69892d 100644 --- a/system/services/rulesEngine/RulesEngineExpressionReaderService.cfc +++ b/system/services/rulesEngine/RulesEngineExpressionReaderService.cfc @@ -92,10 +92,10 @@ component displayName="RulesEngine Expression Reader Service" { */ public struct function getExpressionsFromCfc( required string componentPath, required string rootPath ) { var meta = getComponentMetadata( arguments.componentPath ); - var feature = meta.feature ?: ""; - var category = meta.expressionCategory ?: "default"; - var contexts = ListToArray( meta.expressionContexts ?: "global" ); - var tags = ListToArray( meta.expressionTags ?: "" ); + var feature = _getExtendedMeta( meta, "feature" , "" ); + var category = _getExtendedMeta( meta, "expressionCategory" , "default" ); + var contexts = ListToArray( _getExtendedMeta( meta, "expressionContexts", "global" ) ); + var tags = ListToArray( _getExtendedMeta( meta, "expressionTags" , "" ) ); var contextsEnabled = false; var contextService = _getContextService(); @@ -114,37 +114,27 @@ component displayName="RulesEngine Expression Reader Service" { return {}; } - var functions = meta.functions ?: []; + var functions = _getExtendedFunctionsMeta( meta ); var baseId = arguments.componentPath.replaceNoCase( rootPath, "" ).reReplace( "^\.", "" ); - var filterObjects = []; + var filterObjects = ListToArray( functions.prepareFilters.objects ?: "" ); var expressions = {}; - for( var func in functions ) { - if ( func.name == "evaluateExpression" ) { - expressions[ baseId ] = { - contexts = _getContextService().expandContexts( ListToArray( meta.expressionContexts ?: "global" ) ) - , fields = getExpressionFieldsFromFunctionDefinition( func ) - , filterObjects = filterObjects - , category = category - , tags = tags - , expressionHandler = "rules.expressions.#baseId#.evaluateExpression" - , filterHandler = filterObjects.len() ? "rules.expressions.#baseId#.prepareFilters" : "" - , labelHandler = "rules.expressions.#baseId#.getLabel" - , textHandler = "rules.expressions.#baseId#.getText" - , expressionHandlerArgs = {} - , filterHandlerArgs = {} - , labelHandlerArgs = {} - , textHandlerArgs = {} - }; - - } else if ( func.name == "prepareFilters" ) { - filterObjects = ListToArray( func.objects ?: "" ); - if ( StructKeyExists( expressions, baseId ) ) { - expressions[ baseId ].filterObjects = filterObjects; - expressions[ baseId ].filterHandler = "rules.expressions.#baseId#.prepareFilters"; - break; - } - } + if ( StructKeyExists( functions, "evaluateExpression" ) ) { + expressions[ baseId ] = { + contexts = _getContextService().expandContexts( contexts ) + , fields = getExpressionFieldsFromFunctionDefinition( functions.evaluateExpression ) + , filterObjects = filterObjects + , category = category + , tags = tags + , expressionHandler = "rules.expressions.#baseId#.evaluateExpression" + , filterHandler = filterObjects.len() ? "rules.expressions.#baseId#.prepareFilters" : "" + , labelHandler = "rules.expressions.#baseId#.getLabel" + , textHandler = "rules.expressions.#baseId#.getText" + , expressionHandlerArgs = {} + , filterHandlerArgs = {} + , labelHandlerArgs = {} + , textHandlerArgs = {} + }; } return expressions; @@ -256,6 +246,56 @@ component displayName="RulesEngine Expression Reader Service" { return "text"; } + private any function _getExtendedMeta( required struct metaData, required string key, any defaultValue ){ + if ( StructKeyExists( arguments.metaData, arguments.key ) ) { + return arguments.metadata[ arguments.key ]; + } + + if ( StructKeyExists( arguments.metaData, "extends" ) ) { + return _getExtendedMeta( arguments.metaData.extends, arguments.key, arguments.defaultValue ); + } + + return arguments.defaultValue; + } + + private struct function _getExtendedFunctionsMeta( required struct metaData ){ + var extendedFunctions = {}; + var metaDataFunctions = arguments.metaData.functions ?: []; + + for ( var func in metaDataFunctions ) { + extendedFunctions[ func.name ] = Duplicate( func ); + } + + if ( StructKeyExists( arguments.metaData, "extends" ) ) { + var parentFunctions = _getExtendedFunctionsMeta( arguments.metaData.extends ); + + for ( var parentFunctionName in parentFunctions ) { + if ( StructKeyExists( extendedFunctions, parentFunctionName ) ) { + var extendedFunc = extendedFunctions[ parentFunctionName ]; + var parentFunc = parentFunctions[ parentFunctionName ]; + + StructAppend( extendedFunc, parentFunc, false ); + + for ( var parentParam in parentFunc.parameters ) { + var extendedParamIndex = ArrayFind( extendedFunc.parameters, function( param ){ + return param.name == parentParam.name; + } ); + + if ( extendedParamIndex ) { + StructAppend( extendedFunc.parameters[ extendedParamIndex ], parentParam, false ) + } else { + ArrayAppend( extendedFunc.parameters, parentParam ); + } + } + } else { + extendedFunctions[ parentFunctionName ] = parentFunctions[ parentFunctionName ]; + } + } + } + + return extendedFunctions; + } + // GETTERS AND SETTERS private any function _getContextService() { return _contextService; diff --git a/system/services/webflow/WebflowInstanceService.cfc b/system/services/webflow/WebflowInstanceService.cfc index abc2d94501..b915a638ea 100755 --- a/system/services/webflow/WebflowInstanceService.cfc +++ b/system/services/webflow/WebflowInstanceService.cfc @@ -622,8 +622,11 @@ component { } ); } else { ArrayAppend( extraFilters, { - having = "GROUP_CONCAT( DISTINCT instance_histories.result ) LIKE :stepId#paramSuffix#" - , filterParams = { "stepId#paramSuffix#"={ type="cf_sql_varchar", value="%#arguments.webflowStep#" } } + filter = "instance_steps.step = :stepId#paramSuffix# AND instance_steps.status = :stepStatus#paramSuffix#" + , filterParams = { + "stepId#paramSuffix#" = { type="cf_sql_varchar", value=arguments.webflowStep } + , "stepStatus#paramSuffix#" = { type="cf_sql_varchar", value="active" } + } } ); } } diff --git a/tests/unit/api/rulesEngine/RulesEngineExpressionReaderServiceTest.cfc b/tests/unit/api/rulesEngine/RulesEngineExpressionReaderServiceTest.cfc index 16efa4824f..3a2d6eb2a2 100644 --- a/tests/unit/api/rulesEngine/RulesEngineExpressionReaderServiceTest.cfc +++ b/tests/unit/api/rulesEngine/RulesEngineExpressionReaderServiceTest.cfc @@ -50,7 +50,7 @@ component extends="resources.HelperObjects.PresideBddTestCase" { var meta = GetComponentMetadata( cfc ); var dummyDefs = { test=CreateUUId() }; - service.$( "getExpressionFieldsFromFunctionDefinition" ).$args( meta.functions[1] ).$results( dummyDefs ); + service.$( "getExpressionFieldsFromFunctionDefinition" ).$args( Duplicate( meta.functions[1] ) ).$results( dummyDefs ); var expressions = service.getExpressionsFromCfc( componentPath=cfc, rootPath=rootPath ); @@ -93,7 +93,7 @@ component extends="resources.HelperObjects.PresideBddTestCase" { var meta = GetComponentMetadata( cfc ); var dummyDefs = { test=CreateUUId() }; - service.$( "getExpressionFieldsFromFunctionDefinition" ).$args( meta.functions[1] ).$results( dummyDefs ); + service.$( "getExpressionFieldsFromFunctionDefinition" ).$args( Duplicate( meta.functions[1] ) ).$results( dummyDefs ); var expressions = service.getExpressionsFromCfc( componentPath=cfc, rootPath=rootPath ); @@ -107,7 +107,7 @@ component extends="resources.HelperObjects.PresideBddTestCase" { var meta = GetComponentMetadata( cfc ); var dummyDefs = { test=CreateUUId() }; - service.$( "getExpressionFieldsFromFunctionDefinition" ).$args( meta.functions[1] ).$results( dummyDefs ); + service.$( "getExpressionFieldsFromFunctionDefinition" ).$args( Duplicate( meta.functions[1] ) ).$results( dummyDefs ); var expressions = service.getExpressionsFromCfc( componentPath=cfc, rootPath=rootPath );