From d053ec8238f827b8bd7f16dba0f082b1cf5ebc79 Mon Sep 17 00:00:00 2001 From: Carlos Ernesto Alvarez Berumen Date: Sat, 27 Dec 2025 18:01:43 -0600 Subject: [PATCH 01/11] v1 --- .../user/workflow/WorkflowResource.scala | 15 +++++---------- .../workflow-persist/workflow-persist.service.ts | 6 +++--- .../detail/hub-workflow-detail.component.ts | 6 +++--- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala index 4248d06bdd8..b6b4bde553f 100644 --- a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala +++ b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala @@ -712,23 +712,18 @@ class WorkflowResource extends LazyLogging { } @GET - @Path("/owner_user") - def getOwnerUser(@QueryParam("wid") wid: Integer): User = { + @Produces(Array(MediaType.TEXT_PLAIN)) + @Path("/owner_name") + def getOwnerName(@QueryParam("wid") wid: Integer): String = { context .select( - USER.UID, - USER.NAME, - USER.EMAIL, - USER.PASSWORD, - USER.GOOGLE_ID, - USER.ROLE, - USER.GOOGLE_AVATAR + USER.NAME ) .from(WORKFLOW_OF_USER) .join(USER) .on(WORKFLOW_OF_USER.UID.eq(USER.UID)) .where(WORKFLOW_OF_USER.WID.eq(wid)) - .fetchOneInto(classOf[User]) + .fetchOneInto(classOf[String]) } @GET diff --git a/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts b/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts index 8f629d5da4e..f3c04a7013b 100644 --- a/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts +++ b/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts @@ -41,7 +41,7 @@ export const WORKFLOW_UPDATENAME_URL = WORKFLOW_BASE_URL + "/update/name"; export const WORKFLOW_UPDATEDESCRIPTION_URL = WORKFLOW_BASE_URL + "/update/description"; export const WORKFLOW_OWNER_URL = WORKFLOW_BASE_URL + "/user-workflow-owners"; export const WORKFLOW_ID_URL = WORKFLOW_BASE_URL + "/user-workflow-ids"; -export const WORKFLOW_OWNER_USER = WORKFLOW_BASE_URL + "/owner_user"; +export const WORKFLOW_OWNER_NAME = WORKFLOW_BASE_URL + "/owner_name"; export const WORKFLOW_NAME = WORKFLOW_BASE_URL + "/workflow_name"; export const WORKFLOW_PUBLIC_WORKFLOW = WORKFLOW_BASE_URL + "/publicised"; export const WORKFLOW_DESCRIPTION = WORKFLOW_BASE_URL + "/workflow_description"; @@ -242,9 +242,9 @@ export class WorkflowPersistService { * can be used without logging in * @param wid */ - public getOwnerUser(wid: number): Observable { + public getOwnerName(wid: number): Observable { const params = new HttpParams().set("wid", wid); - return this.http.get(`${AppSettings.getApiEndpoint()}/${WORKFLOW_OWNER_USER}`, { params }); + return this.http.get(`${AppSettings.getApiEndpoint()}/${WORKFLOW_OWNER_NAME}`, { params, responseType: "text", }); } /** diff --git a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts index 2b6318fb535..3b0d0b8c502 100644 --- a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts +++ b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts @@ -98,10 +98,10 @@ export class HubWorkflowDetailComponent implements AfterViewInit, OnDestroy, OnI this.viewCount = count; }); this.workflowPersistService - .getOwnerUser(this.wid) + .getOwnerName(this.wid) .pipe(untilDestroyed(this)) - .subscribe(owner => { - this.ownerName = owner.name; + .subscribe(ownerName => { + this.ownerName = ownerName; }); this.workflowPersistService .getWorkflowName(this.wid) From 1f651fc58ae2af7583d7d08a6467861247506967 Mon Sep 17 00:00:00 2001 From: Carlos Ernesto Alvarez Berumen Date: Sun, 28 Dec 2025 01:12:24 -0600 Subject: [PATCH 02/11] v2 --- .../user/workflow/WorkflowResource.scala | 30 ++++++++++++++----- .../workflow-persist.service.ts | 23 +++++++++----- .../detail/hub-workflow-detail.component.ts | 6 ++-- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala index b6b4bde553f..9ace1255828 100644 --- a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala +++ b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala @@ -712,18 +712,34 @@ class WorkflowResource extends LazyLogging { } @GET - @Produces(Array(MediaType.TEXT_PLAIN)) - @Path("/owner_name") - def getOwnerName(@QueryParam("wid") wid: Integer): String = { + @Path("/owner_info") + @Produces(Array(MediaType.APPLICATION_JSON)) + def getOwnerInfo( + @QueryParam("wid") wid: Integer, + @QueryParam("fields") fields: java.util.List[String] // e.g. &fields=name + ): User = { + + val allowedFields = Map( + "name" -> USER.NAME + ) + + val requestedFields = + Option(fields).map(_.asScala.toList).getOrElse(List("name")) + .map(_.trim.toLowerCase) + .filter(_.nonEmpty) + .distinct + + val selectedFields = requestedFields.map { field => + allowedFields.getOrElse(field, throw new NotAcceptableException(s"Unsupported field: $field")) + } + context - .select( - USER.NAME - ) + .select(selectedFields: _*) .from(WORKFLOW_OF_USER) .join(USER) .on(WORKFLOW_OF_USER.UID.eq(USER.UID)) .where(WORKFLOW_OF_USER.WID.eq(wid)) - .fetchOneInto(classOf[String]) + .fetchOneInto(classOf[User]) } @GET diff --git a/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts b/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts index f3c04a7013b..3172f3f68d9 100644 --- a/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts +++ b/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts @@ -41,7 +41,7 @@ export const WORKFLOW_UPDATENAME_URL = WORKFLOW_BASE_URL + "/update/name"; export const WORKFLOW_UPDATEDESCRIPTION_URL = WORKFLOW_BASE_URL + "/update/description"; export const WORKFLOW_OWNER_URL = WORKFLOW_BASE_URL + "/user-workflow-owners"; export const WORKFLOW_ID_URL = WORKFLOW_BASE_URL + "/user-workflow-ids"; -export const WORKFLOW_OWNER_NAME = WORKFLOW_BASE_URL + "/owner_name"; +export const WORKFLOW_OWNER_INFO = WORKFLOW_BASE_URL + "/owner_info"; export const WORKFLOW_NAME = WORKFLOW_BASE_URL + "/workflow_name"; export const WORKFLOW_PUBLIC_WORKFLOW = WORKFLOW_BASE_URL + "/publicised"; export const WORKFLOW_DESCRIPTION = WORKFLOW_BASE_URL + "/workflow_description"; @@ -238,13 +238,22 @@ export class WorkflowPersistService { } /** - * retrieve the complete information of the owner corresponding to the wid - * can be used without logging in - * @param wid + * Retrieve owner info for a workflow (no login required). + * @param wid workflow id + * @param fields which fields to return (default: ["name"]) */ - public getOwnerName(wid: number): Observable { - const params = new HttpParams().set("wid", wid); - return this.http.get(`${AppSettings.getApiEndpoint()}/${WORKFLOW_OWNER_NAME}`, { params, responseType: "text", }); + public getOwnerInfo(wid: number, fields: string[] = ["name"]): Observable { + let params = new HttpParams().set("wid", wid); + + // Repeat query param: &fields=name&fields=uid... + fields.forEach(f => { + params = params.append("fields", f); + }); + + return this.http.get( + `${AppSettings.getApiEndpoint()}/${WORKFLOW_OWNER_INFO}`, + { params } + ); } /** diff --git a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts index 3b0d0b8c502..ce9a63daf9d 100644 --- a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts +++ b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts @@ -98,10 +98,10 @@ export class HubWorkflowDetailComponent implements AfterViewInit, OnDestroy, OnI this.viewCount = count; }); this.workflowPersistService - .getOwnerName(this.wid) + .getOwnerInfo(this.wid) .pipe(untilDestroyed(this)) - .subscribe(ownerName => { - this.ownerName = ownerName; + .subscribe(ownerInfo => { + this.ownerName = ownerInfo.name; }); this.workflowPersistService .getWorkflowName(this.wid) From 23582f26763eb7dbb623546a7e40ec61205849c2 Mon Sep 17 00:00:00 2001 From: Carlos Ernesto Alvarez Berumen Date: Sun, 28 Dec 2025 02:44:05 -0600 Subject: [PATCH 03/11] added tests --- .../user/workflow/WorkflowResource.scala | 10 +- .../user/workflow/WorkflowResourceSpec.scala | 107 ++++++++++++++++++ 2 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceSpec.scala diff --git a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala index 9ace1255828..9ee5afd2bbe 100644 --- a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala +++ b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala @@ -715,16 +715,18 @@ class WorkflowResource extends LazyLogging { @Path("/owner_info") @Produces(Array(MediaType.APPLICATION_JSON)) def getOwnerInfo( - @QueryParam("wid") wid: Integer, - @QueryParam("fields") fields: java.util.List[String] // e.g. &fields=name - ): User = { + @QueryParam("wid") wid: Integer, + @QueryParam("fields") fields: java.util.List[String] // e.g. &fields=name&fields=... + ): User = { val allowedFields = Map( "name" -> USER.NAME ) val requestedFields = - Option(fields).map(_.asScala.toList).getOrElse(List("name")) + Option(fields) + .map(_.asScala.toList) + .getOrElse(List("name")) .map(_.trim.toLowerCase) .filter(_.nonEmpty) .distinct diff --git a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceSpec.scala b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceSpec.scala new file mode 100644 index 00000000000..b674ff53886 --- /dev/null +++ b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceSpec.scala @@ -0,0 +1,107 @@ +package org.apache.texera.web.resource.dashboard.user.workflow + +import org.apache.texera.dao.MockTexeraDB +import org.apache.texera.dao.jooq.generated.Tables._ +import org.apache.texera.dao.jooq.generated.tables.daos.{UserDao, WorkflowDao} +import org.apache.texera.dao.jooq.generated.tables.pojos.{User, Workflow} +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.BeforeAndAfterAll + +import java.sql.Timestamp +import java.util +import java.util.UUID +import java.util.concurrent.atomic.AtomicInteger +import javax.ws.rs.NotAcceptableException + +class WorkflowResourceSpec extends AnyFlatSpec with BeforeAndAfterAll with MockTexeraDB { + + private val widSeq = new AtomicInteger(3000) + private val uidSeq = new AtomicInteger(1000) + + override protected def beforeAll(): Unit = { + initializeDBAndReplaceDSLContext() + } + + override protected def afterAll(): Unit = { + shutdownDB() + } + + private def seedUser(uid: Int, name: String): Unit = { + val userDao = new UserDao(getDSLContext.configuration()) + val user = new User + user.setUid(uid) + user.setName(name) + user.setEmail(s"$name@example.com") + user.setPassword("password") + userDao.insert(user) + } + + private def seedWorkflow(wid: Int): Unit = { + val workflowDao = new WorkflowDao(getDSLContext.configuration()) + val wf = new Workflow + wf.setWid(wid) + wf.setName("test_workflow_" + UUID.randomUUID().toString.substring(0, 8)) + wf.setContent("{}") + wf.setDescription("test description") + wf.setCreationTime(new Timestamp(System.currentTimeMillis())) + wf.setLastModifiedTime(new Timestamp(System.currentTimeMillis())) + workflowDao.insert(wf) + } + + /** Setup row needed by /owner_info */ + private def linkWorkflowOwner(wid: Int, uid: Int): Unit = { + getDSLContext + .insertInto(WORKFLOW_OF_USER) + .set(WORKFLOW_OF_USER.WID, Integer.valueOf(wid)) + .set(WORKFLOW_OF_USER.UID, Integer.valueOf(uid)) + .execute() + } + + // --- Owner-info test fixture (reusable for any owner_info-related tests) --- + private def withOwnerInfoFixture(testCode: (Int, Int) => Any): Unit = { + val wid = widSeq.getAndIncrement() + val uid = uidSeq.getAndIncrement() + + seedUser(uid, "test_user") + seedWorkflow(wid) + linkWorkflowOwner(wid, uid) + + try testCode(wid, uid) + finally cleanupOwnerInfoFixture(wid, uid) + } + + private def cleanupOwnerInfoFixture(wid: Int, uid: Int): Unit = { + getDSLContext + .deleteFrom(WORKFLOW_OF_USER) + .where(WORKFLOW_OF_USER.WID.eq(wid)) + .and(WORKFLOW_OF_USER.UID.eq(uid)) + .execute() + + getDSLContext.deleteFrom(WORKFLOW).where(WORKFLOW.WID.eq(wid)).execute() + getDSLContext.deleteFrom(USER).where(USER.UID.eq(uid)).execute() + } + + // --- Tests --- + + "WorkflowResource /owner_info" should "return owner info name when fields includes name" in + withOwnerInfoFixture { (wid, _) => + val resource = new WorkflowResource + val owner = resource.getOwnerInfo(wid, util.Arrays.asList("name")) + assert(owner.getName == "test_user") + } + + it should "default owner info fields to name when fields is missing" in + withOwnerInfoFixture { (wid, _) => + val resource = new WorkflowResource + val owner = resource.getOwnerInfo(wid, null) + assert(owner.getName == "test_user") + } + + it should "reject unsupported owner info fields" in + withOwnerInfoFixture { (wid, _) => + val resource = new WorkflowResource + assertThrows[NotAcceptableException] { + resource.getOwnerInfo(wid, util.Arrays.asList("password")) + } + } +} From ca6ba7ba6a59d5525734745dd75da0a16e113c8f Mon Sep 17 00:00:00 2001 From: Carlos Ernesto Alvarez Berumen Date: Sun, 28 Dec 2025 14:08:55 -0600 Subject: [PATCH 04/11] added apache header --- .../user/workflow/WorkflowResourceSpec.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceSpec.scala b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceSpec.scala index b674ff53886..efa69c71fb3 100644 --- a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceSpec.scala +++ b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceSpec.scala @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.apache.texera.web.resource.dashboard.user.workflow import org.apache.texera.dao.MockTexeraDB From 6a665f68ef2c09db82ec424f910a795e0f86ae72 Mon Sep 17 00:00:00 2001 From: Carlos Ernesto Alvarez Berumen Date: Sun, 28 Dec 2025 15:45:36 -0600 Subject: [PATCH 05/11] frontend format fix --- .../service/workflow-persist/workflow-persist.service.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts b/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts index 3172f3f68d9..a9834dd7c20 100644 --- a/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts +++ b/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts @@ -250,10 +250,7 @@ export class WorkflowPersistService { params = params.append("fields", f); }); - return this.http.get( - `${AppSettings.getApiEndpoint()}/${WORKFLOW_OWNER_INFO}`, - { params } - ); + return this.http.get(`${AppSettings.getApiEndpoint()}/${WORKFLOW_OWNER_INFO}`, { params }); } /** From dac6a7fe5735a7a9876a8ba3505216a316e9ff13 Mon Sep 17 00:00:00 2001 From: Carlos Ernesto Alvarez Berumen Date: Sun, 28 Dec 2025 15:49:46 -0600 Subject: [PATCH 06/11] renamed WorkflowResourceSpec --- ...flowResourceSpec.scala => WorkflowResourceBackendSpec.scala} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/{WorkflowResourceSpec.scala => WorkflowResourceBackendSpec.scala} (97%) diff --git a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceSpec.scala b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceBackendSpec.scala similarity index 97% rename from amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceSpec.scala rename to amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceBackendSpec.scala index efa69c71fb3..a0758deb498 100644 --- a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceSpec.scala +++ b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceBackendSpec.scala @@ -32,7 +32,7 @@ import java.util.UUID import java.util.concurrent.atomic.AtomicInteger import javax.ws.rs.NotAcceptableException -class WorkflowResourceSpec extends AnyFlatSpec with BeforeAndAfterAll with MockTexeraDB { +class WorkflowResourceBackendSpec extends AnyFlatSpec with BeforeAndAfterAll with MockTexeraDB { private val widSeq = new AtomicInteger(3000) private val uidSeq = new AtomicInteger(1000) From 33afa8810a90884f164751c4a1208ab7dfda71a0 Mon Sep 17 00:00:00 2001 From: Carlos Ernesto Alvarez Berumen Date: Sun, 28 Dec 2025 15:54:11 -0600 Subject: [PATCH 07/11] Update WorkflowResourceDashboardUserSpec.scala --- ...ackendSpec.scala => WorkflowResourceDashboardUserSpec.scala} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/{WorkflowResourceBackendSpec.scala => WorkflowResourceDashboardUserSpec.scala} (97%) diff --git a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceBackendSpec.scala b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala similarity index 97% rename from amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceBackendSpec.scala rename to amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala index a0758deb498..c7618f14fa6 100644 --- a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceBackendSpec.scala +++ b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala @@ -32,7 +32,7 @@ import java.util.UUID import java.util.concurrent.atomic.AtomicInteger import javax.ws.rs.NotAcceptableException -class WorkflowResourceBackendSpec extends AnyFlatSpec with BeforeAndAfterAll with MockTexeraDB { +class WorkflowResourceDashboardUserSpec extends AnyFlatSpec with BeforeAndAfterAll with MockTexeraDB { private val widSeq = new AtomicInteger(3000) private val uidSeq = new AtomicInteger(1000) From 785f86f1bdc37862236a6193f0464bf57cf9189e Mon Sep 17 00:00:00 2001 From: Carlos Ernesto Alvarez Berumen Date: Sun, 28 Dec 2025 21:56:22 -0600 Subject: [PATCH 08/11] v3 getOwnerName --- .../user/workflow/WorkflowResource.scala | 36 +++-------- .../WorkflowResourceDashboardUserSpec.scala | 60 +++---------------- .../workflow-persist.service.ts | 17 ++---- .../detail/hub-workflow-detail.component.ts | 6 +- 4 files changed, 25 insertions(+), 94 deletions(-) diff --git a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala index 9ee5afd2bbe..2a25821659a 100644 --- a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala +++ b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala @@ -712,36 +712,16 @@ class WorkflowResource extends LazyLogging { } @GET - @Path("/owner_info") - @Produces(Array(MediaType.APPLICATION_JSON)) - def getOwnerInfo( - @QueryParam("wid") wid: Integer, - @QueryParam("fields") fields: java.util.List[String] // e.g. &fields=name&fields=... - ): User = { - - val allowedFields = Map( - "name" -> USER.NAME - ) - - val requestedFields = - Option(fields) - .map(_.asScala.toList) - .getOrElse(List("name")) - .map(_.trim.toLowerCase) - .filter(_.nonEmpty) - .distinct - - val selectedFields = requestedFields.map { field => - allowedFields.getOrElse(field, throw new NotAcceptableException(s"Unsupported field: $field")) - } - + @Path("/owner_name") + @Produces(Array(MediaType.TEXT_PLAIN)) + def getOwnerName(@QueryParam("wid") wid: Integer): String = { context - .select(selectedFields: _*) - .from(WORKFLOW_OF_USER) - .join(USER) - .on(WORKFLOW_OF_USER.UID.eq(USER.UID)) + .select(USER.NAME) + .from(USER) + .join(WORKFLOW_OF_USER) + .on(USER.UID.eq(WORKFLOW_OF_USER.UID)) .where(WORKFLOW_OF_USER.WID.eq(wid)) - .fetchOneInto(classOf[User]) + .fetchOneInto(classOf[String]) } @GET diff --git a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala index c7618f14fa6..57163d3393f 100644 --- a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala +++ b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala @@ -1,22 +1,3 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - package org.apache.texera.web.resource.dashboard.user.workflow import org.apache.texera.dao.MockTexeraDB @@ -27,23 +8,19 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.BeforeAndAfterAll import java.sql.Timestamp -import java.util import java.util.UUID import java.util.concurrent.atomic.AtomicInteger -import javax.ws.rs.NotAcceptableException -class WorkflowResourceDashboardUserSpec extends AnyFlatSpec with BeforeAndAfterAll with MockTexeraDB { +class WorkflowResourceDashboardUserSpec + extends AnyFlatSpec + with BeforeAndAfterAll + with MockTexeraDB { private val widSeq = new AtomicInteger(3000) private val uidSeq = new AtomicInteger(1000) - override protected def beforeAll(): Unit = { - initializeDBAndReplaceDSLContext() - } - - override protected def afterAll(): Unit = { - shutdownDB() - } + override protected def beforeAll(): Unit = initializeDBAndReplaceDSLContext() + override protected def afterAll(): Unit = shutdownDB() private def seedUser(uid: Int, name: String): Unit = { val userDao = new UserDao(getDSLContext.configuration()) @@ -67,7 +44,6 @@ class WorkflowResourceDashboardUserSpec extends AnyFlatSpec with BeforeAndAfterA workflowDao.insert(wf) } - /** Setup row needed by /owner_info */ private def linkWorkflowOwner(wid: Int, uid: Int): Unit = { getDSLContext .insertInto(WORKFLOW_OF_USER) @@ -76,7 +52,6 @@ class WorkflowResourceDashboardUserSpec extends AnyFlatSpec with BeforeAndAfterA .execute() } - // --- Owner-info test fixture (reusable for any owner_info-related tests) --- private def withOwnerInfoFixture(testCode: (Int, Int) => Any): Unit = { val wid = widSeq.getAndIncrement() val uid = uidSeq.getAndIncrement() @@ -100,27 +75,10 @@ class WorkflowResourceDashboardUserSpec extends AnyFlatSpec with BeforeAndAfterA getDSLContext.deleteFrom(USER).where(USER.UID.eq(uid)).execute() } - // --- Tests --- - - "WorkflowResource /owner_info" should "return owner info name when fields includes name" in - withOwnerInfoFixture { (wid, _) => - val resource = new WorkflowResource - val owner = resource.getOwnerInfo(wid, util.Arrays.asList("name")) - assert(owner.getName == "test_user") - } - - it should "default owner info fields to name when fields is missing" in - withOwnerInfoFixture { (wid, _) => - val resource = new WorkflowResource - val owner = resource.getOwnerInfo(wid, null) - assert(owner.getName == "test_user") - } - - it should "reject unsupported owner info fields" in + "WorkflowResource /owner_name" should "return owner name as plain text" in withOwnerInfoFixture { (wid, _) => val resource = new WorkflowResource - assertThrows[NotAcceptableException] { - resource.getOwnerInfo(wid, util.Arrays.asList("password")) - } + val ownerName = resource.getOwnerName(wid) + assert(ownerName == "test_user") } } diff --git a/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts b/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts index a9834dd7c20..5e738c5add2 100644 --- a/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts +++ b/frontend/src/app/common/service/workflow-persist/workflow-persist.service.ts @@ -41,7 +41,7 @@ export const WORKFLOW_UPDATENAME_URL = WORKFLOW_BASE_URL + "/update/name"; export const WORKFLOW_UPDATEDESCRIPTION_URL = WORKFLOW_BASE_URL + "/update/description"; export const WORKFLOW_OWNER_URL = WORKFLOW_BASE_URL + "/user-workflow-owners"; export const WORKFLOW_ID_URL = WORKFLOW_BASE_URL + "/user-workflow-ids"; -export const WORKFLOW_OWNER_INFO = WORKFLOW_BASE_URL + "/owner_info"; +export const WORKFLOW_OWNER_NAME = WORKFLOW_BASE_URL + "/owner_name"; export const WORKFLOW_NAME = WORKFLOW_BASE_URL + "/workflow_name"; export const WORKFLOW_PUBLIC_WORKFLOW = WORKFLOW_BASE_URL + "/publicised"; export const WORKFLOW_DESCRIPTION = WORKFLOW_BASE_URL + "/workflow_description"; @@ -238,19 +238,12 @@ export class WorkflowPersistService { } /** - * Retrieve owner info for a workflow (no login required). + * Retrieve workflow owner name (no login required). * @param wid workflow id - * @param fields which fields to return (default: ["name"]) */ - public getOwnerInfo(wid: number, fields: string[] = ["name"]): Observable { - let params = new HttpParams().set("wid", wid); - - // Repeat query param: &fields=name&fields=uid... - fields.forEach(f => { - params = params.append("fields", f); - }); - - return this.http.get(`${AppSettings.getApiEndpoint()}/${WORKFLOW_OWNER_INFO}`, { params }); + public getOwnerName(wid: number): Observable { + const params = new HttpParams().set("wid", wid); + return this.http.get(`${AppSettings.getApiEndpoint()}/${WORKFLOW_OWNER_NAME}`, { params, responseType: "text" }); } /** diff --git a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts index ce9a63daf9d..3b0d0b8c502 100644 --- a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts +++ b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.ts @@ -98,10 +98,10 @@ export class HubWorkflowDetailComponent implements AfterViewInit, OnDestroy, OnI this.viewCount = count; }); this.workflowPersistService - .getOwnerInfo(this.wid) + .getOwnerName(this.wid) .pipe(untilDestroyed(this)) - .subscribe(ownerInfo => { - this.ownerName = ownerInfo.name; + .subscribe(ownerName => { + this.ownerName = ownerName; }); this.workflowPersistService .getWorkflowName(this.wid) From 084e4946f7db9156e316e8512c8d36b9b0552c16 Mon Sep 17 00:00:00 2001 From: Carlos Ernesto Alvarez Berumen Date: Sun, 28 Dec 2025 22:03:37 -0600 Subject: [PATCH 09/11] Update WorkflowResource.scala --- .../web/resource/dashboard/user/workflow/WorkflowResource.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala index 2a25821659a..45ed49a23dc 100644 --- a/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala +++ b/amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala @@ -712,8 +712,8 @@ class WorkflowResource extends LazyLogging { } @GET - @Path("/owner_name") @Produces(Array(MediaType.TEXT_PLAIN)) + @Path("/owner_name") def getOwnerName(@QueryParam("wid") wid: Integer): String = { context .select(USER.NAME) From 946abc559c8f82f3a7077d46c56c2c79f7cfc104 Mon Sep 17 00:00:00 2001 From: Carlos Ernesto Alvarez Berumen Date: Sun, 28 Dec 2025 22:43:21 -0600 Subject: [PATCH 10/11] added apache header --- .../WorkflowResourceDashboardUserSpec.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala index 57163d3393f..be58ad0f168 100644 --- a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala +++ b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.apache.texera.web.resource.dashboard.user.workflow import org.apache.texera.dao.MockTexeraDB From e4b81aa88031cc3f5e743af2c67b9fa24216e1c5 Mon Sep 17 00:00:00 2001 From: Carlos Ernesto Alvarez Berumen Date: Mon, 29 Dec 2025 01:13:46 -0600 Subject: [PATCH 11/11] refactored testing --- .../dashboard/file/WorkflowResourceSpec.scala | 17 +++ .../WorkflowResourceDashboardUserSpec.scala | 103 ------------------ 2 files changed, 17 insertions(+), 103 deletions(-) delete mode 100644 amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala 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 3aa9b78d86b..74a68ee65ea 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 @@ -300,6 +300,23 @@ class WorkflowResourceSpec ) } + "WorkflowResource /owner_name" should "return owner name as plain text" in { + workflowResource.persistWorkflow(testWorkflow1, sessionUser1) + + val workflows = workflowResource.retrieveWorkflowsBySessionUser(sessionUser1) + assert(workflows.nonEmpty) + + val wid = + workflows + .find(_.workflow.getName == testWorkflow1.getName) + .map(_.workflow.getWid) + .getOrElse(workflows.head.workflow.getWid) + + val ownerName = workflowResource.getOwnerName(wid) + + assert(ownerName == testUser.getName) + } + "/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 diff --git a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala deleted file mode 100644 index be58ad0f168..00000000000 --- a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResourceDashboardUserSpec.scala +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.texera.web.resource.dashboard.user.workflow - -import org.apache.texera.dao.MockTexeraDB -import org.apache.texera.dao.jooq.generated.Tables._ -import org.apache.texera.dao.jooq.generated.tables.daos.{UserDao, WorkflowDao} -import org.apache.texera.dao.jooq.generated.tables.pojos.{User, Workflow} -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.BeforeAndAfterAll - -import java.sql.Timestamp -import java.util.UUID -import java.util.concurrent.atomic.AtomicInteger - -class WorkflowResourceDashboardUserSpec - extends AnyFlatSpec - with BeforeAndAfterAll - with MockTexeraDB { - - private val widSeq = new AtomicInteger(3000) - private val uidSeq = new AtomicInteger(1000) - - override protected def beforeAll(): Unit = initializeDBAndReplaceDSLContext() - override protected def afterAll(): Unit = shutdownDB() - - private def seedUser(uid: Int, name: String): Unit = { - val userDao = new UserDao(getDSLContext.configuration()) - val user = new User - user.setUid(uid) - user.setName(name) - user.setEmail(s"$name@example.com") - user.setPassword("password") - userDao.insert(user) - } - - private def seedWorkflow(wid: Int): Unit = { - val workflowDao = new WorkflowDao(getDSLContext.configuration()) - val wf = new Workflow - wf.setWid(wid) - wf.setName("test_workflow_" + UUID.randomUUID().toString.substring(0, 8)) - wf.setContent("{}") - wf.setDescription("test description") - wf.setCreationTime(new Timestamp(System.currentTimeMillis())) - wf.setLastModifiedTime(new Timestamp(System.currentTimeMillis())) - workflowDao.insert(wf) - } - - private def linkWorkflowOwner(wid: Int, uid: Int): Unit = { - getDSLContext - .insertInto(WORKFLOW_OF_USER) - .set(WORKFLOW_OF_USER.WID, Integer.valueOf(wid)) - .set(WORKFLOW_OF_USER.UID, Integer.valueOf(uid)) - .execute() - } - - private def withOwnerInfoFixture(testCode: (Int, Int) => Any): Unit = { - val wid = widSeq.getAndIncrement() - val uid = uidSeq.getAndIncrement() - - seedUser(uid, "test_user") - seedWorkflow(wid) - linkWorkflowOwner(wid, uid) - - try testCode(wid, uid) - finally cleanupOwnerInfoFixture(wid, uid) - } - - private def cleanupOwnerInfoFixture(wid: Int, uid: Int): Unit = { - getDSLContext - .deleteFrom(WORKFLOW_OF_USER) - .where(WORKFLOW_OF_USER.WID.eq(wid)) - .and(WORKFLOW_OF_USER.UID.eq(uid)) - .execute() - - getDSLContext.deleteFrom(WORKFLOW).where(WORKFLOW.WID.eq(wid)).execute() - getDSLContext.deleteFrom(USER).where(USER.UID.eq(uid)).execute() - } - - "WorkflowResource /owner_name" should "return owner name as plain text" in - withOwnerInfoFixture { (wid, _) => - val resource = new WorkflowResource - val ownerName = resource.getOwnerName(wid) - assert(ownerName == "test_user") - } -}