Skip to content

Commit de3451c

Browse files
zhey16kunwp1
authored andcommitted
Execution Dashboard Backend Pagination & Frontend Loading Icon (#3105)
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]>
1 parent f61a326 commit de3451c

File tree

5 files changed

+239
-136
lines changed

5 files changed

+239
-136
lines changed

core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/admin/execution/AdminExecutionResource.scala

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import edu.uci.ics.texera.web.auth.SessionUser
66
import edu.uci.ics.texera.dao.jooq.generated.Tables._
77
import edu.uci.ics.texera.web.resource.dashboard.admin.execution.AdminExecutionResource._
88
import io.dropwizard.auth.Auth
9+
import org.jooq.impl.DSL
910
import org.jooq.types.UInteger
1011

1112
import javax.annotation.security.RolesAllowed
1213
import javax.ws.rs._
1314
import javax.ws.rs.core.MediaType
14-
import scala.jdk.CollectionConverters.IterableHasAsScala
15+
import scala.jdk.CollectionConverters._
1516

1617
/**
1718
* This file handles various request related to saved-executions.
@@ -48,49 +49,130 @@ object AdminExecutionResource {
4849
}
4950
}
5051

52+
def mapToStatus(status: String): Int = {
53+
status match {
54+
case "READY" => 0
55+
case "RUNNING" => 1
56+
case "PAUSED" => 2
57+
case "COMPLETED" => 3
58+
case "FAILED" => 4
59+
case "KILLED" => 5
60+
case _ => -1 // or throw an exception, depends on your needs
61+
}
62+
}
63+
64+
val sortFieldMapping = Map(
65+
"workflow_name" -> WORKFLOW.NAME,
66+
"execution_name" -> WORKFLOW_EXECUTIONS.NAME,
67+
"initiator" -> USER.NAME,
68+
"end_time" -> WORKFLOW_EXECUTIONS.LAST_UPDATE_TIME
69+
)
70+
5171
}
5272

5373
@Produces(Array(MediaType.APPLICATION_JSON))
5474
@Path("/admin/execution")
5575
@RolesAllowed(Array("ADMIN"))
5676
class AdminExecutionResource {
5777

78+
@GET
79+
@Path("/totalWorkflow")
80+
@Produces()
81+
def getTotalWorkflows: Int = {
82+
context
83+
.select(
84+
DSL.countDistinct(WORKFLOW.WID)
85+
)
86+
.from(WORKFLOW_EXECUTIONS)
87+
.join(WORKFLOW_VERSION)
88+
.on(WORKFLOW_EXECUTIONS.VID.eq(WORKFLOW_VERSION.VID))
89+
.join(USER)
90+
.on(WORKFLOW_EXECUTIONS.UID.eq(USER.UID))
91+
.join(WORKFLOW)
92+
.on(WORKFLOW.WID.eq(WORKFLOW_VERSION.WID))
93+
.fetchOne(0, classOf[Int])
94+
}
95+
5896
/**
59-
* This method retrieves all existing executions
97+
* This method retrieves latest execution of each workflow for specified page.
98+
* The returned executions are sorted and filtered according to the parameters.
6099
*/
61100
@GET
62-
@Path("/executionList")
101+
@Path("/executionList/{pageSize}/{pageIndex}/{sortField}/{sortDirection}")
63102
@Produces(Array(MediaType.APPLICATION_JSON))
64-
def listWorkflows(@Auth current_user: SessionUser): List[dashboardExecution] = {
65-
val workflowEntries = context
103+
def listWorkflows(
104+
@Auth current_user: SessionUser,
105+
@PathParam("pageSize") page_size: Int = 20,
106+
@PathParam("pageIndex") page_index: Int = 0,
107+
@PathParam("sortField") sortField: String = "end_time",
108+
@PathParam("sortDirection") sortDirection: String = "desc",
109+
@QueryParam("filter") filter: java.util.List[String]
110+
): List[dashboardExecution] = {
111+
val filter_status = filter.asScala.map(mapToStatus).toSeq.filter(_ != -1).asJava
112+
113+
// Base query that retrieves latest execution info for each workflow without sorting and filtering.
114+
// Only retrieving executions in current page according to pageSize and pageIndex parameters.
115+
val executions_base_query = context
66116
.select(
67117
WORKFLOW_EXECUTIONS.UID,
68118
USER.NAME,
69119
WORKFLOW_VERSION.WID,
70120
WORKFLOW.NAME,
71121
WORKFLOW_EXECUTIONS.EID,
72-
WORKFLOW_EXECUTIONS.VID,
73122
WORKFLOW_EXECUTIONS.STARTING_TIME,
74123
WORKFLOW_EXECUTIONS.LAST_UPDATE_TIME,
75124
WORKFLOW_EXECUTIONS.STATUS,
76125
WORKFLOW_EXECUTIONS.NAME
77126
)
78127
.from(WORKFLOW_EXECUTIONS)
79-
.leftJoin(WORKFLOW_VERSION)
128+
.join(WORKFLOW_VERSION)
80129
.on(WORKFLOW_EXECUTIONS.VID.eq(WORKFLOW_VERSION.VID))
81-
.leftJoin(USER)
130+
.join(USER)
82131
.on(WORKFLOW_EXECUTIONS.UID.eq(USER.UID))
83-
.leftJoin(WORKFLOW)
132+
.join(WORKFLOW)
84133
.on(WORKFLOW.WID.eq(WORKFLOW_VERSION.WID))
85-
.fetch()
134+
.naturalJoin(
135+
context
136+
.select(
137+
DSL.max(WORKFLOW_EXECUTIONS.EID).as("eid")
138+
)
139+
.from(WORKFLOW_EXECUTIONS)
140+
.join(WORKFLOW_VERSION)
141+
.on(WORKFLOW_VERSION.VID.eq(WORKFLOW_EXECUTIONS.VID))
142+
.groupBy(WORKFLOW_VERSION.WID)
143+
)
144+
145+
// Apply filter if the status are not empty.
146+
val executions_apply_filter = if (!filter_status.isEmpty) {
147+
executions_base_query.where(WORKFLOW_EXECUTIONS.STATUS.in(filter_status))
148+
} else {
149+
executions_base_query
150+
}
151+
152+
// Apply sorting if user specified.
153+
var executions_apply_order =
154+
executions_apply_filter.limit(page_size).offset(page_index * page_size)
155+
if (sortField != "NO_SORTING") {
156+
executions_apply_order = executions_apply_filter
157+
.orderBy(
158+
if (sortDirection == "desc") sortFieldMapping.getOrElse(sortField, WORKFLOW.NAME).desc()
159+
else sortFieldMapping.getOrElse(sortField, WORKFLOW.NAME).asc()
160+
)
161+
.limit(page_size)
162+
.offset(page_index * page_size)
163+
}
164+
165+
val executions = executions_apply_order.fetch()
86166

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

93-
workflowEntries
174+
// Calculate the statistics needed for each execution.
175+
executions
94176
.map(workflowRecord => {
95177
val startingTime =
96178
workflowRecord.get(WORKFLOW_EXECUTIONS.STARTING_TIME).getTime

core/gui/src/app/dashboard/component/admin/execution/admin-execution.component.html

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,42 @@
1010

1111
<nz-table
1212
#basicTable
13-
[nzData]="listOfExecutions">
13+
nzShowSizeChanger
14+
[nzScroll]="{y: '500px'}"
15+
[nzData]="listOfExecutions"
16+
[nzLoading]="isLoading"
17+
[nzLoadingIndicator]="loadingTemplate"
18+
[nzTemplateMode]="true"
19+
[nzFrontPagination]="false"
20+
[nzTotal]="totalWorkflows"
21+
[nzPageSize]="pageSize"
22+
[nzPageIndex]="currentPageIndex + 1"
23+
[nzPageSizeOptions]="[5, 10, 20, 50]"
24+
(nzQueryParams)="onQueryParamsChange($event)"
25+
class="execution-table">
1426
<thead>
1527
<tr>
1628
<th
17-
[nzSortFn]="sortByWorkflowName"
18-
[nzSortDirections]="['ascend', 'descend']"
29+
[nzShowSort]="true"
30+
[nzSortFn]="true"
31+
[nzSortDirections]="['ascend', 'descend', null]"
32+
(nzSortOrderChange)="onSortChange('workflow_name', $event)"
1933
nzWidth="16%">
2034
Workflow (ID)
2135
</th>
2236
<th
23-
[nzSortFn]="sortByExecutionName"
24-
[nzSortDirections]="['ascend', 'descend']"
37+
[nzShowSort]="true"
38+
[nzSortFn]="true"
39+
[nzSortDirections]="['ascend', 'descend', null]"
40+
(nzSortOrderChange)="onSortChange('execution_name', $event)"
2541
nzWidth="16%">
2642
Execution Name (ID)
2743
</th>
2844
<th
29-
[nzSortFn]="sortByInitiator"
30-
[nzSortDirections]="['ascend', 'descend']"
45+
[nzShowSort]="true"
46+
[nzSortFn]="true"
47+
[nzSortDirections]="['ascend', 'descend', null]"
48+
(nzSortOrderChange)="onSortChange('initiator', $event)"
3149
nzWidth="12%">
3250
Initiator
3351
</th>
@@ -42,25 +60,28 @@
4260
{ text: 'KILLED', value: 'KILLED'},
4361
{ text: 'JUST COMPLETED', value: 'JUST COMPLETED'},
4462
{ text: 'UNKNOWN', value: 'UNKNOWN'}]"
45-
[nzFilterFn]="filterByStatus"
46-
nzWidth="16%">
63+
[nzFilterFn]="true"
64+
(nzFilterChange)="onFilterChange($event)"
65+
nzWidth="13%">
4766
Status
4867
</th>
4968
<th nzWidth="10%">Time Used (hh:mm:ss)</th>
5069
<th
51-
[nzSortFn]="sortByCompletedTime"
52-
[nzSortDirections]="['ascend', 'descend']"
53-
nzWidth="15%">
70+
[nzShowSort]="true"
71+
[nzSortFn]="true"
72+
[nzSortDirections]="['ascend', 'descend', null]"
73+
(nzSortOrderChange)="onSortChange('end_time', $event)"
74+
nzWidth="20%">
5475
End Time
5576
</th>
56-
<th nzWidth="15%">Action</th>
77+
<th nzWidth="13%">Action</th>
5778
</tr>
5879
</thead>
5980
<tbody>
6081
<tr *ngFor="let execution of basicTable.data">
6182
<td>
6283
<div *ngIf="execution.access; else normalWorkflowName">
63-
<a href="/workflow/{{execution.workflowId}}">
84+
<a href="/dashboard/user/workspace/{{execution.workflowId}}">
6485
{{ maxStringLength(execution.workflowName, 16) }} ({{ execution.workflowId }})
6586
</a>
6687
</div>
@@ -126,7 +147,7 @@
126147
nzType="redo"></i>
127148
</button>
128149
<button
129-
(click)="clickToViewHistory(execution.workflowId)"
150+
(click)="clickToViewHistory(execution.workflowId, execution.workflowName)"
130151
nz-button
131152
nz-tooltip="previous execution of the workflow: {{
132153
execution.workflowName
@@ -141,3 +162,12 @@
141162
</tr>
142163
</tbody>
143164
</nz-table>
165+
166+
<ng-template #loadingTemplate>
167+
<div class="loading-container">
168+
<nz-spin
169+
nzTip="Loading..."
170+
[nzSpinning]="true"
171+
nzSize="large"></nz-spin>
172+
</div>
173+
</ng-template>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.loading-container {
2+
display: flex;
3+
justify-content: center;
4+
flex-direction: column;
5+
height: 300px;
6+
}
7+
8+
.execution-table {
9+
display: block;
10+
grid-row-start: 3;
11+
grid-row-end: 4;
12+
}

0 commit comments

Comments
 (0)