diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/Presto/SqlSearchButton/RunButton/GuidedRunButton.tsx b/components/webui/client/src/pages/SearchPage/SearchControls/Presto/SqlSearchButton/RunButton/GuidedRunButton.tsx index 7122e45857..25ceaec7f5 100644 --- a/components/webui/client/src/pages/SearchPage/SearchControls/Presto/SqlSearchButton/RunButton/GuidedRunButton.tsx +++ b/components/webui/client/src/pages/SearchPage/SearchControls/Presto/SqlSearchButton/RunButton/GuidedRunButton.tsx @@ -38,6 +38,8 @@ const GuidedRunButton = () => { const select = usePrestoSearchState((state) => state.select); const from = usePrestoSearchState((state) => state.from); const timestampKey = usePrestoSearchState((state) => state.timestampKey); + const updateCachedGuidedSearchQueryString = + usePrestoSearchState((state) => state.updateCachedGuidedSearchQueryString); const [messageApi, contextHolder] = message.useMessage(); @@ -69,6 +71,7 @@ const GuidedRunButton = () => { const {searchQueryString, timelineQueryString} = buildPrestoGuidedQueries(newTimeRange); handlePrestoGuidedQuerySubmit(searchQueryString, timelineQueryString); + updateCachedGuidedSearchQueryString(searchQueryString); const newTimelineConfig = computeTimelineConfig(newTimeRange); updateTimelineConfig(newTimelineConfig); }, [ @@ -77,6 +80,7 @@ const GuidedRunButton = () => { timeRangeOption, updateTimeRange, updateTimelineConfig, + updateCachedGuidedSearchQueryString, ]); return ( diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/Presto/presto-guided-search-requests.ts b/components/webui/client/src/pages/SearchPage/SearchControls/Presto/presto-guided-search-requests.ts index f493394c27..68aa936af9 100644 --- a/components/webui/client/src/pages/SearchPage/SearchControls/Presto/presto-guided-search-requests.ts +++ b/components/webui/client/src/pages/SearchPage/SearchControls/Presto/presto-guided-search-requests.ts @@ -119,6 +119,10 @@ const handlePrestoGuidedQuerySubmit = (searchQueryString: string, timelineQueryS searchUiState, } = useSearchStore.getState(); + const { + updateErrorMsg, updateErrorName, + } = usePrestoSearchState.getState(); + // User should NOT be able to submit a new query while an existing query is in progress. if ( searchUiState !== SEARCH_UI_STATE.DEFAULT && @@ -137,6 +141,9 @@ const handlePrestoGuidedQuerySubmit = (searchQueryString: string, timelineQueryS updateNumSearchResultsMetadata(SEARCH_STATE_DEFAULT.numSearchResultsMetadata); updateSearchUiState(SEARCH_UI_STATE.QUERY_ID_PENDING); + updateErrorMsg(null); + updateErrorName(null); + submitQuery({queryString: searchQueryString}) .then((result) => { const {searchJobId} = result.data; diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/OpenQueryDrawerButton.tsx b/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/OpenQueryDrawerButton.tsx new file mode 100644 index 0000000000..661aad29ce --- /dev/null +++ b/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/OpenQueryDrawerButton.tsx @@ -0,0 +1,47 @@ +import {EyeOutlined} from "@ant-design/icons"; +import { + Button, + theme, + Tooltip, +} from "antd"; + +import useSearchStore from "../../SearchState"; +import usePrestoSearchState from "../../SearchState/Presto"; +import {SEARCH_UI_STATE} from "../../SearchState/typings"; + + +/** + * Renders a button that opens the query drawer. + * + * @return + */ +const OpenQueryDrawerButton = () => { + const {token} = theme.useToken(); + const updateQueryDrawerOpen = usePrestoSearchState((s) => s.updateQueryDrawerOpen); + const searchUiState = useSearchStore((s) => s.searchUiState); + + const tooltipText = + searchUiState === SEARCH_UI_STATE.FAILED ? + "See modified query and error" : + "See modified query"; + + return ( + + + } + onClick={() => { + updateQueryDrawerOpen(true); + }}/> + + ); +}; + +export default OpenQueryDrawerButton; diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/QueryDrawer.module.css b/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/QueryDrawer.module.css new file mode 100644 index 0000000000..20d839f1b6 --- /dev/null +++ b/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/QueryDrawer.module.css @@ -0,0 +1,5 @@ +.container { + display: flex; + flex-direction: column; + gap: 16px; +} diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/QueryDrawer.tsx b/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/QueryDrawer.tsx new file mode 100644 index 0000000000..dd6501535f --- /dev/null +++ b/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/QueryDrawer.tsx @@ -0,0 +1,86 @@ +import { + Alert, + Drawer, + theme, + Typography, +} from "antd"; + +import SqlEditor from "../../../../components/SqlEditor"; +import useSearchStore from "../../SearchState"; +import usePrestoSearchState from "../../SearchState/Presto"; +import {SEARCH_UI_STATE} from "../../SearchState/typings"; +import styles from "./QueryDrawer.module.css"; + + +const {Text} = Typography; + +/** + * Renders the query details drawer, showing the modified SQL query and error (if any). + * + * @return + */ +const QueryDrawer = () => { + const {token} = theme.useToken(); + const queryDrawerOpen = usePrestoSearchState((s) => s.queryDrawerOpen); + const errorName = usePrestoSearchState((s) => s.errorName); + const errorMsg = usePrestoSearchState((s) => s.errorMsg); + const cachedGuidedSearchQueryString = + usePrestoSearchState((s) => s.cachedGuidedSearchQueryString); + const updateQueryDrawerOpen = usePrestoSearchState((s) => s.updateQueryDrawerOpen); + const searchUiState = useSearchStore((s) => s.searchUiState); + const searchJobId = useSearchStore((s) => s.searchJobId); + + return ( + { + updateQueryDrawerOpen(false); + }} + > + + + {"Modified SQL Query: "} + + + + + {searchUiState === SEARCH_UI_STATE.FAILED && ( + + )} + + + ); +}; + +export default QueryDrawer; diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/Results.tsx b/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/Results.tsx index df03b09010..9c93eb9655 100644 --- a/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/Results.tsx +++ b/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/Results.tsx @@ -1,16 +1,11 @@ import {useMemo} from "react"; -import { - GetProps, - Typography, -} from "antd"; +import {Typography} from "antd"; import useSearchStore from "../../SearchState/index"; -import {SEARCH_UI_STATE} from "../../SearchState/typings"; const {Text} = Typography; -type TextTypes = GetProps["type"]; /** @@ -22,7 +17,6 @@ const Results = () => { const numSearchResultsMetadata = useSearchStore((state) => state.numSearchResultsMetadata); const numSearchResultsTimeline = useSearchStore((state) => state.numSearchResultsTimeline); const numSearchResultsTable = useSearchStore((state) => state.numSearchResultsTable); - const searchUiState = useSearchStore((state) => state.searchUiState); // Number of results is the maximum from timeline, table, and server metadata sources. // Multiple sources provide more timely updates. Source behavior differs by query engine: @@ -37,29 +31,10 @@ const Results = () => { ] ); - let textType: TextTypes; - switch (searchUiState) { - case SEARCH_UI_STATE.QUERYING: - textType = "warning"; - break; - case SEARCH_UI_STATE.DEFAULT: - case SEARCH_UI_STATE.QUERY_ID_PENDING: - textType = "secondary"; - break; - case SEARCH_UI_STATE.DONE: - textType = "success"; - break; - case SEARCH_UI_STATE.FAILED: - textType = "danger"; - break; - default: - textType = "secondary"; - } - return ( {numResults} diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/index.tsx b/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/index.tsx index 597a3bf176..76f4146e7d 100644 --- a/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/index.tsx +++ b/components/webui/client/src/pages/SearchPage/SearchControls/QueryStatus/index.tsx @@ -1,7 +1,15 @@ import {Typography} from "antd"; +import { + CLP_QUERY_ENGINES, + SETTINGS_QUERY_ENGINE, +} from "../../../../config"; import useSearchStore from "../../SearchState/index"; +import usePrestoSearchState from "../../SearchState/Presto"; +import {PRESTO_SQL_INTERFACE} from "../../SearchState/Presto/typings"; import {SEARCH_UI_STATE} from "../../SearchState/typings"; +import OpenQueryDrawerButton from "./OpenQueryDrawerButton"; +import QueryDrawer from "./QueryDrawer"; import Results from "./Results"; @@ -18,22 +26,100 @@ const QueryStatus = () => { searchUiState, } = useSearchStore(); + const sqlInterface = usePrestoSearchState((state) => state.sqlInterface); + const isPrestoGuided = SETTINGS_QUERY_ENGINE === CLP_QUERY_ENGINES.PRESTO && + sqlInterface === PRESTO_SQL_INTERFACE.GUIDED; + return ( - - {(searchUiState === SEARCH_UI_STATE.QUERYING || - searchUiState === SEARCH_UI_STATE.DONE) && ( - - Search job + <> + {searchUiState === SEARCH_UI_STATE.QUERY_ID_PENDING && ( + <> + {isPrestoGuided && ( + <> + + {" "} + > + )} + + Running + + > + )} + {searchUiState === SEARCH_UI_STATE.QUERYING && ( + <> + {isPrestoGuided && ( + <> + + {" "} + > + )} + + Running + + + {` - search job ${searchJobId} found `} + + {" "} - {searchJobId} + results + > + )} + {searchUiState === SEARCH_UI_STATE.DONE && ( + <> + {isPrestoGuided && ( + <> + + {" "} + > + )} + + Success + + + {` - search job ${searchJobId} found `} + + {" "} - found + results + > + )} + {searchUiState === SEARCH_UI_STATE.FAILED && ( + <> + {isPrestoGuided && ( + <> + + {" "} + > + )} + + Failure + + + {` - search job ${searchJobId}`} + + > + )} + {(searchUiState === SEARCH_UI_STATE.DEFAULT) && ( + <> + {" "} - + results + > )} - - results - + {isPrestoGuided && } + > ); }; diff --git a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTimeline/Presto/PrestoResultsTimeline.tsx b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTimeline/Presto/PrestoResultsTimeline.tsx index 447405fd4c..0e20c50276 100644 --- a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTimeline/Presto/PrestoResultsTimeline.tsx +++ b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTimeline/Presto/PrestoResultsTimeline.tsx @@ -13,6 +13,7 @@ import { } from "../../../SearchControls/Presto/presto-guided-search-requests"; import {TIME_RANGE_OPTION} from "../../../SearchControls/TimeRangeInput/utils"; import useSearchStore from "../../../SearchState/index"; +import usePrestoSearchState from "../../../SearchState/Presto"; import {SEARCH_UI_STATE} from "../../../SearchState/typings"; import {computeTimelineConfig} from "../utils"; import {usePrestoAggregationResults} from "./usePrestoAggregationResults"; @@ -37,12 +38,15 @@ const PrestoResultsTimeline = () => { updateTimelineConfig, } = useSearchStore.getState(); + const {updateCachedGuidedSearchQueryString} = usePrestoSearchState.getState(); + // Update range picker selection to match zoomed range. updateTimeRange(newTimeRange); updateTimeRangeOption(TIME_RANGE_OPTION.CUSTOM); updateTimelineConfig(newTimelineConfig); const {searchQueryString, timelineQueryString} = buildPrestoGuidedQueries(newTimeRange); + updateCachedGuidedSearchQueryString(searchQueryString); handlePrestoGuidedQuerySubmit(searchQueryString, timelineQueryString); }, []); diff --git a/components/webui/client/src/pages/SearchPage/SearchState/Presto/index.tsx b/components/webui/client/src/pages/SearchPage/SearchState/Presto/index.tsx index 277d86f883..7b143db05a 100644 --- a/components/webui/client/src/pages/SearchPage/SearchState/Presto/index.tsx +++ b/components/webui/client/src/pages/SearchPage/SearchState/Presto/index.tsx @@ -8,8 +8,12 @@ import {PRESTO_SQL_INTERFACE} from "./typings"; * Default values of the Presto search state. */ const PRESTO_SEARCH_STATE_DEFAULT = Object.freeze({ + cachedGuidedSearchQueryString: "", + errorMsg: null, + errorName: null, from: null, orderBy: "", + queryDrawerOpen: false, select: "*", sqlInterface: PRESTO_SQL_INTERFACE.FREEFORM, timestampKey: null, @@ -17,6 +21,21 @@ const PRESTO_SEARCH_STATE_DEFAULT = Object.freeze({ }); interface PrestoSearchState { + /** + * Last submitted guided search query string. + */ + cachedGuidedSearchQueryString: string; + + /** + * Presto error message if query failed. + */ + errorMsg: Nullable; + + /** + * Presto error name if query failed. + */ + errorName: Nullable; + /** * FROM input. */ @@ -27,6 +46,11 @@ interface PrestoSearchState { */ orderBy: string; + /** + * Whether the query preview drawer is open. + */ + queryDrawerOpen: boolean; + /** * SELECT input. */ @@ -48,8 +72,12 @@ interface PrestoSearchState { where: string; setSqlInterface: (iface: PRESTO_SQL_INTERFACE) => void; + updateCachedGuidedSearchQueryString: (query: string) => void; + updateErrorMsg: (msg: Nullable) => void; + updateErrorName: (name: Nullable) => void; updateFrom: (database: Nullable) => void; updateOrderBy: (items: string) => void; + updateQueryDrawerOpen: (open: boolean) => void; updateSelect: (items: string) => void; updateTimestampKey: (key: Nullable) => void; updateWhere: (expression: string) => void; @@ -60,12 +88,24 @@ const usePrestoSearchState = create((set) => ({ setSqlInterface: (iface) => { set({sqlInterface: iface}); }, + updateCachedGuidedSearchQueryString: (query) => { + set({cachedGuidedSearchQueryString: query}); + }, + updateErrorMsg: (msg) => { + set({errorMsg: msg}); + }, + updateErrorName: (name) => { + set({errorName: name}); + }, updateFrom: (database) => { set({from: database}); }, updateOrderBy: (items) => { set({orderBy: items}); }, + updateQueryDrawerOpen: (open) => { + set({queryDrawerOpen: open}); + }, updateSelect: (items) => { set({select: items}); }, diff --git a/components/webui/client/src/pages/SearchPage/SearchState/useUpdateStateWithMetadata.ts b/components/webui/client/src/pages/SearchPage/SearchState/useUpdateStateWithMetadata.ts index aaf656de2c..fa2a0b2ac6 100644 --- a/components/webui/client/src/pages/SearchPage/SearchState/useUpdateStateWithMetadata.ts +++ b/components/webui/client/src/pages/SearchPage/SearchState/useUpdateStateWithMetadata.ts @@ -7,6 +7,8 @@ import { import {notification} from "antd"; import useSearchStore from "./index"; +import usePrestoSearchState from "./Presto"; +import {PRESTO_SQL_INTERFACE} from "./Presto/typings"; import {SEARCH_UI_STATE} from "./typings"; import {useResultsMetadata} from "./useResultsMetadata"; @@ -24,7 +26,11 @@ const PRESTO_CANCEL_ERROR_NAME = "USER_CANCELED"; * - Updates the number of search results from the metadata. */ const useUpdateStateWithMetadata = () => { - const {updateSearchUiState, updateNumSearchResultsMetadata} = useSearchStore(); + const { + updateNumSearchResultsMetadata, + updateSearchUiState, + } = useSearchStore(); + const {updateErrorMsg, updateErrorName, sqlInterface} = usePrestoSearchState(); const resultsMetadata = useResultsMetadata(); useEffect(() => { @@ -41,32 +47,45 @@ const useUpdateStateWithMetadata = () => { case PRESTO_SEARCH_SIGNAL.DONE: updateSearchUiState(SEARCH_UI_STATE.DONE); break; - case PRESTO_SEARCH_SIGNAL.FAILED: + case PRESTO_SEARCH_SIGNAL.FAILED: { + const errorMsg = resultsMetadata.errorMsg || "An error occurred during search"; + const errorName = resultsMetadata.errorName || "Search Failed"; + + updateErrorMsg(errorMsg); + updateErrorName(errorName); + // Presto reports query cancellation as a failure, but we treat as a successful // completion. if (resultsMetadata.errorName === PRESTO_CANCEL_ERROR_NAME) { updateSearchUiState(SEARCH_UI_STATE.DONE); - break; } updateSearchUiState(SEARCH_UI_STATE.FAILED); - notification.error({ - description: resultsMetadata.errorMsg || "An error occurred during search", - duration: 15, - key: `search-failed-${resultsMetadata._id}`, - message: resultsMetadata.errorName || "Search Failed", - pauseOnHover: true, - placement: "bottomRight", - showProgress: true, - }); + + // Error for guided UI is shown in drawer. + if (sqlInterface === PRESTO_SQL_INTERFACE.FREEFORM) { + notification.error({ + description: errorMsg, + duration: 15, + key: `search-failed-${resultsMetadata._id}`, + message: errorName, + pauseOnHover: true, + placement: "bottomRight", + showProgress: true, + }); + } break; + } default: break; } }, [ resultsMetadata, - updateSearchUiState, updateNumSearchResultsMetadata, + updateErrorMsg, + updateErrorName, + updateSearchUiState, + sqlInterface, ]); }; diff --git a/components/webui/client/src/sql-parser/index.ts b/components/webui/client/src/sql-parser/index.ts index 6f2c19f0c3..33ad6fed60 100644 --- a/components/webui/client/src/sql-parser/index.ts +++ b/components/webui/client/src/sql-parser/index.ts @@ -110,10 +110,10 @@ WHERE to_unixtime(${timestampKey}) BETWEEN ${startTimestamp.unix()} AND ${endTim queryString += ` AND (${booleanExpression})`; } if ("undefined" !== typeof sortItemList) { - queryString += ` ORDER BY ${sortItemList}`; + queryString += `\nORDER BY ${sortItemList}`; } if ("undefined" !== typeof limitValue) { - queryString += ` LIMIT ${limitValue}`; + queryString += `\nLIMIT ${limitValue}`; } try { diff --git a/components/webui/client/src/sql-parser/typings.ts b/components/webui/client/src/sql-parser/typings.ts index 5419b2150c..2943cf4626 100644 --- a/components/webui/client/src/sql-parser/typings.ts +++ b/components/webui/client/src/sql-parser/typings.ts @@ -21,6 +21,7 @@ interface BuildTimelineQueryProps { timestampKey: string; } + export type { BuildSearchQueryProps, BuildTimelineQueryProps,