diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/admin/execution/AdminExecutionResource.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/admin/execution/AdminExecutionResource.scala index 5bdb31437ee..9a280f921f1 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/admin/execution/AdminExecutionResource.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/admin/execution/AdminExecutionResource.scala @@ -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. @@ -48,6 +49,25 @@ 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)) @@ -55,42 +75,104 @@ object AdminExecutionResource { @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 diff --git a/core/gui/src/app/dashboard/component/admin/execution/admin-execution.component.html b/core/gui/src/app/dashboard/component/admin/execution/admin-execution.component.html index 115b5b339a2..66ebf1dbfcd 100644 --- a/core/gui/src/app/dashboard/component/admin/execution/admin-execution.component.html +++ b/core/gui/src/app/dashboard/component/admin/execution/admin-execution.component.html @@ -10,24 +10,42 @@ + 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"> Workflow (ID) Execution Name (ID) Initiator @@ -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 Time Used (hh:mm:ss) + [nzShowSort]="true" + [nzSortFn]="true" + [nzSortDirections]="['ascend', 'descend', null]" + (nzSortOrderChange)="onSortChange('end_time', $event)" + nzWidth="20%"> End Time - Action + Action
- + {{ maxStringLength(execution.workflowName, 16) }} ({{ execution.workflowId }})
@@ -126,7 +147,7 @@ nzType="redo">