Skip to content

Commit

Permalink
Add output parameters to complex workflows
Browse files Browse the repository at this point in the history
Change-Id: Icdcda4c635aa53e759e23e82e7b998da16b63045
Partial-Bug: #1783120
  • Loading branch information
lukasz committed Aug 2, 2018
1 parent c7d7791 commit d15de20
Show file tree
Hide file tree
Showing 12 changed files with 297 additions and 60 deletions.
4 changes: 2 additions & 2 deletions o11nplugin-contrail-config/buildNumber.properties
@@ -1,3 +1,3 @@
#maven.buildNumber.plugin properties file
#Wed Aug 01 17:04:01 CEST 2018
buildNumber=1643
#Thu Aug 02 17:35:22 CEST 2018
buildNumber=1648
@@ -0,0 +1,57 @@
package net.juniper.contrail.vro.tests.dsl

import spock.lang.Specification

class AutomaticOutputSpec extends Specification {

def "Script Item is connected to all nodes previously connected to EndItem"() {
given:
def attributeNames = []
def parameterNames = []
def choices = 2
def inputItems = 2
when: "Complex workflow is created"
def workflow = SomeWorkflowKt.someComplexWorkflowWithInputChoiceAutomaticOutput(choices, inputItems, attributeNames, parameterNames)
then: "All connections previously targeting EndItem now target scriptItem"
def scriptItem = workflow.workflowItems.find { it.type == "task" }
def outConnections = workflow.workflowItems.collect { it.outName }
def switchItems = workflow.workflowItems.findAll { it.type == "switch" }
def allConditions = switchItems.collect { it.conditions }.flatten()
def allLabels = allConditions.collect { it.label }
def scriptConnections = (outConnections + allLabels).findAll { it == scriptItem.name }
scriptConnections.size() == choices*2 + inputItems
}


def "Script Item has all relevant Bindings"() {
given:
def attributeNames = ["attribute1", "attribute2", "attribute3", "attribute4"]
def parameterNames = ["parameter1", "parameter2", "parameter3", "parameter4"]
def choices = 0
def inputItems = 1
when: "Complex workflow is created"
def workflow = SomeWorkflowKt.someComplexWorkflowWithInputChoiceAutomaticOutput(choices, inputItems, attributeNames, parameterNames)
then: "ScriptItem has correct bindings"
def scriptItem = workflow.workflowItems.find { it.type == "task" }

def inBindings = scriptItem.inBinding.binds
def inBindNamesWithAttributeNames = [
inBindings.collect { it.name },
inBindings.collect { it.exportName },
attributeNames
].transpose()
def inBindBooleans = inBindNamesWithAttributeNames.collect { it[0] == it[1] && it[1] == it[2] }

def outBindings = scriptItem.outBinding.binds
def outBindNameWithParameterNames = [
outBindings.collect { it.name },
outBindings.collect { it.exportName },
attributeNames,
parameterNames
].transpose()
def outBindBooleans = outBindNameWithParameterNames.collect { it[0] == it[2] && it[1] == it[3] }

inBindBooleans.every { it } && inBindBooleans.size() == attributeNames.size()
outBindBooleans.every { it } && outBindBooleans.size() == attributeNames.size()
}
}
Expand Up @@ -9,11 +9,13 @@ import net.juniper.contrail.vro.workflows.dsl.WorkflowDefinition
import net.juniper.contrail.vro.workflows.dsl.withComplexParameters
import net.juniper.contrail.vro.workflows.dsl.withScript
import net.juniper.contrail.vro.workflows.dsl.workflow
import net.juniper.contrail.vro.workflows.dsl.workflowEndItemId
import net.juniper.contrail.vro.workflows.model.string

val start = 1
val choice1 = 2
val choice2 = 3
val item = 4
val workflowDefinitions = mutableListOf<WorkflowDefinition>()

