add pagination for query history;add user filter;
This commit is contained in:
parent
591c9958b1
commit
3d5ec838de
|
|
@ -53,8 +53,8 @@ public class QueryResource
|
|||
|
||||
@Inject
|
||||
public QueryResource(JobHistoryStore jobHistoryStore,
|
||||
QueryStore queryStore,
|
||||
ActiveJobsStore activeJobsStore)
|
||||
QueryStore queryStore,
|
||||
ActiveJobsStore activeJobsStore)
|
||||
{
|
||||
this.jobHistoryStore = jobHistoryStore;
|
||||
this.queryStore = queryStore;
|
||||
|
|
@ -80,7 +80,7 @@ public class QueryResource
|
|||
String user = UiAuthenticator.getUser(servletRequest);
|
||||
|
||||
if (tables.size() < 1) {
|
||||
recentlyRun = new HashSet<>(jobHistoryStore.getRecentlyRun(200));
|
||||
recentlyRun = new HashSet<>(jobHistoryStore.getRecentlyRunForUser(user, 200));
|
||||
Set<Job> activeJobs = activeJobsStore.getJobsForUser(user);
|
||||
recentlyRun.addAll(activeJobs);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,23 @@ public class LocalJobHistoryStore
|
|||
@Override
|
||||
public List<Job> getRecentlyRunForUser(String user, long maxResults)
|
||||
{
|
||||
return null;
|
||||
final ImmutableList.Builder<Job> builder = ImmutableList.builder();
|
||||
long added = 0;
|
||||
|
||||
for (Iterator<Job> job = historyCache.descendingIterator(); job.hasNext(); ) {
|
||||
Job nextJob = job.next();
|
||||
if (!nextJob.getUser().equals(user)) {
|
||||
continue;
|
||||
}
|
||||
if (added + 1 > maxResults) {
|
||||
break;
|
||||
}
|
||||
|
||||
builder.add(nextJob);
|
||||
added += 1;
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -26,11 +26,14 @@ import io.prestosql.execution.QueryManager;
|
|||
import io.prestosql.execution.QueryState;
|
||||
import io.prestosql.execution.QueryStats;
|
||||
import io.prestosql.execution.StageId;
|
||||
import io.prestosql.queryeditorui.security.UiAuthenticator;
|
||||
import io.prestosql.server.security.SecurityRequireNonNull;
|
||||
import io.prestosql.spi.ErrorType;
|
||||
import io.prestosql.spi.PrestoException;
|
||||
import io.prestosql.spi.QueryId;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.PUT;
|
||||
|
|
@ -38,9 +41,13 @@ import javax.ws.rs.Path;
|
|||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.NoSuchElementException;
|
||||
|
|
@ -48,6 +55,7 @@ import java.util.Optional;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static io.prestosql.client.PrestoHeaders.PRESTO_USER;
|
||||
import static io.prestosql.connector.system.KillQueryProcedure.createKillQueryException;
|
||||
import static io.prestosql.connector.system.KillQueryProcedure.createPreemptQueryException;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
|
@ -66,6 +74,12 @@ public class QueryResource
|
|||
private final DispatchManager dispatchManager;
|
||||
private final QueryManager queryManager;
|
||||
|
||||
public enum SortOrder
|
||||
{
|
||||
ASCENDING,
|
||||
DESCENDING,
|
||||
}
|
||||
|
||||
@Inject
|
||||
public QueryResource(DispatchManager dispatchManager, QueryManager queryManager, HttpServerInfo httpServerInfo)
|
||||
{
|
||||
|
|
@ -75,16 +89,110 @@ public class QueryResource
|
|||
}
|
||||
|
||||
@GET
|
||||
public List<BasicQueryInfo> getAllQueryInfo(@QueryParam("state") String stateFilter)
|
||||
public Response getAllQueryInfo(
|
||||
@QueryParam("state") String stateFilter,
|
||||
@QueryParam("failed") String failedFilter,
|
||||
@QueryParam("sort") String sortFilter,
|
||||
@QueryParam("sortOrder") String sortOrder,
|
||||
@QueryParam("search") String searchFilter,
|
||||
@QueryParam("pageNum") Integer pageNum,
|
||||
@QueryParam("pageSize") Integer pageSize,
|
||||
@Context HttpServletRequest servletRequest)
|
||||
{
|
||||
QueryState expectedState = stateFilter == null ? null : QueryState.valueOf(stateFilter.toUpperCase(Locale.ENGLISH));
|
||||
ImmutableList.Builder<BasicQueryInfo> builder = new ImmutableList.Builder<>();
|
||||
for (BasicQueryInfo queryInfo : dispatchManager.getQueries()) {
|
||||
if (stateFilter == null || queryInfo.getState() == expectedState) {
|
||||
builder.add(queryInfo);
|
||||
if (pageNum != null && pageNum <= 0) {
|
||||
return Response.status(Status.BAD_REQUEST).build();
|
||||
}
|
||||
if (pageSize != null && pageSize <= 0) {
|
||||
return Response.status(Status.BAD_REQUEST).build();
|
||||
}
|
||||
|
||||
String[] states = (stateFilter == null || stateFilter.equals("")) ? new String[0] : stateFilter.split(",");
|
||||
String[] failed = (failedFilter == null || failedFilter.equals("")) ? new String[0] : failedFilter.split(",");
|
||||
|
||||
HashMap<QueryState, Boolean> statesMap = new HashMap<>();
|
||||
for (String state : states) {
|
||||
try {
|
||||
QueryState expectedState = QueryState.valueOf(state.toUpperCase(Locale.ENGLISH));
|
||||
statesMap.put(expectedState, true);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return Response.status(Status.BAD_REQUEST).build();
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
|
||||
HashMap<ErrorType, Boolean> failedMap = new HashMap<>();
|
||||
for (String failedValue : failed) {
|
||||
try {
|
||||
ErrorType failedState = ErrorType.valueOf(failedValue.toUpperCase(Locale.ENGLISH));
|
||||
failedMap.put(failedState, true);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return Response.status(Status.BAD_REQUEST).build();
|
||||
}
|
||||
}
|
||||
|
||||
List<BasicQueryInfo> allQueries = dispatchManager.getQueries();
|
||||
List<BasicQueryInfo> filterQueries = new ArrayList<>();
|
||||
|
||||
for (BasicQueryInfo queryInfo : allQueries) {
|
||||
if (filterUser(queryInfo, servletRequest) && filterState(queryInfo, stateFilter, statesMap, failedFilter, failedMap) &&
|
||||
filterSearch(queryInfo, searchFilter)) {
|
||||
filterQueries.add(queryInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// default sort order ascending
|
||||
SortOrder sortOrderValue;
|
||||
if (sortOrder == null || sortOrder.equals("")) {
|
||||
sortOrderValue = SortOrder.ASCENDING;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
sortOrderValue = SortOrder.valueOf(sortOrder.toUpperCase(Locale.ENGLISH));
|
||||
}
|
||||
catch (Exception e) {
|
||||
return Response.status(Status.BAD_REQUEST).build();
|
||||
}
|
||||
}
|
||||
|
||||
if (sortFilter != null && !sortFilter.equals("")) {
|
||||
try {
|
||||
QuerySortFilter sort = QuerySortFilter.valueOf(sortFilter.toUpperCase(Locale.ENGLISH));
|
||||
if (sortOrderValue == SortOrder.DESCENDING) {
|
||||
filterQueries.sort(Collections.reverseOrder(sort.getCompare()));
|
||||
}
|
||||
else {
|
||||
filterQueries.sort(sort.getCompare());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return Response.status(Status.BAD_REQUEST).build();
|
||||
}
|
||||
}
|
||||
|
||||
if (pageNum == null || pageSize == null) {
|
||||
ImmutableList.Builder<BasicQueryInfo> builder = new ImmutableList.Builder<>();
|
||||
for (BasicQueryInfo queryInfo : filterQueries) {
|
||||
builder.add(queryInfo);
|
||||
}
|
||||
return Response.ok(builder.build()).build();
|
||||
}
|
||||
|
||||
// pagination
|
||||
int total = filterQueries.size();
|
||||
if (total == 0) {
|
||||
return Response.ok(new QueryResponse(total, filterQueries)).build();
|
||||
}
|
||||
else if (total - pageSize * (pageNum - 1) <= 0) {
|
||||
return Response.status(Status.BAD_REQUEST).build();
|
||||
}
|
||||
|
||||
QueryResponse res;
|
||||
int start = (pageNum - 1) * pageSize;
|
||||
int end = Math.min(pageNum * pageSize, total);
|
||||
List<BasicQueryInfo> subList = filterQueries.subList(start, end);
|
||||
res = new QueryResponse(total, subList);
|
||||
return Response.ok(res).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
|
|
@ -297,4 +405,69 @@ public class QueryResource
|
|||
String localhost = httpServerInfo.getHttpUri() != null ? httpServerInfo.getHttpUri().getHost() : httpServerInfo.getHttpsUri().getHost();
|
||||
return basicQueryInfo.getSelf().getHost().equals(localhost);
|
||||
}
|
||||
|
||||
private boolean filterUser(BasicQueryInfo queryInfo, HttpServletRequest servletRequest)
|
||||
{
|
||||
String sessionUser = queryInfo.getSession().getUser();
|
||||
if (sessionUser == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// presto user
|
||||
String user = servletRequest.getHeader(PRESTO_USER);
|
||||
if (user != null) {
|
||||
return sessionUser.equals(user);
|
||||
}
|
||||
|
||||
// authentication user mapping
|
||||
user = (String) servletRequest.getAttribute(PRESTO_USER);
|
||||
if (user != null) {
|
||||
return sessionUser.equals(user);
|
||||
}
|
||||
|
||||
// principle
|
||||
user = UiAuthenticator.getUser(servletRequest);
|
||||
return sessionUser.equals(user);
|
||||
}
|
||||
|
||||
private boolean filterState(BasicQueryInfo queryInfo,
|
||||
String stateFilter,
|
||||
HashMap<QueryState, Boolean> statesMap,
|
||||
String failedFilter,
|
||||
HashMap<ErrorType, Boolean> failedMap)
|
||||
{
|
||||
if (stateFilter == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QueryState state = queryInfo.getState();
|
||||
if (state == QueryState.FAILED) {
|
||||
if (failedFilter == null) {
|
||||
return statesMap.containsKey(state);
|
||||
}
|
||||
return statesMap.containsKey(state) && failedMap.containsKey(queryInfo.getErrorType());
|
||||
}
|
||||
|
||||
return statesMap.containsKey(state);
|
||||
}
|
||||
|
||||
private boolean filterSearch(BasicQueryInfo queryInfo, String searchFilter)
|
||||
{
|
||||
if (searchFilter == null || searchFilter.equals("")) {
|
||||
return true;
|
||||
}
|
||||
String queryId = queryInfo.getQueryId().toString().toLowerCase(Locale.ENGLISH);
|
||||
String query = queryInfo.getQuery().toLowerCase(Locale.ENGLISH);
|
||||
String user = queryInfo.getSession().getUser().toLowerCase(Locale.ENGLISH);
|
||||
String source = queryInfo.getSession().getSource().toString().toLowerCase(Locale.ENGLISH);
|
||||
String resource = queryInfo.getResourceGroupId().toString().toLowerCase(Locale.ENGLISH);
|
||||
if (queryId.contains(searchFilter) ||
|
||||
query.contains(searchFilter) ||
|
||||
user.contains(searchFilter) ||
|
||||
source.contains(searchFilter) ||
|
||||
resource.contains(searchFilter)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.prestosql.server;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class QueryResponse
|
||||
{
|
||||
private final int total;
|
||||
private final List<BasicQueryInfo> queries;
|
||||
|
||||
@JsonCreator
|
||||
public QueryResponse(
|
||||
@JsonProperty("total") int total,
|
||||
@JsonProperty("queries") List<BasicQueryInfo> queries)
|
||||
{
|
||||
this.total = total;
|
||||
this.queries = queries;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
public int getTotal()
|
||||
{
|
||||
return total;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
public List<BasicQueryInfo> getQueries()
|
||||
{
|
||||
return queries;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.prestosql.server;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public enum QuerySortFilter
|
||||
{
|
||||
// Creation Time
|
||||
CREATION((o1, o2) -> {
|
||||
return o1.getQueryStats().getCreateTime().compareTo(o2.getQueryStats().getCreateTime());
|
||||
}),
|
||||
// Elapsed Time
|
||||
ELAPSED((o1, o2) -> {
|
||||
return o1.getQueryStats().getElapsedTime().compareTo(o2.getQueryStats().getElapsedTime());
|
||||
}),
|
||||
// CPU Time
|
||||
CPU((o1, o2) -> {
|
||||
return o1.getQueryStats().getTotalCpuTime().compareTo(o2.getQueryStats().getTotalCpuTime());
|
||||
}),
|
||||
// Execution Time
|
||||
EXECUTION((o1, o2) -> {
|
||||
return o1.getQueryStats().getExecutionTime().compareTo(o2.getQueryStats().getExecutionTime());
|
||||
}),
|
||||
// Current Memory
|
||||
MEMORY((o1, o2) -> {
|
||||
return o1.getQueryStats().getUserMemoryReservation().compareTo(o2.getQueryStats().getUserMemoryReservation());
|
||||
}),
|
||||
// Cumulative User Memory
|
||||
CUMULATIVE((o1, o2) -> {
|
||||
double m1 = o1.getQueryStats().getCumulativeUserMemory();
|
||||
double m2 = o2.getQueryStats().getCumulativeUserMemory();
|
||||
return Double.compare(m1, m2);
|
||||
});
|
||||
|
||||
private final Comparator<BasicQueryInfo> compare;
|
||||
|
||||
QuerySortFilter(Comparator<BasicQueryInfo> compare)
|
||||
{
|
||||
this.compare = compare;
|
||||
}
|
||||
|
||||
public Comparator<BasicQueryInfo> getCompare()
|
||||
{
|
||||
return this.compare;
|
||||
}
|
||||
}
|
||||
|
|
@ -898,7 +898,7 @@ g .operator-stats:hover {
|
|||
}
|
||||
|
||||
.queryListContainer {
|
||||
max-height: calc(100vh - 190px);
|
||||
max-height: calc(100vh - 220px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -36,6 +36,7 @@
|
|||
<link rel="stylesheet" href="assets/fonts/font-awesome/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="assets/stylesheets/hetuui.css">
|
||||
<link rel="stylesheet" href="assets/stylesheets/headerfooter.css"/>
|
||||
<link rel="stylesheet" href="assets/stylesheets/plugins/rc-pagination.css"/>
|
||||
<!-- Custom CSS -->
|
||||
<link href="assets/presto.css" rel="stylesheet">
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -12,25 +12,23 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { Fragment } from "react";
|
||||
|
||||
import {
|
||||
formatDataSizeBytes,
|
||||
formatShortTime,
|
||||
getHumanReadableState,
|
||||
getProgressBarPercentage,
|
||||
getProgressBarTitle,
|
||||
getQueryStateColor,
|
||||
GLYPHICON_DEFAULT,
|
||||
GLYPHICON_HIGHLIGHT,
|
||||
parseDataSize,
|
||||
parseDuration,
|
||||
truncateString
|
||||
} from "../utils";
|
||||
import Header from "../queryeditor/components/Header";
|
||||
import Footer from "../queryeditor/components/Footer";
|
||||
import StatusFooter from "../queryeditor/components/StatusFooter";
|
||||
import NavigationMenu from "../NavigationMenu";
|
||||
import Pagination from 'rc-pagination';
|
||||
|
||||
export class QueryListItem extends React.Component {
|
||||
static stripQueryTextWhitespace(queryText) {
|
||||
|
|
@ -71,36 +69,36 @@ export class QueryListItem extends React.Component {
|
|||
|
||||
render() {
|
||||
const query = this.props.query;
|
||||
const progressBarStyle = {width: getProgressBarPercentage(query) + "%", backgroundColor: getQueryStateColor(query)};
|
||||
const progressBarStyle = { width: getProgressBarPercentage(query) + "%", backgroundColor: getQueryStateColor(query) };
|
||||
|
||||
const splitDetails = (
|
||||
<div className="col-xs-12 tinystat-row">
|
||||
<span className="tinystat" data-toggle="tooltip" data-placement="top" title="Completed splits">
|
||||
<span className="glyphicon glyphicon-ok" style={GLYPHICON_HIGHLIGHT}/>
|
||||
<span className="glyphicon glyphicon-ok" style={GLYPHICON_HIGHLIGHT} />
|
||||
{query.queryStats.completedDrivers}
|
||||
</span>
|
||||
<span className="tinystat" data-toggle="tooltip" data-placement="top" title="Running splits">
|
||||
<span className="glyphicon glyphicon-play" style={GLYPHICON_HIGHLIGHT}/>
|
||||
<span className="glyphicon glyphicon-play" style={GLYPHICON_HIGHLIGHT} />
|
||||
{(query.state === "FINISHED" || query.state === "FAILED") ? 0 : query.queryStats.runningDrivers}
|
||||
</span>
|
||||
<span className="tinystat" data-toggle="tooltip" data-placement="top" title="Queued splits">
|
||||
<span className="glyphicon glyphicon-pause" style={GLYPHICON_HIGHLIGHT}/>
|
||||
<span className="glyphicon glyphicon-pause" style={GLYPHICON_HIGHLIGHT} />
|
||||
{(query.state === "FINISHED" || query.state === "FAILED") ? 0 : query.queryStats.queuedDrivers}
|
||||
</span>
|
||||
</span>
|
||||
</div>);
|
||||
|
||||
const timingDetails = (
|
||||
<div className="col-xs-12 tinystat-row">
|
||||
<span className="tinystat" data-toggle="tooltip" data-placement="top" title="Wall time spent executing the query (not including queued time)">
|
||||
<span className="glyphicon glyphicon-hourglass" style={GLYPHICON_HIGHLIGHT}/>
|
||||
<span className="glyphicon glyphicon-hourglass" style={GLYPHICON_HIGHLIGHT} />
|
||||
{query.queryStats.executionTime}
|
||||
</span>
|
||||
<span className="tinystat" data-toggle="tooltip" data-placement="top" title="Total query wall time">
|
||||
<span className="glyphicon glyphicon-time" style={GLYPHICON_HIGHLIGHT}/>
|
||||
<span className="glyphicon glyphicon-time" style={GLYPHICON_HIGHLIGHT} />
|
||||
{query.queryStats.elapsedTime}
|
||||
</span>
|
||||
<span className="tinystat" data-toggle="tooltip" data-placement="top" title="CPU time spent by this query">
|
||||
<span className="glyphicon glyphicon-dashboard" style={GLYPHICON_HIGHLIGHT}/>
|
||||
<span className="glyphicon glyphicon-dashboard" style={GLYPHICON_HIGHLIGHT} />
|
||||
{query.queryStats.totalCpuTime}
|
||||
</span>
|
||||
</div>);
|
||||
|
|
@ -108,15 +106,15 @@ export class QueryListItem extends React.Component {
|
|||
const memoryDetails = (
|
||||
<div className="col-xs-12 tinystat-row">
|
||||
<span className="tinystat" data-toggle="tooltip" data-placement="top" title="Current total reserved memory">
|
||||
<span className="glyphicon glyphicon-scale" style={GLYPHICON_HIGHLIGHT}/>
|
||||
<span className="glyphicon glyphicon-scale" style={GLYPHICON_HIGHLIGHT} />
|
||||
{query.queryStats.totalMemoryReservation}
|
||||
</span>
|
||||
<span className="tinystat" data-toggle="tooltip" data-placement="top" title="Peak total memory">
|
||||
<span className="glyphicon glyphicon-fire" style={GLYPHICON_HIGHLIGHT}/>
|
||||
<span className="glyphicon glyphicon-fire" style={GLYPHICON_HIGHLIGHT} />
|
||||
{query.queryStats.peakTotalMemoryReservation}
|
||||
</span>
|
||||
<span className="tinystat" data-toggle="tooltip" data-placement="top" title="Cumulative user memory">
|
||||
<span className="glyphicon glyphicon-equalizer" style={GLYPHICON_HIGHLIGHT}/>
|
||||
<span className="glyphicon glyphicon-equalizer" style={GLYPHICON_HIGHLIGHT} />
|
||||
{formatDataSizeBytes(query.queryStats.cumulativeUserMemory / 1000.0)}
|
||||
</span>
|
||||
</div>);
|
||||
|
|
@ -124,7 +122,7 @@ export class QueryListItem extends React.Component {
|
|||
let user = (<span>{query.session.user}</span>);
|
||||
if (query.session.principal) {
|
||||
user = (
|
||||
<span>{query.session.user}<span className="glyphicon glyphicon-lock-inverse" style={GLYPHICON_DEFAULT}/></span>
|
||||
<span>{query.session.user}<span className="glyphicon glyphicon-lock-inverse" style={GLYPHICON_DEFAULT} /></span>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +141,7 @@ export class QueryListItem extends React.Component {
|
|||
<div className="row stat-row">
|
||||
<div className="col-xs-12">
|
||||
<span data-toggle="tooltip" data-placement="right" title="User">
|
||||
<span className="glyphicon glyphicon-user" style={GLYPHICON_DEFAULT}/>
|
||||
<span className="glyphicon glyphicon-user" style={GLYPHICON_DEFAULT} />
|
||||
<span>{truncateString(user, 35)}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -151,7 +149,7 @@ export class QueryListItem extends React.Component {
|
|||
<div className="row stat-row">
|
||||
<div className="col-xs-12">
|
||||
<span data-toggle="tooltip" data-placement="right" title="Source">
|
||||
<span className="glyphicon glyphicon-log-in" style={GLYPHICON_DEFAULT}/>
|
||||
<span className="glyphicon glyphicon-log-in" style={GLYPHICON_DEFAULT} />
|
||||
<span>{truncateString(query.session.source, 35)}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -159,7 +157,7 @@ export class QueryListItem extends React.Component {
|
|||
<div className="row stat-row">
|
||||
<div className="col-xs-12">
|
||||
<span data-toggle="tooltip" data-placement="right" title="Resource Group">
|
||||
<span className="glyphicon glyphicon-road" style={GLYPHICON_DEFAULT}/>
|
||||
<span className="glyphicon glyphicon-road" style={GLYPHICON_DEFAULT} />
|
||||
<span>{truncateString(query.resourceGroupId ? query.resourceGroupId.join(".") : "n/a", 35)}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -179,7 +177,7 @@ export class QueryListItem extends React.Component {
|
|||
<div className="col-xs-12 query-progress-container">
|
||||
<div className="progress">
|
||||
<div className="progress-bar progress-bar-info" role="progressbar" aria-valuenow={getProgressBarPercentage(query)} aria-valuemin="0"
|
||||
aria-valuemax="100" style={progressBarStyle}>
|
||||
aria-valuemax="100" style={progressBarStyle}>
|
||||
{getProgressBarTitle(query)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -201,7 +199,7 @@ class DisplayedQueriesList extends React.Component {
|
|||
render() {
|
||||
const queryNodes = this.props.queries.map(function (query) {
|
||||
return (
|
||||
<QueryListItem key={query.queryId} query={query}/>
|
||||
<QueryListItem key={query.queryId} query={query} />
|
||||
);
|
||||
}.bind(this));
|
||||
return (
|
||||
|
|
@ -213,32 +211,36 @@ class DisplayedQueriesList extends React.Component {
|
|||
}
|
||||
|
||||
const FILTER_TYPE = {
|
||||
RUNNING: function (query) {
|
||||
return !(query.state === "QUEUED" || query.state === "FINISHED" || query.state === "FAILED");
|
||||
},
|
||||
QUEUED: function (query) { return query.state === "QUEUED"},
|
||||
FINISHED: function (query) { return query.state === "FINISHED"},
|
||||
QUEUED: "queued",
|
||||
WAITING_FOR_RESOURCES: "waiting_for_resources",
|
||||
DISPATCHING: "dispatching",
|
||||
PLANNING: "planning",
|
||||
STARTING: "starting",
|
||||
RUNNING: "running",
|
||||
FINISHING: "finishing",
|
||||
FINISHED: "finished",
|
||||
FAILED: "failed",
|
||||
};
|
||||
|
||||
const SORT_TYPE = {
|
||||
CREATED: function (query) {return Date.parse(query.queryStats.createTime)},
|
||||
ELAPSED: function (query) {return parseDuration(query.queryStats.elapsedTime)},
|
||||
EXECUTION: function (query) {return parseDuration(query.queryStats.executionTime)},
|
||||
CPU: function (query) {return parseDuration(query.queryStats.totalCpuTime)},
|
||||
CUMULATIVE_MEMORY: function (query) {return query.queryStats.cumulativeUserMemory},
|
||||
CURRENT_MEMORY: function (query) {return parseDataSize(query.queryStats.userMemoryReservation)},
|
||||
CREATED: "creation",
|
||||
ELAPSED: "elapsed",
|
||||
EXECUTION: "execution",
|
||||
CPU: "cpu",
|
||||
CUMULATIVE_MEMORY: "cumulative",
|
||||
CURRENT_MEMORY: "memory",
|
||||
};
|
||||
|
||||
const ERROR_TYPE = {
|
||||
USER_ERROR: function (query) {return query.state === "FAILED" && query.errorType === "USER_ERROR"},
|
||||
INTERNAL_ERROR: function (query) {return query.state === "FAILED" && query.errorType === "INTERNAL_ERROR"},
|
||||
INSUFFICIENT_RESOURCES: function (query) {return query.state === "FAILED" && query.errorType === "INSUFFICIENT_RESOURCES"},
|
||||
EXTERNAL: function (query) {return query.state === "FAILED" && query.errorType === "EXTERNAL"},
|
||||
USER_ERROR: "user_error",
|
||||
INTERNAL_ERROR: "internal_error",
|
||||
INSUFFICIENT_RESOURCES: "insufficient_resources",
|
||||
EXTERNAL: "external",
|
||||
};
|
||||
|
||||
const SORT_ORDER = {
|
||||
ASCENDING: function (value) {return value},
|
||||
DESCENDING: function (value) {return -value}
|
||||
ASCENDING: "ascending",
|
||||
DESCENDING: "descending",
|
||||
};
|
||||
|
||||
export class QueryList extends React.Component {
|
||||
|
|
@ -246,76 +248,31 @@ export class QueryList extends React.Component {
|
|||
super(props);
|
||||
this.state = {
|
||||
allQueries: [],
|
||||
displayedQueries: [],
|
||||
reorderInterval: 5000,
|
||||
currentSortType: SORT_TYPE.CREATED,
|
||||
currentSortOrder: SORT_ORDER.DESCENDING,
|
||||
stateFilters: [FILTER_TYPE.RUNNING, FILTER_TYPE.QUEUED, FILTER_TYPE.FINISHED],
|
||||
stateFilters: [
|
||||
FILTER_TYPE.QUEUED,
|
||||
FILTER_TYPE.WAITING_FOR_RESOURCES,
|
||||
FILTER_TYPE.DISPATCHING,
|
||||
FILTER_TYPE.PLANNING,
|
||||
FILTER_TYPE.STARTING,
|
||||
FILTER_TYPE.RUNNING,
|
||||
FILTER_TYPE.FINISHING,
|
||||
FILTER_TYPE.FINISHED,
|
||||
FILTER_TYPE.FAILED],
|
||||
errorTypeFilters: [ERROR_TYPE.INTERNAL_ERROR, ERROR_TYPE.INSUFFICIENT_RESOURCES, ERROR_TYPE.EXTERNAL],
|
||||
searchString: '',
|
||||
maxQueries: 100,
|
||||
lastRefresh: Date.now(),
|
||||
lastReorder: Date.now(),
|
||||
initialized: false
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
};
|
||||
|
||||
this.refreshLoop = this.refreshLoop.bind(this);
|
||||
this.handleSearchStringChange = this.handleSearchStringChange.bind(this);
|
||||
this.executeSearch = this.executeSearch.bind(this);
|
||||
this.handleSortClick = this.handleSortClick.bind(this);
|
||||
}
|
||||
|
||||
sortAndLimitQueries(queries, sortType, sortOrder, maxQueries) {
|
||||
queries.sort(function (queryA, queryB) {
|
||||
return sortOrder(sortType(queryA) - sortType(queryB));
|
||||
}, this);
|
||||
|
||||
if (maxQueries !== 0 && queries.length > maxQueries) {
|
||||
queries.splice(maxQueries, (queries.length - maxQueries));
|
||||
}
|
||||
}
|
||||
|
||||
filterQueries(queries, stateFilters, errorTypeFilters, searchString) {
|
||||
const stateFilteredQueries = queries.filter(function (query) {
|
||||
for (let i = 0; i < stateFilters.length; i++) {
|
||||
if (stateFilters[i](query)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < errorTypeFilters.length; i++) {
|
||||
if (errorTypeFilters[i](query)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (searchString === '') {
|
||||
return stateFilteredQueries;
|
||||
}
|
||||
else {
|
||||
return stateFilteredQueries.filter(function (query) {
|
||||
const term = searchString.toLowerCase();
|
||||
if (query.queryId.toLowerCase().indexOf(term) !== -1 ||
|
||||
getHumanReadableState(query).toLowerCase().indexOf(term) !== -1 ||
|
||||
query.query.toLowerCase().indexOf(term) !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (query.session.user && query.session.user.toLowerCase().indexOf(term) !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (query.session.source && query.session.source.toLowerCase().indexOf(term) !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (query.resourceGroupId && query.resourceGroupId.join(".").toLowerCase().indexOf(term) !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}, this);
|
||||
}
|
||||
this.refreshData = this.refreshData.bind(this);
|
||||
this.onPageChange = this.onPageChange.bind(this);
|
||||
this.debounceSearch = _.debounce(() => { _.defer(this.refreshData) }, 200)
|
||||
}
|
||||
|
||||
resetTimer() {
|
||||
|
|
@ -328,63 +285,38 @@ export class QueryList extends React.Component {
|
|||
|
||||
refreshLoop() {
|
||||
clearTimeout(this.timeoutId); // to stop multiple series of refreshLoop from going on simultaneously
|
||||
clearTimeout(this.searchTimeoutId);
|
||||
this.refreshData();
|
||||
}
|
||||
|
||||
$.get('../v1/query', function (queryList) {
|
||||
const queryMap = queryList.reduce(function (map, query) {
|
||||
map[query.queryId] = query;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
let updatedQueries = [];
|
||||
this.state.displayedQueries.forEach(function (oldQuery) {
|
||||
if (oldQuery.queryId in queryMap) {
|
||||
updatedQueries.push(queryMap[oldQuery.queryId]);
|
||||
queryMap[oldQuery.queryId] = false;
|
||||
}
|
||||
});
|
||||
|
||||
let newQueries = [];
|
||||
for (const queryId in queryMap) {
|
||||
if (queryMap[queryId]) {
|
||||
newQueries.push(queryMap[queryId]);
|
||||
}
|
||||
}
|
||||
newQueries = this.filterQueries(newQueries, this.state.stateFilters, this.state.errorTypeFilters, this.state.searchString);
|
||||
|
||||
const lastRefresh = Date.now();
|
||||
let lastReorder = this.state.lastReorder;
|
||||
|
||||
if (this.state.reorderInterval !== 0 && ((lastRefresh - lastReorder) >= this.state.reorderInterval)) {
|
||||
updatedQueries = this.filterQueries(updatedQueries, this.state.stateFilters, this.state.errorTypeFilters, this.state.searchString);
|
||||
updatedQueries = updatedQueries.concat(newQueries);
|
||||
this.sortAndLimitQueries(updatedQueries, this.state.currentSortType, this.state.currentSortOrder, 0);
|
||||
lastReorder = Date.now();
|
||||
}
|
||||
else {
|
||||
this.sortAndLimitQueries(newQueries, this.state.currentSortType, this.state.currentSortOrder, 0);
|
||||
updatedQueries = updatedQueries.concat(newQueries);
|
||||
}
|
||||
|
||||
if (this.state.maxQueries !== 0 && (updatedQueries.length > this.state.maxQueries)) {
|
||||
updatedQueries.splice(this.state.maxQueries, (updatedQueries.length - this.state.maxQueries));
|
||||
}
|
||||
refreshData() {
|
||||
const { stateFilters, errorTypeFilters, currentSortType, currentSortOrder, searchString,
|
||||
currentPage, pageSize } = this.state;
|
||||
let queryParam = {
|
||||
state: stateFilters.join(","),
|
||||
failed: errorTypeFilters.join(","),
|
||||
sort: currentSortType,
|
||||
sortOrder: currentSortOrder,
|
||||
search: searchString,
|
||||
pageNum: currentPage,
|
||||
pageSize: pageSize,
|
||||
}
|
||||
let queryArray = [];
|
||||
_.each(queryParam, (value, key) => {
|
||||
queryArray.push(`${key}=${value}`);
|
||||
})
|
||||
let queryString = queryArray.join("&&");
|
||||
|
||||
$.get(`../v1/query?${queryString}`, function (queryList) {
|
||||
this.setState({
|
||||
allQueries: queryList,
|
||||
displayedQueries: updatedQueries,
|
||||
lastRefresh: lastRefresh,
|
||||
lastReorder: lastReorder,
|
||||
initialized: true
|
||||
allQueries: queryList.queries,
|
||||
total: queryList.total,
|
||||
});
|
||||
this.resetTimer();
|
||||
}.bind(this))
|
||||
.error(function () {
|
||||
this.setState({
|
||||
initialized: true,
|
||||
});
|
||||
this.resetTimer();
|
||||
}.bind(this));
|
||||
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -393,61 +325,20 @@ export class QueryList extends React.Component {
|
|||
|
||||
handleSearchStringChange(event) {
|
||||
const newSearchString = event.target.value;
|
||||
clearTimeout(this.searchTimeoutId);
|
||||
|
||||
this.setState({
|
||||
currentPage: 1,
|
||||
searchString: newSearchString
|
||||
});
|
||||
|
||||
this.searchTimeoutId = setTimeout(this.executeSearch, 200);
|
||||
this.debounceSearch();
|
||||
}
|
||||
|
||||
executeSearch() {
|
||||
clearTimeout(this.searchTimeoutId);
|
||||
|
||||
const newDisplayedQueries = this.filterQueries(this.state.allQueries, this.state.stateFilters, this.state.errorTypeFilters, this.state.searchString);
|
||||
this.sortAndLimitQueries(newDisplayedQueries, this.state.currentSortType, this.state.currentSortOrder, this.state.maxQueries);
|
||||
|
||||
this.setState({
|
||||
displayedQueries: newDisplayedQueries
|
||||
});
|
||||
}
|
||||
|
||||
renderMaxQueriesListItem(maxQueries, maxQueriesText) {
|
||||
return (
|
||||
<li><a href="#" className={this.state.maxQueries === maxQueries ? "selected" : ""} onClick={this.handleMaxQueriesClick.bind(this, maxQueries)}>{maxQueriesText}</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
handleMaxQueriesClick(newMaxQueries) {
|
||||
const filteredQueries = this.filterQueries(this.state.allQueries, this.state.stateFilters, this.state.errorTypeFilters, this.state.searchString);
|
||||
this.sortAndLimitQueries(filteredQueries, this.state.currentSortType, this.state.currentSortOrder, newMaxQueries);
|
||||
|
||||
this.setState({
|
||||
maxQueries: newMaxQueries,
|
||||
displayedQueries: filteredQueries
|
||||
});
|
||||
}
|
||||
|
||||
renderReorderListItem(interval, intervalText) {
|
||||
return (
|
||||
<li><a href="#" className={this.state.reorderInterval === interval ? "selected" : ""} onClick={this.handleReorderClick.bind(this, interval)}>{intervalText}</a></li>
|
||||
);
|
||||
}
|
||||
|
||||
handleReorderClick(interval) {
|
||||
if (this.state.reorderInterval !== interval) {
|
||||
this.setState({
|
||||
reorderInterval: interval,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderSortListItem(sortType, sortText) {
|
||||
if (this.state.currentSortType === sortType) {
|
||||
const directionArrow = this.state.currentSortOrder === SORT_ORDER.ASCENDING ? <span className="glyphicon glyphicon-triangle-top"/> :
|
||||
<span className="glyphicon glyphicon-triangle-bottom"/>;
|
||||
const directionArrow = this.state.currentSortOrder === SORT_ORDER.ASCENDING ? <span className="glyphicon glyphicon-triangle-top" /> :
|
||||
<span className="glyphicon glyphicon-triangle-bottom" />;
|
||||
return (
|
||||
<li>
|
||||
<a href="#" className="selected" onClick={this.handleSortClick.bind(this, sortType)}>
|
||||
|
|
@ -472,30 +363,38 @@ export class QueryList extends React.Component {
|
|||
if (this.state.currentSortType === sortType && this.state.currentSortOrder === SORT_ORDER.DESCENDING) {
|
||||
newSortOrder = SORT_ORDER.ASCENDING;
|
||||
}
|
||||
|
||||
const newDisplayedQueries = this.filterQueries(this.state.allQueries, this.state.stateFilters, this.state.errorTypeFilters, this.state.searchString);
|
||||
this.sortAndLimitQueries(newDisplayedQueries, newSortType, newSortOrder, this.state.maxQueries);
|
||||
|
||||
this.setState({
|
||||
displayedQueries: newDisplayedQueries,
|
||||
currentPage: 1,
|
||||
currentSortType: newSortType,
|
||||
currentSortOrder: newSortOrder
|
||||
});
|
||||
_.defer(this.refreshData);
|
||||
}
|
||||
|
||||
renderFilterButton(filterType, filterText) {
|
||||
let checkmarkStyle = {color: '#57aac7'};
|
||||
let classNames = "btn btn-sm btn-info style-check";
|
||||
if (this.state.stateFilters.indexOf(filterType) > -1) {
|
||||
classNames += " active";
|
||||
checkmarkStyle = {color: '#ffffff'};
|
||||
}
|
||||
// let checkmarkStyle = { color: '#57aac7' };
|
||||
// let classNames = "btn btn-sm btn-info style-check";
|
||||
// if (this.state.stateFilters.indexOf(filterType) > -1) {
|
||||
// classNames += " active";
|
||||
// checkmarkStyle = { color: '#ffffff' };
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <button type="button" className={classNames} onClick={this.handleStateFilterClick.bind(this, filterType)}>
|
||||
// <span className="glyphicon glyphicon-ok" style={checkmarkStyle} /> {filterText}
|
||||
// </button>
|
||||
// );
|
||||
let checkmarkStyle = { color: '#ffffff' };
|
||||
if (this.state.stateFilters.indexOf(filterType) > -1) {
|
||||
checkmarkStyle = GLYPHICON_HIGHLIGHT;
|
||||
}
|
||||
return (
|
||||
<button type="button" className={classNames} onClick={this.handleStateFilterClick.bind(this, filterType)}>
|
||||
<span className="glyphicon glyphicon-ok" style={checkmarkStyle}/> {filterText}
|
||||
</button>
|
||||
);
|
||||
<li>
|
||||
<a href="#" onClick={this.handleStateFilterClick.bind(this, filterType)}>
|
||||
<span className="glyphicon glyphicon-ok" style={checkmarkStyle} />
|
||||
{filterText}
|
||||
</a>
|
||||
</li>);
|
||||
}
|
||||
|
||||
handleStateFilterClick(filter) {
|
||||
|
|
@ -507,24 +406,22 @@ export class QueryList extends React.Component {
|
|||
newFilters.push(filter);
|
||||
}
|
||||
|
||||
const filteredQueries = this.filterQueries(this.state.allQueries, newFilters, this.state.errorTypeFilters, this.state.searchString);
|
||||
this.sortAndLimitQueries(filteredQueries, this.state.currentSortType, this.state.currentSortOrder);
|
||||
|
||||
this.setState({
|
||||
currentPage: 1,
|
||||
stateFilters: newFilters,
|
||||
displayedQueries: filteredQueries
|
||||
});
|
||||
_.defer(this.refreshData);
|
||||
}
|
||||
|
||||
renderErrorTypeListItem(errorType, errorTypeText) {
|
||||
let checkmarkStyle = {color: '#ffffff'};
|
||||
let checkmarkStyle = { color: '#ffffff' };
|
||||
if (this.state.errorTypeFilters.indexOf(errorType) > -1) {
|
||||
checkmarkStyle = GLYPHICON_HIGHLIGHT;
|
||||
}
|
||||
return (
|
||||
<li>
|
||||
<a href="#" onClick={this.handleErrorTypeFilterClick.bind(this, errorType)}>
|
||||
<span className="glyphicon glyphicon-ok" style={checkmarkStyle}/>
|
||||
<span className="glyphicon glyphicon-ok" style={checkmarkStyle} />
|
||||
{errorTypeText}
|
||||
</a>
|
||||
</li>);
|
||||
|
|
@ -539,26 +436,25 @@ export class QueryList extends React.Component {
|
|||
newFilters.push(errorType);
|
||||
}
|
||||
|
||||
const filteredQueries = this.filterQueries(this.state.allQueries, this.state.stateFilters, newFilters, this.state.searchString);
|
||||
this.sortAndLimitQueries(filteredQueries, this.state.currentSortType, this.state.currentSortOrder);
|
||||
|
||||
this.setState({
|
||||
currentPage: 1,
|
||||
errorTypeFilters: newFilters,
|
||||
displayedQueries: filteredQueries
|
||||
});
|
||||
_.defer(this.refreshData);
|
||||
}
|
||||
|
||||
onPageChange(current, pageSize) {
|
||||
this.setState({ currentPage: current });
|
||||
_.defer(this.refreshData);
|
||||
}
|
||||
|
||||
render() {
|
||||
let queryList = <DisplayedQueriesList queries={this.state.displayedQueries}/>;
|
||||
if (this.state.displayedQueries === null || this.state.displayedQueries.length === 0) {
|
||||
const { allQueries, currentPage, total, stateFilters } = this.state;
|
||||
let queryList = <DisplayedQueriesList queries={allQueries} />;
|
||||
if (allQueries.queries === null || total === 0) {
|
||||
let label = (<div className="loader">Loading...</div>);
|
||||
if (this.state.initialized) {
|
||||
if (this.state.allQueries === null || this.state.allQueries.length === 0) {
|
||||
label = "No queries";
|
||||
}
|
||||
else {
|
||||
label = "No queries matched filters";
|
||||
}
|
||||
if (allQueries.queries === null || total === 0) {
|
||||
label = "No queries";
|
||||
}
|
||||
queryList = (
|
||||
<div className="row error-message">
|
||||
|
|
@ -573,80 +469,81 @@ export class QueryList extends React.Component {
|
|||
<Header />
|
||||
</div>
|
||||
<div className='flex flex-row content'>
|
||||
<NavigationMenu active={"queryhistory"}/>
|
||||
<div className="container">
|
||||
<div className="row toolbar-row">
|
||||
<div className="col-xs-12 toolbar-col">
|
||||
<div className="input-group input-group-sm">
|
||||
<input type="text" className="form-control form-control-small search-bar" placeholder="User, source, query ID, resource group, or query text"
|
||||
onChange={this.handleSearchStringChange} value={this.state.searchString}/>
|
||||
<span className="input-group-addon filter-addon">State:</span>
|
||||
<div className="input-group-btn">
|
||||
{this.renderFilterButton(FILTER_TYPE.RUNNING, "Running")}
|
||||
{this.renderFilterButton(FILTER_TYPE.QUEUED, "Queued")}
|
||||
{this.renderFilterButton(FILTER_TYPE.FINISHED, "Finished")}
|
||||
<button type="button" id="error-type-dropdown" className="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Failed <span className="caret"/>
|
||||
</button>
|
||||
<ul className="dropdown-menu error-type-dropdown-menu">
|
||||
{this.renderErrorTypeListItem(ERROR_TYPE.INTERNAL_ERROR, "Internal Error")}
|
||||
{this.renderErrorTypeListItem(ERROR_TYPE.EXTERNAL, "External Error")}
|
||||
{this.renderErrorTypeListItem(ERROR_TYPE.INSUFFICIENT_RESOURCES, "Resources Error")}
|
||||
{this.renderErrorTypeListItem(ERROR_TYPE.USER_ERROR, "User Error")}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="input-group-btn">
|
||||
<button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Sort <span className="caret"/>
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
{this.renderSortListItem(SORT_TYPE.CREATED, "Creation Time")}
|
||||
{this.renderSortListItem(SORT_TYPE.ELAPSED, "Elapsed Time")}
|
||||
{this.renderSortListItem(SORT_TYPE.CPU, "CPU Time")}
|
||||
{this.renderSortListItem(SORT_TYPE.EXECUTION, "Execution Time")}
|
||||
{this.renderSortListItem(SORT_TYPE.CURRENT_MEMORY, "Current Memory")}
|
||||
{this.renderSortListItem(SORT_TYPE.CUMULATIVE_MEMORY, "Cumulative User Memory")}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="input-group-btn">
|
||||
<button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Reorder Interval <span className="caret"/>
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
{this.renderReorderListItem(1000, "1s")}
|
||||
{this.renderReorderListItem(5000, "5s")}
|
||||
{this.renderReorderListItem(10000, "10s")}
|
||||
{this.renderReorderListItem(30000, "30s")}
|
||||
<li role="separator" className="divider"/>
|
||||
{this.renderReorderListItem(0, "Off")}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="input-group-btn">
|
||||
<button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Show <span className="caret"/>
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
{this.renderMaxQueriesListItem(20, "20 queries")}
|
||||
{this.renderMaxQueriesListItem(50, "50 queries")}
|
||||
{this.renderMaxQueriesListItem(100, "100 queries")}
|
||||
<li role="separator" className="divider"/>
|
||||
{this.renderMaxQueriesListItem(0, "All queries")}
|
||||
</ul>
|
||||
<NavigationMenu active={"queryhistory"} />
|
||||
<div className="container">
|
||||
<div className="row toolbar-row">
|
||||
<div className="col-xs-12 toolbar-col">
|
||||
<div className="input-group input-group-sm">
|
||||
<input type="text" className="form-control form-control-small search-bar" placeholder="User, source, query ID, resource group, or query text"
|
||||
onChange={this.handleSearchStringChange} value={this.state.searchString} />
|
||||
<span className="input-group-addon filter-addon">State:</span>
|
||||
<div className="input-group-btn">
|
||||
<button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
State <span className="caret" />
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-left">
|
||||
{this.renderFilterButton(FILTER_TYPE.QUEUED, "Queued")}
|
||||
{this.renderFilterButton(FILTER_TYPE.WAITING_FOR_RESOURCES, "Waiting For Resources")}
|
||||
{this.renderFilterButton(FILTER_TYPE.DISPATCHING, "Dispatching")}
|
||||
{this.renderFilterButton(FILTER_TYPE.PLANNING, "Planning")}
|
||||
{this.renderFilterButton(FILTER_TYPE.STARTING, "Starting")}
|
||||
{this.renderFilterButton(FILTER_TYPE.RUNNING, "Running")}
|
||||
{this.renderFilterButton(FILTER_TYPE.FINISHING, "Finishing")}
|
||||
{this.renderFilterButton(FILTER_TYPE.FINISHED, "Finished")}
|
||||
{this.renderFilterButton(FILTER_TYPE.FAILED, "Failed")}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{stateFilters.indexOf(FILTER_TYPE.FAILED) > 0 &&
|
||||
<Fragment>
|
||||
<div className="input-group-btn">
|
||||
<button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Failed <span className="caret" />
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
{this.renderErrorTypeListItem(ERROR_TYPE.INTERNAL_ERROR, "Internal Error")}
|
||||
{this.renderErrorTypeListItem(ERROR_TYPE.EXTERNAL, "External Error")}
|
||||
{this.renderErrorTypeListItem(ERROR_TYPE.INSUFFICIENT_RESOURCES, "Resources Error")}
|
||||
{this.renderErrorTypeListItem(ERROR_TYPE.USER_ERROR, "User Error")}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</Fragment>
|
||||
}
|
||||
<div className="input-group-btn">
|
||||
<button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Sort <span className="caret" />
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-right">
|
||||
{this.renderSortListItem(SORT_TYPE.CREATED, "Creation Time")}
|
||||
{this.renderSortListItem(SORT_TYPE.ELAPSED, "Elapsed Time")}
|
||||
{this.renderSortListItem(SORT_TYPE.CPU, "CPU Time")}
|
||||
{this.renderSortListItem(SORT_TYPE.EXECUTION, "Execution Time")}
|
||||
{this.renderSortListItem(SORT_TYPE.CURRENT_MEMORY, "Current Memory")}
|
||||
{this.renderSortListItem(SORT_TYPE.CUMULATIVE_MEMORY, "Cumulative User Memory")}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{queryList}
|
||||
{allQueries.length > 0 &&
|
||||
<Pagination
|
||||
defaultCurrent={1}
|
||||
current={currentPage}
|
||||
total={total}
|
||||
onChange={this.onPageChange}
|
||||
showTotal={total => `Total ${total} items`}
|
||||
style={{ marginTop: 10 }}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{queryList}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-row flex-initial statusFooter'>
|
||||
<StatusFooter />
|
||||
<StatusFooter />
|
||||
</div>
|
||||
<div className='flex flex-row flex-initial footer'>
|
||||
<Footer/>
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@
|
|||
"webpack-command": "^0.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bosket/core": "latest",
|
||||
"@bosket/react": "latest",
|
||||
"@bosket/tools": "latest",
|
||||
"ace": "^1.3.0",
|
||||
"ace-builds": "1.4.10",
|
||||
"alt": "^0.14.3",
|
||||
|
|
@ -26,21 +29,18 @@
|
|||
"lodash": "^4.17.15",
|
||||
"moment": "^2.9.0",
|
||||
"moment-timezone": "^0.5.31",
|
||||
"rc-pagination": "^3.1.3",
|
||||
"react": "^16.8.0",
|
||||
"react-ace": "^9.0.0",
|
||||
"react-bootstrap": "^1.0.1",
|
||||
"react-contextmenu": "^2.14.0",
|
||||
"react-simple-multi-select": "^1.1.0",
|
||||
"react-dom": "^16.8.0",
|
||||
"react-moment": "^0.9.7",
|
||||
"react-simple-multi-select": "^1.1.0",
|
||||
"react-tabs": "^3.1.1",
|
||||
"reactable": "^1.0.2",
|
||||
"require-dir": "^1.2.0",
|
||||
"@bosket/core": "latest",
|
||||
"@bosket/tools": "latest",
|
||||
"@bosket/react": "latest",
|
||||
"react-transition-group": "^1.2.1",
|
||||
"rc-pagination": "^3.1.3"
|
||||
"reactable": "^1.0.2",
|
||||
"require-dir": "^1.2.0"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
|
|
|
|||
|
|
@ -81,6 +81,13 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.10.1":
|
||||
version "7.12.13"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d"
|
||||
integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7":
|
||||
version "7.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.4.tgz#a6724f1a6b8d2f6ea5236dbfe58c7d7ea9c5eb99"
|
||||
|
|
@ -1552,7 +1559,7 @@ class-utils@^0.3.5:
|
|||
isobject "^3.0.0"
|
||||
static-extend "^0.1.1"
|
||||
|
||||
classnames@^2.2.5, classnames@^2.2.6:
|
||||
classnames@^2.2.1, classnames@^2.2.5, classnames@^2.2.6:
|
||||
version "2.2.6"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
||||
|
|
@ -4601,6 +4608,14 @@ randomfill@^1.0.3:
|
|||
randombytes "^2.0.5"
|
||||
safe-buffer "^5.1.0"
|
||||
|
||||
rc-pagination@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-3.1.3.tgz#afd779839fefab2cb14248d5e7b74027960bb48b"
|
||||
integrity sha512-Z7CdC4xGkedfAwcUHPtfqNhYwVyDgkmhkvfsmoByCOwAd89p42t5O5T3ORar1wRmVWf3jxk/Bf4k0atenNvlFA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.1"
|
||||
classnames "^2.2.1"
|
||||
|
||||
rc@^1.0.1, rc@^1.1.6:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ public class TestQueryResource
|
|||
assertEquals(infos.size(), 3);
|
||||
assertStateCounts(infos, 2, 1, 0);
|
||||
|
||||
// test state
|
||||
infos = getQueryInfos("/v1/query?state=finished");
|
||||
assertEquals(infos.size(), 2);
|
||||
assertStateCounts(infos, 2, 0, 0);
|
||||
|
|
@ -90,6 +91,24 @@ public class TestQueryResource
|
|||
infos = getQueryInfos("/v1/query?state=running");
|
||||
assertEquals(infos.size(), 0);
|
||||
assertStateCounts(infos, 0, 0, 0);
|
||||
|
||||
//test failed
|
||||
infos = getQueryInfos("/v1/query?state=failed&&failed=user_error");
|
||||
assertEquals(infos.size(), 1);
|
||||
assertStateCounts(infos, 0, 1, 0);
|
||||
|
||||
infos = getQueryInfos("/v1/query?state=failed&&failed=internal_error");
|
||||
assertEquals(infos.size(), 0);
|
||||
assertStateCounts(infos, 0, 0, 0);
|
||||
|
||||
infos = getQueryInfos("/v1/query?state=finished&&failed=user_error");
|
||||
assertEquals(infos.size(), 2);
|
||||
assertStateCounts(infos, 2, 0, 0);
|
||||
|
||||
// test search
|
||||
infos = getQueryInfos("/v1/query?search=from");
|
||||
assertEquals(infos.size(), 1);
|
||||
assertStateCounts(infos, 0, 1, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -133,7 +152,7 @@ public class TestQueryResource
|
|||
|
||||
private List<BasicQueryInfo> getQueryInfos(String path)
|
||||
{
|
||||
Request request = prepareGet().setUri(server.resolve(path)).build();
|
||||
Request request = prepareGet().setHeader(PRESTO_USER, "user").setUri(server.resolve(path)).build();
|
||||
return client.execute(request, createJsonResponseHandler(listJsonCodec(BasicQueryInfo.class)));
|
||||
}
|
||||
|
||||
|
|
@ -172,7 +191,9 @@ public class TestQueryResource
|
|||
Request request = prepareGet()
|
||||
.setUri(uri)
|
||||
.build();
|
||||
JsonCodec<QueryInfo> codec = server.getInstance(Key.get(new TypeLiteral<JsonCodec<QueryInfo>>() {}));
|
||||
JsonCodec<QueryInfo> codec = server.getInstance(Key.get(new TypeLiteral<JsonCodec<QueryInfo>>()
|
||||
{
|
||||
}));
|
||||
return client.execute(request, createJsonResponseHandler(codec));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,10 +82,20 @@ public class TestQueryStateInfoResource
|
|||
|
||||
// queries are started in the background, so they may not all be immediately visible
|
||||
while (true) {
|
||||
List<BasicQueryInfo> queryInfos = client.execute(
|
||||
prepareGet().setUri(uriBuilderFrom(server.getBaseUrl()).replacePath("/v1/query").build()).build(),
|
||||
List<BasicQueryInfo> queryInfos1 = client.execute(
|
||||
prepareGet()
|
||||
.setUri(uriBuilderFrom(server.getBaseUrl()).replacePath("/v1/query").build())
|
||||
.setHeader(PRESTO_USER, "user1")
|
||||
.build(),
|
||||
createJsonResponseHandler(listJsonCodec(BasicQueryInfo.class)));
|
||||
if ((queryInfos.size() == 2) && queryInfos.stream().allMatch(info -> info.getState() == RUNNING)) {
|
||||
List<BasicQueryInfo> queryInfos2 = client.execute(
|
||||
prepareGet()
|
||||
.setUri(uriBuilderFrom(server.getBaseUrl()).replacePath("/v1/query").build())
|
||||
.setHeader(PRESTO_USER, "user2")
|
||||
.build(),
|
||||
createJsonResponseHandler(listJsonCodec(BasicQueryInfo.class)));
|
||||
if ((queryInfos1.size() == 1) && queryInfos1.stream().allMatch(info -> info.getState() == RUNNING) &&
|
||||
(queryInfos2.size() == 1) && queryInfos2.stream().allMatch(info -> info.getState() == RUNNING)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue