diff --git a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/Presto/PrestoResultsVirtualTable/index.tsx b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/Presto/PrestoResultsVirtualTable/index.tsx new file mode 100644 index 0000000000..e7455674f0 --- /dev/null +++ b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/Presto/PrestoResultsVirtualTable/index.tsx @@ -0,0 +1,54 @@ +import { + useEffect, + useMemo, +} from "react"; + +import type {PrestoSearchResult} from "../../../../../../../../common/index.js"; +import VirtualTable from "../../../../../../components/VirtualTable"; +import useSearchStore from "../../../../SearchState/index"; +import {usePrestoSearchResults} from "./usePrestoSearchResults"; +import {getPrestoSearchResultsTableColumns} from "./utils"; + + +interface PrestoResultsVirtualTableProps { + tableHeight: number; +} + +/** + * Renders Presto search results in a virtual table. + * + * @param props + * @param props.tableHeight + * @return + */ +const PrestoResultsVirtualTable = ({tableHeight}: PrestoResultsVirtualTableProps) => { + const {updateNumSearchResultsTable} = useSearchStore(); + const prestoSearchResults = usePrestoSearchResults(); + + const columns = useMemo( + () => getPrestoSearchResultsTableColumns(prestoSearchResults || []), + [prestoSearchResults] + ); + + useEffect(() => { + const num = prestoSearchResults ? + prestoSearchResults.length : + 0; + + updateNumSearchResultsTable(num); + }, [ + prestoSearchResults, + updateNumSearchResultsTable, + ]); + + return ( + + columns={columns} + dataSource={prestoSearchResults || []} + pagination={false} + rowKey={(record) => record._id} + scroll={{y: tableHeight}}/> + ); +}; + +export default PrestoResultsVirtualTable; diff --git a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/Presto/PrestoResultsVirtualTable/usePrestoSearchResults.ts b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/Presto/PrestoResultsVirtualTable/usePrestoSearchResults.ts new file mode 100644 index 0000000000..0e43b1d242 --- /dev/null +++ b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/Presto/PrestoResultsVirtualTable/usePrestoSearchResults.ts @@ -0,0 +1,48 @@ +import type {PrestoSearchResult} from "../../../../../../../../common/index.js"; +import MongoSocketCollection from "../../../../../../api/socket/MongoSocketCollection"; +import {useCursor} from "../../../../../../api/socket/useCursor"; +import useSearchStore, {SEARCH_STATE_DEFAULT} from "../../../../SearchState/index"; +import {SEARCH_MAX_NUM_RESULTS} from "../../typings"; + + +/** + * Custom hook to get Presto search results for the current searchJobId. + * + * @return + */ +const usePrestoSearchResults = () => { + const searchJobId = useSearchStore((state) => state.searchJobId); + + const searchResultsCursor = useCursor( + () => { + // If there is no active search job, there are no results to fetch. The cursor will + // return null. + if (searchJobId === SEARCH_STATE_DEFAULT.searchJobId) { + return null; + } + + console.log( + `Subscribing to updates to Presto search results with job ID: ${searchJobId}` + ); + + // Retrieve 1k most recent results. + const options = { + sort: [ + [ + "_id", + "desc", + ], + ], + limit: SEARCH_MAX_NUM_RESULTS, + }; + + const collection = new MongoSocketCollection(searchJobId); + return collection.find({}, options); + }, + [searchJobId] + ); + + return searchResultsCursor; +}; + +export {usePrestoSearchResults}; diff --git a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/Presto/PrestoResultsVirtualTable/utils.ts b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/Presto/PrestoResultsVirtualTable/utils.ts new file mode 100644 index 0000000000..5ca3b2cfb6 --- /dev/null +++ b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/Presto/PrestoResultsVirtualTable/utils.ts @@ -0,0 +1,33 @@ +import {TableProps} from "antd"; + +import type {PrestoSearchResult} from "../../../../../../../../common/index.js"; + + +/** + * Generates dynamic columns configuration for Presto query engine. + * + * @param data Array of Presto search results + * @return + */ +const getPrestoSearchResultsTableColumns = ( + data: PrestoSearchResult[] +): NonNullable["columns"]> => { + if (0 === data.length || + "undefined" === typeof data[0] || + "undefined" === typeof data[0].row + ) { + return []; + } + + return Object.keys(data[0].row) + .map((key) => ({ + dataIndex: [ + "row", + key, + ], + key: key, + title: key, + })); +}; + +export {getPrestoSearchResultsTableColumns}; diff --git a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/SearchResultsVirtualTable/index.tsx b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/SearchResultsVirtualTable/index.tsx new file mode 100644 index 0000000000..e0e4e97f26 --- /dev/null +++ b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/SearchResultsVirtualTable/index.tsx @@ -0,0 +1,48 @@ +import {useEffect} from "react"; + +import VirtualTable from "../../../../../components/VirtualTable"; +import useSearchStore from "../../../SearchState/index"; +import { + SearchResult, + searchResultsTableColumns, +} from "./typings"; +import {useSearchResults} from "./useSearchResults"; + + +interface SearchResultsVirtualTableProps { + tableHeight: number; +} + +/** + * Renders search results in a virtual table. + * + * @param props + * @param props.tableHeight + * @return + */ +const SearchResultsVirtualTable = ({tableHeight}: SearchResultsVirtualTableProps) => { + const {updateNumSearchResultsTable} = useSearchStore(); + const searchResults = useSearchResults(); + + useEffect(() => { + const num = searchResults ? + searchResults.length : + 0; + + updateNumSearchResultsTable(num); + }, [ + searchResults, + updateNumSearchResultsTable, + ]); + + return ( + + columns={searchResultsTableColumns} + dataSource={searchResults || []} + pagination={false} + rowKey={(record) => record._id.toString()} + scroll={{y: tableHeight}}/> + ); +}; + +export default SearchResultsVirtualTable; diff --git a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/typings.tsx b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/SearchResultsVirtualTable/typings.tsx similarity index 76% rename from components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/typings.tsx rename to components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/SearchResultsVirtualTable/typings.tsx index 495d3e050a..1ee1971a50 100644 --- a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/typings.tsx +++ b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/SearchResultsVirtualTable/typings.tsx @@ -1,13 +1,13 @@ import {TableProps} from "antd"; import dayjs from "dayjs"; -import {DATETIME_FORMAT_TEMPLATE} from "../../../../typings/datetime"; import { CLP_STORAGE_ENGINES, SETTINGS_STORAGE_ENGINE, -} from ".././../../../config"; -import Message from "./Message"; -import {getStreamId} from "./utils"; +} from "../../../../../config"; +import {DATETIME_FORMAT_TEMPLATE} from "../../../../../typings/datetime"; +import Message from "../Message"; +import {getStreamId} from "../utils"; /** @@ -25,7 +25,7 @@ interface SearchResult { } /** - * Columns configuration for the seach results table. + * Columns configuration for the search results table. */ const searchResultsTableColumns: NonNullable["columns"]> = [ { @@ -70,20 +70,5 @@ const searchResultsTableColumns: NonNullable["columns"] }, ]; -/** - * Padding for the table to the bottom of the page. - */ -const TABLE_BOTTOM_PADDING = 75; - -/** - * The maximum number of results to retrieve for a search. - */ -const SEARCH_MAX_NUM_RESULTS = 1000; - - export type {SearchResult}; -export { - SEARCH_MAX_NUM_RESULTS, - searchResultsTableColumns, - TABLE_BOTTOM_PADDING, -}; +export {searchResultsTableColumns}; diff --git a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/useSearchResults.ts b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/SearchResultsVirtualTable/useSearchResults.ts similarity index 79% rename from components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/useSearchResults.ts rename to components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/SearchResultsVirtualTable/useSearchResults.ts index e148ee91a5..69375c2e23 100644 --- a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/useSearchResults.ts +++ b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/SearchResultsVirtualTable/useSearchResults.ts @@ -1,10 +1,8 @@ -import MongoSocketCollection from "../../../../api/socket/MongoSocketCollection"; -import {useCursor} from "../../../../api/socket/useCursor"; -import useSearchStore, {SEARCH_STATE_DEFAULT} from "../../SearchState/index"; -import { - SEARCH_MAX_NUM_RESULTS, - SearchResult, -} from "./typings"; +import MongoSocketCollection from "../../../../../api/socket/MongoSocketCollection"; +import {useCursor} from "../../../../../api/socket/useCursor"; +import useSearchStore, {SEARCH_STATE_DEFAULT} from "../../../SearchState/index"; +import {SEARCH_MAX_NUM_RESULTS} from "../typings"; +import {SearchResult} from "./typings"; /** diff --git a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/index.tsx b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/index.tsx index ffc81b0c50..5cdfcf6841 100644 --- a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/index.tsx +++ b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/index.tsx @@ -4,14 +4,13 @@ import { useState, } from "react"; -import VirtualTable from "../../../../components/VirtualTable"; -import useSearchStore from "../../SearchState/index"; import { - SearchResult, - searchResultsTableColumns, - TABLE_BOTTOM_PADDING, -} from "./typings"; -import {useSearchResults} from "./useSearchResults"; + CLP_QUERY_ENGINES, + SETTINGS_QUERY_ENGINE, +} from "../../../../config"; +import PrestoResultsVirtualTable from "./Presto/PrestoResultsVirtualTable"; +import SearchResultsVirtualTable from "./SearchResultsVirtualTable"; +import {TABLE_BOTTOM_PADDING} from "./typings"; /** @@ -20,22 +19,9 @@ import {useSearchResults} from "./useSearchResults"; * @return */ const SearchResultsTable = () => { - const {updateNumSearchResultsTable} = useSearchStore(); - const searchResults = useSearchResults(); const [tableHeight, setTableHeight] = useState(0); const containerRef = useRef(null); - useEffect(() => { - const num = searchResults ? - searchResults.length : - 0; - - updateNumSearchResultsTable(num); - }, [ - searchResults, - updateNumSearchResultsTable, - ]); - // Antd table requires a fixed height for virtual scrolling. The effect sets a fixed height // based on the window height, container top, and fixed padding. useEffect(() => { @@ -60,12 +46,13 @@ const SearchResultsTable = () => { ref={containerRef} style={{outline: "none"}} > - - columns={searchResultsTableColumns} - dataSource={searchResults || []} - pagination={false} - rowKey={(record) => record._id.toString()} - scroll={{y: tableHeight}}/> + {CLP_QUERY_ENGINES.PRESTO === SETTINGS_QUERY_ENGINE ? + ( + + ) : + ( + + )} ); }; diff --git a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/typings.ts b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/typings.ts new file mode 100644 index 0000000000..f23975edfc --- /dev/null +++ b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/typings.ts @@ -0,0 +1,14 @@ +/** + * Padding for the table to the bottom of the page. + */ +const TABLE_BOTTOM_PADDING = 75; + +/** + * The maximum number of results to retrieve for a search. + */ +const SEARCH_MAX_NUM_RESULTS = 1000; + +export { + SEARCH_MAX_NUM_RESULTS, + TABLE_BOTTOM_PADDING, +}; diff --git a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/utils.ts b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/utils.ts index 071fb42800..3aa3cf6072 100644 --- a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/utils.ts +++ b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/utils.ts @@ -2,7 +2,7 @@ import { CLP_STORAGE_ENGINES, SETTINGS_STORAGE_ENGINE, } from "../../../../config"; -import type {SearchResult} from "./typings"; +import type {SearchResult} from "./SearchResultsVirtualTable/typings"; /** diff --git a/components/webui/common/index.ts b/components/webui/common/index.ts index b891cf00b0..c6efc11857 100644 --- a/components/webui/common/index.ts +++ b/components/webui/common/index.ts @@ -129,12 +129,29 @@ interface SearchResultsMetadataDocument { numTotalResults?: number; queryEngine: CLP_QUERY_ENGINES; } + +/** + * Presto row wrapped in a `row` property to prevent conflicts with MongoDB's `_id` field. + */ +interface PrestoRowObject { + row: Record; +} + +/** + * Presto search result in MongoDB. + */ +interface PrestoSearchResult extends PrestoRowObject { + _id: string; +} + export { CLP_QUERY_ENGINES, PRESTO_SEARCH_SIGNAL, SEARCH_SIGNAL, }; export type { + PrestoRowObject, + PrestoSearchResult, SearchResultsMetadataDocument, ClientToServerEvents, Err, diff --git a/components/webui/server/src/plugins/app/Presto.ts b/components/webui/server/src/plugins/app/Presto.ts index dd6ccf31be..5aea097c69 100644 --- a/components/webui/server/src/plugins/app/Presto.ts +++ b/components/webui/server/src/plugins/app/Presto.ts @@ -4,6 +4,7 @@ import { ClientOptions, } from "presto-client"; +import {CLP_QUERY_ENGINES} from "../../../../common/index.js"; import settings from "../../../settings.json" with {type: "json"}; @@ -29,7 +30,7 @@ declare module "fastify" { export default fp( (fastify) => { - if ("presto" !== settings.ClpQueryEngine) { + if (CLP_QUERY_ENGINES.PRESTO !== settings.ClpQueryEngine as CLP_QUERY_ENGINES) { return; } @@ -40,7 +41,7 @@ export default fp( fastify.log.info( clientOptions, - "Initializing Presto" + "Initializing Presto client" ); fastify.decorate("Presto", new Presto(clientOptions)); }, diff --git a/components/webui/server/src/routes/api/presto-search/index.ts b/components/webui/server/src/routes/api/presto-search/index.ts index 5870c192d7..56a90e2e80 100644 --- a/components/webui/server/src/routes/api/presto-search/index.ts +++ b/components/webui/server/src/routes/api/presto-search/index.ts @@ -168,6 +168,8 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => { throw error; } + await mongoDb.createCollection(searchJobId); + reply.code(StatusCodes.CREATED); return {searchJobId}; diff --git a/components/webui/server/src/routes/api/presto-search/utils.ts b/components/webui/server/src/routes/api/presto-search/utils.ts index 4574a0aff8..33681f32a9 100644 --- a/components/webui/server/src/routes/api/presto-search/utils.ts +++ b/components/webui/server/src/routes/api/presto-search/utils.ts @@ -3,6 +3,8 @@ import type { InsertManyResult, } from "mongodb"; +import type {PrestoRowObject} from "../../../../../common/index.js"; + /** * Converts a Presto result row (array of values) into an object, using the provided column @@ -15,13 +17,14 @@ import type { const prestoRowToObject = ( row: unknown[], columns: {name: string}[] -): Record => { +): PrestoRowObject => { const obj: Record = {}; columns.forEach((col, idx) => { obj[col.name] = row[idx]; }); - return obj; + // Object is wrapped in a `row` property to prevent conflicts with MongoDB's `_id` field. + return {row: obj}; }; /**