diff --git a/data/src/main/kotlin/io/zeebe/zeeqs/data/entity/BpmnElementType.kt b/data/src/main/kotlin/io/zeebe/zeeqs/data/entity/BpmnElementType.kt index 570be2e6..08ba6586 100644 --- a/data/src/main/kotlin/io/zeebe/zeeqs/data/entity/BpmnElementType.kt +++ b/data/src/main/kotlin/io/zeebe/zeeqs/data/entity/BpmnElementType.kt @@ -25,5 +25,6 @@ enum class BpmnElementType { BUSINESS_RULE_TASK, SCRIPT_TASK, SEND_TASK, - INCLUSIVE_GATEWAY + INCLUSIVE_GATEWAY, + GROUP } diff --git a/data/src/main/kotlin/io/zeebe/zeeqs/data/entity/VariableFilter.kt b/data/src/main/kotlin/io/zeebe/zeeqs/data/entity/VariableFilter.kt new file mode 100644 index 00000000..eaa49231 --- /dev/null +++ b/data/src/main/kotlin/io/zeebe/zeeqs/data/entity/VariableFilter.kt @@ -0,0 +1,12 @@ +package io.zeebe.zeeqs.data.entity + +enum class EqualityOperation { + EQUALS, + CONTAINS +} + +class VariableFilter ( + val name: String, + val value: String, + val equalityOperation: EqualityOperation = EqualityOperation.EQUALS +) \ No newline at end of file diff --git a/data/src/main/kotlin/io/zeebe/zeeqs/data/repository/VariableRepository.kt b/data/src/main/kotlin/io/zeebe/zeeqs/data/repository/VariableRepository.kt index 82c87003..54107210 100644 --- a/data/src/main/kotlin/io/zeebe/zeeqs/data/repository/VariableRepository.kt +++ b/data/src/main/kotlin/io/zeebe/zeeqs/data/repository/VariableRepository.kt @@ -17,4 +17,8 @@ interface VariableRepository : PagingAndSortingRepository { @Transactional(readOnly = true) fun findByProcessInstanceKeyInAndName(processInstanceKey: List, name: String): List + + @Transactional(readOnly = true) + fun findByProcessInstanceKeyInAndNameIn(processInstanceKey: List, name: List): List + } \ No newline at end of file diff --git a/data/src/main/kotlin/io/zeebe/zeeqs/data/service/BpmnElementExtensionProperties.kt b/data/src/main/kotlin/io/zeebe/zeeqs/data/service/BpmnElementExtensionProperties.kt new file mode 100644 index 00000000..5bc1f02e --- /dev/null +++ b/data/src/main/kotlin/io/zeebe/zeeqs/data/service/BpmnElementExtensionProperties.kt @@ -0,0 +1,6 @@ +package io.zeebe.zeeqs.data.service + +data class BpmnElementExtensionProperties ( + val name: String? = null, + val value: String? = null, +) \ No newline at end of file diff --git a/data/src/main/kotlin/io/zeebe/zeeqs/data/service/BpmnElementInfo.kt b/data/src/main/kotlin/io/zeebe/zeeqs/data/service/BpmnElementInfo.kt index f3d786ea..74520c5a 100644 --- a/data/src/main/kotlin/io/zeebe/zeeqs/data/service/BpmnElementInfo.kt +++ b/data/src/main/kotlin/io/zeebe/zeeqs/data/service/BpmnElementInfo.kt @@ -1,10 +1,14 @@ package io.zeebe.zeeqs.data.service +import io.camunda.zeebe.model.bpmn.instance.Documentation +import io.camunda.zeebe.model.bpmn.instance.zeebe.ZeebeProperty import io.zeebe.zeeqs.data.entity.BpmnElementType data class BpmnElementInfo( val elementId: String, val elementName: String?, val elementType: BpmnElementType, - val metadata: BpmnElementMetadata + val metadata: BpmnElementMetadata, + val extensionProperties: Collection?, + val documentation: String? ) \ No newline at end of file diff --git a/data/src/main/kotlin/io/zeebe/zeeqs/data/service/ProcessInstanceService.kt b/data/src/main/kotlin/io/zeebe/zeeqs/data/service/ProcessInstanceService.kt index 24c871ab..e9d0468c 100644 --- a/data/src/main/kotlin/io/zeebe/zeeqs/data/service/ProcessInstanceService.kt +++ b/data/src/main/kotlin/io/zeebe/zeeqs/data/service/ProcessInstanceService.kt @@ -1,8 +1,6 @@ package io.zeebe.zeeqs.data.service -import io.zeebe.zeeqs.data.entity.ProcessInstance -import io.zeebe.zeeqs.data.entity.ProcessInstanceState -import io.zeebe.zeeqs.data.entity.Variable +import io.zeebe.zeeqs.data.entity.* import io.zeebe.zeeqs.data.repository.ProcessInstanceKeyOnly import io.zeebe.zeeqs.data.repository.ProcessInstanceRepository import io.zeebe.zeeqs.data.repository.VariableRepository @@ -14,26 +12,35 @@ class ProcessInstanceService( private val processInstancesRepository: ProcessInstanceRepository, private val variableRepository: VariableRepository) { - private fun getVariables(stateIn: List, variableName: String, variableValue: String): List { + private fun getVariables(stateIn: List, variables: List): List { val processInstances = processInstancesRepository.findByStateIn(stateIn).toList(); - return getVariablesByProcessInstanceKeys(processInstances, variableName, variableValue); + return getVariablesByProcessInstanceKeys(processInstances, variables); } - private fun getVariables(stateIn: List, processDefinitionKey: Long, variableName: String, variableValue: String): List { + private fun getVariables(stateIn: List, processDefinitionKey: Long, variables: List): List { val processInstances = processInstancesRepository.findByProcessDefinitionKeyAndStateIn(processDefinitionKey, stateIn).toList(); - return getVariablesByProcessInstanceKeys(processInstances, variableName, variableValue); + return getVariablesByProcessInstanceKeys(processInstances, variables); } - private fun getVariablesByProcessInstanceKeys(processInstances: List, variableName: String, variableValue: String): List { - val variables = variableRepository.findByProcessInstanceKeyInAndName(processInstances.map { it.getKey() }, variableName); - val filteredVariables = variables.filter { it.value == variableValue }; + private fun getVariablesByProcessInstanceKeys(processInstances: List, variables: List): List { + val variableNames = variables.map { it.name } + val processInstancesKeys = processInstances.map { it.getKey() } + val variablesList = variableRepository.findByProcessInstanceKeyInAndNameIn(processInstancesKeys, variableNames); + val filteredVariables = variablesList.filter { variable -> + variables.any { filter -> + when (filter.equalityOperation) { + EqualityOperation.EQUALS -> variable.name == filter.name && variable.value == filter.value + EqualityOperation.CONTAINS -> variable.name == filter.name && variable.value.contains(filter.value) + } + } + } return filteredVariables; } - fun getProcessInstances(perPage: Int, page: Int, stateIn: List, variableName: String?, variableValue: String?): List { - if(variableName != null && variableValue != null) { - val filteredVariables = getVariables(stateIn, variableName, variableValue); + fun getProcessInstances(perPage: Int, page: Int, stateIn: List, variables: List?): List { + if(!variables.isNullOrEmpty()) { + val filteredVariables = getVariables(stateIn, variables); val filteredProcessInstances = processInstancesRepository.findByStateInAndKeyIn(stateIn, filteredVariables.map { it.processInstanceKey }, PageRequest.of(page, perPage)).toList(); return filteredProcessInstances; } @@ -42,9 +49,9 @@ class ProcessInstanceService( } } - fun countProcessInstances(stateIn: List, variableName: String?, variableValue: String?): Long { - if(variableName != null && variableValue != null) { - val filteredVariables = getVariables(stateIn, variableName, variableValue); + fun countProcessInstances(stateIn: List, variables: List?): Long { + if(!variables.isNullOrEmpty()) { + val filteredVariables = getVariables(stateIn, variables); return filteredVariables.count().toLong(); } @@ -54,25 +61,25 @@ class ProcessInstanceService( } - fun getProcessInstances(perPage: Int, page: Int, stateIn: List, processDefinitionKey: Long, variableName: String?, variableValue: String?): List { - if(variableName != null && variableValue != null) { - val filteredVariables = getVariables(stateIn, processDefinitionKey, variableName, variableValue); + fun getProcessInstances(perPage: Int, page: Int, stateIn: List, processDefinitionKey: Long, variables: List?): List { + if(!variables.isNullOrEmpty()) { + val filteredVariables = getVariables(stateIn, processDefinitionKey, variables); val filteredProcessInstances = processInstancesRepository.findByStateInAndKeyIn(stateIn, filteredVariables.map { it.processInstanceKey }, PageRequest.of(page, perPage)).toList(); return filteredProcessInstances; } else { - return processInstancesRepository.findByStateIn(stateIn, PageRequest.of(page, perPage)).toList(); + return processInstancesRepository.findByProcessDefinitionKeyAndStateIn(processDefinitionKey, stateIn, PageRequest.of(page, perPage)).toList(); } } - fun countProcessInstances(stateIn: List, processDefinitionKey: Long, variableName: String?, variableValue: String?): Long { - if(variableName != null && variableValue != null) { - val filteredVariables = getVariables(stateIn, processDefinitionKey, variableName, variableValue); + fun countProcessInstances(stateIn: List, processDefinitionKey: Long, variables: List?): Long { + if(!variables.isNullOrEmpty()) { + val filteredVariables = getVariables(stateIn, processDefinitionKey, variables); return filteredVariables.count().toLong(); } else { - return processInstancesRepository.countByStateIn(stateIn); + return processInstancesRepository.countByProcessDefinitionKeyAndStateIn(processDefinitionKey, stateIn); } } diff --git a/data/src/main/kotlin/io/zeebe/zeeqs/data/service/ProcessService.kt b/data/src/main/kotlin/io/zeebe/zeeqs/data/service/ProcessService.kt index 794a692f..192a822e 100644 --- a/data/src/main/kotlin/io/zeebe/zeeqs/data/service/ProcessService.kt +++ b/data/src/main/kotlin/io/zeebe/zeeqs/data/service/ProcessService.kt @@ -20,18 +20,28 @@ class ProcessService(val processRepository: ProcessRepository) { @Cacheable(cacheNames = ["bpmnElementInfo"]) fun getBpmnElementInfo(processDefinitionKey: Long): Map? { return getBpmnModel(processDefinitionKey) - ?.let { it.getModelElementsByType(FlowElement::class.java) } - ?.map { flowElement -> - Pair( - flowElement.id, BpmnElementInfo( - elementId = flowElement.id, - elementName = flowElement.name, - elementType = getBpmnElementType(flowElement), - metadata = getMetadata(flowElement) - ) - ) - } - ?.toMap() + ?.let { model -> + (model.getModelElementsByType(FlowElement::class.java)?.associate { flowElement -> + flowElement.id to BpmnElementInfo( + elementId = flowElement.id, + elementName = flowElement.name, + elementType = getBpmnElementType(flowElement), + metadata = getMetadata(flowElement), + extensionProperties = getExtensionProperties(flowElement), + documentation = getDocumentation(flowElement) + ) + }.orEmpty() + + model.getModelElementsByType(Group::class.java)?.associate { groupElement -> + groupElement.id to BpmnElementInfo( + elementId = groupElement.id, + elementName = groupElement.category?.value ?: "", + elementType = getBpmnElementType(groupElement), + metadata = getMetadata(groupElement), + extensionProperties = getExtensionProperties(groupElement), + documentation = getDocumentation(groupElement) + ) + }.orEmpty()) + } } private fun getBpmnModel(processDefinitionKey: Long): BpmnModelInstance? { @@ -41,7 +51,7 @@ class ProcessService(val processRepository: ProcessRepository) { ?.let { Bpmn.readModelFromStream(it) } } - private fun getBpmnElementType(element: FlowElement): BpmnElementType { + private fun getBpmnElementType(element: BaseElement): BpmnElementType { return when (element.elementType.typeName) { BpmnModelConstants.BPMN_ELEMENT_PROCESS -> BpmnElementType.PROCESS BpmnModelConstants.BPMN_ELEMENT_SUB_PROCESS -> getBpmnSubprocessType(element) @@ -63,11 +73,12 @@ class ProcessService(val processRepository: ProcessRepository) { BpmnModelConstants.BPMN_ELEMENT_BUSINESS_RULE_TASK -> BpmnElementType.BUSINESS_RULE_TASK BpmnModelConstants.BPMN_ELEMENT_SCRIPT_TASK -> BpmnElementType.SCRIPT_TASK BpmnModelConstants.BPMN_ELEMENT_INCLUSIVE_GATEWAY -> BpmnElementType.INCLUSIVE_GATEWAY + BpmnModelConstants.BPMN_ELEMENT_GROUP -> BpmnElementType.GROUP else -> BpmnElementType.UNKNOWN } } - private fun getBpmnSubprocessType(element: FlowElement) = + private fun getBpmnSubprocessType(element: BaseElement) = if (element is SubProcess) { if (element.triggeredByEvent()) { BpmnElementType.EVENT_SUB_PROCESS @@ -78,7 +89,7 @@ class ProcessService(val processRepository: ProcessRepository) { BpmnElementType.UNKNOWN } - private fun getMetadata(element: FlowElement): BpmnElementMetadata { + private fun getMetadata(element: BaseElement): BpmnElementMetadata { return BpmnElementMetadata( jobType = element .getSingleExtensionElement(ZeebeTaskDefinition::class.java) @@ -156,6 +167,23 @@ class ProcessService(val processRepository: ProcessRepository) { ) } + private fun getExtensionProperties(element: BaseElement): Collection? { + return element.extensionElements?.elementsQuery + ?.filterByType(ZeebeProperties::class.java) + ?.findSingleResult() + ?.map { properties -> + properties.properties?.map { property -> + BpmnElementExtensionProperties(name = property.name, value = property.value) + } + } + ?.orElse(null) + } + + + private fun getDocumentation(element: BaseElement): String { + return element.documentations.joinToString(separator = "") { it.textContent } + } + @Cacheable(cacheNames = ["userTaskForm"]) fun getForm(processDefinitionKey: Long, formKey: String): String? { return getBpmnModel(processDefinitionKey) diff --git a/data/src/test/kotlin/io/zeebe/zeeqs/ProcessServiceTest.kt b/data/src/test/kotlin/io/zeebe/zeeqs/ProcessServiceTest.kt index 337243b1..2ca531f5 100644 --- a/data/src/test/kotlin/io/zeebe/zeeqs/ProcessServiceTest.kt +++ b/data/src/test/kotlin/io/zeebe/zeeqs/ProcessServiceTest.kt @@ -52,12 +52,12 @@ class ProcessServiceTest( // then assertThat(info) .isNotNull() - .contains(entry("s", BpmnElementInfo("s", "start", BpmnElementType.START_EVENT, BpmnElementMetadata()))) - .contains(entry("t", BpmnElementInfo("t", "task", BpmnElementType.SERVICE_TASK, BpmnElementMetadata(jobType = "test")))) + .contains(entry("s", BpmnElementInfo("s", "start", BpmnElementType.START_EVENT, BpmnElementMetadata(), null, ""))) + .contains(entry("t", BpmnElementInfo("t", "task", BpmnElementType.SERVICE_TASK, BpmnElementMetadata(jobType = "test"), null, ""))) .contains(entry("u", BpmnElementInfo("u", "userTask", BpmnElementType.USER_TASK, BpmnElementMetadata( - userTaskAssignmentDefinition = UserTaskAssignmentDefinition(assignee = "user1", candidateGroups = "group1")))) + userTaskAssignmentDefinition = UserTaskAssignmentDefinition(assignee = "user1", candidateGroups = "group1")), null, "")) ) - .contains(entry("e", BpmnElementInfo("e", null, BpmnElementType.END_EVENT, BpmnElementMetadata()))) + .contains(entry("e", BpmnElementInfo("e", null, BpmnElementType.END_EVENT, BpmnElementMetadata(), null, ""))) } @Test @@ -90,7 +90,8 @@ class ProcessServiceTest( key = "camunda-forms:bpmn:form_A", resource = """{"x":1}""" ) - ) + ), + null, "" ) ) } diff --git a/graphql-api/src/main/kotlin/io/zeebe/zeeqs/graphql/resolvers/query/ProcessInstanceQueryResolver.kt b/graphql-api/src/main/kotlin/io/zeebe/zeeqs/graphql/resolvers/query/ProcessInstanceQueryResolver.kt index 7310f050..4b7af6c8 100644 --- a/graphql-api/src/main/kotlin/io/zeebe/zeeqs/graphql/resolvers/query/ProcessInstanceQueryResolver.kt +++ b/graphql-api/src/main/kotlin/io/zeebe/zeeqs/graphql/resolvers/query/ProcessInstanceQueryResolver.kt @@ -2,6 +2,7 @@ package io.zeebe.zeeqs.graphql.resolvers.query import io.zeebe.zeeqs.data.entity.ProcessInstance import io.zeebe.zeeqs.data.entity.ProcessInstanceState +import io.zeebe.zeeqs.data.entity.VariableFilter import io.zeebe.zeeqs.data.repository.ProcessInstanceRepository import io.zeebe.zeeqs.data.service.ProcessInstanceService import io.zeebe.zeeqs.graphql.resolvers.connection.ProcessInstanceConnection @@ -22,12 +23,11 @@ class ProcessInstanceQueryResolver( @Argument perPage: Int, @Argument page: Int, @Argument stateIn: List, - @Argument variableName: String?, - @Argument variableValue: String? + @Argument variables: List? ): ProcessInstanceConnection { return ProcessInstanceConnection( - getItems = { processInstanceService.getProcessInstances(perPage, page, stateIn, variableName, variableValue) }, - getCount = { processInstanceService.countProcessInstances(stateIn, variableName, variableValue) } + getItems = { processInstanceService.getProcessInstances(perPage, page, stateIn, variables) }, + getCount = { processInstanceService.countProcessInstances(stateIn, variables) } ) } diff --git a/graphql-api/src/main/kotlin/io/zeebe/zeeqs/graphql/resolvers/type/BpmnElementResolver.kt b/graphql-api/src/main/kotlin/io/zeebe/zeeqs/graphql/resolvers/type/BpmnElementResolver.kt index b867d74b..1704a716 100644 --- a/graphql-api/src/main/kotlin/io/zeebe/zeeqs/graphql/resolvers/type/BpmnElementResolver.kt +++ b/graphql-api/src/main/kotlin/io/zeebe/zeeqs/graphql/resolvers/type/BpmnElementResolver.kt @@ -5,6 +5,7 @@ import io.zeebe.zeeqs.data.entity.ElementInstanceState import io.zeebe.zeeqs.data.entity.Process import io.zeebe.zeeqs.data.repository.ElementInstanceRepository import io.zeebe.zeeqs.data.repository.ProcessRepository +import io.zeebe.zeeqs.data.service.BpmnElementExtensionProperties import io.zeebe.zeeqs.data.service.BpmnElementInfo import io.zeebe.zeeqs.data.service.BpmnElementMetadata import io.zeebe.zeeqs.data.service.ProcessService @@ -41,6 +42,21 @@ class BpmnElementResolver( ?: BpmnElementMetadata() } + + @SchemaMapping(typeName = "BpmnElement", field = "extensionProperties") + fun extensionProperties(element: BpmnElement): Collection? { + return findElementInfo(element) + ?.extensionProperties + } + + + @SchemaMapping(typeName = "BpmnElement", field = "documentation") + fun documentation(element: BpmnElement): String? { + return findElementInfo(element) + ?.documentation + + } + @SchemaMapping(typeName = "BpmnElement", field = "process") fun process(element: BpmnElement): Process? { return processRepository.findByIdOrNull(element.processDefinitionKey) diff --git a/graphql-api/src/main/kotlin/io/zeebe/zeeqs/graphql/resolvers/type/ProcessResolver.kt b/graphql-api/src/main/kotlin/io/zeebe/zeeqs/graphql/resolvers/type/ProcessResolver.kt index 98172e21..1a28c8eb 100644 --- a/graphql-api/src/main/kotlin/io/zeebe/zeeqs/graphql/resolvers/type/ProcessResolver.kt +++ b/graphql-api/src/main/kotlin/io/zeebe/zeeqs/graphql/resolvers/type/ProcessResolver.kt @@ -30,8 +30,7 @@ class ProcessResolver( @Argument perPage: Int, @Argument page: Int, @Argument stateIn: List, - @Argument variableName: String?, - @Argument variableValue: String? + @Argument variables: List? ): ProcessInstanceConnection { return ProcessInstanceConnection( getItems = { @@ -40,16 +39,14 @@ class ProcessResolver( page, stateIn, process.key, - variableName, - variableValue + variables ).toList() }, getCount = { processInstanceService.countProcessInstances( stateIn, process.key, - variableName, - variableValue + variables ) } ) diff --git a/graphql-api/src/main/resources/graphql/Element.graphqls b/graphql-api/src/main/resources/graphql/Element.graphqls index 4e84b9e2..3209d162 100644 --- a/graphql-api/src/main/resources/graphql/Element.graphqls +++ b/graphql-api/src/main/resources/graphql/Element.graphqls @@ -10,6 +10,12 @@ type BpmnElement { # the metadata of the BPMN element metadata: BpmnElementMetadata! + # extension properties of the BPMN element + extensionProperties: [BpmnElementExtensionProperties] + + # documentation of the BPMN element + documentation: String + # the process that contains the BPMN element process: Process # the instances of the BPMN element @@ -46,6 +52,7 @@ enum BpmnElementType { SCRIPT_TASK SEND_TASK INCLUSIVE_GATEWAY + GROUP } # Additional metadata that are defined statically on the BPMN element. @@ -84,4 +91,11 @@ type UserTaskAssignmentDefinition { assignee: String # the candidate groups candidateGroups: String +} + +type BpmnElementExtensionProperties { + # the name of property + name: String + # the value of property + value: String } \ No newline at end of file diff --git a/graphql-api/src/main/resources/graphql/Process.graphqls b/graphql-api/src/main/resources/graphql/Process.graphqls index d182d2bd..78c65ba6 100644 --- a/graphql-api/src/main/resources/graphql/Process.graphqls +++ b/graphql-api/src/main/resources/graphql/Process.graphqls @@ -14,8 +14,7 @@ type Process { perPage: Int = 10, page: Int = 0, stateIn: [ProcessInstanceState!] = [ACTIVATED, COMPLETED, TERMINATED] - variableName: String = null, - variableValue: String = null): ProcessInstanceConnection! + variables: [VariableFilter] = null): ProcessInstanceConnection! # the scheduled timers of the timer start events of the process timers: [Timer!] # the opened message subscriptions of the message start events of the process diff --git a/graphql-api/src/main/resources/graphql/ProcessInstance.graphqls b/graphql-api/src/main/resources/graphql/ProcessInstance.graphqls index ff7afe3e..470fedec 100644 --- a/graphql-api/src/main/resources/graphql/ProcessInstance.graphqls +++ b/graphql-api/src/main/resources/graphql/ProcessInstance.graphqls @@ -74,8 +74,7 @@ type Query { perPage: Int = 10, page: Int = 0, stateIn: [ProcessInstanceState!] = [ACTIVATED, COMPLETED, TERMINATED], - variableName: String = null, - variableValue: String = null + variables: [VariableFilter] = null ): ProcessInstanceConnection! } diff --git a/graphql-api/src/main/resources/graphql/Variable.graphqls b/graphql-api/src/main/resources/graphql/Variable.graphqls index 29e38f97..a6d467ff 100644 --- a/graphql-api/src/main/resources/graphql/Variable.graphqls +++ b/graphql-api/src/main/resources/graphql/Variable.graphqls @@ -14,3 +14,15 @@ type VariableUpdate { value: String! timestamp(zoneId: String = "Z"): String! } + +input VariableFilter { + name: String!, + value: String!, + equalityOperation: EqualityOperation = EQUALS +} + +# The type of a variable value filter +enum EqualityOperation { + EQUALS, + CONTAINS +}