diff --git a/components/webui/client/src/components/QueryBox/index.module.css b/components/webui/client/src/components/QueryBox/index.module.css
index db8f244373..011a233fff 100644
--- a/components/webui/client/src/components/QueryBox/index.module.css
+++ b/components/webui/client/src/components/QueryBox/index.module.css
@@ -1,6 +1,7 @@
.queryBox {
+ flex: 1;
+ min-width: 100px;
position: relative;
- width: 100%;
}
.progressBarMask {
diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/Dataset/index.module.css b/components/webui/client/src/pages/SearchPage/SearchControls/Dataset/index.module.css
index 110f752160..20273aa92a 100644
--- a/components/webui/client/src/pages/SearchPage/SearchControls/Dataset/index.module.css
+++ b/components/webui/client/src/pages/SearchPage/SearchControls/Dataset/index.module.css
@@ -1,5 +1,6 @@
.datasetContainer {
display: flex;
+ max-width: 250px;
}
.selectContainer {
diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/MaxResultsSelect/index.module.css b/components/webui/client/src/pages/SearchPage/SearchControls/MaxResultsSelect/index.module.css
new file mode 100644
index 0000000000..e5798ca767
--- /dev/null
+++ b/components/webui/client/src/pages/SearchPage/SearchControls/MaxResultsSelect/index.module.css
@@ -0,0 +1,13 @@
+.maxResultsContainer {
+ display: flex;
+}
+
+.maxResultsContainer :global(.ant-select) {
+ min-width: 80px;
+ max-width: 80px;
+}
+
+.maxResultsContainer :global(.ant-select-selector) {
+ border-top-left-radius: 0 !important;
+ border-bottom-left-radius: 0 !important;
+}
diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/MaxResultsSelect/index.tsx b/components/webui/client/src/pages/SearchPage/SearchControls/MaxResultsSelect/index.tsx
new file mode 100644
index 0000000000..e59d5406ab
--- /dev/null
+++ b/components/webui/client/src/pages/SearchPage/SearchControls/MaxResultsSelect/index.tsx
@@ -0,0 +1,46 @@
+import {Select} from "antd";
+
+import InputLabel from "../../../../components/InputLabel";
+import useSearchStore from "../../SearchState";
+import {
+ MAX_RESULTS_OPTIONS,
+ SEARCH_UI_STATE,
+} from "../../SearchState/typings";
+import styles from "./index.module.css";
+
+
+/**
+ * Renders a dropdown to select the maximum number of search results.
+ *
+ * @return
+ */
+const MaxResultsSelect = () => {
+ const maxNumResults = useSearchStore((state) => state.maxNumResults);
+ const updateMaxNumResults = useSearchStore((state) => state.updateMaxNumResults);
+ const searchUiState = useSearchStore((state) => state.searchUiState);
+
+ const isDisabled = searchUiState === SEARCH_UI_STATE.QUERY_ID_PENDING ||
+ searchUiState === SEARCH_UI_STATE.QUERYING;
+
+ const handleChange = (value: number) => {
+ updateMaxNumResults(value);
+ };
+
+ return (
+
+ Limit
+
+ );
+};
+
+export default MaxResultsSelect;
diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/Native/NativeControls.tsx b/components/webui/client/src/pages/SearchPage/SearchControls/Native/NativeControls.tsx
index d7a1116699..3feab9bec4 100644
--- a/components/webui/client/src/pages/SearchPage/SearchControls/Native/NativeControls.tsx
+++ b/components/webui/client/src/pages/SearchPage/SearchControls/Native/NativeControls.tsx
@@ -3,6 +3,7 @@ import {CLP_STORAGE_ENGINES} from "@webui/common/config";
import {SETTINGS_STORAGE_ENGINE} from "../../../../config";
import Dataset from "../Dataset";
import styles from "../index.module.css";
+import MaxResultsSelect from "../MaxResultsSelect";
import QueryStatus from "../QueryStatus";
import TimeRangeInput from "../TimeRangeInput";
import QueryInput from "./QueryInput";
@@ -20,6 +21,7 @@ const NativeControls = () => (
{CLP_STORAGE_ENGINES.CLP_S === SETTINGS_STORAGE_ENGINE && }
+
diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/Native/SearchButton/SubmitButton/index.tsx b/components/webui/client/src/pages/SearchPage/SearchControls/Native/SearchButton/SubmitButton/index.tsx
index 2423ec580b..a0ad57ed06 100644
--- a/components/webui/client/src/pages/SearchPage/SearchControls/Native/SearchButton/SubmitButton/index.tsx
+++ b/components/webui/client/src/pages/SearchPage/SearchControls/Native/SearchButton/SubmitButton/index.tsx
@@ -36,6 +36,7 @@ const SubmitButton = () => {
);
const queryString = useSearchStore((state) => state.queryString);
const selectedDatasets = useSearchStore((state) => state.selectedDatasets);
+ const maxNumResults = useSearchStore((state) => state.maxNumResults);
const updateQueriedDatasets = useSearchStore((state) => state.updateQueriedDatasets);
const [messageApi, contextHolder] = message.useMessage();
@@ -79,6 +80,7 @@ const SubmitButton = () => {
handleQuerySubmit({
datasets: selectedDatasets,
ignoreCase: false === queryIsCaseSensitive,
+ maxNumResults: maxNumResults,
queryString: queryString,
timeRangeBucketSizeMillis: newTimelineConfig.bucketDuration.asMilliseconds(),
...(
@@ -102,6 +104,7 @@ const SubmitButton = () => {
messageApi,
selectedDatasets,
updateQueriedDatasets,
+ maxNumResults,
]);
const isQueryStringEmpty = "" === queryString;
diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/TimeRangeInput/index.module.css b/components/webui/client/src/pages/SearchPage/SearchControls/TimeRangeInput/index.module.css
index ef97f5c683..de07b1f669 100644
--- a/components/webui/client/src/pages/SearchPage/SearchControls/TimeRangeInput/index.module.css
+++ b/components/webui/client/src/pages/SearchPage/SearchControls/TimeRangeInput/index.module.css
@@ -4,5 +4,6 @@
.rangePicker {
min-width: 350px;
+ max-width: 350px;
}
diff --git a/components/webui/client/src/pages/SearchPage/SearchControls/index.module.css b/components/webui/client/src/pages/SearchPage/SearchControls/index.module.css
index 02a8e65689..741d665186 100644
--- a/components/webui/client/src/pages/SearchPage/SearchControls/index.module.css
+++ b/components/webui/client/src/pages/SearchPage/SearchControls/index.module.css
@@ -6,6 +6,7 @@
.inputsAndButtonRow {
display: flex;
+ flex-wrap: wrap;
gap: 10px;
}
@@ -15,6 +16,7 @@
.runRow {
display: flex;
+ flex-wrap: wrap;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
diff --git a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/Native/SearchResultsVirtualTable/useSearchResults.ts b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/Native/SearchResultsVirtualTable/useSearchResults.ts
index cfd88155e5..84e4167751 100644
--- a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/Native/SearchResultsVirtualTable/useSearchResults.ts
+++ b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTable/Native/SearchResultsVirtualTable/useSearchResults.ts
@@ -1,7 +1,6 @@
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";
@@ -11,7 +10,7 @@ import {SearchResult} from "./typings";
* @return
*/
const useSearchResults = () => {
- const {searchJobId} = useSearchStore();
+ const {searchJobId, maxNumResults} = useSearchStore();
const searchResultsCursor = useCursor(
() => {
@@ -25,7 +24,6 @@ const useSearchResults = () => {
`Subscribing to updates to search results with job ID: ${searchJobId}`
);
- // Retrieve 1k most recent results.
const options = {
sort: [
[
@@ -37,13 +35,16 @@ const useSearchResults = () => {
"desc",
],
],
- limit: SEARCH_MAX_NUM_RESULTS,
+ limit: maxNumResults,
};
const collection = new MongoSocketCollection(searchJobId);
return collection.find({}, options);
},
- [searchJobId]
+ [
+ searchJobId,
+ maxNumResults,
+ ]
);
return searchResultsCursor;
diff --git a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTimeline/Native/NativeResultsTimeline.tsx b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTimeline/Native/NativeResultsTimeline.tsx
index e1a5a3c7c2..86faa41876 100644
--- a/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTimeline/Native/NativeResultsTimeline.tsx
+++ b/components/webui/client/src/pages/SearchPage/SearchResults/SearchResultsTimeline/Native/NativeResultsTimeline.tsx
@@ -28,6 +28,7 @@ const NativeResultsTimeline = () => {
const searchUiState = useSearchStore((state) => state.searchUiState);
const selectedDatasets = useSearchStore((state) => state.selectedDatasets);
const timelineConfig = useSearchStore((state) => state.timelineConfig);
+ const maxNumResults = useSearchStore((state) => state.maxNumResults);
const aggregationResults = useAggregationResults();
@@ -63,12 +64,14 @@ const NativeResultsTimeline = () => {
handleQuerySubmit({
datasets: selectedDatasets,
ignoreCase: false === queryIsCaseSensitive,
+ maxNumResults: maxNumResults,
queryString: queryString,
timeRangeBucketSizeMillis: newTimelineConfig.bucketDuration.asMilliseconds(),
timestampBegin: newTimeRange[0].valueOf(),
timestampEnd: newTimeRange[1].valueOf(),
});
}, [
+ maxNumResults,
queryIsCaseSensitive,
queryString,
selectedDatasets,
diff --git a/components/webui/client/src/pages/SearchPage/SearchState/index.tsx b/components/webui/client/src/pages/SearchPage/SearchState/index.tsx
index fc6ac6cb2f..8240af0696 100644
--- a/components/webui/client/src/pages/SearchPage/SearchState/index.tsx
+++ b/components/webui/client/src/pages/SearchPage/SearchState/index.tsx
@@ -15,7 +15,10 @@ import {
} from "../SearchResults/SearchResultsTable/Native/SearchResultsVirtualTable/typings";
import {formatExportFilenameTimestamp} from "../SearchResults/SearchResultsTable/Native/utils";
import {computeTimelineConfig} from "../SearchResults/SearchResultsTimeline/utils";
-import {SEARCH_UI_STATE} from "./typings";
+import {
+ DEFAULT_MAX_NUM_RESULTS,
+ SEARCH_UI_STATE,
+} from "./typings";
/**
@@ -23,6 +26,7 @@ import {SEARCH_UI_STATE} from "./typings";
*/
const SEARCH_STATE_DEFAULT = Object.freeze({
aggregationJobId: null,
+ maxNumResults: DEFAULT_MAX_NUM_RESULTS,
numSearchResultsMetadata: 0,
numSearchResultsTable: 0,
numSearchResultsTimeline: 0,
@@ -44,6 +48,11 @@ interface SearchState {
*/
aggregationJobId: string | null;
+ /**
+ * Maximum number of search results to retrieve.
+ */
+ maxNumResults: number;
+
/**
* The number of search results from server metadata.
*/
@@ -121,6 +130,7 @@ interface SearchState {
handleSearchResultsExport: () => void;
updateAggregationJobId: (id: string | null) => void;
+ updateMaxNumResults: (max: number) => void;
updateNumSearchResultsMetadata: (num: number) => void;
updateNumSearchResultsTable: (num: number) => void;
updateNumSearchResultsTimeline: (num: number) => void;
@@ -159,6 +169,9 @@ const useSearchStore = create((set, get) => ({
updateAggregationJobId: (id) => {
set({aggregationJobId: id});
},
+ updateMaxNumResults: (max) => {
+ set({maxNumResults: max});
+ },
updateNumSearchResultsMetadata: (num) => {
set({numSearchResultsMetadata: num});
},
diff --git a/components/webui/client/src/pages/SearchPage/SearchState/typings.ts b/components/webui/client/src/pages/SearchPage/SearchState/typings.ts
index 6d5d014c9d..8b9477ea95 100644
--- a/components/webui/client/src/pages/SearchPage/SearchState/typings.ts
+++ b/components/webui/client/src/pages/SearchPage/SearchState/typings.ts
@@ -1,3 +1,23 @@
+/**
+ * Available options for the maximum number of search results.
+ */
+/* eslint-disable no-magic-numbers */
+const MAX_RESULTS_OPTIONS = [
+ 10,
+ 50,
+ 100,
+ 500,
+ 1000,
+ 5000,
+ 10000,
+] as const;
+/* eslint-enable no-magic-numbers */
+
+/**
+ * Default value for the maximum number of search results.
+ */
+const DEFAULT_MAX_NUM_RESULTS: number = 1000;
+
/**
* Search UI states.
*/
@@ -28,4 +48,8 @@ enum SEARCH_UI_STATE {
FAILED,
}
-export {SEARCH_UI_STATE};
+export {
+ DEFAULT_MAX_NUM_RESULTS,
+ MAX_RESULTS_OPTIONS,
+ SEARCH_UI_STATE,
+};
diff --git a/components/webui/common/src/schemas/search.ts b/components/webui/common/src/schemas/search.ts
index 4434b1762d..28ecb523cb 100644
--- a/components/webui/common/src/schemas/search.ts
+++ b/components/webui/common/src/schemas/search.ts
@@ -15,6 +15,7 @@ import {
const QueryJobCreationSchema = Type.Object({
datasets: Type.Array(Type.String()),
ignoreCase: Type.Boolean(),
+ maxNumResults: Type.Optional(Type.Integer({minimum: 1})),
queryString: StringSchema,
timeRangeBucketSizeMillis: Type.Integer(),
timestampBegin: Type.Union([Type.Null(),
diff --git a/components/webui/server/src/routes/api/search/index.ts b/components/webui/server/src/routes/api/search/index.ts
index 445b9992a6..ced108b15d 100644
--- a/components/webui/server/src/routes/api/search/index.ts
+++ b/components/webui/server/src/routes/api/search/index.ts
@@ -16,7 +16,7 @@ import {
import {constants} from "http2";
import settings from "../../../../settings.json" with {type: "json"};
-import {SEARCH_MAX_NUM_RESULTS} from "./typings.js";
+import {DEFAULT_SEARCH_MAX_NUM_RESULTS} from "./typings.js";
import {
createMongoIndexes,
updateSearchSignalWhenJobsFinish,
@@ -68,6 +68,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
timestampBegin,
timestampEnd,
ignoreCase,
+ maxNumResults,
timeRangeBucketSizeMillis,
queryString,
} = request.body;
@@ -79,7 +80,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
null,
end_timestamp: timestampEnd,
ignore_case: ignoreCase,
- max_num_results: SEARCH_MAX_NUM_RESULTS,
+ max_num_results: maxNumResults ?? DEFAULT_SEARCH_MAX_NUM_RESULTS,
query_string: queryString,
};
@@ -126,6 +127,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
updateSearchSignalWhenJobsFinish({
aggregationJobId: aggregationJobId,
logger: request.log,
+ maxNumResults: maxNumResults ?? DEFAULT_SEARCH_MAX_NUM_RESULTS,
mongoDb: mongoDb,
queryJobDbManager: QueryJobDbManager,
searchJobId: searchJobId,
diff --git a/components/webui/server/src/routes/api/search/typings.ts b/components/webui/server/src/routes/api/search/typings.ts
index 86ddbd4ac6..2824c3e78f 100644
--- a/components/webui/server/src/routes/api/search/typings.ts
+++ b/components/webui/server/src/routes/api/search/typings.ts
@@ -12,11 +12,12 @@ import type {
/**
* The maximum number of results to retrieve for a search.
*/
-const SEARCH_MAX_NUM_RESULTS = 1000;
+const DEFAULT_SEARCH_MAX_NUM_RESULTS = 1000;
type UpdateSearchSignalWhenJobsFinishProps = {
aggregationJobId: number;
logger: FastifyBaseLogger;
+ maxNumResults: number;
mongoDb: Db;
queryJobDbManager: FastifyInstance["QueryJobDbManager"];
searchJobId: number;
@@ -32,7 +33,6 @@ type CreateMongoIndexesProps = {
export {
CreateMongoIndexesProps,
- SEARCH_MAX_NUM_RESULTS,
- SearchResultsMetadataDocument,
+ DEFAULT_SEARCH_MAX_NUM_RESULTS,
UpdateSearchSignalWhenJobsFinishProps,
};
diff --git a/components/webui/server/src/routes/api/search/utils.ts b/components/webui/server/src/routes/api/search/utils.ts
index a37bf0e19c..f95c1920d6 100644
--- a/components/webui/server/src/routes/api/search/utils.ts
+++ b/components/webui/server/src/routes/api/search/utils.ts
@@ -3,7 +3,6 @@ import type {Db} from "mongodb";
import {
CreateMongoIndexesProps,
- SEARCH_MAX_NUM_RESULTS,
UpdateSearchSignalWhenJobsFinishProps,
} from "./typings.js";
@@ -26,14 +25,16 @@ const hasCollection = async (mongoDb: Db, collectionName: string): Promise