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-7106df496367311d8e4b8325da4cd62a0561f132
in repository https://gitbox.apache.org/repos/asf/texera.git

commit 551b0baab5c7d462cb4047b383264bd8cca47a38
Author: Yicong Huang <[email protected]>
AuthorDate: Thu Jul 30 09:13:25 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

Reply via email to