From 2f31eac4990363f4bb2122f186d4f0370be2a9f3 Mon Sep 17 00:00:00 2001 From: Patrick Schalk Date: Mon, 1 Jul 2024 15:08:55 +0200 Subject: [PATCH] Add JPA Queries for task attribute names + values #843 --- docs/reference-guide/components/view-api.md | 28 ++++---- .../src/main/kotlin/VariableSerializer.kt | 2 +- .../view/jpa/JpaPolyflowViewTaskService.kt | 27 +++++++ .../polyflow/view/jpa/task/TaskRepository.kt | 33 ++++++++- .../jpa/JpaPolyflowViewServiceTaskITest.kt | 32 ++++++++- .../simple/service/SimpleTaskPoolService.kt | 48 +++++++++++++ .../service/SimpleTaskPoolServiceTest.kt | 72 +++++++++++++++++++ .../view/simple/service/TaskPoolStages.kt | 43 +++++++++++ .../src/main/kotlin/QueryGatewayExt.kt | 10 +++ .../src/main/kotlin/TaskQueryClient.kt | 18 +++++ .../main/kotlin/query/task/AllTasksQuery.kt | 2 - .../task/AllTasksWithDataEntriesQuery.kt | 1 - .../src/main/kotlin/query/task/TaskApi.kt | 10 +++ .../query/task/TaskAttributeNamesQuery.kt | 21 ++++++ .../task/TaskAttributeNamesQueryResult.kt | 14 ++++ .../query/task/TaskAttributeValuesQuery.kt | 34 +++++++++ .../task/TaskAttributeValuesQueryResult.kt | 15 ++++ 17 files changed, 391 insertions(+), 19 deletions(-) create mode 100755 view/view-api/src/main/kotlin/query/task/TaskAttributeNamesQuery.kt create mode 100644 view/view-api/src/main/kotlin/query/task/TaskAttributeNamesQueryResult.kt create mode 100755 view/view-api/src/main/kotlin/query/task/TaskAttributeValuesQuery.kt create mode 100644 view/view-api/src/main/kotlin/query/task/TaskAttributeValuesQueryResult.kt diff --git a/docs/reference-guide/components/view-api.md b/docs/reference-guide/components/view-api.md index d3c54365a..87f473e7e 100644 --- a/docs/reference-guide/components/view-api.md +++ b/docs/reference-guide/components/view-api.md @@ -21,19 +21,21 @@ and generic query paging and sorting. The Task API allows to query for tasks handled by the task-pool. -| Query Type | Description | Payload types | In-Memory | JPA | Mongo DB | -|-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------|-----------|------------|----------| -| AllTasksQuery | Retrieves a list of tasks applying additional filters | List | yes | yes | no | -| TasksForUserQuery | Retrieves a list of tasks accessible by the user and applying additional filters | List | yes | yes | yes | -| TasksForGroupQuery | Retrieves a list of tasks accessible by the user's group and applying additional filters | List | yes | yes | no | -| TasksForCandidateUserAndGroupQuery | Retrieves a list of tasks accessible by the user because listed as candidate and the user's group and applying additional filters | List | yes | yes | no | -| TaskForIdQuery | Retrieves a task by id (without any other filters) | Task or null | yes | yes | yes | -| TasksForApplicationQuery | Retrieves all tasks by given application name (without any further filters) | List | yes | yes | yes | -| AllTasksWithDataEntriesQuery | Retrieves a list of tasks applying additional filters and correlates result with data entries, if available | List<(Task, List) | yes | incubation | no | -| TasksWithDataEntriesForGroupQuery | Retrieves a list of tasks accessible by the user's group and applying additional filters and correlates result with data entries, if available | List<(Task, List) | yes | incubation | no | -| TasksWithDataEntriesForUserQuery | Retrieves a list of tasks accessible by the user and applying additional filters and correlates result with data entries, if available | List<(Task, List) | yes | incubation | yes | -| TaskWithDataEntriesForIdQuery | Retrieves a task by id and correlates result with data entries, if available | (Task, List) or null | yes | yes | yes | -| TaskCountByApplicationQuery | Counts tasks grouped by application names, useful for monitoring | List<(ApplicationName, Count)> | yes | no | yes | +| Query Type | Description | Payload types | In-Memory | JPA | Mongo DB | +|------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------|-----------|------------|----------| +| AllTasksQuery | Retrieves a list of tasks applying additional filters | List | yes | yes | no | +| TasksForUserQuery | Retrieves a list of tasks accessible by the user and applying additional filters | List | yes | yes | yes | +| TasksForGroupQuery | Retrieves a list of tasks accessible by the user's group and applying additional filters | List | yes | yes | no | +| TasksForCandidateUserAndGroupQuery | Retrieves a list of tasks accessible by the user because listed as candidate and the user's group and applying additional filters | List | yes | yes | no | +| TaskForIdQuery | Retrieves a task by id (without any other filters) | Task or null | yes | yes | yes | +| TasksForApplicationQuery | Retrieves all tasks by given application name (without any further filters) | List | yes | yes | yes | +| AllTasksWithDataEntriesQuery | Retrieves a list of tasks applying additional filters and correlates result with data entries, if available | List<(Task, List) | yes | incubation | no | +| TasksWithDataEntriesForGroupQuery | Retrieves a list of tasks accessible by the user's group and applying additional filters and correlates result with data entries, if available | List<(Task, List) | yes | incubation | no | +| TasksWithDataEntriesForUserQuery | Retrieves a list of tasks accessible by the user and applying additional filters and correlates result with data entries, if available | List<(Task, List) | yes | incubation | yes | +| TaskWithDataEntriesForIdQuery | Retrieves a task by id and correlates result with data entries, if available | (Task, List) or null | yes | yes | yes | +| TaskCountByApplicationQuery | Counts tasks grouped by application names, useful for monitoring | List<(ApplicationName, Count)> | yes | no | yes | +| TaskAttributeNamesQuery | Retrieves a list of all task (payload) attribut names | List<(String, Count)> | yes | no | yes | +| TaskAttributeValuesQuery | Retrieves a list of task (payload) attribut values for given name | List<(String, Count)> | yes | yes | no | ### Process Definition API diff --git a/integration/common/variable-serializer/src/main/kotlin/VariableSerializer.kt b/integration/common/variable-serializer/src/main/kotlin/VariableSerializer.kt index e02c34eca..86f0bca3c 100644 --- a/integration/common/variable-serializer/src/main/kotlin/VariableSerializer.kt +++ b/integration/common/variable-serializer/src/main/kotlin/VariableSerializer.kt @@ -96,7 +96,7 @@ internal fun Pair.toJsonPathWithValue( } else if (value is List<*>) { value.map { (key to it).toJsonPathWithValue(prefix, limit, filter) }.flatten() } else { - // ignore complex objects + // ignore complex objects, in default scenarios, complex objects got already deserialized by the sender in ProjectingCommandAccumulator.serializePayloadIfNeeded listOf() } } diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewTaskService.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewTaskService.kt index daba37c4c..ab2126248 100644 --- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewTaskService.kt +++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewTaskService.kt @@ -5,6 +5,7 @@ import io.holixon.axon.gateway.query.RevisionValue import io.holunda.camunda.taskpool.api.task.* import io.holunda.polyflow.view.Task import io.holunda.polyflow.view.TaskWithDataEntries +import io.holunda.polyflow.view.auth.User import io.holunda.polyflow.view.filter.toCriteria import io.holunda.polyflow.view.jpa.JpaPolyflowViewTaskService.Companion.PROCESSING_GROUP import io.holunda.polyflow.view.jpa.auth.AuthorizationPrincipal @@ -251,6 +252,28 @@ class JpaPolyflowViewTaskService( ) } + @QueryHandler + override fun query(query: TaskAttributeNamesQuery): TaskAttributeNamesQueryResult { + val assignee = if(query.assignedToMeOnly) query.user?.username else null + val distinctKeys = taskRepository.getTaskAttributeNames(assignee, query.user?.toAuthorizationPrincipalStrings()) + + return TaskAttributeNamesQueryResult( + elements = distinctKeys.toList(), + totalElementCount = distinctKeys.size + ) + } + + @QueryHandler + override fun query(query: TaskAttributeValuesQuery): TaskAttributeValuesQueryResult { + val assignee = if(query.assignedToMeOnly) query.user?.username else null + val distinctValues = taskRepository.getTaskAttributeValues(query.attributeName, assignee, query.user?.toAuthorizationPrincipalStrings()) + + return TaskAttributeValuesQueryResult( + elements = distinctValues.toList(), + totalElementCount = distinctValues.size + ) + } + @QueryHandler override fun query(query: TaskWithDataEntriesForIdQuery): Optional { return Optional.ofNullable(taskRepository.findByIdOrNull(query.id)?.let { taskEntity -> @@ -496,3 +519,7 @@ class JpaPolyflowViewTaskService( } } + +private fun User.toAuthorizationPrincipalStrings(): Set { + return this.groups.map(AuthorizationPrincipal.Companion::group).map { it.toString() }.toSet() + user(this.username).toString() +} diff --git a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/task/TaskRepository.kt b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/task/TaskRepository.kt index c8772fac8..db93b2d7e 100644 --- a/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/task/TaskRepository.kt +++ b/view/jpa/src/main/kotlin/io/holunda/polyflow/view/jpa/task/TaskRepository.kt @@ -366,7 +366,6 @@ interface TaskRepository : CrudRepository, JpaSpecificationE } } - /** * Counts user tasks grouped by application name, resulting in a total amount of tasks per application (=process engine). * Helpful for monitoring of tasks on the task pool projection side vs. engine side. @@ -375,4 +374,36 @@ interface TaskRepository : CrudRepository, JpaSpecificationE @Query("select new io.holunda.polyflow.view.jpa.CountByApplication(t.sourceReference.applicationName, count(t) ) from TaskEntity t group by t.sourceReference.applicationName") fun getCountByApplication(): List + /** + * Returns all task payload attribut names (path). + * If assignee is given, just attributes for the given assignee is queried. + * If authorizedPrincipals are given, just attributes for the given authorizedPrincipals are queried. + * @return list of task payload attribut names (path) + */ + @Query(""" + select att.path + from TaskEntity task + join task.payloadAttributes att + join task.authorizedPrincipals auth + where (?1 is null or task.assignee = ?1) + and (?2 is null or auth in ?2) + """) + fun getTaskAttributeNames(assignee: String?, principals: Set?): Set + + /** + * Returns a list of all task payload attribute values for the given task payload attribute name. + * If assignee is given, just attributes for the given assignee is queried. + * If authorizedPrincipals are given, just attributes for the given authorizedPrincipals are queried. + * @return list of task payload attribut values + */ + @Query(""" + select att.value + from TaskEntity task + join task.payloadAttributes att + join task.authorizedPrincipals auth + where att.path = ?1 + and (?2 is null or task.assignee = ?2) + and (?3 is null or auth in ?3) + """) + fun getTaskAttributeValues(attributeName: String, assignee: String?, principals: Set?): Set } diff --git a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceTaskITest.kt b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceTaskITest.kt index 6da80a10f..39c886d71 100644 --- a/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceTaskITest.kt +++ b/view/jpa/src/test/kotlin/io/holunda/polyflow/view/jpa/JpaPolyflowViewServiceTaskITest.kt @@ -524,6 +524,34 @@ internal class JpaPolyflowViewServiceTaskITest { assertThat(counts[0].taskCount).isEqualTo(3) } + @Test + fun `should find task attribute names`() { + // Some for zoro in muppets + val names = jpaPolyflowViewService.query(TaskAttributeNamesQuery(user = User("zoro", setOf("muppets")))) + assertThat(names).isNotNull + assertThat(names.elements).hasSize(4) + assertThat(names.elements).contains("key", "key-int", "complex.attribute1", "complex.attribute2") + + // But none for bud in heros + val namesOSH = jpaPolyflowViewService.query(TaskAttributeNamesQuery(user = User("bud", setOf("old_school_heros")))) + assertThat(namesOSH).isNotNull + assertThat(namesOSH.elements).hasSize(0) + } + + @Test + fun `should find task attribute values`() { + // Some for zoro in muppets + val names = jpaPolyflowViewService.query(TaskAttributeValuesQuery(user = User("zoro", setOf("muppets")), attributeName = "key")) + assertThat(names).isNotNull + assertThat(names.elements).hasSize(2) + assertThat(names.elements).contains("value", "otherValue") + + // But none for bud in heros + val namesOSH = jpaPolyflowViewService.query(TaskAttributeValuesQuery(user = User("bud", setOf("old_school_heros")), attributeName = "key")) + assertThat(namesOSH).isNotNull + assertThat(namesOSH.elements).hasSize(0) + } + private fun captureEmittedQueryUpdates(): List> { val queryTypeCaptor = argumentCaptor>() val predicateCaptor = argumentCaptor>() @@ -561,7 +589,9 @@ internal class JpaPolyflowViewServiceTaskITest { return mapOf( "key" to value, "key-int" to 1, - "complex" to Pojo( + "complex.attribute1" to "value", + "complex.attribute2" to Date.from(now), + "complexIgnored" to Pojo( // Normally, the event will never have a complex object like this in the payload. (Got already deserialized by the sender in ProjectingCommandAccumulator.serializePayloadIfNeeded) attribute1 = "value", attribute2 = Date.from(now) ) diff --git a/view/simple/src/main/kotlin/io/holunda/polyflow/view/simple/service/SimpleTaskPoolService.kt b/view/simple/src/main/kotlin/io/holunda/polyflow/view/simple/service/SimpleTaskPoolService.kt index 588d6e39c..1bcabeb2f 100755 --- a/view/simple/src/main/kotlin/io/holunda/polyflow/view/simple/service/SimpleTaskPoolService.kt +++ b/view/simple/src/main/kotlin/io/holunda/polyflow/view/simple/service/SimpleTaskPoolService.kt @@ -8,6 +8,7 @@ import io.holunda.polyflow.view.TaskWithDataEntries import io.holunda.polyflow.view.filter.createTaskPredicates import io.holunda.polyflow.view.filter.filterByPredicate import io.holunda.polyflow.view.filter.toCriteria +import io.holunda.polyflow.view.filter.toPayloadPredicates import io.holunda.polyflow.view.query.task.* import io.holunda.polyflow.view.simple.updateMapFilterQuery import io.holunda.polyflow.view.sort.taskComparator @@ -18,6 +19,7 @@ import org.axonframework.config.ProcessingGroup import org.axonframework.eventhandling.EventHandler import org.axonframework.queryhandling.QueryHandler import org.axonframework.queryhandling.QueryUpdateEmitter +import org.camunda.bpm.engine.variable.VariableMap import org.springframework.stereotype.Component import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -199,6 +201,52 @@ class SimpleTaskPoolService( return queryForTasks(query) } + /** + * Retrieves all task attribute names + */ + @QueryHandler + override fun query(query: TaskAttributeNamesQuery): TaskAttributeNamesQueryResult { + val filterAssignee = query.assignedToMeOnly && query.user != null + val filterCandidates = query.user != null + + val distinctFilteredKeys = tasks.values.asSequence() + .filter { !filterAssignee || it.assignee == query.user!!.username } + .filter { task -> !filterCandidates || (task.candidateUsers.contains(query.user!!.username) || task.candidateGroups.any { query.user!!.groups.contains(it) } ) } + .map(Task::payload) + .flatMap(VariableMap::keys) + .distinct() + .toList() + + return TaskAttributeNamesQueryResult( + elements = distinctFilteredKeys, + totalElementCount = distinctFilteredKeys.size + ) + } + + /** + * Retrieves all task attribute values for an attribute name + */ + @QueryHandler + override fun query(query: TaskAttributeValuesQuery): TaskAttributeValuesQueryResult { + val filterAssignee = query.assignedToMeOnly && query.user != null + val filterCandidates = query.user != null + + val distinctFilteredValues = tasks.values.asSequence() + .filter { !filterAssignee || it.assignee == query.user!!.username } + .filter { task -> !filterCandidates || (task.candidateUsers.contains(query.user!!.username) || task.candidateGroups.any { query.user!!.groups.contains(it) } ) } + .map(Task::payload) + .filter { it.containsKey(query.attributeName) } + .mapNotNull { it[query.attributeName] } + .distinct() + .toList() + + return TaskAttributeValuesQueryResult( + elements = distinctFilteredValues, + totalElementCount = distinctFilteredValues.size + ) + } + + @QueryHandler private fun queryForTasks(query: PageableSortableFilteredTaskQuery): TaskQueryResult { val predicates = createTaskPredicates(toCriteria(query.filters)) val filtered = tasks.values.filter { query.applyFilter(it) } diff --git a/view/simple/src/test/kotlin/io/holunda/polyflow/view/simple/service/SimpleTaskPoolServiceTest.kt b/view/simple/src/test/kotlin/io/holunda/polyflow/view/simple/service/SimpleTaskPoolServiceTest.kt index ba5b1b94d..a267b0d6c 100755 --- a/view/simple/src/test/kotlin/io/holunda/polyflow/view/simple/service/SimpleTaskPoolServiceTest.kt +++ b/view/simple/src/test/kotlin/io/holunda/polyflow/view/simple/service/SimpleTaskPoolServiceTest.kt @@ -290,6 +290,78 @@ class SimpleTaskPoolServiceTest : ScenarioTest, Simp .all_task_are_returned_and_sorted_by(reversed = true) { it.task.businessKey } } + @Test + fun `should find task attribute names`() { + given() + .tasks_exist(3) + + `when`() + .task_attribute_names_are_queried("kermit", "muppetshow") + + then() + .attribute_names_are_returned(3) + } + + @Test + fun `should not find task attribute names if there is no matching candidate user`() { + given() + .tasks_exist(3) + + `when`() + .task_attribute_names_are_queried("bud", "old_school_heros") + + then() + .attribute_names_are_returned(0) + } + + @Test + fun `should not find task attribute names for wrong assignee`() { + given() + .tasks_exist(3) + + `when`() + .task_attribute_names_are_queried_for_assigned_user(user = "bud", group = null) + + then() + .attribute_names_are_returned(0) + } + + @Test + fun `should find task attribute values`() { + given() + .tasks_exist(3) + + `when`() + .task_attribute_values_are_queried("payloadIdString", "kermit", "muppetshow") + + then() + .attribute_values_are_returned(3) + } + + @Test + fun `should not find task attribute values if there is no matching candidate user`() { + given() + .tasks_exist(3) + + `when`() + .task_attribute_values_are_queried("payloadIdString", "bud", "old_school_heros") + + then() + .attribute_values_are_returned(0) + } + + @Test + fun `should not find task attribute values for wrong assignee`() { + given() + .tasks_exist(3) + + `when`() + .task_attribute_values_are_queried_for_assigned_user("payloadIdString", "bud", null) + + then() + .attribute_values_are_returned(0) + } + private infix fun String.withTaskCount(taskCount: Int) = ApplicationWithTaskCount(this, taskCount) } diff --git a/view/simple/src/test/kotlin/io/holunda/polyflow/view/simple/service/TaskPoolStages.kt b/view/simple/src/test/kotlin/io/holunda/polyflow/view/simple/service/TaskPoolStages.kt index 8d04c8d18..28ae7ac61 100644 --- a/view/simple/src/test/kotlin/io/holunda/polyflow/view/simple/service/TaskPoolStages.kt +++ b/view/simple/src/test/kotlin/io/holunda/polyflow/view/simple/service/TaskPoolStages.kt @@ -63,6 +63,7 @@ class SimpleTaskPoolGivenStage> : Abstract payload = createVariables().apply { put("payloadIdInt", i) put("payloadIdString", "$i") + put("payloadComplex.attr1", "$i") } ) @@ -93,6 +94,12 @@ class SimpleTaskPoolWhenStage> : AbstractSi @ExpectedScenarioState(resolution = ScenarioState.Resolution.NAME, required = true) private lateinit var tasks: List + @ProvidedScenarioState(resolution = ScenarioState.Resolution.NAME) + private var attributeNames: List = listOf() + + @ProvidedScenarioState(resolution = ScenarioState.Resolution.NAME) + private var attributeValues: List = listOf() + @ProvidedScenarioState(resolution = ScenarioState.Resolution.NAME) private var queriedTasks: MutableList = mutableListOf() @@ -130,6 +137,26 @@ class SimpleTaskPoolWhenStage> : AbstractSi queriedTasks.addAll(simpleTaskPoolService.query(AllTasksQuery(sort = sort, filters = filters)).elements.map { TaskWithDataEntries(it) }) } + @As("Task Attribute Names are queried for user $ with group $") + fun task_attribute_names_are_queried(user: String, group: String) = step { + attributeNames = simpleTaskPoolService.query(TaskAttributeNamesQuery(user = User(user, setOf(group)))).elements + } + + @As("Task Attribute Names are queried for assigned user $ with group $") + fun task_attribute_names_are_queried_for_assigned_user(user: String, group: String?) = step { + attributeNames = simpleTaskPoolService.query(TaskAttributeNamesQuery(user = User(user, setOfNotNull(group)), assignedToMeOnly = true)).elements + } + + @As("Task Attribute Values are queried for name $ and user $ with group $") + fun task_attribute_values_are_queried(name: String, user: String, group: String) = step { + attributeValues = simpleTaskPoolService.query(TaskAttributeValuesQuery(attributeName = name, user = User(user, setOf(group)))).elements + } + + @As("Task Attribute Values are queried for name $ and assigned user $ with group $") + fun task_attribute_values_are_queried_for_assigned_user(name: String, user: String, group: String?) = step { + attributeValues = simpleTaskPoolService.query(TaskAttributeValuesQuery(attributeName = name, user = User(user, setOfNotNull(group)), assignedToMeOnly = true)).elements + } + } @JGivenKotlinStage @@ -147,6 +174,12 @@ class SimpleTaskPoolThenStage> : AbstractSi @ExpectedScenarioState(resolution = ScenarioState.Resolution.NAME, required = true) private lateinit var returnedTasksForApplication: TaskQueryResult + @ExpectedScenarioState(resolution = ScenarioState.Resolution.NAME) + private var attributeNames: List = listOf() + + @ExpectedScenarioState(resolution = ScenarioState.Resolution.NAME) + private var attributeValues: List = listOf() + @As("$ tasks are returned") fun num_tasks_are_returned(numTasks: Int) = step { assertThat(queriedTasks.size).isEqualTo(numTasks) @@ -177,6 +210,16 @@ class SimpleTaskPoolThenStage> : AbstractSi assertThat(returnedTasksForApplication.elements).containsExactlyInAnyOrder(*expectedTasks) } + @As("attribute names $ are returned") + fun attribute_names_are_returned(count: Int) = step { + assertThat(attributeNames).hasSize(count) + } + + @As("attribute values $ are returned") + fun attribute_values_are_returned(count: Int) = step { + assertThat(attributeValues).hasSize(count) + } + fun task_is_created(task: Task) = step { val result = simpleTaskPoolService.query(TaskForIdQuery(task.id)) assertThat(result).isPresent diff --git a/view/view-api-client/src/main/kotlin/QueryGatewayExt.kt b/view/view-api-client/src/main/kotlin/QueryGatewayExt.kt index 05c3e687f..320b24cf0 100644 --- a/view/view-api-client/src/main/kotlin/QueryGatewayExt.kt +++ b/view/view-api-client/src/main/kotlin/QueryGatewayExt.kt @@ -134,4 +134,14 @@ object QueryGatewayExt { */ fun QueryGateway.tasksForCandidateUserAndGroup(query: TasksForCandidateUserAndGroupQuery): CompletableFuture = TaskQueryClient(this).query(query) + /** + * @see [TaskAttributeNamesQuery] + */ + fun QueryGateway.taskAttributeNames(query: TaskAttributeNamesQuery): CompletableFuture = TaskQueryClient(this).query(query) + + /** + * @see [TaskAttributeValuesQuery] + */ + fun QueryGateway.taskAttributeValues(query: TaskAttributeValuesQuery): CompletableFuture = TaskQueryClient(this).query(query) + } diff --git a/view/view-api-client/src/main/kotlin/TaskQueryClient.kt b/view/view-api-client/src/main/kotlin/TaskQueryClient.kt index 8cd29dc4f..cc2cce8cf 100644 --- a/view/view-api-client/src/main/kotlin/TaskQueryClient.kt +++ b/view/view-api-client/src/main/kotlin/TaskQueryClient.kt @@ -113,4 +113,22 @@ open class TaskQueryClient( ResponseTypes.instanceOf(TaskQueryResult::class.java) ) + /** + * @see io.holunda.polyflow.view.query.task.TaskApi.query + * @see io.holunda.polyflow.view.query.task.TaskAttributeNamesQuery + */ + open fun query(query: TaskAttributeNamesQuery): CompletableFuture = queryGateway.query( + query, + ResponseTypes.instanceOf(TaskAttributeNamesQueryResult::class.java) + ) + + /** + * @see io.holunda.polyflow.view.query.task.TaskApi.query + * @see io.holunda.polyflow.view.query.task.TaskAttributeValuesQuery + */ + open fun query(query: TaskAttributeValuesQuery): CompletableFuture = queryGateway.query( + query, + ResponseTypes.instanceOf(TaskAttributeValuesQueryResult::class.java) + ) + } diff --git a/view/view-api/src/main/kotlin/query/task/AllTasksQuery.kt b/view/view-api/src/main/kotlin/query/task/AllTasksQuery.kt index c4bc4e639..bd5373d55 100755 --- a/view/view-api/src/main/kotlin/query/task/AllTasksQuery.kt +++ b/view/view-api/src/main/kotlin/query/task/AllTasksQuery.kt @@ -1,8 +1,6 @@ package io.holunda.polyflow.view.query.task import io.holunda.polyflow.view.Task -import io.holunda.polyflow.view.query.FilterQuery -import io.holunda.polyflow.view.query.PageableSortableQuery /** * Query for all tasks. diff --git a/view/view-api/src/main/kotlin/query/task/AllTasksWithDataEntriesQuery.kt b/view/view-api/src/main/kotlin/query/task/AllTasksWithDataEntriesQuery.kt index 81b06e06d..443227d80 100755 --- a/view/view-api/src/main/kotlin/query/task/AllTasksWithDataEntriesQuery.kt +++ b/view/view-api/src/main/kotlin/query/task/AllTasksWithDataEntriesQuery.kt @@ -1,7 +1,6 @@ package io.holunda.polyflow.view.query.task import io.holunda.polyflow.view.TaskWithDataEntries -import io.holunda.polyflow.view.auth.User import io.holunda.polyflow.view.query.FilterQuery import io.holunda.polyflow.view.query.PageableSortableQuery diff --git a/view/view-api/src/main/kotlin/query/task/TaskApi.kt b/view/view-api/src/main/kotlin/query/task/TaskApi.kt index 3e6c88e64..30cf49b0d 100644 --- a/view/view-api/src/main/kotlin/query/task/TaskApi.kt +++ b/view/view-api/src/main/kotlin/query/task/TaskApi.kt @@ -63,4 +63,14 @@ interface TaskApi { * Retrieve all tasks. */ fun query(query: AllTasksQuery): TaskQueryResult + + /** + * Retrieves all task attribute names + */ + fun query(query: TaskAttributeNamesQuery): TaskAttributeNamesQueryResult + + /** + * Retrieves all task attribute values for an attribute name + */ + fun query(query: TaskAttributeValuesQuery): TaskAttributeValuesQueryResult } diff --git a/view/view-api/src/main/kotlin/query/task/TaskAttributeNamesQuery.kt b/view/view-api/src/main/kotlin/query/task/TaskAttributeNamesQuery.kt new file mode 100755 index 000000000..11dc586ad --- /dev/null +++ b/view/view-api/src/main/kotlin/query/task/TaskAttributeNamesQuery.kt @@ -0,0 +1,21 @@ +package io.holunda.polyflow.view.query.task + +import io.holunda.polyflow.view.Task +import io.holunda.polyflow.view.auth.User +import io.holunda.polyflow.view.query.FilterQuery +import io.holunda.polyflow.view.query.PageableSortableQuery + +/** + * Query for tasks attribute names. + * @param user - the user with groups accessing the tasks. If non is passed, all task attributes will be queried. + * @param assignedToMeOnly flag indicating if the resulting tasks must be assigned to the user only. + */ +data class TaskAttributeNamesQuery( + val user: User?, + val assignedToMeOnly: Boolean = false, +) : FilterQuery { + + override fun applyFilter(element: Task): Boolean = true + +} + diff --git a/view/view-api/src/main/kotlin/query/task/TaskAttributeNamesQueryResult.kt b/view/view-api/src/main/kotlin/query/task/TaskAttributeNamesQueryResult.kt new file mode 100644 index 000000000..c48f97c78 --- /dev/null +++ b/view/view-api/src/main/kotlin/query/task/TaskAttributeNamesQueryResult.kt @@ -0,0 +1,14 @@ +package io.holunda.polyflow.view.query.task + +import io.holunda.polyflow.view.query.PageableSortableQuery +import io.holunda.polyflow.view.query.QueryResult + +/** + * Result of query for multiple task attributes. + */ +data class TaskAttributeNamesQueryResult( + override val elements: List, + override val totalElementCount: Int = elements.size +) : QueryResult(elements = elements, totalElementCount = totalElementCount) { + override fun slice(query: PageableSortableQuery) = this.copy(elements = super.slice(query).elements) +} diff --git a/view/view-api/src/main/kotlin/query/task/TaskAttributeValuesQuery.kt b/view/view-api/src/main/kotlin/query/task/TaskAttributeValuesQuery.kt new file mode 100755 index 000000000..8873eb386 --- /dev/null +++ b/view/view-api/src/main/kotlin/query/task/TaskAttributeValuesQuery.kt @@ -0,0 +1,34 @@ +package io.holunda.polyflow.view.query.task + +import io.holunda.polyflow.view.Task +import io.holunda.polyflow.view.auth.User +import io.holunda.polyflow.view.query.FilterQuery +import io.holunda.polyflow.view.query.PageableSortableQuery + +/** + * Query and distinct tasks attribute values for the given attribute name. + * @param user - the user with groups accessing the tasks. If non is passed, all tasks attribute values will be queried. + * @param assignedToMeOnly flag indicating if the resulting tasks must be assigned to the user only. + * @param filters - currently not supported + */ +data class TaskAttributeValuesQuery( + val attributeName: String, + val user: User?, + val assignedToMeOnly: Boolean = false, +) : FilterQuery { + + override fun applyFilter(element: Task): Boolean = user == null || + if (assignedToMeOnly) { + // assignee + element.assignee == this.user.username + } else { + // candidate user + element.candidateUsers.contains(this.user.username) + // candidate groups + || element.candidateGroups.any { candidateGroup -> this.user.groups.contains(candidateGroup) } + // assignee + || element.assignee == this.user.username + } + +} + diff --git a/view/view-api/src/main/kotlin/query/task/TaskAttributeValuesQueryResult.kt b/view/view-api/src/main/kotlin/query/task/TaskAttributeValuesQueryResult.kt new file mode 100644 index 000000000..87bada373 --- /dev/null +++ b/view/view-api/src/main/kotlin/query/task/TaskAttributeValuesQueryResult.kt @@ -0,0 +1,15 @@ +package io.holunda.polyflow.view.query.task + +import io.holunda.polyflow.view.Task +import io.holunda.polyflow.view.query.PageableSortableQuery +import io.holunda.polyflow.view.query.QueryResult + +/** + * Result of query for multiple task attributes. + */ +data class TaskAttributeValuesQueryResult( + override val elements: List, + override val totalElementCount: Int = elements.size +) : QueryResult(elements = elements, totalElementCount = totalElementCount) { + override fun slice(query: PageableSortableQuery) = this.copy(elements = super.slice(query).elements) +}