diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/hub/EntityTables.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/hub/EntityTables.scala index bb42647cca2..259441cbe97 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/hub/EntityTables.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/hub/EntityTables.scala @@ -19,6 +19,7 @@ object EntityTables { type R <: Record val table: Table[R] val isPublicColumn: TableField[R, java.lang.Byte] + val idColumn: TableField[R, UInteger] } object BaseEntityTable { @@ -26,12 +27,14 @@ object EntityTables { override type R = WorkflowRecord override val table: Table[WorkflowRecord] = WORKFLOW override val isPublicColumn: TableField[WorkflowRecord, java.lang.Byte] = WORKFLOW.IS_PUBLIC + override val idColumn: TableField[WorkflowRecord, UInteger] = WORKFLOW.WID } case object DatasetTable extends BaseEntityTable { override type R = DatasetRecord override val table: Table[DatasetRecord] = DATASET override val isPublicColumn: TableField[DatasetRecord, java.lang.Byte] = DATASET.IS_PUBLIC + override val idColumn: TableField[DatasetRecord, UInteger] = DATASET.DID } def apply(entityType: String): BaseEntityTable = { diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/hub/HubResource.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/hub/HubResource.scala index ad95146902b..d26374fe4e3 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/hub/HubResource.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/hub/HubResource.scala @@ -3,8 +3,8 @@ package edu.uci.ics.texera.web.resource.dashboard.hub import edu.uci.ics.amber.core.storage.StorageConfig import edu.uci.ics.texera.dao.SqlServer import edu.uci.ics.texera.dao.jooq.generated.Tables._ -import edu.uci.ics.texera.dao.jooq.generated.tables.pojos.Workflow import HubResource.{ + fetchDashboardDatasetsByDids, fetchDashboardWorkflowsByWids, getUserLCCount, isLikedHelper, @@ -13,18 +13,27 @@ import HubResource.{ userRequest, validateEntityType } -import edu.uci.ics.texera.web.resource.dashboard.user.workflow.WorkflowResource.DashboardWorkflow +import edu.uci.ics.texera.web.resource.dashboard.user.workflow.WorkflowResource.{ + DashboardWorkflow, + baseWorkflowSelect, + mapWorkflowEntries +} import org.jooq.impl.DSL import org.jooq.types.UInteger import java.util -import java.util.Collections import java.util.regex.Pattern import javax.servlet.http.HttpServletRequest import javax.ws.rs._ import javax.ws.rs.core.{Context, MediaType} import scala.jdk.CollectionConverters._ import EntityTables._ +import edu.uci.ics.texera.web.resource.dashboard.DashboardResource.DashboardClickableFileEntry +import edu.uci.ics.texera.web.resource.dashboard.user.dataset.DatasetResource.{ + DashboardDataset, + baseDatasetSelect, + mapDashboardDataset +} object HubResource { case class userRequest(entityId: UInteger, userId: UInteger, entityType: String) @@ -228,52 +237,31 @@ object HubResource { .fetchOne(0, classOf[Int]) } - // todo: refactor api related to landing page - def fetchDashboardWorkflowsByWids(wids: Seq[UInteger]): util.List[DashboardWorkflow] = { - if (wids.nonEmpty) { - context - .select( - WORKFLOW.NAME, - WORKFLOW.DESCRIPTION, - WORKFLOW.WID, - WORKFLOW.CREATION_TIME, - WORKFLOW.LAST_MODIFIED_TIME, - USER.NAME.as("ownerName"), - WORKFLOW_OF_USER.UID.as("ownerId") - ) - .from(WORKFLOW) - .join(WORKFLOW_OF_USER) - .on(WORKFLOW.WID.eq(WORKFLOW_OF_USER.WID)) - .join(USER) - .on(WORKFLOW_OF_USER.UID.eq(USER.UID)) - .where(WORKFLOW.WID.in(wids: _*)) - .fetch() - .asScala - .map(record => { - val workflow = new Workflow( - record.get(WORKFLOW.NAME), - record.get(WORKFLOW.DESCRIPTION), - record.get(WORKFLOW.WID), - null, - record.get(WORKFLOW.CREATION_TIME), - record.get(WORKFLOW.LAST_MODIFIED_TIME), - null - ) + def fetchDashboardWorkflowsByWids(wids: Seq[UInteger], uid: UInteger): List[DashboardWorkflow] = { + if (wids.isEmpty) { + return List.empty[DashboardWorkflow] + } - DashboardWorkflow( - isOwner = false, - accessLevel = "", - ownerName = record.get("ownerName", classOf[String]), - workflow = workflow, - projectIDs = List(), - ownerId = record.get("ownerId", classOf[UInteger]) - ) - }) - .toList - .asJava - } else { - Collections.emptyList[DashboardWorkflow]() + val records = baseWorkflowSelect() + .where(WORKFLOW.WID.in(wids: _*)) + .groupBy(WORKFLOW.WID) + .fetch() + + mapWorkflowEntries(records, uid) + } + + def fetchDashboardDatasetsByDids(dids: Seq[UInteger], uid: UInteger): List[DashboardDataset] = { + if (dids.isEmpty) { + return List.empty[DashboardDataset] } + + val records = baseDatasetSelect() + .where(DATASET.DID.in(dids: _*)) + .groupBy(DATASET.DID) + .fetch() + + println(mapDashboardDataset(records, uid)) + mapDashboardDataset(records, uid) } } @@ -411,42 +399,65 @@ class HubResource { } @GET - @Path("/topLovedWorkflows") + @Path("/getTops") @Produces(Array(MediaType.APPLICATION_JSON)) - def getTopLovedWorkflows: util.List[DashboardWorkflow] = { - val topLovedWorkflowsWids = context - .select(WORKFLOW_USER_LIKES.WID) - .from(WORKFLOW_USER_LIKES) - .join(WORKFLOW) - .on(WORKFLOW_USER_LIKES.WID.eq(WORKFLOW.WID)) - .where(WORKFLOW.IS_PUBLIC.eq(1.toByte)) - .groupBy(WORKFLOW_USER_LIKES.WID) - .orderBy(DSL.count(WORKFLOW_USER_LIKES.WID).desc()) - .limit(8) - .fetchInto(classOf[UInteger]) - .asScala - .toSeq + def getTops( + @QueryParam("entityType") entityType: String, + @QueryParam("actionType") actionType: String, + @QueryParam("uid") uid: Integer + ): util.List[DashboardClickableFileEntry] = { + validateEntityType(entityType) - fetchDashboardWorkflowsByWids(topLovedWorkflowsWids) - } + val baseTable = BaseEntityTable(entityType) + val entityTables = actionType match { + case "like" => LikeTable(entityType) + case "clone" => CloneTable(entityType) + case _ => throw new IllegalArgumentException(s"Invalid action type: $actionType") + } - @GET - @Path("/topClonedWorkflows") - @Produces(Array(MediaType.APPLICATION_JSON)) - def getTopClonedWorkflows: util.List[DashboardWorkflow] = { - val topClonedWorkflowsWids = context - .select(WORKFLOW_USER_CLONES.WID) - .from(WORKFLOW_USER_CLONES) - .join(WORKFLOW) - .on(WORKFLOW_USER_CLONES.WID.eq(WORKFLOW.WID)) - .where(WORKFLOW.IS_PUBLIC.eq(1.toByte)) - .groupBy(WORKFLOW_USER_CLONES.WID) - .orderBy(DSL.count(WORKFLOW_USER_CLONES.WID).desc()) + val (table, idColumn) = (entityTables.table, entityTables.idColumn) + val (isPublicColumn, baseIdColumn) = (baseTable.isPublicColumn, baseTable.idColumn) + + val topEntityIds = context + .select(idColumn) + .from(table) + .join(baseTable.table) + .on(idColumn.eq(baseIdColumn)) + .where(isPublicColumn.eq(1.toByte)) + .groupBy(idColumn) + .orderBy(DSL.count(idColumn).desc()) .limit(8) .fetchInto(classOf[UInteger]) .asScala .toSeq - fetchDashboardWorkflowsByWids(topClonedWorkflowsWids) + val currentUid: UInteger = if (uid == null || uid == -1) null else UInteger.valueOf(uid) + + val clickableFileEntries = + if (entityType == "workflow") { + val workflows = fetchDashboardWorkflowsByWids(topEntityIds, currentUid) + workflows.map { w => + DashboardClickableFileEntry( + resourceType = "workflow", + workflow = Some(w), + project = None, + dataset = None + ) + } + } else if (entityType == "dataset") { + val datasets = fetchDashboardDatasetsByDids(topEntityIds, currentUid) + datasets.map { d => + DashboardClickableFileEntry( + resourceType = "dataset", + workflow = None, + project = None, + dataset = Some(d) + ) + } + } else { + Seq.empty[DashboardClickableFileEntry] + } + + clickableFileEntries.toList.asJava } } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/dataset/DatasetResource.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/dataset/DatasetResource.scala index 98cb2b3724e..ac9165ec546 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/dataset/DatasetResource.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/dataset/DatasetResource.scala @@ -32,7 +32,7 @@ import io.dropwizard.auth.Auth import org.apache.commons.lang3.StringUtils import org.glassfish.jersey.media.multipart.{FormDataMultiPart, FormDataParam} import org.jooq.types.UInteger -import org.jooq.{DSLContext, EnumType} +import org.jooq.{DSLContext, EnumType, Record, Result, SelectJoinStep} import play.api.libs.json.Json import java.io.{IOException, InputStream, OutputStream} @@ -342,6 +342,34 @@ object DatasetResource { fileNodes: List[DatasetFileNode], size: Long ) + + def baseDatasetSelect(): SelectJoinStep[Record] = { + context + .select() + .from( + DATASET + .leftJoin(DATASET_USER_ACCESS) + .on(DATASET_USER_ACCESS.DID.eq(DATASET.DID)) + .leftJoin(USER) + .on(USER.UID.eq(DATASET.OWNER_UID)) + ) + } + + def mapDashboardDataset(records: Result[Record], uid: UInteger): List[DashboardDataset] = { + records.asScala.map { record => + val dataset = record.into(DATASET).into(classOf[Dataset]) + val datasetAccess = record.into(DATASET_USER_ACCESS).into(classOf[DatasetUserAccess]) + val ownerEmail = record.into(USER).getEmail + DashboardDataset( + isOwner = if (uid == null) false else dataset.getOwnerUid == uid, + dataset = dataset, + accessPrivilege = datasetAccess.getPrivilege, + versions = List(), + ownerEmail = ownerEmail, + size = calculateDatasetVersionSize(dataset.getDid) + ) + }.toList + } } @Produces(Array(MediaType.APPLICATION_JSON, "image/jpeg", "application/pdf")) @@ -626,35 +654,14 @@ class DatasetResource { ): List[DashboardDataset] = { val uid = user.getUid withTransaction(context)(ctx => { - var accessibleDatasets: ListBuffer[DashboardDataset] = ListBuffer() // first fetch all datasets user have explicit access to - accessibleDatasets = ListBuffer.from( - ctx - .select() - .from( - DATASET - .leftJoin(DATASET_USER_ACCESS) - .on(DATASET_USER_ACCESS.DID.eq(DATASET.DID)) - .leftJoin(USER) - .on(USER.UID.eq(DATASET.OWNER_UID)) - ) - .where(DATASET_USER_ACCESS.UID.eq(uid)) - .fetch() - .map(record => { - val dataset = record.into(DATASET).into(classOf[Dataset]) - val datasetAccess = record.into(DATASET_USER_ACCESS).into(classOf[DatasetUserAccess]) - val ownerEmail = record.into(USER).getEmail - DashboardDataset( - isOwner = dataset.getOwnerUid == uid, - dataset = dataset, - accessPrivilege = datasetAccess.getPrivilege, - versions = List(), - ownerEmail = ownerEmail, - size = calculateDatasetVersionSize(dataset.getDid) - ) - }) - .asScala - ) + + val userDatasetRecords = baseDatasetSelect() + .where(DATASET_USER_ACCESS.UID.eq(uid)) + .fetch() + + var accessibleDatasets: ListBuffer[DashboardDataset] = + ListBuffer.from(mapDashboardDataset(userDatasetRecords, uid)) // then we fetch the public datasets and merge it as a part of the result if not exist val publicDatasets = ctx diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala index 687dde8cb9b..d5c714ff3a8 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala @@ -19,9 +19,10 @@ import edu.uci.ics.texera.web.resource.dashboard.hub.HubResource.recordCloneActi import edu.uci.ics.texera.web.resource.dashboard.user.workflow.WorkflowAccessResource.hasReadAccess import edu.uci.ics.texera.web.resource.dashboard.user.workflow.WorkflowResource._ import io.dropwizard.auth.Auth -import org.jooq.Condition +import org.jooq.{Condition, Record9, SelectOnConditionStep} import org.jooq.impl.DSL.{groupConcatDistinct, noCondition} import org.jooq.types.UInteger +import org.jooq.{Record, Result} import java.sql.Timestamp import java.util @@ -160,6 +161,77 @@ object WorkflowResource { updatedContent.replace(oldId, newId) } } + + def baseWorkflowSelect(): SelectOnConditionStep[Record9[ + UInteger, + String, + String, + Timestamp, + Timestamp, + WorkflowUserAccessPrivilege, + UInteger, + String, + String + ]] = { + context + .select( + WORKFLOW.WID, + WORKFLOW.NAME, + WORKFLOW.DESCRIPTION, + WORKFLOW.CREATION_TIME, + WORKFLOW.LAST_MODIFIED_TIME, + WORKFLOW_USER_ACCESS.PRIVILEGE, + WORKFLOW_OF_USER.UID, + USER.NAME, + groupConcatDistinct(WORKFLOW_OF_PROJECT.PID).as("projects") + ) + .from(WORKFLOW) + .leftJoin(WORKFLOW_USER_ACCESS) + .on(WORKFLOW_USER_ACCESS.WID.eq(WORKFLOW.WID)) + .leftJoin(WORKFLOW_OF_USER) + .on(WORKFLOW_OF_USER.WID.eq(WORKFLOW.WID)) + .leftJoin(USER) + .on(USER.UID.eq(WORKFLOW_OF_USER.UID)) + .leftJoin(WORKFLOW_OF_PROJECT) + .on(WORKFLOW.WID.eq(WORKFLOW_OF_PROJECT.WID)) + } + + def mapWorkflowEntries( + workflowEntries: Result[Record9[ + UInteger, + String, + String, + Timestamp, + Timestamp, + WorkflowUserAccessPrivilege, + UInteger, + String, + String + ]], + uid: UInteger + ): List[DashboardWorkflow] = { + workflowEntries + .map(workflowRecord => + DashboardWorkflow( + if (uid != null) + workflowRecord.into(WORKFLOW_OF_USER).getUid.eq(uid) + else false, + workflowRecord + .into(WORKFLOW_USER_ACCESS) + .into(classOf[WorkflowUserAccess]) + .getPrivilege + .toString, + workflowRecord.into(USER).getName, + workflowRecord.into(WORKFLOW).into(classOf[Workflow]), + if (workflowRecord.component9() == null) List[UInteger]() + else + workflowRecord.component9().split(',').map(number => UInteger.valueOf(number)).toList, + workflowRecord.into(WORKFLOW_OF_USER).getUid + ) + ) + .asScala + .toList + } } @Produces(Array(MediaType.APPLICATION_JSON)) @@ -267,49 +339,11 @@ class WorkflowResource extends LazyLogging { @Auth sessionUser: SessionUser ): List[DashboardWorkflow] = { val user = sessionUser.getUser - val workflowEntries = context - .select( - WORKFLOW.WID, - WORKFLOW.NAME, - WORKFLOW.DESCRIPTION, - WORKFLOW.CREATION_TIME, - WORKFLOW.LAST_MODIFIED_TIME, - WORKFLOW_USER_ACCESS.PRIVILEGE, - WORKFLOW_OF_USER.UID, - USER.NAME, - groupConcatDistinct(WORKFLOW_OF_PROJECT.PID).as("projects") - ) - .from(WORKFLOW) - .leftJoin(WORKFLOW_USER_ACCESS) - .on(WORKFLOW_USER_ACCESS.WID.eq(WORKFLOW.WID)) - .leftJoin(WORKFLOW_OF_USER) - .on(WORKFLOW_OF_USER.WID.eq(WORKFLOW.WID)) - .leftJoin(USER) - .on(USER.UID.eq(WORKFLOW_OF_USER.UID)) - .leftJoin(WORKFLOW_OF_PROJECT) - .on(WORKFLOW.WID.eq(WORKFLOW_OF_PROJECT.WID)) + val workflowEntries = baseWorkflowSelect() .where(WORKFLOW_USER_ACCESS.UID.eq(user.getUid)) .groupBy(WORKFLOW.WID, WORKFLOW_OF_USER.UID) .fetch() - workflowEntries - .map(workflowRecord => - DashboardWorkflow( - workflowRecord.into(WORKFLOW_OF_USER).getUid.eq(user.getUid), - workflowRecord - .into(WORKFLOW_USER_ACCESS) - .into(classOf[WorkflowUserAccess]) - .getPrivilege - .toString, - workflowRecord.into(USER).getName, - workflowRecord.into(WORKFLOW).into(classOf[Workflow]), - if (workflowRecord.component9() == null) List[UInteger]() - else - workflowRecord.component9().split(',').map(number => UInteger.valueOf(number)).toList, - workflowRecord.into(WORKFLOW_OF_USER).getUid - ) - ) - .asScala - .toList + mapWorkflowEntries(workflowEntries, user.getUid) } /** diff --git a/core/gui/src/app/hub/component/browse-section/browse-section.component.html b/core/gui/src/app/hub/component/browse-section/browse-section.component.html index cb1371735ad..cf599458340 100644 --- a/core/gui/src/app/hub/component/browse-section/browse-section.component.html +++ b/core/gui/src/app/hub/component/browse-section/browse-section.component.html @@ -1,22 +1,22 @@
{{ workflow.description || 'No description available' }}
+ class="entity-card" + *ngFor="let entity of entities"> +{{ entity.description || 'No description available' }}
@@ -27,10 +27,10 @@