fun someComplexWorkflow() : WorkflowDefinition {
Expand Down Expand Up @@ -44,9 +46,31 @@ fun someWorkflowWithInputItem(attributeNames: List<String> = listOf("attribute1"
//The Attribute type here is arbitrary but has to be the same for all attributes because the parameter() function doesn't support polymorphism dependent on type.
attributeNames.forEach { attribute(it, string) }

addWorkflowItemWithAttributes(start, END.id) {
attributeInput(start, END.id) {
attributeNames.forEach { parameter(it, string) {} }
}

}
}
}

fun someComplexWorkflowWithInputChoiceAutomaticOutput(choices: Int = 0, inputItems: Int = 1, attributeNames: List<String> = listOf("Attribute1"), parameterNames: List<String> = listOf("Parameter1")): WorkflowDefinition {
return workflow("Some complex workflow").withComplexParameters(1, workflowDefinitions) {
require(choices > 0 || inputItems > 0) { "There must be at least 1 Item with a connection to EndItem in this workflow" }

attributeNames.forEach { attribute(it, string) }

(1..choices).forEach {
choice(it, workflowEndItemId, "") {
option("", workflowEndItemId)
}
}

(1..inputItems).forEach { attributeInput(choices + it, workflowEndItemId) {} }

val attributeAndParameterPairs = attributeNames.zip(parameterNames)

automaticWorkflowOutput {
attributeAndParameterPairs.forEach { output(it.first, it.second) }
}
}
}
Expand Up @@ -30,4 +30,14 @@ class BindAggregator {

val inBinds: Map<String, String> = _inBinds
val outBinds: Map<String, String> = _outBinds
}

@WorkflowBuilder
class OutputAggregator {
private val _output: MutableMap<String, String> = mutableMapOf()
fun output(attribute: String, name: String) {
_output[attribute] = name
}

val outputs: Map<String, String> = _output
}
Expand Up @@ -5,25 +5,28 @@
package net.juniper.contrail.vro.workflows.dsl

import net.juniper.contrail.vro.config.constants.basePackageName
import net.juniper.contrail.vro.workflows.model.Attribute
import net.juniper.contrail.vro.workflows.model.AttributeDefinition
import net.juniper.contrail.vro.workflows.model.Bind
import net.juniper.contrail.vro.workflows.model.Binding
import net.juniper.contrail.vro.workflows.model.DefaultCondition
import net.juniper.contrail.vro.workflows.model.EqualsCondition
import net.juniper.contrail.vro.workflows.model.DefaultConditionDefinition
import net.juniper.contrail.vro.workflows.model.EqualsConditionDefinition
import net.juniper.contrail.vro.workflows.model.Parameter
import net.juniper.contrail.vro.workflows.model.ParameterType
import net.juniper.contrail.vro.workflows.model.Script
import net.juniper.contrail.vro.workflows.model.WorkflowItemDefinition
import net.juniper.contrail.vro.workflows.model.bindAttributes
import net.juniper.contrail.vro.workflows.model.existsConnectedToEnd
import net.juniper.contrail.vro.workflows.model.replaceEndLabels
import net.juniper.contrail.vro.workflows.model.string
import net.juniper.contrail.vro.workflows.model.toFullItemId
import net.juniper.contrail.vro.workflows.util.generateID

// TODO: Output parameters (I see no way to do this other than dummy script object with relevant bindings)
@WorkflowBuilder
class ComplexWorkflowBuilder(
private val workflowDefinitions: List<WorkflowDefinition>,
val items: MutableList<WorkflowItemDefinition> = mutableListOf(),
val attributes: MutableList<Attribute> = mutableListOf()
val attributes: MutableList<AttributeDefinition> = mutableListOf(),
val outputParameters: MutableList<ParameterInfo> = mutableListOf()
) {
private var baseFreeId = 1000000
private fun nextId(): Int {
Expand All @@ -32,7 +35,7 @@ class ComplexWorkflowBuilder(
}

fun <T : Any> attribute (attributeName: String, attributeType: ParameterType<T>, description: String? = null) {
attributes.add(Attribute(attributeName, attributeType, description))
attributes.add(AttributeDefinition(attributeName, attributeType, description))
}

fun workflowInvocation(itemId: Int, outItemId: Int, workflowName: String, workflowPackage: String = basePackageName, setup: BindAggregator.() -> Unit) {
Expand All @@ -57,6 +60,7 @@ class ComplexWorkflowBuilder(
outputMapping,
attributePrefix
)

val newAttributes = invocationParameters.newAttributes
val workflowInputBinding = invocationParameters.workflowInputBinding
val workflowOutputBinding = invocationParameters.workflowOutputBinding
Expand Down Expand Up @@ -97,7 +101,7 @@ class ComplexWorkflowBuilder(
val options = aggregator.choices

val decisionInput = "Decision_$itemId"
attributes.add(Attribute(decisionInput, string))
attributes.add(AttributeDefinition(decisionInput, string))

items.add(inputWorkflowItem(
itemId,
Expand All @@ -111,16 +115,59 @@ class ComplexWorkflowBuilder(
predefinedAnswers = options.map { it.name }
}
})
val conditions = options.map {
EqualsConditionDefinition(decisionInput, it.name, string, it.targetId.toFullItemId)
} + DefaultConditionDefinition(defaultOut.toFullItemId)

items.add(switchWorkflowItem(
switchId,
Binding(listOf(Bind(decisionInput, string, decisionInput))),
options.map {
EqualsCondition(decisionInput, it.name, string, it.targetId.toFullItemId)
} + DefaultCondition(defaultOut.toFullItemId)
conditions
))
}

fun addWorkflowItemWithAttributes(itemId: Int, outItemId: Int, parameterDefinitions: ParameterAggregator.() -> Unit = {}) {
fun automaticWorkflowOutput(setup: OutputAggregator.() -> Unit) {
val itemsConnectedToEnd = items.filter { it.isConnectedToEnd() }
val itemsWithConditionToEnd = items.filter { it.withConditionToEnd() }
if (itemsConnectedToEnd.isEmpty() && itemsWithConditionToEnd.isEmpty()) throw IllegalStateException("There are no workflowItems connected to EndItem")

val itemId = nextId()

val newItems = items.map {
when {
it.isConnectedToEnd() -> it.copy(outItemId = itemId)
it.withConditionToEnd() -> it.copy(conditions = it.conditions!!.replaceEndLabels(itemId))
else -> it
}
}

items.clear()
items.addAll(newItems)
workflowOutput(itemId, setup)
}

private fun workflowOutput(itemId: Int, setup: OutputAggregator.() -> Unit) {
val aggregator = OutputAggregator().apply(setup)
val outputs = aggregator.outputs
val attributeNames = attributes.map { it.name }
val outputAttributeNames = outputs.keys
if (!outputAttributeNames.all { it in attributeNames }) throw IllegalArgumentException("Given attributes don't exist in this workflow")

val attributesToBind = attributes.filter { it.name in outputAttributeNames }
attributesToBind.forEach { outputParameters.add(ParameterInfo(outputs[it.name]!!, it.type)) }
val inputBinds = Binding(attributesToBind.map { Bind(it.name, it.type, it.name, "" ) })
val outputBinds = Binding(attributesToBind.map { Bind(it.name, it.type, outputs[it.name]!!, "") })

items.add(scriptWorkflowItem(
itemId,
Script(""),
inputBinds,
outputBinds,
workflowEndItemId
))
}

fun attributeInput(itemId: Int, outItemId: Int, parameterDefinitions: ParameterAggregator.() -> Unit = {}) {
if (itemId > baseFreeId) throw IllegalArgumentException("Use ID lower than $baseFreeId")

val parameters = mutableListOf<ParameterInfo>()
Expand All @@ -146,7 +193,7 @@ class ComplexWorkflowBuilder(
class WorkflowInvocationParameters(
val workflowInputBinding: Binding,
val workflowOutputBinding: Binding,
val newAttributes: List<Attribute>
val newAttributes: List<AttributeDefinition>
)

private fun prepareWorkflowInvocationParameters(
Expand All @@ -173,6 +220,7 @@ class ComplexWorkflowBuilder(
}

val workflowInputBinding = Binding(interactionParamBinds + externalBinds)

val workflowOutputBinding = Binding(
boundOutputParams.map {
val exportName = outputMapping[it.name]!!
Expand All @@ -182,12 +230,12 @@ class ComplexWorkflowBuilder(
val newInputAttributes =
unboundInputParams.map {
val attributeName = attributeName(attributePrefix, it.name)
it.asAttribute(attributeName)
it.asAttributeDefinition(attributeName)
}
val newOutputAttributes =
unboundOutputParams.map {
val attributeName = attributeName(attributePrefix, it.name)
it.asAttribute(attributeName)
it.asAttributeDefinition(attributeName)
}

return WorkflowInvocationParameters(
Expand All @@ -201,12 +249,16 @@ class ComplexWorkflowBuilder(

private fun Parameter.asBindWithExportName(exportName: String) = toParameterInfo().asBindWithExportName(exportName)

private fun Parameter.asAttribute(attributeName: String): Attribute {
private fun Parameter.asAttributeDefinition(attributeName: String): AttributeDefinition {
val parameterInfo = toParameterInfo()
return Attribute(
return AttributeDefinition(
attributeName,
parameterInfo.type,
parameterInfo.description
)
}
}

private fun WorkflowItemDefinition.isConnectedToEnd() = outItemId == workflowEndItemId

private fun WorkflowItemDefinition.withConditionToEnd() = conditions != null && conditions.existsConnectedToEnd
}
Expand Up @@ -14,7 +14,7 @@ class PresentationParametersBuilder(
parameters: MutableList<ParameterInfo>,
allParameters: MutableList<ParameterInfo>,
private val outputParameters: MutableList<ParameterInfo>,
private val attributes: MutableList<Attribute>
private val attributes: MutableList<AttributeDefinition>
) : ParameterAggregator(parameters, allParameters) {

fun step(title: String, setup: ParameterAggregator.() -> Unit) {
Expand Down Expand Up @@ -298,5 +298,5 @@ class AttributeBuilder(val name: String, val type: ParameterType<Any>) {
var readOnly: Boolean = false

fun buildAttribute() =
Attribute(name, type, description, readOnly)
AttributeDefinition(name, type, description, readOnly)
}
Expand Up @@ -5,14 +5,13 @@
package net.juniper.contrail.vro.workflows.dsl

import net.juniper.contrail.vro.workflows.model.Binding
import net.juniper.contrail.vro.workflows.model.Condition
import net.juniper.contrail.vro.workflows.model.ConditionDefinition
import net.juniper.contrail.vro.workflows.model.Position
import net.juniper.contrail.vro.workflows.model.Presentation
import net.juniper.contrail.vro.workflows.model.WorkflowItemType
import net.juniper.contrail.vro.workflows.model.Script
import net.juniper.contrail.vro.workflows.model.WorkflowItemDefinition
import net.juniper.contrail.vro.workflows.model.WorkflowItemType
import net.juniper.contrail.vro.workflows.model.boolean
import net.juniper.contrail.vro.workflows.model.generateSwitchScript

@DslMarker
annotation class WorkflowBuilder
Expand All @@ -32,13 +31,13 @@ fun scriptWorkflowItem(id: Int, script: Script, inBinding: Binding, outBinding:
fun workflowWorkflowItem(id: Int, workflowName: String, workflowId: String, inBinding: Binding, outBinding: Binding, outId: Int) =
WorkflowItemDefinition(id, WorkflowItemType.link, defaultPosition, workflowName, null, inBinding, outBinding, outId, null, null, workflowId)

fun switchWorkflowItem(id: Int, inBinding: Binding, conditions: List<Condition>) =
WorkflowItemDefinition(id, WorkflowItemType.switch, defaultPosition, "Switch", generateSwitchScript(conditions), inBinding, null, null, conditions)
fun switchWorkflowItem(id: Int, inBinding: Binding, conditions: List<ConditionDefinition>) =
WorkflowItemDefinition(id, WorkflowItemType.switch, defaultPosition, "Switch", null, inBinding, null, null, conditions)

fun inputWorkflowItem(id: Int, inBinding: Binding, outBinding: Binding, outId: Int, preparedPresentation: Presentation? = null, parameterDefinitions: ParameterAggregator.() -> Unit = {}): WorkflowItemDefinition {
val parameters = mutableListOf<ParameterInfo>()
val allParameters = mutableListOf<ParameterInfo>()
val presentation = if (preparedPresentation == null) {
val parameters = mutableListOf<ParameterInfo>()
val allParameters = mutableListOf<ParameterInfo>()
ParameterAggregator(parameters, allParameters).apply(parameterDefinitions)
Presentation(parameters.map { it.asPresentationParameter })
} else {
Expand Down

0 comments on commit d15de20

Please sign in to comment.