!641 Query history support pagination

Merge pull request !641 from 张冰清/dev
This commit is contained in:
i-robot 2021-03-01 09:59:36 +08:00 committed by Gitee
commit 45d4be1587
22 changed files with 125209 additions and 28911 deletions

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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>

View File

@ -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}/>&nbsp;&nbsp;
<span className="glyphicon glyphicon-ok" style={GLYPHICON_HIGHLIGHT} />&nbsp;&nbsp;
{query.queryStats.completedDrivers}
</span>
<span className="tinystat" data-toggle="tooltip" data-placement="top" title="Running splits">
<span className="glyphicon glyphicon-play" style={GLYPHICON_HIGHLIGHT}/>&nbsp;&nbsp;
<span className="glyphicon glyphicon-play" style={GLYPHICON_HIGHLIGHT} />&nbsp;&nbsp;
{(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}/>&nbsp;&nbsp;
<span className="glyphicon glyphicon-pause" style={GLYPHICON_HIGHLIGHT} />&nbsp;&nbsp;
{(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}/>&nbsp;&nbsp;
<span className="glyphicon glyphicon-hourglass" style={GLYPHICON_HIGHLIGHT} />&nbsp;&nbsp;
{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}/>&nbsp;&nbsp;
<span className="glyphicon glyphicon-time" style={GLYPHICON_HIGHLIGHT} />&nbsp;&nbsp;
{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}/>&nbsp;&nbsp;
<span className="glyphicon glyphicon-dashboard" style={GLYPHICON_HIGHLIGHT} />&nbsp;&nbsp;
{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}/>&nbsp;&nbsp;
<span className="glyphicon glyphicon-scale" style={GLYPHICON_HIGHLIGHT} />&nbsp;&nbsp;
{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}/>&nbsp;&nbsp;
<span className="glyphicon glyphicon-fire" style={GLYPHICON_HIGHLIGHT} />&nbsp;&nbsp;
{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}/>&nbsp;&nbsp;
<span className="glyphicon glyphicon-equalizer" style={GLYPHICON_HIGHLIGHT} />&nbsp;&nbsp;
{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}/>&nbsp;&nbsp;
<span className="glyphicon glyphicon-user" style={GLYPHICON_DEFAULT} />&nbsp;&nbsp;
<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}/>&nbsp;&nbsp;
<span className="glyphicon glyphicon-log-in" style={GLYPHICON_DEFAULT} />&nbsp;&nbsp;
<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}/>&nbsp;&nbsp;
<span className="glyphicon glyphicon-road" style={GLYPHICON_DEFAULT} />&nbsp;&nbsp;
<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} />&nbsp;{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}/>&nbsp;{filterText}
</button>
);
<li>
<a href="#" onClick={this.handleStateFilterClick.bind(this, filterType)}>
<span className="glyphicon glyphicon-ok" style={checkmarkStyle} />
&nbsp;{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} />
&nbsp;{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>
&nbsp;
<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>
&nbsp;
<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>
&nbsp;
<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>
&nbsp;
{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>
&nbsp;
</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>
);

View File

@ -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": [

View File

@ -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"

View File

@ -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));
}
}

View File

@ -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;
}
}