Skip to content

Commit

Permalink
Execution Dashboard Backend Pagination & Frontend Loading Icon (#3105)
Browse files Browse the repository at this point in the history
This PR addresses the issue #3016 that execution dashboard takes long
time to load.

Changed made:
- Add loading icon in the frontend while loading the execution info
- Move the pagination to backend, including the sorting and filtering
function.

Loading Icon: 

![execution_loading](https://github.com/user-attachments/assets/d7494b54-eb15-4361-b1cd-a70f276aa6f6)

Page Size Change:

![newnewnew](https://github.com/user-attachments/assets/9e1da835-7b2c-4ca1-a02b-87b7b3694101)

---------

Co-authored-by: Chris <[email protected]>
  • Loading branch information
MiuMiuMiue and kunwp1 authored Jan 16, 2025
1 parent 13cb52e commit c5c0701
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import edu.uci.ics.texera.web.auth.SessionUser
import edu.uci.ics.texera.dao.jooq.generated.Tables._
import edu.uci.ics.texera.web.resource.dashboard.admin.execution.AdminExecutionResource._
import io.dropwizard.auth.Auth
import org.jooq.impl.DSL
import org.jooq.types.UInteger

import javax.annotation.security.RolesAllowed
import javax.ws.rs._
import javax.ws.rs.core.MediaType
import scala.jdk.CollectionConverters.IterableHasAsScala
import scala.jdk.CollectionConverters._

/**
* This file handles various request related to saved-executions.
Expand Down Expand Up @@ -48,49 +49,130 @@ object AdminExecutionResource {
}
}

def mapToStatus(status: String): Int = {
status match {
case "READY" => 0
case "RUNNING" => 1
case "PAUSED" => 2
case "COMPLETED" => 3
case "FAILED" => 4
case "KILLED" => 5
case _ => -1 // or throw an exception, depends on your needs
}
}

val sortFieldMapping = Map(
"workflow_name" -> WORKFLOW.NAME,
"execution_name" -> WORKFLOW_EXECUTIONS.NAME,
"initiator" -> USER.NAME,
"end_time" -> WORKFLOW_EXECUTIONS.LAST_UPDATE_TIME
)

}

@Produces(Array(MediaType.APPLICATION_JSON))
@Path("/admin/execution")
@RolesAllowed(Array("ADMIN"))
class AdminExecutionResource {

@GET
@Path("/totalWorkflow")
@Produces()
def getTotalWorkflows: Int = {
context
.select(
DSL.countDistinct(WORKFLOW.WID)
)
.from(WORKFLOW_EXECUTIONS)
.join(WORKFLOW_VERSION)
.on(WORKFLOW_EXECUTIONS.VID.eq(WORKFLOW_VERSION.VID))
.join(USER)
.on(WORKFLOW_EXECUTIONS.UID.eq(USER.UID))
.join(WORKFLOW)
.on(WORKFLOW.WID.eq(WORKFLOW_VERSION.WID))
.fetchOne(0, classOf[Int])
}

/**
* This method retrieves all existing executions
* This method retrieves latest execution of each workflow for specified page.
* The returned executions are sorted and filtered according to the parameters.
*/
@GET
@Path("/executionList")
@Path("/executionList/{pageSize}/{pageIndex}/{sortField}/{sortDirection}")
@Produces(Array(MediaType.APPLICATION_JSON))
def listWorkflows(@Auth current_user: SessionUser): List[dashboardExecution] = {
val workflowEntries = context
def listWorkflows(
@Auth current_user: SessionUser,
@PathParam("pageSize") page_size: Int = 20,
@PathParam("pageIndex") page_index: Int = 0,
@PathParam("sortField") sortField: String = "end_time",
@PathParam("sortDirection") sortDirection: String = "desc",
@QueryParam("filter") filter: java.util.List[String]
): List[dashboardExecution] = {
val filter_status = filter.asScala.map(mapToStatus).toSeq.filter(_ != -1).asJava

// Base query that retrieves latest execution info for each workflow without sorting and filtering.
// Only retrieving executions in current page according to pageSize and pageIndex parameters.
val executions_base_query = context
.select(
WORKFLOW_EXECUTIONS.UID,
USER.NAME,
WORKFLOW_VERSION.WID,
WORKFLOW.NAME,
WORKFLOW_EXECUTIONS.EID,
WORKFLOW_EXECUTIONS.VID,
WORKFLOW_EXECUTIONS.STARTING_TIME,
WORKFLOW_EXECUTIONS.LAST_UPDATE_TIME,
WORKFLOW_EXECUTIONS.STATUS,
WORKFLOW_EXECUTIONS.NAME
)
.from(WORKFLOW_EXECUTIONS)
.leftJoin(WORKFLOW_VERSION)
.join(WORKFLOW_VERSION)
.on(WORKFLOW_EXECUTIONS.VID.eq(WORKFLOW_VERSION.VID))
.leftJoin(USER)
.join(USER)
.on(WORKFLOW_EXECUTIONS.UID.eq(USER.UID))
.leftJoin(WORKFLOW)
.join(WORKFLOW)
.on(WORKFLOW.WID.eq(WORKFLOW_VERSION.WID))
.fetch()
.naturalJoin(
context
.select(
DSL.max(WORKFLOW_EXECUTIONS.EID).as("eid")
)
.from(WORKFLOW_EXECUTIONS)
.join(WORKFLOW_VERSION)
.on(WORKFLOW_VERSION.VID.eq(WORKFLOW_EXECUTIONS.VID))
.groupBy(WORKFLOW_VERSION.WID)
)

// Apply filter if the status are not empty.
val executions_apply_filter = if (!filter_status.isEmpty) {
executions_base_query.where(WORKFLOW_EXECUTIONS.STATUS.in(filter_status))
} else {
executions_base_query
}

// Apply sorting if user specified.
var executions_apply_order =
executions_apply_filter.limit(page_size).offset(page_index * page_size)
if (sortField != "NO_SORTING") {
executions_apply_order = executions_apply_filter
.orderBy(
if (sortDirection == "desc") sortFieldMapping.getOrElse(sortField, WORKFLOW.NAME).desc()
else sortFieldMapping.getOrElse(sortField, WORKFLOW.NAME).asc()
)
.limit(page_size)
.offset(page_index * page_size)
}

val executions = executions_apply_order.fetch()

// Retrieve the id of each workflow that the user has access to.
val availableWorkflowIds = context
.select(WORKFLOW_USER_ACCESS.WID)
.from(WORKFLOW_USER_ACCESS)
.where(WORKFLOW_USER_ACCESS.UID.eq(current_user.getUid))
.fetchInto(classOf[UInteger])

workflowEntries
// Calculate the statistics needed for each execution.
executions
.map(workflowRecord => {
val startingTime =
workflowRecord.get(WORKFLOW_EXECUTIONS.STARTING_TIME).getTime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,42 @@

<nz-table
#basicTable
[nzData]="listOfExecutions">
nzShowSizeChanger
[nzScroll]="{y: '500px'}"
[nzData]="listOfExecutions"
[nzLoading]="isLoading"
[nzLoadingIndicator]="loadingTemplate"
[nzTemplateMode]="true"
[nzFrontPagination]="false"
[nzTotal]="totalWorkflows"
[nzPageSize]="pageSize"
[nzPageIndex]="currentPageIndex + 1"
[nzPageSizeOptions]="[5, 10, 20, 50]"
(nzQueryParams)="onQueryParamsChange($event)"
class="execution-table">
<thead>
<tr>
<th
[nzSortFn]="sortByWorkflowName"
[nzSortDirections]="['ascend', 'descend']"
[nzShowSort]="true"
[nzSortFn]="true"
[nzSortDirections]="['ascend', 'descend', null]"
(nzSortOrderChange)="onSortChange('workflow_name', $event)"
nzWidth="16%">
Workflow (ID)
</th>
<th
[nzSortFn]="sortByExecutionName"
[nzSortDirections]="['ascend', 'descend']"
[nzShowSort]="true"
[nzSortFn]="true"
[nzSortDirections]="['ascend', 'descend', null]"
(nzSortOrderChange)="onSortChange('execution_name', $event)"
nzWidth="16%">
Execution Name (ID)
</th>
<th
[nzSortFn]="sortByInitiator"
[nzSortDirections]="['ascend', 'descend']"
[nzShowSort]="true"
[nzSortFn]="true"
[nzSortDirections]="['ascend', 'descend', null]"
(nzSortOrderChange)="onSortChange('initiator', $event)"
nzWidth="12%">
Initiator
</th>
Expand All @@ -42,25 +60,28 @@
{ text: 'KILLED', value: 'KILLED'},
{ text: 'JUST COMPLETED', value: 'JUST COMPLETED'},
{ text: 'UNKNOWN', value: 'UNKNOWN'}]"
[nzFilterFn]="filterByStatus"
nzWidth="16%">
[nzFilterFn]="true"
(nzFilterChange)="onFilterChange($event)"
nzWidth="13%">
Status
</th>
<th nzWidth="10%">Time Used (hh:mm:ss)</th>
<th
[nzSortFn]="sortByCompletedTime"
[nzSortDirections]="['ascend', 'descend']"
nzWidth="15%">
[nzShowSort]="true"
[nzSortFn]="true"
[nzSortDirections]="['ascend', 'descend', null]"
(nzSortOrderChange)="onSortChange('end_time', $event)"
nzWidth="20%">
End Time
</th>
<th nzWidth="15%">Action</th>
<th nzWidth="13%">Action</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let execution of basicTable.data">
<td>
<div *ngIf="execution.access; else normalWorkflowName">
<a href="/workflow/{{execution.workflowId}}">
<a href="/dashboard/user/workspace/{{execution.workflowId}}">
{{ maxStringLength(execution.workflowName, 16) }} ({{ execution.workflowId }})
</a>
</div>
Expand Down Expand Up @@ -126,7 +147,7 @@
nzType="redo"></i>
</button>
<button
(click)="clickToViewHistory(execution.workflowId)"
(click)="clickToViewHistory(execution.workflowId, execution.workflowName)"
nz-button
nz-tooltip="previous execution of the workflow: {{
execution.workflowName
Expand All @@ -141,3 +162,12 @@
</tr>
</tbody>
</nz-table>

<ng-template #loadingTemplate>
<div class="loading-container">
<nz-spin
nzTip="Loading..."
[nzSpinning]="true"
nzSize="large"></nz-spin>
</div>
</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.loading-container {
display: flex;
justify-content: center;
flex-direction: column;
height: 300px;
}

.execution-table {
display: block;
grid-row-start: 3;
grid-row-end: 4;
}
Loading

0 comments on commit c5c0701

Please sign in to comment.