Skip to content

Commit 759f3b0

Browse files
authored
feat(webui): Add support for cancelling Presto SQL queries. (#1175)
1 parent cd31259 commit 759f3b0

File tree

10 files changed

+195
-8
lines changed

10 files changed

+195
-8
lines changed

components/webui/client/src/api/presto-search/index.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,28 @@ const submitQuery = async (
2828
return axios.post<PrestoQueryJobSchema>("/api/presto-search/query", payload);
2929
};
3030

31+
32+
/**
33+
* Sends post request to server to cancel presto query.
34+
*
35+
* @param payload
36+
* @return
37+
*/
38+
const cancelQuery = async (
39+
payload: PrestoQueryJobSchema
40+
): Promise<AxiosResponse<null>> => {
41+
console.log("Cancelling query:", JSON.stringify(payload));
42+
43+
return axios.post("/api/presto-search/cancel", payload);
44+
};
45+
46+
3147
export type {
3248
PrestoQueryJobCreationSchema,
3349
PrestoQueryJobSchema,
3450
};
3551

36-
export {submitQuery};
52+
export {
53+
cancelQuery,
54+
submitQuery,
55+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.cancelButton {
2+
width: 100%;
3+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {useCallback} from "react";
2+
3+
import {CloseOutlined} from "@ant-design/icons";
4+
import {
5+
Button,
6+
Tooltip,
7+
} from "antd";
8+
9+
import useSearchStore from "../../../../SearchState/index";
10+
import {handlePrestoQueryCancel} from "../../presto-search-requests";
11+
import styles from "./index.module.css";
12+
13+
14+
/**
15+
* Renders a button to cancel the SQL query.
16+
*
17+
* @return
18+
*/
19+
const CancelButton = () => {
20+
const searchJobId = useSearchStore((state) => state.searchJobId);
21+
22+
const handleClick = useCallback(() => {
23+
if (null === searchJobId) {
24+
console.error("Cannot cancel query: searchJobId is not set.");
25+
26+
return;
27+
}
28+
handlePrestoQueryCancel({searchJobId});
29+
}, [searchJobId]);
30+
31+
return (
32+
<Tooltip title={"Cancel query"}>
33+
<Button
34+
className={styles["cancelButton"] || ""}
35+
color={"red"}
36+
icon={<CloseOutlined/>}
37+
size={"large"}
38+
variant={"solid"}
39+
onClick={handleClick}
40+
>
41+
Cancel
42+
</Button>
43+
</Tooltip>
44+
);
45+
};
46+
47+
export default CancelButton;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.runButton {
2+
width: 100%;
3+
}

components/webui/client/src/pages/SearchPage/SearchControls/Presto/RunButton/index.tsx renamed to components/webui/client/src/pages/SearchPage/SearchControls/Presto/SqlSearchButton/RunButton/index.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import {
66
Tooltip,
77
} from "antd";
88

9-
import useSearchStore from "../../../SearchState/index";
10-
import {handlePrestoQuerySubmit} from "../presto-search-requests";
9+
import useSearchStore from "../../../../SearchState/index";
10+
import {SEARCH_UI_STATE} from "../../../../SearchState/typings";
11+
import {handlePrestoQuerySubmit} from "../../presto-search-requests";
12+
import styles from "./index.module.css";
1113

1214

1315
/**
@@ -16,6 +18,7 @@ import {handlePrestoQuerySubmit} from "../presto-search-requests";
1618
* @return
1719
*/
1820
const RunButton = () => {
21+
const searchUiState = useSearchStore((state) => state.searchUiState);
1922
const queryString = useSearchStore((state) => state.queryString);
2023

2124
const isQueryStringEmpty = "" === queryString;
@@ -30,11 +33,13 @@ const RunButton = () => {
3033
return (
3134
<Tooltip title={tooltipTitle}>
3235
<Button
36+
className={styles["runButton"] || ""}
3337
color={"green"}
34-
disabled={isQueryStringEmpty}
3538
icon={<CaretRightOutlined/>}
3639
size={"large"}
3740
variant={"solid"}
41+
disabled={isQueryStringEmpty ||
42+
searchUiState === SEARCH_UI_STATE.QUERY_ID_PENDING}
3843
onClick={handleClick}
3944
>
4045
Run
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.runButtonContainer {
2+
width: 130px;
3+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import useSearchStore from "../../../SearchState/index";
2+
import {SEARCH_UI_STATE} from "../../../SearchState/typings";
3+
import CancelButton from "./CancelButton";
4+
import styles from "./index.module.css";
5+
import RunButton from "./RunButton";
6+
7+
8+
/**
9+
* Renders a button to submit or cancel the SQL query.
10+
*
11+
* @return
12+
*/
13+
const SqlSearchButton = () => {
14+
const searchUiState = useSearchStore((state) => state.searchUiState);
15+
16+
return (
17+
<div className={styles["runButtonContainer"] || ""}>
18+
{ (searchUiState === SEARCH_UI_STATE.QUERYING) ?
19+
<CancelButton/> :
20+
<RunButton/>}
21+
</div>
22+
);
23+
};
24+
25+
export default SqlSearchButton;

components/webui/client/src/pages/SearchPage/SearchControls/Presto/presto-search-requests.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import {
2+
cancelQuery,
23
type PrestoQueryJobCreationSchema,
4+
type PrestoQueryJobSchema,
35
submitQuery,
46
} from "../../../../api/presto-search";
7+
import useSearchStore from "../../SearchState";
8+
import {SEARCH_UI_STATE} from "../../SearchState/typings";
59

610

711
/**
@@ -10,9 +14,30 @@ import {
1014
* @param payload
1115
*/
1216
const handlePrestoQuerySubmit = (payload: PrestoQueryJobCreationSchema) => {
17+
const {updateSearchJobId, updateSearchUiState, searchUiState} = useSearchStore.getState();
18+
19+
// User should NOT be able to submit a new query while an existing query is in progress.
20+
if (
21+
searchUiState !== SEARCH_UI_STATE.DEFAULT &&
22+
searchUiState !== SEARCH_UI_STATE.DONE
23+
) {
24+
console.error("Cannot submit query while existing query is in progress.");
25+
26+
return;
27+
}
28+
29+
updateSearchUiState(SEARCH_UI_STATE.QUERY_ID_PENDING);
30+
1331
submitQuery(payload)
1432
.then((result) => {
1533
const {searchJobId} = result.data;
34+
35+
updateSearchJobId(searchJobId);
36+
updateSearchUiState(SEARCH_UI_STATE.QUERYING);
37+
38+
// eslint-disable-next-line no-warning-comments
39+
// TODO: Delete previous query results when the backend is ready
40+
1641
console.debug(
1742
"Presto search job created - ",
1843
"Search job ID:",
@@ -24,4 +49,29 @@ const handlePrestoQuerySubmit = (payload: PrestoQueryJobCreationSchema) => {
2449
});
2550
};
2651

27-
export {handlePrestoQuerySubmit};
52+
/**
53+
* Cancels an ongoing Presto search query on server.
54+
*
55+
* @param payload
56+
*/
57+
const handlePrestoQueryCancel = (payload: PrestoQueryJobSchema) => {
58+
const {searchUiState, updateSearchUiState} = useSearchStore.getState();
59+
if (searchUiState !== SEARCH_UI_STATE.QUERYING) {
60+
console.error("Cannot cancel query if there is no ongoing query.");
61+
62+
return;
63+
}
64+
65+
updateSearchUiState(SEARCH_UI_STATE.DONE);
66+
cancelQuery(payload)
67+
.then(() => {
68+
console.debug("Query cancelled successfully");
69+
})
70+
.catch((err: unknown) => {
71+
console.error("Failed to cancel query:", err);
72+
});
73+
};
74+
75+
export {
76+
handlePrestoQueryCancel, handlePrestoQuerySubmit,
77+
};

components/webui/client/src/pages/SearchPage/SearchControls/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import {
66
} from "../../../config";
77
import Dataset from "./Dataset";
88
import styles from "./index.module.css";
9-
import RunButton from "./Presto/RunButton";
109
import SqlQueryInput from "./Presto/SqlQueryInput";
10+
import SqlSearchButton from "./Presto/SqlSearchButton";
1111
import QueryInput from "./QueryInput";
1212
import SearchButton from "./SearchButton";
1313
import TimeRangeInput from "./TimeRangeInput";
@@ -43,7 +43,7 @@ const SearchControls = () => {
4343
(
4444
<>
4545
<SqlQueryInput/>
46-
<RunButton/>
46+
<SqlSearchButton/>
4747
</>
4848
)}
4949
</div>

components/webui/server/src/routes/api/presto-search/index.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import {FastifyPluginAsyncTypebox} from "@fastify/type-provider-typebox";
1+
import {
2+
FastifyPluginAsyncTypebox,
3+
Type,
4+
} from "@fastify/type-provider-typebox";
25
import {StatusCodes} from "http-status-codes";
36

47
import {
@@ -148,6 +151,35 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
148151
return {searchJobId};
149152
}
150153
);
154+
155+
fastify.post(
156+
"/cancel",
157+
{
158+
schema: {
159+
body: PrestoQueryJobSchema,
160+
response: {
161+
[StatusCodes.NO_CONTENT]: Type.Null(),
162+
[StatusCodes.INTERNAL_SERVER_ERROR]: ErrorSchema,
163+
},
164+
tags: ["Presto Search"],
165+
},
166+
},
167+
async (request, reply) => {
168+
const {searchJobId} = request.body;
169+
await new Promise<void>((resolve, reject) => {
170+
Presto.client.kill(searchJobId, (error) => {
171+
if (null !== error) {
172+
reject(new Error("Failed to kill the Presto query job.", {cause: error}));
173+
}
174+
resolve();
175+
});
176+
});
177+
request.log.info(searchJobId, "Presto search cancelled");
178+
reply.code(StatusCodes.NO_CONTENT);
179+
180+
return null;
181+
}
182+
);
151183
};
152184

153185
export default plugin;

0 commit comments

Comments
 (0)