Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor the landing page backend API and add the top-loved dataset. #3270

Merged
merged 13 commits into from
Feb 20, 2025
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,22 @@ object EntityTables {
type R <: Record
val table: Table[R]
val isPublicColumn: TableField[R, java.lang.Byte]
val idColumn: TableField[R, UInteger]
}

object BaseEntityTable {
case object WorkflowTable extends BaseEntityTable {
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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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
Expand Down
Loading