This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/release/v1.2/pr-7107-e3b5b60c484219c083e55087f6ae77503123260e in repository https://gitbox.apache.org/repos/asf/texera.git
commit 296377aa64b766c19187a29bd807a8af2035d67f Author: Yicong Huang <[email protected]> AuthorDate: Thu Jul 30 10:02:44 2026 -0400 fix(query, v1.2): Remove duplicated rows when workflows shared Publicly in the hub page (#7107) ### What changes were proposed in this PR? Backport of #6017 to `release/v1.2`, cherry-picked from 812f17705fbeb0a47601d9a4047ff59e74611ae8. Follows the Direct Backport Push convention; opened as a PR (rather than a direct push) as part of a backport-coverage audit for fixes merged to `main` since early June that were never labeled for backport. ### Any related issues, documentation, discussions? Backport of #6017. Originally linked #5957. ### How was this PR tested? Release-branch CI runs once the conflicts are resolved and this PR is marked ready for review. The cherry-pick **conflicted** and was committed with conflict markers. ### Was this PR authored or co-authored using generative AI tooling? Yes — backport prepared with Claude Code (mechanical cherry-pick; conflicts left as markers for the original author to resolve; the change itself is #6017 by its original author). Co-authored-by: Mrudhulraj <[email protected]> --- .../dashboard/WorkflowSearchQueryBuilder.scala | 13 +++-- .../dashboard/file/WorkflowResourceSpec.scala | 56 +++++++++++++++++++++- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/WorkflowSearchQueryBuilder.scala b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/WorkflowSearchQueryBuilder.scala index cfa653316d..0c862c0509 100644 --- a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/WorkflowSearchQueryBuilder.scala +++ b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/WorkflowSearchQueryBuilder.scala @@ -29,6 +29,7 @@ import org.jooq.impl.DSL.groupConcatDistinct import org.jooq.{Condition, GroupField, Record, TableLike} import scala.jdk.CollectionConverters.CollectionHasAsScala +import org.apache.texera.dao.jooq.generated.enums.PrivilegeEnum object WorkflowSearchQueryBuilder extends SearchQueryBuilder { @@ -56,6 +57,7 @@ object WorkflowSearchQueryBuilder extends SearchQueryBuilder { val baseQuery = WORKFLOW .leftJoin(WORKFLOW_USER_ACCESS) .on(WORKFLOW_USER_ACCESS.WID.eq(WORKFLOW.WID)) + .and(if (uid == null) DSL.falseCondition() else WORKFLOW_USER_ACCESS.UID.eq(uid)) .leftJoin(WORKFLOW_OF_USER) .on(WORKFLOW_OF_USER.WID.eq(WORKFLOW.WID)) .leftJoin(USER) @@ -64,13 +66,14 @@ object WorkflowSearchQueryBuilder extends SearchQueryBuilder { .on(WORKFLOW_OF_PROJECT.WID.eq(WORKFLOW.WID)) .leftJoin(PROJECT_USER_ACCESS) .on(PROJECT_USER_ACCESS.PID.eq(WORKFLOW_OF_PROJECT.PID)) + .and(if (uid == null) DSL.falseCondition() else PROJECT_USER_ACCESS.UID.eq(uid)) var condition: Condition = DSL.trueCondition() if (uid == null) { condition = WORKFLOW.IS_PUBLIC.eq(true) } else { val privateAccessCondition = - WORKFLOW_USER_ACCESS.UID.eq(uid).or(PROJECT_USER_ACCESS.UID.eq(uid)) + WORKFLOW_USER_ACCESS.UID.eq(uid).or(PROJECT_USER_ACCESS.UID.isNotNull) if (includePublic) { condition = privateAccessCondition.or(WORKFLOW.IS_PUBLIC.eq(true)) } else { @@ -138,10 +141,10 @@ object WorkflowSearchQueryBuilder extends SearchQueryBuilder { ): DashboardResource.DashboardClickableFileEntry = { val pidField = groupConcatDistinct(WORKFLOW_OF_PROJECT.PID) val dw = DashboardWorkflow( - record.into(WORKFLOW_OF_USER).getUid.eq(uid), - record - .get(WORKFLOW_USER_ACCESS.PRIVILEGE) - .toString, + record.into(WORKFLOW_OF_USER).getUid == uid, + Option(record.get(WORKFLOW_USER_ACCESS.PRIVILEGE, classOf[PrivilegeEnum])) + .map(_.toString) + .getOrElse(PrivilegeEnum.NONE.toString), record.into(USER).getName, record.into(WORKFLOW).into(classOf[Workflow]), if (record.get(pidField) == null) { diff --git a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/file/WorkflowResourceSpec.scala b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/file/WorkflowResourceSpec.scala index 74a68ee65e..30eefb41c3 100644 --- a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/file/WorkflowResourceSpec.scala +++ b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/file/WorkflowResourceSpec.scala @@ -44,6 +44,7 @@ import java.time.{Duration, OffsetDateTime, ZoneOffset} import java.util import java.util.Collections import java.util.concurrent.TimeUnit +import org.apache.texera.web.resource.dashboard.user.workflow.WorkflowAccessResource class WorkflowResourceSpec extends AnyFlatSpec @@ -59,6 +60,7 @@ class WorkflowResourceSpec val user = new User user.setUid(Integer.valueOf(1)) user.setName("test_user") + user.setEmail("[email protected]") user.setRole(UserRoleEnum.ADMIN) user.setPassword("123") user.setComment("test_comment") @@ -70,6 +72,7 @@ class WorkflowResourceSpec val user = new User user.setUid(Integer.valueOf(2)) user.setName("test_user2") + user.setEmail("[email protected]") user.setRole(UserRoleEnum.ADMIN) user.setPassword("123") user.setComment("test_comment2") @@ -317,7 +320,7 @@ class WorkflowResourceSpec assert(ownerName == testUser.getName) } - "/search API " should "be able to search for workflows in different columns in Workflow table" in { + "/search API" should "be able to search for workflows in different columns in Workflow table" in { // testWorkflow1: {name: test_name, descrption: test_description, content: test_content} // search "test_name" or "test_description" or "test_content" should return testWorkflow1 workflowResource.persistWorkflow(testWorkflow1, sessionUser1) @@ -368,6 +371,57 @@ class WorkflowResourceSpec assert(DashboardWorkflowEntryList.results.length == 2) } + it should "return only single instance of workflow when owned and shared publicly" in { + // Create a public workflow + val publicWorkflow = new Workflow() + publicWorkflow.setName("public_workflow_1") + publicWorkflow.setDescription(testWorkflow1.getDescription) + publicWorkflow.setContent(testWorkflow1.getContent) + publicWorkflow.setIsPublic(true) + + // Persist workflow with testUser as owner + workflowResource.persistWorkflow(publicWorkflow, sessionUser1) + + val DashboardWorkflowEntryList = + dashboardResource.searchAllResourcesCall( + sessionUser1, + SearchQueryParams(), + includePublic = true + ) + assert(DashboardWorkflowEntryList.results.length == 1) + assertSameWorkflow(publicWorkflow, DashboardWorkflowEntryList.results.head.workflow.get) + } + + it should "return only single instance of workflow when publicly and explicitly shared" in { + // Create a public workflow + val publicWorkflow = new Workflow() + publicWorkflow.setName("public_workflow_2") + publicWorkflow.setDescription(testWorkflow1.getDescription) + publicWorkflow.setContent(testWorkflow1.getContent) + publicWorkflow.setIsPublic(true) + + // Persist workflow with testUser as owner + val savedWorkflow = workflowResource.persistWorkflow(publicWorkflow, sessionUser1) + + // Share workflow with read access to testUser2 + val workflowAccessResource = new WorkflowAccessResource() + workflowAccessResource.grantAccess( + savedWorkflow.getWid, + testUser2.getEmail, + "READ", + sessionUser1 + ) + + val DashboardWorkflowEntryList = + dashboardResource.searchAllResourcesCall( + sessionUser2, + SearchQueryParams(), + includePublic = true + ) + assert(DashboardWorkflowEntryList.results.length == 1) + assertSameWorkflow(publicWorkflow, DashboardWorkflowEntryList.results.head.workflow.get) + } + it should "be able to search with arbitrary number of keywords in different combinations" in { // testWorkflow1: {name: test_name, description: test_description, content: "key pair"} // search ["key"] or ["pair", "key"] should return the testWorkflow1
